Merge pull request #22 from brendandouglas/master

Project import generated by Copybara.
diff --git a/.gitignore b/.gitignore
index ac51a05..6d8ad95 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1 @@
-bazel-*
+bazel-*
\ No newline at end of file
diff --git a/BUILD b/BUILD
index ca39524..9d98461 100644
--- a/BUILD
+++ b/BUILD
@@ -2,15 +2,17 @@
 # Description: Blaze plugin for various IntelliJ products.
 #
 
+licenses(["notice"])  # Apache 2.0
+
 # IJwB tests, run with an IntelliJ plugin SDK
 test_suite(
     name = "ijwb_tests",
     tests = [
-        "//blaze-base:integration_tests",
-        "//blaze-base:unit_tests",
-        "//blaze-java:integration_tests",
-        "//blaze-java:unit_tests",
-        "//blaze-plugin-dev:integration_tests",
+        "//base:integration_tests",
+        "//base:unit_tests",
+        "//java:integration_tests",
+        "//java:unit_tests",
+        "//plugin_dev:integration_tests",
     ],
 )
 
@@ -19,15 +21,29 @@
     name = "aswb_tests",
     tests = [
         "//aswb:unit_tests",
-        "//blaze-base:unit_tests",
-        "//blaze-java:unit_tests",
+        "//base:unit_tests",
+        "//java:unit_tests",
     ],
 )
 
-# Version file
-filegroup(
-    name = "version",
-    srcs = ["VERSION"],
-    visibility = ["//visibility:public"],
+# CLwB tests, run with a CLion plugin SDK
+test_suite(
+    name = "clwb_tests",
+    tests = [
+        "//base:unit_tests",
+    ],
 )
 
+load(
+    ":version.bzl",
+    "VERSION",
+)
+
+# Version file
+genrule(
+    name = "version",
+    srcs = [],
+    outs = ["VERSION"],
+    cmd = "echo '%s' > $@" % VERSION,
+    visibility = ["//visibility:public"],
+)
diff --git a/README.md b/README.md
index f6f2a87..0fc36c1 100644
--- a/README.md
+++ b/README.md
@@ -25,4 +25,4 @@
 
 Install Bazel, then run 'bazel build //ijwb:ijwb_bazel' from
 the project root. This will create a plugin jar in
-'bazel-genfiles/ijwb/ijwb_bazel.jar'.
+'bazel-genfiles/ijwb/ijwb_bazel.jar'.
\ No newline at end of file
diff --git a/VERSION b/VERSION
deleted file mode 100644
index 2b26b8d..0000000
--- a/VERSION
+++ /dev/null
@@ -1 +0,0 @@
-1.5.9
diff --git a/WORKSPACE b/WORKSPACE
index e9e2b96..f443141 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -4,25 +4,64 @@
 # and run integration tests.
 new_http_archive(
     name = "intellij_latest",
-    build_file = "remote_platform_sdks/BUILD.idea",
-    sha256 = "d1cd3f9fd650c00ba85181da6d66b4b80b8e48ce5f4f15b5f4dc67453e96a179",
-    url = "https://download.jetbrains.com/idea/ideaIC-2016.1.3.tar.gz",
+    build_file = "intellij_platform_sdk/BUILD.idea",
+    url = "https://download.jetbrains.com/idea/ideaIC-2016.2.1.tar.gz",
 )
 
 # The plugin api for CLion 2016.1.3. This is required to build CLwB,
 # and run integration tests.
 new_http_archive(
     name = "clion_latest",
-    build_file = "remote_platform_sdks/BUILD.clion",
-    sha256 = "470063f1bb65ba03c6e1aba354cb81e2c04bd280d9b8da98622be1ba6b0a9c88",
-    url = "https://download.jetbrains.com/cpp/CLion-2016.1.3.tar.gz",
+    build_file = "intellij_platform_sdk/BUILD.clion",
+    url = "https://download.jetbrains.com/cpp/CLion-2016.2.1.tar.gz",
 )
 
-# The plugin api for Android Studio 2.2. preview 4. This is required to build ASwB,
+# The plugin api for Android Studio 2.2 stable. This is required to build ASwB,
 # and run integration tests.
 new_http_archive(
     name = "android_studio_latest",
-    build_file = "remote_platform_sdks/BUILD.android_studio",
-    sha256 = "530b630914b42f9ad9f5442a36b421214838443429a4a1b96194d45a5d586f17",
-    url = "https://dl.google.com/dl/android/studio/ide-zips/2.2.0.3/android-studio-ide-145.3001415-linux.zip",
+    build_file = "intellij_platform_sdk/BUILD.android_studio",
+    url = "https://dl.google.com/dl/android/studio/ide-zips/2.2.0.12/android-studio-ide-145.3276617-linux.zip",
+)
+
+# LICENSE: Common Public License 1.0
+maven_jar(
+    name = "junit",
+    artifact = "junit:junit:4.11",
+    sha1 = "4e031bb61df09069aeb2bffb4019e7a5034a4ee0",
+)
+
+# LICENSE: The Apache Software License, Version 2.0
+maven_jar(
+    name = "jsr305_annotations",
+    artifact = "com.google.code.findbugs:jsr305:3.0.1",
+    sha1 = "f7be08ec23c21485b9b5a1cf1654c2ec8c58168d",
+)
+
+# LICENSE: The Apache Software License, Version 2.0
+maven_jar(
+    name = "truth",
+    artifact = "com.google.truth:truth:0.30",
+    sha1 = "9d591b5a66eda81f0b88cf1c748ab8853d99b18b",
+)
+
+# LICENSE: The Apache Software License, Version 2.0
+maven_jar(
+    name = "mockito",
+    artifact = "org.mockito:mockito-all:1.9.5",
+    sha1 = "79a8984096fc6591c1e3690e07d41be506356fa5",
+)
+
+# LICENSE: The Apache Software License, Version 2.0
+maven_jar(
+    name = "objenesis",
+    artifact = "org.objenesis:objenesis:1.3",
+    sha1 = "dc13ae4faca6df981fc7aeb5a522d9db446d5d50",
+)
+
+# LICENSE: The Apache Software License, Version 2.0
+maven_jar(
+    name = "jarjar",
+    artifact = "com.googlecode.jarjar:jarjar:1.3",
+    sha1 = "b81c2719c63fa8e6f3eca5b11b8e9b5ad79463db",
 )
diff --git a/aswb/.bazelproject b/aswb/.bazelproject
index 3cbf8dc..2fdb3b8 100644
--- a/aswb/.bazelproject
+++ b/aswb/.bazelproject
@@ -3,10 +3,11 @@
   -ijwb
   -blaze-plugin-dev
   -clwb
-  -blaze-cpp
+  -blaze-cpp/src/com/google/idea/blaze/cpp/versioned/v162
 
 targets:
   //aswb:aswb_bazel
+  //aswb:aswb_blaze
   //:aswb_tests
 
 workspace_type: intellij_plugin
diff --git a/aswb/BUILD b/aswb/BUILD
index 01df2a6..2312a96 100644
--- a/aswb/BUILD
+++ b/aswb/BUILD
@@ -2,25 +2,30 @@
 # Description: Builds ASwB for blaze and bazel
 #
 
+licenses(["notice"])  # Apache 2.0
+
 load(
     "//build_defs:build_defs.bzl",
+    "intellij_plugin",
     "merged_plugin_xml",
     "stamped_plugin_xml",
-    "intellij_plugin",
 )
 
 merged_plugin_xml(
     name = "merged_plugin_xml_common",
     srcs = [
         "src/META-INF/aswb.xml",
-        "//blaze-base:plugin_xml",
-        "//blaze-cpp:plugin_xml",
-        "//blaze-java:plugin_xml",
+        "//base:plugin_xml",
+        "//cpp:plugin_xml",
+        "//java:plugin_xml",
+    ],
+    visibility = [
+        "//visibility:public",
     ],
 )
 
 merged_plugin_xml(
-    name = "merged_plugin_xml_bazel",
+    name = "merged_plugin_xml",
     srcs = [
         "src/META-INF/aswb_bazel.xml",
         ":merged_plugin_xml_common",
@@ -28,9 +33,11 @@
 )
 
 stamped_plugin_xml(
-    name = "stamped_plugin_xml_bazel",
+    name = "stamped_plugin_xml",
     include_product_code_in_stamp = True,
-    plugin_xml = ":merged_plugin_xml_bazel",
+    plugin_id = "com.google.idea.bazel.aswb",
+    plugin_name = "Android Studio with Bazel",
+    plugin_xml = ":merged_plugin_xml",
     stamp_since_build = True,
     version_file = "//:version",
 )
@@ -39,44 +46,46 @@
     name = "aswb_lib",
     srcs = glob(["src/**/*.java"]),
     resources = glob(["resources/**/*"]),
-    visibility = ["//visibility:public"],
+    visibility = [
+        "//visibility:public",
+    ],
     deps = [
-        "//blaze-base",
-        "//blaze-base:proto-deps",
-        "//blaze-cpp",
-        "//blaze-java",
-        "//intellij-platform-sdk:bundled_plugins",
-        "//intellij-platform-sdk:plugin_api",
-        "//third_party:jsr305",
+        "//base",
+        "//common/experiments",
+        "//cpp",
+        "//intellij_platform_sdk:plugin_api",
+        "//java",
+        "//proto_deps",
+        "@jsr305_annotations//jar",
     ],
 )
 
 load(
     "//intellij_test:test_defs.bzl",
-    "intellij_test",
+    "intellij_unit_test_suite",
 )
 
-intellij_test(
+intellij_unit_test_suite(
     name = "unit_tests",
     srcs = glob(["tests/unittests/**/*.java"]),
     test_package_root = "com.google.idea.blaze.android",
     deps = [
         ":aswb_lib",
-        "//blaze-base",
-        "//blaze-base:proto-deps",
-        "//blaze-base:unit_test_utils",
-        "//blaze-java",
-        "//intellij-platform-sdk:bundled_plugins_for_tests",
-        "//intellij-platform-sdk:plugin_api_for_tests",
-        "//intellij_test:lib",
-        "//third_party:jsr305",
-        "//third_party:test_lib",
+        "//base",
+        "//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_bazel",
+    plugin_xml = ":stamped_plugin_xml",
     deps = [
         ":aswb_lib",
     ],
diff --git a/aswb/aswb.iml b/aswb/aswb.iml
new file mode 100644
index 0000000..37230b3
--- /dev/null
+++ b/aswb/aswb.iml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/tests/unittests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" relativeOutputPath="aswb/resources" />
+    </content>
+    <orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="module" module-name="blaze-base-aswb" />
+    <orderEntry type="module" module-name="blaze-cpp-aswb" />
+    <orderEntry type="module" module-name="blaze-java-aswb" />
+    <orderEntry type="module" module-name="execution-openapi" />
+    <orderEntry type="module" module-name="java-impl" />
+    <orderEntry type="module" module-name="openapi" />
+    <orderEntry type="module" module-name="execution-impl" />
+    <orderEntry type="module" module-name="junit" />
+    <orderEntry type="library" scope="TEST" name="mockito" level="project" />
+    <orderEntry type="module" module-name="community-main" scope="RUNTIME" />
+    <orderEntry type="module" module-name="debugger-impl" />
+    <orderEntry type="module" module-name="android" />
+    <orderEntry type="module" module-name="dom-openapi" />
+    <orderEntry type="module" module-name="cidr-lang" />
+    <orderEntry type="module" module-name="android-ndk" />
+    <orderEntry type="module" module-name="cidr-common" />
+    <orderEntry type="module" module-name="instant-run-client" />
+    <orderEntry type="module" module-name="external-system-api" />
+  </component>
+</module>
\ No newline at end of file
diff --git a/aswb/src/META-INF/aswb.xml b/aswb/src/META-INF/aswb.xml
index 87eb089..f7b42c3 100644
--- a/aswb/src/META-INF/aswb.xml
+++ b/aswb/src/META-INF/aswb.xml
@@ -14,17 +14,15 @@
   ~ limitations under the License.
   -->
 <idea-plugin>
-  <id>com.google.idea.blaze.aswb</id>
   <vendor>Google</vendor>
 
-  <depends optional="true">com.intellij.modules.androidstudio</depends>
+  <depends>com.intellij.modules.androidstudio</depends>
   <depends>org.jetbrains.android</depends>
 
   <extensions defaultExtensionNs="com.intellij">
     <java.elementFinder implementation="com.google.idea.blaze.android.resources.AndroidResourceClassFinder"
         order="first, before java"/>
     <java.elementFinder implementation="com.google.idea.blaze.android.resources.AndroidResourcePackageFinder"/>
-    <stepsBeforeRunProvider implementation="com.google.idea.blaze.android.run.BlazeBeforeRunTaskProvider"/>
     <projectService serviceImplementation="com.google.idea.blaze.android.resources.LightResourceClassService"/>
     <runConfigurationProducer
         implementation="com.google.idea.blaze.android.run.test.BlazeAndroidTestClassRunConfigurationProducer"
@@ -53,9 +51,11 @@
     <SyncListener implementation="com.google.idea.blaze.android.sync.BlazeAndroidSyncListener"/>
     <SyncListener implementation="com.google.idea.blaze.android.cppimpl.BlazeNdkSupportEnabler"/>
     <SyncListener implementation="com.google.idea.blaze.android.manifest.ManifestParser$ClearManifestParser"/>
-    <RuleConfigurationFactory implementation="com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryRunConfigurationType$BlazeAndroidBinaryRuleConfigurationFactory"/>
-    <RuleConfigurationFactory implementation="com.google.idea.blaze.android.run.test.BlazeAndroidTestRunConfigurationType$BlazeAndroidTestRuleConfigurationFactory"/>
+    <RuleConfigurationFactory implementation="com.google.idea.blaze.android.run.BlazeAndroidRuleConfigurationFactory"/>
     <java.JavaSyncAugmenter implementation="com.google.idea.blaze.android.sync.BlazeAndroidJavaSyncAugmenter"/>
+    <PrefetchFileSource implementation="com.google.idea.blaze.android.sync.AndroidPrefetchFileSource"/>
+    <BlazeCommandRunConfigurationHandlerProvider implementation="com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryRunConfigurationHandlerProvider"/>
+    <BlazeCommandRunConfigurationHandlerProvider implementation="com.google.idea.blaze.android.run.test.BlazeAndroidTestRunConfigurationHandlerProvider"/>
   </extensions>
 
   <extensions defaultExtensionNs="com.android.ide">
diff --git a/aswb/src/META-INF/aswb_bazel.xml b/aswb/src/META-INF/aswb_bazel.xml
index abcc021..4b89d60 100644
--- a/aswb/src/META-INF/aswb_bazel.xml
+++ b/aswb/src/META-INF/aswb_bazel.xml
@@ -14,6 +14,5 @@
   ~ limitations under the License.
   -->
 <idea-plugin>
-  <name>Android Studio with Bazel</name>
   <description>Provides the ability to import Bazel projects in Android Studio.</description>
 </idea-plugin>
diff --git a/aswb/src/META-INF/aswb_blaze.xml b/aswb/src/META-INF/aswb_blaze.xml
deleted file mode 100644
index 0e3acf7..0000000
--- a/aswb/src/META-INF/aswb_blaze.xml
+++ /dev/null
@@ -1,19 +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.
-  -->
-<idea-plugin>
-  <name>Android Studio with Blaze</name>
-  <description>Provides the ability to import Blaze projects in Android Studio.</description>
-</idea-plugin>
diff --git a/aswb/src/com/google/idea/blaze/android/cppapi/BlazeNativeDebuggerIdProvider.java b/aswb/src/com/google/idea/blaze/android/cppapi/BlazeNativeDebuggerIdProvider.java
index 4ecc40d..1645aaa 100644
--- a/aswb/src/com/google/idea/blaze/android/cppapi/BlazeNativeDebuggerIdProvider.java
+++ b/aswb/src/com/google/idea/blaze/android/cppapi/BlazeNativeDebuggerIdProvider.java
@@ -16,9 +16,9 @@
 package com.google.idea.blaze.android.cppapi;
 
 import com.intellij.openapi.components.ServiceManager;
-
 import javax.annotation.Nullable;
 
+/** Provides the ID of the native debugger to use */
 public abstract class BlazeNativeDebuggerIdProvider {
   @Nullable
   public static BlazeNativeDebuggerIdProvider getInstance() {
diff --git a/aswb/src/com/google/idea/blaze/android/cppapi/NdkSupport.java b/aswb/src/com/google/idea/blaze/android/cppapi/NdkSupport.java
index 8435113..506dfe6 100644
--- a/aswb/src/com/google/idea/blaze/android/cppapi/NdkSupport.java
+++ b/aswb/src/com/google/idea/blaze/android/cppapi/NdkSupport.java
@@ -15,8 +15,9 @@
  */
 package com.google.idea.blaze.android.cppapi;
 
-import com.google.idea.blaze.base.experiments.BoolExperiment;
+import com.google.idea.common.experiments.BoolExperiment;
 
+/** Contains the experiment that turns on NDK support */
 public class NdkSupport {
   public static final BoolExperiment NDK_SUPPORT = new BoolExperiment("ndk.support", false);
 }
diff --git a/aswb/src/com/google/idea/blaze/android/cppimpl/BlazeNdkSupportEnabler.java b/aswb/src/com/google/idea/blaze/android/cppimpl/BlazeNdkSupportEnabler.java
index a162635..1918b2c 100644
--- a/aswb/src/com/google/idea/blaze/android/cppimpl/BlazeNdkSupportEnabler.java
+++ b/aswb/src/com/google/idea/blaze/android/cppimpl/BlazeNdkSupportEnabler.java
@@ -15,6 +15,8 @@
  */
 package com.google.idea.blaze.android.cppimpl;
 
+import static com.jetbrains.cidr.lang.OCLanguage.LANGUAGE_SUPPORT_DISABLED;
+
 import com.android.tools.ndk.NdkHelper;
 import com.google.idea.blaze.base.model.BlazeProjectData;
 import com.google.idea.blaze.base.model.primitives.LanguageClass;
@@ -27,29 +29,25 @@
 import com.jetbrains.cidr.lang.workspace.OCWorkspace;
 import org.jetbrains.annotations.NotNull;
 
-import static com.jetbrains.cidr.lang.OCLanguage.LANGUAGE_SUPPORT_DISABLED;
-
-public final class BlazeNdkSupportEnabler implements SyncListener {
-  @Override
-  public void onSyncStart(Project project) {
-  }
+final class BlazeNdkSupportEnabler extends SyncListener.Adapter {
 
   @Override
-  public void onSyncComplete(Project project,
-                             BlazeImportSettings importSettings,
-                             ProjectViewSet projectViewSet,
-                             BlazeProjectData blazeProjectData) {
+  public void onSyncComplete(
+      Project project,
+      BlazeImportSettings importSettings,
+      ProjectViewSet projectViewSet,
+      BlazeProjectData blazeProjectData,
+      SyncResult syncResult) {
     boolean enabled = blazeProjectData.workspaceLanguageSettings.isLanguageActive(LanguageClass.C);
     enableCSupportInIde(project, enabled);
   }
 
   /**
-   * If {@code enabled} is true, this method will enable C support in the IDE if it is not already enabled. if {@code enabled} is false this
-   * method will clear out any currently stored information in the IDE about C and will disable C support in the IDE, unless support is
-   * already disabled.
-   *
-   * </p>
-   * In either case, if the value of enabled matches what the IDE currently does, this method will do nothing.
+   * If {@code enabled} is true, this method will enable C support in the IDE if it is not already
+   * enabled. if {@code enabled} is false this method will clear out any currently stored
+   * information in the IDE about C and will disable C support in the IDE, unless support is already
+   * disabled. In either case, if the value of enabled matches what the IDE currently does, this
+   * method will do nothing.
    *
    * @param project the project to enable or disable c support in.
    * @param enabled if true, turn on C support in the IDE. If false, turn off C support in the IDE.
@@ -64,18 +62,20 @@
   }
 
   private static void rebuildSymbols(@NotNull Project project, @NotNull OCWorkspace workspace) {
-    ApplicationManager.getApplication().runReadAction(() -> {
-      if (project.isDisposed()) {
-        return;
-      }
-      // Notifying BuildSettingsChangeTracker in unitTestMode will leads to a dead lock. See b/23087433 for more information.
-      if (!ApplicationManager.getApplication().isUnitTestMode()) {
-        workspace.getModificationTrackers().getBuildSettingsChangesTracker().incModificationCount();
-      }
-    });
-  }
-
-  @Override
-  public void afterSync(Project project, boolean successful) {
+    ApplicationManager.getApplication()
+        .runReadAction(
+            () -> {
+              if (project.isDisposed()) {
+                return;
+              }
+              // Notifying BuildSettingsChangeTracker in unitTestMode will leads to a dead lock.
+              // See b/23087433 for more information.
+              if (!ApplicationManager.getApplication().isUnitTestMode()) {
+                workspace
+                    .getModificationTrackers()
+                    .getBuildSettingsChangesTracker()
+                    .incModificationCount();
+              }
+            });
   }
 }
diff --git a/aswb/src/com/google/idea/blaze/android/cppimpl/debug/BlazeAndroidNativeDebuggerLanguageSupportFactory.java b/aswb/src/com/google/idea/blaze/android/cppimpl/debug/BlazeAndroidNativeDebuggerLanguageSupportFactory.java
index 9ca88db..2f08b63 100644
--- a/aswb/src/com/google/idea/blaze/android/cppimpl/debug/BlazeAndroidNativeDebuggerLanguageSupportFactory.java
+++ b/aswb/src/com/google/idea/blaze/android/cppimpl/debug/BlazeAndroidNativeDebuggerLanguageSupportFactory.java
@@ -15,7 +15,7 @@
  */
 package com.google.idea.blaze.android.cppimpl.debug;
 
-import com.google.idea.blaze.android.run.BlazeAndroidRunConfiguration;
+import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationHandler;
 import com.intellij.execution.configurations.RunProfile;
 import com.intellij.openapi.application.Result;
 import com.intellij.openapi.application.WriteAction;
@@ -35,21 +35,19 @@
 import com.jetbrains.cidr.lang.OCFileType;
 import com.jetbrains.cidr.lang.OCLanguage;
 import com.jetbrains.cidr.lang.util.OCElementFactory;
+import javax.annotation.Nullable;
 import org.jetbrains.annotations.NotNull;
 
-import javax.annotation.Nullable;
-
-public class BlazeAndroidNativeDebuggerLanguageSupportFactory extends OCDebuggerLanguageSupportFactory {
+class BlazeAndroidNativeDebuggerLanguageSupportFactory extends OCDebuggerLanguageSupportFactory {
   @Override
   public XDebuggerEditorsProvider createEditor(RunProfile profile) {
     if (profile == null) {
       return new DebuggerEditorsProvider();
     }
-    if (profile instanceof BlazeAndroidRunConfiguration) {
-      BlazeAndroidRunConfiguration runConfig = (BlazeAndroidRunConfiguration)profile;
-      if (runConfig.getCommonState().isNativeDebuggingEnabled()) {
-        return new DebuggerEditorsProvider();
-      }
+    BlazeAndroidRunConfigurationHandler handler =
+        BlazeAndroidRunConfigurationHandler.getHandlerFrom(profile);
+    if (handler != null && handler.getCommonState().isNativeDebuggingEnabled()) {
+      return new DebuggerEditorsProvider();
     }
     return null;
   }
@@ -63,25 +61,28 @@
 
     @NotNull
     @Override
-    public Document createDocument(final Project project,
-                                   final String text,
-                                   @Nullable XSourcePosition sourcePosition,
-                                   final EvaluationMode mode) {
+    public Document createDocument(
+        final Project project,
+        final String text,
+        @Nullable XSourcePosition sourcePosition,
+        final EvaluationMode mode) {
       final PsiElement context = OCDebuggerTypesHelper.getContextElement(sourcePosition, project);
-      if (context != null && context.getLanguage() == OCLanguage.getInstance())   {
+      if (context != null && context.getLanguage() == OCLanguage.getInstance()) {
         return new WriteAction<Document>() {
           @Override
           protected void run(Result<Document> result) throws Throwable {
-            PsiFile fragment = mode == EvaluationMode.EXPRESSION
-                               ? OCElementFactory.expressionCodeFragment(text, project, context, true, false)
-                               : OCElementFactory.expressionOrStatementsCodeFragment(text, project, context, true, false);
+            PsiFile fragment =
+                mode == EvaluationMode.EXPRESSION
+                    ? OCElementFactory.expressionCodeFragment(text, project, context, true, false)
+                    : OCElementFactory.expressionOrStatementsCodeFragment(
+                        text, project, context, true, false);
             //noinspection ConstantConditions
             result.setResult(PsiDocumentManager.getInstance(project).getDocument(fragment));
           }
         }.execute().getResultObject();
-      }
-      else {
-        final LightVirtualFile plainTextFile = new LightVirtualFile("oc-debug-editor-when-no-source-position-available.txt", text);
+      } else {
+        final LightVirtualFile plainTextFile =
+            new LightVirtualFile("oc-debug-editor-when-no-source-position-available.txt", text);
         //noinspection ConstantConditions
         return FileDocumentManager.getInstance().getDocument(plainTextFile);
       }
diff --git a/aswb/src/com/google/idea/blaze/android/cppimpl/debug/BlazeAutoAndroidDebugger.java b/aswb/src/com/google/idea/blaze/android/cppimpl/debug/BlazeAutoAndroidDebugger.java
index 2434eb9..821ddd1 100644
--- a/aswb/src/com/google/idea/blaze/android/cppimpl/debug/BlazeAutoAndroidDebugger.java
+++ b/aswb/src/com/google/idea/blaze/android/cppimpl/debug/BlazeAutoAndroidDebugger.java
@@ -22,13 +22,15 @@
 import com.intellij.openapi.project.Project;
 import org.jetbrains.annotations.NotNull;
 
-public class BlazeAutoAndroidDebugger extends AutoAndroidDebugger {
+class BlazeAutoAndroidDebugger extends AutoAndroidDebugger {
   public static String ID = "BlazeAuto";
 
   @Override
   protected boolean isNativeProject(@NotNull Project project) {
-    BlazeProjectData blazeProjectData = BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
-    return blazeProjectData != null && blazeProjectData.workspaceLanguageSettings.isLanguageActive(LanguageClass.C);
+    BlazeProjectData blazeProjectData =
+        BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
+    return blazeProjectData != null
+        && blazeProjectData.workspaceLanguageSettings.isLanguageActive(LanguageClass.C);
   }
 
   @NotNull
diff --git a/aswb/src/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeAndroidDebuggerIdProviderImpl.java b/aswb/src/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeAndroidDebuggerIdProviderImpl.java
index c87b3ac..f9a9dc0 100644
--- a/aswb/src/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeAndroidDebuggerIdProviderImpl.java
+++ b/aswb/src/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeAndroidDebuggerIdProviderImpl.java
@@ -17,7 +17,7 @@
 
 import com.google.idea.blaze.android.cppapi.BlazeNativeDebuggerIdProvider;
 
-public class BlazeNativeAndroidDebuggerIdProviderImpl extends BlazeNativeDebuggerIdProvider {
+class BlazeNativeAndroidDebuggerIdProviderImpl extends BlazeNativeDebuggerIdProvider {
 
   @Override
   public String getDebuggerId() {
diff --git a/aswb/src/com/google/idea/blaze/android/manifest/ManifestParser.java b/aswb/src/com/google/idea/blaze/android/manifest/ManifestParser.java
index cae3b0f..00cbe6b 100644
--- a/aswb/src/com/google/idea/blaze/android/manifest/ManifestParser.java
+++ b/aswb/src/com/google/idea/blaze/android/manifest/ManifestParser.java
@@ -30,21 +30,17 @@
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.psi.PsiDocumentManager;
 import com.intellij.util.ArrayUtil;
-import org.jetbrains.android.dom.manifest.Manifest;
-import org.jetbrains.android.util.AndroidUtils;
-
 import java.io.File;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.stream.Collectors;
-
 import javax.annotation.Nullable;
+import org.jetbrains.android.dom.manifest.Manifest;
+import org.jetbrains.android.util.AndroidUtils;
 
-/**
- * Parses manifests from the project.
- */
+/** Parses manifests from the project. */
 public class ManifestParser {
   private static final Logger LOG = Logger.getInstance(ManifestParser.class);
   private final Project project;
@@ -69,9 +65,9 @@
     }
     final VirtualFile virtualFile;
     if (ApplicationManager.getApplication().isDispatchThread()) {
-       virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file);
+      virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file);
     } else {
-       virtualFile = LocalFileSystem.getInstance().findFileByIoFile(file);
+      virtualFile = LocalFileSystem.getInstance().findFileByIoFile(file);
     }
     if (virtualFile == null) {
       LOG.error("Could not find manifest: " + file);
@@ -83,24 +79,29 @@
   }
 
   public void refreshManifests(Collection<File> manifestFiles) {
-    List<VirtualFile> manifestVirtualFiles = manifestFiles.stream()
-      .map(file -> VfsUtil.findFileByIoFile(file, false))
-      .filter(Objects::nonNull)
-      .collect(Collectors.toList());
+    List<VirtualFile> manifestVirtualFiles =
+        manifestFiles
+            .stream()
+            .map(file -> VfsUtil.findFileByIoFile(file, false))
+            .filter(Objects::nonNull)
+            .collect(Collectors.toList());
 
-    VfsUtil.markDirtyAndRefresh(false, false, false, ArrayUtil.toObjectArray(manifestVirtualFiles, VirtualFile.class));
-    ApplicationManager.getApplication().invokeAndWait(
-      () -> PsiDocumentManager.getInstance(project).commitAllDocuments(),
-      ModalityState.any()
-    );
+    VfsUtil.markDirtyAndRefresh(
+        false, false, false, ArrayUtil.toObjectArray(manifestVirtualFiles, VirtualFile.class));
+    ApplicationManager.getApplication()
+        .invokeAndWait(
+            () -> PsiDocumentManager.getInstance(project).commitAllDocuments(),
+            ModalityState.any());
   }
 
-  public static class ClearManifestParser extends SyncListener.Adapter {
+  static class ClearManifestParser extends SyncListener.Adapter {
     @Override
-    public void onSyncComplete(Project project,
-                               BlazeImportSettings importSettings,
-                               ProjectViewSet projectViewSet,
-                               BlazeProjectData blazeProjectData) {
+    public void onSyncComplete(
+        Project project,
+        BlazeImportSettings importSettings,
+        ProjectViewSet projectViewSet,
+        BlazeProjectData blazeProjectData,
+        SyncResult syncResult) {
       getInstance(project).manifestFileMap.clear();
     }
   }
diff --git a/aswb/src/com/google/idea/blaze/android/plugin/AswbPlugin.java b/aswb/src/com/google/idea/blaze/android/plugin/AswbPlugin.java
index a9e5866..ff54441 100644
--- a/aswb/src/com/google/idea/blaze/android/plugin/AswbPlugin.java
+++ b/aswb/src/com/google/idea/blaze/android/plugin/AswbPlugin.java
@@ -15,16 +15,20 @@
  */
 package com.google.idea.blaze.android.plugin;
 
+import com.google.idea.blaze.base.bazel.BuildSystemProvider;
 import com.google.idea.blaze.base.plugin.BlazePluginId;
+import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
 
-/**
- * ASwB plugin configuration information.
- */
+/** ASwB plugin configuration information. */
 public class AswbPlugin implements BlazePluginId {
-  private static final String PLUGIN_ID = "com.google.idea.blaze.aswb"; // Please keep up-to-date with plugin.xml
 
   @Override
   public String getPluginId() {
-    return PLUGIN_ID;
+    // Please keep these up-to-date with plugin xmls
+    BuildSystem type = BuildSystemProvider.defaultBuildSystem().buildSystem();
+    if (type == BuildSystem.Blaze) {
+      return "com.google.idea.blaze.aswb";
+    }
+    return "com.google.idea.bazel.aswb";
   }
 }
diff --git a/aswb/src/com/google/idea/blaze/android/plugin/PluginCompatibilityEnforcer.java b/aswb/src/com/google/idea/blaze/android/plugin/PluginCompatibilityEnforcer.java
index a4a62d7..bb2ff51 100644
--- a/aswb/src/com/google/idea/blaze/android/plugin/PluginCompatibilityEnforcer.java
+++ b/aswb/src/com/google/idea/blaze/android/plugin/PluginCompatibilityEnforcer.java
@@ -25,21 +25,20 @@
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.ui.MessageType;
 import com.intellij.openapi.util.BuildNumber;
-import org.jetbrains.annotations.NotNull;
-
-import javax.annotation.Nullable;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import javax.annotation.Nullable;
+import org.jetbrains.annotations.NotNull;
 
 /**
- * Checks META-INF/product-build.txt for a product build number and compares
- * them against the build. If incompatible, it informs the user.
+ * Checks META-INF/product-build.txt for a product build number and compares them against the build.
+ * If incompatible, it informs the user.
  */
 public class PluginCompatibilityEnforcer implements ApplicationComponent {
   private static final Logger LOG = Logger.getInstance(PluginCompatibilityEnforcer.class);
   private static final NotificationGroup NOTIFICATION_GROUP =
-    new NotificationGroup("ASwB Plugin Version", NotificationDisplayType.BALLOON, true);
+      new NotificationGroup("ASwB Plugin Version", NotificationDisplayType.BALLOON, true);
 
   public void checkPluginCompatibility() {
     String pluginProductBuildString = readProductBuildTxt();
@@ -57,12 +56,13 @@
     }
 
     if (!isCompatible(pluginProductBuild)) {
-      String message = Joiner.on(' ').join(
-        "Invalid Android Studio version for the ASwB plugin.",
-        "Android Studio version: " + ApplicationInfo.getInstance().getBuild(),
-        "Compatible version: " + pluginProductBuild,
-        "Please update the ASwB plugin from the plugin manager."
-      );
+      String message =
+          Joiner.on(' ')
+              .join(
+                  "Invalid Android Studio version for the ASwB plugin.",
+                  "Android Studio version: " + ApplicationInfo.getInstance().getBuild(),
+                  "Compatible version: " + pluginProductBuild,
+                  "Please update the ASwB plugin from the plugin manager.");
       NOTIFICATION_GROUP.createNotification(message, MessageType.ERROR).notify(null);
       LOG.warn(message);
     }
@@ -81,7 +81,8 @@
 
   @Nullable
   private String readProductBuildTxt() {
-    try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream("META-INF/product-build.txt")) {
+    try (InputStream inputStream =
+        getClass().getClassLoader().getResourceAsStream("META-INF/product-build.txt")) {
       if (inputStream == null) {
         return null;
       }
@@ -98,9 +99,7 @@
   }
 
   @Override
-  public void disposeComponent() {
-
-  }
+  public void disposeComponent() {}
 
   @NotNull
   @Override
diff --git a/aswb/src/com/google/idea/blaze/android/projectview/AndroidSdkPlatformSection.java b/aswb/src/com/google/idea/blaze/android/projectview/AndroidSdkPlatformSection.java
index 1cec804..b2c7177 100644
--- a/aswb/src/com/google/idea/blaze/android/projectview/AndroidSdkPlatformSection.java
+++ b/aswb/src/com/google/idea/blaze/android/projectview/AndroidSdkPlatformSection.java
@@ -15,20 +15,19 @@
  */
 package com.google.idea.blaze.android.projectview;
 
-import com.google.common.base.CharMatcher;
 import com.google.idea.blaze.base.projectview.parser.ParseContext;
 import com.google.idea.blaze.base.projectview.parser.ProjectViewParser;
 import com.google.idea.blaze.base.projectview.section.ScalarSection;
 import com.google.idea.blaze.base.projectview.section.ScalarSectionParser;
 import com.google.idea.blaze.base.projectview.section.SectionKey;
 import com.google.idea.blaze.base.projectview.section.SectionParser;
+import com.intellij.openapi.util.text.StringUtil;
 import org.jetbrains.annotations.Nullable;
 
-/**
- * Allows manual override of the android sdk.
- */
+/** Allows manual override of the android sdk. */
 public class AndroidSdkPlatformSection {
-  public static final SectionKey<String, ScalarSection<String>> KEY = SectionKey.of("android_sdk_platform");
+  public static final SectionKey<String, ScalarSection<String>> KEY =
+      SectionKey.of("android_sdk_platform");
   public static final SectionParser PARSER = new AndroidSdkPlatformParser();
 
   private static class AndroidSdkPlatformParser extends ScalarSectionParser<String> {
@@ -38,11 +37,8 @@
 
     @Nullable
     @Override
-    protected String parseItem(
-      ProjectViewParser parser,
-      ParseContext parseContext,
-      String rest) {
-      return CharMatcher.is('\"').trimFrom(rest.trim());
+    protected String parseItem(ProjectViewParser parser, ParseContext parseContext, String rest) {
+      return StringUtil.unquoteString(rest);
     }
 
     @Override
diff --git a/aswb/src/com/google/idea/blaze/android/resources/AndroidPackageRClass.java b/aswb/src/com/google/idea/blaze/android/resources/AndroidPackageRClass.java
index f875247..b1802aa 100644
--- a/aswb/src/com/google/idea/blaze/android/resources/AndroidPackageRClass.java
+++ b/aswb/src/com/google/idea/blaze/android/resources/AndroidPackageRClass.java
@@ -16,7 +16,7 @@
 package com.google.idea.blaze.android.resources;
 
 import com.android.resources.ResourceType;
-import com.google.idea.blaze.base.experiments.BoolExperiment;
+import com.google.idea.common.experiments.BoolExperiment;
 import com.intellij.ide.highlighter.JavaFileType;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.module.Module;
@@ -30,6 +30,9 @@
 import com.intellij.psi.util.CachedValueProvider;
 import com.intellij.psi.util.CachedValuesManager;
 import com.intellij.psi.util.PsiModificationTracker;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
 import org.jetbrains.android.augment.AndroidLightClassBase;
 import org.jetbrains.android.augment.ResourceTypeClass;
 import org.jetbrains.android.dom.converters.ResourceReferenceConverter;
@@ -38,35 +41,27 @@
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Represents a dynamic "class R" for resources in an Android module.
- */
+/** Represents a dynamic "class R" for resources in an Android module. */
 public class AndroidPackageRClass extends AndroidLightClassBase {
   private static final Logger LOG = Logger.getInstance(AndroidPackageRClass.class);
-  private static final BoolExperiment USE_OUT_OF_CODE_MOD_COUNT = new BoolExperiment("use.out.of.code.modcount.for.r.class.cache", true);
+  private static final BoolExperiment USE_OUT_OF_CODE_MOD_COUNT =
+      new BoolExperiment("use.out.of.code.modcount.for.r.class.cache", true);
 
-  @NotNull
-  private final PsiFile myFile;
-  @NotNull
-  private final String myFullyQualifiedName;
-  @NotNull
-  private final Module myModule;
+  @NotNull private final PsiFile myFile;
+  @NotNull private final String myFullyQualifiedName;
+  @NotNull private final Module myModule;
 
   private CachedValue<PsiClass[]> myClassCache;
 
-  public AndroidPackageRClass(@NotNull PsiManager psiManager,
-                              @NotNull String packageName,
-                              @NotNull Module module) {
+  public AndroidPackageRClass(
+      @NotNull PsiManager psiManager, @NotNull String packageName, @NotNull Module module) {
     super(psiManager);
 
     myModule = module;
     myFullyQualifiedName = packageName + AndroidResourceClassFinder.INTERNAL_R_CLASS_SHORTNAME;
-    myFile = PsiFileFactory.getInstance(myManager.getProject())
-      .createFileFromText("R.java", JavaFileType.INSTANCE, "package " + packageName + ";");
+    myFile =
+        PsiFileFactory.getInstance(myManager.getProject())
+            .createFileFromText("R.java", JavaFileType.INSTANCE, "package " + packageName + ";");
 
     this.putUserData(ModuleUtilCore.KEY_MODULE, module);
     // Some scenarios move up to the file level and then attempt to get the module from the file.
@@ -110,16 +105,19 @@
   @Override
   public PsiClass[] getInnerClasses() {
     if (myClassCache == null) {
-      myClassCache = CachedValuesManager.getManager(getProject())
-        .createCachedValue(new CachedValueProvider<PsiClass[]>() {
-          @Override
-          public Result<PsiClass[]> compute() {
-            return Result.create(doGetInnerClasses(),
-                                 USE_OUT_OF_CODE_MOD_COUNT.getValue() ?
-                                 PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT :
-                                 PsiModificationTracker.MODIFICATION_COUNT);
-          }
-        });
+      myClassCache =
+          CachedValuesManager.getManager(getProject())
+              .createCachedValue(
+                  new CachedValueProvider<PsiClass[]>() {
+                    @Override
+                    public Result<PsiClass[]> compute() {
+                      return Result.create(
+                          doGetInnerClasses(),
+                          USE_OUT_OF_CODE_MOD_COUNT.getValue()
+                              ? PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT
+                              : PsiModificationTracker.MODIFICATION_COUNT);
+                    }
+                  });
     }
     return myClassCache.getValue();
   }
@@ -136,7 +134,8 @@
       return new PsiClass[0];
     }
 
-    final Set<ResourceType> types = ResourceReferenceConverter.getResourceTypesInCurrentModule(facet);
+    final Set<ResourceType> types =
+        ResourceReferenceConverter.getResourceTypesInCurrentModule(facet);
     final List<PsiClass> result = new ArrayList<PsiClass>();
 
     for (ResourceType type : types) {
diff --git a/aswb/src/com/google/idea/blaze/android/resources/AndroidResourceClassFinder.java b/aswb/src/com/google/idea/blaze/android/resources/AndroidResourceClassFinder.java
index 3cc85e1..9de9176 100644
--- a/aswb/src/com/google/idea/blaze/android/resources/AndroidResourceClassFinder.java
+++ b/aswb/src/com/google/idea/blaze/android/resources/AndroidResourceClassFinder.java
@@ -21,14 +21,11 @@
 import com.intellij.psi.PsiElementFinder;
 import com.intellij.psi.PsiPackage;
 import com.intellij.psi.search.GlobalSearchScope;
+import java.util.List;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import java.util.List;
-
-/**
- * Provides dynamic Android Resource classes (class R {...} ).
- */
+/** Provides dynamic Android Resource classes (class R {...} ). */
 public class AndroidResourceClassFinder extends PsiElementFinder {
   static final String INTERNAL_R_CLASS_SHORTNAME = ".R";
   private final Project project;
diff --git a/aswb/src/com/google/idea/blaze/android/resources/AndroidResourcePackage.java b/aswb/src/com/google/idea/blaze/android/resources/AndroidResourcePackage.java
index 3cd62a1..b016394 100644
--- a/aswb/src/com/google/idea/blaze/android/resources/AndroidResourcePackage.java
+++ b/aswb/src/com/google/idea/blaze/android/resources/AndroidResourcePackage.java
@@ -18,9 +18,7 @@
 import com.intellij.psi.PsiManager;
 import com.intellij.psi.impl.file.PsiPackageImpl;
 
-/**
- * A generated stub PsiPackage for generated R classes.
- */
+/** A generated stub PsiPackage for generated R classes. */
 public class AndroidResourcePackage extends PsiPackageImpl {
   public AndroidResourcePackage(PsiManager manager, String qualifiedName) {
     super(manager, qualifiedName);
diff --git a/aswb/src/com/google/idea/blaze/android/resources/AndroidResourcePackageFinder.java b/aswb/src/com/google/idea/blaze/android/resources/AndroidResourcePackageFinder.java
index 6c37015..ee7dec2 100644
--- a/aswb/src/com/google/idea/blaze/android/resources/AndroidResourcePackageFinder.java
+++ b/aswb/src/com/google/idea/blaze/android/resources/AndroidResourcePackageFinder.java
@@ -23,9 +23,7 @@
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-/**
- * Provides dynamic Android resource packages.
- */
+/** Provides dynamic Android resource packages. */
 public class AndroidResourcePackageFinder extends PsiElementFinder {
 
   private final Project project;
diff --git a/aswb/src/com/google/idea/blaze/android/resources/LightResourceClassService.java b/aswb/src/com/google/idea/blaze/android/resources/LightResourceClassService.java
index e8d1cdc..183221f 100644
--- a/aswb/src/com/google/idea/blaze/android/resources/LightResourceClassService.java
+++ b/aswb/src/com/google/idea/blaze/android/resources/LightResourceClassService.java
@@ -17,7 +17,7 @@
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Maps;
-import com.google.idea.blaze.base.experiments.BoolExperiment;
+import com.google.idea.common.experiments.BoolExperiment;
 import com.intellij.openapi.components.ServiceManager;
 import com.intellij.openapi.module.Module;
 import com.intellij.openapi.project.Project;
@@ -25,34 +25,31 @@
 import com.intellij.psi.PsiManager;
 import com.intellij.psi.PsiPackage;
 import com.intellij.psi.search.GlobalSearchScope;
+import java.util.List;
+import java.util.Map;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import java.util.List;
-import java.util.Map;
-
-/**
- * A service for storing and finding light R classes.
- */
+/** A service for storing and finding light R classes. */
 public class LightResourceClassService {
 
-  @NotNull
-  private Map<String, AndroidPackageRClass> rClasses = Maps.newHashMap();
-  @NotNull
-  private Map<String, PsiPackage> rClassPackages = Maps.newHashMap();
+  @NotNull private Map<String, AndroidPackageRClass> rClasses = Maps.newHashMap();
+  @NotNull private Map<String, PsiPackage> rClassPackages = Maps.newHashMap();
 
-  // It should be harmless to create stub resource PsiPackages which shadow any "real" PsiPackages. Based on the ordering
-  // of PsiElementFinder it would prefer the real package (PsiElementFinderImpl has 'order="first"').
+  // It should be harmless to create stub resource PsiPackages which shadow any "real" PsiPackages.
+  // Based on the ordering of PsiElementFinder it would prefer the real package
+  // (PsiElementFinderImpl has 'order="first"').
   // Put under experiment just in case we find a problem w/ other element finders.
-  private static final BoolExperiment CREATE_STUB_RESOURCE_PACKAGES = new BoolExperiment("create.stub.resource.packages", true);
+  private static final BoolExperiment CREATE_STUB_RESOURCE_PACKAGES =
+      new BoolExperiment("create.stub.resource.packages", true);
 
-  public LightResourceClassService() {
-  }
+  public LightResourceClassService() {}
 
   public static LightResourceClassService getInstance(@NotNull Project project) {
     return ServiceManager.getService(project, LightResourceClassService.class);
   }
 
+  /** Builds light R classes */
   public static class Builder {
     Map<String, AndroidPackageRClass> rClassMap = Maps.newHashMap();
     Map<String, PsiPackage> rClassPackages = Maps.newHashMap();
@@ -64,11 +61,8 @@
     }
 
     public void addRClass(String resourceJavaPackage, Module module) {
-      AndroidPackageRClass rClass = new AndroidPackageRClass(
-        psiManager,
-        resourceJavaPackage,
-        module
-      );
+      AndroidPackageRClass rClass =
+          new AndroidPackageRClass(psiManager, resourceJavaPackage, module);
       rClassMap.put(getQualifiedRClassName(resourceJavaPackage), rClass);
       if (CREATE_STUB_RESOURCE_PACKAGES.getValue()) {
         addStubPackages(resourceJavaPackage);
@@ -85,7 +79,8 @@
         if (rClassPackages.containsKey(resourceJavaPackage)) {
           return;
         }
-        rClassPackages.put(resourceJavaPackage, new AndroidResourcePackage(psiManager, resourceJavaPackage));
+        rClassPackages.put(
+            resourceJavaPackage, new AndroidResourcePackage(psiManager, resourceJavaPackage));
         int nextIndex = resourceJavaPackage.lastIndexOf('.');
         if (nextIndex < 0) {
           return;
@@ -102,7 +97,7 @@
 
   @NotNull
   public List<PsiClass> getLightRClasses(
-    @NotNull String qualifiedName, @NotNull GlobalSearchScope scope) {
+      @NotNull String qualifiedName, @NotNull GlobalSearchScope scope) {
     AndroidPackageRClass rClass = this.rClasses.get(qualifiedName);
     if (rClass != null) {
       if (scope.isSearchInModuleContent(rClass.getModule())) {
diff --git a/aswb/src/com/google/idea/blaze/android/resources/actions/BlazeCreateResourceDirectoryDialog.java b/aswb/src/com/google/idea/blaze/android/resources/actions/BlazeCreateResourceDirectoryDialog.java
index 64d46f4..aa1857b 100644
--- a/aswb/src/com/google/idea/blaze/android/resources/actions/BlazeCreateResourceDirectoryDialog.java
+++ b/aswb/src/com/google/idea/blaze/android/resources/actions/BlazeCreateResourceDirectoryDialog.java
@@ -20,6 +20,7 @@
 import com.intellij.openapi.actionSystem.CommonDataKeys;
 import com.intellij.openapi.actionSystem.DataContext;
 import com.intellij.openapi.actionSystem.LangDataKeys;
+import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
 import com.intellij.openapi.module.Module;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.ui.Messages;
@@ -33,6 +34,15 @@
 import com.intellij.ui.components.JBLabel;
 import com.intellij.uiDesigner.core.GridConstraints;
 import com.intellij.uiDesigner.core.GridLayoutManager;
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.Insets;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
 import org.jetbrains.android.actions.CreateResourceDirectoryDialogBase;
 import org.jetbrains.android.actions.ElementCreatingValidator;
 import org.jetbrains.android.uipreview.DeviceConfiguratorPanel;
@@ -40,12 +50,9 @@
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import javax.swing.*;
-import java.awt.*;
-
 /**
- * Dialog to decide where to create a res/ subdirectory (e.g., layout/, values-foo/, etc.)
- * and how to name the subdirectory based on resource type and chosen configuration.
+ * Dialog to decide where to create a res/ subdirectory (e.g., layout/, values-foo/, etc.) and how
+ * to name the subdirectory based on resource type and chosen configuration.
  */
 public class BlazeCreateResourceDirectoryDialog extends CreateResourceDirectoryDialogBase {
 
@@ -63,36 +70,40 @@
   private PsiDirectory myResDirectory;
   private DataContext myDataContext;
 
-  public BlazeCreateResourceDirectoryDialog(Project project,
-                                            @Nullable Module module,
-                                            @Nullable ResourceFolderType resType,
-                                            @Nullable PsiDirectory resDirectory,
-                                            @Nullable DataContext dataContext,
-                                            ValidatorFactory validatorFactory) {
+  public BlazeCreateResourceDirectoryDialog(
+      Project project,
+      @Nullable Module module,
+      @Nullable ResourceFolderType resType,
+      @Nullable PsiDirectory resDirectory,
+      @Nullable DataContext dataContext,
+      ValidatorFactory validatorFactory) {
     super(project);
     setupUi();
     myResDirectory = resDirectory;
     myDataContext = dataContext;
     myValidatorFactory = validatorFactory;
     myResourceTypeComboBox.setModel(new EnumComboBoxModel<>(ResourceFolderType.class));
-    myResourceTypeComboBox.setRenderer(new ListCellRendererWrapper() {
-      @Override
-      public void customize(JList list, Object value, int index, boolean selected, boolean hasFocus) {
-        if (value instanceof ResourceFolderType) {
-          setText(((ResourceFolderType)value).getName());
-        }
-      }
-    });
+    myResourceTypeComboBox.setRenderer(
+        new ListCellRendererWrapper() {
+          @Override
+          public void customize(
+              JList list, Object value, int index, boolean selected, boolean hasFocus) {
+            if (value instanceof ResourceFolderType) {
+              setText(((ResourceFolderType) value).getName());
+            }
+          }
+        });
 
-    myDeviceConfiguratorPanel = setupDeviceConfigurationPanel(myResourceTypeComboBox, myDirectoryNameTextField, myErrorLabel);
+    myDeviceConfiguratorPanel =
+        setupDeviceConfigurationPanel(
+            myResourceTypeComboBox, myDirectoryNameTextField, myErrorLabel);
     myDeviceConfiguratorWrapper.add(myDeviceConfiguratorPanel, BorderLayout.CENTER);
     myResourceTypeComboBox.addActionListener(e -> myDeviceConfiguratorPanel.applyEditors());
 
     if (resType != null) {
       myResourceTypeComboBox.setSelectedItem(resType);
       myResourceTypeComboBox.setEnabled(false);
-    }
-    else {
+    } else {
       // Select values by default if not otherwise specified
       myResourceTypeComboBox.setSelectedItem(ResourceFolderType.VALUES);
     }
@@ -100,6 +111,8 @@
     // If myResDirectory is known before this, just use that.
     myResDirLabel.setVisible(false);
     myResDirCombo.setVisible(false);
+    myResDirCombo.addBrowseFolderListener(
+        project, FileChooserDescriptorFactory.createSingleFolderDescriptor());
     if (myResDirectory == null) {
       assert dataContext != null;
       assert module != null;
@@ -107,14 +120,17 @@
       VirtualFile contextFile = CommonDataKeys.VIRTUAL_FILE.getData(dataContext);
       if (contextFile != null) {
         PsiManager manager = PsiManager.getInstance(project);
-        VirtualFile virtualDirectory = BlazeCreateResourceUtils.getResDirFromDataContext(contextFile);
-        PsiDirectory directory = virtualDirectory != null ? manager.findDirectory(virtualDirectory) : null;
+        VirtualFile virtualDirectory =
+            BlazeCreateResourceUtils.getResDirFromDataContext(contextFile);
+        PsiDirectory directory =
+            virtualDirectory != null ? manager.findDirectory(virtualDirectory) : null;
         if (directory != null) {
           myResDirectory = directory;
-        }
-        else {
-          // As a last resort, if we have poor context, e.g., from File > New w/ a .java file open, set up the UI.
-          BlazeCreateResourceUtils.setupResDirectoryChoices(module.getProject(), contextFile, myResDirLabel, myResDirCombo);
+        } else {
+          // As a last resort, if we have poor context
+          // e.g., from File > New w/ a .java file open, set up the UI.
+          BlazeCreateResourceUtils.setupResDirectoryChoices(
+              module.getProject(), contextFile, myResDirLabel, myResDirCombo);
         }
       }
     }
@@ -131,8 +147,8 @@
     PsiDirectory resourceDirectory = getResourceDirectory();
     if (resourceDirectory == null) {
       Module module = LangDataKeys.MODULE.getData(myDataContext);
-      Messages.showErrorDialog(AndroidBundle.message("check.resource.dir.error", module),
-                               CommonBundle.getErrorTitle());
+      Messages.showErrorDialog(
+          AndroidBundle.message("check.resource.dir.error", module), CommonBundle.getErrorTitle());
       // Not much the user can do, just close the dialog.
       super.doOKAction();
       return;
@@ -152,8 +168,7 @@
   public JComponent getPreferredFocusedComponent() {
     if (myResourceTypeComboBox.isEnabled()) {
       return myResourceTypeComboBox;
-    }
-    else {
+    } else {
       return myDirectoryNameTextField;
     }
   }
@@ -183,62 +198,160 @@
     return myContentPanel;
   }
 
-  /**
-   * Initially generated by IntelliJ from a .form file.
-   */
+  /** Initially generated by IntelliJ from a .form file. */
   private void setupUi() {
     myContentPanel = new JPanel();
     myContentPanel.setLayout(new GridLayoutManager(5, 2, new Insets(0, 0, 0, 0), -1, -1));
     myContentPanel.setPreferredSize(new Dimension(800, 400));
     myResourceTypeComboBox = new JComboBox();
-    myContentPanel.add(myResourceTypeComboBox, new GridConstraints(1, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL,
-                                                                   GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED,
-                                                                   null, null, null, 0, false));
+    myContentPanel.add(
+        myResourceTypeComboBox,
+        new GridConstraints(
+            1,
+            1,
+            1,
+            1,
+            GridConstraints.ANCHOR_WEST,
+            GridConstraints.FILL_HORIZONTAL,
+            GridConstraints.SIZEPOLICY_CAN_GROW,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
     final JBLabel jBLabel1 = new JBLabel();
     jBLabel1.setText("Resource type:");
     jBLabel1.setDisplayedMnemonic('R');
     jBLabel1.setDisplayedMnemonicIndex(0);
-    myContentPanel.add(jBLabel1, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE,
-                                                     GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null,
-                                                     0, false));
+    myContentPanel.add(
+        jBLabel1,
+        new GridConstraints(
+            1,
+            0,
+            1,
+            1,
+            GridConstraints.ANCHOR_WEST,
+            GridConstraints.FILL_NONE,
+            GridConstraints.SIZEPOLICY_FIXED,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
     myDeviceConfiguratorWrapper = new JPanel();
     myDeviceConfiguratorWrapper.setLayout(new BorderLayout(0, 0));
-    myContentPanel.add(myDeviceConfiguratorWrapper,
-                       new GridConstraints(3, 0, 1, 2, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH,
-                                           GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
-                                           GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0,
-                                           false));
+    myContentPanel.add(
+        myDeviceConfiguratorWrapper,
+        new GridConstraints(
+            3,
+            0,
+            1,
+            2,
+            GridConstraints.ANCHOR_CENTER,
+            GridConstraints.FILL_BOTH,
+            GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
+            GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
+            null,
+            null,
+            null,
+            0,
+            false));
     final JLabel label1 = new JLabel();
     label1.setText("Directory name:");
     label1.setDisplayedMnemonic('D');
     label1.setDisplayedMnemonicIndex(0);
-    myContentPanel.add(label1, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE,
-                                                   GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0,
-                                                   false));
+    myContentPanel.add(
+        label1,
+        new GridConstraints(
+            0,
+            0,
+            1,
+            1,
+            GridConstraints.ANCHOR_WEST,
+            GridConstraints.FILL_NONE,
+            GridConstraints.SIZEPOLICY_FIXED,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
     myDirectoryNameTextField = new JTextField();
     myDirectoryNameTextField.setEnabled(true);
-    myContentPanel.add(myDirectoryNameTextField,
-                       new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL,
-                                           GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null,
-                                           new Dimension(150, -1), null, 0, false));
+    myContentPanel.add(
+        myDirectoryNameTextField,
+        new GridConstraints(
+            0,
+            1,
+            1,
+            1,
+            GridConstraints.ANCHOR_WEST,
+            GridConstraints.FILL_HORIZONTAL,
+            GridConstraints.SIZEPOLICY_WANT_GROW,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            new Dimension(150, -1),
+            null,
+            0,
+            false));
     myErrorLabel = new JBLabel();
-    myContentPanel.add(myErrorLabel, new GridConstraints(4, 0, 1, 2, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH,
-                                                         GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null,
-                                                         null, 0, false));
+    myContentPanel.add(
+        myErrorLabel,
+        new GridConstraints(
+            4,
+            0,
+            1,
+            2,
+            GridConstraints.ANCHOR_CENTER,
+            GridConstraints.FILL_BOTH,
+            GridConstraints.SIZEPOLICY_FIXED,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
     myResDirLabel = new JBLabel();
     myResDirLabel.setText("Base directory:");
     myResDirLabel.setDisplayedMnemonic('B');
     myResDirLabel.setDisplayedMnemonicIndex(0);
-    myContentPanel.add(myResDirLabel, new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE,
-                                                          GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null,
-                                                          null, 0, false));
+    myContentPanel.add(
+        myResDirLabel,
+        new GridConstraints(
+            2,
+            0,
+            1,
+            1,
+            GridConstraints.ANCHOR_WEST,
+            GridConstraints.FILL_NONE,
+            GridConstraints.SIZEPOLICY_FIXED,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
     myResDirCombo = new ComboboxWithBrowseButton();
-    myContentPanel.add(myResDirCombo, new GridConstraints(2, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL,
-                                                          GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null,
-                                                          null, 0, false));
+    myContentPanel.add(
+        myResDirCombo,
+        new GridConstraints(
+            2,
+            1,
+            1,
+            1,
+            GridConstraints.ANCHOR_WEST,
+            GridConstraints.FILL_HORIZONTAL,
+            GridConstraints.SIZEPOLICY_CAN_GROW,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
     jBLabel1.setLabelFor(myResourceTypeComboBox);
     label1.setLabelFor(myDirectoryNameTextField);
     myResDirLabel.setLabelFor(myResourceTypeComboBox);
   }
-
 }
diff --git a/aswb/src/com/google/idea/blaze/android/resources/actions/BlazeCreateResourceFileDialog.java b/aswb/src/com/google/idea/blaze/android/resources/actions/BlazeCreateResourceFileDialog.java
index a0bee66..6c132b0 100644
--- a/aswb/src/com/google/idea/blaze/android/resources/actions/BlazeCreateResourceFileDialog.java
+++ b/aswb/src/com/google/idea/blaze/android/resources/actions/BlazeCreateResourceFileDialog.java
@@ -15,16 +15,16 @@
  */
 package com.google.idea.blaze.android.resources.actions;
 
-import com.google.common.annotations.VisibleForTesting;
-
 import com.android.ide.common.resources.configuration.FolderConfiguration;
 import com.android.resources.ResourceConstants;
 import com.android.resources.ResourceFolderType;
 import com.android.tools.idea.res.ResourceNameValidator;
+import com.google.common.annotations.VisibleForTesting;
 import com.intellij.CommonBundle;
 import com.intellij.ide.actions.TemplateKindCombo;
 import com.intellij.openapi.actionSystem.CommonDataKeys;
 import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.ui.Messages;
 import com.intellij.openapi.vfs.VirtualFile;
@@ -39,6 +39,17 @@
 import com.intellij.uiDesigner.core.GridConstraints;
 import com.intellij.uiDesigner.core.GridLayoutManager;
 import com.intellij.util.PlatformIcons;
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.Insets;
+import java.util.Collection;
+import java.util.List;
+import javax.annotation.Nullable;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.event.DocumentEvent;
 import org.jetbrains.android.actions.CreateResourceFileDialogBase;
 import org.jetbrains.android.actions.CreateTypedResourceFileAction;
 import org.jetbrains.android.actions.ElementCreatingValidator;
@@ -46,16 +57,9 @@
 import org.jetbrains.android.uipreview.DeviceConfiguratorPanel;
 import org.jetbrains.android.util.AndroidBundle;
 
-import javax.annotation.Nullable;
-import javax.swing.*;
-import javax.swing.event.DocumentEvent;
-import java.awt.*;
-import java.util.Collection;
-import java.util.List;
-
 /**
- * Dialog to decide where and how to create a resource file of a given type
- * (which base res/ directory, which subdirectory, and how to name the new file).
+ * Dialog to decide where and how to create a resource file of a given type (which base res/
+ * directory, which subdirectory, and how to name the new file).
  */
 public class BlazeCreateResourceFileDialog extends CreateResourceFileDialogBase {
   private JTextField myFileNameField;
@@ -79,17 +83,18 @@
   private final AndroidFacet myFacet;
   private PsiDirectory myResDirectory;
 
-  public BlazeCreateResourceFileDialog(AndroidFacet facet,
-                                       Collection<CreateTypedResourceFileAction> actions,
-                                       ResourceFolderType folderType,
-                                       String filename,
-                                       String rootElement,
-                                       FolderConfiguration folderConfiguration,
-                                       boolean chooseFileName,
-                                       boolean chooseModule,
-                                       PsiDirectory resDirectory,
-                                       DataContext dataContext,
-                                       ValidatorFactory validatorFactory) {
+  public BlazeCreateResourceFileDialog(
+      AndroidFacet facet,
+      Collection<CreateTypedResourceFileAction> actions,
+      ResourceFolderType folderType,
+      String filename,
+      String rootElement,
+      FolderConfiguration folderConfiguration,
+      boolean chooseFileName,
+      boolean chooseModule,
+      PsiDirectory resDirectory,
+      DataContext dataContext,
+      ValidatorFactory validatorFactory) {
     super(facet.getModule().getProject());
     setupUi();
     myFacet = facet;
@@ -101,15 +106,19 @@
     myUpDownHint.setIcon(PlatformIcons.UP_DOWN_ARROWS);
     String selectedTemplate = setupSubActions(actions, myResourceTypeCombo, folderType);
 
-    myDeviceConfiguratorPanel = setupDeviceConfigurationPanel(myDirectoryNameTextField, myResourceTypeCombo, myErrorLabel);
+    myDeviceConfiguratorPanel =
+        setupDeviceConfigurationPanel(myDirectoryNameTextField, myResourceTypeCombo, myErrorLabel);
     if (folderConfiguration != null) {
       myDeviceConfiguratorPanel.init(folderConfiguration);
     }
 
-    myResourceTypeCombo.getComboBox().addActionListener(e -> {
-      myDeviceConfiguratorPanel.applyEditors();
-      updateRootElementTextField();
-    });
+    myResourceTypeCombo
+        .getComboBox()
+        .addActionListener(
+            e -> {
+              myDeviceConfiguratorPanel.applyEditors();
+              updateRootElementTextField();
+            });
 
     if (folderType != null && selectedTemplate != null) {
       final boolean v = folderType == ResourceFolderType.LAYOUT;
@@ -139,29 +148,34 @@
       myFileNameField.setText(filename);
     }
 
+    Project project = myFacet.getModule().getProject();
     // Set up UI to choose the base directory if needed (use context to prune selection).
     // There may be a resource directory already pre-selected, in which case hide the UI by default.
-    myResDirCombo.setVisible(false);
     myResDirLabel.setVisible(false);
-    Project project = myFacet.getModule().getProject();
+    myResDirCombo.setVisible(false);
+    myResDirCombo.addBrowseFolderListener(
+        project, FileChooserDescriptorFactory.createSingleFolderDescriptor());
     if (myResDirectory == null) {
       // Try to figure out from context (e.g., right click in project view).
       VirtualFile contextFile = CommonDataKeys.VIRTUAL_FILE.getData(dataContext);
       if (contextFile != null) {
         PsiManager manager = PsiManager.getInstance(project);
-        VirtualFile virtualDirectory = BlazeCreateResourceUtils.getResDirFromDataContext(contextFile);
-        PsiDirectory directory = virtualDirectory != null ? manager.findDirectory(virtualDirectory) : null;
+        VirtualFile virtualDirectory =
+            BlazeCreateResourceUtils.getResDirFromDataContext(contextFile);
+        PsiDirectory directory =
+            virtualDirectory != null ? manager.findDirectory(virtualDirectory) : null;
         if (directory != null) {
           myResDirectory = directory;
+        } else {
+          // As a last resort, if we have poor context,
+          // e.g., from File > New w/ a .java file open, set up the UI.
+          BlazeCreateResourceUtils.setupResDirectoryChoices(
+              project, contextFile, myResDirLabel, myResDirCombo);
         }
-        else {
-          // As a last resort, if we have poor context, e.g., from File > New w/ a .java file open, set up the UI.
-          BlazeCreateResourceUtils.setupResDirectoryChoices(project, contextFile, myResDirLabel, myResDirCombo);
-        }
-      }
-      else {
+      } else {
         // As a last resort, if we have no context, set up the UI.
-        BlazeCreateResourceUtils.setupResDirectoryChoices(project, null, myResDirLabel, myResDirCombo);
+        BlazeCreateResourceUtils.setupResDirectoryChoices(
+            project, null, myResDirLabel, myResDirCombo);
       }
     }
 
@@ -179,12 +193,15 @@
 
     setTitle(AndroidBundle.message("new.resource.dialog.title"));
 
-    myFileNameField.getDocument().addDocumentListener(new DocumentAdapter() {
-      @Override
-      public void textChanged(DocumentEvent event) {
-        validateName();
-      }
-    });
+    myFileNameField
+        .getDocument()
+        .addDocumentListener(
+            new DocumentAdapter() {
+              @Override
+              public void textChanged(DocumentEvent event) {
+                validateName();
+              }
+            });
     myResourceTypeCombo.getComboBox().addActionListener(actionEvent -> validateName());
     if (validateImmediately) {
       validateName();
@@ -222,12 +239,14 @@
 
     if (action != null) {
       final List<String> allowedTagNames = action.getSortedAllowedTagNames(myFacet);
-      myRootElementField = new TextFieldWithAutoCompletion<>(
-        myFacet.getModule().getProject(), new StringsCompletionProvider(allowedTagNames, null), true, null);
+      myRootElementField =
+          new TextFieldWithAutoCompletion<>(
+              myFacet.getModule().getProject(),
+              new StringsCompletionProvider(allowedTagNames, null),
+              true,
+              null);
       myRootElementField.setEnabled(allowedTagNames.size() > 1);
-      myRootElementField.setText(!action.isChooseTagName()
-                                 ? action.getDefaultRootTag()
-                                 : "");
+      myRootElementField.setText(!action.isChooseTagName() ? action.getDefaultRootTag() : "");
       myRootElementFieldWrapper.removeAll();
       myRootElementFieldWrapper.add(myRootElementField, BorderLayout.CENTER);
       myRootElementLabel.setLabelFor(myRootElementField);
@@ -247,19 +266,28 @@
     assert action != null;
 
     if (fileName.length() == 0) {
-      Messages.showErrorDialog(myPanel, AndroidBundle.message("file.name.not.specified.error"), CommonBundle.getErrorTitle());
+      Messages.showErrorDialog(
+          myPanel,
+          AndroidBundle.message("file.name.not.specified.error"),
+          CommonBundle.getErrorTitle());
       return;
     }
 
     String rootElement = getRootElement();
     if (!action.isChooseTagName() && rootElement.length() == 0) {
-      Messages.showErrorDialog(myPanel, AndroidBundle.message("root.element.not.specified.error"), CommonBundle.getErrorTitle());
+      Messages.showErrorDialog(
+          myPanel,
+          AndroidBundle.message("root.element.not.specified.error"),
+          CommonBundle.getErrorTitle());
       return;
     }
 
     final String subdirName = getSubdirName();
     if (subdirName.length() == 0) {
-      Messages.showErrorDialog(myPanel, AndroidBundle.message("directory.not.specified.error"), CommonBundle.getErrorTitle());
+      Messages.showErrorDialog(
+          myPanel,
+          AndroidBundle.message("directory.not.specified.error"),
+          CommonBundle.getErrorTitle());
       return;
     }
 
@@ -270,8 +298,10 @@
     }
     PsiDirectory resDir = getResourceDirectory();
     if (resDir == null) {
-      Messages.showErrorDialog(myPanel, AndroidBundle.message("check.resource.dir.error", myFacet.getModule()),
-                               CommonBundle.getErrorTitle());
+      Messages.showErrorDialog(
+          myPanel,
+          AndroidBundle.message("check.resource.dir.error", myFacet.getModule()),
+          CommonBundle.getErrorTitle());
       super.doOKAction();
       return;
     }
@@ -297,7 +327,8 @@
     if (myResDirectory != null) {
       return myResDirectory;
     }
-    return BlazeCreateResourceUtils.getResDirFromUI(myFacet.getModule().getProject(), myResDirCombo);
+    return BlazeCreateResourceUtils.getResDirFromUI(
+        myFacet.getModule().getProject(), myResDirCombo);
   }
 
   private String getSubdirName() {
@@ -318,19 +349,15 @@
     String name = myFileNameField.getText();
     if (name.length() == 0 || getNameError(name) != null) {
       return myFileNameField;
-    }
-    else if (myResourceTypeCombo.isVisible()) {
+    } else if (myResourceTypeCombo.isVisible()) {
       return myResourceTypeCombo;
-    }
-    else if (myRootElementFieldWrapper.isVisible()) {
+    } else if (myRootElementFieldWrapper.isVisible()) {
       return myRootElementField;
     }
     return myDirectoryNameTextField;
   }
 
-  /**
-   * Initially generated by IntelliJ from a .form file.
-   */
+  /** Initially generated by IntelliJ from a .form file. */
   private void setupUi() {
     myPanel = new JPanel();
     myPanel.setLayout(new GridLayoutManager(7, 3, new Insets(0, 0, 0, 0), -1, -1));
@@ -339,78 +366,244 @@
     myFileNameLabel.setText("File name:");
     myFileNameLabel.setDisplayedMnemonic('F');
     myFileNameLabel.setDisplayedMnemonicIndex(0);
-    myPanel.add(myFileNameLabel,
-                new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED,
-                                    GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
+    myPanel.add(
+        myFileNameLabel,
+        new GridConstraints(
+            0,
+            0,
+            1,
+            1,
+            GridConstraints.ANCHOR_WEST,
+            GridConstraints.FILL_NONE,
+            GridConstraints.SIZEPOLICY_FIXED,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
     myFileNameField = new JTextField();
-    myPanel.add(myFileNameField, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL,
-                                                     GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null,
-                                                     new Dimension(150, -1), null, 0, false));
+    myPanel.add(
+        myFileNameField,
+        new GridConstraints(
+            0,
+            1,
+            1,
+            1,
+            GridConstraints.ANCHOR_WEST,
+            GridConstraints.FILL_HORIZONTAL,
+            GridConstraints.SIZEPOLICY_WANT_GROW,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            new Dimension(150, -1),
+            null,
+            0,
+            false));
     myResTypeLabel = new JLabel();
     myResTypeLabel.setText("Resource type:");
     myResTypeLabel.setDisplayedMnemonic('R');
     myResTypeLabel.setDisplayedMnemonicIndex(0);
-    myPanel.add(myResTypeLabel,
-                new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED,
-                                    GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
+    myPanel.add(
+        myResTypeLabel,
+        new GridConstraints(
+            1,
+            0,
+            1,
+            1,
+            GridConstraints.ANCHOR_WEST,
+            GridConstraints.FILL_NONE,
+            GridConstraints.SIZEPOLICY_FIXED,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
     myUpDownHint = new JLabel();
     myUpDownHint.setToolTipText("Pressing Up or Down arrows while in editor changes the kind");
-    myPanel.add(myUpDownHint,
-                new GridConstraints(0, 2, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED,
-                                    GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
+    myPanel.add(
+        myUpDownHint,
+        new GridConstraints(
+            0,
+            2,
+            1,
+            1,
+            GridConstraints.ANCHOR_WEST,
+            GridConstraints.FILL_NONE,
+            GridConstraints.SIZEPOLICY_FIXED,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
     myResourceTypeCombo = new TemplateKindCombo();
-    myPanel.add(myResourceTypeCombo, new GridConstraints(1, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL,
-                                                         GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
-                                                         GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
+    myPanel.add(
+        myResourceTypeCombo,
+        new GridConstraints(
+            1,
+            1,
+            1,
+            1,
+            GridConstraints.ANCHOR_CENTER,
+            GridConstraints.FILL_HORIZONTAL,
+            GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
     myDeviceConfiguratorWrapper = new JPanel();
     myDeviceConfiguratorWrapper.setLayout(new BorderLayout(0, 0));
-    myPanel.add(myDeviceConfiguratorWrapper, new GridConstraints(5, 0, 1, 3, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH,
-                                                                 GridConstraints.SIZEPOLICY_CAN_SHRINK |
-                                                                 GridConstraints.SIZEPOLICY_CAN_GROW,
-                                                                 GridConstraints.SIZEPOLICY_CAN_SHRINK |
-                                                                 GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
+    myPanel.add(
+        myDeviceConfiguratorWrapper,
+        new GridConstraints(
+            5,
+            0,
+            1,
+            3,
+            GridConstraints.ANCHOR_CENTER,
+            GridConstraints.FILL_BOTH,
+            GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
+            GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
+            null,
+            null,
+            null,
+            0,
+            false));
     myErrorLabel = new JBLabel();
-    myPanel.add(myErrorLabel,
-                new GridConstraints(6, 0, 1, 3, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_FIXED,
-                                    GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
+    myPanel.add(
+        myErrorLabel,
+        new GridConstraints(
+            6,
+            0,
+            1,
+            3,
+            GridConstraints.ANCHOR_CENTER,
+            GridConstraints.FILL_BOTH,
+            GridConstraints.SIZEPOLICY_FIXED,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
     final JBLabel jBLabel1 = new JBLabel();
     jBLabel1.setText("Sub-directory:");
     jBLabel1.setDisplayedMnemonic('Y');
     jBLabel1.setDisplayedMnemonicIndex(12);
-    myPanel.add(jBLabel1,
-                new GridConstraints(4, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED,
-                                    GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
+    myPanel.add(
+        jBLabel1,
+        new GridConstraints(
+            4,
+            0,
+            1,
+            1,
+            GridConstraints.ANCHOR_WEST,
+            GridConstraints.FILL_NONE,
+            GridConstraints.SIZEPOLICY_FIXED,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
     myDirectoryNameTextField = new JTextField();
     myDirectoryNameTextField.setEditable(true);
     myDirectoryNameTextField.setEnabled(true);
-    myPanel.add(myDirectoryNameTextField, new GridConstraints(4, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL,
-                                                              GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null,
-                                                              new Dimension(150, -1), null, 0, false));
+    myPanel.add(
+        myDirectoryNameTextField,
+        new GridConstraints(
+            4,
+            1,
+            1,
+            1,
+            GridConstraints.ANCHOR_WEST,
+            GridConstraints.FILL_HORIZONTAL,
+            GridConstraints.SIZEPOLICY_WANT_GROW,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            new Dimension(150, -1),
+            null,
+            0,
+            false));
     myRootElementLabel = new JBLabel();
     myRootElementLabel.setText("Root element:");
     myRootElementLabel.setDisplayedMnemonic('E');
     myRootElementLabel.setDisplayedMnemonicIndex(5);
-    myPanel.add(myRootElementLabel,
-                new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED,
-                                    GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
+    myPanel.add(
+        myRootElementLabel,
+        new GridConstraints(
+            2,
+            0,
+            1,
+            1,
+            GridConstraints.ANCHOR_WEST,
+            GridConstraints.FILL_NONE,
+            GridConstraints.SIZEPOLICY_FIXED,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
     myRootElementFieldWrapper = new JPanel();
     myRootElementFieldWrapper.setLayout(new BorderLayout(0, 0));
-    myPanel.add(myRootElementFieldWrapper, new GridConstraints(2, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH,
-                                                               GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
-                                                               GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
+    myPanel.add(
+        myRootElementFieldWrapper,
+        new GridConstraints(
+            2,
+            1,
+            1,
+            1,
+            GridConstraints.ANCHOR_CENTER,
+            GridConstraints.FILL_BOTH,
+            GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
     myResDirLabel = new JBLabel();
     myResDirLabel.setText("Base directory:");
     myResDirLabel.setDisplayedMnemonic('B');
     myResDirLabel.setDisplayedMnemonicIndex(0);
-    myPanel.add(myResDirLabel,
-                new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED,
-                                    GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
+    myPanel.add(
+        myResDirLabel,
+        new GridConstraints(
+            3,
+            0,
+            1,
+            1,
+            GridConstraints.ANCHOR_WEST,
+            GridConstraints.FILL_NONE,
+            GridConstraints.SIZEPOLICY_FIXED,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
     myResDirCombo = new ComboboxWithBrowseButton();
-    myPanel.add(myResDirCombo, new GridConstraints(3, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL,
-                                                   GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null,
-                                                   0, false));
+    myPanel.add(
+        myResDirCombo,
+        new GridConstraints(
+            3,
+            1,
+            1,
+            1,
+            GridConstraints.ANCHOR_WEST,
+            GridConstraints.FILL_HORIZONTAL,
+            GridConstraints.SIZEPOLICY_CAN_GROW,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
     myFileNameLabel.setLabelFor(myFileNameField);
     jBLabel1.setLabelFor(myDirectoryNameTextField);
   }
-
 }
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 03ce15c..2b98ee9 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
@@ -25,36 +25,37 @@
 import com.google.idea.blaze.base.model.primitives.Label;
 import com.google.idea.blaze.base.rulemaps.SourceToRuleMap;
 import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
-import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
+import com.intellij.ide.util.DirectoryUtil;
+import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Computable;
 import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.openapi.vfs.VfsUtil;
 import com.intellij.openapi.vfs.VfsUtilCore;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.psi.PsiDirectory;
 import com.intellij.psi.PsiManager;
 import com.intellij.ui.ComboboxWithBrowseButton;
 import com.intellij.ui.components.JBLabel;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import javax.swing.*;
 import java.io.File;
 import java.util.Set;
+import javax.swing.JComboBox;
+import org.jetbrains.annotations.Nullable;
 
-/**
- * Utilities for setting up create resource actions and dialogs.
- */
+/** Utilities for setting up create resource actions and dialogs. */
 class BlazeCreateResourceUtils {
 
-  private static final String PLACEHOLDER_TEXT = "choose a res/ directory with dropdown or browse button";
+  private static final String PLACEHOLDER_TEXT =
+      "choose a res/ directory with dropdown or browse button";
 
-  static void setupResDirectoryChoices(@NotNull Project project, @Nullable VirtualFile contextFile,
-                                       @NotNull JBLabel resDirLabel,
-                                       @NotNull ComboboxWithBrowseButton resDirComboAndBrowser) {
-    resDirComboAndBrowser.addBrowseFolderListener(
-      project, FileChooserDescriptorFactory.createSingleFolderDescriptor());
-    BlazeProjectData blazeProjectData = BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
+  static void setupResDirectoryChoices(
+      Project project,
+      @Nullable VirtualFile contextFile,
+      JBLabel resDirLabel,
+      ComboboxWithBrowseButton resDirComboAndBrowser) {
+    // Reset the item list before filling it back up.
+    resDirComboAndBrowser.getComboBox().removeAllItems();
+    BlazeProjectData blazeProjectData =
+        BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
     if (blazeProjectData != null) {
       BlazeAndroidSyncData syncData = blazeProjectData.syncState.get(BlazeAndroidSyncData.class);
       if (syncData != null) {
@@ -62,23 +63,30 @@
         File fileFromContext = null;
         if (contextFile != null) {
           fileFromContext = VfsUtilCore.virtualToIoFile(contextFile);
-          labelsRelatedToContext = SourceToRuleMap.getInstance(project).getTargetsForSourceFile(fileFromContext);
+          labelsRelatedToContext =
+              SourceToRuleMap.getInstance(project).getTargetsForSourceFile(fileFromContext);
           if (labelsRelatedToContext.isEmpty()) {
             labelsRelatedToContext = null;
           }
         }
         // Sort:
-        // - the "closest" thing to the contextFile at the top
-        // - the rest of the direct dirs, then transitive dirs of the context rules, then any known res dir in the project
+        // - contextFile/res if contextFile is a directory,
+        //   to optimize the right click on directory case, or the "closest" string
+        //   match to the contextFile from the res directories known to blaze
+        // - the rest of the direct dirs, then transitive dirs of the context rules,
+        //   then any known res dir in the project
         //   as a backup, in alphabetical order.
         Set<File> resourceDirs = Sets.newTreeSet();
         Set<File> transitiveDirs = Sets.newTreeSet();
         Set<File> allResDirs = Sets.newTreeSet();
-        for (AndroidResourceModule androidResourceModule : syncData.importResult.androidResourceModules) {
-          // labelsRelatedToContext should include deps, but as a first pass we only check the rules themselves
+        for (AndroidResourceModule androidResourceModule :
+            syncData.importResult.androidResourceModules) {
+          // labelsRelatedToContext should include deps,
+          // but as a first pass we only check the rules themselves
           // for resources. If we come up empty, then have anyResDir as a backup.
           allResDirs.addAll(androidResourceModule.transitiveResources);
-          if (labelsRelatedToContext != null && !labelsRelatedToContext.contains(androidResourceModule.label)) {
+          if (labelsRelatedToContext != null
+              && !labelsRelatedToContext.contains(androidResourceModule.label)) {
             continue;
           }
           for (File resDir : androidResourceModule.resources) {
@@ -90,13 +98,36 @@
         }
         // No need to show some directories twice.
         transitiveDirs.removeAll(resourceDirs);
-        File closestDirToContext = null;
-        if (fileFromContext != null) {
-          closestDirToContext = findClosestDirToContext(fileFromContext.getPath(), resourceDirs);
-          closestDirToContext = closestDirToContext != null ? closestDirToContext :
-                                findClosestDirToContext(fileFromContext.getPath(), transitiveDirs);
-        }
+
         JComboBox resDirCombo = resDirComboAndBrowser.getComboBox();
+        // Allow the user to browse and overwrite some of the entries,
+        // in case our inference is wrong.
+        resDirCombo.setEditable(true);
+        // Optimize the right-click on a non-res directory (consider res directory right under that)
+        // After the use confirms the choice, a directory will be created if it is missing.
+        if (fileFromContext != null && fileFromContext.isDirectory()) {
+          File closestDirToContext = new File(fileFromContext.getPath(), "res");
+          resDirCombo.setSelectedItem(closestDirToContext);
+        } else {
+          // If we're not completely sure, let people know there are options
+          // via the placeholder text, and put the most likely on top.
+          String placeHolder = PLACEHOLDER_TEXT;
+          resDirCombo.addItem(placeHolder);
+          resDirCombo.setSelectedItem(placeHolder);
+          if (fileFromContext != null) {
+            File closestDirToContext =
+                findClosestDirToContext(fileFromContext.getPath(), resourceDirs);
+            closestDirToContext =
+                closestDirToContext != null
+                    ? closestDirToContext
+                    : findClosestDirToContext(fileFromContext.getPath(), transitiveDirs);
+            if (closestDirToContext != null) {
+              resDirCombo.addItem(closestDirToContext);
+              resourceDirs.remove(closestDirToContext);
+              transitiveDirs.remove(closestDirToContext);
+            }
+          }
+        }
         if (!resourceDirs.isEmpty() || !transitiveDirs.isEmpty()) {
           for (File resourceDir : resourceDirs) {
             resDirCombo.addItem(resourceDir);
@@ -104,22 +135,11 @@
           for (File resourceDir : transitiveDirs) {
             resDirCombo.addItem(resourceDir);
           }
-        }
-        else {
+        } else {
           for (File resourceDir : allResDirs) {
             resDirCombo.addItem(resourceDir);
           }
         }
-        // Allow the user to browse and overwrite some of the entries.
-        resDirCombo.setEditable(true);
-        if (closestDirToContext != null) {
-          resDirCombo.setSelectedItem(closestDirToContext);
-        }
-        else {
-          String placeHolder = PLACEHOLDER_TEXT;
-          resDirCombo.insertItemAt(placeHolder, 0);
-          resDirCombo.setSelectedItem(placeHolder);
-        }
         resDirComboAndBrowser.setVisible(true);
         resDirLabel.setVisible(true);
       }
@@ -141,24 +161,33 @@
 
   static PsiDirectory getResDirFromUI(Project project, ComboboxWithBrowseButton directoryCombo) {
     PsiManager psiManager = PsiManager.getInstance(project);
-    Object selectedItem = directoryCombo.getComboBox().getSelectedItem();
-    VirtualFile file = null;
-    if(selectedItem instanceof File) {
-      file = VfsUtil.findFileByIoFile((File)selectedItem, true);
+    Object selectedItem = directoryCombo.getComboBox().getEditor().getItem();
+    File selectedFile = null;
+    if (selectedItem instanceof File) {
+      selectedFile = (File) selectedItem;
     } else if (selectedItem instanceof String) {
-      String selectedDir = (String)selectedItem;
+      String selectedDir = (String) selectedItem;
       if (!selectedDir.equals(PLACEHOLDER_TEXT)) {
-        file = VfsUtil.findFileByIoFile(new File(selectedDir), true);
+        selectedFile = new File(selectedDir);
       }
     }
-    if (file != null) {
-      return psiManager.findDirectory(file);
+    if (selectedFile == null) {
+      return null;
     }
-    return null;
+    final File finalSelectedFile = selectedFile;
+    return ApplicationManager.getApplication()
+        .runWriteAction(
+            new Computable<PsiDirectory>() {
+              @Override
+              public PsiDirectory compute() {
+                return DirectoryUtil.mkdirs(psiManager, finalSelectedFile.getPath());
+              }
+            });
   }
 
-   static VirtualFile getResDirFromDataContext(VirtualFile contextFile) {
-    // Check if the contextFile is somewhere in the <path>/res/resType/foo.xml hierarchy and return <path>/res/.
+  static VirtualFile getResDirFromDataContext(VirtualFile contextFile) {
+    // Check if the contextFile is somewhere in
+    // the <path>/res/resType/foo.xml hierarchy and return <path>/res/.
     if (contextFile.isDirectory()) {
       if (contextFile.getName().equalsIgnoreCase(SdkConstants.FD_RES)) {
         return contextFile;
@@ -169,11 +198,11 @@
           return parent;
         }
       }
-    }
-    else {
+    } else {
       VirtualFile parent = contextFile.getParent();
       if (parent != null && ResourceFolderType.getFolderType(parent.getName()) != null) {
-        // Otherwise, the contextFile is a file w/ a parent that is plausible. Recurse one level, on the parent.
+        // Otherwise, the contextFile is a file w/ a parent that is plausible.
+        // Recurse one level, on the parent.
         return getResDirFromDataContext(parent);
       }
     }
diff --git a/aswb/src/com/google/idea/blaze/android/resources/actions/BlazeCreateXmlResourcePanel.java b/aswb/src/com/google/idea/blaze/android/resources/actions/BlazeCreateXmlResourcePanel.java
index 15b79fb..cf4a6d8 100644
--- a/aswb/src/com/google/idea/blaze/android/resources/actions/BlazeCreateXmlResourcePanel.java
+++ b/aswb/src/com/google/idea/blaze/android/resources/actions/BlazeCreateXmlResourcePanel.java
@@ -19,6 +19,7 @@
 import com.android.resources.ResourceType;
 import com.android.tools.idea.res.ResourceNameValidator;
 import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
 import com.intellij.openapi.module.Module;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.ui.ValidationInfo;
@@ -30,6 +31,20 @@
 import com.intellij.ui.components.JBLabel;
 import com.intellij.uiDesigner.core.GridConstraints;
 import com.intellij.uiDesigner.core.GridLayoutManager;
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.Insets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
 import org.jetbrains.android.actions.CreateXmlResourceDialog;
 import org.jetbrains.android.actions.CreateXmlResourcePanel;
 import org.jetbrains.android.actions.CreateXmlResourceSubdirPanel;
@@ -38,17 +53,11 @@
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import javax.swing.*;
-import java.awt.*;
-import java.util.*;
-import java.util.List;
-import java.util.function.Function;
-
 /**
- * Embeddable UI for selecting how to create a new resource value (which XML file and directories to place it).
+ * Embeddable UI for selecting how to create a new resource value (which XML file and directories to
+ * place it).
  */
-public class BlazeCreateXmlResourcePanel implements CreateXmlResourcePanel,
-                                                    Parent {
+public class BlazeCreateXmlResourcePanel implements CreateXmlResourcePanel, Parent {
 
   private JPanel myPanel;
   private JTextField myNameField;
@@ -60,28 +69,29 @@
   private ComboboxWithBrowseButton myResDirCombo;
   private JBLabel myFileNameLabel;
 
-  private final @NotNull Module myModule;
-  private final @NotNull ResourceType myResourceType;
+  private final Module myModule;
+  private final ResourceType myResourceType;
 
   private JPanel myDirectoriesPanel;
   private JBLabel myDirectoriesLabel;
   private CreateXmlResourceSubdirPanel mySubdirPanel;
 
   private ResourceNameValidator myResourceNameValidator;
-  private @Nullable VirtualFile myContextFile;
-  private @Nullable VirtualFile myResDirectory;
+  @Nullable private VirtualFile myContextFile;
+  @Nullable private VirtualFile myResDirectory;
 
-  public BlazeCreateXmlResourcePanel(@NotNull Module module,
-                                     @NotNull ResourceType resourceType,
-                                     @NotNull ResourceFolderType folderType,
-                                     @Nullable String resourceName,
-                                     @Nullable String resourceValue,
-                                     boolean chooseName,
-                                     boolean chooseValue,
-                                     boolean chooseFilename,
-                                     @Nullable VirtualFile defaultFile,
-                                     @Nullable VirtualFile contextFile,
-                                     @NotNull Function<Module, ResourceNameValidator> nameValidatorFactory) {
+  public BlazeCreateXmlResourcePanel(
+      @NotNull Module module,
+      @NotNull ResourceType resourceType,
+      @NotNull ResourceFolderType folderType,
+      @Nullable String resourceName,
+      @Nullable String resourceValue,
+      boolean chooseName,
+      boolean chooseValue,
+      boolean chooseFilename,
+      @Nullable VirtualFile defaultFile,
+      @Nullable VirtualFile contextFile,
+      @NotNull Function<Module, ResourceNameValidator> nameValidatorFactory) {
     setupUi();
     setChangeNameVisible(false);
     setChangeValueVisible(false);
@@ -90,9 +100,9 @@
     myContextFile = contextFile;
     if (chooseName) {
       setChangeNameVisible(true);
-      if (!StringUtil.isEmpty(resourceName)) {
-        myNameField.setText(resourceName);
-      }
+    }
+    if (!StringUtil.isEmpty(resourceName)) {
+      myNameField.setText(resourceName);
     }
 
     if (chooseValue) {
@@ -106,8 +116,10 @@
 
     ApplicationManager.getApplication().assertReadAccessAllowed();
     // Set up UI to choose the base directory if needed (use context to prune selection).
-    myResDirCombo.setVisible(false);
     myResDirLabel.setVisible(false);
+    myResDirCombo.setVisible(false);
+    myResDirCombo.addBrowseFolderListener(
+        module.getProject(), FileChooserDescriptorFactory.createSingleFolderDescriptor());
     setupResourceDirectoryCombo();
 
     if (defaultFile == null) {
@@ -119,7 +131,8 @@
     }
 
     myDirectoriesLabel.setLabelFor(myDirectoriesPanel);
-    mySubdirPanel = new CreateXmlResourceSubdirPanel(module.getProject(), folderType, myDirectoriesPanel, this);
+    mySubdirPanel =
+        new CreateXmlResourceSubdirPanel(module.getProject(), folderType, myDirectoriesPanel, this);
     myResourceNameValidator = nameValidatorFactory.apply(getModule());
 
     if (defaultFile != null) {
@@ -130,23 +143,27 @@
   private void setupResourceDirectoryCombo() {
     Project project = myModule.getProject();
     if (myContextFile != null) {
-      // Try to figure out res/ dir from context (e.g., while refactoring an xml file that's a child of a res/ directory).
+      // Try to figure out res/ dir from context
+      // (e.g., while refactoring an xml file that's a child of a res/ directory).
       // We currently take the parent and hide the combo box.
       PsiManager manager = PsiManager.getInstance(project);
-      VirtualFile virtualDirectory = BlazeCreateResourceUtils.getResDirFromDataContext(myContextFile);
-      PsiDirectory directory = virtualDirectory != null ? manager.findDirectory(virtualDirectory) : null;
+      VirtualFile virtualDirectory =
+          BlazeCreateResourceUtils.getResDirFromDataContext(myContextFile);
+      PsiDirectory directory =
+          virtualDirectory != null ? manager.findDirectory(virtualDirectory) : null;
       if (directory != null) {
         myResDirectory = directory.getVirtualFile();
-      }
-      else {
-        // As a last resort, if we have poor context, e.g., quick fix from within a .java file, set up the UI
+      } else {
+        // As a last resort, if we have poor context,
+        // e.g., quick fix from within a .java file, set up the UI
         // based on the deps of the .java file.
-        BlazeCreateResourceUtils.setupResDirectoryChoices(project, myContextFile, myResDirLabel, myResDirCombo);
+        BlazeCreateResourceUtils.setupResDirectoryChoices(
+            project, myContextFile, myResDirLabel, myResDirCombo);
       }
-    }
-    else {
+    } else {
       // As a last resort, if we have no context at all, set up some UI.
-      BlazeCreateResourceUtils.setupResDirectoryChoices(project, null, myResDirLabel, myResDirCombo);
+      BlazeCreateResourceUtils.setupResDirectoryChoices(
+          project, null, myResDirLabel, myResDirCombo);
     }
   }
 
@@ -193,7 +210,8 @@
       return myResDirectory;
     }
     if (myResDirCombo.isVisible()) {
-      PsiDirectory directory = BlazeCreateResourceUtils.getResDirFromUI(myModule.getProject(), myResDirCombo);
+      PsiDirectory directory =
+          BlazeCreateResourceUtils.getResDirFromUI(myModule.getProject(), myResDirCombo);
       return directory != null ? directory.getVirtualFile() : null;
     }
     return null;
@@ -206,7 +224,7 @@
 
   @Override
   public String getFileName() {
-    return ((String)myFileNameCombo.getEditor().getItem()).trim();
+    return ((String) myFileNameCombo.getEditor().getItem()).trim();
   }
 
   @Override
@@ -218,22 +236,19 @@
 
     if (myNameField.isVisible() && resourceName.isEmpty()) {
       return new ValidationInfo("specify resource name", myNameField);
-    }
-    else if (myNameField.isVisible() && !AndroidResourceUtil.isCorrectAndroidResourceName(resourceName)) {
+    } else if (myNameField.isVisible()
+        && !AndroidResourceUtil.isCorrectAndroidResourceName(resourceName)) {
       return new ValidationInfo(resourceName + " is not correct resource name", myNameField);
-    }
-    else if (fileName.isEmpty()) {
+    } else if (fileName.isEmpty()) {
       return new ValidationInfo("specify file name", myFileNameCombo);
-    }
-    else if (resourceDir == null) {
+    } else if (resourceDir == null) {
       return new ValidationInfo("specify a resource directory", myResDirCombo);
-    }
-    else if (directoryNames.isEmpty()) {
+    } else if (directoryNames.isEmpty()) {
       return new ValidationInfo("choose directories", myDirectoriesPanel);
     }
 
-    return CreateXmlResourceDialog.checkIfResourceAlreadyExists(myModule.getProject(), resourceDir, resourceName,
-                                                                myResourceType, directoryNames, fileName);
+    return CreateXmlResourceDialog.checkIfResourceAlreadyExists(
+        myModule.getProject(), resourceDir, resourceName, myResourceType, directoryNames, fileName);
   }
 
   @Override
@@ -247,19 +262,15 @@
     return myModule;
   }
 
-  /**
-   * @see CreateXmlResourceDialog#getPreferredFocusedComponent()
-   */
+  /** @see CreateXmlResourceDialog#getPreferredFocusedComponent() */
   @Override
   public JComponent getPreferredFocusedComponent() {
     String name = myNameField.getText();
     if (name.isEmpty()) {
       return myNameField;
-    }
-    else if (myValueField.isVisible()) {
+    } else if (myValueField.isVisible()) {
       return myValueField;
-    }
-    else {
+    } else {
       return myFileNameCombo;
     }
   }
@@ -301,9 +312,7 @@
     myFileNameCombo.getEditor().setItem(oldItem);
   }
 
-  /**
-   * Initially generated by IntelliJ from a .form file.
-   */
+  /** Initially generated by IntelliJ from a .form file. */
   private void setupUi() {
     myPanel = new JPanel();
     myPanel.setLayout(new GridLayoutManager(6, 2, new Insets(0, 0, 5, 0), -1, -1));
@@ -311,63 +320,191 @@
     myNameLabel.setText("Resource name:");
     myNameLabel.setDisplayedMnemonic('N');
     myNameLabel.setDisplayedMnemonicIndex(9);
-    myPanel.add(myNameLabel,
-                new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED,
-                                    GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
+    myPanel.add(
+        myNameLabel,
+        new GridConstraints(
+            0,
+            0,
+            1,
+            1,
+            GridConstraints.ANCHOR_WEST,
+            GridConstraints.FILL_NONE,
+            GridConstraints.SIZEPOLICY_FIXED,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
     myNameField = new JTextField();
-    myPanel.add(myNameField, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL,
-                                                 GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null,
-                                                 new Dimension(150, -1), null, 0, false));
+    myPanel.add(
+        myNameField,
+        new GridConstraints(
+            0,
+            1,
+            1,
+            1,
+            GridConstraints.ANCHOR_WEST,
+            GridConstraints.FILL_HORIZONTAL,
+            GridConstraints.SIZEPOLICY_WANT_GROW,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            new Dimension(150, -1),
+            null,
+            0,
+            false));
     myFileNameLabel = new JBLabel();
     myFileNameLabel.setText("File name:");
     myFileNameLabel.setDisplayedMnemonic('F');
     myFileNameLabel.setDisplayedMnemonicIndex(0);
-    myPanel.add(myFileNameLabel,
-                new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED,
-                                    GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
+    myPanel.add(
+        myFileNameLabel,
+        new GridConstraints(
+            3,
+            0,
+            1,
+            1,
+            GridConstraints.ANCHOR_WEST,
+            GridConstraints.FILL_NONE,
+            GridConstraints.SIZEPOLICY_FIXED,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
     myDirectoriesPanel = new JPanel();
     myDirectoriesPanel.setLayout(new BorderLayout(0, 0));
-    myPanel.add(myDirectoriesPanel, new GridConstraints(5, 0, 1, 2, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH,
-                                                        GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
-                                                        GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null,
-                                                        null, null, 0, false));
+    myPanel.add(
+        myDirectoriesPanel,
+        new GridConstraints(
+            5,
+            0,
+            1,
+            2,
+            GridConstraints.ANCHOR_CENTER,
+            GridConstraints.FILL_BOTH,
+            GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
+            GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
+            null,
+            null,
+            null,
+            0,
+            false));
     myDirectoriesLabel = new JBLabel();
     myDirectoriesLabel.setText("Create the resource in directories:");
     myDirectoriesLabel.setDisplayedMnemonic('C');
     myDirectoriesLabel.setDisplayedMnemonicIndex(0);
-    myPanel.add(myDirectoriesLabel,
-                new GridConstraints(4, 0, 1, 2, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED,
-                                    GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
+    myPanel.add(
+        myDirectoriesLabel,
+        new GridConstraints(
+            4,
+            0,
+            1,
+            2,
+            GridConstraints.ANCHOR_WEST,
+            GridConstraints.FILL_NONE,
+            GridConstraints.SIZEPOLICY_FIXED,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
     myValueLabel = new JBLabel();
     myValueLabel.setText("Resource value:");
     myValueLabel.setDisplayedMnemonic('V');
     myValueLabel.setDisplayedMnemonicIndex(9);
-    myPanel.add(myValueLabel,
-                new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED,
-                                    GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
+    myPanel.add(
+        myValueLabel,
+        new GridConstraints(
+            1,
+            0,
+            1,
+            1,
+            GridConstraints.ANCHOR_WEST,
+            GridConstraints.FILL_NONE,
+            GridConstraints.SIZEPOLICY_FIXED,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
     myValueField = new JTextField();
-    myPanel.add(myValueField, new GridConstraints(1, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL,
-                                                  GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null,
-                                                  new Dimension(150, -1), null, 0, false));
+    myPanel.add(
+        myValueField,
+        new GridConstraints(
+            1,
+            1,
+            1,
+            1,
+            GridConstraints.ANCHOR_WEST,
+            GridConstraints.FILL_HORIZONTAL,
+            GridConstraints.SIZEPOLICY_WANT_GROW,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            new Dimension(150, -1),
+            null,
+            0,
+            false));
     myFileNameCombo = new JComboBox();
     myFileNameCombo.setEditable(true);
-    myPanel.add(myFileNameCombo, new GridConstraints(3, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL,
-                                                     GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null,
-                                                     null, 0, false));
+    myPanel.add(
+        myFileNameCombo,
+        new GridConstraints(
+            3,
+            1,
+            1,
+            1,
+            GridConstraints.ANCHOR_WEST,
+            GridConstraints.FILL_HORIZONTAL,
+            GridConstraints.SIZEPOLICY_CAN_GROW,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
     myResDirLabel = new JBLabel();
     myResDirLabel.setText("Base directory:");
     myResDirLabel.setDisplayedMnemonic('B');
     myResDirLabel.setDisplayedMnemonicIndex(0);
-    myPanel.add(myResDirLabel,
-                new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED,
-                                    GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
+    myPanel.add(
+        myResDirLabel,
+        new GridConstraints(
+            2,
+            0,
+            1,
+            1,
+            GridConstraints.ANCHOR_WEST,
+            GridConstraints.FILL_NONE,
+            GridConstraints.SIZEPOLICY_FIXED,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
     myResDirCombo = new ComboboxWithBrowseButton();
-    myPanel.add(myResDirCombo, new GridConstraints(2, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL,
-                                                   GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null,
-                                                   0, false));
+    myPanel.add(
+        myResDirCombo,
+        new GridConstraints(
+            2,
+            1,
+            1,
+            1,
+            GridConstraints.ANCHOR_WEST,
+            GridConstraints.FILL_HORIZONTAL,
+            GridConstraints.SIZEPOLICY_CAN_GROW,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
     myNameLabel.setLabelFor(myNameField);
     myFileNameLabel.setLabelFor(myFileNameCombo);
     myValueLabel.setLabelFor(myValueField);
   }
-
 }
diff --git a/aswb/src/com/google/idea/blaze/android/resources/actions/BlazeNewResourceCreationHandler.java b/aswb/src/com/google/idea/blaze/android/resources/actions/BlazeNewResourceCreationHandler.java
index 03a34a9..6bf7deb 100644
--- a/aswb/src/com/google/idea/blaze/android/resources/actions/BlazeNewResourceCreationHandler.java
+++ b/aswb/src/com/google/idea/blaze/android/resources/actions/BlazeNewResourceCreationHandler.java
@@ -25,17 +25,18 @@
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.psi.PsiDirectory;
-import org.jetbrains.android.actions.*;
+import java.util.Collection;
+import java.util.function.Function;
+import org.jetbrains.android.actions.CreateResourceDirectoryDialogBase;
+import org.jetbrains.android.actions.CreateResourceFileDialogBase;
+import org.jetbrains.android.actions.CreateTypedResourceFileAction;
+import org.jetbrains.android.actions.CreateXmlResourcePanel;
+import org.jetbrains.android.actions.NewResourceCreationHandler;
 import org.jetbrains.android.facet.AndroidFacet;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import java.util.Collection;
-import java.util.function.Function;
-
-/**
- * Decides which create resource dialogs to use for Blaze projects.
- */
+/** Decides which create resource dialogs to use for Blaze projects. */
 public class BlazeNewResourceCreationHandler implements NewResourceCreationHandler {
 
   @Override
@@ -46,47 +47,68 @@
   @NotNull
   @Override
   public CreateResourceDirectoryDialogBase createNewResourceDirectoryDialog(
-    @NotNull Project project,
-    @Nullable Module module,
-    @Nullable ResourceFolderType resType,
-    @Nullable PsiDirectory resDirectory,
-    @Nullable DataContext dataContext,
-    @NotNull CreateResourceDirectoryDialogBase.ValidatorFactory validatorFactory) {
-    return new BlazeCreateResourceDirectoryDialog(project, module, resType, resDirectory, dataContext, validatorFactory);
+      @NotNull Project project,
+      @Nullable Module module,
+      @Nullable ResourceFolderType resType,
+      @Nullable PsiDirectory resDirectory,
+      @Nullable DataContext dataContext,
+      @NotNull CreateResourceDirectoryDialogBase.ValidatorFactory validatorFactory) {
+    return new BlazeCreateResourceDirectoryDialog(
+        project, module, resType, resDirectory, dataContext, validatorFactory);
   }
 
   @NotNull
   @Override
   public CreateResourceFileDialogBase createNewResourceFileDialog(
-    @NotNull AndroidFacet facet,
-    @NotNull Collection<CreateTypedResourceFileAction> actions,
-    @Nullable ResourceFolderType folderType,
-    @Nullable String filename,
-    @Nullable String rootElement,
-    @Nullable FolderConfiguration folderConfiguration,
-    boolean chooseFileName,
-    boolean chooseModule,
-    @Nullable PsiDirectory resDirectory,
-    @Nullable DataContext dataContext,
-    @NotNull CreateResourceFileDialogBase.ValidatorFactory validatorFactory) {
-    return new BlazeCreateResourceFileDialog(facet, actions, folderType, filename, rootElement, folderConfiguration, chooseFileName,
-                                             chooseModule, resDirectory, dataContext, validatorFactory);
+      @NotNull AndroidFacet facet,
+      @NotNull Collection<CreateTypedResourceFileAction> actions,
+      @Nullable ResourceFolderType folderType,
+      @Nullable String filename,
+      @Nullable String rootElement,
+      @Nullable FolderConfiguration folderConfiguration,
+      boolean chooseFileName,
+      boolean chooseModule,
+      @Nullable PsiDirectory resDirectory,
+      @Nullable DataContext dataContext,
+      @NotNull CreateResourceFileDialogBase.ValidatorFactory validatorFactory) {
+    return new BlazeCreateResourceFileDialog(
+        facet,
+        actions,
+        folderType,
+        filename,
+        rootElement,
+        folderConfiguration,
+        chooseFileName,
+        chooseModule,
+        resDirectory,
+        dataContext,
+        validatorFactory);
   }
 
   @Override
   public CreateXmlResourcePanel createNewResourceValuePanel(
-    @NotNull Module module,
-    @NotNull ResourceType resourceType,
-    @NotNull ResourceFolderType folderType,
-    @Nullable String resourceName,
-    @Nullable String resourceValue,
-    boolean chooseName,
-    boolean chooseValue,
-    boolean chooseFilename,
-    @Nullable VirtualFile defaultFile,
-    @Nullable VirtualFile contextFile,
-    @NotNull Function<Module, ResourceNameValidator> nameValidatorFactory) {
-    return new BlazeCreateXmlResourcePanel(module, resourceType, folderType, resourceName, resourceValue,
-                                           chooseName, chooseValue, chooseFilename, defaultFile, contextFile, nameValidatorFactory);
+      @NotNull Module module,
+      @NotNull ResourceType resourceType,
+      @NotNull ResourceFolderType folderType,
+      @Nullable String resourceName,
+      @Nullable String resourceValue,
+      boolean chooseName,
+      boolean chooseValue,
+      boolean chooseFilename,
+      @Nullable VirtualFile defaultFile,
+      @Nullable VirtualFile contextFile,
+      @NotNull Function<Module, ResourceNameValidator> nameValidatorFactory) {
+    return new BlazeCreateXmlResourcePanel(
+        module,
+        resourceType,
+        folderType,
+        resourceName,
+        resourceValue,
+        chooseName,
+        chooseValue,
+        chooseFilename,
+        defaultFile,
+        contextFile,
+        nameValidatorFactory);
   }
 }
diff --git a/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRuleConfigurationFactory.java b/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRuleConfigurationFactory.java
new file mode 100644
index 0000000..008982a
--- /dev/null
+++ b/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRuleConfigurationFactory.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.android.run;
+
+import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+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;
+import com.google.idea.blaze.base.run.BlazeRuleConfigurationFactory;
+import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
+import com.intellij.execution.configurations.ConfigurationFactory;
+import com.intellij.execution.configurations.RunConfiguration;
+
+/** Creates run configurations for android_binary and android_test. */
+public class BlazeAndroidRuleConfigurationFactory extends BlazeRuleConfigurationFactory {
+  @Override
+  public boolean handlesRule(
+      WorkspaceLanguageSettings workspaceLanguageSettings, RuleIdeInfo rule) {
+    return rule.kindIsOneOf(Kind.ANDROID_BINARY, Kind.ANDROID_TEST);
+  }
+
+  @Override
+  protected ConfigurationFactory getConfigurationFactory() {
+    return BlazeCommandRunConfigurationType.getInstance().getFactory();
+  }
+
+  @Override
+  public void setupConfiguration(RunConfiguration configuration, RuleIdeInfo rule) {
+    final BlazeCommandRunConfiguration blazeConfig = (BlazeCommandRunConfiguration) configuration;
+    blazeConfig.setTarget(rule.label);
+    blazeConfig.setGeneratedName();
+  }
+}
diff --git a/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfiguration.java b/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfiguration.java
deleted file mode 100644
index ff5dc7f..0000000
--- a/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfiguration.java
+++ /dev/null
@@ -1,44 +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.run;
-
-import com.google.common.collect.ImmutableList;
-import com.google.idea.blaze.android.run.runner.BlazeAndroidRunConfigurationRunner;
-import com.google.idea.blaze.android.run.runner.BlazeAndroidRunContext;
-import com.google.idea.blaze.base.model.primitives.Label;
-import com.google.idea.blaze.base.run.BlazeRunConfiguration;
-import com.intellij.execution.runners.ExecutionEnvironment;
-import com.intellij.openapi.project.Project;
-import org.jetbrains.android.facet.AndroidFacet;
-
-/**
- * Common interface between android_binary and android_test configurations.
- */
-public interface BlazeAndroidRunConfiguration extends BlazeRunConfiguration {
-
-  BlazeAndroidRunContext createRunContext(Project project,
-                                          AndroidFacet facet,
-                                          ExecutionEnvironment env,
-                                          ImmutableList<String> buildFlags);
-
-  BlazeAndroidRunConfigurationRunner getRunner();
-
-  BlazeAndroidRunConfigurationCommonState getCommonState();
-
-  @Override
-  Label getTarget();
-}
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 06b4262..2acea9b 100644
--- a/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationCommonState.java
+++ b/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationCommonState.java
@@ -15,55 +15,30 @@
  */
 package com.google.idea.blaze.android.run;
 
-import com.android.tools.idea.run.ValidationError;
-import com.google.common.collect.ImmutableList;
-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.google.idea.blaze.base.run.rulefinder.RuleFinder;
-import com.google.idea.blaze.base.settings.Blaze;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.DefaultJDOMExternalizer;
-import com.intellij.openapi.util.InvalidDataException;
-import com.intellij.openapi.util.JDOMExternalizable;
-import com.intellij.openapi.util.WriteExternalException;
-import org.jdom.Element;
-import org.jetbrains.annotations.Nullable;
-
-import java.util.List;
-
 import static com.google.idea.blaze.android.cppapi.NdkSupport.NDK_SUPPORT;
 
+import com.google.common.collect.ImmutableList;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.WriteExternalException;
+import java.util.List;
+import org.jdom.Element;
+
 /**
- * A shared state class for run configurations targeting Blaze Android rules.
- * We implement the deprecated JDomExternalizable to fit with the other run configs.
+ * A shared state class for run configurations targeting Blaze Android rules. We implement the
+ * deprecated JDomExternalizable to fit with the other run configs.
  */
-public class BlazeAndroidRunConfigurationCommonState implements JDOMExternalizable {
-  private static final String TARGET_ATTR = "blaze-target";
+public class BlazeAndroidRunConfigurationCommonState implements BlazeAndroidRunConfigurationState {
   private static final String USER_FLAG_TAG = "blaze-user-flag";
   private static final String NATIVE_DEBUG_ATTR = "blaze-native-debug";
 
-  @Nullable private Label target;
   private List<String> userFlags;
   private boolean nativeDebuggingEnabled = false;
 
-  /**
-   * Creates a configuration state initialized with the given rule and flags.
-   */
-  public BlazeAndroidRunConfigurationCommonState(@Nullable Label target, List<String> userFlags) {
-    this.target = target;
+  /** Creates a configuration state initialized with the given flags. */
+  public BlazeAndroidRunConfigurationCommonState(List<String> userFlags) {
     this.userFlags = userFlags;
   }
 
-  @Nullable
-  public Label getTarget() {
-    return target;
-  }
-
-  public void setTarget(@Nullable Label target) {
-    this.target = target;
-  }
-
   public List<String> getUserFlags() {
     return userFlags;
   }
@@ -80,34 +55,8 @@
     this.nativeDebuggingEnabled = nativeDebuggingEnabled;
   }
 
-  public void checkConfiguration(Project project, Kind kind, List<ValidationError> errors) {
-    RuleIdeInfo rule = target != null ? RuleFinder.getInstance().ruleForTarget(project, target) : null;
-    if (rule == null) {
-      errors.add(ValidationError.fatal(
-        String.format("No existing %s rule selected.", Blaze.buildSystemName(project))
-      ));
-    }
-    else if (!rule.kindIsOneOf(kind)) {
-      errors.add(ValidationError.fatal(
-        String.format("Selected %s rule is not %s", Blaze.buildSystemName(project), kind.toString())
-      ));
-    }
-  }
-
   @Override
   public void readExternal(Element element) throws InvalidDataException {
-    DefaultJDOMExternalizer.readExternal(this, element);
-
-    target = null;
-    String targetString = element.getAttributeValue(TARGET_ATTR);
-    if (targetString != null) {
-      try {
-        target = new Label(targetString);
-      }
-      catch (IllegalArgumentException e) {
-        throw new InvalidDataException("Bad configuration target", e);
-      }
-    }
     ImmutableList.Builder<String> flagsBuilder = ImmutableList.builder();
     for (Element e : element.getChildren(USER_FLAG_TAG)) {
       String flag = e.getTextTrim();
@@ -121,11 +70,6 @@
 
   @Override
   public void writeExternal(Element element) throws WriteExternalException {
-    DefaultJDOMExternalizer.writeExternal(this, element);
-
-    if (target != null) {
-      element.setAttribute(TARGET_ATTR, target.toString());
-    }
     for (String flag : userFlags) {
       Element child = new Element(USER_FLAG_TAG);
       child.setText(flag);
diff --git a/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationCommonStateEditor.java b/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationCommonStateEditor.java
index 04b74ae..3a0c511 100644
--- a/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationCommonStateEditor.java
+++ b/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationCommonStateEditor.java
@@ -17,51 +17,27 @@
 
 import com.google.common.base.Strings;
 import com.google.common.collect.Lists;
-import com.google.common.collect.Ordering;
 import com.google.idea.blaze.android.cppapi.NdkSupport;
-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.google.idea.blaze.base.run.rulefinder.RuleFinder;
 import com.google.idea.blaze.base.settings.Blaze;
-import com.google.idea.blaze.base.ui.ComboWrapper;
-import com.intellij.openapi.options.ConfigurationException;
 import com.intellij.openapi.project.Project;
-import com.intellij.ui.ListCellRendererWrapper;
 import com.intellij.util.execution.ParametersListUtil;
-
-import javax.swing.*;
 import java.util.List;
+import javax.swing.JCheckBox;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JTextArea;
 
 /**
- * A simplified, Blaze-specific variant of
- * {@link org.jetbrains.android.run.AndroidRunConfigurationEditor}.
+ * A simplified, Blaze-specific variant of {@link
+ * org.jetbrains.android.run.AndroidRunConfigurationEditor}.
  */
 public class BlazeAndroidRunConfigurationCommonStateEditor {
-  private static final Ordering<RuleIdeInfo> ALPHABETICAL = Ordering.usingToString().onResultOf((ruleIdeInfo) -> ruleIdeInfo.label);
-
   private final Project project;
-  private final Kind kind;
-  private final ComboWrapper<RuleIdeInfo> ruleCombo;
   private final JTextArea userFlagsField;
   private final JCheckBox enableNativeDebuggingCheckBox;
 
-  public BlazeAndroidRunConfigurationCommonStateEditor(
-    Project project,
-    Kind kind) {
+  public BlazeAndroidRunConfigurationCommonStateEditor(Project project) {
     this.project = project;
-    this.kind = kind;
-
-    ruleCombo = ComboWrapper.create();
-    List<RuleIdeInfo> rules = ALPHABETICAL.sortedCopy(RuleFinder.getInstance().rulesOfKinds(project, kind));
-    ruleCombo.setItems(rules);
-    ruleCombo.setRenderer(new ListCellRendererWrapper<RuleIdeInfo>() {
-      @Override
-      public void customize(JList list, RuleIdeInfo value, int index,
-                            boolean selected, boolean hasFocus) {
-        setText(value == null ? "" : value.label.toString());
-      }
-    });
 
     userFlagsField = new JTextArea(3 /* rows */, 50 /* columns */);
     userFlagsField.setToolTipText("e.g. --config=android_arm");
@@ -69,33 +45,24 @@
   }
 
   public void resetEditorFrom(BlazeAndroidRunConfigurationCommonState runConfigurationState) {
-    Label target = runConfigurationState.getTarget();
-    RuleIdeInfo rule = target != null ? RuleFinder.getInstance().ruleForTarget(project, target) : null;
-    ruleCombo.setSelectedItem(rule);
     userFlagsField.setText(ParametersListUtil.join(runConfigurationState.getUserFlags()));
     enableNativeDebuggingCheckBox.setSelected(runConfigurationState.isNativeDebuggingEnabled());
   }
 
-  public void applyEditorTo(BlazeAndroidRunConfigurationCommonState runConfigurationState)
-    throws ConfigurationException {
-    RuleIdeInfo rule = ruleCombo.getSelectedItem();
-    Label target = rule != null ? rule.label : null;
-    runConfigurationState.setTarget(target);
-    List<String> userFlags = ParametersListUtil.parse(Strings.nullToEmpty(userFlagsField.getText()));
+  public void applyEditorTo(BlazeAndroidRunConfigurationCommonState runConfigurationState) {
+    List<String> userFlags =
+        ParametersListUtil.parse(Strings.nullToEmpty(userFlagsField.getText()));
     runConfigurationState.setUserFlags(userFlags);
     runConfigurationState.setNativeDebuggingEnabled(enableNativeDebuggingCheckBox.isSelected());
   }
 
   public List<JComponent> getComponents() {
-    List<JComponent> result = Lists.newArrayList(
-      new JLabel(kind.toString() + " rule:"),
-      ruleCombo.getCombo(),
-      new JLabel(String.format("Custom %s build flags:", Blaze.buildSystemName(project))),
-      userFlagsField
-    );
-
+    List<JComponent> result =
+        Lists.newArrayList(
+            new JLabel(String.format("Custom %s build flags:", Blaze.buildSystemName(project))),
+            userFlagsField);
     if (NdkSupport.NDK_SUPPORT.getValue()) {
-       result.add(enableNativeDebuggingCheckBox);
+      result.add(enableNativeDebuggingCheckBox);
     }
     return result;
   }
diff --git a/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationHandler.java b/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationHandler.java
new file mode 100644
index 0000000..ed5a394
--- /dev/null
+++ b/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationHandler.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.android.run;
+
+import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.android.run.runner.BlazeAndroidRunContext;
+import com.google.idea.blaze.base.model.primitives.Label;
+import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
+import com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationHandler;
+import com.intellij.execution.configurations.RunProfile;
+import com.intellij.execution.runners.ExecutionEnvironment;
+import com.intellij.openapi.project.Project;
+import org.jetbrains.android.facet.AndroidFacet;
+import org.jetbrains.annotations.Nullable;
+
+/** Common interface for Blaze Android run configuration handlers. */
+public interface BlazeAndroidRunConfigurationHandler extends BlazeCommandRunConfigurationHandler {
+  /**
+   * A convenience method for getting a {@link BlazeAndroidRunConfigurationHandler} from a {@link
+   * RunProfile}, without having to do repetitive casts. Returns null if the given profile is not a
+   * {@link BlazeCommandRunConfiguration} with a {@link BlazeAndroidRunConfigurationHandler} for its
+   * handler.
+   */
+  @Nullable
+  static BlazeAndroidRunConfigurationHandler getHandlerFrom(RunProfile profile) {
+    if (!(profile instanceof BlazeCommandRunConfiguration)) {
+      return null;
+    }
+    BlazeCommandRunConfiguration blazeConfiguration = (BlazeCommandRunConfiguration) profile;
+    return blazeConfiguration.getHandlerIfType(BlazeAndroidRunConfigurationHandler.class);
+  }
+
+  /** @return A {@link BlazeAndroidRunContext} for this handler with the given settings. */
+  BlazeAndroidRunContext createRunContext(
+      Project project,
+      AndroidFacet facet,
+      ExecutionEnvironment env,
+      ImmutableList<String> buildFlags);
+
+  /**
+   * @return The {@link Label} this handler's configuration targets, or null if it does not target a
+   *     Label.
+   */
+  @Nullable
+  Label getLabel();
+
+  /** @return This handler's common state. */
+  BlazeAndroidRunConfigurationCommonState getCommonState();
+
+  /** @return This handler's type-specific state. */
+  BlazeAndroidRunConfigurationState getConfigState();
+}
diff --git a/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationHandlerEditor.java b/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationHandlerEditor.java
new file mode 100644
index 0000000..e23f0a8
--- /dev/null
+++ b/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationHandlerEditor.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.android.run;
+
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationHandler;
+import com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationHandlerEditor;
+import com.google.idea.blaze.base.ui.UiUtil;
+import com.intellij.openapi.project.Project;
+import java.awt.Component;
+import java.util.List;
+import javax.swing.JComponent;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * A simplified, Blaze-specific variant of {@link
+ * org.jetbrains.android.run.AndroidRunConfigurationEditor}.
+ */
+public class BlazeAndroidRunConfigurationHandlerEditor
+    implements BlazeCommandRunConfigurationHandlerEditor {
+  private final BlazeAndroidRunConfigurationCommonStateEditor commonStateEditor;
+  private final BlazeAndroidRunConfigurationStateEditor kindSpecificEditor;
+
+  public BlazeAndroidRunConfigurationHandlerEditor(
+      Project project, BlazeAndroidRunConfigurationStateEditor kindSpecificEditor) {
+    this.commonStateEditor = new BlazeAndroidRunConfigurationCommonStateEditor(project);
+    this.kindSpecificEditor = kindSpecificEditor;
+  }
+
+  @Override
+  public void resetEditorFrom(BlazeCommandRunConfigurationHandler h) {
+    BlazeAndroidRunConfigurationHandler handler = (BlazeAndroidRunConfigurationHandler) h;
+    commonStateEditor.resetEditorFrom(handler.getCommonState());
+    kindSpecificEditor.resetEditorFrom(handler.getConfigState());
+  }
+
+  @Override
+  public void applyEditorTo(BlazeCommandRunConfigurationHandler h) {
+    BlazeAndroidRunConfigurationHandler handler = (BlazeAndroidRunConfigurationHandler) h;
+    commonStateEditor.applyEditorTo(handler.getCommonState());
+    kindSpecificEditor.applyEditorTo(handler.getConfigState());
+  }
+
+  @Override
+  @NotNull
+  public JComponent createEditor() {
+    List<Component> components = Lists.newArrayList();
+    components.addAll(commonStateEditor.getComponents());
+    components.add(kindSpecificEditor.getComponent());
+    return UiUtil.createBox(components);
+  }
+}
diff --git a/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationState.java b/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationState.java
new file mode 100644
index 0000000..1d13b56
--- /dev/null
+++ b/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationState.java
@@ -0,0 +1,21 @@
+/*
+ * 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 com.intellij.openapi.util.JDOMExternalizable;
+
+/** Indicates a class stores state for a Blaze Android run configuration. */
+public interface BlazeAndroidRunConfigurationState extends JDOMExternalizable {}
diff --git a/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationStateEditor.java b/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationStateEditor.java
new file mode 100644
index 0000000..7cbd815
--- /dev/null
+++ b/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationStateEditor.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.android.run;
+
+import java.awt.Component;
+
+/** An editor for Blaze Android run configuration state. */
+public interface BlazeAndroidRunConfigurationStateEditor {
+
+  void resetEditorFrom(BlazeAndroidRunConfigurationState state);
+
+  void applyEditorTo(BlazeAndroidRunConfigurationState state);
+
+  Component getComponent();
+}
diff --git a/aswb/src/com/google/idea/blaze/android/run/BlazeBeforeRunTaskProvider.java b/aswb/src/com/google/idea/blaze/android/run/BlazeBeforeRunTaskProvider.java
deleted file mode 100644
index 87358f5..0000000
--- a/aswb/src/com/google/idea/blaze/android/run/BlazeBeforeRunTaskProvider.java
+++ /dev/null
@@ -1,122 +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.run;
-
-import com.google.idea.blaze.android.run.runner.BlazeAndroidRunConfigurationRunner;
-import com.google.idea.blaze.base.settings.Blaze;
-import com.intellij.execution.BeforeRunTask;
-import com.intellij.execution.BeforeRunTaskProvider;
-import com.intellij.execution.configurations.RunConfiguration;
-import com.intellij.execution.runners.ExecutionEnvironment;
-import com.intellij.openapi.actionSystem.DataContext;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.Key;
-import icons.BlazeIcons;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import javax.swing.*;
-
-/**
- * Provides a before run task provider that immediately transfers control
- * to {@link BlazeAndroidRunConfigurationRunner}
- */
-public final class BlazeBeforeRunTaskProvider extends BeforeRunTaskProvider<BlazeBeforeRunTaskProvider.Task> {
-  @NotNull
-  public static final Key<Task> ID = Key.create("Android.Blaze.BeforeRunTask");
-  private static final String TASK_NAME = "Blaze before-run task";
-
-  public static class Task extends BeforeRunTask<Task> {
-    private Task() {
-      super(ID);
-      setEnabled(true);
-    }
-  }
-
-  @NotNull
-  private final Project project;
-
-  public BlazeBeforeRunTaskProvider(@NotNull Project project) {
-    this.project = project;
-  }
-
-  @Override
-  public Key<Task> getId() {
-    return ID;
-  }
-
-  @Nullable
-  @Override
-  public Icon getIcon() {
-    return BlazeIcons.Blaze;
-  }
-
-  @Nullable
-  @Override
-  public Icon getTaskIcon(Task task) {
-    return BlazeIcons.Blaze;
-  }
-
-  @Override
-  public String getName() {
-    return Blaze.guessBuildSystemName() + "before-run task";
-  }
-
-  @Override
-  public String getDescription(Task task) {
-    return Blaze.guessBuildSystemName() + "before-run task";
-  }
-
-  @Override
-  public boolean isConfigurable() {
-    return false;
-  }
-
-  @Nullable
-  @Override
-  public Task createTask(RunConfiguration runConfiguration) {
-    if (runConfiguration instanceof BlazeAndroidRunConfiguration) {
-      return new Task();
-    }
-    else {
-      return null;
-    }
-  }
-
-  @Override
-  public boolean configureTask(RunConfiguration runConfiguration, Task task) {
-    return false;
-  }
-
-  @Override
-  public boolean canExecuteTask(RunConfiguration configuration, Task task) {
-    return configuration instanceof BlazeAndroidRunConfiguration;
-  }
-
-  @Override
-  public boolean executeTask(
-    final DataContext dataContext,
-    final RunConfiguration configuration,
-    final ExecutionEnvironment env,
-    Task task) {
-    if (!canExecuteTask(configuration, task)) {
-      return false;
-    }
-
-    final BlazeAndroidRunConfiguration blazeConfiguration = (BlazeAndroidRunConfiguration)configuration;
-    return blazeConfiguration.getRunner().executeBuild(env);
-  }
-}
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryApplicationIdProvider.java b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryApplicationIdProvider.java
index 51a0f66..d89f58e 100644
--- a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryApplicationIdProvider.java
+++ b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryApplicationIdProvider.java
@@ -27,15 +27,13 @@
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-/**
- * Application id provider for android_binary.
- */
+/** Application id provider for android_binary. */
 public class BlazeAndroidBinaryApplicationIdProvider implements ApplicationIdProvider {
   private final Project project;
   private final ListenableFuture<BlazeAndroidDeployInfo> deployInfoFuture;
 
-  public BlazeAndroidBinaryApplicationIdProvider(Project project,
-                                                 ListenableFuture<BlazeAndroidDeployInfo> deployInfoFuture) {
+  public BlazeAndroidBinaryApplicationIdProvider(
+      Project project, ListenableFuture<BlazeAndroidDeployInfo> deployInfoFuture) {
     this.project = project;
     this.deployInfoFuture = deployInfoFuture;
   }
@@ -46,13 +44,15 @@
     BlazeAndroidDeployInfo deployInfo = Futures.get(deployInfoFuture, ApkProvisionException.class);
     Manifest manifest = deployInfo.getMergedManifest();
     if (manifest == null) {
-      throw new ApkProvisionException("Could not find merged manifest: " + deployInfo.getMergedManifestFile());
+      throw new ApkProvisionException(
+          "Could not find merged manifest: " + deployInfo.getMergedManifestFile());
     }
-    String applicationId = ApplicationManager.getApplication().runReadAction(
-      (Computable<String>)() -> manifest.getPackage().getValue()
-    );
+    String applicationId =
+        ApplicationManager.getApplication()
+            .runReadAction((Computable<String>) () -> manifest.getPackage().getValue());
     if (applicationId == null) {
-      throw new ApkProvisionException("No application id in merged manifest: " + deployInfo.getMergedManifestFile());
+      throw new ApkProvisionException(
+          "No application id in merged manifest: " + deployInfo.getMergedManifestFile());
     }
     return applicationId;
   }
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryApplicationLaunchTaskProvider.java b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryApplicationLaunchTaskProvider.java
index 9233f20..f450693 100644
--- a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryApplicationLaunchTaskProvider.java
+++ b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryApplicationLaunchTaskProvider.java
@@ -25,47 +25,51 @@
 import com.android.tools.idea.run.util.ProcessHandlerLaunchStatus;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.project.Project;
-
 import java.io.File;
 
-/**
- * Provides the launch task for android_binary
- */
+/** Provides the launch task for android_binary */
 public class BlazeAndroidBinaryApplicationLaunchTaskProvider {
-  private static final Logger LOG = Logger.getInstance(BlazeAndroidBinaryApplicationLaunchTaskProvider.class);
+  private static final Logger LOG =
+      Logger.getInstance(BlazeAndroidBinaryApplicationLaunchTaskProvider.class);
 
-  public static LaunchTask getApplicationLaunchTask(Project project,
-                                                    ApplicationIdProvider applicationIdProvider,
-                                                    File mergedManifestFile,
-                                                    BlazeAndroidBinaryRunConfigurationState configState,
-                                                    StartActivityFlagsProvider startActivityFlagsProvider,
-                                                    ProcessHandlerLaunchStatus processHandlerLaunchStatus) {
+  public static LaunchTask getApplicationLaunchTask(
+      Project project,
+      ApplicationIdProvider applicationIdProvider,
+      File mergedManifestFile,
+      BlazeAndroidBinaryRunConfigurationState configState,
+      StartActivityFlagsProvider startActivityFlagsProvider,
+      ProcessHandlerLaunchStatus processHandlerLaunchStatus) {
     try {
       String applicationId = applicationIdProvider.getPackageName();
 
       final LaunchTask launchTask;
 
-      switch (configState.MODE) {
+      switch (configState.getMode()) {
         case BlazeAndroidBinaryRunConfigurationState.DO_NOTHING:
           launchTask = null;
           break;
         case BlazeAndroidBinaryRunConfigurationState.LAUNCH_DEFAULT_ACTIVITY:
-          BlazeDefaultActivityLocator activityLocator = new BlazeDefaultActivityLocator(project, mergedManifestFile);
-          launchTask = new DefaultActivityLaunchTask(applicationId, activityLocator, startActivityFlagsProvider);
+          BlazeDefaultActivityLocator activityLocator =
+              new BlazeDefaultActivityLocator(project, mergedManifestFile);
+          launchTask =
+              new DefaultActivityLaunchTask(
+                  applicationId, activityLocator, startActivityFlagsProvider);
           break;
         case BlazeAndroidBinaryRunConfigurationState.LAUNCH_SPECIFIC_ACTIVITY:
-          launchTask = new SpecificActivityLaunchTask(applicationId, configState.ACTIVITY_CLASS, startActivityFlagsProvider);
+          launchTask =
+              new SpecificActivityLaunchTask(
+                  applicationId, configState.getActivityClass(), startActivityFlagsProvider);
           break;
         case BlazeAndroidBinaryRunConfigurationState.LAUNCH_DEEP_LINK:
-          launchTask = new AndroidDeepLinkLaunchTask(configState.DEEP_LINK, startActivityFlagsProvider);
+          launchTask =
+              new AndroidDeepLinkLaunchTask(configState.getDeepLink(), startActivityFlagsProvider);
           break;
         default:
           launchTask = null;
           break;
       }
       return launchTask;
-    }
-    catch (ApkProvisionException e) {
+    } catch (ApkProvisionException e) {
       LOG.error(e);
       processHandlerLaunchStatus.terminateLaunch("Unable to identify application id");
       return null;
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryConsoleProvider.java b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryConsoleProvider.java
index 776f16d..32183ef 100644
--- a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryConsoleProvider.java
+++ b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryConsoleProvider.java
@@ -26,9 +26,7 @@
 import com.intellij.openapi.project.Project;
 import org.jetbrains.annotations.NotNull;
 
-/**
- * Console provider for android_binary
- */
+/** Console provider for android_binary */
 public class BlazeAndroidBinaryConsoleProvider implements ConsoleProvider {
   private final Project project;
 
@@ -38,11 +36,11 @@
 
   @NotNull
   @Override
-  public ConsoleView createAndAttach(@NotNull Disposable parent,
-                                     @NotNull ProcessHandler handler,
-                                     @NotNull Executor executor)
-    throws ExecutionException {
-    final TextConsoleBuilder builder = TextConsoleBuilderFactory.getInstance().createBuilder(project);
+  public ConsoleView createAndAttach(
+      @NotNull Disposable parent, @NotNull ProcessHandler handler, @NotNull Executor executor)
+      throws ExecutionException {
+    final TextConsoleBuilder builder =
+        TextConsoleBuilderFactory.getInstance().createBuilder(project);
     ConsoleView console = builder.getConsole();
     console.attachToProcess(handler);
     return console;
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 6adef27..412c341 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
@@ -16,7 +16,12 @@
 package com.google.idea.blaze.android.run.binary;
 
 import com.android.ddmlib.IDevice;
-import com.android.tools.idea.run.*;
+import com.android.tools.idea.run.ApkInfo;
+import com.android.tools.idea.run.ApkProvisionException;
+import com.android.tools.idea.run.ApplicationIdProvider;
+import com.android.tools.idea.run.ConsolePrinter;
+import com.android.tools.idea.run.ConsoleProvider;
+import com.android.tools.idea.run.LaunchOptions;
 import com.android.tools.idea.run.activity.DefaultStartActivityFlagsProvider;
 import com.android.tools.idea.run.activity.StartActivityFlagsProvider;
 import com.android.tools.idea.run.editor.AndroidDebugger;
@@ -28,27 +33,27 @@
 import com.android.tools.idea.run.util.ProcessHandlerLaunchStatus;
 import com.google.common.collect.ImmutableList;
 import com.google.common.util.concurrent.Futures;
-import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationCommonState;
 import com.google.idea.blaze.android.run.deployinfo.BlazeAndroidDeployInfo;
 import com.google.idea.blaze.android.run.deployinfo.BlazeApkProvider;
-import com.google.idea.blaze.android.run.runner.*;
+import com.google.idea.blaze.android.run.runner.BlazeAndroidDeviceSelector;
+import com.google.idea.blaze.android.run.runner.BlazeAndroidLaunchTasksProvider;
+import com.google.idea.blaze.android.run.runner.BlazeAndroidRunConfigurationDebuggerManager;
+import com.google.idea.blaze.android.run.runner.BlazeAndroidRunContext;
+import com.google.idea.blaze.android.run.runner.BlazeApkBuildStep;
+import com.google.idea.blaze.android.run.runner.BlazeApkBuildStepNormalBuild;
+import com.google.idea.blaze.base.model.primitives.Label;
 import com.intellij.execution.ExecutionException;
 import com.intellij.execution.configurations.RunConfiguration;
 import com.intellij.execution.runners.ExecutionEnvironment;
-import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.project.Project;
+import java.util.Collection;
+import java.util.Set;
+import javax.annotation.Nullable;
 import org.jetbrains.android.facet.AndroidFacet;
 import org.jetbrains.annotations.NotNull;
 
-import javax.annotation.Nullable;
-import java.util.Collection;
-import java.util.Set;
-
-/**
- * Run context for android_binary.
- */
+/** Run context for android_binary. */
 class BlazeAndroidBinaryNormalBuildRunContext implements BlazeAndroidRunContext {
-  private static final Logger LOG = Logger.getInstance(BlazeAndroidBinaryNormalBuildRunContext.class);
 
   private final Project project;
   private final AndroidFacet facet;
@@ -60,27 +65,28 @@
   private final BlazeApkProvider apkProvider;
   private final ApplicationIdProvider applicationIdProvider;
 
-  public BlazeAndroidBinaryNormalBuildRunContext(Project project,
-                                                 AndroidFacet facet,
-                                                 RunConfiguration runConfiguration,
-                                                 ExecutionEnvironment env,
-                                                 BlazeAndroidRunConfigurationCommonState commonState,
-                                                 BlazeAndroidBinaryRunConfigurationState configState,
-                                                 ImmutableList<String> buildFlags) {
+  public BlazeAndroidBinaryNormalBuildRunContext(
+      Project project,
+      AndroidFacet facet,
+      RunConfiguration runConfiguration,
+      ExecutionEnvironment env,
+      BlazeAndroidBinaryRunConfigurationState configState,
+      Label label,
+      ImmutableList<String> buildFlags) {
     this.project = project;
     this.facet = facet;
     this.runConfiguration = runConfiguration;
     this.env = env;
     this.configState = configState;
     this.consoleProvider = new BlazeAndroidBinaryConsoleProvider(project);
-    this.buildStep = new BlazeApkBuildStepNormalBuild(project, commonState, buildFlags);
+    this.buildStep = new BlazeApkBuildStepNormalBuild(project, label, buildFlags);
     this.apkProvider = new BlazeApkProvider(project, buildStep.getDeployInfo());
-    this.applicationIdProvider = new BlazeAndroidBinaryApplicationIdProvider(project, buildStep.getDeployInfo());
+    this.applicationIdProvider =
+        new BlazeAndroidBinaryApplicationIdProvider(project, buildStep.getDeployInfo());
   }
 
   @Override
-  public void augmentEnvironment(ExecutionEnvironment env) {
-  }
+  public void augmentEnvironment(ExecutionEnvironment env) {}
 
   @Override
   public BlazeAndroidDeviceSelector getDeviceSelector() {
@@ -89,10 +95,7 @@
 
   @Override
   public void augmentLaunchOptions(@NotNull LaunchOptions.Builder options) {
-    options
-      .setDeploy(true)
-      .setPmInstallOptions(configState.ACTIVITY_EXTRA_FLAGS)
-      .setOpenLogcatAutomatically(true);
+    options.setDeploy(true).setOpenLogcatAutomatically(true);
   }
 
   @NotNull
@@ -113,56 +116,71 @@
 
   @Override
   public LaunchTasksProvider getLaunchTasksProvider(
-    LaunchOptions launchOptions,
-    BlazeAndroidRunConfigurationDebuggerManager debuggerManager) throws ExecutionException {
-    return new BlazeAndroidLaunchTasksProvider(project, this, applicationIdProvider, launchOptions, debuggerManager);
+      LaunchOptions.Builder launchOptionsBuilder,
+      boolean isDebug,
+      BlazeAndroidRunConfigurationDebuggerManager debuggerManager)
+      throws ExecutionException {
+    return new BlazeAndroidLaunchTasksProvider(
+        project, this, applicationIdProvider, launchOptionsBuilder, isDebug, debuggerManager);
   }
 
   @Nullable
   @Override
-  public ImmutableList<LaunchTask> getDeployTasks(IDevice device, LaunchOptions launchOptions) throws ExecutionException {
+  public ImmutableList<LaunchTask> getDeployTasks(IDevice device, LaunchOptions launchOptions)
+      throws ExecutionException {
     Collection<ApkInfo> apks;
     try {
       apks = apkProvider.getApks(device);
-    }
-    catch (ApkProvisionException e) {
+    } catch (ApkProvisionException e) {
       throw new ExecutionException(e);
     }
     return ImmutableList.of(new DeployApkTask(project, launchOptions, apks));
   }
 
   @Override
-  public LaunchTask getApplicationLaunchTask(LaunchOptions launchOptions,
-                                             AndroidDebugger androidDebugger,
-                                             AndroidDebuggerState androidDebuggerState,
-                                             ProcessHandlerLaunchStatus processHandlerLaunchStatus) throws ExecutionException {
-    final StartActivityFlagsProvider startActivityFlagsProvider = new DefaultStartActivityFlagsProvider(
-      androidDebugger,
-      androidDebuggerState,
-      project,
-      launchOptions.isDebug(),
-      configState.ACTIVITY_EXTRA_FLAGS
-    );
+  public LaunchTask getApplicationLaunchTask(
+      LaunchOptions launchOptions,
+      @Nullable Integer userId,
+      AndroidDebugger androidDebugger,
+      AndroidDebuggerState androidDebuggerState,
+      ProcessHandlerLaunchStatus processHandlerLaunchStatus)
+      throws ExecutionException {
+    final StartActivityFlagsProvider startActivityFlagsProvider =
+        new DefaultStartActivityFlagsProvider(
+            androidDebugger,
+            androidDebuggerState,
+            project,
+            launchOptions.isDebug(),
+            UserIdHelper.getFlagsFromUserId(userId));
 
-    BlazeAndroidDeployInfo deployInfo = Futures.get(buildStep.getDeployInfo(), ExecutionException.class);
+    BlazeAndroidDeployInfo deployInfo =
+        Futures.get(buildStep.getDeployInfo(), ExecutionException.class);
 
     return BlazeAndroidBinaryApplicationLaunchTaskProvider.getApplicationLaunchTask(
-      project,
-      applicationIdProvider,
-      deployInfo.getMergedManifestFile(),
-      configState,
-      startActivityFlagsProvider,
-      processHandlerLaunchStatus
-    );
+        project,
+        applicationIdProvider,
+        deployInfo.getMergedManifestFile(),
+        configState,
+        startActivityFlagsProvider,
+        processHandlerLaunchStatus);
   }
 
   @Nullable
   @Override
-  public DebugConnectorTask getDebuggerTask(LaunchOptions launchOptions,
-                                            AndroidDebugger androidDebugger,
-                                            AndroidDebuggerState androidDebuggerState,
-                                            Set<String> packageIds) throws ExecutionException {
+  public DebugConnectorTask getDebuggerTask(
+      AndroidDebugger androidDebugger,
+      AndroidDebuggerState androidDebuggerState,
+      Set<String> packageIds)
+      throws ExecutionException {
     //noinspection unchecked
-    return androidDebugger.getConnectDebuggerTask(env, null, packageIds,facet, androidDebuggerState, runConfiguration.getType().getId());
+    return androidDebugger.getConnectDebuggerTask(
+        env, null, packageIds, facet, androidDebuggerState, runConfiguration.getType().getId());
+  }
+
+  @Nullable
+  @Override
+  public Integer getUserId(IDevice device, ConsolePrinter consolePrinter)
+      throws ExecutionException {
+    return UserIdHelper.getUserIdFromConfigurationState(device, consolePrinter, configState);
   }
 }
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryProgramRunner.java b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryProgramRunner.java
index b178b12..5c41e6a 100644
--- a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryProgramRunner.java
+++ b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryProgramRunner.java
@@ -17,9 +17,11 @@
 
 import com.android.tools.idea.fd.InstantRunUtils;
 import com.android.tools.idea.run.AndroidSessionInfo;
+import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationHandler;
 import com.google.idea.blaze.android.run.binary.mobileinstall.IncrementalInstallDebugExecutor;
 import com.google.idea.blaze.android.run.binary.mobileinstall.IncrementalInstallRunExecutor;
 import com.intellij.execution.ExecutionException;
+import com.intellij.execution.configurations.RunConfigurationBase;
 import com.intellij.execution.configurations.RunProfile;
 import com.intellij.execution.configurations.RunProfileState;
 import com.intellij.execution.executors.DefaultDebugExecutor;
@@ -30,37 +32,51 @@
 import com.intellij.execution.ui.RunContentDescriptor;
 import org.jetbrains.annotations.NotNull;
 
-/**
- * Program runner for {@link BlazeAndroidBinaryRunConfiguration}
- */
+/** Program runner for {@link BlazeAndroidRunConfiguration} */
 public class BlazeAndroidBinaryProgramRunner extends DefaultProgramRunner {
   @Override
   public boolean canRun(@NotNull String executorId, @NotNull RunProfile profile) {
-    if (!(profile instanceof BlazeAndroidBinaryRunConfiguration)) {
+    BlazeAndroidRunConfigurationHandler handler =
+        BlazeAndroidRunConfigurationHandler.getHandlerFrom(profile);
+    if (handler == null) {
       return false;
     }
-    BlazeAndroidBinaryRunConfiguration runConfiguration = (BlazeAndroidBinaryRunConfiguration) profile;
-    if (runConfiguration.getConfigState().isMobileInstall()) {
-      return (IncrementalInstallDebugExecutor.EXECUTOR_ID.equals(executorId)
-              || IncrementalInstallRunExecutor.EXECUTOR_ID.equals(executorId));
+    // In practice, the stock runner will probably handle all non-incremental-install configs.
+    if (DefaultDebugExecutor.EXECUTOR_ID.equals(executorId)
+        || DefaultRunExecutor.EXECUTOR_ID.equals(executorId)) {
+      return true;
     }
-
-    return DefaultDebugExecutor.EXECUTOR_ID.equals(executorId) || DefaultRunExecutor.EXECUTOR_ID.equals(executorId);
+    // Otherwise, the configuration must be a Blaze incremental install configuration running with
+    // an incremental install executor.
+    if (!(handler instanceof BlazeAndroidBinaryRunConfigurationHandler)) {
+      return false;
+    }
+    return ((BlazeAndroidBinaryRunConfigurationHandler) handler).getConfigState().mobileInstall()
+        && (IncrementalInstallDebugExecutor.EXECUTOR_ID.equals(executorId)
+            || IncrementalInstallRunExecutor.EXECUTOR_ID.equals(executorId));
   }
 
   @Override
-  protected RunContentDescriptor doExecute(@NotNull final RunProfileState state, @NotNull final ExecutionEnvironment env)
-    throws ExecutionException {
+  protected RunContentDescriptor doExecute(
+      @NotNull final RunProfileState state, @NotNull final ExecutionEnvironment env)
+      throws ExecutionException {
     RunContentDescriptor descriptor = super.doExecute(state, env);
     if (descriptor != null) {
       ProcessHandler processHandler = descriptor.getProcessHandler();
       assert processHandler != null;
 
       RunProfile runProfile = env.getRunProfile();
-      int uniqueId = (runProfile instanceof BlazeAndroidBinaryRunConfiguration)
-                     ? ((BlazeAndroidBinaryRunConfiguration)runProfile).getUniqueID() : -1;
-      AndroidSessionInfo sessionInfo = new AndroidSessionInfo(processHandler, descriptor, uniqueId, env.getExecutor().getId(),
-                                                              InstantRunUtils.isInstantRunEnabled(env));
+      int uniqueId =
+          (runProfile instanceof RunConfigurationBase)
+              ? ((RunConfigurationBase) runProfile).getUniqueID()
+              : -1;
+      AndroidSessionInfo sessionInfo =
+          new AndroidSessionInfo(
+              processHandler,
+              descriptor,
+              uniqueId,
+              env.getExecutor().getId(),
+              InstantRunUtils.isInstantRunEnabled(env));
       processHandler.putUserData(AndroidSessionInfo.KEY, sessionInfo);
     }
 
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfiguration.java b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfiguration.java
deleted file mode 100644
index 7a02c45..0000000
--- a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfiguration.java
+++ /dev/null
@@ -1,218 +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.run.binary;
-
-import com.android.sdklib.AndroidVersion;
-import com.android.tools.idea.fd.InstantRunManager;
-import com.android.tools.idea.run.AndroidSessionInfo;
-import com.android.tools.idea.run.ValidationError;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Ordering;
-import com.google.idea.blaze.android.run.BlazeAndroidRunConfiguration;
-import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationCommonState;
-import com.google.idea.blaze.android.run.binary.instantrun.BlazeAndroidBinaryInstantRunContext;
-import com.google.idea.blaze.android.run.binary.mobileinstall.BlazeAndroidBinaryMobileInstallRunContext;
-import com.google.idea.blaze.android.run.runner.BlazeAndroidRunConfigurationRunner;
-import com.google.idea.blaze.android.run.runner.BlazeAndroidRunContext;
-import com.google.idea.blaze.android.sync.projectstructure.BlazeAndroidProjectStructureSyncer;
-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.google.idea.blaze.base.run.rulefinder.RuleFinder;
-import com.intellij.execution.ExecutionException;
-import com.intellij.execution.Executor;
-import com.intellij.execution.RunnerIconProvider;
-import com.intellij.execution.configurations.*;
-import com.intellij.execution.executors.DefaultRunExecutor;
-import com.intellij.execution.runners.ExecutionEnvironment;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.module.Module;
-import com.intellij.openapi.options.SettingsEditor;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.InvalidDataException;
-import com.intellij.openapi.util.WriteExternalException;
-import icons.AndroidIcons;
-import org.jdom.Element;
-import org.jetbrains.android.facet.AndroidFacet;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import javax.swing.*;
-import java.util.List;
-
-/**
- * An extension of the normal Android Studio run configuration for launching Android applications,
- * adapted specifically for selecting and launching android_binary targets.
- */
-public final class BlazeAndroidBinaryRunConfiguration extends LocatableConfigurationBase
-  implements BlazeAndroidRunConfiguration, RunConfiguration, RunnerIconProvider {
-  private static final Logger LOG = Logger.getInstance(BlazeAndroidBinaryRunConfiguration.class);
-  private final Project project;
-  private final BlazeAndroidRunConfigurationCommonState commonState;
-  private final BlazeAndroidBinaryRunConfigurationState configState = new BlazeAndroidBinaryRunConfigurationState();
-  private final BlazeAndroidRunConfigurationRunner runner;
-
-  public BlazeAndroidBinaryRunConfiguration(Project project, ConfigurationFactory factory) {
-    super(project, factory, "");
-    this.project = project;
-
-    RuleIdeInfo rule = RuleFinder.getInstance().firstRuleOfKinds(project, Kind.ANDROID_BINARY);
-    this.commonState = new BlazeAndroidRunConfigurationCommonState(rule != null ? rule.label : null, ImmutableList.of());
-    this.runner = new BlazeAndroidRunConfigurationRunner(project, this, this.commonState, false, getUniqueID());
-  }
-
-  @Nullable
-  @Override
-  public final Label getTarget() {
-    return commonState.getTarget();
-  }
-
-  public final void setTarget(@Nullable Label target) {
-    commonState.setTarget(target);
-  }
-
-  @Override
-  public BlazeAndroidRunConfigurationCommonState getCommonState() {
-    return commonState;
-  }
-
-  @Override
-  public BlazeAndroidRunConfigurationRunner getRunner() {
-    return runner;
-  }
-
-  @Override
-  public BlazeAndroidRunContext createRunContext(Project project,
-                                                 AndroidFacet facet,
-                                                 ExecutionEnvironment env,
-                                                 ImmutableList<String> buildFlags) {
-    if (configState.isInstantRun()) {
-      return new BlazeAndroidBinaryInstantRunContext(project, facet, this, env, commonState, configState, buildFlags);
-    }
-    else if (configState.isMobileInstall()) {
-      return new BlazeAndroidBinaryMobileInstallRunContext(project, facet, this, env, commonState, configState, buildFlags);
-    }
-    else {
-      return new BlazeAndroidBinaryNormalBuildRunContext(project, facet, this, env, commonState, configState, buildFlags);
-    }
-  }
-
-  @Override
-  @NotNull
-  public SettingsEditor<? extends RunConfiguration> getConfigurationEditor() {
-    return new BlazeAndroidBinaryRunConfigurationEditor(
-      getProject(),
-      new BlazeAndroidBinaryRunConfigurationStateEditor(getProject())
-    );
-  }
-
-  @Override
-  public final void checkConfiguration() throws RuntimeConfigurationException {
-    List<ValidationError> errors = validate();
-    if (errors.isEmpty()) {
-      return;
-    }
-    // TODO: Do something with the extra error information? Error count?
-    ValidationError topError = Ordering.natural().max(errors);
-    if (topError.isFatal()) {
-      throw new RuntimeConfigurationError(topError.getMessage(), topError.getQuickfix());
-    }
-    throw new RuntimeConfigurationWarning(topError.getMessage(), topError.getQuickfix());
-  }
-
-  private List<ValidationError> validate() {
-    List<ValidationError> errors = Lists.newArrayList();
-    errors.addAll(runner.validate(getModule()));
-    commonState.checkConfiguration(getProject(), Kind.ANDROID_BINARY, errors);
-    return errors;
-  }
-
-  private Module getModule() {
-    return BlazeAndroidProjectStructureSyncer.ensureRunConfigurationModule(project, getTarget());
-  }
-
-  @Override
-  @Nullable
-  public final RunProfileState getState(@NotNull final Executor executor, @NotNull ExecutionEnvironment env) throws ExecutionException {
-    final Module module = getModule();
-    return runner.getState(module, executor, env);
-  }
-
-  @NotNull
-  public BlazeAndroidBinaryRunConfigurationState getConfigState() {
-    return configState;
-  }
-
-  @Nullable
-  @Override
-  public Icon getExecutorIcon(@NotNull RunConfiguration configuration, @NotNull Executor executor) {
-    if (!configState.isInstantRun()) {
-      return null;
-    }
-
-    AndroidSessionInfo info = AndroidSessionInfo.findOldSession(getProject(), null, getUniqueID());
-    if (info == null || !info.isInstantRun() || !info.getExecutorId().equals(executor.getId())) {
-      return null;
-    }
-
-    // Make sure instant run is supported on the relevant device, if found.
-    AndroidVersion androidVersion = InstantRunManager.getMinDeviceApiLevel(info.getProcessHandler());
-    if (!InstantRunManager.isInstantRunCapableDeviceVersion(androidVersion)) {
-      return null;
-    }
-
-    return executor instanceof DefaultRunExecutor
-           ? AndroidIcons.RunIcons.Replay
-           : AndroidIcons.RunIcons.DebugReattach;
-  }
-
-  @Override
-  public void readExternal(Element element) throws InvalidDataException {
-    super.readExternal(element);
-
-    commonState.readExternal(element);
-    runner.readExternal(element);;
-    configState.readExternal(element);
-  }
-
-  @Override
-  public void writeExternal(Element element) throws WriteExternalException {
-    super.writeExternal(element);
-
-    commonState.writeExternal(element);
-    runner.writeExternal(element);;
-    configState.writeExternal(element);
-  }
-
-  @Override
-  public RunConfiguration clone() {
-    final Element element = new Element("dummy");
-    try {
-      writeExternal(element);
-      BlazeAndroidBinaryRunConfiguration clone = new BlazeAndroidBinaryRunConfiguration(
-        getProject(), getFactory());
-      clone.readExternal(element);
-      return clone;
-    } catch (InvalidDataException e) {
-      LOG.error(e);
-      return null;
-    } catch (WriteExternalException e) {
-      LOG.error(e);
-      return null;
-    }
-  }
-}
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationEditor.java b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationEditor.java
deleted file mode 100644
index 54d0ee8..0000000
--- a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationEditor.java
+++ /dev/null
@@ -1,68 +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.run.binary;
-
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationCommonStateEditor;
-import com.google.idea.blaze.base.model.primitives.Kind;
-import com.google.idea.blaze.base.ui.UiUtil;
-import com.intellij.openapi.options.ConfigurationException;
-import com.intellij.openapi.options.SettingsEditor;
-import com.intellij.openapi.project.Project;
-import org.jetbrains.annotations.NotNull;
-
-import javax.swing.*;
-import java.awt.*;
-import java.util.List;
-
-/**
- * A simplified, Blaze-specific variant of
- * {@link org.jetbrains.android.run.AndroidRunConfigurationEditor}.
- */
-class BlazeAndroidBinaryRunConfigurationEditor extends SettingsEditor<BlazeAndroidBinaryRunConfiguration> {
-
-  private final BlazeAndroidBinaryRunConfigurationStateEditor kindSpecificEditor;
-  private final BlazeAndroidRunConfigurationCommonStateEditor commonStateEditor;
-
-  public BlazeAndroidBinaryRunConfigurationEditor(
-    Project project,
-    BlazeAndroidBinaryRunConfigurationStateEditor kindSpecificEditor) {
-    this.kindSpecificEditor = kindSpecificEditor;
-    this.commonStateEditor = new BlazeAndroidRunConfigurationCommonStateEditor(project, Kind.ANDROID_BINARY);
-  }
-
-  @Override
-  protected void resetEditorFrom(BlazeAndroidBinaryRunConfiguration configuration) {
-    commonStateEditor.resetEditorFrom(configuration.getCommonState());
-    kindSpecificEditor.resetFrom(configuration);
-  }
-
-  @Override
-  protected void applyEditorTo(@NotNull BlazeAndroidBinaryRunConfiguration configuration)
-    throws ConfigurationException {
-    commonStateEditor.applyEditorTo(configuration.getCommonState());
-    kindSpecificEditor.applyTo(configuration);
-  }
-
-  @Override
-  @NotNull
-  protected JComponent createEditor() {
-    List<Component> components = Lists.newArrayList();
-    components.addAll(commonStateEditor.getComponents());
-    components.add(kindSpecificEditor.getComponent());
-    return UiUtil.createBox(components);
-  }
-}
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationHandler.java b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationHandler.java
new file mode 100644
index 0000000..2bd07cc
--- /dev/null
+++ b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationHandler.java
@@ -0,0 +1,277 @@
+/*
+ * 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 com.android.sdklib.AndroidVersion;
+import com.android.tools.idea.fd.InstantRunManager;
+import com.android.tools.idea.run.AndroidSessionInfo;
+import com.android.tools.idea.run.ValidationError;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Ordering;
+import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationCommonState;
+import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationHandler;
+import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationHandlerEditor;
+import com.google.idea.blaze.android.run.binary.instantrun.BlazeAndroidBinaryInstantRunContext;
+import com.google.idea.blaze.android.run.binary.mobileinstall.BlazeAndroidBinaryMobileInstallRunContext;
+import com.google.idea.blaze.android.run.runner.BlazeAndroidRunConfigurationRunner;
+import com.google.idea.blaze.android.run.runner.BlazeAndroidRunContext;
+import com.google.idea.blaze.android.sync.projectstructure.BlazeAndroidProjectStructureSyncer;
+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.google.idea.blaze.base.model.primitives.TargetExpression;
+import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
+import com.google.idea.blaze.base.run.BlazeConfigurationNameBuilder;
+import com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationHandlerEditor;
+import com.google.idea.blaze.base.run.rulefinder.RuleFinder;
+import com.google.idea.blaze.base.settings.Blaze;
+import com.intellij.execution.ExecutionException;
+import com.intellij.execution.Executor;
+import com.intellij.execution.configurations.RunConfiguration;
+import com.intellij.execution.configurations.RunProfileState;
+import com.intellij.execution.configurations.RuntimeConfigurationError;
+import com.intellij.execution.configurations.RuntimeConfigurationException;
+import com.intellij.execution.configurations.RuntimeConfigurationWarning;
+import com.intellij.execution.executors.DefaultRunExecutor;
+import com.intellij.execution.runners.ExecutionEnvironment;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Comparing;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.WriteExternalException;
+import icons.AndroidIcons;
+import java.util.List;
+import javax.swing.Icon;
+import org.jdom.Element;
+import org.jetbrains.android.facet.AndroidFacet;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * {@link com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationHandler} for
+ * android_binary targets.
+ */
+public class BlazeAndroidBinaryRunConfigurationHandler
+    implements BlazeAndroidRunConfigurationHandler {
+  private static final Logger LOG =
+      Logger.getInstance(BlazeAndroidBinaryRunConfigurationHandler.class);
+
+  private final BlazeCommandRunConfiguration configuration;
+  private final BlazeAndroidRunConfigurationCommonState commonState;
+  private final BlazeAndroidBinaryRunConfigurationState configState;
+  private final BlazeAndroidRunConfigurationRunner runner;
+
+  BlazeAndroidBinaryRunConfigurationHandler(BlazeCommandRunConfiguration configuration) {
+    this.configuration = configuration;
+    commonState = new BlazeAndroidRunConfigurationCommonState(ImmutableList.of());
+    configState = new BlazeAndroidBinaryRunConfigurationState();
+    runner =
+        new BlazeAndroidRunConfigurationRunner(
+            configuration.getProject(), this, commonState, false, configuration.getUniqueID());
+  }
+
+  @Override
+  public BlazeAndroidRunContext createRunContext(
+      Project project,
+      AndroidFacet facet,
+      ExecutionEnvironment env,
+      ImmutableList<String> buildFlags) {
+    if (configState.instantRun()) {
+      return new BlazeAndroidBinaryInstantRunContext(
+          project, facet, configuration, env, configState, getLabel(), buildFlags);
+    } else if (configState.mobileInstall()) {
+      return new BlazeAndroidBinaryMobileInstallRunContext(
+          project, facet, configuration, env, configState, getLabel(), buildFlags);
+    } else {
+      return new BlazeAndroidBinaryNormalBuildRunContext(
+          project, facet, configuration, env, configState, getLabel(), buildFlags);
+    }
+  }
+
+  @Override
+  @Nullable
+  public Label getLabel() {
+    TargetExpression target = configuration.getTarget();
+    if (target instanceof Label) {
+      return (Label) target;
+    }
+    return null;
+  }
+
+  @Override
+  public BlazeAndroidRunConfigurationCommonState getCommonState() {
+    return commonState;
+  }
+
+  @Override
+  public BlazeAndroidBinaryRunConfigurationState getConfigState() {
+    return configState;
+  }
+
+  @Nullable
+  private Module getModule() {
+    Label target = getLabel();
+    return target != null
+        ? BlazeAndroidProjectStructureSyncer.ensureRunConfigurationModule(
+            configuration.getProject(), target)
+        : null;
+  }
+
+  @Override
+  public final void checkConfiguration() throws RuntimeConfigurationException {
+    List<ValidationError> errors = validate();
+    if (errors.isEmpty()) {
+      return;
+    }
+    // TODO: Do something with the extra error information? Error count?
+    ValidationError topError = Ordering.natural().max(errors);
+    if (topError.isFatal()) {
+      throw new RuntimeConfigurationError(topError.getMessage(), topError.getQuickfix());
+    }
+    throw new RuntimeConfigurationWarning(topError.getMessage(), topError.getQuickfix());
+  }
+
+  private List<ValidationError> validate() {
+    List<ValidationError> errors = Lists.newArrayList();
+    errors.addAll(runner.validate(getModule()));
+    validateLabel(errors);
+    return errors;
+  }
+
+  private void validateLabel(List<ValidationError> errors) {
+    Project project = configuration.getProject();
+    Label target = getLabel();
+    Kind kind = Kind.ANDROID_BINARY;
+    RuleIdeInfo rule =
+        target != null ? RuleFinder.getInstance().ruleForTarget(project, target) : null;
+    if (rule == null) {
+      errors.add(
+          ValidationError.fatal(
+              String.format("No existing %s rule selected.", Blaze.buildSystemName(project))));
+    } else if (!rule.kindIsOneOf(kind)) {
+      errors.add(
+          ValidationError.fatal(
+              String.format(
+                  "Selected %s rule is not %s", Blaze.buildSystemName(project), kind.toString())));
+    }
+  }
+
+  @Override
+  public void readExternal(Element element) throws InvalidDataException {
+    commonState.readExternal(element);
+    runner.readExternal(element);
+    configState.readExternal(element);
+  }
+
+  @Override
+  public void writeExternal(Element element) throws WriteExternalException {
+    commonState.writeExternal(element);
+    runner.writeExternal(element);
+    configState.writeExternal(element);
+  }
+
+  @Override
+  public BlazeAndroidBinaryRunConfigurationHandler cloneFor(
+      BlazeCommandRunConfiguration configuration) {
+    final Element element = new Element("dummy");
+    try {
+      writeExternal(element);
+      final BlazeAndroidBinaryRunConfigurationHandler handler =
+          new BlazeAndroidBinaryRunConfigurationHandler(configuration);
+      handler.readExternal(element);
+      return handler;
+    } catch (InvalidDataException | WriteExternalException e) {
+      LOG.error(e);
+      return null;
+    }
+  }
+
+  @Override
+  @Nullable
+  public final RunProfileState getState(
+      @NotNull final Executor executor, @NotNull ExecutionEnvironment env)
+      throws ExecutionException {
+    final Module module = getModule();
+    return runner.getState(module, executor, env);
+  }
+
+  @Override
+  public boolean executeBeforeRunTask(ExecutionEnvironment environment) {
+    return runner.executeBuild(environment);
+  }
+
+  @Override
+  @Nullable
+  public String suggestedName() {
+    Label target = getLabel();
+    if (target == null) {
+      return null;
+    }
+    // buildSystemName and commandName are intentionally omitted.
+    return new BlazeConfigurationNameBuilder().setTargetString(target).build();
+  }
+
+  @Override
+  public boolean isGeneratedName(boolean hasGeneratedFlag) {
+    return Comparing.equal(configuration.getName(), suggestedName());
+  }
+
+  @Override
+  @Nullable
+  public String getCommandName() {
+    return null;
+  }
+
+  @Override
+  public String getHandlerName() {
+    return "Android Binary Handler";
+  }
+
+  @Override
+  @Nullable
+  public Icon getExecutorIcon(@NotNull RunConfiguration configuration, @NotNull Executor executor) {
+    if (!configState.instantRun()) {
+      return null;
+    }
+
+    AndroidSessionInfo info =
+        AndroidSessionInfo.findOldSession(
+            this.configuration.getProject(), null, this.configuration.getUniqueID());
+    if (info == null || !info.isInstantRun() || !info.getExecutorId().equals(executor.getId())) {
+      return null;
+    }
+
+    // Make sure instant run is supported on the relevant device, if found.
+    AndroidVersion androidVersion =
+        InstantRunManager.getMinDeviceApiLevel(info.getProcessHandler());
+    if (!InstantRunManager.isInstantRunCapableDeviceVersion(androidVersion)) {
+      return null;
+    }
+
+    return executor instanceof DefaultRunExecutor
+        ? AndroidIcons.RunIcons.Replay
+        : AndroidIcons.RunIcons.DebugReattach;
+  }
+
+  @Override
+  public BlazeCommandRunConfigurationHandlerEditor getHandlerEditor() {
+    Project project = configuration.getProject();
+    return new BlazeAndroidRunConfigurationHandlerEditor(
+        project, new BlazeAndroidBinaryRunConfigurationStateEditor(project));
+  }
+}
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationHandlerProvider.java b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationHandlerProvider.java
new file mode 100644
index 0000000..5af756b
--- /dev/null
+++ b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationHandlerProvider.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.android.run.binary;
+
+import com.google.idea.blaze.base.model.primitives.Kind;
+import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
+import com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationHandler;
+import com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationHandlerProvider;
+
+/** Handler provider for android_binary targets. */
+public class BlazeAndroidBinaryRunConfigurationHandlerProvider
+    implements BlazeCommandRunConfigurationHandlerProvider {
+
+  public static BlazeAndroidBinaryRunConfigurationHandlerProvider getInstance() {
+    return BlazeCommandRunConfigurationHandlerProvider.EP_NAME.findExtension(
+        BlazeAndroidBinaryRunConfigurationHandlerProvider.class);
+  }
+
+  @Override
+  public boolean canHandleKind(Kind kind) {
+    return kind == Kind.ANDROID_BINARY;
+  }
+
+  @Override
+  public BlazeCommandRunConfigurationHandler createHandler(BlazeCommandRunConfiguration config) {
+    return new BlazeAndroidBinaryRunConfigurationHandler(config);
+  }
+
+  @Override
+  public String getId() {
+    return "BlazeAndroidBinaryRunConfigurationHandlerProvider";
+  }
+}
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationState.java b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationState.java
index e048889..aab1abc 100644
--- a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationState.java
+++ b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationState.java
@@ -15,35 +15,43 @@
  */
 package com.google.idea.blaze.android.run.binary;
 
-import com.intellij.openapi.util.DefaultJDOMExternalizer;
+import com.android.tools.idea.run.util.LaunchUtils;
+import com.google.common.base.Strings;
+import com.google.common.collect.Maps;
+import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationState;
 import com.intellij.openapi.util.InvalidDataException;
-import com.intellij.openapi.util.JDOMExternalizable;
 import com.intellij.openapi.util.WriteExternalException;
+import java.util.Map;
 import org.jdom.Element;
 
-/**
- * State specific to the android binary run configuration.
- */
-public final class BlazeAndroidBinaryRunConfigurationState implements JDOMExternalizable {
+/** State specific to the android binary run configuration. */
+public final class BlazeAndroidBinaryRunConfigurationState
+    implements BlazeAndroidRunConfigurationState {
   public static final String LAUNCH_DEFAULT_ACTIVITY = "default_activity";
   public static final String LAUNCH_SPECIFIC_ACTIVITY = "specific_activity";
   public static final String DO_NOTHING = "do_nothing";
   public static final String LAUNCH_DEEP_LINK = "launch_deep_link";
-  public String DEEP_LINK = "";
-  public String ACTIVITY_CLASS = "";
-
-  public String MODE = LAUNCH_DEFAULT_ACTIVITY;
-  // Launch options
-  public String ACTIVITY_EXTRA_FLAGS = "";
 
   private static final String MOBILE_INSTALL_ATTR = "blaze-mobile-install";
   private static final String USE_SPLIT_APKS_IF_POSSIBLE = "use-split-apks-if-possible";
   private static final String INSTANT_RUN_ATTR = "instant-run";
+  private static final String WORK_PROFILE_ATTR = "use-work-profile-if-present";
+  private static final String USER_ID_ATTR = "user-id";
   private boolean mobileInstall = false;
   private boolean useSplitApksIfPossible = true;
   private boolean instantRun = false;
+  private boolean useWorkProfileIfPresent = false;
+  private Integer userId;
 
-  boolean isMobileInstall() {
+  private static final String DEEP_LINK = "DEEP_LINK";
+  private static final String ACTIVITY_CLASS = "ACTIVITY_CLASS";
+  private static final String MODE = "MODE";
+  private static final String ACTIVITY_EXTRA_FLAGS = "ACTIVITY_EXTRA_FLAGS";
+  private String deepLink = "";
+  private String activityClass = "";
+  private String mode = LAUNCH_DEFAULT_ACTIVITY;
+
+  boolean mobileInstall() {
     return mobileInstall;
   }
 
@@ -51,7 +59,7 @@
     this.mobileInstall = mobileInstall;
   }
 
-  public boolean isUseSplitApksIfPossible() {
+  public boolean useSplitApksIfPossible() {
     return useSplitApksIfPossible;
   }
 
@@ -59,7 +67,7 @@
     this.useSplitApksIfPossible = useSplitApksIfPossible;
   }
 
-  boolean isInstantRun() {
+  boolean instantRun() {
     return instantRun;
   }
 
@@ -67,19 +75,108 @@
     this.instantRun = instantRun;
   }
 
+  public boolean useWorkProfileIfPresent() {
+    return useWorkProfileIfPresent;
+  }
+
+  void setUseWorkProfileIfPresent(boolean useWorkProfileIfPresent) {
+    this.useWorkProfileIfPresent = useWorkProfileIfPresent;
+  }
+
+  Integer getUserId() {
+    return userId;
+  }
+
+  void setUserId(Integer userId) {
+    this.userId = userId;
+  }
+
+  public String getDeepLink() {
+    return deepLink;
+  }
+
+  public void setDeepLink(String deepLink) {
+    this.deepLink = deepLink;
+  }
+
+  public String getActivityClass() {
+    return activityClass;
+  }
+
+  public void setActivityClass(String activityClass) {
+    this.activityClass = activityClass;
+  }
+
+  public String getMode() {
+    return mode;
+  }
+
+  public void setMode(String mode) {
+    this.mode = mode;
+  }
+
   @Override
   public void readExternal(Element element) throws InvalidDataException {
-    DefaultJDOMExternalizer.readExternal(this, element);
+    setDeepLink(Strings.nullToEmpty(element.getAttributeValue(DEEP_LINK)));
+    setActivityClass(Strings.nullToEmpty(element.getAttributeValue(ACTIVITY_CLASS)));
+    setMode(Strings.nullToEmpty(element.getAttributeValue(MODE)));
     setMobileInstall(Boolean.parseBoolean(element.getAttributeValue(MOBILE_INSTALL_ATTR)));
-    setUseSplitApksIfPossible(Boolean.parseBoolean(element.getAttributeValue(USE_SPLIT_APKS_IF_POSSIBLE)));
+    setUseSplitApksIfPossible(
+        Boolean.parseBoolean(element.getAttributeValue(USE_SPLIT_APKS_IF_POSSIBLE)));
     setInstantRun(Boolean.parseBoolean(element.getAttributeValue(INSTANT_RUN_ATTR)));
+    setUseWorkProfileIfPresent(Boolean.parseBoolean(element.getAttributeValue(WORK_PROFILE_ATTR)));
+
+    String userIdString = element.getAttributeValue(USER_ID_ATTR);
+    if (userIdString != null) {
+      setUserId(Integer.parseInt(userIdString));
+    }
+
+    for (Map.Entry<String, String> entry : getLegacyValues(element).entrySet()) {
+      String value = entry.getValue();
+      switch (entry.getKey()) {
+        case DEEP_LINK:
+          deepLink = Strings.nullToEmpty(value);
+          break;
+        case ACTIVITY_CLASS:
+          activityClass = Strings.nullToEmpty(value);
+          break;
+        case MODE:
+          mode = Strings.nullToEmpty(value);
+          break;
+        case ACTIVITY_EXTRA_FLAGS:
+          if (userId == null) {
+            userId = LaunchUtils.getUserIdFromFlags(value);
+          }
+          break;
+        default:
+          break;
+      }
+    }
   }
 
   @Override
   public void writeExternal(Element element) throws WriteExternalException {
-    DefaultJDOMExternalizer.writeExternal(this, element);
+    element.setAttribute(DEEP_LINK, deepLink);
+    element.setAttribute(ACTIVITY_CLASS, activityClass);
+    element.setAttribute(MODE, mode);
     element.setAttribute(MOBILE_INSTALL_ATTR, Boolean.toString(mobileInstall));
     element.setAttribute(USE_SPLIT_APKS_IF_POSSIBLE, Boolean.toString(useSplitApksIfPossible));
     element.setAttribute(INSTANT_RUN_ATTR, Boolean.toString(instantRun));
+    element.setAttribute(WORK_PROFILE_ATTR, Boolean.toString(useWorkProfileIfPresent));
+
+    if (userId != null) {
+      element.setAttribute(USER_ID_ATTR, Integer.toString(userId));
+    }
+  }
+
+  /** Imports legacy values in the old reflective JDOM externalizer manner. Can be removed ~2.0+. */
+  private static Map<String, String> getLegacyValues(Element element) {
+    Map<String, String> result = Maps.newHashMap();
+    for (Element option : element.getChildren("option")) {
+      String name = option.getAttributeValue("name");
+      String value = option.getAttributeValue("value");
+      result.put(name, value);
+    }
+    return result;
   }
 }
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationStateEditor.java b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationStateEditor.java
index e85da80..ddcbf9a 100644
--- a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationStateEditor.java
+++ b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationStateEditor.java
@@ -15,9 +15,9 @@
  */
 package com.google.idea.blaze.android.run.binary;
 
-import com.android.tools.idea.run.ConfigurationSpecificEditor;
 import com.android.tools.idea.run.activity.ActivityLocatorUtils;
-import com.android.tools.idea.run.util.LaunchUtils;
+import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationState;
+import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationStateEditor;
 import com.google.idea.blaze.android.run.binary.instantrun.InstantRunExperiment;
 import com.google.idea.blaze.base.ui.IntegerTextField;
 import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
@@ -39,31 +39,37 @@
 import com.intellij.ui.LanguageTextField;
 import com.intellij.uiDesigner.core.GridConstraints;
 import com.intellij.uiDesigner.core.GridLayoutManager;
-import org.jetbrains.android.util.AndroidBundle;
-import org.jetbrains.android.util.AndroidUtils;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import javax.swing.*;
-import javax.swing.border.TitledBorder;
-import java.awt.*;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Font;
+import java.awt.Insets;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.util.ResourceBundle;
+import javax.swing.AbstractButton;
+import javax.swing.BorderFactory;
+import javax.swing.ButtonGroup;
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.border.TitledBorder;
+import org.jetbrains.android.util.AndroidBundle;
+import org.jetbrains.android.util.AndroidUtils;
+import org.jetbrains.annotations.Nullable;
 
 /**
- * The part of the Blaze Android run configuration editor that allows the user to pick an
- * android_binary target and an activity to launch.
- * Patterned after {@link org.jetbrains.android.run.ApplicationRunParameters}.
+ * The part of the Blaze Android Binary handler editor that allows the user to pick an activity to
+ * launch. Patterned after {@link org.jetbrains.android.run.ApplicationRunParameters}.
  */
-class BlazeAndroidBinaryRunConfigurationStateEditor implements ConfigurationSpecificEditor<BlazeAndroidBinaryRunConfiguration> {
-  public static final Key<BlazeAndroidBinaryRunConfigurationStateEditor> ACTIVITY_CLASS_TEXT_FIELD_KEY =
-    Key.create("BlazeActivityClassTextField");
+class BlazeAndroidBinaryRunConfigurationStateEditor
+    implements BlazeAndroidRunConfigurationStateEditor {
+  public static final Key<BlazeAndroidBinaryRunConfigurationStateEditor>
+      ACTIVITY_CLASS_TEXT_FIELD_KEY = Key.create("BlazeActivityClassTextField");
 
-  @NotNull
   private final Project project;
-  @Nullable
-  private JPanel panel;
+
+  @Nullable private JPanel panel;
   private ComponentWithBrowseButton<EditorTextField> activityField;
   private JRadioButton launchNothingButton;
   private JRadioButton launchDefaultButton;
@@ -71,45 +77,56 @@
   private JCheckBox mobileInstallCheckBox;
   private JCheckBox splitApksCheckBox;
   private JCheckBox instantRunCheckBox;
+  private JCheckBox useWorkProfileIfPresentCheckBox;
+  private JLabel userIdLabel;
   private IntegerTextField userIdField;
 
-  BlazeAndroidBinaryRunConfigurationStateEditor(@NotNull final Project project) {
+  BlazeAndroidBinaryRunConfigurationStateEditor(Project project) {
     this.project = project;
 
     setupUI();
     userIdField.setMinValue(0);
 
-    activityField.addActionListener(new ActionListener() {
-      @Override
-      public void actionPerformed(ActionEvent e) {
-        if (!project.isInitialized()) {
-          return;
-        }
-        // We find all Activity classes in the module for the selected variant (or any of its deps).
-        final JavaPsiFacade facade = JavaPsiFacade.getInstance(project);
-        PsiClass activityBaseClass = facade.findClass(
-          AndroidUtils.ACTIVITY_BASE_CLASS_NAME, ProjectScope.getAllScope(project));
-        if (activityBaseClass == null) {
-          Messages
-            .showErrorDialog(panel, AndroidBundle.message("cant.find.activity.class.error"));
-          return;
-        }
-        GlobalSearchScope searchScope = GlobalSearchScope.projectScope(project);
-        PsiClass initialSelection = facade.findClass(
-          activityField.getChildComponent().getText(), searchScope);
-        TreeClassChooser chooser = TreeClassChooserFactory.getInstance(project)
-          .createInheritanceClassChooser("Select Activity Class",
-                                         searchScope, activityBaseClass,
-                                         initialSelection, null);
-        chooser.showDialog();
-        PsiClass selClass = chooser.getSelected();
-        if (selClass != null) {
-          // This must be done because Android represents inner static class paths differently than java.
-          String qualifiedActivityName = ActivityLocatorUtils.getQualifiedActivityName(selClass);
-          activityField.getChildComponent().setText(qualifiedActivityName);
-        }
-      }
-    });
+    activityField.addActionListener(
+        new ActionListener() {
+          @Override
+          public void actionPerformed(ActionEvent e) {
+            if (!project.isInitialized()) {
+              return;
+            }
+            // We find all Activity classes in the module for the selected variant
+            // (or any of its deps).
+            final JavaPsiFacade facade = JavaPsiFacade.getInstance(project);
+            PsiClass activityBaseClass =
+                facade.findClass(
+                    AndroidUtils.ACTIVITY_BASE_CLASS_NAME, ProjectScope.getAllScope(project));
+            if (activityBaseClass == null) {
+              Messages.showErrorDialog(
+                  panel, AndroidBundle.message("cant.find.activity.class.error"));
+              return;
+            }
+            GlobalSearchScope searchScope = GlobalSearchScope.projectScope(project);
+            PsiClass initialSelection =
+                facade.findClass(activityField.getChildComponent().getText(), searchScope);
+            TreeClassChooser chooser =
+                TreeClassChooserFactory.getInstance(project)
+                    .createInheritanceClassChooser(
+                        "Select Activity Class",
+                        searchScope,
+                        activityBaseClass,
+                        initialSelection,
+                        null);
+            chooser.showDialog();
+            PsiClass selClass = chooser.getSelected();
+            if (selClass != null) {
+              // This must be done because Android represents
+              // inner static class paths differently than java.
+              String qualifiedActivityName =
+                  ActivityLocatorUtils.getQualifiedActivityName(selClass);
+              activityField.getChildComponent().setText(qualifiedActivityName);
+            }
+          }
+        });
     ActionListener listener = e -> activityField.setEnabled(launchCustomButton.isSelected());
     launchCustomButton.addActionListener(listener);
     launchDefaultButton.addActionListener(listener);
@@ -117,48 +134,64 @@
 
     instantRunCheckBox.setVisible(InstantRunExperiment.INSTANT_RUN_ENABLED.getValue());
 
-    /**
-     * Only one of mobile-install and instant run can be selected at any one time
-     */
-    mobileInstallCheckBox.addActionListener(e -> {
-      if (mobileInstallCheckBox.isSelected()) {
-        instantRunCheckBox.setSelected(false);
-      }
-    });
-    instantRunCheckBox.addActionListener(e -> {
-      if (instantRunCheckBox.isSelected()) {
-        mobileInstallCheckBox.setSelected(false);
-      }
-    });
+    /** Only one of mobile-install and instant run can be selected at any one time */
+    mobileInstallCheckBox.addActionListener(
+        e -> {
+          if (mobileInstallCheckBox.isSelected()) {
+            instantRunCheckBox.setSelected(false);
+          }
+        });
+    instantRunCheckBox.addActionListener(
+        e -> {
+          if (instantRunCheckBox.isSelected()) {
+            mobileInstallCheckBox.setSelected(false);
+          }
+        });
 
-    mobileInstallCheckBox.addActionListener(e -> splitApksCheckBox.setVisible(mobileInstallCheckBox.isSelected()));
+    mobileInstallCheckBox.addActionListener(
+        e -> splitApksCheckBox.setVisible(mobileInstallCheckBox.isSelected()));
+
+    useWorkProfileIfPresentCheckBox.addActionListener(
+        e -> {
+          setUserIdEnabled(!useWorkProfileIfPresentCheckBox.isSelected());
+        });
   }
 
   @Override
-  public void resetFrom(BlazeAndroidBinaryRunConfiguration configuration) {
-    BlazeAndroidBinaryRunConfigurationState configState = configuration.getConfigState();
-    boolean launchSpecificActivity = configState.MODE.equals(BlazeAndroidBinaryRunConfigurationState.LAUNCH_SPECIFIC_ACTIVITY);
-    if (configState.MODE.equals(BlazeAndroidBinaryRunConfigurationState.LAUNCH_DEFAULT_ACTIVITY)) {
+  public void resetEditorFrom(BlazeAndroidRunConfigurationState state) {
+    BlazeAndroidBinaryRunConfigurationState configState =
+        (BlazeAndroidBinaryRunConfigurationState) state;
+    boolean launchSpecificActivity =
+        configState
+            .getMode()
+            .equals(BlazeAndroidBinaryRunConfigurationState.LAUNCH_SPECIFIC_ACTIVITY);
+    if (configState
+        .getMode()
+        .equals(BlazeAndroidBinaryRunConfigurationState.LAUNCH_DEFAULT_ACTIVITY)) {
       launchDefaultButton.setSelected(true);
-    }
-    else if (launchSpecificActivity) {
+    } else if (launchSpecificActivity) {
       launchCustomButton.setSelected(true);
-    }
-    else {
+    } else {
       launchNothingButton.setSelected(true);
     }
     activityField.setEnabled(launchSpecificActivity);
     if (launchSpecificActivity) {
-      activityField.getChildComponent().setText(configState.ACTIVITY_CLASS);
+      activityField.getChildComponent().setText(configState.getActivityClass());
     }
 
-    mobileInstallCheckBox.setSelected(configState.isMobileInstall());
-    splitApksCheckBox.setSelected(configState.isUseSplitApksIfPossible());
-    instantRunCheckBox.setSelected(configState.isInstantRun());
+    mobileInstallCheckBox.setSelected(configState.mobileInstall());
+    splitApksCheckBox.setSelected(configState.useSplitApksIfPossible());
+    instantRunCheckBox.setSelected(configState.instantRun());
+    useWorkProfileIfPresentCheckBox.setSelected(configState.useWorkProfileIfPresent());
 
-    userIdField.setEnabled(!configState.MODE.equals(BlazeAndroidBinaryRunConfigurationState.DO_NOTHING));
-    userIdField.setValue(LaunchUtils.getUserIdFromFlags(configState.ACTIVITY_EXTRA_FLAGS));
-    splitApksCheckBox.setVisible(configState.isMobileInstall());
+    userIdField.setValue(configState.getUserId());
+    setUserIdEnabled(!configState.useWorkProfileIfPresent());
+    splitApksCheckBox.setVisible(configState.mobileInstall());
+  }
+
+  private void setUserIdEnabled(boolean enabled) {
+    userIdLabel.setEnabled(enabled);
+    userIdField.setEnabled(enabled);
   }
 
   @Override
@@ -167,121 +200,291 @@
   }
 
   @Override
-  public void applyTo(BlazeAndroidBinaryRunConfiguration configuration) {
-    BlazeAndroidBinaryRunConfigurationState configState = configuration.getConfigState();
-    configState.ACTIVITY_EXTRA_FLAGS = getFlagsFromUserId((Number)userIdField.getValue());
+  public void applyEditorTo(BlazeAndroidRunConfigurationState state) {
+    BlazeAndroidBinaryRunConfigurationState configState =
+        (BlazeAndroidBinaryRunConfigurationState) state;
+    configState.setUserId((Integer) userIdField.getValue());
     if (launchDefaultButton.isSelected()) {
-      configState.MODE = BlazeAndroidBinaryRunConfigurationState.LAUNCH_DEFAULT_ACTIVITY;
-    }
-    else if (launchCustomButton.isSelected()) {
-      configState.MODE = BlazeAndroidBinaryRunConfigurationState.LAUNCH_SPECIFIC_ACTIVITY;
-      configState.ACTIVITY_CLASS = activityField.getChildComponent().getText();
-    }
-    else {
-      configState.MODE = BlazeAndroidBinaryRunConfigurationState.DO_NOTHING;
+      configState.setMode(BlazeAndroidBinaryRunConfigurationState.LAUNCH_DEFAULT_ACTIVITY);
+    } else if (launchCustomButton.isSelected()) {
+      configState.setMode(BlazeAndroidBinaryRunConfigurationState.LAUNCH_SPECIFIC_ACTIVITY);
+      configState.setActivityClass(activityField.getChildComponent().getText());
+    } else {
+      configState.setMode(BlazeAndroidBinaryRunConfigurationState.DO_NOTHING);
     }
     configState.setMobileInstall(mobileInstallCheckBox.isSelected());
     configState.setUseSplitApksIfPossible(splitApksCheckBox.isSelected());
     configState.setInstantRun(instantRunCheckBox.isSelected());
-  }
-
-  @Override
-  public JComponent getAnchor() {
-    return null;
-  }
-
-  @Override
-  public void setAnchor(JComponent anchor) {
+    configState.setUseWorkProfileIfPresent(useWorkProfileIfPresentCheckBox.isSelected());
   }
 
   private void createUIComponents() {
-    final EditorTextField editorTextField = new LanguageTextField(PlainTextLanguage.INSTANCE,
-                                                                  project, "") {
-      @Override
-      protected EditorEx createEditor() {
-        final EditorEx editor = super.createEditor();
-        final PsiFile file =
-          PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
+    final EditorTextField editorTextField =
+        new LanguageTextField(PlainTextLanguage.INSTANCE, project, "") {
+          @Override
+          protected EditorEx createEditor() {
+            final EditorEx editor = super.createEditor();
+            final PsiFile file =
+                PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
 
-        if (file != null) {
-          DaemonCodeAnalyzer.getInstance(project).setHighlightingEnabled(file, false);
-        }
-        editor.putUserData(ACTIVITY_CLASS_TEXT_FIELD_KEY, BlazeAndroidBinaryRunConfigurationStateEditor.this);
-        return editor;
-      }
-    };
+            if (file != null) {
+              DaemonCodeAnalyzer.getInstance(project).setHighlightingEnabled(file, false);
+            }
+            editor.putUserData(
+                ACTIVITY_CLASS_TEXT_FIELD_KEY, BlazeAndroidBinaryRunConfigurationStateEditor.this);
+            return editor;
+          }
+        };
     activityField = new ComponentWithBrowseButton<EditorTextField>(editorTextField, null);
   }
 
-  @NotNull
-  private static String getFlagsFromUserId(@Nullable Number userId) {
-    return userId != null ? ("--user " + userId.intValue()) : "";
-  }
-
-  /**
-   * Initially generated by IntelliJ from a .form file, then checked in as source.
-   */
+  /** Initially generated by IntelliJ from a .form file, then checked in as source. */
   private void setupUI() {
     createUIComponents();
     panel = new JPanel();
-    panel.setLayout(new GridLayoutManager(4, 2, new Insets(0, 0, 0, 0), -1, -1));
-    final JPanel panel1 = new JPanel();
-    panel1.setLayout(new GridLayoutManager(4, 2, new Insets(0, 0, 0, 0), -1, -1));
-    panel.add(panel1, new GridConstraints(3, 0, 1, 2, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH,
-                                          GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
-                                          GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0,
-                                          false));
-    panel1.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), "Activity", TitledBorder.DEFAULT_JUSTIFICATION,
-                                                      TitledBorder.DEFAULT_POSITION,
-                                                      new Font(panel1.getFont().getName(), panel1.getFont().getStyle(),
-                                                               panel1.getFont().getSize()), new Color(-16777216)));
+    panel.setLayout(new GridLayoutManager(5, 2, new Insets(0, 0, 0, 0), -1, -1));
+    final JPanel activityPanel = new JPanel();
+    activityPanel.setLayout(new GridLayoutManager(4, 2, new Insets(0, 0, 0, 0), -1, -1));
+    panel.add(
+        activityPanel,
+        new GridConstraints(
+            3,
+            0,
+            1,
+            2,
+            GridConstraints.ANCHOR_CENTER,
+            GridConstraints.FILL_BOTH,
+            GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
+            GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
+            null,
+            null,
+            null,
+            0,
+            false));
+    activityPanel.setBorder(
+        BorderFactory.createTitledBorder(
+            BorderFactory.createEtchedBorder(),
+            "Activity",
+            TitledBorder.DEFAULT_JUSTIFICATION,
+            TitledBorder.DEFAULT_POSITION,
+            new Font(
+                activityPanel.getFont().getName(),
+                activityPanel.getFont().getStyle(),
+                activityPanel.getFont().getSize()),
+            new Color(-16777216)));
+    final JPanel userPanel = new JPanel();
+    userPanel.setLayout(new GridLayoutManager(2, 2, new Insets(0, 0, 0, 0), -1, -1));
+    panel.add(
+        userPanel,
+        new GridConstraints(
+            4,
+            0,
+            1,
+            2,
+            GridConstraints.ANCHOR_CENTER,
+            GridConstraints.FILL_BOTH,
+            GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
+            GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
+            null,
+            null,
+            null,
+            0,
+            false));
+    userPanel.setBorder(
+        BorderFactory.createTitledBorder(
+            BorderFactory.createEtchedBorder(),
+            "User",
+            TitledBorder.DEFAULT_JUSTIFICATION,
+            TitledBorder.DEFAULT_POSITION,
+            new Font(
+                userPanel.getFont().getName(),
+                userPanel.getFont().getStyle(),
+                userPanel.getFont().getSize()),
+            new Color(-16777216)));
     launchNothingButton = new JRadioButton();
-    this.loadButtonText(launchNothingButton,
-                              ResourceBundle.getBundle("messages/AndroidBundle").getString("android.run.configuration.do.nothing.label"));
-    panel1.add(launchNothingButton, new GridConstraints(0, 0, 1, 2, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL,
-                                                        GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
-                                                        GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
+    this.loadButtonText(
+        launchNothingButton,
+        ResourceBundle.getBundle("messages/AndroidBundle")
+            .getString("android.run.configuration.do.nothing.label"));
+    activityPanel.add(
+        launchNothingButton,
+        new GridConstraints(
+            0,
+            0,
+            1,
+            2,
+            GridConstraints.ANCHOR_CENTER,
+            GridConstraints.FILL_HORIZONTAL,
+            GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
     launchDefaultButton = new JRadioButton();
     launchDefaultButton.setText("Launch default Activity");
     launchDefaultButton.setMnemonic('L');
     launchDefaultButton.setDisplayedMnemonicIndex(0);
-    panel1.add(launchDefaultButton, new GridConstraints(1, 0, 1, 2, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL,
-                                                        GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
-                                                        GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
+    activityPanel.add(
+        launchDefaultButton,
+        new GridConstraints(
+            1,
+            0,
+            1,
+            2,
+            GridConstraints.ANCHOR_CENTER,
+            GridConstraints.FILL_HORIZONTAL,
+            GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
     launchCustomButton = new JRadioButton();
     launchCustomButton.setText("Launch:");
     launchCustomButton.setMnemonic('A');
     launchCustomButton.setDisplayedMnemonicIndex(1);
-    panel1.add(launchCustomButton,
-               new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED,
-                                   GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
-    panel1.add(activityField, new GridConstraints(2, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH,
-                                                  GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
-                                                  GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
-    final JLabel label1 = new JLabel();
-    label1.setText("User ID");
-    panel1.add(label1,
-               new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED,
-                                   GridConstraints.SIZEPOLICY_FIXED, null, null, null, 1, false));
+    activityPanel.add(
+        launchCustomButton,
+        new GridConstraints(
+            2,
+            0,
+            1,
+            1,
+            GridConstraints.ANCHOR_WEST,
+            GridConstraints.FILL_NONE,
+            GridConstraints.SIZEPOLICY_FIXED,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
+    activityPanel.add(
+        activityField,
+        new GridConstraints(
+            2,
+            1,
+            1,
+            1,
+            GridConstraints.ANCHOR_CENTER,
+            GridConstraints.FILL_BOTH,
+            GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
+    useWorkProfileIfPresentCheckBox = new JCheckBox();
+    useWorkProfileIfPresentCheckBox.setText(" Use work profile if present");
+    userPanel.add(
+        useWorkProfileIfPresentCheckBox,
+        new GridConstraints(
+            0,
+            0,
+            1,
+            2,
+            GridConstraints.ANCHOR_WEST,
+            GridConstraints.FILL_NONE,
+            GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
+    userIdLabel = new JLabel();
+    userIdLabel.setText("User ID");
+    userPanel.add(
+        userIdLabel,
+        new GridConstraints(
+            1,
+            0,
+            1,
+            1,
+            GridConstraints.ANCHOR_WEST,
+            GridConstraints.FILL_NONE,
+            GridConstraints.SIZEPOLICY_FIXED,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            1,
+            false));
     userIdField = new IntegerTextField();
-    panel1.add(userIdField, new GridConstraints(3, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL,
-                                                GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0,
-                                                false));
+    userPanel.add(
+        userIdField,
+        new GridConstraints(
+            1,
+            1,
+            1,
+            1,
+            GridConstraints.ANCHOR_WEST,
+            GridConstraints.FILL_HORIZONTAL,
+            GridConstraints.SIZEPOLICY_WANT_GROW,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
     mobileInstallCheckBox = new JCheckBox();
-    mobileInstallCheckBox.setText(" Use blaze mobile-install (go/as-mi)");
-    panel.add(mobileInstallCheckBox, new GridConstraints(0, 0, 1, 2, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE,
-                                                         GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
-                                                         GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
+    mobileInstallCheckBox.setText(" Use mobile-install");
+    panel.add(
+        mobileInstallCheckBox,
+        new GridConstraints(
+            0,
+            0,
+            1,
+            2,
+            GridConstraints.ANCHOR_WEST,
+            GridConstraints.FILL_NONE,
+            GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
     splitApksCheckBox = new JCheckBox();
     splitApksCheckBox.setText(" Use --split_apks where possible");
-    panel.add(splitApksCheckBox, new GridConstraints(1, 0, 1, 2, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE,
-                                                         GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
-                                                         GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
+    panel.add(
+        splitApksCheckBox,
+        new GridConstraints(
+            1,
+            0,
+            1,
+            2,
+            GridConstraints.ANCHOR_WEST,
+            GridConstraints.FILL_NONE,
+            GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
     instantRunCheckBox = new JCheckBox();
     instantRunCheckBox.setText(" Use InstantRun");
-    panel.add(instantRunCheckBox, new GridConstraints(2, 0, 1, 2, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE,
-                                                         GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
-                                                         GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
+    panel.add(
+        instantRunCheckBox,
+        new GridConstraints(
+            2,
+            0,
+            1,
+            2,
+            GridConstraints.ANCHOR_WEST,
+            GridConstraints.FILL_NONE,
+            GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
     ButtonGroup buttonGroup;
     buttonGroup = new ButtonGroup();
     buttonGroup.add(launchDefaultButton);
@@ -289,9 +492,7 @@
     buttonGroup.add(launchNothingButton);
   }
 
-  /**
-   * Initially generated by IntelliJ from a .form file.
-   */
+  /** Initially generated by IntelliJ from a .form file. */
   private void loadButtonText(AbstractButton component, String text) {
     StringBuffer result = new StringBuffer();
     boolean haveMnemonic = false;
@@ -300,7 +501,9 @@
     for (int i = 0; i < text.length(); i++) {
       if (text.charAt(i) == '&') {
         i++;
-        if (i == text.length()) break;
+        if (i == text.length()) {
+          break;
+        }
         if (!haveMnemonic && text.charAt(i) != '&') {
           haveMnemonic = true;
           mnemonic = text.charAt(i);
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationType.java b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationType.java
index d4f9e6a..bfc5a02 100644
--- a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationType.java
+++ b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationType.java
@@ -15,57 +15,54 @@
  */
 package com.google.idea.blaze.android.run.binary;
 
-import com.google.idea.blaze.android.run.BlazeBeforeRunTaskProvider;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.model.primitives.Kind;
-import com.google.idea.blaze.base.run.BlazeRuleConfigurationFactory;
+import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
+import com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType;
 import com.google.idea.blaze.base.settings.Blaze;
-import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
 import com.intellij.execution.BeforeRunTask;
-import com.intellij.execution.RunManager;
-import com.intellij.execution.RunnerAndConfigurationSettings;
 import com.intellij.execution.configurations.ConfigurationFactory;
 import com.intellij.execution.configurations.ConfigurationType;
 import com.intellij.execution.configurations.ConfigurationTypeUtil;
+import com.intellij.execution.configurations.UnknownConfigurationType;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.Key;
 import icons.AndroidIcons;
+import javax.swing.Icon;
 import org.jetbrains.annotations.NotNull;
 
-import javax.swing.*;
-
 /**
  * A type for Android application run configurations adapted specifically to run android_binary
  * targets.
+ *
+ * @deprecated See {@link com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType}. Retained
+ *     in 1.9 for legacy purposes, to allow existing BlazeAndroidBinaryRunConfigurations to be
+ *     updated to BlazeCommandRunConfigurations. Intended to be removed in 2.1.
  */
-public class BlazeAndroidBinaryRunConfigurationType implements ConfigurationType {
+// Hack: extend UnknownConfigurationType to completely hide it in the Run/Debug Configurations UI.
+@Deprecated
+public class BlazeAndroidBinaryRunConfigurationType extends UnknownConfigurationType {
   private final BlazeAndroidBinaryRunConfigurationFactory factory =
-    new BlazeAndroidBinaryRunConfigurationFactory(this);
+      new BlazeAndroidBinaryRunConfigurationFactory(this);
 
-  public static class BlazeAndroidBinaryRuleConfigurationFactory implements BlazeRuleConfigurationFactory {
-    @Override
-    public boolean handlesRule(WorkspaceLanguageSettings workspaceLanguageSettings, @NotNull RuleIdeInfo rule) {
-      return rule.kindIsOneOf(Kind.ANDROID_BINARY);
-    }
-
-    @Override
-    @NotNull
-    public RunnerAndConfigurationSettings createForRule(@NotNull RunManager runManager, @NotNull RuleIdeInfo rule) {
-      return getInstance().factory.createForRule(runManager, rule);
-    }
-  }
-
-  public static class BlazeAndroidBinaryRunConfigurationFactory
-    extends ConfigurationFactory {
+  static class BlazeAndroidBinaryRunConfigurationFactory extends ConfigurationFactory {
 
     protected BlazeAndroidBinaryRunConfigurationFactory(@NotNull ConfigurationType type) {
       super(type);
     }
 
     @Override
+    public String getName() {
+      // Used to look up this ConfigurationFactory.
+      // Preserve value so legacy configurations can be loaded.
+      return Blaze.defaultBuildSystemName() + " Android Binary";
+    }
+
+    @Override
     @NotNull
-    public BlazeAndroidBinaryRunConfiguration createTemplateConfiguration(@NotNull Project project) {
-      return new BlazeAndroidBinaryRunConfiguration(project, this);
+    public BlazeCommandRunConfiguration createTemplateConfiguration(@NotNull Project project) {
+      // Create a BlazeCommandRunConfiguration instead, to update legacy configurations.
+      return BlazeCommandRunConfigurationType.getInstance()
+          .getFactory()
+          .createTemplateConfiguration(project);
     }
 
     @Override
@@ -75,23 +72,13 @@
 
     @Override
     public boolean isApplicable(@NotNull Project project) {
-      return Blaze.isBlazeProject(project);
+      return false;
     }
 
     @Override
     public void configureBeforeRunTaskDefaults(
-      Key<? extends BeforeRunTask> providerID, BeforeRunTask task) {
-      task.setEnabled(providerID.equals(BlazeBeforeRunTaskProvider.ID));
-    }
-
-    @NotNull
-    public RunnerAndConfigurationSettings createForRule(@NotNull RunManager runManager, @NotNull RuleIdeInfo rule) {
-      final RunnerAndConfigurationSettings settings =
-        runManager.createRunConfiguration(rule.label.toString(), this);
-      final BlazeAndroidBinaryRunConfiguration configuration =
-        (BlazeAndroidBinaryRunConfiguration) settings.getConfiguration();
-      configuration.setTarget(rule.label);
-      return settings;
+        Key<? extends BeforeRunTask> providerID, BeforeRunTask task) {
+      // Removed BlazeAndroidBeforeRunTaskProvider; this method won't be called anymore anyhow.
     }
 
     @Override
@@ -102,19 +89,20 @@
 
   @NotNull
   public static BlazeAndroidBinaryRunConfigurationType getInstance() {
-    return
-      ConfigurationTypeUtil.findConfigurationType(BlazeAndroidBinaryRunConfigurationType.class);
+    return ConfigurationTypeUtil.findConfigurationType(
+        BlazeAndroidBinaryRunConfigurationType.class);
   }
 
   @Override
   @NotNull
   public String getDisplayName() {
-    return Blaze.defaultBuildSystemName() + " Android Binary";
+    return "Legacy " + Blaze.defaultBuildSystemName() + " Android Binary";
   }
 
   @Override
   public String getConfigurationTypeDescription() {
-    return "Launch/debug configuration for android_binary rules";
+    return "Launch/debug configuration for android_binary rules. "
+        + "Use Blaze Command instead; this legacy configuration type is being removed.";
   }
 
   @Override
@@ -125,11 +113,13 @@
   @Override
   @NotNull
   public String getId() {
+    // Used to look up this ConfigurationType.
+    // Preserve value so legacy configurations can be loaded.
     return "BlazeAndroidBinaryRunConfigurationType";
   }
 
   @Override
   public BlazeAndroidBinaryRunConfigurationFactory[] getConfigurationFactories() {
-    return new BlazeAndroidBinaryRunConfigurationFactory[]{factory};
+    return new BlazeAndroidBinaryRunConfigurationFactory[] {factory};
   }
 }
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeDefaultActivityLocator.java b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeDefaultActivityLocator.java
index 0891fb8..d6fcd4a 100644
--- a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeDefaultActivityLocator.java
+++ b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeDefaultActivityLocator.java
@@ -22,11 +22,10 @@
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.Computable;
+import java.io.File;
 import org.jetbrains.android.dom.manifest.Manifest;
 import org.jetbrains.annotations.NotNull;
 
-import java.io.File;
-
 /**
  * An activity launcher which extracts the default launch activity from a generated APK and starts
  * it.
@@ -35,17 +34,13 @@
   private final Project project;
   private final File mergedManifestFile;
 
-  public BlazeDefaultActivityLocator(
-    Project project,
-    File mergedManifestFile
-  ) {
+  public BlazeDefaultActivityLocator(Project project, File mergedManifestFile) {
     this.project = project;
     this.mergedManifestFile = mergedManifestFile;
   }
 
   @Override
-  public void validate() throws ActivityLocatorException {
-  }
+  public void validate() throws ActivityLocatorException {}
 
   @NotNull
   @Override
@@ -54,9 +49,11 @@
     if (manifest == null) {
       throw new ActivityLocatorException("Could not locate merged manifest");
     }
-    String activityName = ApplicationManager.getApplication().runReadAction(
-      (Computable<String>)() -> DefaultActivityLocator.getDefaultLauncherActivityName(project, manifest)
-    );
+    String activityName =
+        ApplicationManager.getApplication()
+            .runReadAction(
+                (Computable<String>)
+                    () -> DefaultActivityLocator.getDefaultLauncherActivityName(project, manifest));
     if (activityName == null) {
       throw new ActivityLocatorException("Could not locate default activity to launch.");
     }
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/UserIdHelper.java b/aswb/src/com/google/idea/blaze/android/run/binary/UserIdHelper.java
new file mode 100644
index 0000000..0ca4ec5
--- /dev/null
+++ b/aswb/src/com/google/idea/blaze/android/run/binary/UserIdHelper.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.android.run.binary;
+
+import com.android.ddmlib.AdbCommandRejectedException;
+import com.android.ddmlib.CollectingOutputReceiver;
+import com.android.ddmlib.IDevice;
+import com.android.ddmlib.ShellCommandUnresponsiveException;
+import com.android.ddmlib.TimeoutException;
+import com.android.tools.idea.run.ConsolePrinter;
+import com.intellij.execution.ExecutionException;
+import java.io.IOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.annotation.Nullable;
+
+/** Helpers for user id */
+public final class UserIdHelper {
+  private static final Pattern USER_ID_REGEX =
+      Pattern.compile("UserInfo\\{([0-9]+):Work profile:[0-9]+\\}");
+
+  @Nullable
+  public static Integer getUserIdFromConfigurationState(
+      IDevice device, ConsolePrinter consolePrinter, BlazeAndroidBinaryRunConfigurationState state)
+      throws ExecutionException {
+    if (state.useWorkProfileIfPresent()) {
+      try {
+        Integer userId = getWorkProfileId(device);
+        if (userId == null) {
+          consolePrinter.stderr(
+              "Could not locate work profile on selected device. Launching default user.\n");
+        }
+        return userId;
+      } catch (TimeoutException
+          | AdbCommandRejectedException
+          | ShellCommandUnresponsiveException
+          | IOException e) {
+        throw new ExecutionException(e);
+      }
+    }
+    return state.getUserId();
+  }
+
+  @Nullable
+  public static Integer getWorkProfileId(IDevice device)
+      throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException,
+          IOException {
+    CollectingOutputReceiver receiver = new CollectingOutputReceiver();
+    device.executeShellCommand("pm list users", receiver);
+    String result = receiver.getOutput();
+    Matcher matcher = USER_ID_REGEX.matcher(result);
+    if (matcher.find()) {
+      return Integer.parseInt(matcher.group(1));
+    }
+    return null;
+  }
+
+  public static String getFlagsFromUserId(@Nullable Integer userId) {
+    return userId != null ? ("--user " + userId.intValue()) : "";
+  }
+}
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 4d260c0..f365e5d 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
@@ -19,6 +19,7 @@
 import com.android.tools.idea.fd.InstantRunBuildAnalyzer;
 import com.android.tools.idea.fd.InstantRunUtils;
 import com.android.tools.idea.run.ApplicationIdProvider;
+import com.android.tools.idea.run.ConsolePrinter;
 import com.android.tools.idea.run.ConsoleProvider;
 import com.android.tools.idea.run.LaunchOptions;
 import com.android.tools.idea.run.activity.DefaultStartActivityFlagsProvider;
@@ -32,27 +33,27 @@
 import com.android.tools.idea.run.util.ProcessHandlerLaunchStatus;
 import com.google.common.collect.ImmutableList;
 import com.google.common.util.concurrent.Futures;
-import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationCommonState;
 import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryApplicationLaunchTaskProvider;
 import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryConsoleProvider;
 import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryRunConfigurationState;
-import com.google.idea.blaze.android.run.runner.*;
+import com.google.idea.blaze.android.run.binary.UserIdHelper;
+import com.google.idea.blaze.android.run.runner.BlazeAndroidDeviceSelector;
+import com.google.idea.blaze.android.run.runner.BlazeAndroidLaunchTasksProvider;
+import com.google.idea.blaze.android.run.runner.BlazeAndroidRunConfigurationDebuggerManager;
+import com.google.idea.blaze.android.run.runner.BlazeAndroidRunContext;
+import com.google.idea.blaze.android.run.runner.BlazeApkBuildStep;
+import com.google.idea.blaze.base.model.primitives.Label;
 import com.intellij.execution.ExecutionException;
 import com.intellij.execution.configurations.RunConfiguration;
 import com.intellij.execution.runners.ExecutionEnvironment;
-import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.project.Project;
+import java.util.Set;
+import javax.annotation.Nullable;
 import org.jetbrains.android.facet.AndroidFacet;
 import org.jetbrains.annotations.NotNull;
 
-import javax.annotation.Nullable;
-import java.util.Set;
-
-/**
- * Run context for InstantRun.
- */
+/** Run context for InstantRun. */
 public class BlazeAndroidBinaryInstantRunContext implements BlazeAndroidRunContext {
-  private static final Logger LOG = Logger.getInstance(BlazeAndroidBinaryInstantRunContext.class);
 
   private final Project project;
   private final AndroidFacet facet;
@@ -63,20 +64,21 @@
   private final BlazeAndroidBinaryConsoleProvider consoleProvider;
   private final BlazeApkBuildStepInstantRun buildStep;
 
-  public BlazeAndroidBinaryInstantRunContext(Project project,
-                                             AndroidFacet facet,
-                                             RunConfiguration runConfiguration,
-                                             ExecutionEnvironment env,
-                                             BlazeAndroidRunConfigurationCommonState commonState,
-                                             BlazeAndroidBinaryRunConfigurationState configState,
-                                             ImmutableList<String> buildFlags) {
+  public BlazeAndroidBinaryInstantRunContext(
+      Project project,
+      AndroidFacet facet,
+      RunConfiguration runConfiguration,
+      ExecutionEnvironment env,
+      BlazeAndroidBinaryRunConfigurationState configState,
+      Label label,
+      ImmutableList<String> buildFlags) {
     this.project = project;
     this.facet = facet;
     this.runConfiguration = runConfiguration;
     this.env = env;
     this.configState = configState;
     this.consoleProvider = new BlazeAndroidBinaryConsoleProvider(project);
-    this.buildStep = new BlazeApkBuildStepInstantRun(project, env, commonState, buildFlags);
+    this.buildStep = new BlazeApkBuildStepInstantRun(project, env, label, buildFlags);
   }
 
   @Override
@@ -91,10 +93,7 @@
 
   @Override
   public void augmentLaunchOptions(@NotNull LaunchOptions.Builder options) {
-    options
-      .setDeploy(true)
-      .setPmInstallOptions(configState.ACTIVITY_EXTRA_FLAGS)
-      .setOpenLogcatAutomatically(true);
+    options.setDeploy(true).setOpenLogcatAutomatically(true);
   }
 
   @NotNull
@@ -115,59 +114,77 @@
 
   @Override
   public LaunchTasksProvider getLaunchTasksProvider(
-    LaunchOptions launchOptions,
-    BlazeAndroidRunConfigurationDebuggerManager debuggerManager) throws ExecutionException {
-    InstantRunBuildAnalyzer analyzer = Futures.get(buildStep.getInstantRunBuildAnalyzer(), ExecutionException.class);
+      LaunchOptions.Builder launchOptionsBuilder,
+      boolean isDebug,
+      BlazeAndroidRunConfigurationDebuggerManager debuggerManager)
+      throws ExecutionException {
+    InstantRunBuildAnalyzer analyzer =
+        Futures.get(buildStep.getInstantRunBuildAnalyzer(), ExecutionException.class);
 
     if (analyzer.canReuseProcessHandler()) {
       return new UpdateSessionTasksProvider(analyzer);
     }
-    return new BlazeAndroidLaunchTasksProvider(project, this, getApplicationIdProvider(), launchOptions, debuggerManager);
+    return new BlazeAndroidLaunchTasksProvider(
+        project, this, getApplicationIdProvider(), launchOptionsBuilder, isDebug, debuggerManager);
   }
 
   @Override
-  public ImmutableList<LaunchTask> getDeployTasks(IDevice device, LaunchOptions launchOptions) throws ExecutionException {
-    InstantRunBuildAnalyzer analyzer = Futures.get(buildStep.getInstantRunBuildAnalyzer(), ExecutionException.class);
+  public ImmutableList<LaunchTask> getDeployTasks(IDevice device, LaunchOptions launchOptions)
+      throws ExecutionException {
+    InstantRunBuildAnalyzer analyzer =
+        Futures.get(buildStep.getInstantRunBuildAnalyzer(), ExecutionException.class);
     return ImmutableList.<LaunchTask>builder()
-      .addAll(analyzer.getDeployTasks(launchOptions))
-      .add(analyzer.getNotificationTask())
-      .build();
+        .addAll(analyzer.getDeployTasks(launchOptions))
+        .add(analyzer.getNotificationTask())
+        .build();
   }
 
   @Nullable
   @Override
-  public LaunchTask getApplicationLaunchTask(LaunchOptions launchOptions,
-                                             AndroidDebugger androidDebugger,
-                                             AndroidDebuggerState androidDebuggerState,
-                                             ProcessHandlerLaunchStatus processHandlerLaunchStatus) throws ExecutionException {
-    BlazeApkBuildStepInstantRun.BuildResult buildResult = Futures.get(buildStep.getBuildResult(), ExecutionException.class);
+  public LaunchTask getApplicationLaunchTask(
+      LaunchOptions launchOptions,
+      @Nullable Integer userId,
+      AndroidDebugger androidDebugger,
+      AndroidDebuggerState androidDebuggerState,
+      ProcessHandlerLaunchStatus processHandlerLaunchStatus)
+      throws ExecutionException {
+    BlazeApkBuildStepInstantRun.BuildResult buildResult =
+        Futures.get(buildStep.getBuildResult(), ExecutionException.class);
 
-    final StartActivityFlagsProvider startActivityFlagsProvider = new DefaultStartActivityFlagsProvider(
-      androidDebugger,
-      androidDebuggerState,
-      project,
-      launchOptions.isDebug(),
-      configState.ACTIVITY_EXTRA_FLAGS
-    );
+    final StartActivityFlagsProvider startActivityFlagsProvider =
+        new DefaultStartActivityFlagsProvider(
+            androidDebugger,
+            androidDebuggerState,
+            project,
+            launchOptions.isDebug(),
+            UserIdHelper.getFlagsFromUserId(userId));
 
     ApplicationIdProvider applicationIdProvider = getApplicationIdProvider();
     return BlazeAndroidBinaryApplicationLaunchTaskProvider.getApplicationLaunchTask(
-      project,
-      applicationIdProvider,
-      buildResult.mergedManifestFile,
-      configState,
-      startActivityFlagsProvider,
-      processHandlerLaunchStatus
-    );
+        project,
+        applicationIdProvider,
+        buildResult.mergedManifestFile,
+        configState,
+        startActivityFlagsProvider,
+        processHandlerLaunchStatus);
   }
 
   @Nullable
   @Override
-  public DebugConnectorTask getDebuggerTask(LaunchOptions launchOptions,
-                                            AndroidDebugger androidDebugger,
-                                            AndroidDebuggerState androidDebuggerState,
-                                            Set<String> packageIds) throws ExecutionException {
+  public DebugConnectorTask getDebuggerTask(
+      AndroidDebugger androidDebugger,
+      AndroidDebuggerState androidDebuggerState,
+      Set<String> packageIds)
+      throws ExecutionException {
     //noinspection unchecked
-    return androidDebugger.getConnectDebuggerTask(env, null, packageIds, facet, androidDebuggerState, runConfiguration.getType().getId());
+    return androidDebugger.getConnectDebuggerTask(
+        env, null, packageIds, facet, androidDebuggerState, runConfiguration.getType().getId());
+  }
+
+  @Nullable
+  @Override
+  public Integer getUserId(IDevice device, ConsolePrinter consolePrinter)
+      throws ExecutionException {
+    return UserIdHelper.getUserIdFromConfigurationState(device, consolePrinter, configState);
   }
 }
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 46ca755..75821b1 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
@@ -16,16 +16,24 @@
 package com.google.idea.blaze.android.run.binary.instantrun;
 
 import com.android.ddmlib.IDevice;
-import com.android.tools.idea.fd.*;
+import com.android.tools.idea.fd.InstantRunBuildAnalyzer;
+import com.android.tools.idea.fd.InstantRunBuilder;
+import com.android.tools.idea.fd.InstantRunContext;
+import com.android.tools.idea.fd.InstantRunUtils;
+import com.android.tools.idea.fd.RunAsValidityService;
 import com.android.tools.idea.gradle.run.MakeBeforeRunTaskProvider;
-import com.android.tools.idea.run.*;
+import com.android.tools.idea.run.AndroidDevice;
+import com.android.tools.idea.run.AndroidRunConfigContext;
+import com.android.tools.idea.run.AndroidSessionInfo;
+import com.android.tools.idea.run.ApkProvisionException;
+import com.android.tools.idea.run.ApplicationIdProvider;
+import com.android.tools.idea.run.DeviceFutures;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.SettableFuture;
 import com.google.idea.blaze.android.manifest.ManifestParser;
-import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationCommonState;
 import com.google.idea.blaze.android.run.runner.BlazeAndroidDeviceSelector;
 import com.google.idea.blaze.android.run.runner.BlazeApkBuildStep;
 import com.google.idea.blaze.base.async.executor.BlazeExecutor;
@@ -36,8 +44,10 @@
 import com.google.idea.blaze.base.command.BlazeFlags;
 import com.google.idea.blaze.base.command.ExperimentalShowArtifactsLineProcessor;
 import com.google.idea.blaze.base.command.info.BlazeInfo;
+import com.google.idea.blaze.base.filecache.FileCaches;
 import com.google.idea.blaze.base.issueparser.IssueOutputLineProcessor;
 import com.google.idea.blaze.base.metrics.Action;
+import com.google.idea.blaze.base.model.primitives.Label;
 import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
 import com.google.idea.blaze.base.scope.BlazeContext;
 import com.google.idea.blaze.base.scope.ScopedTask;
@@ -51,74 +61,82 @@
 import com.intellij.execution.runners.ExecutionEnvironment;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.project.Project;
-import com.intellij.openapi.vfs.LocalFileSystem;
-import org.jetbrains.annotations.NotNull;
-
-import javax.annotation.Nullable;
-import java.io.*;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
 import java.lang.reflect.InvocationTargetException;
 import java.util.List;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
+import javax.annotation.Nullable;
+import org.jetbrains.annotations.NotNull;
 
-/**
- * Builds the APK using normal blaze build.
- */
+/** Builds the APK using normal blaze build. */
 class BlazeApkBuildStepInstantRun implements BlazeApkBuildStep {
   private static final Logger LOG = Logger.getInstance(BlazeApkBuildStepInstantRun.class);
 
   private final Project project;
   private final Executor executor;
   private final ExecutionEnvironment env;
-  private final BlazeAndroidRunConfigurationCommonState commonState;
+  private final Label label;
   private final ImmutableList<String> buildFlags;
   private final File instantRunArtifactDirectory;
   private final File instantRunGradleBuildFile;
   private final File instantRunBuildInfoFile;
   private final File instantRunGradlePropertiesFile;
 
-
   public static class BuildResult {
     public final File executionRoot;
     public final File mergedManifestFile;
     public final File apkManifestProtoFile;
     public final ApkManifestOuterClass.ApkManifest apkManifestProto;
-    public BuildResult(File executionRoot,
-                       File mergedManifestFile,
-                       File apkManifestProtoFile,
-                       ApkManifestOuterClass.ApkManifest apkManifestProto) {
+
+    public BuildResult(
+        File executionRoot,
+        File mergedManifestFile,
+        File apkManifestProtoFile,
+        ApkManifestOuterClass.ApkManifest apkManifestProto) {
       this.executionRoot = executionRoot;
       this.mergedManifestFile = mergedManifestFile;
       this.apkManifestProtoFile = apkManifestProtoFile;
       this.apkManifestProto = apkManifestProto;
     }
   }
-  private final SettableFuture<BuildResult> buildResultFuture = SettableFuture.create();
-  private final SettableFuture<ApplicationIdProvider> applicationIdProviderFuture = SettableFuture.create();
-  private final SettableFuture<InstantRunContext> instantRunContextFuture = SettableFuture.create();
-  private final SettableFuture<InstantRunBuildAnalyzer> instantRunBuildAnalyzerFuture = SettableFuture.create();
 
-  public BlazeApkBuildStepInstantRun(Project project,
-                                     ExecutionEnvironment env,
-                                     BlazeAndroidRunConfigurationCommonState commonState,
-                                     ImmutableList<String> buildFlags) {
+  private final SettableFuture<BuildResult> buildResultFuture = SettableFuture.create();
+  private final SettableFuture<ApplicationIdProvider> applicationIdProviderFuture =
+      SettableFuture.create();
+  private final SettableFuture<InstantRunContext> instantRunContextFuture = SettableFuture.create();
+  private final SettableFuture<InstantRunBuildAnalyzer> instantRunBuildAnalyzerFuture =
+      SettableFuture.create();
+
+  public BlazeApkBuildStepInstantRun(
+      Project project, ExecutionEnvironment env, Label label, ImmutableList<String> buildFlags) {
     this.project = project;
     this.executor = env.getExecutor();
     this.env = env;
-    this.commonState = commonState;
+    this.label = label;
     this.buildFlags = buildFlags;
-    this.instantRunArtifactDirectory = BlazeInstantRunGradleIntegration.getInstantRunArtifactDirectory(project, commonState.getTarget());
-    this.instantRunBuildInfoFile = new File(instantRunArtifactDirectory, "build/reload-dex/debug/build-info.xml");
+    this.instantRunArtifactDirectory =
+        BlazeInstantRunGradleIntegration.getInstantRunArtifactDirectory(project, label);
+    this.instantRunBuildInfoFile =
+        new File(instantRunArtifactDirectory, "build/reload-dex/debug/build-info.xml");
     this.instantRunGradleBuildFile = new File(instantRunArtifactDirectory, "build.gradle");
-    this.instantRunGradlePropertiesFile = new File(instantRunArtifactDirectory, "gradle.properties");
+    this.instantRunGradlePropertiesFile =
+        new File(instantRunArtifactDirectory, "gradle.properties");
   }
 
   @Override
-  public boolean build(BlazeContext context, BlazeAndroidDeviceSelector.DeviceSession deviceSession) {
+  public boolean build(
+      BlazeContext context, BlazeAndroidDeviceSelector.DeviceSession deviceSession) {
     if (!instantRunArtifactDirectory.exists() && !instantRunArtifactDirectory.mkdirs()) {
-      IssueOutput.error("Could not create instant run artifact directory: " + instantRunArtifactDirectory).submit(context);
+      IssueOutput.error(
+              "Could not create instant run artifact directory: " + instantRunArtifactDirectory)
+          .submit(context);
       return false;
     }
 
@@ -132,18 +150,16 @@
       return false;
     }
 
-    ApplicationIdProvider applicationIdProvider = new BlazeInstantRunApplicationIdProvider(project, buildResult);
+    ApplicationIdProvider applicationIdProvider =
+        new BlazeInstantRunApplicationIdProvider(project, buildResult);
     applicationIdProviderFuture.set(applicationIdProvider);
 
     // Write build.gradle
     try (PrintWriter printWriter = new PrintWriter(instantRunGradleBuildFile)) {
-      printWriter.print(BlazeInstantRunGradleIntegration.getGradleBuildInfoString(
-        gradleUrl,
-        buildResult.executionRoot,
-        buildResult.apkManifestProtoFile
-      ));
-    }
-    catch (IOException e) {
+      printWriter.print(
+          BlazeInstantRunGradleIntegration.getGradleBuildInfoString(
+              gradleUrl, buildResult.executionRoot, buildResult.apkManifestProtoFile));
+    } catch (IOException e) {
       IssueOutput.error("Could not write build.gradle file: " + e).submit(context);
       return false;
     }
@@ -151,8 +167,7 @@
     // Write gradle.properties
     try (PrintWriter printWriter = new PrintWriter(instantRunGradlePropertiesFile)) {
       printWriter.print(BlazeInstantRunGradleIntegration.getGradlePropertiesString());
-    }
-    catch (IOException e) {
+    } catch (IOException e) {
       IssueOutput.error("Could not write build.gradle file: " + e).submit(context);
       return false;
     }
@@ -160,8 +175,7 @@
     String applicationId = null;
     try {
       applicationId = applicationIdProvider.getPackageName();
-    }
-    catch (ApkProvisionException e) {
+    } catch (ApkProvisionException e) {
       return false;
     }
 
@@ -169,78 +183,79 @@
   }
 
   private BuildResult buildApkManifest(BlazeContext context) {
-    final ScopedTask buildTask = new ScopedTask(context) {
-      @Override
-      protected void execute(@NotNull BlazeContext context) {
-        WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProject(project);
-        String executionRoot = getExecutionRoot(context, workspaceRoot);
-        if (executionRoot == null) {
-          IssueOutput.error("Could not get execution root").submit(context);
-          return;
-        }
+    final ScopedTask buildTask =
+        new ScopedTask(context) {
+          @Override
+          protected void execute(@NotNull BlazeContext context) {
+            WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProject(project);
+            String executionRoot = getExecutionRoot(context, workspaceRoot);
+            if (executionRoot == null) {
+              IssueOutput.error("Could not get execution root").submit(context);
+              return;
+            }
 
-        BlazeCommand.Builder command = BlazeCommand.builder(Blaze.getBuildSystem(project), BlazeCommandName.BUILD);
+            BlazeCommand.Builder command =
+                BlazeCommand.builder(Blaze.getBuildSystem(project), BlazeCommandName.BUILD);
 
-        command
-          .addTargets(commonState.getTarget())
-          .addBlazeFlags(buildFlags)
-          .addBlazeFlags("--output_groups=apk_manifest")
-          .addBlazeFlags(BlazeFlags.EXPERIMENTAL_SHOW_ARTIFACTS)
-        ;
+            command
+                .addTargets(label)
+                .addBlazeFlags(buildFlags)
+                .addBlazeFlags("--output_groups=apk_manifest")
+                .addBlazeFlags(BlazeFlags.EXPERIMENTAL_SHOW_ARTIFACTS);
 
-        List<File> apkManifestFiles = Lists.newArrayList();
+            List<File> apkManifestFiles = Lists.newArrayList();
 
-        SaveUtil.saveAllFiles();
-        int retVal = ExternalTask.builder(workspaceRoot, command.build())
-          .context(context)
-          .stderr(LineProcessingOutputStream.of(
-            new ExperimentalShowArtifactsLineProcessor(apkManifestFiles, "apk_manifest"),
-            new IssueOutputLineProcessor(project, context, workspaceRoot)
-          ))
-          .build()
-          .run(new LoggedTimingScope(project, Action.BLAZE_BUILD));
-        LocalFileSystem.getInstance().refresh(true);
+            SaveUtil.saveAllFiles();
+            int retVal =
+                ExternalTask.builder(workspaceRoot)
+                    .addBlazeCommand(command.build())
+                    .context(context)
+                    .stderr(
+                        LineProcessingOutputStream.of(
+                            new ExperimentalShowArtifactsLineProcessor(
+                                apkManifestFiles, "apk_manifest"),
+                            new IssueOutputLineProcessor(project, context, workspaceRoot)))
+                    .build()
+                    .run(new LoggedTimingScope(project, Action.BLAZE_BUILD));
+            FileCaches.refresh(project);
 
-        if (retVal != 0) {
-          context.setHasError();
-          return;
-        }
+            if (retVal != 0) {
+              context.setHasError();
+              return;
+            }
 
-        File apkManifestFile = Iterables.getOnlyElement(apkManifestFiles, null);
-        if (apkManifestFile == null) {
-          IssueOutput.error("Could not find APK manifest file").submit(context);
-          return;
-        }
+            File apkManifestFile = Iterables.getOnlyElement(apkManifestFiles, null);
+            if (apkManifestFile == null) {
+              IssueOutput.error("Could not find APK manifest file").submit(context);
+              return;
+            }
 
-        ApkManifestOuterClass.ApkManifest apkManifestProto;
-        try (InputStream inputStream = new FileInputStream(apkManifestFile)) {
-          apkManifestProto = ApkManifestOuterClass.ApkManifest.parseFrom(inputStream);
-        }
-        catch (IOException e) {
-          LOG.error(e);
-          IssueOutput.error("Error parsing apk proto").submit(context);
-          return;
-        }
+            ApkManifestOuterClass.ApkManifest apkManifestProto;
+            try (InputStream inputStream = new FileInputStream(apkManifestFile)) {
+              apkManifestProto = ApkManifestOuterClass.ApkManifest.parseFrom(inputStream);
+            } catch (IOException e) {
+              LOG.error(e);
+              IssueOutput.error("Error parsing apk proto").submit(context);
+              return;
+            }
 
-        // Refresh the manifest
-        File mergedManifestFile = new File(executionRoot, apkManifestProto.getAndroidManifest().getExecRootPath());
-        ManifestParser.getInstance(project).refreshManifests(ImmutableList.of(mergedManifestFile));
+            // Refresh the manifest
+            File mergedManifestFile =
+                new File(executionRoot, apkManifestProto.getAndroidManifest().getExecRootPath());
+            ManifestParser.getInstance(project)
+                .refreshManifests(ImmutableList.of(mergedManifestFile));
 
-        BuildResult buildResult = new BuildResult(
-          new File(executionRoot),
-          mergedManifestFile,
-          apkManifestFile,
-          apkManifestProto
-        );
-        buildResultFuture.set(buildResult);
-      }
-    };
+            BuildResult buildResult =
+                new BuildResult(
+                    new File(executionRoot), mergedManifestFile, apkManifestFile, apkManifestProto);
+            buildResultFuture.set(buildResult);
+          }
+        };
 
     BlazeExecutor.submitTask(
-      project,
-      String.format("Executing %s apk build", Blaze.buildSystemName(project)),
-      buildTask
-    );
+        project,
+        String.format("Executing %s apk build", Blaze.buildSystemName(project)),
+        buildTask);
 
     try {
       BuildResult buildResult = buildResultFuture.get();
@@ -248,30 +263,25 @@
         return null;
       }
       return buildResult;
-    }
-    catch (InterruptedException|ExecutionException e) {
+    } catch (InterruptedException | ExecutionException e) {
       context.setHasError();
-    }
-    catch (CancellationException e) {
+    } catch (CancellationException e) {
       context.setCancelled();
     }
     return null;
   }
 
-  private boolean invokeGradleIrTasks(BlazeContext context,
-                                      BlazeAndroidDeviceSelector.DeviceSession deviceSession,
-                                      BuildResult buildResult,
-                                      String applicationId) {
-    InstantRunContext instantRunContext = new BlazeInstantRunContext(
-      project,
-      buildResult.apkManifestProto,
-      applicationId,
-      instantRunBuildInfoFile
-    );
+  private boolean invokeGradleIrTasks(
+      BlazeContext context,
+      BlazeAndroidDeviceSelector.DeviceSession deviceSession,
+      BuildResult buildResult,
+      String applicationId) {
+    InstantRunContext instantRunContext =
+        new BlazeInstantRunContext(
+            project, buildResult.apkManifestProto, applicationId, instantRunBuildInfoFile);
     instantRunContextFuture.set(instantRunContext);
-    ProcessHandler previousSessionProcessHandler = deviceSession.sessionInfo != null
-                                                   ? deviceSession.sessionInfo.getProcessHandler()
-                                                   : null;
+    ProcessHandler previousSessionProcessHandler =
+        deviceSession.sessionInfo != null ? deviceSession.sessionInfo.getProcessHandler() : null;
     DeviceFutures deviceFutures = deviceSession.deviceFutures;
     assert deviceFutures != null;
     List<AndroidDevice> targetDevices = deviceFutures.getDevices();
@@ -282,42 +292,39 @@
     runConfigContext.setTargetDevices(deviceFutures);
 
     AndroidSessionInfo info = deviceSession.sessionInfo;
-    runConfigContext.setSameExecutorAsPreviousSession(info != null && executor.getId().equals(info.getExecutorId()));
+    runConfigContext.setSameExecutorAsPreviousSession(
+        info != null && executor.getId().equals(info.getExecutorId()));
     runConfigContext.setCleanRerun(InstantRunUtils.isCleanReRun(env));
 
-    InstantRunBuilder instantRunBuilder = new InstantRunBuilder(
-      device,
-      instantRunContext,
-      runConfigContext,
-      new BlazeInstantRunTasksProvider(),
-      RunAsValidityService.getInstance()
-    );
+    InstantRunBuilder instantRunBuilder =
+        new InstantRunBuilder(
+            device,
+            instantRunContext,
+            runConfigContext,
+            new BlazeInstantRunTasksProvider(),
+            RunAsValidityService.getInstance());
 
     try {
       List<String> cmdLineArgs = Lists.newArrayList();
       cmdLineArgs.addAll(MakeBeforeRunTaskProvider.getDeviceSpecificArguments(targetDevices));
-      BlazeInstantRunGradleTaskRunner taskRunner = new BlazeInstantRunGradleTaskRunner(project, context, instantRunGradleBuildFile);
+      BlazeInstantRunGradleTaskRunner taskRunner =
+          new BlazeInstantRunGradleTaskRunner(project, context, instantRunGradleBuildFile);
       boolean success = instantRunBuilder.build(taskRunner, cmdLineArgs);
       LOG.info("Gradle invocation complete, success = " + success);
       if (!success) {
         return false;
       }
-    }
-    catch (InvocationTargetException e) {
+    } catch (InvocationTargetException e) {
       LOG.info("Unexpected error while launching gradle before run tasks", e);
       return false;
-    }
-    catch (InterruptedException e) {
+    } catch (InterruptedException e) {
       LOG.info("Interrupted while launching gradle before run tasks");
       Thread.currentThread().interrupt();
       return false;
     }
 
-    InstantRunBuildAnalyzer analyzer = new InstantRunBuildAnalyzer(
-      project,
-      instantRunContext,
-      previousSessionProcessHandler
-    );
+    InstantRunBuildAnalyzer analyzer =
+        new InstantRunBuildAnalyzer(project, instantRunContext, previousSessionProcessHandler);
     instantRunBuildAnalyzerFuture.set(analyzer);
     return true;
   }
@@ -339,19 +346,19 @@
   }
 
   private String getExecutionRoot(BlazeContext context, WorkspaceRoot workspaceRoot) {
-    ListenableFuture<String> execRootFuture = BlazeInfo.getInstance().runBlazeInfo(
-      context, Blaze.getBuildSystem(project),
-      workspaceRoot,
-      buildFlags,
-      BlazeInfo.EXECUTION_ROOT_KEY
-    );
+    ListenableFuture<String> execRootFuture =
+        BlazeInfo.getInstance()
+            .runBlazeInfo(
+                context,
+                Blaze.getBuildSystem(project),
+                workspaceRoot,
+                buildFlags,
+                BlazeInfo.EXECUTION_ROOT_KEY);
     try {
       return execRootFuture.get();
-    }
-    catch (InterruptedException e) {
+    } catch (InterruptedException e) {
       context.setCancelled();
-    }
-    catch (ExecutionException e) {
+    } catch (ExecutionException e) {
       LOG.error(e);
       context.setHasError();
     }
@@ -367,12 +374,10 @@
 
     try {
       return device.getLaunchedDevice().get(1, TimeUnit.MILLISECONDS);
-    }
-    catch (InterruptedException e) {
+    } catch (InterruptedException e) {
       Thread.currentThread().interrupt();
       return null;
-    }
-    catch (ExecutionException | TimeoutException e) {
+    } catch (ExecutionException | TimeoutException e) {
       return null;
     }
   }
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunApplicationIdProvider.java b/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunApplicationIdProvider.java
index 7349986..ab1461e 100644
--- a/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunApplicationIdProvider.java
+++ b/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunApplicationIdProvider.java
@@ -21,21 +21,18 @@
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.Computable;
+import java.io.File;
 import org.jetbrains.android.dom.manifest.Manifest;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import java.io.File;
-
-/**
- * Application id provider for blaze instant run.
- */
+/** Application id provider for blaze instant run. */
 public class BlazeInstantRunApplicationIdProvider implements ApplicationIdProvider {
   private final Project project;
   private final BlazeApkBuildStepInstantRun.BuildResult buildResult;
 
-  public BlazeInstantRunApplicationIdProvider(Project project,
-                                              BlazeApkBuildStepInstantRun.BuildResult buildResult) {
+  public BlazeInstantRunApplicationIdProvider(
+      Project project, BlazeApkBuildStepInstantRun.BuildResult buildResult) {
     this.project = project;
     this.buildResult = buildResult;
   }
@@ -43,14 +40,17 @@
   @NotNull
   @Override
   public String getPackageName() throws ApkProvisionException {
-    File manifestFile = new File(buildResult.executionRoot, buildResult.apkManifestProto.getAndroidManifest().getExecRootPath());
+    File manifestFile =
+        new File(
+            buildResult.executionRoot,
+            buildResult.apkManifestProto.getAndroidManifest().getExecRootPath());
     Manifest manifest = ManifestParser.getInstance(project).getManifest(manifestFile);
     if (manifest == null) {
       throw new ApkProvisionException("Could not find merged manifest: " + manifestFile);
     }
-    String applicationId = ApplicationManager.getApplication().runReadAction(
-      (Computable<String>)() -> manifest.getPackage().getValue()
-    );
+    String applicationId =
+        ApplicationManager.getApplication()
+            .runReadAction((Computable<String>) () -> manifest.getPackage().getValue());
     if (applicationId == null) {
       throw new ApkProvisionException("No application id in merged manifest: " + manifestFile);
     }
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunContext.java b/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunContext.java
index 2d86199..9ea7c03 100644
--- a/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunContext.java
+++ b/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunContext.java
@@ -23,18 +23,15 @@
 import com.google.repackaged.devtools.build.lib.rules.android.apkmanifest.ApkManifestOuterClass;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.project.Project;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
 import java.io.File;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Paths;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
-/**
- * Blaze implementation of instant run context.
- */
+/** Blaze implementation of instant run context. */
 public class BlazeInstantRunContext implements InstantRunContext {
   private static final Logger LOG = Logger.getInstance(BlazeInstantRunContext.class);
   private final Project project;
@@ -43,10 +40,11 @@
   private final File instantRunBuildInfoFile;
   private BuildSelection buildSelection;
 
-  BlazeInstantRunContext(Project project,
-                         ApkManifestOuterClass.ApkManifest apkManifest,
-                         String applicationId,
-                         File instantRunBuildInfoFile) {
+  BlazeInstantRunContext(
+      Project project,
+      ApkManifestOuterClass.ApkManifest apkManifest,
+      String applicationId,
+      File instantRunBuildInfoFile) {
     this.project = project;
     this.apkManifest = apkManifest;
     this.applicationId = applicationId;
@@ -83,10 +81,12 @@
   public InstantRunBuildInfo getInstantRunBuildInfo() {
     if (instantRunBuildInfoFile.exists()) {
       try {
-        String xml = new String(Files.readAllBytes(Paths.get(instantRunBuildInfoFile.getPath())), StandardCharsets.UTF_8);
+        String xml =
+            new String(
+                Files.readAllBytes(Paths.get(instantRunBuildInfoFile.getPath())),
+                StandardCharsets.UTF_8);
         return InstantRunBuildInfo.get(xml);
-      }
-      catch (IOException e) {
+      } catch (IOException e) {
         LOG.error(e);
       }
     }
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunDeviceSelector.java b/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunDeviceSelector.java
index 0bebd20..8c26fc4 100644
--- a/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunDeviceSelector.java
+++ b/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunDeviceSelector.java
@@ -20,35 +20,35 @@
 import com.android.tools.idea.fd.InstantRunUtils;
 import com.android.tools.idea.run.AndroidSessionInfo;
 import com.android.tools.idea.run.DeviceFutures;
-import com.google.idea.blaze.android.run.runner.BlazeAndroidRunConfigurationDeployTargetManager;
 import com.google.idea.blaze.android.run.runner.BlazeAndroidDeviceSelector;
+import com.google.idea.blaze.android.run.runner.BlazeAndroidRunConfigurationDeployTargetManager;
 import com.intellij.execution.ExecutionException;
 import com.intellij.execution.Executor;
 import com.intellij.execution.runners.ExecutionEnvironment;
 import com.intellij.openapi.project.Project;
+import java.util.List;
+import javax.annotation.Nullable;
 import org.jetbrains.android.facet.AndroidFacet;
 
-import javax.annotation.Nullable;
-import java.util.List;
-
-/**
- * Tries to reuse devices from a previous session.
- */
+/** Tries to reuse devices from a previous session. */
 public class BlazeInstantRunDeviceSelector implements BlazeAndroidDeviceSelector {
   NormalDeviceSelector normalDeviceSelector = new NormalDeviceSelector();
 
   @Override
-  public DeviceSession getDevice(Project project,
-                                 AndroidFacet facet,
-                                 BlazeAndroidRunConfigurationDeployTargetManager deployTargetManager,
-                                 Executor executor,
-                                 ExecutionEnvironment env,
-                                 AndroidSessionInfo info,
-                                 boolean debug,
-                                 int runConfigId) throws ExecutionException {
+  public DeviceSession getDevice(
+      Project project,
+      AndroidFacet facet,
+      BlazeAndroidRunConfigurationDeployTargetManager deployTargetManager,
+      Executor executor,
+      ExecutionEnvironment env,
+      AndroidSessionInfo info,
+      boolean debug,
+      int runConfigId)
+      throws ExecutionException {
     DeviceFutures deviceFutures = null;
     if (info != null) {
-      // if there is an existing previous session, then see if we can detect devices to fast deploy to
+      // if there is an existing previous session,
+      // then see if we can detect devices to fast deploy to
       deviceFutures = getFastDeployDevices(executor, info);
 
       if (InstantRunUtils.isReRun(env)) {
@@ -62,27 +62,32 @@
     }
 
     // Fall back to normal device selection
-    return normalDeviceSelector.getDevice(project, facet, deployTargetManager, executor, env, info, debug, runConfigId);
+    return normalDeviceSelector.getDevice(
+        project, facet, deployTargetManager, executor, env, info, debug, runConfigId);
   }
 
   @Nullable
-  private static DeviceFutures getFastDeployDevices(Executor executor,
-                                                    AndroidSessionInfo info) {
+  private static DeviceFutures getFastDeployDevices(Executor executor, AndroidSessionInfo info) {
     if (!info.getExecutorId().equals(executor.getId())) {
-      String msg = String.format("Cannot Instant Run since old executor (%1$s) doesn't match current executor (%2$s)", info.getExecutorId(),
-                                 executor.getId());
+      String msg =
+          String.format(
+              "Cannot Instant Run since old executor (%1$s) doesn't match current executor (%2$s)",
+              info.getExecutorId(), executor.getId());
       InstantRunManager.LOG.info(msg);
       return null;
     }
 
     List<IDevice> devices = info.getDevices();
     if (devices == null || devices.isEmpty()) {
-      InstantRunManager.LOG.info("Cannot Instant Run since we could not locate the devices from the existing launch session");
+      InstantRunManager.LOG.info(
+          "Cannot Instant Run since we could not locate "
+              + "the devices from the existing launch session");
       return null;
     }
 
     if (devices.size() > 1) {
-      InstantRunManager.LOG.info("Last run was on > 1 device, not reusing devices and prompting again");
+      InstantRunManager.LOG.info(
+          "Last run was on > 1 device, not reusing devices and prompting again");
       return null;
     }
 
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunGradleIntegration.java b/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunGradleIntegration.java
index c95f42e..a9873ca 100644
--- a/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunGradleIntegration.java
+++ b/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunGradleIntegration.java
@@ -17,42 +17,39 @@
 
 import com.android.SdkConstants;
 import com.google.common.base.Joiner;
-import com.google.common.collect.ImmutableList;
 import com.google.common.hash.Hashing;
 import com.google.idea.blaze.base.async.process.ExternalTask;
 import com.google.idea.blaze.base.async.process.LineProcessingOutputStream;
 import com.google.idea.blaze.base.async.process.PrintOutputLineProcessor;
-import com.google.idea.blaze.base.experiments.DeveloperFlag;
-import com.google.idea.blaze.base.experiments.StringExperiment;
 import com.google.idea.blaze.base.model.primitives.Label;
 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.PrintOutput;
+import com.google.idea.blaze.base.scope.output.StatusOutput;
 import com.google.idea.blaze.base.settings.BlazeImportSettings;
 import com.google.idea.blaze.base.settings.BlazeImportSettingsManager;
-import com.google.idea.blaze.base.sync.projectstructure.ModuleDataStorage;
+import com.google.idea.blaze.base.sync.data.BlazeDataStorage;
+import com.google.idea.common.experiments.DeveloperFlag;
+import com.google.idea.common.experiments.StringExperiment;
 import com.intellij.openapi.application.PathManager;
 import com.intellij.openapi.project.Project;
-
-import javax.annotation.Nullable;
 import java.io.File;
+import javax.annotation.Nullable;
 
-/**
- * Defines where instant run storage and artifacts go.
- */
+/** Defines where instant run storage and artifacts go. */
 class BlazeInstantRunGradleIntegration {
   private static final String INSTANT_RUN_SUBDIRECTORY = "instantrun";
 
-  private static StringExperiment LOCAL_GRADLE_VERSION = new StringExperiment("use.local.gradle.version");
-  private static DeveloperFlag REBUILD_LOCAL_GRADLE = new DeveloperFlag("rebuild.local.gradle");
+  private static final StringExperiment LOCAL_GRADLE_VERSION =
+      new StringExperiment("use.local.gradle.version");
+  private static final DeveloperFlag REBUILD_LOCAL_GRADLE =
+      new DeveloperFlag("rebuild.local.gradle");
 
-  /**
-   * Gets a unique directory for a given target that can be used for the build process.
-   */
+  /** Gets a unique directory for a given target that can be used for the build process. */
   static File getInstantRunArtifactDirectory(Project project, Label target) {
-    BlazeImportSettings importSettings = BlazeImportSettingsManager.getInstance(project).getImportSettings();
+    BlazeImportSettings importSettings =
+        BlazeImportSettingsManager.getInstance(project).getImportSettings();
     assert importSettings != null;
-    File dataSubDirectory = new File(importSettings.getProjectDataDirectory(), ModuleDataStorage.DATA_SUBDIRECTORY);
+    File dataSubDirectory = BlazeDataStorage.getProjectDataDir(importSettings);
     File instantRunDirectory = new File(dataSubDirectory, INSTANT_RUN_SUBDIRECTORY);
     String targetHash = Hashing.md5().hashUnencodedChars(target.toString()).toString();
     return new File(instantRunDirectory, targetHash);
@@ -67,14 +64,18 @@
       String toolsIdeaPath = PathManager.getHomePath();
       File toolsDir = new File(toolsIdeaPath).getParentFile();
       File repoDir = toolsDir.getParentFile();
-      File localGradleDirectory = new File(new File(repoDir, "out/repo/com/android/tools/build/builder"), localGradleVersion);
+      File localGradleDirectory =
+          new File(
+              new File(repoDir, "out/repo/com/android/tools/build/builder"), localGradleVersion);
       if (REBUILD_LOCAL_GRADLE.getValue() || !localGradleDirectory.exists()) {
         // Build gradle
-        context.output(PrintOutput.output("Building local Gradle..."));
-        int retVal = ExternalTask.builder(toolsDir, ImmutableList.of("./gradlew", ":init", ":publishLocal"))
-          .stdout(LineProcessingOutputStream.of(new PrintOutputLineProcessor(context)))
-          .build()
-          .run();
+        context.output(new StatusOutput("Building local Gradle..."));
+        int retVal =
+            ExternalTask.builder(toolsDir)
+                .args("./gradlew", ":init", ":publishLocal")
+                .stdout(LineProcessingOutputStream.of(new PrintOutputLineProcessor(context)))
+                .build()
+                .run();
 
         if (retVal != 0) {
           IssueOutput.error("Gradle build failed.").submit(context);
@@ -85,42 +86,41 @@
     }
 
     // Not supported yet
-    IssueOutput.error("You must specify 'use.local.gradle.version' experiment, non-local gradle not supported yet.").submit(context);
+    IssueOutput.error(
+            "You must specify 'use.local.gradle.version' experiment, "
+                + "non-local gradle not supported yet.")
+        .submit(context);
     return null;
   }
 
   static String getGradlePropertiesString() {
-    return Joiner.on('\n').join(
-      "org.gradle.daemon=true",
-      "org.gradle.jvmargs=-XX:MaxPermSize=1024m -Xmx4096m"
-    );
+    return Joiner.on('\n')
+        .join("org.gradle.daemon=true", "org.gradle.jvmargs=-XX:MaxPermSize=1024m -Xmx4096m");
   }
 
-  static String getGradleBuildInfoString(String gradleUrl, File executionRoot, File apkManifestFile) {
-    String template = Joiner.on('\n').join(
-      "buildscript {",
-      "  repositories {",
-      "    jcenter()",
-      "    maven { url '%s' }",
-      "  }",
-      "  dependencies {",
-      "    classpath 'com.android.tools.build:gradle:%s'",
-      "  }",
-      "}",
-      "apply plugin: 'com.android.external.build'",
-      "externalBuild {",
-      "  executionRoot = '%s'",
-      "  buildManifestPath = '%s'",
-      "}"
-    );
+  static String getGradleBuildInfoString(
+      String gradleUrl, File executionRoot, File apkManifestFile) {
+    String template =
+        Joiner.on('\n')
+            .join(
+                "buildscript {",
+                "  repositories {",
+                "    jcenter()",
+                "    maven { url '%s' }",
+                "  }",
+                "  dependencies {",
+                "    classpath 'com.android.tools.build:gradle:%s'",
+                "  }",
+                "}",
+                "apply plugin: 'com.android.external.build'",
+                "externalBuild {",
+                "  executionRoot = '%s'",
+                "  buildManifestPath = '%s'",
+                "}");
     String gradleVersion = LOCAL_GRADLE_VERSION.getValue();
     gradleVersion = gradleVersion != null ? gradleVersion : SdkConstants.GRADLE_LATEST_VERSION;
 
-    return String.format(template,
-                         gradleUrl,
-                         gradleVersion,
-                         executionRoot.getPath(),
-                         apkManifestFile.getPath()
-    );
+    return String.format(
+        template, gradleUrl, gradleVersion, executionRoot.getPath(), apkManifestFile.getPath());
   }
 }
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunGradleTaskRunner.java b/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunGradleTaskRunner.java
index 51554b2..bdeab5a 100644
--- a/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunGradleTaskRunner.java
+++ b/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunGradleTaskRunner.java
@@ -15,6 +15,9 @@
  */
 package com.google.idea.blaze.android.run.binary.instantrun;
 
+import static com.android.tools.idea.gradle.util.GradleUtil.GRADLE_SYSTEM_ID;
+import static com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskType.EXECUTE_TASK;
+
 import com.android.builder.model.AndroidProject;
 import com.android.tools.idea.gradle.invoker.GradleInvocationResult;
 import com.android.tools.idea.gradle.invoker.GradleInvoker;
@@ -30,33 +33,33 @@
 import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListenerAdapter;
 import com.intellij.openapi.project.Project;
 import com.intellij.util.concurrency.Semaphore;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import javax.swing.*;
 import java.io.File;
 import java.lang.reflect.InvocationTargetException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
+import javax.swing.SwingUtilities;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
-import static com.android.tools.idea.gradle.util.GradleUtil.GRADLE_SYSTEM_ID;
-import static com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskType.EXECUTE_TASK;
-
-public class BlazeInstantRunGradleTaskRunner implements GradleTaskRunner {
+class BlazeInstantRunGradleTaskRunner implements GradleTaskRunner {
   private final Project project;
   private final BlazeContext context;
   private final File instantRunGradleBuildFile;
 
-  public BlazeInstantRunGradleTaskRunner(Project project, BlazeContext context, File instantRunGradleBuildFile) {
+  public BlazeInstantRunGradleTaskRunner(
+      Project project, BlazeContext context, File instantRunGradleBuildFile) {
     this.project = project;
     this.context = context;
     this.instantRunGradleBuildFile = instantRunGradleBuildFile;
   }
 
   @Override
-  public boolean run(@NotNull List<String> tasks, @Nullable BuildMode buildMode, @NotNull List<String> commandLineArguments)
-    throws InvocationTargetException, InterruptedException {
+  public boolean run(
+      @NotNull List<String> tasks,
+      @Nullable BuildMode buildMode,
+      @NotNull List<String> commandLineArguments)
+      throws InvocationTargetException, InterruptedException {
     assert !ApplicationManager.getApplication().isDispatchThread();
 
     final GradleInvoker gradleInvoker = GradleInvoker.getInstance(project);
@@ -65,40 +68,47 @@
     final Semaphore done = new Semaphore();
     done.down();
 
-    final GradleInvoker.AfterGradleInvocationTask afterTask = new GradleInvoker.AfterGradleInvocationTask() {
-      @Override
-      public void execute(@NotNull GradleInvocationResult result) {
-        success.set(result.isBuildSuccessful());
-        gradleInvoker.removeAfterGradleInvocationTask(this);
-        done.up();
-      }
-    };
+    final GradleInvoker.AfterGradleInvocationTask afterTask =
+        new GradleInvoker.AfterGradleInvocationTask() {
+          @Override
+          public void execute(@NotNull GradleInvocationResult result) {
+            success.set(result.isBuildSuccessful());
+            gradleInvoker.removeAfterGradleInvocationTask(this);
+            done.up();
+          }
+        };
 
-    ExternalSystemTaskId taskId = ExternalSystemTaskId.create(GRADLE_SYSTEM_ID, EXECUTE_TASK, project);
+    ExternalSystemTaskId taskId =
+        ExternalSystemTaskId.create(GRADLE_SYSTEM_ID, EXECUTE_TASK, project);
     List<String> jvmArguments = ImmutableList.of();
 
-    // https://code.google.com/p/android/issues/detail?id=213040 - make split apks only available if an env var is set
+    // https://code.google.com/p/android/issues/detail?id=213040 -
+    // make split apks only available if an env var is set
     List<String> args = new ArrayList<>(commandLineArguments);
     if (!Boolean.valueOf(System.getenv(GradleTaskRunner.USE_SPLIT_APK))) {
       // force multi dex when the env var is not set to true
-      args.add(AndroidGradleSettings.createProjectProperty(AndroidProject.PROPERTY_SIGNING_COLDSWAP_MODE, "MULTIDEX"));
+      args.add(
+          AndroidGradleSettings.createProjectProperty(
+              AndroidProject.PROPERTY_SIGNING_COLDSWAP_MODE, "MULTIDEX"));
     }
 
-    // To ensure that the "Run Configuration" waits for the Gradle tasks to be executed, we use SwingUtilities.invokeAndWait. I tried
-    // using Application.invokeAndWait but it never worked. IDEA also uses SwingUtilities in this scenario (see CompileStepBeforeRun.)
-    SwingUtilities.invokeAndWait(() -> {
-      gradleInvoker.addAfterGradleInvocationTask(afterTask);
-      gradleInvoker.executeTasks(
-        tasks,
-        jvmArguments,
-        args,
-        taskId,
-        new GradleNotificationListener(),
-        instantRunGradleBuildFile,
-        false,
-        true
-      );
-    });
+    // To ensure that the "Run Configuration" waits for the Gradle tasks to be executed,
+    // we use SwingUtilities.invokeAndWait. I tried
+    // using Application.invokeAndWait but it never worked.
+    // IDEA also uses SwingUtilities in this scenario (see CompileStepBeforeRun.)
+    SwingUtilities.invokeAndWait(
+        () -> {
+          gradleInvoker.addAfterGradleInvocationTask(afterTask);
+          gradleInvoker.executeTasks(
+              tasks,
+              jvmArguments,
+              args,
+              taskId,
+              new GradleNotificationListener(),
+              instantRunGradleBuildFile,
+              false,
+              true);
+        });
 
     done.waitFor();
     return success.get();
@@ -106,11 +116,14 @@
 
   class GradleNotificationListener extends ExternalSystemTaskNotificationListenerAdapter {
     @Override
-    public void onTaskOutput(@NotNull ExternalSystemTaskId id, @NotNull String text, boolean stdOut) {
+    public void onTaskOutput(
+        @NotNull ExternalSystemTaskId id, @NotNull String text, boolean stdOut) {
       super.onTaskOutput(id, text, stdOut);
       String toPrint = text.trim();
       if (!Strings.isNullOrEmpty(toPrint)) {
-        context.output(new PrintOutput(toPrint, stdOut ? PrintOutput.OutputType.NORMAL : PrintOutput.OutputType.ERROR));
+        context.output(
+            new PrintOutput(
+                toPrint, stdOut ? PrintOutput.OutputType.NORMAL : PrintOutput.OutputType.ERROR));
       }
     }
   }
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunTasksProvider.java b/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunTasksProvider.java
index f528f25..52a82f7 100644
--- a/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunTasksProvider.java
+++ b/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunTasksProvider.java
@@ -17,13 +17,10 @@
 
 import com.android.tools.idea.fd.InstantRunTasksProvider;
 import com.google.common.collect.ImmutableList;
+import java.util.List;
 import org.jetbrains.annotations.NotNull;
 
-import java.util.List;
-
-/**
- * Returns blaze-specific instant run tasks.
- */
+/** Returns blaze-specific instant run tasks. */
 public class BlazeInstantRunTasksProvider implements InstantRunTasksProvider {
   @NotNull
   @Override
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/InstantRunExperiment.java b/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/InstantRunExperiment.java
index ef58a9b..148b1d9 100644
--- a/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/InstantRunExperiment.java
+++ b/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/InstantRunExperiment.java
@@ -15,11 +15,10 @@
  */
 package com.google.idea.blaze.android.run.binary.instantrun;
 
-import com.google.idea.blaze.base.experiments.BoolExperiment;
+import com.google.idea.common.experiments.BoolExperiment;
 
-/**
- * Holds the instant run experiment
- */
+/** Holds the instant run experiment */
 public class InstantRunExperiment {
-  public static final BoolExperiment INSTANT_RUN_ENABLED = new BoolExperiment("instant.run.enabled", false);
+  public static final BoolExperiment INSTANT_RUN_ENABLED =
+      new BoolExperiment("instant.run.enabled", false);
 }
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 f7603b7..205c95c 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
@@ -17,6 +17,7 @@
 
 import com.android.ddmlib.IDevice;
 import com.android.tools.idea.run.ApplicationIdProvider;
+import com.android.tools.idea.run.ConsolePrinter;
 import com.android.tools.idea.run.ConsoleProvider;
 import com.android.tools.idea.run.LaunchOptions;
 import com.android.tools.idea.run.activity.DefaultStartActivityFlagsProvider;
@@ -29,26 +30,28 @@
 import com.android.tools.idea.run.util.ProcessHandlerLaunchStatus;
 import com.google.common.collect.ImmutableList;
 import com.google.common.util.concurrent.Futures;
-import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationCommonState;
 import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryApplicationIdProvider;
 import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryApplicationLaunchTaskProvider;
 import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryConsoleProvider;
 import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryRunConfigurationState;
+import com.google.idea.blaze.android.run.binary.UserIdHelper;
 import com.google.idea.blaze.android.run.deployinfo.BlazeAndroidDeployInfo;
-import com.google.idea.blaze.android.run.runner.*;
+import com.google.idea.blaze.android.run.runner.BlazeAndroidDeviceSelector;
+import com.google.idea.blaze.android.run.runner.BlazeAndroidLaunchTasksProvider;
+import com.google.idea.blaze.android.run.runner.BlazeAndroidRunConfigurationDebuggerManager;
+import com.google.idea.blaze.android.run.runner.BlazeAndroidRunContext;
+import com.google.idea.blaze.android.run.runner.BlazeApkBuildStep;
+import com.google.idea.blaze.base.model.primitives.Label;
 import com.intellij.execution.ExecutionException;
 import com.intellij.execution.configurations.RunConfiguration;
 import com.intellij.execution.runners.ExecutionEnvironment;
 import com.intellij.openapi.project.Project;
+import java.util.Set;
+import javax.annotation.Nullable;
 import org.jetbrains.android.facet.AndroidFacet;
 import org.jetbrains.annotations.NotNull;
 
-import javax.annotation.Nullable;
-import java.util.Set;
-
-/**
- * Run context for android_binary.
- */
+/** Run context for android_binary. */
 public class BlazeAndroidBinaryMobileInstallRunContext implements BlazeAndroidRunContext {
 
   private final Project project;
@@ -60,21 +63,25 @@
   private final ApplicationIdProvider applicationIdProvider;
   private final BlazeApkBuildStepMobileInstall buildStep;
 
-  public BlazeAndroidBinaryMobileInstallRunContext(Project project,
-                                                   AndroidFacet facet,
-                                                   RunConfiguration runConfiguration,
-                                                   ExecutionEnvironment env,
-                                                   BlazeAndroidRunConfigurationCommonState commonState,
-                                                   BlazeAndroidBinaryRunConfigurationState configState,
-                                                   ImmutableList<String> buildFlags) {
+  public BlazeAndroidBinaryMobileInstallRunContext(
+      Project project,
+      AndroidFacet facet,
+      RunConfiguration runConfiguration,
+      ExecutionEnvironment env,
+      BlazeAndroidBinaryRunConfigurationState configState,
+      Label label,
+      ImmutableList<String> buildFlags) {
     this.project = project;
     this.facet = facet;
     this.runConfiguration = runConfiguration;
     this.env = env;
     this.configState = configState;
     this.consoleProvider = new BlazeAndroidBinaryConsoleProvider(project);
-    this.buildStep = new BlazeApkBuildStepMobileInstall(project, env, commonState, buildFlags, configState.isUseSplitApksIfPossible());
-    this.applicationIdProvider = new BlazeAndroidBinaryApplicationIdProvider(project, buildStep.getDeployInfo());
+    this.buildStep =
+        new BlazeApkBuildStepMobileInstall(
+            project, env, label, buildFlags, configState.useSplitApksIfPossible());
+    this.applicationIdProvider =
+        new BlazeAndroidBinaryApplicationIdProvider(project, buildStep.getDeployInfo());
   }
 
   @Override
@@ -83,15 +90,11 @@
   }
 
   @Override
-  public void augmentEnvironment(ExecutionEnvironment env) {
-  }
+  public void augmentEnvironment(ExecutionEnvironment env) {}
 
   @Override
   public void augmentLaunchOptions(@NotNull LaunchOptions.Builder options) {
-    options
-      .setDeploy(false)
-      .setPmInstallOptions(configState.ACTIVITY_EXTRA_FLAGS)
-      .setOpenLogcatAutomatically(true);
+    options.setDeploy(false).setOpenLogcatAutomatically(true);
   }
 
   @NotNull
@@ -112,48 +115,64 @@
 
   @Override
   public LaunchTasksProvider getLaunchTasksProvider(
-    LaunchOptions launchOptions,
-    BlazeAndroidRunConfigurationDebuggerManager debuggerManager) throws ExecutionException {
-    return new BlazeAndroidLaunchTasksProvider(project, this, applicationIdProvider, launchOptions, debuggerManager);
+      LaunchOptions.Builder launchOptionsBuilder,
+      boolean isDebug,
+      BlazeAndroidRunConfigurationDebuggerManager debuggerManager)
+      throws ExecutionException {
+    return new BlazeAndroidLaunchTasksProvider(
+        project, this, applicationIdProvider, launchOptionsBuilder, isDebug, debuggerManager);
   }
 
   @Override
-  public ImmutableList<LaunchTask> getDeployTasks(IDevice device, LaunchOptions launchOptions) throws ExecutionException {
+  public ImmutableList<LaunchTask> getDeployTasks(IDevice device, LaunchOptions launchOptions)
+      throws ExecutionException {
     return ImmutableList.of();
   }
 
   @Override
-  public LaunchTask getApplicationLaunchTask(LaunchOptions launchOptions,
-                                             AndroidDebugger androidDebugger,
-                                             AndroidDebuggerState androidDebuggerState,
-                                             ProcessHandlerLaunchStatus processHandlerLaunchStatus) throws ExecutionException {
-    final StartActivityFlagsProvider startActivityFlagsProvider = new DefaultStartActivityFlagsProvider(
-      androidDebugger,
-      androidDebuggerState,
-      project,
-      launchOptions.isDebug(),
-      configState.ACTIVITY_EXTRA_FLAGS
-    );
+  public LaunchTask getApplicationLaunchTask(
+      LaunchOptions launchOptions,
+      @Nullable Integer userId,
+      AndroidDebugger androidDebugger,
+      AndroidDebuggerState androidDebuggerState,
+      ProcessHandlerLaunchStatus processHandlerLaunchStatus)
+      throws ExecutionException {
+    final StartActivityFlagsProvider startActivityFlagsProvider =
+        new DefaultStartActivityFlagsProvider(
+            androidDebugger,
+            androidDebuggerState,
+            project,
+            launchOptions.isDebug(),
+            UserIdHelper.getFlagsFromUserId(userId));
 
-    BlazeAndroidDeployInfo deployInfo = Futures.get(buildStep.getDeployInfo(), ExecutionException.class);
+    BlazeAndroidDeployInfo deployInfo =
+        Futures.get(buildStep.getDeployInfo(), ExecutionException.class);
 
     return BlazeAndroidBinaryApplicationLaunchTaskProvider.getApplicationLaunchTask(
-      project,
-      applicationIdProvider,
-      deployInfo.getMergedManifestFile(),
-      configState,
-      startActivityFlagsProvider,
-      processHandlerLaunchStatus
-    );
+        project,
+        applicationIdProvider,
+        deployInfo.getMergedManifestFile(),
+        configState,
+        startActivityFlagsProvider,
+        processHandlerLaunchStatus);
   }
 
   @Nullable
   @Override
-  public DebugConnectorTask getDebuggerTask(LaunchOptions launchOptions,
-                                            AndroidDebugger androidDebugger,
-                                            AndroidDebuggerState androidDebuggerState,
-                                            Set<String> packageIds) throws ExecutionException {
+  public DebugConnectorTask getDebuggerTask(
+      AndroidDebugger androidDebugger,
+      AndroidDebuggerState androidDebuggerState,
+      Set<String> packageIds)
+      throws ExecutionException {
     //noinspection unchecked
-    return androidDebugger.getConnectDebuggerTask(env, null, packageIds, facet, androidDebuggerState, runConfiguration.getType().getId());
+    return androidDebugger.getConnectDebuggerTask(
+        env, null, packageIds, facet, androidDebuggerState, runConfiguration.getType().getId());
+  }
+
+  @Nullable
+  @Override
+  public Integer getUserId(IDevice device, ConsolePrinter consolePrinter)
+      throws ExecutionException {
+    return UserIdHelper.getUserIdFromConfigurationState(device, consolePrinter, configState);
   }
 }
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeApkBuildStepMobileInstall.java b/aswb/src/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeApkBuildStepMobileInstall.java
index 9923856..24d49b6 100644
--- a/aswb/src/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeApkBuildStepMobileInstall.java
+++ b/aswb/src/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeApkBuildStepMobileInstall.java
@@ -23,7 +23,6 @@
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.SettableFuture;
 import com.google.common.util.concurrent.UncheckedExecutionException;
-import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationCommonState;
 import com.google.idea.blaze.android.run.deployinfo.BlazeAndroidDeployInfo;
 import com.google.idea.blaze.android.run.deployinfo.BlazeApkDeployInfoProtoHelper;
 import com.google.idea.blaze.android.run.runner.BlazeAndroidDeviceSelector;
@@ -36,132 +35,135 @@
 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.experiments.BoolExperiment;
+import com.google.idea.blaze.base.filecache.FileCaches;
 import com.google.idea.blaze.base.issueparser.IssueOutputLineProcessor;
 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.WorkspaceRoot;
 import com.google.idea.blaze.base.scope.BlazeContext;
 import com.google.idea.blaze.base.scope.ScopedTask;
 import com.google.idea.blaze.base.scope.output.IssueOutput;
-import com.google.idea.blaze.base.scope.output.PrintOutput;
+import com.google.idea.blaze.base.scope.output.StatusOutput;
 import com.google.idea.blaze.base.settings.Blaze;
 import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
 import com.google.idea.blaze.base.util.SaveUtil;
+import com.google.idea.common.experiments.BoolExperiment;
 import com.intellij.execution.ExecutionException;
 import com.intellij.execution.runners.ExecutionEnvironment;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.projectRoots.Sdk;
-import com.intellij.openapi.vfs.LocalFileSystem;
-import org.jetbrains.android.sdk.AndroidSdkUtils;
-import org.jetbrains.annotations.NotNull;
-
-import javax.annotation.Nullable;
 import java.io.File;
 import java.nio.file.Paths;
 import java.util.concurrent.CancellationException;
+import javax.annotation.Nullable;
+import org.jetbrains.android.sdk.AndroidSdkUtils;
+import org.jetbrains.annotations.NotNull;
 
-/**
- * Builds and installs the APK using mobile-install.
- */
+/** Builds and installs the APK using mobile-install. */
 public class BlazeApkBuildStepMobileInstall implements BlazeApkBuildStep {
   private static final BoolExperiment USE_SDK_ADB = new BoolExperiment("use.sdk.adb", true);
 
   private final Project project;
   private final ExecutionEnvironment env;
-  private final BlazeAndroidRunConfigurationCommonState commonState;
+  private final Label label;
   private final ImmutableList<String> buildFlags;
   private final boolean useSplitApksIfPossible;
   private final SettableFuture<BlazeAndroidDeployInfo> deployInfoFuture = SettableFuture.create();
 
-  public BlazeApkBuildStepMobileInstall(Project project,
-                                        ExecutionEnvironment env,
-                                        BlazeAndroidRunConfigurationCommonState commonState,
-                                        ImmutableList<String> buildFlags,
-                                        boolean useSplitApksIfPossible) {
+  public BlazeApkBuildStepMobileInstall(
+      Project project,
+      ExecutionEnvironment env,
+      Label label,
+      ImmutableList<String> buildFlags,
+      boolean useSplitApksIfPossible) {
     this.project = project;
     this.env = env;
-    this.commonState = commonState;
+    this.label = label;
     this.buildFlags = buildFlags;
     this.useSplitApksIfPossible = useSplitApksIfPossible;
   }
 
   @Override
-  public boolean build(BlazeContext context, BlazeAndroidDeviceSelector.DeviceSession deviceSession) {
-    final ScopedTask buildTask = new ScopedTask(context) {
-      @Override
-      protected void execute(@NotNull BlazeContext context) {
-        boolean incrementalInstall = env.getExecutor() instanceof IncrementalInstallExecutor;
+  public boolean build(
+      BlazeContext context, BlazeAndroidDeviceSelector.DeviceSession deviceSession) {
+    final ScopedTask buildTask =
+        new ScopedTask(context) {
+          @Override
+          protected void execute(@NotNull BlazeContext context) {
+            boolean incrementalInstall = env.getExecutor() instanceof IncrementalInstallExecutor;
 
-        DeviceFutures deviceFutures = deviceSession.deviceFutures;
-        assert deviceFutures != null;
-        IDevice device = resolveDevice(context, deviceFutures);
-        if (device == null) {
-          return;
-        }
-        BlazeCommand.Builder command = BlazeCommand.builder(Blaze.getBuildSystem(project), BlazeCommandName.MOBILE_INSTALL);
-        command.addBlazeFlags(BlazeFlags.adbSerialFlags(device.getSerialNumber()));
+            DeviceFutures deviceFutures = deviceSession.deviceFutures;
+            assert deviceFutures != null;
+            IDevice device = resolveDevice(context, deviceFutures);
+            if (device == null) {
+              return;
+            }
+            BlazeCommand.Builder command =
+                BlazeCommand.builder(
+                    Blaze.getBuildSystem(project), BlazeCommandName.MOBILE_INSTALL);
+            command.addBlazeFlags(BlazeFlags.adbSerialFlags(device.getSerialNumber()));
 
-        if (USE_SDK_ADB.getValue()) {
-          File adb = getSdkAdb(project);
-          if (adb != null) {
-            command.addBlazeFlags(ImmutableList.of("--adb", adb.toString()));
+            if (USE_SDK_ADB.getValue()) {
+              File adb = getSdkAdb(project);
+              if (adb != null) {
+                command.addBlazeFlags(ImmutableList.of("--adb", adb.toString()));
+              }
+            }
+
+            // split-apks only supported for API level 23 and above
+            if (useSplitApksIfPossible && device.getVersion().getApiLevel() >= 23) {
+              command.addBlazeFlags(BlazeFlags.SPLIT_APKS);
+            } else if (incrementalInstall) {
+              command.addBlazeFlags(BlazeFlags.INCREMENTAL);
+            }
+            WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProject(project);
+
+            command
+                .addTargets(label)
+                .addBlazeFlags(buildFlags)
+                .addBlazeFlags(BlazeFlags.EXPERIMENTAL_SHOW_ARTIFACTS);
+
+            BlazeApkDeployInfoProtoHelper deployInfoHelper =
+                new BlazeApkDeployInfoProtoHelper(project, buildFlags);
+
+            SaveUtil.saveAllFiles();
+            int retVal =
+                ExternalTask.builder(workspaceRoot)
+                    .addBlazeCommand(command.build())
+                    .context(context)
+                    .stderr(
+                        LineProcessingOutputStream.of(
+                            deployInfoHelper.getLineProcessor(),
+                            new IssueOutputLineProcessor(project, context, workspaceRoot)))
+                    .build()
+                    .run();
+            FileCaches.refresh(project);
+
+            if (retVal != 0) {
+              context.setHasError();
+              return;
+            }
+
+            BlazeAndroidDeployInfo deployInfo = deployInfoHelper.readDeployInfo(context);
+            if (deployInfo == null) {
+              IssueOutput.error("Could not read apk deploy info from build").submit(context);
+              return;
+            }
+            deployInfoFuture.set(deployInfo);
           }
-        }
+        };
 
-        // split-apks only supported for API level 23 and above
-        if (useSplitApksIfPossible && device.getVersion().getApiLevel() >= 23) {
-          command.addBlazeFlags(BlazeFlags.SPLIT_APKS);
-        }
-        else if (incrementalInstall) {
-          command.addBlazeFlags(BlazeFlags.INCREMENTAL);
-        }
-        WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProject(project);
-
-        command
-          .addTargets(commonState.getTarget())
-          .addBlazeFlags(buildFlags)
-          .addBlazeFlags(BlazeFlags.EXPERIMENTAL_SHOW_ARTIFACTS)
-        ;
-
-        BlazeApkDeployInfoProtoHelper deployInfoHelper = new BlazeApkDeployInfoProtoHelper(project, buildFlags);
-
-        SaveUtil.saveAllFiles();
-        int retVal = ExternalTask.builder(workspaceRoot, command.build())
-          .context(context)
-          .stderr(LineProcessingOutputStream.of(
-            deployInfoHelper.getLineProcessor(),
-            new IssueOutputLineProcessor(project, context, workspaceRoot)))
-          .build()
-          .run();
-        LocalFileSystem.getInstance().refresh(true);
-
-        if (retVal != 0) {
-          context.setHasError();
-          return;
-        }
-
-        BlazeAndroidDeployInfo deployInfo = deployInfoHelper.readDeployInfo(context);
-        if (deployInfo == null) {
-          IssueOutput.error("Could not read apk deploy info from build").submit(context);
-          return;
-        }
-        deployInfoFuture.set(deployInfo);
-      }
-    };
-
-    ListenableFuture<Void> buildFuture = BlazeExecutor.submitTask(
-      project,
-      String.format("Executing %s apk build", Blaze.buildSystemName(project)),
-      buildTask
-    );
+    ListenableFuture<Void> buildFuture =
+        BlazeExecutor.submitTask(
+            project,
+            String.format("Executing %s apk build", Blaze.buildSystemName(project)),
+            buildTask);
 
     try {
       Futures.get(buildFuture, ExecutionException.class);
-    }
-    catch (ExecutionException e) {
+    } catch (ExecutionException e) {
       context.setHasError();
-    }
-    catch (CancellationException e) {
+    } catch (CancellationException e) {
       context.setCancelled();
     }
     return context.shouldContinue();
@@ -172,7 +174,8 @@
   }
 
   private static File getSdkAdb(Project project) {
-    BlazeProjectData projectData = BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
+    BlazeProjectData projectData =
+        BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
     if (projectData == null) {
       return null;
     }
@@ -200,23 +203,17 @@
   }
 
   @Nullable
-  private static IDevice resolveDevice(@NotNull BlazeContext context, @NotNull DeviceFutures deviceFutures) {
+  private static IDevice resolveDevice(
+      @NotNull BlazeContext context, @NotNull DeviceFutures deviceFutures) {
     if (deviceFutures.get().size() != 1) {
-      IssueOutput
-        .error("Only one device can be used with mobile-install.")
-        .submit(context);
+      IssueOutput.error("Only one device can be used with mobile-install.").submit(context);
       return null;
     }
-    context.output(new PrintOutput("Waiting for mobile-install device target..."));
+    context.output(new StatusOutput("Waiting for mobile-install device target..."));
     try {
-      return Futures.get(
-        Iterables.getOnlyElement(deviceFutures.get()),
-        ExecutionException.class
-      );
-    } catch (ExecutionException|UncheckedExecutionException e) {
-      IssueOutput
-        .error("Could not get device: " + e.getMessage())
-        .submit(context);
+      return Futures.get(Iterables.getOnlyElement(deviceFutures.get()), ExecutionException.class);
+    } catch (ExecutionException | UncheckedExecutionException e) {
+      IssueOutput.error("Could not get device: " + e.getMessage()).submit(context);
       return null;
     } catch (CancellationException e) {
       // The user cancelled the device launch.
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/mobileinstall/IncrementalInstallDebugExecutor.java b/aswb/src/com/google/idea/blaze/android/run/binary/mobileinstall/IncrementalInstallDebugExecutor.java
index c20f13c..88accc8 100644
--- a/aswb/src/com/google/idea/blaze/android/run/binary/mobileinstall/IncrementalInstallDebugExecutor.java
+++ b/aswb/src/com/google/idea/blaze/android/run/binary/mobileinstall/IncrementalInstallDebugExecutor.java
@@ -18,17 +18,16 @@
 import com.google.idea.blaze.base.settings.Blaze;
 import com.intellij.execution.executors.DefaultDebugExecutor;
 import icons.BlazeAndroidIcons;
+import javax.swing.Icon;
 import org.jetbrains.annotations.NotNull;
 
-import javax.swing.*;
-
 /**
  * Executor for running blaze mobile-install --incremental and then launching the current run
  * configuration in debug mode. This executor adds a launch button that is only enabled for
  * mobile-install run configurations.
  */
-public class IncrementalInstallDebugExecutor
-  extends DefaultDebugExecutor implements IncrementalInstallExecutor {
+public class IncrementalInstallDebugExecutor extends DefaultDebugExecutor
+    implements IncrementalInstallExecutor {
   public static final String EXECUTOR_ID = "blaze.incremental.install.debug";
 
   @NotNull
@@ -56,7 +55,8 @@
 
   @Override
   public String getDescription() {
-    return Blaze.guessBuildSystemName().toLowerCase() + " mobile-install --incremental, launch with debugger";
+    return Blaze.guessBuildSystemName().toLowerCase()
+        + " mobile-install --incremental, launch with debugger";
   }
 
   @NotNull
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/mobileinstall/IncrementalInstallExecutor.java b/aswb/src/com/google/idea/blaze/android/run/binary/mobileinstall/IncrementalInstallExecutor.java
index e8bba2e..c33463c 100644
--- a/aswb/src/com/google/idea/blaze/android/run/binary/mobileinstall/IncrementalInstallExecutor.java
+++ b/aswb/src/com/google/idea/blaze/android/run/binary/mobileinstall/IncrementalInstallExecutor.java
@@ -16,8 +16,6 @@
 package com.google.idea.blaze.android.run.binary.mobileinstall;
 
 /**
- * A marker interface for executors that specify --incremental should be used
- * with mobile-install.
+ * A marker interface for executors that specify --incremental should be used with mobile-install.
  */
-public interface IncrementalInstallExecutor {
-}
+public interface IncrementalInstallExecutor {}
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/mobileinstall/IncrementalInstallRunExecutor.java b/aswb/src/com/google/idea/blaze/android/run/binary/mobileinstall/IncrementalInstallRunExecutor.java
index 3e98d87..5c21041 100644
--- a/aswb/src/com/google/idea/blaze/android/run/binary/mobileinstall/IncrementalInstallRunExecutor.java
+++ b/aswb/src/com/google/idea/blaze/android/run/binary/mobileinstall/IncrementalInstallRunExecutor.java
@@ -18,17 +18,16 @@
 import com.google.idea.blaze.base.settings.Blaze;
 import com.intellij.execution.executors.DefaultRunExecutor;
 import icons.BlazeAndroidIcons;
+import javax.swing.Icon;
 import org.jetbrains.annotations.NotNull;
 
-import javax.swing.*;
-
 /**
  * Executor for running blaze mobile-install --incremental and then launching the current run
  * configuration. This executor adds a launch button that is only enabled for mobile-install run
  * configurations.
  */
-public class IncrementalInstallRunExecutor
-  extends DefaultRunExecutor implements IncrementalInstallExecutor {
+public class IncrementalInstallRunExecutor extends DefaultRunExecutor
+    implements IncrementalInstallExecutor {
   public static final String EXECUTOR_ID = "blaze.incremental.install.run";
 
   @NotNull
diff --git a/aswb/src/com/google/idea/blaze/android/run/deployinfo/BlazeAndroidDeployInfo.java b/aswb/src/com/google/idea/blaze/android/run/deployinfo/BlazeAndroidDeployInfo.java
index 1e3c0bd..f3ca382 100644
--- a/aswb/src/com/google/idea/blaze/android/run/deployinfo/BlazeAndroidDeployInfo.java
+++ b/aswb/src/com/google/idea/blaze/android/run/deployinfo/BlazeAndroidDeployInfo.java
@@ -19,25 +19,23 @@
 import com.google.idea.blaze.android.manifest.ManifestParser;
 import com.google.repackaged.devtools.build.lib.rules.android.deployinfo.AndroidDeployInfoOuterClass;
 import com.intellij.openapi.project.Project;
-import org.jetbrains.android.dom.manifest.Manifest;
-
-import javax.annotation.Nullable;
 import java.io.File;
 import java.util.List;
 import java.util.Objects;
 import java.util.stream.Collectors;
+import javax.annotation.Nullable;
+import org.jetbrains.android.dom.manifest.Manifest;
 
-/**
- * Info about the android_binary/android_test to deploy.
- */
+/** Info about the android_binary/android_test to deploy. */
 public class BlazeAndroidDeployInfo {
   private final Project project;
   private final File executionRoot;
   private final AndroidDeployInfoOuterClass.AndroidDeployInfo deployInfo;
 
-  public BlazeAndroidDeployInfo(Project project,
-                                File executionRoot,
-                                AndroidDeployInfoOuterClass.AndroidDeployInfo deployInfo) {
+  public BlazeAndroidDeployInfo(
+      Project project,
+      File executionRoot,
+      AndroidDeployInfoOuterClass.AndroidDeployInfo deployInfo) {
     this.project = project;
     this.executionRoot = executionRoot;
     this.deployInfo = deployInfo;
@@ -55,16 +53,19 @@
   }
 
   public List<File> getAdditionalMergedManifestFiles() {
-    return deployInfo.getAdditionalMergedManifestsList().stream()
-      .map(artifact -> new File(executionRoot, artifact.getExecRootPath()))
-      .collect(Collectors.toList());
+    return deployInfo
+        .getAdditionalMergedManifestsList()
+        .stream()
+        .map(artifact -> new File(executionRoot, artifact.getExecRootPath()))
+        .collect(Collectors.toList());
   }
 
   public List<Manifest> getAdditionalMergedManifests() {
-    return getAdditionalMergedManifestFiles().stream()
-      .map(file -> ManifestParser.getInstance(project).getManifest(file))
-      .filter(Objects::nonNull)
-      .collect(Collectors.toList());
+    return getAdditionalMergedManifestFiles()
+        .stream()
+        .map(file -> ManifestParser.getInstance(project).getManifest(file))
+        .filter(Objects::nonNull)
+        .collect(Collectors.toList());
   }
 
   public List<File> getManifestFiles() {
@@ -74,12 +75,12 @@
     return result;
   }
 
-  /**
-   * Returns the full list of apks to deploy, if any.
-   */
+  /** Returns the full list of apks to deploy, if any. */
   public List<File> getApksToDeploy() {
-    return deployInfo.getApksToDeployList().stream()
-      .map(artifact -> new File(executionRoot, artifact.getExecRootPath()))
-      .collect(Collectors.toList());
+    return deployInfo
+        .getApksToDeployList()
+        .stream()
+        .map(artifact -> new File(executionRoot, artifact.getExecRootPath()))
+        .collect(Collectors.toList());
   }
 }
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 9628565..477e1f2 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
@@ -29,18 +29,15 @@
 import com.google.repackaged.devtools.build.lib.rules.android.deployinfo.AndroidDeployInfoOuterClass;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.project.Project;
-
-import javax.annotation.Nullable;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
+import javax.annotation.Nullable;
 
-/**
- * Reads the deploy info from a build step.
- */
+/** Reads the deploy info from a build step. */
 public class BlazeApkDeployInfoProtoHelper {
   private static final Logger LOG = Logger.getInstance(BlazeApkDeployInfoProtoHelper.class);
 
@@ -49,7 +46,7 @@
   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, ".deployinfo.pb");
 
   public BlazeApkDeployInfoProtoHelper(Project project, ImmutableList<String> buildFlags) {
     this.project = project;
@@ -78,7 +75,8 @@
     if (executionRoot == null) {
       return null;
     }
-    BlazeAndroidDeployInfo androidDeployInfo = new BlazeAndroidDeployInfo(project, new File(executionRoot), deployInfo);
+    BlazeAndroidDeployInfo androidDeployInfo =
+        new BlazeAndroidDeployInfo(project, new File(executionRoot), deployInfo);
 
     List<File> manifestFiles = androidDeployInfo.getManifestFiles();
     ManifestParser.getInstance(project).refreshManifests(manifestFiles);
@@ -88,19 +86,19 @@
 
   @Nullable
   private String getExecutionRoot(BlazeContext context) {
-    ListenableFuture<String> execRootFuture = BlazeInfo.getInstance().runBlazeInfo(
-      context, Blaze.getBuildSystem(project),
-      workspaceRoot,
-      buildFlags,
-      BlazeInfo.EXECUTION_ROOT_KEY
-    );
+    ListenableFuture<String> execRootFuture =
+        BlazeInfo.getInstance()
+            .runBlazeInfo(
+                context,
+                Blaze.getBuildSystem(project),
+                workspaceRoot,
+                buildFlags,
+                BlazeInfo.EXECUTION_ROOT_KEY);
     try {
       return execRootFuture.get();
-    }
-    catch (InterruptedException e) {
+    } catch (InterruptedException e) {
       context.setCancelled();
-    }
-    catch (ExecutionException e) {
+    } catch (ExecutionException e) {
       LOG.error(e);
       context.setHasError();
     }
diff --git a/aswb/src/com/google/idea/blaze/android/run/deployinfo/BlazeApkProvider.java b/aswb/src/com/google/idea/blaze/android/run/deployinfo/BlazeApkProvider.java
index 5bf27c0..bdcc5d6 100644
--- a/aswb/src/com/google/idea/blaze/android/run/deployinfo/BlazeApkProvider.java
+++ b/aswb/src/com/google/idea/blaze/android/run/deployinfo/BlazeApkProvider.java
@@ -25,21 +25,18 @@
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.idea.blaze.android.run.runner.AaptUtil;
 import com.intellij.openapi.project.Project;
-import org.jetbrains.annotations.NotNull;
-
 import java.io.File;
 import java.util.Collection;
 import java.util.List;
+import org.jetbrains.annotations.NotNull;
 
-/**
- * Apk provider from deploy info proto
- */
+/** Apk provider from deploy info proto */
 public class BlazeApkProvider implements ApkProvider {
   private final Project project;
   private final ListenableFuture<BlazeAndroidDeployInfo> deployInfoFuture;
 
-  public BlazeApkProvider(Project project,
-                          ListenableFuture<BlazeAndroidDeployInfo> deployInfoFuture) {
+  public BlazeApkProvider(
+      Project project, ListenableFuture<BlazeAndroidDeployInfo> deployInfoFuture) {
     this.project = project;
     this.deployInfoFuture = deployInfoFuture;
   }
@@ -49,7 +46,7 @@
   public Collection<ApkInfo> getApks(@NotNull IDevice device) throws ApkProvisionException {
     BlazeAndroidDeployInfo deployInfo = Futures.get(deployInfoFuture, ApkProvisionException.class);
     ImmutableList.Builder<ApkInfo> apkInfos = ImmutableList.builder();
-    for (File apk : deployInfo.getApksToDeploy())  {
+    for (File apk : deployInfo.getApksToDeploy()) {
       apkInfos.add(new ApkInfo(apk, manifestPackageForApk(apk)));
     }
     return apkInfos.build();
@@ -59,12 +56,13 @@
   private String manifestPackageForApk(@NotNull final File apk) throws ApkProvisionException {
     try {
       return AaptUtil.getApkManifestPackage(project, apk);
-    }
-    catch (AaptUtil.AaptUtilException e) {
+    } catch (AaptUtil.AaptUtilException e) {
       throw new ApkProvisionException(
-        "Could not determine manifest package for apk: " + apk.getPath()
-        + "\nbecause: " + e.getMessage(),
-        e);
+          "Could not determine manifest package for apk: "
+              + apk.getPath()
+              + "\nbecause: "
+              + e.getMessage(),
+          e);
     }
   }
 
diff --git a/aswb/src/com/google/idea/blaze/android/run/runner/AaptUtil.java b/aswb/src/com/google/idea/blaze/android/run/runner/AaptUtil.java
index 9fc61a7..9ad2aa0 100644
--- a/aswb/src/com/google/idea/blaze/android/run/runner/AaptUtil.java
+++ b/aswb/src/com/google/idea/blaze/android/run/runner/AaptUtil.java
@@ -22,9 +22,6 @@
 import com.intellij.execution.configurations.GeneralCommandLine;
 import com.intellij.execution.process.OSProcessHandler;
 import com.intellij.openapi.project.Project;
-import org.jetbrains.android.sdk.AndroidPlatform;
-
-import javax.annotation.Nullable;
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.IOException;
@@ -32,17 +29,18 @@
 import java.util.regex.MatchResult;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import javax.annotation.Nullable;
+import org.jetbrains.android.sdk.AndroidPlatform;
 
-/**
- * A collection of utilities for extracting information from APKs using aapt.
- */
+/** A collection of utilities for extracting information from APKs using aapt. */
 public final class AaptUtil {
 
   private static final Pattern DEBUGGABLE_PATTERN = Pattern.compile("^application-debuggable$");
   private static final Pattern PACKAGE_PATTERN = Pattern.compile("^package: .*name='([\\w\\.]+)'");
   private static final Pattern LAUNCHABLE_PATTERN =
-    Pattern.compile("^launchable-activity: .*name='([\\w\\.]+)'");
+      Pattern.compile("^launchable-activity: .*name='([\\w\\.]+)'");
 
+  /** exception thrown by this class */
   public static class AaptUtilException extends Exception {
     public AaptUtilException(String message) {
       super(message);
@@ -53,46 +51,32 @@
     }
   }
 
-  private AaptUtil() {
-  }
+  private AaptUtil() {}
 
   /**
    * Determines whether the given APK is debuggable. Trying to debug a non-debuggable APK on a
    * release-keys device will fail.
    */
-  public static boolean isApkDebuggable(
-    Project project,
-    File apk
-  ) throws AaptUtilException {
+  public static boolean isApkDebuggable(Project project, File apk) throws AaptUtilException {
     return getAaptBadging(project, apk, DEBUGGABLE_PATTERN) != null;
   }
 
-  /**
-   * Determines the manifest package name for the given APK.
-   */
-  public static String getApkManifestPackage(
-    Project project,
-    File apk
-  ) throws AaptUtilException {
+  /** Determines the manifest package name for the given APK. */
+  public static String getApkManifestPackage(Project project, File apk) throws AaptUtilException {
     MatchResult packageResult = getAaptBadging(project, apk, PACKAGE_PATTERN);
     if (packageResult == null) {
       throw new AaptUtilException(
-        "No match found in `aapt dump badging` for package manifest pattern.");
+          "No match found in `aapt dump badging` for package manifest pattern.");
     }
     return packageResult.group(1);
   }
 
-  /**
-   * Determines the default launchable activity for the given apk.
-   */
-  public static String getLaunchableActivity(
-    Project project,
-    File apk
-  ) throws AaptUtilException {
+  /** Determines the default launchable activity for the given apk. */
+  public static String getLaunchableActivity(Project project, File apk) throws AaptUtilException {
     MatchResult activityResult = getAaptBadging(project, apk, LAUNCHABLE_PATTERN);
     if (activityResult == null) {
       throw new AaptUtilException(
-        "No match found in `aapt dump badging` for launchable activity pattern.");
+          "No match found in `aapt dump badging` for launchable activity pattern.");
     }
     return activityResult.group(1);
   }
@@ -102,41 +86,34 @@
    * output matching the given pattern.
    */
   @Nullable
-  private static MatchResult getAaptBadging(
-    Project project,
-    File apk,
-    Pattern pattern
-  ) throws AaptUtilException {
+  private static MatchResult getAaptBadging(Project project, File apk, Pattern pattern)
+      throws AaptUtilException {
     if (!apk.exists()) {
       throw new AaptUtilException("apk file does not exist: " + apk);
     }
     AndroidPlatform androidPlatform = SdkUtil.getAndroidPlatform(project);
     if (androidPlatform == null) {
       throw new AaptUtilException(
-        "Could not find Android platform sdk for project " + project.getName());
+          "Could not find Android platform sdk for project " + project.getName());
     }
     BuildToolInfo toolInfo = androidPlatform.getSdkData().getLatestBuildTool();
     if (toolInfo == null) {
       throw new AaptUtilException(
-        "Could not find Android sdk build-tools for project " + project.getName());
+          "Could not find Android sdk build-tools for project " + project.getName());
     }
     String aapt = toolInfo.getPath(PathId.AAPT);
-    GeneralCommandLine commandLine = new GeneralCommandLine(
-      aapt,
-      "dump",
-      "badging",
-      apk.getAbsolutePath());
+    GeneralCommandLine commandLine =
+        new GeneralCommandLine(aapt, "dump", "badging", apk.getAbsolutePath());
     OSProcessHandler handler;
     try {
       handler = new OSProcessHandler(commandLine);
-    }
-    catch (ExecutionException e) {
+    } catch (ExecutionException e) {
       throw new AaptUtilException("Could not execute aapt to extract apk information.", e);
     }
 
     // The wrapped stream is closed by the process handler.
-    BufferedReader reader = new BufferedReader(
-      new InputStreamReader(handler.getProcess().getInputStream()));
+    BufferedReader reader =
+        new BufferedReader(new InputStreamReader(handler.getProcess().getInputStream()));
     try {
       String line;
       while ((line = reader.readLine()) != null) {
@@ -145,8 +122,7 @@
           return matcher.toMatchResult();
         }
       }
-    }
-    catch (IOException e) {
+    } catch (IOException e) {
       throw new AaptUtilException("Could not read aapt output.", e);
     }
     return null;
diff --git a/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidDeviceSelector.java b/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidDeviceSelector.java
index bc6737a..a1ef71c 100644
--- a/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidDeviceSelector.java
+++ b/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidDeviceSelector.java
@@ -15,6 +15,8 @@
  */
 package com.google.idea.blaze.android.run.runner;
 
+import static org.jetbrains.android.actions.RunAndroidAvdManagerAction.getName;
+
 import com.android.tools.idea.run.AndroidSessionInfo;
 import com.android.tools.idea.run.DeviceFutures;
 import com.android.tools.idea.run.editor.DeployTarget;
@@ -27,43 +29,44 @@
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.ui.DialogWrapper;
 import com.intellij.openapi.ui.Messages;
+import javax.annotation.Nullable;
 import org.jetbrains.android.facet.AndroidFacet;
 
-import javax.annotation.Nullable;
-
-import static org.jetbrains.android.actions.RunAndroidAvdManagerAction.getName;
-
-/**
- * Selects a device.
- */
+/** Selects a device. */
 public interface BlazeAndroidDeviceSelector {
 
+  /** A device session */
   class DeviceSession {
     @Nullable public final DeployTarget deployTarget;
     @Nullable public final DeviceFutures deviceFutures;
     @Nullable public final AndroidSessionInfo sessionInfo;
 
-    public DeviceSession(@Nullable DeployTarget deployTarget,
-                         @Nullable DeviceFutures deviceFutures,
-                         @Nullable AndroidSessionInfo sessionInfo) {
+    public DeviceSession(
+        @Nullable DeployTarget deployTarget,
+        @Nullable DeviceFutures deviceFutures,
+        @Nullable AndroidSessionInfo sessionInfo) {
       this.deployTarget = deployTarget;
       this.deviceFutures = deviceFutures;
       this.sessionInfo = sessionInfo;
     }
   }
 
-  DeviceSession getDevice(Project project,
-                          AndroidFacet facet,
-                          BlazeAndroidRunConfigurationDeployTargetManager deployTargetManager,
-                          Executor executor,
-                          ExecutionEnvironment env,
-                          AndroidSessionInfo info,
-                          boolean debug,
-                          int runConfigId) throws ExecutionException;
+  DeviceSession getDevice(
+      Project project,
+      AndroidFacet facet,
+      BlazeAndroidRunConfigurationDeployTargetManager deployTargetManager,
+      Executor executor,
+      ExecutionEnvironment env,
+      AndroidSessionInfo info,
+      boolean debug,
+      int runConfigId)
+      throws ExecutionException;
 
+  /** Standard device selector */
   class NormalDeviceSelector implements BlazeAndroidDeviceSelector {
 
-    private static final DialogWrapper.DoNotAskOption ourKillLaunchOption = new KillLaunchDialogOption();
+    private static final DialogWrapper.DoNotAskOption ourKillLaunchOption =
+        new KillLaunchDialogOption();
     private static final Logger LOG = Logger.getInstance(NormalDeviceSelector.class);
 
     static class KillLaunchDialogOption implements DialogWrapper.DoNotAskOption {
@@ -96,14 +99,16 @@
     }
 
     @Override
-    public DeviceSession getDevice(Project project,
-                                   AndroidFacet facet,
-                                   BlazeAndroidRunConfigurationDeployTargetManager deployTargetManager,
-                                   Executor executor,
-                                   ExecutionEnvironment env,
-                                   AndroidSessionInfo info,
-                                   boolean debug,
-                                   int runConfigId) throws ExecutionException {
+    public DeviceSession getDevice(
+        Project project,
+        AndroidFacet facet,
+        BlazeAndroidRunConfigurationDeployTargetManager deployTargetManager,
+        Executor executor,
+        ExecutionEnvironment env,
+        AndroidSessionInfo info,
+        boolean debug,
+        int runConfigId)
+        throws ExecutionException {
       // If there is an existing session, then terminate those sessions
       if (info != null) {
         boolean continueLaunch = promptAndKillSession(executor, project, info);
@@ -120,36 +125,45 @@
       DeviceFutures deviceFutures = null;
       DeployTargetState deployTargetState = deployTargetManager.getCurrentDeployTargetState();
       if (!deployTarget.hasCustomRunProfileState(executor)) {
-        deviceFutures = deployTarget.getDevices(
-          deployTargetState,
-          facet,
-          deployTargetManager.getDeviceCount(),
-          debug,
-          runConfigId
-        );
+        deviceFutures =
+            deployTarget.getDevices(
+                deployTargetState, facet, deployTargetManager.getDeviceCount(), debug, runConfigId);
       }
       return new DeviceSession(deployTarget, deviceFutures, info);
     }
 
-    private boolean promptAndKillSession(Executor executor, Project project, AndroidSessionInfo info) {
+    private boolean promptAndKillSession(
+        Executor executor, Project project, AndroidSessionInfo info) {
       String previousExecutor = info.getExecutorId();
       String currentExecutor = executor.getId();
 
       if (ourKillLaunchOption.isToBeShown()) {
         String msg, noText;
         if (previousExecutor.equals(currentExecutor)) {
-          msg = String.format("Restart App?\nThe app is already running. Would you like to kill it and restart the session?");
+          msg =
+              String.format(
+                  "Restart App?\nThe app is already running. "
+                      + "Would you like to kill it and restart the session?");
           noText = "Cancel";
-        }
-        else {
-          msg = String.format("To switch from %1$s to %2$s, the app has to restart. Continue?", previousExecutor, currentExecutor);
+        } else {
+          msg =
+              String.format(
+                  "To switch from %1$s to %2$s, the app has to restart. Continue?",
+                  previousExecutor, currentExecutor);
           noText = "Cancel " + currentExecutor;
         }
 
         String title = "Launching " + getName();
         String yesText = "Restart " + getName();
-        if (Messages.NO ==
-            Messages.showYesNoDialog(project, msg, title, yesText, noText, AllIcons.General.QuestionDialog, ourKillLaunchOption)) {
+        if (Messages.NO
+            == Messages.showYesNoDialog(
+                project,
+                msg,
+                title,
+                yesText,
+                noText,
+                AllIcons.General.QuestionDialog,
+                ourKillLaunchOption)) {
           return false;
         }
       }
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 79b21cd..2916688 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
@@ -17,55 +17,73 @@
 
 import com.android.ddmlib.IDevice;
 import com.android.sdklib.AndroidVersion;
-import com.android.tools.idea.run.*;
+import com.android.tools.idea.run.AndroidLaunchTasksProvider;
+import com.android.tools.idea.run.ApkProvisionException;
+import com.android.tools.idea.run.ApplicationIdProvider;
+import com.android.tools.idea.run.ConsolePrinter;
+import com.android.tools.idea.run.LaunchOptions;
 import com.android.tools.idea.run.editor.AndroidDebugger;
 import com.android.tools.idea.run.editor.AndroidDebuggerState;
-import com.android.tools.idea.run.tasks.*;
+import com.android.tools.idea.run.tasks.ClearLogcatTask;
+import com.android.tools.idea.run.tasks.DebugConnectorTask;
+import com.android.tools.idea.run.tasks.DismissKeyguardTask;
+import com.android.tools.idea.run.tasks.LaunchTask;
+import com.android.tools.idea.run.tasks.LaunchTasksProvider;
+import com.android.tools.idea.run.tasks.ShowLogcatTask;
 import com.android.tools.idea.run.util.LaunchStatus;
 import com.android.tools.idea.run.util.ProcessHandlerLaunchStatus;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
+import com.google.idea.blaze.android.run.binary.UserIdHelper;
 import com.intellij.execution.ExecutionException;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.project.Project;
+import java.util.List;
+import java.util.Set;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import java.util.List;
-import java.util.Set;
-
-/**
- * Normal launch tasks provider.
- */
+/** Normal launch tasks provider. */
 public class BlazeAndroidLaunchTasksProvider implements LaunchTasksProvider {
   private static final Logger LOG = Logger.getInstance(BlazeAndroidLaunchTasksProvider.class);
 
   private final Project project;
   private final BlazeAndroidRunContext runContext;
   private final ApplicationIdProvider applicationIdProvider;
-  private final LaunchOptions launchOptions;
+  private final LaunchOptions.Builder launchOptionsBuilder;
+  private final boolean isDebug;
   private final BlazeAndroidRunConfigurationDebuggerManager debuggerManager;
 
-  public BlazeAndroidLaunchTasksProvider(Project project,
-                                         BlazeAndroidRunContext runContext,
-                                         ApplicationIdProvider applicationIdProvider,
-                                         LaunchOptions launchOptions,
-                                         BlazeAndroidRunConfigurationDebuggerManager debuggerManager) {
+  public BlazeAndroidLaunchTasksProvider(
+      Project project,
+      BlazeAndroidRunContext runContext,
+      ApplicationIdProvider applicationIdProvider,
+      LaunchOptions.Builder launchOptionsBuilder,
+      boolean isDebug,
+      BlazeAndroidRunConfigurationDebuggerManager debuggerManager) {
     this.project = project;
     this.runContext = runContext;
     this.applicationIdProvider = applicationIdProvider;
-    this.launchOptions = launchOptions;
+    this.launchOptionsBuilder = launchOptionsBuilder;
+    this.isDebug = isDebug;
     this.debuggerManager = debuggerManager;
   }
 
   @NotNull
   @Override
-  public List<LaunchTask> getTasks(@NotNull IDevice device,
-                                   @NotNull LaunchStatus launchStatus,
-                                   @NotNull ConsolePrinter consolePrinter) {
+  public List<LaunchTask> getTasks(
+      @NotNull IDevice device,
+      @NotNull LaunchStatus launchStatus,
+      @NotNull ConsolePrinter consolePrinter)
+      throws ExecutionException {
     final List<LaunchTask> launchTasks = Lists.newArrayList();
 
+    Integer userId = runContext.getUserId(device, consolePrinter);
+    launchOptionsBuilder.setPmInstallOptions(UserIdHelper.getFlagsFromUserId(userId));
+
+    LaunchOptions launchOptions = launchOptionsBuilder.build();
+
     if (launchOptions.isClearLogcatBeforeStart()) {
       launchTasks.add(new ClearLogcatTask(project));
     }
@@ -73,14 +91,8 @@
     launchTasks.add(new DismissKeyguardTask());
 
     if (launchOptions.isDeploy()) {
-      try {
-        ImmutableList<LaunchTask> deployTasks = runContext.getDeployTasks(device, launchOptions);
-        launchTasks.addAll(deployTasks);
-      }
-      catch (ExecutionException e) {
-        launchStatus.terminateLaunch(e.getMessage());
-        return ImmutableList.of();
-      }
+      ImmutableList<LaunchTask> deployTasks = runContext.getDeployTasks(device, launchOptions);
+      launchTasks.addAll(deployTasks);
     }
     if (launchStatus.isLaunchTerminated()) {
       return launchTasks;
@@ -90,23 +102,23 @@
     try {
       packageName = applicationIdProvider.getPackageName();
 
-      ProcessHandlerLaunchStatus processHandlerLaunchStatus = (ProcessHandlerLaunchStatus)launchStatus;
-      LaunchTask appLaunchTask = runContext.getApplicationLaunchTask(
-        launchOptions,
-        debuggerManager.getAndroidDebugger(),
-        debuggerManager.getAndroidDebuggerState(),
-        processHandlerLaunchStatus
-      );
+      ProcessHandlerLaunchStatus processHandlerLaunchStatus =
+          (ProcessHandlerLaunchStatus) launchStatus;
+      LaunchTask appLaunchTask =
+          runContext.getApplicationLaunchTask(
+              launchOptions,
+              userId,
+              debuggerManager.getAndroidDebugger(),
+              debuggerManager.getAndroidDebuggerState(),
+              processHandlerLaunchStatus);
       if (appLaunchTask != null) {
         launchTasks.add(appLaunchTask);
       }
-    }
-    catch (ApkProvisionException e) {
+    } catch (ApkProvisionException e) {
       LOG.error(e);
       launchStatus.terminateLaunch("Unable to determine application id: " + e);
       return ImmutableList.of();
-    }
-    catch (ExecutionException e) {
+    } catch (ExecutionException e) {
       launchStatus.terminateLaunch(e.getMessage());
       return ImmutableList.of();
     }
@@ -120,17 +132,16 @@
 
   @Nullable
   @Override
-  public DebugConnectorTask getConnectDebuggerTask(@NotNull LaunchStatus launchStatus,
-                                                   @Nullable AndroidVersion version) {
-    if (!launchOptions.isDebug()) {
+  public DebugConnectorTask getConnectDebuggerTask(
+      @NotNull LaunchStatus launchStatus, @Nullable AndroidVersion version) {
+    if (!isDebug) {
       return null;
     }
     Set<String> packageIds = Sets.newHashSet();
     try {
       String packageName = applicationIdProvider.getPackageName();
       packageIds.add(packageName);
-    }
-    catch (ApkProvisionException e) {
+    } catch (ApkProvisionException e) {
       Logger.getInstance(AndroidLaunchTasksProvider.class).error(e);
     }
 
@@ -139,11 +150,12 @@
       if (packageName != null) {
         packageIds.add(packageName);
       }
-    }
-    catch (ApkProvisionException e) {
+    } catch (ApkProvisionException e) {
       // not as severe as failing to obtain package id for main application
       Logger.getInstance(AndroidLaunchTasksProvider.class)
-        .warn("Unable to obtain test package name, will not connect debugger if tests don't instantiate main application");
+          .warn(
+              "Unable to obtain test package name, will not connect debugger "
+                  + "if tests don't instantiate main application");
     }
 
     AndroidDebugger androidDebugger = debuggerManager.getAndroidDebugger();
@@ -154,14 +166,8 @@
     }
 
     try {
-      return runContext.getDebuggerTask(
-        launchOptions,
-        androidDebugger,
-        androidDebuggerState,
-        packageIds
-      );
-    }
-    catch (ExecutionException e) {
+      return runContext.getDebuggerTask(androidDebugger, androidDebuggerState, packageIds);
+    } catch (ExecutionException e) {
       launchStatus.terminateLaunch(e.getMessage());
       return null;
     }
diff --git a/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidRunConfigurationDebuggerManager.java b/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidRunConfigurationDebuggerManager.java
index 3d174c9..d51f14a 100644
--- a/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidRunConfigurationDebuggerManager.java
+++ b/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidRunConfigurationDebuggerManager.java
@@ -29,33 +29,31 @@
 import com.intellij.openapi.util.InvalidDataException;
 import com.intellij.openapi.util.JDOMExternalizable;
 import com.intellij.openapi.util.WriteExternalException;
-import org.jdom.Element;
-import org.jetbrains.android.facet.AndroidFacet;
-
-import javax.annotation.Nullable;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import javax.annotation.Nullable;
+import org.jdom.Element;
+import org.jetbrains.android.facet.AndroidFacet;
 
-/**
- * Manages android debugger state for the run configurations.
- */
+/** Manages android debugger state for the run configurations. */
 public class BlazeAndroidRunConfigurationDebuggerManager implements JDOMExternalizable {
   private final Project project;
   private final Map<String, AndroidDebuggerState> androidDebuggerStates = Maps.newHashMap();
   private final BlazeAndroidRunConfigurationCommonState commonState;
 
-  BlazeAndroidRunConfigurationDebuggerManager(Project project,
-                                              BlazeAndroidRunConfigurationCommonState commonState) {
+  BlazeAndroidRunConfigurationDebuggerManager(
+      Project project, BlazeAndroidRunConfigurationCommonState commonState) {
     this.project = project;
     this.commonState = commonState;
-    for (AndroidDebugger androidDebugger: getAndroidDebuggers()) {
+    for (AndroidDebugger androidDebugger : getAndroidDebuggers()) {
       this.androidDebuggerStates.put(androidDebugger.getId(), androidDebugger.createState());
     }
   }
 
   List<ValidationError> validate(AndroidFacet facet) {
-    // All of the AndroidDebuggerState classes implement a validate that either does nothing or is specific to gradle so there is no point
+    // All of the AndroidDebuggerState classes implement a validate that
+    // either does nothing or is specific to gradle so there is no point
     // in calling validate on our AndroidDebuggerState.
     return ImmutableList.of();
   }
@@ -63,7 +61,7 @@
   @Nullable
   AndroidDebugger getAndroidDebugger() {
     String debuggerID = getDebuggerID();
-    for (AndroidDebugger androidDebugger: getAndroidDebuggers()) {
+    for (AndroidDebugger androidDebugger : getAndroidDebuggers()) {
       if (androidDebugger.getId().equals(debuggerID)) {
         return androidDebugger;
       }
@@ -76,7 +74,7 @@
     T androidDebuggerState = getAndroidDebuggerState(getDebuggerID());
     // Set our working directory to our workspace root for native debugging.
     if (androidDebuggerState instanceof NativeAndroidDebuggerState) {
-      NativeAndroidDebuggerState nativeState = (NativeAndroidDebuggerState)androidDebuggerState;
+      NativeAndroidDebuggerState nativeState = (NativeAndroidDebuggerState) androidDebuggerState;
       String workingDirPath = WorkspaceRoot.fromProject(project).directory().getPath();
       nativeState.setWorkingDir(workingDirPath);
     }
@@ -89,21 +87,23 @@
   }
 
   private String getDebuggerID() {
-    BlazeNativeDebuggerIdProvider blazeNativeDebuggerIdProvider = BlazeNativeDebuggerIdProvider.getInstance();
+    BlazeNativeDebuggerIdProvider blazeNativeDebuggerIdProvider =
+        BlazeNativeDebuggerIdProvider.getInstance();
     return (blazeNativeDebuggerIdProvider != null && commonState.isNativeDebuggingEnabled())
-           ? blazeNativeDebuggerIdProvider.getDebuggerId()
-           : AndroidJavaDebugger.ID;
+        ? blazeNativeDebuggerIdProvider.getDebuggerId()
+        : AndroidJavaDebugger.ID;
   }
 
   @Nullable
-  private final <T extends AndroidDebuggerState> T getAndroidDebuggerState(String androidDebuggerId) {
+  private final <T extends AndroidDebuggerState> T getAndroidDebuggerState(
+      String androidDebuggerId) {
     AndroidDebuggerState state = androidDebuggerStates.get(androidDebuggerId);
-    return (state != null) ? (T)state : null;
+    return (state != null) ? (T) state : null;
   }
 
   @Override
   public void readExternal(Element element) throws InvalidDataException {
-    for (Map.Entry<String, AndroidDebuggerState> entry: androidDebuggerStates.entrySet()) {
+    for (Map.Entry<String, AndroidDebuggerState> entry : androidDebuggerStates.entrySet()) {
       Element optionElement = element.getChild(entry.getKey());
       if (optionElement != null) {
         entry.getValue().readExternal(optionElement);
@@ -113,7 +113,7 @@
 
   @Override
   public void writeExternal(Element element) throws WriteExternalException {
-    for (Map.Entry<String, AndroidDebuggerState> entry: androidDebuggerStates.entrySet()) {
+    for (Map.Entry<String, AndroidDebuggerState> entry : androidDebuggerStates.entrySet()) {
       Element optionElement = new Element(entry.getKey());
       element.addContent(optionElement);
       entry.getValue().writeExternal(optionElement);
diff --git a/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidRunConfigurationDeployTargetManager.java b/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidRunConfigurationDeployTargetManager.java
index 3b76283..306bec4 100644
--- a/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidRunConfigurationDeployTargetManager.java
+++ b/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidRunConfigurationDeployTargetManager.java
@@ -30,16 +30,13 @@
 import com.intellij.openapi.util.InvalidDataException;
 import com.intellij.openapi.util.JDOMExternalizable;
 import com.intellij.openapi.util.WriteExternalException;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.Nullable;
 import org.jdom.Element;
 import org.jetbrains.android.facet.AndroidFacet;
 
-import javax.annotation.Nullable;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Manages deploy target state for run configurations.
- */
+/** Manages deploy target state for run configurations. */
 public class BlazeAndroidRunConfigurationDeployTargetManager implements JDOMExternalizable {
   private static final String TARGET_SELECTION_MODE = TargetSelectionMode.SHOW_DIALOG.name();
 
@@ -48,8 +45,7 @@
   private final List<DeployTargetProvider> deployTargetProviders;
   private final Map<String, DeployTargetState> deployTargetStates;
 
-  BlazeAndroidRunConfigurationDeployTargetManager(int runConfigId,
-                                                  boolean isAndroidTest) {
+  BlazeAndroidRunConfigurationDeployTargetManager(int runConfigId, boolean isAndroidTest) {
     this.runConfigId = runConfigId;
     this.isAndroidTest = isAndroidTest;
     this.deployTargetProviders = DeployTargetProvider.getProviders();
@@ -66,28 +62,26 @@
   }
 
   @Nullable
-  DeployTarget getDeployTarget(Executor executor,
-                               ExecutionEnvironment env,
-                               AndroidFacet facet) throws ExecutionException {
+  DeployTarget getDeployTarget(Executor executor, ExecutionEnvironment env, AndroidFacet facet)
+      throws ExecutionException {
     DeployTargetProvider currentTargetProvider = getCurrentDeployTargetProvider();
 
     DeployTarget deployTarget;
     if (currentTargetProvider.requiresRuntimePrompt()) {
-      deployTarget = currentTargetProvider.showPrompt(
-        executor,
-        env,
-        facet,
-        getDeviceCount(),
-        isAndroidTest,
-        deployTargetStates,
-        runConfigId,
-        (device) -> LaunchCompatibility.YES
-      );
+      deployTarget =
+          currentTargetProvider.showPrompt(
+              executor,
+              env,
+              facet,
+              getDeviceCount(),
+              isAndroidTest,
+              deployTargetStates,
+              runConfigId,
+              (device) -> LaunchCompatibility.YES);
       if (deployTarget == null) {
         return null;
       }
-    }
-    else {
+    } else {
       deployTarget = currentTargetProvider.getDeployTarget();
     }
 
@@ -99,7 +93,8 @@
     return deployTargetStates.get(currentTarget.getId());
   }
 
-  // TODO(salguarnieri) Currently the target selection mode is always SHOW_DIALOG. This code is here for future use.
+  // TODO(salguarnieri) Currently the target selection mode is always SHOW_DIALOG.
+  // This code is here for future use.
   // If this code still isn't used after ASwB supports native, then we should delete this logic.
   private DeployTargetProvider getCurrentDeployTargetProvider() {
     DeployTargetProvider target = getDeployTargetProvider(TARGET_SELECTION_MODE);
diff --git a/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidRunConfigurationRunner.java b/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidRunConfigurationRunner.java
index eed693e..e0cef2a 100644
--- a/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidRunConfigurationRunner.java
+++ b/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidRunConfigurationRunner.java
@@ -16,8 +16,19 @@
 
 package com.google.idea.blaze.android.run.runner;
 
+import static com.android.tools.idea.gradle.util.Projects.requiredAndroidModelMissing;
+import static org.jetbrains.android.actions.RunAndroidAvdManagerAction.getName;
+
 import com.android.ddmlib.IDevice;
-import com.android.tools.idea.run.*;
+import com.android.tools.idea.run.AndroidProcessHandler;
+import com.android.tools.idea.run.AndroidSessionInfo;
+import com.android.tools.idea.run.ApkProvisionException;
+import com.android.tools.idea.run.ApplicationIdProvider;
+import com.android.tools.idea.run.DeviceFutures;
+import com.android.tools.idea.run.LaunchInfo;
+import com.android.tools.idea.run.LaunchOptions;
+import com.android.tools.idea.run.LaunchTaskRunner;
+import com.android.tools.idea.run.ValidationError;
 import com.android.tools.idea.run.editor.DeployTarget;
 import com.android.tools.idea.run.editor.DeployTargetState;
 import com.android.tools.idea.run.tasks.LaunchTasksProvider;
@@ -26,8 +37,8 @@
 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.android.run.BlazeAndroidRunConfiguration;
 import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationCommonState;
+import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationHandler;
 import com.google.idea.blaze.base.command.BlazeFlags;
 import com.google.idea.blaze.base.experiments.ExperimentScope;
 import com.google.idea.blaze.base.metrics.Action;
@@ -36,6 +47,7 @@
 import com.google.idea.blaze.base.scope.Scope;
 import com.google.idea.blaze.base.scope.output.IssueOutput;
 import com.google.idea.blaze.base.scope.scopes.BlazeConsoleScope;
+import com.google.idea.blaze.base.scope.scopes.IdeaLogScope;
 import com.google.idea.blaze.base.scope.scopes.IssuesScope;
 import com.google.idea.blaze.base.scope.scopes.LoggedTimingScope;
 import com.google.idea.blaze.base.settings.BlazeUserSettings;
@@ -57,6 +69,7 @@
 import com.intellij.openapi.util.JDOMExternalizable;
 import com.intellij.openapi.util.Key;
 import com.intellij.openapi.util.WriteExternalException;
+import java.util.List;
 import org.jdom.Element;
 import org.jetbrains.android.facet.AndroidFacet;
 import org.jetbrains.android.sdk.AndroidSdkUtils;
@@ -64,35 +77,35 @@
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import java.util.List;
-
-import static com.android.tools.idea.gradle.util.Projects.requiredAndroidModelMissing;
-import static org.jetbrains.android.actions.RunAndroidAvdManagerAction.getName;
-
 /**
  * Supports the entire run configuration flow. Used by both android_binary and android_test.
  *
- * Does any verification necessary, builds the APK and installs it, launches and debug tasks, etc.
+ * <p>Does any verification necessary, builds the APK and installs it, launches and debug tasks,
+ * etc.
  *
- * Any indirection between android_binary/android_test, mobile-install, InstantRun etc. should
+ * <p>Any indirection between android_binary/android_test, mobile-install, InstantRun etc. should
  * come via the strategy class.
  */
 public final class BlazeAndroidRunConfigurationRunner implements JDOMExternalizable {
 
   private static final Logger LOG = Logger.getInstance(BlazeAndroidRunConfigurationRunner.class);
 
-  private static final String SYNC_FAILED_ERR_MSG = "Project state is invalid. Please sync and try your action again.";
+  private static final String SYNC_FAILED_ERR_MSG =
+      "Project state is invalid. Please sync and try your action again.";
 
   public static final Key<BlazeAndroidRunContext> RUN_CONTEXT_KEY = Key.create("blaze.run.context");
-  public static final Key<BlazeAndroidDeviceSelector.DeviceSession> DEVICE_SESSION_KEY = Key.create("blaze.device.session");
+  public static final Key<BlazeAndroidDeviceSelector.DeviceSession> DEVICE_SESSION_KEY =
+      Key.create("blaze.device.session");
 
-  // We need to split "-c dbg" into two flags because we pass flags as a list of strings to the command line executor and we need blaze
+  // We need to split "-c dbg" into two flags because we pass flags
+  // as a list of strings to the command line executor and we need blaze
   // to see -c and dbg as two separate entities, not one.
-  private static final ImmutableList<String> NATIVE_DEBUG_FLAGS = ImmutableList.of("--fission=no", "-c", "dbg");
+  private static final ImmutableList<String> NATIVE_DEBUG_FLAGS =
+      ImmutableList.of("--fission=no", "-c", "dbg");
 
   private final Project project;
 
-  private final BlazeAndroidRunConfiguration runConfiguration;
+  private final BlazeAndroidRunConfigurationHandler runConfigurationHandler;
 
   private final BlazeAndroidRunConfigurationCommonState commonState;
 
@@ -102,25 +115,27 @@
 
   private final BlazeAndroidRunConfigurationDebuggerManager debuggerManager;
 
-  public BlazeAndroidRunConfigurationRunner(Project project,
-                                            BlazeAndroidRunConfiguration runConfiguration,
-                                            BlazeAndroidRunConfigurationCommonState commonState,
-                                            boolean isAndroidTest,
-                                            int runConfigId) {
+  public BlazeAndroidRunConfigurationRunner(
+      Project project,
+      BlazeAndroidRunConfigurationHandler runConfigurationHandler,
+      BlazeAndroidRunConfigurationCommonState commonState,
+      boolean isAndroidTest,
+      int runConfigId) {
     this.project = project;
-    this.runConfiguration = runConfiguration;
+    this.runConfigurationHandler = runConfigurationHandler;
     this.commonState = commonState;
     this.runConfigId = runConfigId;
-    this.deployTargetManager = new BlazeAndroidRunConfigurationDeployTargetManager(runConfigId, isAndroidTest);
+    this.deployTargetManager =
+        new BlazeAndroidRunConfigurationDeployTargetManager(runConfigId, isAndroidTest);
     this.debuggerManager = new BlazeAndroidRunConfigurationDebuggerManager(project, commonState);
   }
 
   private ImmutableList<String> getBuildFlags(Project project, ProjectViewSet projectViewSet) {
     return ImmutableList.<String>builder()
-      .addAll(BlazeFlags.buildFlags(project, projectViewSet))
-      .addAll(commonState.getUserFlags())
-      .addAll(getNativeDebuggerFlags())
-      .build();
+        .addAll(BlazeFlags.buildFlags(project, projectViewSet))
+        .addAll(commonState.getUserFlags())
+        .addAll(getNativeDebuggerFlags())
+        .build();
   }
 
   public ImmutableList<String> getNativeDebuggerFlags() {
@@ -128,17 +143,20 @@
   }
 
   /**
-   * We collect errors rather than throwing to avoid missing fatal errors by exiting early for a warning.
-   * We use a separate method for the collection so the compiler prevents us from accidentally throwing.
+   * We collect errors rather than throwing to avoid missing fatal errors by exiting early for a
+   * warning. We use a separate method for the collection so the compiler prevents us from
+   * accidentally throwing.
    */
   public List<ValidationError> validate(@Nullable Module module) {
     List<ValidationError> errors = Lists.newArrayList();
     if (module == null) {
-      errors.add(ValidationError.fatal("No run configuration module found"));
+      errors.add(
+          ValidationError.fatal(
+              "No run configuration module found. Have you successfully synced your project?"));
       return errors;
     }
 
-    if (commonState.getTarget() == null) {
+    if (runConfigurationHandler.getLabel() == null) {
       errors.add(ValidationError.fatal("No target selected"));
       return errors;
     }
@@ -151,7 +169,8 @@
     AndroidFacet facet = AndroidFacet.getInstance(module);
     if (facet == null) {
       // Can't proceed.
-      return ImmutableList.of(ValidationError.fatal(AndroidBundle.message("no.facet.error", module.getName())));
+      return ImmutableList.of(
+          ValidationError.fatal(AndroidBundle.message("no.facet.error", module.getName())));
     }
 
     if (facet.getConfiguration().getAndroidPlatform() == null) {
@@ -165,9 +184,8 @@
   }
 
   @Nullable
-  public final RunProfileState getState(Module module,
-                                        final Executor executor,
-                                        ExecutionEnvironment env) throws ExecutionException {
+  public final RunProfileState getState(
+      Module module, final Executor executor, ExecutionEnvironment env) throws ExecutionException {
 
     assert module != null : "Enforced by fatal validation check in checkConfiguration.";
     final AndroidFacet facet = AndroidFacet.getInstance(module);
@@ -180,33 +198,24 @@
     }
     ImmutableList<String> buildFlags = getBuildFlags(project, projectViewSet);
 
-    BlazeAndroidRunContext runContext = runConfiguration.createRunContext(
-      project,
-      facet,
-      env,
-      buildFlags
-    );
+    BlazeAndroidRunContext runContext =
+        runConfigurationHandler.createRunContext(project, facet, env, buildFlags);
 
     runContext.augmentEnvironment(env);
 
-    boolean debug = executor instanceof  DefaultDebugExecutor;
-    if (debug && !AndroidSdkUtils.activateDdmsIfNecessary(facet.getModule().getProject())) {
-      throw new ExecutionException("Unable to obtain debug bridge. Please check if there is a different tool using adb that is active.");
+    boolean isDebug = executor instanceof DefaultDebugExecutor;
+    if (isDebug && !AndroidSdkUtils.activateDdmsIfNecessary(facet.getModule().getProject())) {
+      throw new ExecutionException(
+          "Unable to obtain debug bridge. "
+              + "Please check if there is a different tool using adb that is active.");
     }
 
     AndroidSessionInfo info = AndroidSessionInfo.findOldSession(project, null, runConfigId);
 
     BlazeAndroidDeviceSelector deviceSelector = runContext.getDeviceSelector();
-    BlazeAndroidDeviceSelector.DeviceSession deviceSession = deviceSelector.getDevice(
-      project,
-      facet,
-      deployTargetManager,
-      executor,
-      env,
-      info,
-      debug,
-      runConfigId
-    );
+    BlazeAndroidDeviceSelector.DeviceSession deviceSession =
+        deviceSelector.getDevice(
+            project, facet, deployTargetManager, executor, env, info, isDebug, runConfigId);
     if (deviceSession == null) {
       return null;
     }
@@ -219,7 +228,8 @@
 
     DeviceFutures deviceFutures = deviceSession.deviceFutures;
     if (deviceFutures == null) {
-      // The user deliberately canceled, or some error was encountered and exposed by the chooser. Quietly exit.
+      // The user deliberately canceled, or some error was encountered and exposed by the chooser.
+      // Quietly exit.
       return null;
     }
 
@@ -227,33 +237,26 @@
       throw new ExecutionException(AndroidBundle.message("deployment.target.not.found"));
     }
 
-    if (debug) {
+    if (isDebug) {
       String error = canDebug(deviceFutures, facet, module.getName());
       if (error != null) {
         throw new ExecutionException(error);
       }
     }
 
-    LaunchOptions.Builder launchOptionsBuilder = getDefaultLaunchOptions()
-      .setDebug(debug);
+    LaunchOptions.Builder launchOptionsBuilder = getDefaultLaunchOptions().setDebug(isDebug);
     runContext.augmentLaunchOptions(launchOptionsBuilder);
-    LaunchOptions launchOptions = launchOptionsBuilder.build();
 
     // Store the run context on the execution environment so before-run tasks can access it.
     env.putCopyableUserData(RUN_CONTEXT_KEY, runContext);
     env.putCopyableUserData(DEVICE_SESSION_KEY, deviceSession);
 
     return new BlazeAndroidRunState(
-      module,
-      env,
-      getName(),
-      launchOptions,
-      deviceSession,
-      runContext
-    );
+        module, env, getName(), launchOptionsBuilder, isDebug, deviceSession, runContext);
   }
 
-  private static String canDebug(DeviceFutures deviceFutures, AndroidFacet facet, String moduleName) {
+  private static String canDebug(
+      DeviceFutures deviceFutures, AndroidFacet facet, String moduleName) {
     // If we are debugging on a device, then the app needs to be debuggable
     for (ListenableFuture<IDevice> future : deviceFutures.get()) {
       if (!future.isDone()) {
@@ -262,45 +265,50 @@
       }
       IDevice device = Futures.getUnchecked(future);
       if (!LaunchUtils.canDebugAppOnDevice(facet, device)) {
-        return AndroidBundle.message("android.cannot.debug.noDebugPermissions", moduleName, device.getName());
+        return AndroidBundle.message(
+            "android.cannot.debug.noDebugPermissions", moduleName, device.getName());
       }
     }
     return null;
   }
 
-
   private static LaunchOptions.Builder getDefaultLaunchOptions() {
     return LaunchOptions.builder()
-      .setClearLogcatBeforeStart(false)
-      .setSkipNoopApkInstallations(true)
-      .setForceStopRunningApp(true);
+        .setClearLogcatBeforeStart(false)
+        .setSkipNoopApkInstallations(true)
+        .setForceStopRunningApp(true);
   }
 
   public boolean executeBuild(ExecutionEnvironment env) {
     boolean suppressConsole = BlazeUserSettings.getInstance().getSuppressConsoleForRunAction();
-    return Scope.root(context -> {
-      context
-        .push(new IssuesScope(project))
-        .push(new ExperimentScope())
-        .push(new BlazeConsoleScope.Builder(project).setSuppressConsole(suppressConsole).build())
-        .push(new LoggedTimingScope(project, Action.APK_BUILD_AND_INSTALL))
-      ;
+    return Scope.root(
+        context -> {
+          context
+              .push(new IssuesScope(project))
+              .push(new ExperimentScope())
+              .push(
+                  new BlazeConsoleScope.Builder(project)
+                      .setSuppressConsole(suppressConsole)
+                      .build())
+              .push(new IdeaLogScope())
+              .push(new LoggedTimingScope(project, Action.APK_BUILD_AND_INSTALL));
 
-      BlazeAndroidRunContext runContext = env.getCopyableUserData(RUN_CONTEXT_KEY);
-      if (runContext == null) {
-        IssueOutput.error("Could not find run context. Please try again").submit(context);
-        return false;
-      }
-      BlazeAndroidDeviceSelector.DeviceSession deviceSession = env.getCopyableUserData(DEVICE_SESSION_KEY);
+          BlazeAndroidRunContext runContext = env.getCopyableUserData(RUN_CONTEXT_KEY);
+          if (runContext == null) {
+            IssueOutput.error("Could not find run context. Please try again").submit(context);
+            return false;
+          }
+          BlazeAndroidDeviceSelector.DeviceSession deviceSession =
+              env.getCopyableUserData(DEVICE_SESSION_KEY);
 
-      BlazeApkBuildStep buildStep = runContext.getBuildStep();
-      try {
-        return buildStep.build(context, deviceSession);
-      } catch (Exception e) {
-        LOG.error(e);
-        return false;
-      }
-    });
+          BlazeApkBuildStep buildStep = runContext.getBuildStep();
+          try {
+            return buildStep.build(context, deviceSession);
+          } catch (Exception e) {
+            LOG.error(e);
+            return false;
+          }
+        });
   }
 
   private final class BlazeAndroidRunState implements RunProfileState {
@@ -310,25 +318,30 @@
     private final String launchConfigName;
     private final BlazeAndroidDeviceSelector.DeviceSession deviceSession;
     private final BlazeAndroidRunContext runContext;
-    private final LaunchOptions launchOptions;
+    private final LaunchOptions.Builder launchOptionsBuilder;
+    private final boolean isDebug;
 
-    private BlazeAndroidRunState(Module module,
-                                 ExecutionEnvironment env,
-                                 String launchConfigName,
-                                 LaunchOptions launchOptions,
-                                 BlazeAndroidDeviceSelector.DeviceSession deviceSession,
-                                 BlazeAndroidRunContext runContext) {
+    private BlazeAndroidRunState(
+        Module module,
+        ExecutionEnvironment env,
+        String launchConfigName,
+        LaunchOptions.Builder launchOptionsBuilder,
+        boolean isDebug,
+        BlazeAndroidDeviceSelector.DeviceSession deviceSession,
+        BlazeAndroidRunContext runContext) {
       this.module = module;
       this.env = env;
       this.launchConfigName = launchConfigName;
       this.deviceSession = deviceSession;
       this.runContext = runContext;
-      this.launchOptions = launchOptions;
+      this.launchOptionsBuilder = launchOptionsBuilder;
+      this.isDebug = isDebug;
     }
 
     @Nullable
     @Override
-    public ExecutionResult execute(Executor executor, @NotNull ProgramRunner runner) throws ExecutionException {
+    public ExecutionResult execute(Executor executor, @NotNull ProgramRunner runner)
+        throws ExecutionException {
       ProcessHandler processHandler;
       ConsoleView console;
 
@@ -337,42 +350,51 @@
       String applicationId;
       try {
         applicationId = applicationIdProvider.getPackageName();
-      }
-      catch (ApkProvisionException e) {
+      } catch (ApkProvisionException e) {
         throw new ExecutionException("Unable to obtain application id", e);
       }
 
-      LaunchTasksProvider launchTasksProvider = runContext.getLaunchTasksProvider(launchOptions, debuggerManager);
+      LaunchTasksProvider launchTasksProvider =
+          runContext.getLaunchTasksProvider(launchOptionsBuilder, isDebug, debuggerManager);
 
       DeviceFutures deviceFutures = deviceSession.deviceFutures;
       assert deviceFutures != null;
-      ProcessHandler previousSessionProcessHandler = deviceSession.sessionInfo != null ?
-                                                     deviceSession.sessionInfo.getProcessHandler() : null;
+      ProcessHandler previousSessionProcessHandler =
+          deviceSession.sessionInfo != null ? deviceSession.sessionInfo.getProcessHandler() : null;
 
       if (launchTasksProvider.createsNewProcess()) {
-        // In the case of cold swap, there is an existing process that is connected, but we are going to launch a new one.
-        // Detach the previous process handler so that we don't end up with 2 run tabs for the same launch (the existing one
-        // and the new one).
+        // In the case of cold swap, there is an existing process that is connected,
+        // but we are going to launch a new one.
+        // Detach the previous process handler so that we don't end up with
+        // 2 run tabs for the same launch (the existing one and the new one).
         if (previousSessionProcessHandler != null) {
           previousSessionProcessHandler.detachProcess();
         }
 
-        processHandler = new AndroidProcessHandler(applicationId, launchTasksProvider.monitorRemoteProcess());
-        console = runContext.getConsoleProvider().createAndAttach(module.getProject(), processHandler, executor);
+        processHandler =
+            new AndroidProcessHandler(applicationId, launchTasksProvider.monitorRemoteProcess());
+        console =
+            runContext
+                .getConsoleProvider()
+                .createAndAttach(module.getProject(), processHandler, executor);
       } else {
-        assert previousSessionProcessHandler != null : "No process handler from previous session, yet current tasks don't create one";
+        assert previousSessionProcessHandler != null
+            : "No process handler from previous session, yet current tasks don't create one";
         processHandler = previousSessionProcessHandler;
         console = null;
       }
 
-      LaunchInfo launchInfo = new LaunchInfo(executor, runner, env, runContext.getConsoleProvider());
+      LaunchInfo launchInfo =
+          new LaunchInfo(executor, runner, env, runContext.getConsoleProvider());
 
-      LaunchTaskRunner task = new LaunchTaskRunner(module.getProject(),
-                                                   launchConfigName,
-                                                   launchInfo,
-                                                   processHandler,
-                                                   deviceSession.deviceFutures,
-                                                   launchTasksProvider);
+      LaunchTaskRunner task =
+          new LaunchTaskRunner(
+              module.getProject(),
+              launchConfigName,
+              launchInfo,
+              processHandler,
+              deviceSession.deviceFutures,
+              launchTasksProvider);
       ProgressManager.getInstance().run(task);
 
       return console == null ? null : new DefaultExecutionResult(console, processHandler);
diff --git a/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidRunContext.java b/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidRunContext.java
index ef6f94d..1778a63 100644
--- a/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidRunContext.java
+++ b/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidRunContext.java
@@ -17,6 +17,7 @@
 
 import com.android.ddmlib.IDevice;
 import com.android.tools.idea.run.ApplicationIdProvider;
+import com.android.tools.idea.run.ConsolePrinter;
 import com.android.tools.idea.run.ConsoleProvider;
 import com.android.tools.idea.run.LaunchOptions;
 import com.android.tools.idea.run.editor.AndroidDebugger;
@@ -28,13 +29,10 @@
 import com.google.common.collect.ImmutableList;
 import com.intellij.execution.ExecutionException;
 import com.intellij.execution.runners.ExecutionEnvironment;
-
-import javax.annotation.Nullable;
 import java.util.Set;
+import javax.annotation.Nullable;
 
-/**
- * Instantiated when the configuration wants to run.
- */
+/** Instantiated when the configuration wants to run. */
 public interface BlazeAndroidRunContext {
 
   BlazeAndroidDeviceSelector getDeviceSelector();
@@ -50,29 +48,33 @@
   ApplicationIdProvider getApplicationIdProvider() throws ExecutionException;
 
   LaunchTasksProvider getLaunchTasksProvider(
-    LaunchOptions launchOptions,
-    BlazeAndroidRunConfigurationDebuggerManager debuggerManager) throws ExecutionException;
+      LaunchOptions.Builder launchOptionsBuilder,
+      boolean isDebug,
+      BlazeAndroidRunConfigurationDebuggerManager debuggerManager)
+      throws ExecutionException;
 
-  /**
-   * Returns the tasks to deploy the application.
-   */
-  ImmutableList<LaunchTask> getDeployTasks(IDevice device, LaunchOptions launchOptions) throws ExecutionException;
+  /** Returns the tasks to deploy the application. */
+  ImmutableList<LaunchTask> getDeployTasks(IDevice device, LaunchOptions launchOptions)
+      throws ExecutionException;
 
-  /**
-   * Returns the task to launch the application.
-   */
+  /** Returns the task to launch the application. */
   @Nullable
-  LaunchTask getApplicationLaunchTask(LaunchOptions launchOptions,
-                                      AndroidDebugger androidDebugger,
-                                      AndroidDebuggerState androidDebuggerState,
-                                      ProcessHandlerLaunchStatus processHandlerLaunchStatus) throws ExecutionException;
+  LaunchTask getApplicationLaunchTask(
+      LaunchOptions launchOptions,
+      @Nullable Integer userId,
+      AndroidDebugger androidDebugger,
+      AndroidDebuggerState androidDebuggerState,
+      ProcessHandlerLaunchStatus processHandlerLaunchStatus)
+      throws ExecutionException;
 
-  /**
-   * Returns the task to connect the debugger.
-   */
+  /** Returns the task to connect the debugger. */
   @Nullable
-  DebugConnectorTask getDebuggerTask(LaunchOptions launchOptions,
-                                     AndroidDebugger androidDebugger,
-                                     AndroidDebuggerState androidDebuggerState,
-                                     Set<String> packageIds) throws ExecutionException;
+  DebugConnectorTask getDebuggerTask(
+      AndroidDebugger androidDebugger,
+      AndroidDebuggerState androidDebuggerState,
+      Set<String> packageIds)
+      throws ExecutionException;
+
+  @Nullable
+  Integer getUserId(IDevice device, ConsolePrinter consolePrinter) throws ExecutionException;
 }
diff --git a/aswb/src/com/google/idea/blaze/android/run/runner/BlazeApkBuildStep.java b/aswb/src/com/google/idea/blaze/android/run/runner/BlazeApkBuildStep.java
index 3667c93..b437f7f 100644
--- a/aswb/src/com/google/idea/blaze/android/run/runner/BlazeApkBuildStep.java
+++ b/aswb/src/com/google/idea/blaze/android/run/runner/BlazeApkBuildStep.java
@@ -17,9 +17,7 @@
 
 import com.google.idea.blaze.base.scope.BlazeContext;
 
-/**
- * Builds the APK.
- */
+/** Builds the APK. */
 public interface BlazeApkBuildStep {
   /**
    * Builds an optionally installs the APK.
diff --git a/aswb/src/com/google/idea/blaze/android/run/runner/BlazeApkBuildStepNormalBuild.java b/aswb/src/com/google/idea/blaze/android/run/runner/BlazeApkBuildStepNormalBuild.java
index 2da6a9a..9c18824 100644
--- a/aswb/src/com/google/idea/blaze/android/run/runner/BlazeApkBuildStepNormalBuild.java
+++ b/aswb/src/com/google/idea/blaze/android/run/runner/BlazeApkBuildStepNormalBuild.java
@@ -19,7 +19,6 @@
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.SettableFuture;
-import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationCommonState;
 import com.google.idea.blaze.android.run.deployinfo.BlazeAndroidDeployInfo;
 import com.google.idea.blaze.android.run.deployinfo.BlazeApkDeployInfoProtoHelper;
 import com.google.idea.blaze.base.async.executor.BlazeExecutor;
@@ -28,8 +27,10 @@
 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.filecache.FileCaches;
 import com.google.idea.blaze.base.issueparser.IssueOutputLineProcessor;
 import com.google.idea.blaze.base.metrics.Action;
+import com.google.idea.blaze.base.model.primitives.Label;
 import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
 import com.google.idea.blaze.base.scope.BlazeContext;
 import com.google.idea.blaze.base.scope.ScopedTask;
@@ -39,82 +40,80 @@
 import com.google.idea.blaze.base.util.SaveUtil;
 import com.intellij.execution.ExecutionException;
 import com.intellij.openapi.project.Project;
-import com.intellij.openapi.vfs.LocalFileSystem;
+import java.util.concurrent.CancellationException;
 import org.jetbrains.annotations.NotNull;
 
-import java.util.concurrent.CancellationException;
-
-/**
- * Builds the APK using normal blaze build.
- */
+/** Builds the APK using normal blaze build. */
 public class BlazeApkBuildStepNormalBuild implements BlazeApkBuildStep {
   private final Project project;
-  private final BlazeAndroidRunConfigurationCommonState commonState;
+  private final Label label;
   private final ImmutableList<String> buildFlags;
   private final SettableFuture<BlazeAndroidDeployInfo> deployInfoFuture = SettableFuture.create();
 
-  public BlazeApkBuildStepNormalBuild(Project project,
-                                      BlazeAndroidRunConfigurationCommonState commonState,
-                                      ImmutableList<String> buildFlags) {
+  public BlazeApkBuildStepNormalBuild(
+      Project project, Label label, ImmutableList<String> buildFlags) {
     this.project = project;
-    this.commonState = commonState;
+    this.label = label;
     this.buildFlags = buildFlags;
   }
 
   @Override
-  public boolean build(BlazeContext context, BlazeAndroidDeviceSelector.DeviceSession deviceSession) {
-    final ScopedTask buildTask = new ScopedTask(context) {
-      @Override
-      protected void execute(@NotNull BlazeContext context) {
-        BlazeCommand.Builder command = BlazeCommand.builder(Blaze.getBuildSystem(project), BlazeCommandName.BUILD);
-        WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProject(project);
+  public boolean build(
+      BlazeContext context, BlazeAndroidDeviceSelector.DeviceSession deviceSession) {
+    final ScopedTask buildTask =
+        new ScopedTask(context) {
+          @Override
+          protected void execute(@NotNull BlazeContext context) {
+            BlazeCommand.Builder command =
+                BlazeCommand.builder(Blaze.getBuildSystem(project), BlazeCommandName.BUILD);
+            WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProject(project);
 
-        command
-          .addTargets(commonState.getTarget())
-          .addBlazeFlags("--output_groups=+android_deploy_info")
-          .addBlazeFlags(buildFlags)
-          .addBlazeFlags(BlazeFlags.EXPERIMENTAL_SHOW_ARTIFACTS)
-        ;
+            command
+                .addTargets(label)
+                .addBlazeFlags("--output_groups=+android_deploy_info")
+                .addBlazeFlags(buildFlags)
+                .addBlazeFlags(BlazeFlags.EXPERIMENTAL_SHOW_ARTIFACTS);
 
-        BlazeApkDeployInfoProtoHelper deployInfoHelper = new BlazeApkDeployInfoProtoHelper(project, buildFlags);
+            BlazeApkDeployInfoProtoHelper deployInfoHelper =
+                new BlazeApkDeployInfoProtoHelper(project, buildFlags);
 
-        SaveUtil.saveAllFiles();
-        int retVal = ExternalTask.builder(workspaceRoot, command.build())
-          .context(context)
-          .stderr(LineProcessingOutputStream.of(
-            deployInfoHelper.getLineProcessor(),
-            new IssueOutputLineProcessor(project, context, workspaceRoot)
-          ))
-          .build()
-          .run(new LoggedTimingScope(project, Action.BLAZE_BUILD));
-        LocalFileSystem.getInstance().refresh(true);
+            SaveUtil.saveAllFiles();
+            int retVal =
+                ExternalTask.builder(workspaceRoot)
+                    .addBlazeCommand(command.build())
+                    .context(context)
+                    .stderr(
+                        LineProcessingOutputStream.of(
+                            deployInfoHelper.getLineProcessor(),
+                            new IssueOutputLineProcessor(project, context, workspaceRoot)))
+                    .build()
+                    .run(new LoggedTimingScope(project, Action.BLAZE_BUILD));
+            FileCaches.refresh(project);
 
-        if (retVal != 0) {
-          context.setHasError();
-          return;
-        }
-        BlazeAndroidDeployInfo deployInfo = deployInfoHelper.readDeployInfo(context);
-        if (deployInfo == null) {
-          IssueOutput.error("Could not read apk deploy info from build").submit(context);
-          return;
-        }
-        deployInfoFuture.set(deployInfo);
-      }
-    };
+            if (retVal != 0) {
+              context.setHasError();
+              return;
+            }
+            BlazeAndroidDeployInfo deployInfo = deployInfoHelper.readDeployInfo(context);
+            if (deployInfo == null) {
+              IssueOutput.error("Could not read apk deploy info from build").submit(context);
+              return;
+            }
+            deployInfoFuture.set(deployInfo);
+          }
+        };
 
-    ListenableFuture<Void> buildFuture = BlazeExecutor.submitTask(
-      project,
-      String.format("Executing %s apk build", Blaze.buildSystemName(project)),
-      buildTask
-    );
+    ListenableFuture<Void> buildFuture =
+        BlazeExecutor.submitTask(
+            project,
+            String.format("Executing %s apk build", Blaze.buildSystemName(project)),
+            buildTask);
 
     try {
       Futures.get(buildFuture, ExecutionException.class);
-    }
-    catch (ExecutionException e) {
+    } catch (ExecutionException e) {
       context.setHasError();
-    }
-    catch (CancellationException e) {
+    } catch (CancellationException e) {
       context.setCancelled();
     }
     return context.shouldContinue();
diff --git a/aswb/src/com/google/idea/blaze/android/run/test/AndroidTestConsoleProvider.java b/aswb/src/com/google/idea/blaze/android/run/test/AndroidTestConsoleProvider.java
index 878cf08..56ff338 100644
--- a/aswb/src/com/google/idea/blaze/android/run/test/AndroidTestConsoleProvider.java
+++ b/aswb/src/com/google/idea/blaze/android/run/test/AndroidTestConsoleProvider.java
@@ -29,17 +29,16 @@
 import com.intellij.openapi.util.Disposer;
 import org.jetbrains.annotations.NotNull;
 
-/**
- * Console provider for android_test
- */
+/** Console provider for android_test */
 class AndroidTestConsoleProvider implements ConsoleProvider {
   private final Project project;
   private final RunConfiguration runConfiguration;
   private final BlazeAndroidTestRunConfigurationState configState;
 
-  AndroidTestConsoleProvider(Project project,
-                             RunConfiguration runConfiguration,
-                             BlazeAndroidTestRunConfigurationState configState) {
+  AndroidTestConsoleProvider(
+      Project project,
+      RunConfiguration runConfiguration,
+      BlazeAndroidTestRunConfigurationState configState) {
     this.project = project;
     this.runConfiguration = runConfiguration;
     this.configState = configState;
@@ -47,23 +46,24 @@
 
   @NotNull
   @Override
-  public ConsoleView createAndAttach(@NotNull Disposable parent,
-                                     @NotNull ProcessHandler handler,
-                                     @NotNull Executor executor) throws ExecutionException {
+  public ConsoleView createAndAttach(
+      @NotNull Disposable parent, @NotNull ProcessHandler handler, @NotNull Executor executor)
+      throws ExecutionException {
     if (!configState.isRunThroughBlaze()) {
       return getStockConsoleProvider().createAndAttach(parent, handler, executor);
     }
-    ConsoleView console = TextConsoleBuilderFactory.getInstance()
-      .createBuilder(project)
-      .getConsole();
+    ConsoleView console =
+        TextConsoleBuilderFactory.getInstance().createBuilder(project).getConsole();
     console.attachToProcess(handler);
     return console;
   }
 
   private ConsoleProvider getStockConsoleProvider() {
     return (parent, handler, executor) -> {
-      AndroidTestConsoleProperties properties = new AndroidTestConsoleProperties(runConfiguration, executor);
-      ConsoleView consoleView = SMTestRunnerConnectionUtil.createAndAttachConsole("Android", handler, properties);
+      AndroidTestConsoleProperties properties =
+          new AndroidTestConsoleProperties(runConfiguration, executor);
+      ConsoleView consoleView =
+          SMTestRunnerConnectionUtil.createAndAttachConsole("Android", handler, properties);
       Disposer.register(parent, consoleView);
       return consoleView;
     };
diff --git a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestApplicationIdProvider.java b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestApplicationIdProvider.java
index d7e7b86..71f37fd 100644
--- a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestApplicationIdProvider.java
+++ b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestApplicationIdProvider.java
@@ -28,15 +28,13 @@
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-/**
- * Application id provider for android_binary.
- */
+/** Application id provider for android_binary. */
 public class BlazeAndroidTestApplicationIdProvider implements ApplicationIdProvider {
   private final Project project;
   private final ListenableFuture<BlazeAndroidDeployInfo> deployInfoFuture;
 
-  public BlazeAndroidTestApplicationIdProvider(Project project,
-                                               ListenableFuture<BlazeAndroidDeployInfo> deployInfoFuture) {
+  public BlazeAndroidTestApplicationIdProvider(
+      Project project, ListenableFuture<BlazeAndroidDeployInfo> deployInfoFuture) {
     this.project = project;
     this.deployInfoFuture = deployInfoFuture;
   }
@@ -47,11 +45,13 @@
     BlazeAndroidDeployInfo deployInfo = Futures.get(deployInfoFuture, ApkProvisionException.class);
     Manifest manifest = Iterables.getFirst(deployInfo.getAdditionalMergedManifests(), null);
     if (manifest == null) {
-      throw new ApkProvisionException("Could not find manifest for application under test");
+      // The application may not have a separate package,
+      // and can instead be in the same package as the tests.
+      return getTestPackageName();
     }
-    String applicationId = ApplicationManager.getApplication().runReadAction(
-      (Computable<String>)() -> manifest.getPackage().getValue()
-    );
+    String applicationId =
+        ApplicationManager.getApplication()
+            .runReadAction((Computable<String>) () -> manifest.getPackage().getValue());
     if (applicationId == null) {
       throw new ApkProvisionException("No application id in manifest under test");
     }
@@ -64,13 +64,15 @@
     BlazeAndroidDeployInfo deployInfo = Futures.get(deployInfoFuture, ApkProvisionException.class);
     Manifest manifest = deployInfo.getMergedManifest();
     if (manifest == null) {
-      throw new ApkProvisionException("Could not find merged manifest: " + deployInfo.getMergedManifestFile());
+      throw new ApkProvisionException(
+          "Could not find merged manifest: " + deployInfo.getMergedManifestFile());
     }
-    String applicationId = ApplicationManager.getApplication().runReadAction(
-      (Computable<String>)() -> manifest.getPackage().getValue()
-    );
+    String applicationId =
+        ApplicationManager.getApplication()
+            .runReadAction((Computable<String>) () -> manifest.getPackage().getValue());
     if (applicationId == null) {
-      throw new ApkProvisionException("No application id in merged manifest: " + deployInfo.getMergedManifestFile());
+      throw new ApkProvisionException(
+          "No application id in merged manifest: " + deployInfo.getMergedManifestFile());
     }
     return applicationId;
   }
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 d09065c..ff1e6b7 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
@@ -19,8 +19,10 @@
 import com.google.common.base.Strings;
 import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
 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;
+import com.google.idea.blaze.base.run.producers.BlazeRunConfigurationProducer;
 import com.google.idea.blaze.java.run.RunUtil;
-import com.google.idea.blaze.java.run.producers.BlazeTestRunConfigurationProducer;
 import com.google.idea.blaze.java.run.producers.JUnitConfigurationUtil;
 import com.google.idea.blaze.java.run.producers.ProducerUtils;
 import com.intellij.execution.JavaExecutionUtil;
@@ -35,21 +37,22 @@
 
 /**
  * Producer for run configurations related to Android test classes in Blaze.
- * <p/>
- * This class is based on
- * {@link org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer}.
+ *
+ * <p>This class is based on {@link
+ * org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer}.
  */
-public class BlazeAndroidTestClassRunConfigurationProducer extends BlazeTestRunConfigurationProducer<BlazeAndroidTestRunConfiguration> {
+public class BlazeAndroidTestClassRunConfigurationProducer
+    extends BlazeRunConfigurationProducer<BlazeCommandRunConfiguration> {
 
   public BlazeAndroidTestClassRunConfigurationProducer() {
-    super(BlazeAndroidTestRunConfigurationType.getInstance());
+    super(BlazeCommandRunConfigurationType.getInstance());
   }
 
   @Override
   protected boolean doSetupConfigFromContext(
-    @NotNull BlazeAndroidTestRunConfiguration configuration,
-    @NotNull ConfigurationContext context,
-    @NotNull Ref<PsiElement> sourceElement) {
+      @NotNull BlazeCommandRunConfiguration configuration,
+      @NotNull ConfigurationContext context,
+      @NotNull Ref<PsiElement> sourceElement) {
 
     final Location contextLocation = context.getLocation();
     assert contextLocation != null;
@@ -76,9 +79,14 @@
       return false;
     }
     configuration.setTarget(rule.label);
-    BlazeAndroidTestRunConfigurationState configState = configuration.getConfigState();
-    configState.TESTING_TYPE = AndroidTestRunConfiguration.TEST_CLASS;
-    configState.CLASS_NAME = testClass.getQualifiedName();
+    BlazeAndroidTestRunConfigurationHandler handler =
+        configuration.getHandlerIfType(BlazeAndroidTestRunConfigurationHandler.class);
+    if (handler == null) {
+      return false;
+    }
+    BlazeAndroidTestRunConfigurationState configState = handler.getConfigState();
+    configState.setTestingType(AndroidTestRunConfiguration.TEST_CLASS);
+    configState.setClassName(testClass.getQualifiedName());
     configuration.setGeneratedName();
 
     return true;
@@ -86,8 +94,7 @@
 
   @Override
   protected boolean doIsConfigFromContext(
-    @NotNull BlazeAndroidTestRunConfiguration configuration,
-    @NotNull ConfigurationContext context) {
+      @NotNull BlazeCommandRunConfiguration configuration, @NotNull ConfigurationContext context) {
 
     final Location contextLocation = context.getLocation();
     assert contextLocation != null;
@@ -114,13 +121,18 @@
   }
 
   private static boolean checkIfAttributesAreTheSame(
-    BlazeAndroidTestRunConfiguration configuration, PsiClass testClass) {
-    BlazeAndroidTestRunConfigurationState configState = configuration.getConfigState();
-    if (Strings.isNullOrEmpty(configState.CLASS_NAME)) {
+      BlazeCommandRunConfiguration configuration, PsiClass testClass) {
+    BlazeAndroidTestRunConfigurationHandler handler =
+        configuration.getHandlerIfType(BlazeAndroidTestRunConfigurationHandler.class);
+    if (handler == null) {
+      return false;
+    }
+    BlazeAndroidTestRunConfigurationState configState = handler.getConfigState();
+    if (Strings.isNullOrEmpty(configState.getClassName())) {
       return false;
     }
 
-    return configState.TESTING_TYPE == AndroidTestRunConfiguration.TEST_CLASS
-           && configState.CLASS_NAME.equals(testClass.getQualifiedName());
+    return configState.getTestingType() == AndroidTestRunConfiguration.TEST_CLASS
+        && configState.getClassName().equals(testClass.getQualifiedName());
   }
 }
diff --git a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestFilter.java b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestFilter.java
index 1b9d234..c6f4a3b 100644
--- a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestFilter.java
+++ b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestFilter.java
@@ -21,12 +21,11 @@
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-/**
- * A test filter specification for Android tests.
- */
+/** A test filter specification for Android tests. */
 final class BlazeAndroidTestFilter {
   // Specifies that Android tests should be filtered by name.
-  private static final String TEST_FILTER_BY_NAME = BlazeFlags.TEST_ARG + "--test_filter_spec=TEST_NAME";
+  private static final String TEST_FILTER_BY_NAME =
+      BlazeFlags.TEST_ARG + "--test_filter_spec=TEST_NAME";
   // As part of a name filter spec, the packages to include.
   private static final String TEST_PACKAGE_NAMES = BlazeFlags.TEST_ARG + "--test_package_names=";
   // As part of a name filter spec, the classes to include.
@@ -35,19 +34,15 @@
   private static final String TEST_METHOD_NAMES = BlazeFlags.TEST_ARG + "--test_method_full_names=";
 
   private final int testingType;
-  @Nullable
-  private final String className;
-  @Nullable
-  private final String methodName;
-  @Nullable
-  private final String packageName;
+  @Nullable private final String className;
+  @Nullable private final String methodName;
+  @Nullable private final String packageName;
 
   public BlazeAndroidTestFilter(
-    int testingType,
-    @Nullable String className,
-    @Nullable String methodName,
-    @Nullable String packageName
-  ) {
+      int testingType,
+      @Nullable String className,
+      @Nullable String methodName,
+      @Nullable String packageName) {
     this.testingType = testingType;
     this.className = className;
     this.methodName = methodName;
diff --git a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestLaunchTask.java b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestLaunchTask.java
index f7d0a9e..7283e53 100644
--- a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestLaunchTask.java
+++ b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestLaunchTask.java
@@ -28,6 +28,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.filecache.FileCaches;
 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.ProjectViewManager;
@@ -37,26 +38,27 @@
 import com.google.idea.blaze.base.scope.ScopedFunction;
 import com.google.idea.blaze.base.scope.output.IssueOutput;
 import com.google.idea.blaze.base.settings.Blaze;
+import com.google.idea.blaze.base.util.SaveUtil;
 import com.intellij.execution.process.ProcessAdapter;
 import com.intellij.execution.process.ProcessEvent;
 import com.intellij.execution.process.ProcessHandler;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.project.Project;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.ide.PooledThreadExecutor;
-
 import java.util.List;
 import java.util.concurrent.Callable;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.ExecutionException;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.ide.PooledThreadExecutor;
 
 /**
- * An Android application launcher that invokes `blaze test` on an android_test target, and sets
- * up process handling and debugging for the test run.
+ * An Android application launcher that invokes `blaze test` on an android_test target, and sets up
+ * process handling and debugging for the test run.
  */
 class BlazeAndroidTestLaunchTask implements LaunchTask {
   // Uses a local device/emulator attached to adb to run an android_test.
-  public static final String TEST_LOCAL_DEVICE = BlazeFlags.TEST_ARG + "--device_broker_type=LOCAL_ADB_SERVER";
+  public static final String TEST_LOCAL_DEVICE =
+      BlazeFlags.TEST_ARG + "--device_broker_type=LOCAL_ADB_SERVER";
   // Uses a local device/emulator attached to adb to run an android_test.
   public static final String TEST_DEBUG = BlazeFlags.TEST_ARG + "--enable_debug";
   // Specifies the serial number for a local test device.
@@ -75,13 +77,12 @@
   private final boolean debug;
 
   public BlazeAndroidTestLaunchTask(
-    Project project,
-    Label target,
-    List<String> buildFlags,
-    BlazeAndroidTestFilter testFilter,
-    BlazeAndroidTestRunContext runContext,
-    boolean debug
-  ) {
+      Project project,
+      Label target,
+      List<String> buildFlags,
+      BlazeAndroidTestFilter testFilter,
+      BlazeAndroidTestRunContext runContext,
+      boolean debug) {
     this.project = project;
     this.target = target;
     this.buildFlags = buildFlags;
@@ -102,65 +103,83 @@
   }
 
   @Override
-  public boolean perform(@NotNull IDevice device, @NotNull LaunchStatus launchStatus, @NotNull ConsolePrinter printer) {
+  public boolean perform(
+      @NotNull IDevice device,
+      @NotNull LaunchStatus launchStatus,
+      @NotNull ConsolePrinter printer) {
     BlazeExecutor executor = BlazeExecutor.getInstance();
 
-    ProcessHandlerLaunchStatus processHandlerLaunchStatus = (ProcessHandlerLaunchStatus)launchStatus;
+    ProcessHandlerLaunchStatus processHandlerLaunchStatus =
+        (ProcessHandlerLaunchStatus) launchStatus;
     final ProcessHandler processHandler = processHandlerLaunchStatus.getProcessHandler();
 
-    blazeResult = executor.submit(new Callable<Boolean>() {
-      @Override
-      public Boolean call() throws Exception {
-        return Scope.root(new ScopedFunction<Boolean>() {
-          @Override
-          public Boolean execute(@NotNull BlazeContext context) {
-            ProjectViewSet projectViewSet = ProjectViewManager.getInstance(project).getProjectViewSet();
-            if (projectViewSet == null) {
-              IssueOutput.error("Could not load project view. Please resync project.").submit(context);
-              return false;
-            }
+    blazeResult =
+        executor.submit(
+            new Callable<Boolean>() {
+              @Override
+              public Boolean call() throws Exception {
+                return Scope.root(
+                    new ScopedFunction<Boolean>() {
+                      @Override
+                      public Boolean execute(@NotNull BlazeContext context) {
+                        ProjectViewSet projectViewSet =
+                            ProjectViewManager.getInstance(project).getProjectViewSet();
+                        if (projectViewSet == null) {
+                          IssueOutput.error("Could not load project view. Please resync project.")
+                              .submit(context);
+                          return false;
+                        }
 
-            BlazeCommand.Builder commandBuilder = BlazeCommand.builder(Blaze.getBuildSystem(project), BlazeCommandName.TEST)
-              .addTargets(target);
-            // Build flags must match BlazeBeforeRunTask.
-            commandBuilder
-              .addBlazeFlags(buildFlags);
-            // Run the test on the selected local device/emulator.
-            commandBuilder
-              .addBlazeFlags(TEST_LOCAL_DEVICE, BlazeFlags.TEST_OUTPUT_STREAMED)
-              .addBlazeFlags(testDeviceSerialFlags(device.getSerialNumber()))
-              .addBlazeFlags(testFilter.getBlazeFlags());
-            if (debug) {
-              commandBuilder.addBlazeFlags(TEST_DEBUG, BlazeFlags.NO_CACHE_TEST_RESULTS);
-            }
-            BlazeCommand command = commandBuilder.build();
+                        BlazeCommand.Builder commandBuilder =
+                            BlazeCommand.builder(
+                                    Blaze.getBuildSystem(project), BlazeCommandName.TEST)
+                                .addTargets(target);
+                        // Build flags must match BlazeBeforeRunTask.
+                        commandBuilder.addBlazeFlags(buildFlags);
+                        // Run the test on the selected local device/emulator.
+                        commandBuilder
+                            .addBlazeFlags(TEST_LOCAL_DEVICE, BlazeFlags.TEST_OUTPUT_STREAMED)
+                            .addBlazeFlags(testDeviceSerialFlags(device.getSerialNumber()))
+                            .addBlazeFlags(testFilter.getBlazeFlags());
+                        if (debug) {
+                          commandBuilder.addBlazeFlags(
+                              TEST_DEBUG, BlazeFlags.NO_CACHE_TEST_RESULTS);
+                        }
+                        BlazeCommand command = commandBuilder.build();
 
-            printer.stdout(String.format("Starting %s test...\n", Blaze.buildSystemName(project)));
-            printer.stdout(command + "\n");
-            LineProcessingOutputStream.LineProcessor stdoutLineProcessor = line -> {
-              printer.stdout(line);
-              return true;
-            };
-            LineProcessingOutputStream.LineProcessor stderrLineProcessor = line -> {
-              printer.stderr(line);
-              return true;
-            };
-            int retVal = ExternalTask.builder(WorkspaceRoot.fromProject(project), command)
-              .context(context)
-              .stdout(LineProcessingOutputStream.of(stdoutLineProcessor))
-              .stderr(LineProcessingOutputStream.of(stderrLineProcessor))
-              .build()
-              .run();
+                        printer.stdout(
+                            String.format("Starting %s test...\n", Blaze.buildSystemName(project)));
+                        printer.stdout(command + "\n");
+                        LineProcessingOutputStream.LineProcessor stdoutLineProcessor =
+                            line -> {
+                              printer.stdout(line);
+                              return true;
+                            };
+                        LineProcessingOutputStream.LineProcessor stderrLineProcessor =
+                            line -> {
+                              printer.stderr(line);
+                              return true;
+                            };
+                        SaveUtil.saveAllFiles();
+                        int retVal =
+                            ExternalTask.builder(WorkspaceRoot.fromProject(project))
+                                .addBlazeCommand(command)
+                                .context(context)
+                                .stdout(LineProcessingOutputStream.of(stdoutLineProcessor))
+                                .stderr(LineProcessingOutputStream.of(stderrLineProcessor))
+                                .build()
+                                .run();
+                        FileCaches.refresh(project);
 
-            if (retVal != 0) {
-              context.setHasError();
-            }
+                        if (retVal != 0) {
+                          context.setHasError();
+                        }
 
-            return !context.hasErrors();
-          }
-        });
-      }
-    });
+                        return !context.hasErrors();
+                      }
+                    });
+              }
+            });
 
     blazeResult.addListener(runContext::onLaunchTaskComplete, PooledThreadExecutor.INSTANCE);
 
@@ -172,36 +191,35 @@
   }
 
   /**
-   *  Hooks up the Blaze process to be killed if the user hits the 'Stop' button, then waits for
-   *  the Blaze process to stop.
-   *  In non-debug mode, we wait for test execution to finish before returning from launch()
-   *  (this matches the behavior of the stock ddmlib runner).
+   * Hooks up the Blaze process to be killed if the user hits the 'Stop' button, then waits for the
+   * Blaze process to stop. In non-debug mode, we wait for test execution to finish before returning
+   * from launch() (this matches the behavior of the stock ddmlib runner).
    */
-  private void waitAndSetUpForKillingBlazeOnStop(@NotNull final ProcessHandler processHandler, @NotNull LaunchStatus launchStatus) {
-    processHandler.addProcessListener(new ProcessAdapter() {
-      @Override
-      public void processWillTerminate(ProcessEvent event, boolean willBeDestroyed) {
-        blazeResult.cancel(true /* mayInterruptIfRunning */);
-        launchStatus.terminateLaunch("Test run stopped.\n");
-      }
-    });
+  private void waitAndSetUpForKillingBlazeOnStop(
+      @NotNull final ProcessHandler processHandler, @NotNull LaunchStatus launchStatus) {
+    processHandler.addProcessListener(
+        new ProcessAdapter() {
+          @Override
+          public void processWillTerminate(ProcessEvent event, boolean willBeDestroyed) {
+            blazeResult.cancel(true /* mayInterruptIfRunning */);
+            launchStatus.terminateLaunch("Test run stopped.\n");
+          }
+        });
 
     try {
       blazeResult.get();
       launchStatus.terminateLaunch("Tests ran to completion.\n");
-    }
-    catch (CancellationException e) {
+    } catch (CancellationException e) {
       // The user has canceled the test.
       launchStatus.terminateLaunch("Test run stopped.\n");
-    }
-    catch (InterruptedException e) {
+    } catch (InterruptedException e) {
       // We've been interrupted - cancel the underlying Blaze process.
       blazeResult.cancel(true /* mayInterruptIfRunning */);
       launchStatus.terminateLaunch("Test run stopped.\n");
-    }
-    catch (ExecutionException e) {
+    } catch (ExecutionException e) {
       LOG.error(e);
-      launchStatus.terminateLaunch("Test run stopped due to internal exception. Please file a bug report.\n");
+      launchStatus.terminateLaunch(
+          "Test run stopped due to internal exception. Please file a bug report.\n");
     }
   }
 
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 8de6656..7f87f1b 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
@@ -19,8 +19,10 @@
 import com.google.common.base.Strings;
 import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
 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;
+import com.google.idea.blaze.base.run.producers.BlazeRunConfigurationProducer;
 import com.google.idea.blaze.java.run.RunUtil;
-import com.google.idea.blaze.java.run.producers.BlazeTestRunConfigurationProducer;
 import com.google.idea.blaze.java.run.producers.JUnitConfigurationUtil;
 import com.google.idea.blaze.java.run.producers.ProducerUtils;
 import com.intellij.execution.Location;
@@ -33,21 +35,22 @@
 
 /**
  * Producer for run configurations related to Android test methods in Blaze.
- * <p/>
- * This class is based on
- * {@link org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer}.
+ *
+ * <p>This class is based on {@link
+ * org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer}.
  */
-public class BlazeAndroidTestMethodRunConfigurationProducer extends BlazeTestRunConfigurationProducer<BlazeAndroidTestRunConfiguration> {
+public class BlazeAndroidTestMethodRunConfigurationProducer
+    extends BlazeRunConfigurationProducer<BlazeCommandRunConfiguration> {
 
   public BlazeAndroidTestMethodRunConfigurationProducer() {
-    super(BlazeAndroidTestRunConfigurationType.getInstance());
+    super(BlazeCommandRunConfigurationType.getInstance());
   }
 
   @Override
   protected boolean doSetupConfigFromContext(
-    @NotNull BlazeAndroidTestRunConfiguration configuration,
-    @NotNull ConfigurationContext context,
-    @NotNull Ref<PsiElement> sourceElement) {
+      @NotNull BlazeCommandRunConfiguration configuration,
+      @NotNull ConfigurationContext context,
+      @NotNull Ref<PsiElement> sourceElement) {
 
     if (JUnitConfigurationUtil.isMultipleElementsSelected(context)) {
       return false;
@@ -76,10 +79,15 @@
       return false;
     }
     configuration.setTarget(rule.label);
-    BlazeAndroidTestRunConfigurationState configState = configuration.getConfigState();
-    configState.TESTING_TYPE = AndroidTestRunConfiguration.TEST_METHOD;
-    configState.CLASS_NAME = containingClass.getQualifiedName();
-    configState.METHOD_NAME = psiMethod.getName();
+    BlazeAndroidTestRunConfigurationHandler handler =
+        configuration.getHandlerIfType(BlazeAndroidTestRunConfigurationHandler.class);
+    if (handler == null) {
+      return false;
+    }
+    BlazeAndroidTestRunConfigurationState configState = handler.getConfigState();
+    configState.setTestingType(AndroidTestRunConfiguration.TEST_METHOD);
+    configState.setClassName(containingClass.getQualifiedName());
+    configState.setMethodName(psiMethod.getName());
     configuration.setGeneratedName();
 
     return true;
@@ -87,8 +95,7 @@
 
   @Override
   protected boolean doIsConfigFromContext(
-    @NotNull BlazeAndroidTestRunConfiguration configuration,
-    @NotNull ConfigurationContext context) {
+      @NotNull BlazeCommandRunConfiguration configuration, @NotNull ConfigurationContext context) {
 
     if (JUnitConfigurationUtil.isMultipleElementsSelected(context)) {
       return false;
@@ -111,16 +118,21 @@
     return checkIfAttributesAreTheSame(configuration, psiMethod);
   }
 
-  private static boolean checkIfAttributesAreTheSame(BlazeAndroidTestRunConfiguration configuration,
-                                                     PsiMethod testMethod) {
-    BlazeAndroidTestRunConfigurationState configState = configuration.getConfigState();
-    if (Strings.isNullOrEmpty(configState.CLASS_NAME)
-        || Strings.isNullOrEmpty(configState.METHOD_NAME)) {
+  private static boolean checkIfAttributesAreTheSame(
+      BlazeCommandRunConfiguration configuration, PsiMethod testMethod) {
+    BlazeAndroidTestRunConfigurationHandler handler =
+        configuration.getHandlerIfType(BlazeAndroidTestRunConfigurationHandler.class);
+    if (handler == null) {
+      return false;
+    }
+    BlazeAndroidTestRunConfigurationState configState = handler.getConfigState();
+    if (Strings.isNullOrEmpty(configState.getClassName())
+        || Strings.isNullOrEmpty(configState.getMethodName())) {
       return false;
     }
 
-    return configState.TESTING_TYPE == AndroidTestRunConfiguration.TEST_METHOD
-           && configState.CLASS_NAME.equals(testMethod.getContainingClass().getQualifiedName())
-           && configState.METHOD_NAME.equals(testMethod.getName());
+    return configState.getTestingType() == AndroidTestRunConfiguration.TEST_METHOD
+        && configState.getClassName().equals(testMethod.getContainingClass().getQualifiedName())
+        && configState.getMethodName().equals(testMethod.getName());
   }
 }
diff --git a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfiguration.java b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfiguration.java
deleted file mode 100644
index 1e01d20..0000000
--- a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfiguration.java
+++ /dev/null
@@ -1,214 +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.run.test;
-
-import com.android.tools.idea.run.ValidationError;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Ordering;
-import com.google.idea.blaze.android.run.BlazeAndroidRunConfiguration;
-import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationCommonState;
-import com.google.idea.blaze.android.run.runner.BlazeAndroidRunConfigurationRunner;
-import com.google.idea.blaze.android.run.runner.BlazeAndroidRunContext;
-import com.google.idea.blaze.android.sync.projectstructure.BlazeAndroidProjectStructureSyncer;
-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.google.idea.blaze.base.run.rulefinder.RuleFinder;
-import com.google.idea.blaze.base.settings.Blaze;
-import com.intellij.execution.ExecutionException;
-import com.intellij.execution.Executor;
-import com.intellij.execution.JavaExecutionUtil;
-import com.intellij.execution.configurations.*;
-import com.intellij.execution.runners.ExecutionEnvironment;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.module.Module;
-import com.intellij.openapi.options.SettingsEditor;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.Comparing;
-import com.intellij.openapi.util.InvalidDataException;
-import com.intellij.openapi.util.WriteExternalException;
-import org.jdom.Element;
-import org.jetbrains.android.facet.AndroidFacet;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.util.List;
-
-/**
- * An extension of the normal Android Studio run configuration for launching Android tests,
- * adapted specifically for selecting and launching android_test targets.
- */
-public final class BlazeAndroidTestRunConfiguration extends LocatableConfigurationBase
-  implements BlazeAndroidRunConfiguration, RunConfiguration {
-
-  private static final Logger LOG = Logger.getInstance(BlazeAndroidTestRunConfiguration.class);
-  private final Project project;
-  private final BlazeAndroidRunConfigurationCommonState commonState;
-  private final BlazeAndroidRunConfigurationRunner runner;
-
-  @NotNull private final BlazeAndroidTestRunConfigurationState configState = new BlazeAndroidTestRunConfigurationState();
-
-  public BlazeAndroidTestRunConfiguration(Project project, ConfigurationFactory factory) {
-    super(project, factory, "");
-    this.project = project;
-
-    RuleIdeInfo rule = RuleFinder.getInstance().firstRuleOfKinds(project, Kind.ANDROID_TEST);
-    this.commonState = new BlazeAndroidRunConfigurationCommonState(rule != null ? rule.label : null, ImmutableList.of());
-    this.runner = new BlazeAndroidRunConfigurationRunner(project, this, commonState, true, getUniqueID());
-  }
-
-  @Override
-  public BlazeAndroidRunConfigurationCommonState getCommonState() {
-    return commonState;
-  }
-
-  @NotNull
-  public BlazeAndroidTestRunConfigurationState getConfigState() {
-    return configState;
-  }
-
-  @Nullable
-  @Override
-  public final Label getTarget() {
-    return commonState.getTarget();
-  }
-
-  public final void setTarget(@Nullable Label target) {
-    commonState.setTarget(target);
-  }
-
-  @Override
-  public BlazeAndroidRunConfigurationRunner getRunner() {
-    return runner;
-  }
-
-  @Override
-  public BlazeAndroidRunContext createRunContext(Project project,
-                                                 AndroidFacet facet,
-                                                 ExecutionEnvironment env,
-                                                 ImmutableList<String> buildFlags) {
-    return new BlazeAndroidTestRunContext(project, facet, this, env, commonState, configState, buildFlags);
-  }
-
-  @NotNull
-  @Override
-  public SettingsEditor<? extends RunConfiguration> getConfigurationEditor() {
-    return new BlazeAndroidTestRunConfigurationEditor(
-      getProject(),
-      new BlazeAndroidTestRunConfigurationStateEditor(project)
-    );
-  }
-
-  @Override
-  public final void checkConfiguration() throws RuntimeConfigurationException {
-    List<ValidationError> errors = validate();
-    if (errors.isEmpty()) {
-      return;
-    }
-    ValidationError topError = Ordering.natural().max(errors);
-    if (topError.isFatal()) {
-      throw new RuntimeConfigurationError(topError.getMessage(), topError.getQuickfix());
-    }
-    throw new RuntimeConfigurationWarning(topError.getMessage(), topError.getQuickfix());
-  }
-
-  private List<ValidationError> validate() {
-    List<ValidationError> errors = Lists.newArrayList();
-    errors.addAll(runner.validate(getModule()));
-    commonState.checkConfiguration(getProject(), Kind.ANDROID_TEST, errors);
-    return errors;
-  }
-
-  @Override
-  @Nullable
-  public final RunProfileState getState(@NotNull final Executor executor, @NotNull ExecutionEnvironment env) throws ExecutionException {
-    final Module module = getModule();
-    return runner.getState(module, executor, env);
-  }
-
-  private Module getModule() {
-    return BlazeAndroidProjectStructureSyncer.ensureRunConfigurationModule(project, getTarget());
-  }
-
-  @Override
-  @Nullable
-  public String suggestedName() {
-    Label target = commonState.getTarget();
-    if (target == null) {
-      return null;
-    }
-    String name = target.ruleName().toString();
-    if (configState.TESTING_TYPE == BlazeAndroidTestRunConfigurationState.TEST_CLASS) {
-      name += ": " + configState.CLASS_NAME;
-    }
-    else if (configState.TESTING_TYPE == BlazeAndroidTestRunConfigurationState.TEST_METHOD) {
-      name += ": " + configState.CLASS_NAME + "#" + configState.METHOD_NAME;
-    }
-    return String.format("%s test: %s", Blaze.buildSystemName(project), name);
-  }
-
-  @Override
-  public boolean isGeneratedName() {
-    final String name = getName();
-
-    if ((configState.TESTING_TYPE == BlazeAndroidTestRunConfigurationState.TEST_CLASS || configState.TESTING_TYPE == BlazeAndroidTestRunConfigurationState.TEST_METHOD) &&
-        (configState.CLASS_NAME == null || configState.CLASS_NAME.length() == 0)) {
-      return JavaExecutionUtil.isNewName(name);
-    }
-    if (configState.TESTING_TYPE == BlazeAndroidTestRunConfigurationState.TEST_METHOD &&
-        (configState.METHOD_NAME == null || configState.METHOD_NAME.length() == 0)) {
-      return JavaExecutionUtil.isNewName(name);
-    }
-    return Comparing.equal(name, suggestedName());
-  }
-
-  @Override
-  public void readExternal(Element element) throws InvalidDataException {
-    super.readExternal(element);
-
-    commonState.readExternal(element);
-    runner.readExternal(element);;
-    configState.readExternal(element);
-  }
-
-  @Override
-  public void writeExternal(Element element) throws WriteExternalException {
-    super.writeExternal(element);
-
-    commonState.writeExternal(element);
-    runner.writeExternal(element);;
-    configState.writeExternal(element);
-  }
-
-  @Override
-  public RunConfiguration clone() {
-    final Element element = new Element("dummy");
-    try {
-      writeExternal(element);
-      BlazeAndroidTestRunConfiguration clone = new BlazeAndroidTestRunConfiguration(
-        getProject(), getFactory());
-      clone.readExternal(element);
-      return clone;
-    } catch (InvalidDataException e) {
-      LOG.error(e);
-      return null;
-    } catch (WriteExternalException e) {
-      LOG.error(e);
-      return null;
-    }
-  }
-}
diff --git a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationEditor.java b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationEditor.java
deleted file mode 100644
index 34f3fcb..0000000
--- a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationEditor.java
+++ /dev/null
@@ -1,68 +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.run.test;
-
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationCommonStateEditor;
-import com.google.idea.blaze.base.model.primitives.Kind;
-import com.google.idea.blaze.base.ui.UiUtil;
-import com.intellij.openapi.options.ConfigurationException;
-import com.intellij.openapi.options.SettingsEditor;
-import com.intellij.openapi.project.Project;
-import org.jetbrains.annotations.NotNull;
-
-import javax.swing.*;
-import java.awt.*;
-import java.util.List;
-
-/**
- * A simplified, Blaze-specific variant of
- * {@link org.jetbrains.android.run.AndroidRunConfigurationEditor}.
- */
-class BlazeAndroidTestRunConfigurationEditor extends SettingsEditor<BlazeAndroidTestRunConfiguration> {
-
-  private final BlazeAndroidTestRunConfigurationStateEditor kindSpecificEditor;
-  private final BlazeAndroidRunConfigurationCommonStateEditor commonStateEditor;
-
-  public BlazeAndroidTestRunConfigurationEditor(
-    Project project,
-    BlazeAndroidTestRunConfigurationStateEditor kindSpecificEditor) {
-    this.kindSpecificEditor = kindSpecificEditor;
-    this.commonStateEditor = new BlazeAndroidRunConfigurationCommonStateEditor(project, Kind.ANDROID_TEST);
-  }
-
-  @Override
-  protected void resetEditorFrom(BlazeAndroidTestRunConfiguration configuration) {
-    commonStateEditor.resetEditorFrom(configuration.getCommonState());
-    kindSpecificEditor.resetFrom(configuration);
-  }
-
-  @Override
-  protected void applyEditorTo(@NotNull BlazeAndroidTestRunConfiguration configuration)
-    throws ConfigurationException {
-    commonStateEditor.applyEditorTo(configuration.getCommonState());
-    kindSpecificEditor.applyTo(configuration);
-  }
-
-  @Override
-  @NotNull
-  protected JComponent createEditor() {
-    List<Component> components = Lists.newArrayList();
-    components.addAll(commonStateEditor.getComponents());
-    components.add(kindSpecificEditor.getComponent());
-    return UiUtil.createBox(components);
-  }
-}
diff --git a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationHandler.java b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationHandler.java
new file mode 100644
index 0000000..885a8ae
--- /dev/null
+++ b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationHandler.java
@@ -0,0 +1,270 @@
+/*
+ * 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 com.android.tools.idea.run.ValidationError;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Ordering;
+import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationCommonState;
+import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationHandler;
+import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationHandlerEditor;
+import com.google.idea.blaze.android.run.runner.BlazeAndroidRunConfigurationRunner;
+import com.google.idea.blaze.android.run.runner.BlazeAndroidRunContext;
+import com.google.idea.blaze.android.sync.projectstructure.BlazeAndroidProjectStructureSyncer;
+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.google.idea.blaze.base.model.primitives.TargetExpression;
+import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
+import com.google.idea.blaze.base.run.BlazeConfigurationNameBuilder;
+import com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationHandlerEditor;
+import com.google.idea.blaze.base.run.rulefinder.RuleFinder;
+import com.google.idea.blaze.base.settings.Blaze;
+import com.intellij.execution.ExecutionException;
+import com.intellij.execution.Executor;
+import com.intellij.execution.JavaExecutionUtil;
+import com.intellij.execution.configurations.RunConfiguration;
+import com.intellij.execution.configurations.RunProfileState;
+import com.intellij.execution.configurations.RuntimeConfigurationError;
+import com.intellij.execution.configurations.RuntimeConfigurationException;
+import com.intellij.execution.configurations.RuntimeConfigurationWarning;
+import com.intellij.execution.runners.ExecutionEnvironment;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Comparing;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.WriteExternalException;
+import java.util.List;
+import javax.swing.Icon;
+import org.jdom.Element;
+import org.jetbrains.android.facet.AndroidFacet;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * {@link com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationHandler} for
+ * android_test targets.
+ */
+public class BlazeAndroidTestRunConfigurationHandler
+    implements BlazeAndroidRunConfigurationHandler {
+  private static final Logger LOG =
+      Logger.getInstance(BlazeAndroidTestRunConfigurationHandler.class);
+
+  private final BlazeCommandRunConfiguration configuration;
+  private final BlazeAndroidRunConfigurationCommonState commonState;
+  private final BlazeAndroidTestRunConfigurationState configState;
+  private final BlazeAndroidRunConfigurationRunner runner;
+
+  BlazeAndroidTestRunConfigurationHandler(BlazeCommandRunConfiguration configuration) {
+    this.configuration = configuration;
+    commonState = new BlazeAndroidRunConfigurationCommonState(ImmutableList.of());
+    configState = new BlazeAndroidTestRunConfigurationState();
+    runner =
+        new BlazeAndroidRunConfigurationRunner(
+            configuration.getProject(), this, commonState, true, configuration.getUniqueID());
+  }
+
+  @Override
+  public BlazeAndroidRunContext createRunContext(
+      Project project,
+      AndroidFacet facet,
+      ExecutionEnvironment env,
+      ImmutableList<String> buildFlags) {
+    return new BlazeAndroidTestRunContext(
+        project, facet, configuration, env, configState, getLabel(), buildFlags);
+  }
+
+  @Override
+  @Nullable
+  public Label getLabel() {
+    TargetExpression target = configuration.getTarget();
+    if (target instanceof Label) {
+      return (Label) target;
+    }
+    return null;
+  }
+
+  @Override
+  public BlazeAndroidRunConfigurationCommonState getCommonState() {
+    return commonState;
+  }
+
+  @Override
+  public BlazeAndroidTestRunConfigurationState getConfigState() {
+    return configState;
+  }
+
+  @Nullable
+  private Module getModule() {
+    Label target = getLabel();
+    return target != null
+        ? BlazeAndroidProjectStructureSyncer.ensureRunConfigurationModule(
+            configuration.getProject(), target)
+        : null;
+  }
+
+  @Override
+  public final void checkConfiguration() throws RuntimeConfigurationException {
+    List<ValidationError> errors = validate();
+    if (errors.isEmpty()) {
+      return;
+    }
+    // TODO: Do something with the extra error information? Error count?
+    ValidationError topError = Ordering.natural().max(errors);
+    if (topError.isFatal()) {
+      throw new RuntimeConfigurationError(topError.getMessage(), topError.getQuickfix());
+    }
+    throw new RuntimeConfigurationWarning(topError.getMessage(), topError.getQuickfix());
+  }
+
+  private List<ValidationError> validate() {
+    List<ValidationError> errors = Lists.newArrayList();
+    errors.addAll(runner.validate(getModule()));
+    validateLabel(errors);
+    return errors;
+  }
+
+  private void validateLabel(List<ValidationError> errors) {
+    Project project = configuration.getProject();
+    Label target = getLabel();
+    Kind kind = Kind.ANDROID_TEST;
+    RuleIdeInfo rule =
+        target != null ? RuleFinder.getInstance().ruleForTarget(project, target) : null;
+    if (rule == null) {
+      errors.add(
+          ValidationError.fatal(
+              String.format("No existing %s rule selected.", Blaze.buildSystemName(project))));
+    } else if (!rule.kindIsOneOf(kind)) {
+      errors.add(
+          ValidationError.fatal(
+              String.format(
+                  "Selected %s rule is not %s", Blaze.buildSystemName(project), kind.toString())));
+    }
+  }
+
+  @Override
+  public void readExternal(Element element) throws InvalidDataException {
+    commonState.readExternal(element);
+    runner.readExternal(element);
+    configState.readExternal(element);
+  }
+
+  @Override
+  public void writeExternal(Element element) throws WriteExternalException {
+    commonState.writeExternal(element);
+    runner.writeExternal(element);
+    configState.writeExternal(element);
+  }
+
+  @Override
+  public BlazeAndroidTestRunConfigurationHandler cloneFor(
+      BlazeCommandRunConfiguration configuration) {
+    final Element element = new Element("dummy");
+    try {
+      writeExternal(element);
+      final BlazeAndroidTestRunConfigurationHandler handler =
+          new BlazeAndroidTestRunConfigurationHandler(configuration);
+      handler.readExternal(element);
+      return handler;
+    } catch (InvalidDataException | WriteExternalException e) {
+      LOG.error(e);
+      return null;
+    }
+  }
+
+  @Override
+  @Nullable
+  public final RunProfileState getState(
+      @NotNull final Executor executor, @NotNull ExecutionEnvironment env)
+      throws ExecutionException {
+    final Module module = getModule();
+    return runner.getState(module, executor, env);
+  }
+
+  @Override
+  public boolean executeBeforeRunTask(ExecutionEnvironment environment) {
+    return runner.executeBuild(environment);
+  }
+
+  @Override
+  @Nullable
+  public String suggestedName() {
+    Label target = getLabel();
+    if (target == null) {
+      return null;
+    }
+    BlazeConfigurationNameBuilder nameBuilder = new BlazeConfigurationNameBuilder(configuration);
+
+    boolean isClassTest =
+        configState.getTestingType() == BlazeAndroidTestRunConfigurationState.TEST_CLASS;
+    boolean isMethodTest =
+        configState.getTestingType() == BlazeAndroidTestRunConfigurationState.TEST_METHOD;
+    if ((isClassTest || isMethodTest) && configState.getClassName() != null) {
+      // Get the class name without the package.
+      String className = JavaExecutionUtil.getPresentableClassName(configState.getClassName());
+      if (className != null) {
+        String targetString = className;
+        if (isMethodTest) {
+          targetString += "#" + configState.getMethodName();
+        }
+        return nameBuilder.setTargetString(targetString).build();
+      }
+    }
+    return nameBuilder.build();
+  }
+
+  @Override
+  public boolean isGeneratedName(boolean hasGeneratedFlag) {
+    final String name = configuration.getName();
+
+    if ((configState.getTestingType() == BlazeAndroidTestRunConfigurationState.TEST_CLASS
+            || configState.getTestingType() == BlazeAndroidTestRunConfigurationState.TEST_METHOD)
+        && (configState.getClassName() == null || configState.getClassName().length() == 0)) {
+      return JavaExecutionUtil.isNewName(name);
+    }
+    if (configState.getTestingType() == BlazeAndroidTestRunConfigurationState.TEST_METHOD
+        && (configState.getMethodName() == null || configState.getMethodName().length() == 0)) {
+      return JavaExecutionUtil.isNewName(name);
+    }
+    return Comparing.equal(name, suggestedName());
+  }
+
+  @Override
+  @Nullable
+  public String getCommandName() {
+    return "test";
+  }
+
+  @Override
+  public String getHandlerName() {
+    return "Android Test Handler";
+  }
+
+  @Override
+  @Nullable
+  public Icon getExecutorIcon(RunConfiguration configuration, Executor executor) {
+    return null;
+  }
+
+  @Override
+  public BlazeCommandRunConfigurationHandlerEditor getHandlerEditor() {
+    Project project = configuration.getProject();
+    return new BlazeAndroidRunConfigurationHandlerEditor(
+        project, new BlazeAndroidTestRunConfigurationStateEditor(project));
+  }
+}
diff --git a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationHandlerProvider.java b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationHandlerProvider.java
new file mode 100644
index 0000000..bef595f
--- /dev/null
+++ b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationHandlerProvider.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.android.run.test;
+
+import com.google.idea.blaze.base.model.primitives.Kind;
+import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
+import com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationHandler;
+import com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationHandlerProvider;
+
+/** Handler provider for android_test targets. */
+public class BlazeAndroidTestRunConfigurationHandlerProvider
+    implements BlazeCommandRunConfigurationHandlerProvider {
+
+  public static BlazeAndroidTestRunConfigurationHandlerProvider getInstance() {
+    return BlazeCommandRunConfigurationHandlerProvider.EP_NAME.findExtension(
+        BlazeAndroidTestRunConfigurationHandlerProvider.class);
+  }
+
+  @Override
+  public boolean canHandleKind(Kind kind) {
+    return kind == Kind.ANDROID_TEST;
+  }
+
+  @Override
+  public BlazeCommandRunConfigurationHandler createHandler(BlazeCommandRunConfiguration config) {
+    return new BlazeAndroidTestRunConfigurationHandler(config);
+  }
+
+  @Override
+  public String getId() {
+    return "BlazeAndroidTestRunConfigurationHandlerProvider";
+  }
+}
diff --git a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationState.java b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationState.java
index 91236aa..9f05bc5 100644
--- a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationState.java
+++ b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationState.java
@@ -15,17 +15,17 @@
  */
 package com.google.idea.blaze.android.run.test;
 
-import com.intellij.openapi.util.DefaultJDOMExternalizer;
+import com.google.common.base.Strings;
+import com.google.common.collect.Maps;
+import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationState;
 import com.intellij.openapi.util.InvalidDataException;
-import com.intellij.openapi.util.JDOMExternalizable;
 import com.intellij.openapi.util.WriteExternalException;
+import java.util.Map;
 import org.jdom.Element;
 import org.jetbrains.annotations.Contract;
 
-/**
- * State specific for the android test configuration.
- */
-final class BlazeAndroidTestRunConfigurationState implements JDOMExternalizable {
+/** State specific for the android test configuration. */
+final class BlazeAndroidTestRunConfigurationState implements BlazeAndroidRunConfigurationState {
 
   private static final String RUN_THROUGH_BLAZE_ATTR = "blaze-run-through-blaze";
 
@@ -34,20 +34,34 @@
   public static final int TEST_CLASS = 2;
   public static final int TEST_METHOD = 3;
 
-  // We reinterpret Android Studio's test mode for running "all tests in a module" (all the tests in the installed test APK) as running all
+  // We reinterpret Android Studio's test mode for running "all tests in a module"
+  // (all the tests in the installed test APK) as running all
   // the tests in a rule.
   public static final int TEST_ALL_IN_TARGET = TEST_ALL_IN_MODULE;
 
-  public int TESTING_TYPE = TEST_ALL_IN_MODULE;
-  public String INSTRUMENTATION_RUNNER_CLASS = InstrumentationRunnerProvider.getDefaultInstrumentationRunnerClass();
-  public String METHOD_NAME = "";
-  public String CLASS_NAME = "";
-  public String PACKAGE_NAME = "";
-  public String EXTRA_OPTIONS = "";
+  private static final String TESTING_TYPE = "TESTING_TYPE";
+  private static final String INSTRUMENTATION_RUNNER_CLASS = "INSTRUMENTATION_RUNNER_CLASS";
+  private static final String METHOD_NAME = "METHOD_NAME";
+  private static final String CLASS_NAME = "CLASS_NAME";
+  private static final String PACKAGE_NAME = "PACKAGE_NAME";
+  private static final String EXTRA_OPTIONS = "EXTRA_OPTIONS";
+
+  private int testingType = TEST_ALL_IN_MODULE;
+  private String instrumentationRunnerClass;
+  private String methodName = "";
+  private String className = "";
+  private String packageName = "";
+  private String extraOptions = "";
 
   // Whether to delegate to 'blaze test'.
   private boolean runThroughBlaze;
 
+  public BlazeAndroidTestRunConfigurationState() {
+    String defaultInstrumentationRunnerClass =
+        InstrumentationRunnerProvider.getDefaultInstrumentationRunnerClass();
+    instrumentationRunnerClass = Strings.nullToEmpty(defaultInstrumentationRunnerClass);
+  }
+
   @Contract(pure = true)
   boolean isRunThroughBlaze() {
     return runThroughBlaze;
@@ -57,17 +71,116 @@
     this.runThroughBlaze = runThroughBlaze;
   }
 
+  public int getTestingType() {
+    return testingType;
+  }
+
+  public void setTestingType(int testingType) {
+    this.testingType = testingType;
+  }
+
+  public String getInstrumentationRunnerClass() {
+    return instrumentationRunnerClass;
+  }
+
+  public void setInstrumentationRunnerClass(String instrumentationRunnerClass) {
+    this.instrumentationRunnerClass = instrumentationRunnerClass;
+  }
+
+  public String getMethodName() {
+    return methodName;
+  }
+
+  public void setMethodName(String methodName) {
+    this.methodName = methodName;
+  }
+
+  public String getClassName() {
+    return className;
+  }
+
+  public void setClassName(String className) {
+    this.className = className;
+  }
+
+  public String getPackageName() {
+    return packageName;
+  }
+
+  public void setPackageName(String packageName) {
+    this.packageName = packageName;
+  }
+
+  public String getExtraOptions() {
+    return extraOptions;
+  }
+
+  public void setExtraOptions(String extraOptions) {
+    this.extraOptions = extraOptions;
+  }
+
   @Override
   public void readExternal(Element element) throws InvalidDataException {
-    DefaultJDOMExternalizer.readExternal(this, element);
-
+    String testingTypeAttribute = element.getAttributeValue(TESTING_TYPE);
+    if (!Strings.isNullOrEmpty(testingTypeAttribute)) {
+      testingType = Integer.parseInt(testingTypeAttribute);
+    }
+    instrumentationRunnerClass =
+        Strings.nullToEmpty(element.getAttributeValue(INSTRUMENTATION_RUNNER_CLASS));
+    methodName = Strings.nullToEmpty(element.getAttributeValue(METHOD_NAME));
+    className = Strings.nullToEmpty(element.getAttributeValue(CLASS_NAME));
+    packageName = Strings.nullToEmpty(element.getAttributeValue(PACKAGE_NAME));
+    extraOptions = Strings.nullToEmpty(element.getAttributeValue(EXTRA_OPTIONS));
     runThroughBlaze = Boolean.parseBoolean(element.getAttributeValue(RUN_THROUGH_BLAZE_ATTR));
+
+    for (Map.Entry<String, String> entry : getLegacyValues(element).entrySet()) {
+      String value = entry.getValue();
+      switch (entry.getKey()) {
+        case TESTING_TYPE:
+          if (!Strings.isNullOrEmpty(value)) {
+            testingType = Integer.parseInt(value);
+          }
+          break;
+        case INSTRUMENTATION_RUNNER_CLASS:
+          instrumentationRunnerClass = Strings.nullToEmpty(value);
+          break;
+        case METHOD_NAME:
+          methodName = Strings.nullToEmpty(value);
+          break;
+        case CLASS_NAME:
+          className = Strings.nullToEmpty(value);
+          break;
+        case PACKAGE_NAME:
+          packageName = Strings.nullToEmpty(value);
+          break;
+        case EXTRA_OPTIONS:
+          extraOptions = Strings.nullToEmpty(value);
+          break;
+        default:
+          break;
+      }
+    }
   }
 
   @Override
   public void writeExternal(Element element) throws WriteExternalException {
-    DefaultJDOMExternalizer.writeExternal(this, element);
-
     element.setAttribute(RUN_THROUGH_BLAZE_ATTR, Boolean.toString(runThroughBlaze));
+    element.setAttribute(TESTING_TYPE, Integer.toString(testingType));
+    element.setAttribute(INSTRUMENTATION_RUNNER_CLASS, instrumentationRunnerClass);
+    element.setAttribute(METHOD_NAME, methodName);
+    element.setAttribute(CLASS_NAME, className);
+    element.setAttribute(PACKAGE_NAME, packageName);
+    element.setAttribute(EXTRA_OPTIONS, extraOptions);
+  }
+
+  /** Imports legacy values in the old reflective JDOM externalizer manner. Can be removed ~2.0+. */
+  private static Map<String, String> getLegacyValues(Element element) {
+    Map<String, String> result = Maps.newHashMap();
+    for (Element option : element.getChildren("option")) {
+      String name = option.getAttributeValue("name");
+      String value = option.getAttributeValue("value");
+      result.put(name, value);
+    }
+    return result;
   }
 }
diff --git a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationStateEditor.java b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationStateEditor.java
index 265bf2f..d03f883 100644
--- a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationStateEditor.java
+++ b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationStateEditor.java
@@ -16,7 +16,13 @@
 
 package com.google.idea.blaze.android.run.test;
 
-import com.android.tools.idea.run.ConfigurationSpecificEditor;
+import static com.android.tools.idea.run.testing.AndroidTestRunConfiguration.TEST_ALL_IN_MODULE;
+import static com.android.tools.idea.run.testing.AndroidTestRunConfiguration.TEST_ALL_IN_PACKAGE;
+import static com.android.tools.idea.run.testing.AndroidTestRunConfiguration.TEST_CLASS;
+import static com.android.tools.idea.run.testing.AndroidTestRunConfiguration.TEST_METHOD;
+
+import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationState;
+import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationStateEditor;
 import com.google.idea.blaze.base.settings.Blaze;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.ui.LabeledComponent;
@@ -25,23 +31,24 @@
 import com.intellij.uiDesigner.core.GridConstraints;
 import com.intellij.uiDesigner.core.GridLayoutManager;
 import com.intellij.uiDesigner.core.Spacer;
-import org.jetbrains.annotations.NotNull;
-
-import javax.swing.*;
-import java.awt.*;
+import java.awt.Component;
+import java.awt.Insets;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.util.ResourceBundle;
-
-import static com.android.tools.idea.run.testing.AndroidTestRunConfiguration.*;
-
+import javax.swing.AbstractButton;
+import javax.swing.ButtonGroup;
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
 
 /**
- * The part of the Blaze Android test configuration editor that allows the user to pick an
- * android_test target and test filters.
+ * The part of the Blaze Android Test handler editor that allows the user to pick test filters.
  * Forked from {@link org.jetbrains.android.run.testing.TestRunParameters}.
  */
-class BlazeAndroidTestRunConfigurationStateEditor implements ConfigurationSpecificEditor<BlazeAndroidTestRunConfiguration> {
+class BlazeAndroidTestRunConfigurationStateEditor
+    implements BlazeAndroidRunConfigurationStateEditor {
   private JRadioButton allInPackageButton;
   private JRadioButton classButton;
   private JRadioButton testMethodButton;
@@ -55,9 +62,6 @@
   private JCheckBox runThroughBlazeTestCheckBox;
   private final JRadioButton[] testingType2RadioButton = new JRadioButton[4];
 
-  @NotNull
-  private JComponent anchor;
-
   BlazeAndroidTestRunConfigurationStateEditor(Project project) {
     setupUI(project);
 
@@ -73,32 +77,17 @@
     addTestingType(TEST_ALL_IN_PACKAGE, allInPackageButton);
     addTestingType(TEST_CLASS, classButton);
     addTestingType(TEST_METHOD, testMethodButton);
-
-    setAnchor(packageComponent.getLabel());
   }
 
   private void addTestingType(final int type, JRadioButton button) {
     testingType2RadioButton[type] = button;
-    button.addActionListener(new ActionListener() {
-      @Override
-      public void actionPerformed(ActionEvent e) {
-        updateLabelComponents(type);
-      }
-    });
-  }
-
-  @Override
-  public JComponent getAnchor() {
-    return anchor;
-  }
-
-  @Override
-  public void setAnchor(JComponent anchor) {
-    this.anchor = anchor;
-    packageComponent.setAnchor(anchor);
-    classComponent.setAnchor(anchor);
-    methodComponent.setAnchor(anchor);
-    labelTest.setAnchor(anchor);
+    button.addActionListener(
+        new ActionListener() {
+          @Override
+          public void actionPerformed(ActionEvent e) {
+            updateLabelComponents(type);
+          }
+        });
   }
 
   private void updateButtonsAndLabelComponents(int type) {
@@ -115,106 +104,268 @@
     methodComponent.setVisible(type == TEST_METHOD);
   }
 
-  private void setupUI(Project project)
-  {
+  private void setupUI(Project project) {
     try {
       doSetupUI(project);
-    }
-    catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
+    } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
       // Can't happen - this is from IJ generated code
     }
   }
 
-  /**
-   * Initially generated by IntelliJ from a .form file, then checked in.
-   */
+  /** Initially generated by IntelliJ from a .form file, then checked in. */
   private void doSetupUI(Project project)
-    throws ClassCastException, InstantiationException, IllegalAccessException, ClassNotFoundException {
+      throws ClassCastException, InstantiationException, IllegalAccessException,
+          ClassNotFoundException {
     panel = new JPanel();
     panel.setLayout(new GridLayoutManager(6, 6, new Insets(0, 0, 0, 0), -1, -1));
     panel.setAlignmentX(0.0f);
     allInPackageButton = new JRadioButton();
-    allInPackageButton
-      .setActionCommand(ResourceBundle.getBundle("messages/ExecutionBundle").getString("jnit.configuration.all.tests.in.package.radio"));
-    this.loadButtonText(allInPackageButton, ResourceBundle.getBundle("messages/AndroidBundle")
-      .getString("android.run.configuration.all.in.package.radio"));
-    panel.add(allInPackageButton,
-              new GridConstraints(1, 2, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED,
-                                    GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
+    allInPackageButton.setActionCommand(
+        ResourceBundle.getBundle("messages/ExecutionBundle")
+            .getString("jnit.configuration.all.tests.in.package.radio"));
+    this.loadButtonText(
+        allInPackageButton,
+        ResourceBundle.getBundle("messages/AndroidBundle")
+            .getString("android.run.configuration.all.in.package.radio"));
+    panel.add(
+        allInPackageButton,
+        new GridConstraints(
+            1,
+            2,
+            1,
+            1,
+            GridConstraints.ANCHOR_WEST,
+            GridConstraints.FILL_NONE,
+            GridConstraints.SIZEPOLICY_FIXED,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
     classButton = new JRadioButton();
-    classButton.setActionCommand(ResourceBundle.getBundle("messages/ExecutionBundle").getString("junit.configuration.test.class.radio"));
+    classButton.setActionCommand(
+        ResourceBundle.getBundle("messages/ExecutionBundle")
+            .getString("junit.configuration.test.class.radio"));
     classButton.setEnabled(true);
     classButton.setSelected(false);
-    this.loadButtonText(classButton,
-                        ResourceBundle.getBundle("messages/AndroidBundle").getString("android.run.configuration.class.radio"));
-    panel.add(classButton,
-              new GridConstraints(1, 3, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED,
-                                    GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
+    this.loadButtonText(
+        classButton,
+        ResourceBundle.getBundle("messages/AndroidBundle")
+            .getString("android.run.configuration.class.radio"));
+    panel.add(
+        classButton,
+        new GridConstraints(
+            1,
+            3,
+            1,
+            1,
+            GridConstraints.ANCHOR_WEST,
+            GridConstraints.FILL_NONE,
+            GridConstraints.SIZEPOLICY_FIXED,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
     testMethodButton = new JRadioButton();
-    testMethodButton
-      .setActionCommand(ResourceBundle.getBundle("messages/ExecutionBundle").getString("junit.configuration.test.method.radio"));
+    testMethodButton.setActionCommand(
+        ResourceBundle.getBundle("messages/ExecutionBundle")
+            .getString("junit.configuration.test.method.radio"));
     testMethodButton.setSelected(false);
-    this.loadButtonText(testMethodButton,
-                        ResourceBundle.getBundle("messages/AndroidBundle").getString("android.run.configuration.method.radio"));
-    panel.add(testMethodButton,
-              new GridConstraints(1, 4, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED,
-                                    GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
+    this.loadButtonText(
+        testMethodButton,
+        ResourceBundle.getBundle("messages/AndroidBundle")
+            .getString("android.run.configuration.method.radio"));
+    panel.add(
+        testMethodButton,
+        new GridConstraints(
+            1,
+            4,
+            1,
+            1,
+            GridConstraints.ANCHOR_WEST,
+            GridConstraints.FILL_NONE,
+            GridConstraints.SIZEPOLICY_FIXED,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
     labelTest = new JBLabel();
     labelTest.setHorizontalAlignment(2);
     labelTest.setHorizontalTextPosition(2);
     labelTest.setIconTextGap(4);
-    this.loadLabelText(labelTest, ResourceBundle.getBundle("messages/ExecutionBundle")
-      .getString("junit.configuration.configure.junit.test.label"));
-    panel.add(labelTest,
-              new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED,
-                                    GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
+    this.loadLabelText(
+        labelTest,
+        ResourceBundle.getBundle("messages/ExecutionBundle")
+            .getString("junit.configuration.configure.junit.test.label"));
+    panel.add(
+        labelTest,
+        new GridConstraints(
+            1,
+            0,
+            1,
+            1,
+            GridConstraints.ANCHOR_WEST,
+            GridConstraints.FILL_NONE,
+            GridConstraints.SIZEPOLICY_FIXED,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
     final Spacer spacer1 = new Spacer();
-    panel.add(spacer1, new GridConstraints(1, 5, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL,
-                                           GridConstraints.SIZEPOLICY_WANT_GROW, 1, null, null, null, 0, false));
+    panel.add(
+        spacer1,
+        new GridConstraints(
+            1,
+            5,
+            1,
+            1,
+            GridConstraints.ANCHOR_CENTER,
+            GridConstraints.FILL_HORIZONTAL,
+            GridConstraints.SIZEPOLICY_WANT_GROW,
+            1,
+            null,
+            null,
+            null,
+            0,
+            false));
     allInTargetButton = new JRadioButton();
     allInTargetButton.setText("All in test target");
     allInTargetButton.setMnemonic('A');
     allInTargetButton.setDisplayedMnemonicIndex(0);
-    panel.add(allInTargetButton, new GridConstraints(1, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE,
-                                                         GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
-                                                     GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
+    panel.add(
+        allInTargetButton,
+        new GridConstraints(
+            1,
+            1,
+            1,
+            1,
+            GridConstraints.ANCHOR_WEST,
+            GridConstraints.FILL_NONE,
+            GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
     classComponent = new LabeledComponent();
     classComponent.setComponentClass("javax.swing.JPanel");
     classComponent.setLabelLocation("West");
-    classComponent.setText(ResourceBundle.getBundle("messages/AndroidBundle").getString("android.run.configuration.class.label"));
-    panel.add(classComponent, new GridConstraints(3, 0, 1, 6, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL,
-                                                      GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
-                                                  GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
+    classComponent.setText(
+        ResourceBundle.getBundle("messages/AndroidBundle")
+            .getString("android.run.configuration.class.label"));
+    panel.add(
+        classComponent,
+        new GridConstraints(
+            3,
+            0,
+            1,
+            6,
+            GridConstraints.ANCHOR_CENTER,
+            GridConstraints.FILL_HORIZONTAL,
+            GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
     methodComponent = new LabeledComponent();
-    methodComponent.setComponentClass("com.intellij.openapi.ui.TextFieldWithBrowseButton$NoPathCompletion");
+    methodComponent.setComponentClass(
+        "com.intellij.openapi.ui.TextFieldWithBrowseButton$NoPathCompletion");
     methodComponent.setLabelLocation("West");
-    methodComponent.setText(ResourceBundle.getBundle("messages/AndroidBundle").getString("android.run.configuration.method.label"));
-    panel.add(methodComponent, new GridConstraints(4, 0, 1, 6, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL,
-                                                       GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
-                                                   GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
+    methodComponent.setText(
+        ResourceBundle.getBundle("messages/AndroidBundle")
+            .getString("android.run.configuration.method.label"));
+    panel.add(
+        methodComponent,
+        new GridConstraints(
+            4,
+            0,
+            1,
+            6,
+            GridConstraints.ANCHOR_CENTER,
+            GridConstraints.FILL_HORIZONTAL,
+            GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
     runnerComponent = new LabeledComponent();
     runnerComponent.setComponentClass("javax.swing.JPanel");
     runnerComponent.setEnabled(true);
     runnerComponent.setLabelLocation("North");
-    runnerComponent
-      .setText(ResourceBundle.getBundle("messages/AndroidBundle").getString("android.test.run.configuration.instrumentation.label"));
-    panel.add(runnerComponent, new GridConstraints(5, 0, 1, 6, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL,
-                                                       GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
-                                                   GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
+    runnerComponent.setText(
+        ResourceBundle.getBundle("messages/AndroidBundle")
+            .getString("android.test.run.configuration.instrumentation.label"));
+    panel.add(
+        runnerComponent,
+        new GridConstraints(
+            5,
+            0,
+            1,
+            6,
+            GridConstraints.ANCHOR_CENTER,
+            GridConstraints.FILL_HORIZONTAL,
+            GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
     packageComponent = new LabeledComponent();
     packageComponent.setComponentClass("javax.swing.JPanel");
     packageComponent.setLabelLocation("West");
-    packageComponent.setText(ResourceBundle.getBundle("messages/AndroidBundle").getString("android.run.configuration.package.label"));
-    panel.add(packageComponent, new GridConstraints(2, 0, 1, 6, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL,
-                                                        GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
-                                                    GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
+    packageComponent.setText(
+        ResourceBundle.getBundle("messages/AndroidBundle")
+            .getString("android.run.configuration.package.label"));
+    panel.add(
+        packageComponent,
+        new GridConstraints(
+            2,
+            0,
+            1,
+            6,
+            GridConstraints.ANCHOR_CENTER,
+            GridConstraints.FILL_HORIZONTAL,
+            GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
     runThroughBlazeTestCheckBox = new JCheckBox();
-    runThroughBlazeTestCheckBox.setText(String.format("Run through '%s test'", Blaze.buildSystemName(project).toLowerCase()));
-    runThroughBlazeTestCheckBox.setToolTipText(String.format("Slower, but more truthful to %s", Blaze.buildSystemName(project)));
-    panel.add(runThroughBlazeTestCheckBox, new GridConstraints(0, 0, 1, 6, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE,
-                                                                   GridConstraints.SIZEPOLICY_CAN_SHRINK |
-                                                                   GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED,
-                                                               null, null, null, 0, false));
+    runThroughBlazeTestCheckBox.setText(
+        String.format("Run through '%s test'", Blaze.buildSystemName(project).toLowerCase()));
+    runThroughBlazeTestCheckBox.setToolTipText(
+        String.format("Slower, but more truthful to %s", Blaze.buildSystemName(project)));
+    panel.add(
+        runThroughBlazeTestCheckBox,
+        new GridConstraints(
+            0,
+            0,
+            1,
+            6,
+            GridConstraints.ANCHOR_WEST,
+            GridConstraints.FILL_NONE,
+            GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
     ButtonGroup buttonGroup;
     buttonGroup = new ButtonGroup();
     buttonGroup.add(allInPackageButton);
@@ -223,9 +374,7 @@
     buttonGroup.add(allInTargetButton);
   }
 
-  /**
-   * Initially generated by IntelliJ from a .form file, then checked in.
-   */
+  /** Initially generated by IntelliJ from a .form file, then checked in. */
   private void loadLabelText(JLabel component, String text) {
     StringBuffer result = new StringBuffer();
     boolean haveMnemonic = false;
@@ -234,7 +383,9 @@
     for (int i = 0; i < text.length(); i++) {
       if (text.charAt(i) == '&') {
         i++;
-        if (i == text.length()) break;
+        if (i == text.length()) {
+          break;
+        }
         if (!haveMnemonic && text.charAt(i) != '&') {
           haveMnemonic = true;
           mnemonic = text.charAt(i);
@@ -250,9 +401,7 @@
     }
   }
 
-  /**
-   * Initially generated by IntelliJ from a .form file and checked in.
-   */
+  /** Initially generated by IntelliJ from a .form file and checked in. */
   private void loadButtonText(AbstractButton component, String text) {
     StringBuffer result = new StringBuffer();
     boolean haveMnemonic = false;
@@ -261,7 +410,9 @@
     for (int i = 0; i < text.length(); i++) {
       if (text.charAt(i) == '&') {
         i++;
-        if (i == text.length()) break;
+        if (i == text.length()) {
+          break;
+        }
         if (!haveMnemonic && text.charAt(i) != '&') {
           haveMnemonic = true;
           mnemonic = text.charAt(i);
@@ -279,7 +430,8 @@
 
   private int getTestingType() {
     for (int i = 0, myTestingType2RadioButtonLength = testingType2RadioButton.length;
-         i < myTestingType2RadioButtonLength; i++) {
+        i < myTestingType2RadioButtonLength;
+        i++) {
       JRadioButton button = testingType2RadioButton[i];
       if (button.isSelected()) {
         return i;
@@ -289,27 +441,29 @@
   }
 
   @Override
-  public void applyTo(BlazeAndroidTestRunConfiguration configuration) {
-    BlazeAndroidTestRunConfigurationState configState = configuration.getConfigState();
+  public void applyEditorTo(BlazeAndroidRunConfigurationState state) {
+    BlazeAndroidTestRunConfigurationState configState =
+        (BlazeAndroidTestRunConfigurationState) state;
     configState.setRunThroughBlaze(runThroughBlazeTestCheckBox.isSelected());
 
-    configState.TESTING_TYPE = getTestingType();
-    configState.CLASS_NAME = classComponent.getComponent().getText();
-    configState.METHOD_NAME = methodComponent.getComponent().getText();
-    configState.PACKAGE_NAME = packageComponent.getComponent().getText();
-    configState.INSTRUMENTATION_RUNNER_CLASS = runnerComponent.getComponent().getText();
+    configState.setTestingType(getTestingType());
+    configState.setClassName(classComponent.getComponent().getText());
+    configState.setMethodName(methodComponent.getComponent().getText());
+    configState.setPackageName(packageComponent.getComponent().getText());
+    configState.setInstrumentationRunnerClass(runnerComponent.getComponent().getText());
   }
 
   @Override
-  public void resetFrom(BlazeAndroidTestRunConfiguration configuration) {
-    BlazeAndroidTestRunConfigurationState configState = configuration.getConfigState();
+  public void resetEditorFrom(BlazeAndroidRunConfigurationState state) {
+    BlazeAndroidTestRunConfigurationState configState =
+        (BlazeAndroidTestRunConfigurationState) state;
     runThroughBlazeTestCheckBox.setSelected(configState.isRunThroughBlaze());
 
-    updateButtonsAndLabelComponents(configState.TESTING_TYPE);
-    packageComponent.getComponent().setText(configState.PACKAGE_NAME);
-    classComponent.getComponent().setText(configState.CLASS_NAME);
-    methodComponent.getComponent().setText(configState.METHOD_NAME);
-    runnerComponent.getComponent().setText(configState.INSTRUMENTATION_RUNNER_CLASS);
+    updateButtonsAndLabelComponents(configState.getTestingType());
+    packageComponent.getComponent().setText(configState.getPackageName());
+    classComponent.getComponent().setText(configState.getClassName());
+    methodComponent.getComponent().setText(configState.getMethodName());
+    runnerComponent.getComponent().setText(configState.getInstrumentationRunnerClass());
   }
 
   @Override
diff --git a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationType.java b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationType.java
index acc06d9..2104d75 100644
--- a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationType.java
+++ b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationType.java
@@ -15,31 +15,32 @@
  */
 package com.google.idea.blaze.android.run.test;
 
-import com.google.idea.blaze.android.run.BlazeBeforeRunTaskProvider;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.model.primitives.Kind;
-import com.google.idea.blaze.base.run.BlazeRuleConfigurationFactory;
+import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
+import com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType;
 import com.google.idea.blaze.base.settings.Blaze;
-import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
 import com.intellij.execution.BeforeRunTask;
-import com.intellij.execution.RunManager;
-import com.intellij.execution.RunnerAndConfigurationSettings;
 import com.intellij.execution.configurations.ConfigurationFactory;
 import com.intellij.execution.configurations.ConfigurationType;
 import com.intellij.execution.configurations.ConfigurationTypeUtil;
+import com.intellij.execution.configurations.UnknownConfigurationType;
 import com.intellij.icons.AllIcons;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.Key;
 import com.intellij.ui.LayeredIcon;
 import icons.AndroidIcons;
+import javax.swing.Icon;
 import org.jetbrains.annotations.NotNull;
 
-import javax.swing.*;
-
 /**
  * A type for Android test run configurations adapted specifically to run android_test targets.
+ *
+ * @deprecated See {@link com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType}. Retained
+ *     in 1.9 for legacy purposes, to allow existing BlazeAndroidTestRunConfigurations to be updated
+ *     to BlazeCommandRunConfigurations. Intended to be removed in 2.1.
  */
-public class BlazeAndroidTestRunConfigurationType implements ConfigurationType {
+// Hack: extend UnknownConfigurationType to completely hide it in the Run/Debug Configurations UI.
+@Deprecated
+public class BlazeAndroidTestRunConfigurationType extends UnknownConfigurationType {
   private static final Icon ANDROID_TEST_ICON;
 
   static {
@@ -50,31 +51,28 @@
   }
 
   private final BlazeAndroidTestRunConfigurationFactory factory =
-    new BlazeAndroidTestRunConfigurationFactory(this);
+      new BlazeAndroidTestRunConfigurationFactory(this);
 
-  public static class BlazeAndroidTestRuleConfigurationFactory implements BlazeRuleConfigurationFactory {
-    @Override
-    public boolean handlesRule(WorkspaceLanguageSettings workspaceLanguageSettings, @NotNull RuleIdeInfo rule) {
-      return rule.kindIsOneOf(Kind.ANDROID_TEST);
-    }
-
-    @Override
-    @NotNull
-    public RunnerAndConfigurationSettings createForRule(@NotNull RunManager runManager, @NotNull RuleIdeInfo rule) {
-      return getInstance().factory.createForRule(runManager, rule);
-    }
-  }
-
-  public static class BlazeAndroidTestRunConfigurationFactory extends ConfigurationFactory {
+  static class BlazeAndroidTestRunConfigurationFactory extends ConfigurationFactory {
 
     protected BlazeAndroidTestRunConfigurationFactory(@NotNull ConfigurationType type) {
       super(type);
     }
 
     @Override
+    public String getName() {
+      // Used to look up this ConfigurationFactory.
+      // Preserve value so legacy configurations can be loaded.
+      return Blaze.defaultBuildSystemName() + " Android Test";
+    }
+
+    @Override
     @NotNull
-    public BlazeAndroidTestRunConfiguration createTemplateConfiguration(@NotNull Project project) {
-      return new BlazeAndroidTestRunConfiguration(project, this);
+    public BlazeCommandRunConfiguration createTemplateConfiguration(@NotNull Project project) {
+      // Create a BlazeCommandRunConfiguration instead, to update legacy configurations.
+      return BlazeCommandRunConfigurationType.getInstance()
+          .getFactory()
+          .createTemplateConfiguration(project);
     }
 
     @Override
@@ -89,18 +87,8 @@
 
     @Override
     public void configureBeforeRunTaskDefaults(
-      Key<? extends BeforeRunTask> providerID, BeforeRunTask task) {
-      task.setEnabled(providerID.equals(BlazeBeforeRunTaskProvider.ID));
-    }
-
-    @NotNull
-    public RunnerAndConfigurationSettings createForRule(@NotNull RunManager runManager, @NotNull RuleIdeInfo rule) {
-      final RunnerAndConfigurationSettings settings =
-        runManager.createRunConfiguration(rule.label.toString(), this);
-      final BlazeAndroidTestRunConfiguration configuration =
-        (BlazeAndroidTestRunConfiguration) settings.getConfiguration();
-      configuration.setTarget(rule.label);
-      return settings;
+        Key<? extends BeforeRunTask> providerID, BeforeRunTask task) {
+      // Removed BlazeAndroidBeforeRunTaskProvider; this method won't be called anymore anyhow.
     }
 
     @Override
@@ -115,12 +103,13 @@
 
   @Override
   public String getDisplayName() {
-    return Blaze.defaultBuildSystemName() + " Android Test";
+    return "Legacy " + Blaze.defaultBuildSystemName() + " Android Test";
   }
 
   @Override
   public String getConfigurationTypeDescription() {
-    return "Launch/debug configuration for android_test rules";
+    return "Launch/debug configuration for android_test rules "
+        + "Use Blaze Command instead; this legacy configuration type is being removed.";
   }
 
   @Override
@@ -131,11 +120,13 @@
   @Override
   @NotNull
   public String getId() {
+    // Used to look up this ConfigurationType.
+    // Preserve value so legacy configurations can be loaded.
     return "BlazeAndroidTestRunConfigurationType";
   }
 
   @Override
   public BlazeAndroidTestRunConfigurationFactory[] getConfigurationFactories() {
-    return new BlazeAndroidTestRunConfigurationFactory[]{factory};
+    return new BlazeAndroidTestRunConfigurationFactory[] {factory};
   }
 }
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 034c7b6..a12d370 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
@@ -16,7 +16,13 @@
 package com.google.idea.blaze.android.run.test;
 
 import com.android.ddmlib.IDevice;
-import com.android.tools.idea.run.*;
+import com.android.tools.idea.run.ApkInfo;
+import com.android.tools.idea.run.ApkProvider;
+import com.android.tools.idea.run.ApkProvisionException;
+import com.android.tools.idea.run.ApplicationIdProvider;
+import com.android.tools.idea.run.ConsolePrinter;
+import com.android.tools.idea.run.ConsoleProvider;
+import com.android.tools.idea.run.LaunchOptions;
 import com.android.tools.idea.run.editor.AndroidDebugger;
 import com.android.tools.idea.run.editor.AndroidDebuggerState;
 import com.android.tools.idea.run.tasks.DebugConnectorTask;
@@ -26,31 +32,33 @@
 import com.android.tools.idea.run.util.ProcessHandlerLaunchStatus;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
-import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationCommonState;
 import com.google.idea.blaze.android.run.deployinfo.BlazeApkProvider;
-import com.google.idea.blaze.android.run.runner.*;
+import com.google.idea.blaze.android.run.runner.BlazeAndroidDeviceSelector;
+import com.google.idea.blaze.android.run.runner.BlazeAndroidLaunchTasksProvider;
+import com.google.idea.blaze.android.run.runner.BlazeAndroidRunConfigurationDebuggerManager;
+import com.google.idea.blaze.android.run.runner.BlazeAndroidRunContext;
+import com.google.idea.blaze.android.run.runner.BlazeApkBuildStep;
+import com.google.idea.blaze.android.run.runner.BlazeApkBuildStepNormalBuild;
+import com.google.idea.blaze.base.model.primitives.Label;
 import com.intellij.execution.ExecutionException;
 import com.intellij.execution.configurations.RunConfiguration;
 import com.intellij.execution.runners.ExecutionEnvironment;
 import com.intellij.openapi.project.Project;
-import org.jetbrains.android.facet.AndroidFacet;
-import org.jetbrains.annotations.NotNull;
-
-import javax.annotation.Nullable;
 import java.util.Collection;
 import java.util.List;
 import java.util.Set;
+import javax.annotation.Nullable;
+import org.jetbrains.android.facet.AndroidFacet;
+import org.jetbrains.annotations.NotNull;
 
-/**
- * Run context for android_test.
- */
+/** Run context for android_test. */
 class BlazeAndroidTestRunContext implements BlazeAndroidRunContext {
   private final Project project;
   private final AndroidFacet facet;
   private final RunConfiguration runConfiguration;
   private final ExecutionEnvironment env;
-  private final BlazeAndroidRunConfigurationCommonState commonState;
   private final BlazeAndroidTestRunConfigurationState configState;
+  private final Label label;
   private final ImmutableList<String> buildFlags;
   private final List<Runnable> launchTaskCompleteListeners = Lists.newArrayList();
   private final ConsoleProvider consoleProvider;
@@ -58,29 +66,30 @@
   private final ApplicationIdProvider applicationIdProvider;
   private final ApkProvider apkProvider;
 
-  public BlazeAndroidTestRunContext(Project project,
-                                    AndroidFacet facet,
-                                    RunConfiguration runConfiguration,
-                                    ExecutionEnvironment env,
-                                    BlazeAndroidRunConfigurationCommonState commonState,
-                                    BlazeAndroidTestRunConfigurationState configState,
-                                    ImmutableList<String> buildFlags) {
+  public BlazeAndroidTestRunContext(
+      Project project,
+      AndroidFacet facet,
+      RunConfiguration runConfiguration,
+      ExecutionEnvironment env,
+      BlazeAndroidTestRunConfigurationState configState,
+      Label label,
+      ImmutableList<String> buildFlags) {
     this.project = project;
     this.facet = facet;
     this.runConfiguration = runConfiguration;
     this.env = env;
-    this.commonState = commonState;
+    this.label = label;
     this.configState = configState;
     this.buildFlags = buildFlags;
     this.consoleProvider = new AndroidTestConsoleProvider(project, runConfiguration, configState);
-    this.buildStep = new BlazeApkBuildStepNormalBuild(project, commonState, buildFlags);
-    this.applicationIdProvider = new BlazeAndroidTestApplicationIdProvider(project, buildStep.getDeployInfo());
+    this.buildStep = new BlazeApkBuildStepNormalBuild(project, label, buildFlags);
+    this.applicationIdProvider =
+        new BlazeAndroidTestApplicationIdProvider(project, buildStep.getDeployInfo());
     this.apkProvider = new BlazeApkProvider(project, buildStep.getDeployInfo());
   }
 
   @Override
-  public void augmentEnvironment(ExecutionEnvironment env) {
-  }
+  public void augmentEnvironment(ExecutionEnvironment env) {}
 
   @Override
   public BlazeAndroidDeviceSelector getDeviceSelector() {
@@ -110,18 +119,21 @@
 
   @Override
   public LaunchTasksProvider getLaunchTasksProvider(
-    LaunchOptions launchOptions,
-    BlazeAndroidRunConfigurationDebuggerManager debuggerManager) throws ExecutionException {
-    return new BlazeAndroidLaunchTasksProvider(project, this, applicationIdProvider, launchOptions, debuggerManager);
+      LaunchOptions.Builder launchOptionsBuilder,
+      boolean isDebug,
+      BlazeAndroidRunConfigurationDebuggerManager debuggerManager)
+      throws ExecutionException {
+    return new BlazeAndroidLaunchTasksProvider(
+        project, this, applicationIdProvider, launchOptionsBuilder, isDebug, debuggerManager);
   }
 
   @Override
-  public ImmutableList<LaunchTask> getDeployTasks(IDevice device, LaunchOptions launchOptions) throws ExecutionException {
+  public ImmutableList<LaunchTask> getDeployTasks(IDevice device, LaunchOptions launchOptions)
+      throws ExecutionException {
     Collection<ApkInfo> apks;
     try {
       apks = apkProvider.getApks(device);
-    }
-    catch (ApkProvisionException e) {
+    } catch (ApkProvisionException e) {
       throw new ExecutionException(e);
     }
     return ImmutableList.of(new DeployApkTask(project, launchOptions, apks));
@@ -129,42 +141,47 @@
 
   @Nullable
   @Override
-  public LaunchTask getApplicationLaunchTask(LaunchOptions launchOptions,
-                                             AndroidDebugger androidDebugger,
-                                             AndroidDebuggerState androidDebuggerState,
-                                             ProcessHandlerLaunchStatus processHandlerLaunchStatus) throws ExecutionException {
+  public LaunchTask getApplicationLaunchTask(
+      LaunchOptions launchOptions,
+      @Nullable Integer userId,
+      AndroidDebugger androidDebugger,
+      AndroidDebuggerState androidDebuggerState,
+      ProcessHandlerLaunchStatus processHandlerLaunchStatus)
+      throws ExecutionException {
     if (configState.isRunThroughBlaze()) {
       return new BlazeAndroidTestLaunchTask(
-        project,
-        commonState.getTarget(),
-        buildFlags,
-        new BlazeAndroidTestFilter(configState.TESTING_TYPE,
-                                   configState.CLASS_NAME,
-                                   configState.METHOD_NAME,
-                                   configState.PACKAGE_NAME),
-        this,
-        launchOptions.isDebug()
-      );
+          project,
+          label,
+          buildFlags,
+          new BlazeAndroidTestFilter(
+              configState.getTestingType(),
+              configState.getClassName(),
+              configState.getMethodName(),
+              configState.getPackageName()),
+          this,
+          launchOptions.isDebug());
     }
     return StockAndroidTestLaunchTask.getStockTestLaunchTask(
-      configState,
-      applicationIdProvider,
-      launchOptions.isDebug(),
-      facet,
-      processHandlerLaunchStatus
-    );
+        configState,
+        applicationIdProvider,
+        launchOptions.isDebug(),
+        facet,
+        processHandlerLaunchStatus);
   }
 
   @Override
-  public DebugConnectorTask getDebuggerTask(LaunchOptions launchOptions,
-                                            AndroidDebugger androidDebugger,
-                                            AndroidDebuggerState androidDebuggerState,
-                                            @NotNull Set<String> packageIds) throws ExecutionException {
+  public DebugConnectorTask getDebuggerTask(
+      AndroidDebugger androidDebugger,
+      AndroidDebuggerState androidDebuggerState,
+      @NotNull Set<String> packageIds)
+      throws ExecutionException {
     if (configState.isRunThroughBlaze()) {
-      return new ConnectBlazeTestDebuggerTask(env.getProject(), androidDebugger, packageIds, applicationIdProvider, this);
+      return new ConnectBlazeTestDebuggerTask(
+          env.getProject(), androidDebugger, packageIds, applicationIdProvider, this);
     }
     //noinspection unchecked
-    return androidDebugger.getConnectDebuggerTask(env, null, packageIds, facet, androidDebuggerState, runConfiguration.getType().getId());
+    return androidDebugger.getConnectDebuggerTask(
+        env, null, packageIds, facet, androidDebuggerState, runConfiguration.getType().getId());
   }
 
   void onLaunchTaskComplete() {
@@ -176,4 +193,10 @@
   void addLaunchTaskCompleteListener(Runnable runnable) {
     launchTaskCompleteListeners.add(runnable);
   }
+
+  @Nullable
+  @Override
+  public Integer getUserId(IDevice device, ConsolePrinter consolePrinter) {
+    return null;
+  }
 }
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 d15ecb6..9fe3377 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
@@ -19,7 +19,14 @@
 import com.android.ddmlib.Client;
 import com.android.ddmlib.ClientData;
 import com.android.ddmlib.IDevice;
-import com.android.tools.idea.run.*;
+import com.android.tools.idea.run.AndroidDebugState;
+import com.android.tools.idea.run.AndroidProcessText;
+import com.android.tools.idea.run.AndroidRunConfigurationBase;
+import com.android.tools.idea.run.AndroidSessionInfo;
+import com.android.tools.idea.run.ApkProvisionException;
+import com.android.tools.idea.run.ApplicationIdProvider;
+import com.android.tools.idea.run.LaunchInfo;
+import com.android.tools.idea.run.ProcessHandlerConsolePrinter;
 import com.android.tools.idea.run.editor.AndroidDebugger;
 import com.android.tools.idea.run.tasks.ConnectDebuggerTask;
 import com.android.tools.idea.run.tasks.ConnectJavaDebuggerTask;
@@ -36,15 +43,12 @@
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.project.Project;
+import java.util.Locale;
+import java.util.Set;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import java.util.Locale;
-import java.util.Set;
-
-/**
- * Connects the blaze debugger during execution.
- */
+/** Connects the blaze debugger during execution. */
 class ConnectBlazeTestDebuggerTask extends ConnectDebuggerTask {
   private static final Logger LOG = Logger.getInstance(ConnectBlazeTestDebuggerTask.class);
 
@@ -53,11 +57,11 @@
   private final BlazeAndroidTestRunContext runContext;
 
   public ConnectBlazeTestDebuggerTask(
-    Project project,
-    AndroidDebugger debugger,
-    Set<String> applicationIds,
-    ApplicationIdProvider applicationIdProvider,
-    BlazeAndroidTestRunContext runContext) {
+      Project project,
+      AndroidDebugger debugger,
+      Set<String> applicationIds,
+      ApplicationIdProvider applicationIdProvider,
+      BlazeAndroidTestRunContext runContext) {
     super(applicationIds, debugger, project);
     this.project = project;
     this.applicationIdProvider = applicationIdProvider;
@@ -66,15 +70,15 @@
 
   @Nullable
   @Override
-  public ProcessHandler perform(@NotNull LaunchInfo launchInfo,
-                                @NotNull IDevice device,
-                                @NotNull ProcessHandlerLaunchStatus state,
-                                @NotNull ProcessHandlerConsolePrinter printer) {
+  public ProcessHandler perform(
+      @NotNull LaunchInfo launchInfo,
+      @NotNull IDevice device,
+      @NotNull ProcessHandlerLaunchStatus state,
+      @NotNull ProcessHandlerConsolePrinter printer) {
     try {
       String packageName = applicationIdProvider.getPackageName();
       setUpForReattachingDebugger(packageName, launchInfo, state, printer);
-    }
-    catch (ApkProvisionException e) {
+    } catch (ApkProvisionException e) {
       LOG.error(e);
     }
 
@@ -83,75 +87,83 @@
   }
 
   /**
-   * Wires up listeners to automatically reconnect the debugger for each test method.
-   * When you `blaze test` an android_test in debug mode, it kills the instrumentation process
-   * between each test method, disconnecting the debugger. We listen for the start of a new
-   * method waiting for a debugger, and reconnect.
-   * TODO: Support stopping Blaze from the UI. This is hard because we have no way to distinguish
-   *   process handler termination/debug session ending initiated by the user.
+   * Wires up listeners to automatically reconnect the debugger for each test method. When you
+   * `blaze test` an android_test in debug mode, it kills the instrumentation process between each
+   * test method, disconnecting the debugger. We listen for the start of a new method waiting for a
+   * debugger, and reconnect. TODO: Support stopping Blaze from the UI. This is hard because we have
+   * no way to distinguish process handler termination/debug session ending initiated by the user.
    */
   private void setUpForReattachingDebugger(
-    String targetPackage,
-    LaunchInfo launchInfo,
-    ProcessHandlerLaunchStatus launchStatus,
-    ProcessHandlerConsolePrinter printer
-  ) {
+      String targetPackage,
+      LaunchInfo launchInfo,
+      ProcessHandlerLaunchStatus launchStatus,
+      ProcessHandlerConsolePrinter printer) {
     final AndroidDebugBridge.IClientChangeListener reattachingListener =
-      new AndroidDebugBridge.IClientChangeListener() {
-        // The target application can either
-        // 1. Match our target name, and become available for debugging.
-        // 2. Be available for debugging, and suddenly have its name changed to match.
-        static final int CHANGE_MASK = Client.CHANGE_DEBUGGER_STATUS | Client.CHANGE_NAME;
+        new AndroidDebugBridge.IClientChangeListener() {
+          // The target application can either
+          // 1. Match our target name, and become available for debugging.
+          // 2. Be available for debugging, and suddenly have its name changed to match.
+          static final int CHANGE_MASK = Client.CHANGE_DEBUGGER_STATUS | Client.CHANGE_NAME;
 
-        @Override
-        public void clientChanged(@NotNull Client client, int changeMask) {
-          ClientData data = client.getClientData();
-          String clientDescription = data.getClientDescription();
-          if (clientDescription != null && clientDescription.equals(targetPackage)
-              && (changeMask & CHANGE_MASK) != 0
-              && data.getDebuggerConnectionStatus().equals(ClientData.DebuggerStatus.WAITING)) {
-            reattachDebugger(launchInfo, client, launchStatus, printer);
+          @Override
+          public void clientChanged(@NotNull Client client, int changeMask) {
+            ClientData data = client.getClientData();
+            String clientDescription = data.getClientDescription();
+            if (clientDescription != null
+                && clientDescription.equals(targetPackage)
+                && (changeMask & CHANGE_MASK) != 0
+                && data.getDebuggerConnectionStatus().equals(ClientData.DebuggerStatus.WAITING)) {
+              reattachDebugger(launchInfo, client, launchStatus, printer);
+            }
           }
-        }
-      };
+        };
 
     AndroidDebugBridge.addClientChangeListener(reattachingListener);
-    runContext.addLaunchTaskCompleteListener(() -> {
-      AndroidDebugBridge.removeClientChangeListener(reattachingListener);
-      launchStatus.terminateLaunch("Test run completed.\n");
-    });
+    runContext.addLaunchTaskCompleteListener(
+        () -> {
+          AndroidDebugBridge.removeClientChangeListener(reattachingListener);
+          launchStatus.terminateLaunch("Test run completed.\n");
+        });
   }
 
   private void reattachDebugger(
-    LaunchInfo launchInfo,
-    final Client client,
-    ProcessHandlerLaunchStatus launchStatus,
-    ProcessHandlerConsolePrinter printer
-  ) {
-    ApplicationManager.getApplication().invokeLater(() -> launchDebugger(launchInfo, client, launchStatus, printer));
+      LaunchInfo launchInfo,
+      final Client client,
+      ProcessHandlerLaunchStatus launchStatus,
+      ProcessHandlerConsolePrinter printer) {
+    ApplicationManager.getApplication()
+        .invokeLater(() -> launchDebugger(launchInfo, client, launchStatus, printer));
   }
 
   /**
-   * Nearly a clone of {@link ConnectJavaDebuggerTask#launchDebugger}. There are a few changes to account for null variables that could
-   * occur in our implementation.
+   * Nearly a clone of {@link ConnectJavaDebuggerTask#launchDebugger}. There are a few changes to
+   * account for null variables that could occur in our implementation.
    */
   @Override
-  public ProcessHandler launchDebugger(@NotNull LaunchInfo currentLaunchInfo,
-                                       @NotNull Client client,
-                                       @NotNull ProcessHandlerLaunchStatus launchStatus,
-                                       @NotNull ProcessHandlerConsolePrinter printer) {
+  public ProcessHandler launchDebugger(
+      @NotNull LaunchInfo currentLaunchInfo,
+      @NotNull Client client,
+      @NotNull ProcessHandlerLaunchStatus launchStatus,
+      @NotNull ProcessHandlerConsolePrinter printer) {
     String debugPort = Integer.toString(client.getDebuggerListenPort());
     int pid = client.getClientData().getPid();
     Logger.getInstance(ConnectJavaDebuggerTask.class)
-      .info(String.format(Locale.US, "Attempting to connect debugger to port %1$s [client %2$d]", debugPort, pid));
+        .info(
+            String.format(
+                Locale.US,
+                "Attempting to connect debugger to port %1$s [client %2$d]",
+                debugPort,
+                pid));
 
     // create a new process handler
     RemoteConnection connection = new RemoteConnection(true, "localhost", debugPort, false);
     RemoteDebugProcessHandler debugProcessHandler = new RemoteDebugProcessHandler(project);
 
     // switch the launch status and console printers to point to the new process handler
-    // this is required, esp. for AndroidTestListener which holds a 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
+    // this is required, esp. for AndroidTestListener which holds a
+    // 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
     launchStatus.setProcessHandler(debugProcessHandler);
     printer.setProcessHandler(debugProcessHandler);
 
@@ -166,29 +178,34 @@
       processHandler.detachProcess();
     }
 
-    AndroidDebugState debugState = new AndroidDebugState(project, debugProcessHandler, connection, currentLaunchInfo.consoleProvider);
+    AndroidDebugState debugState =
+        new AndroidDebugState(
+            project, debugProcessHandler, connection, currentLaunchInfo.consoleProvider);
 
     RunContentDescriptor debugDescriptor;
     try {
       // @formatter:off
-      ExecutionEnvironment debugEnv = new ExecutionEnvironmentBuilder(currentLaunchInfo.env)
-        .executor(currentLaunchInfo.executor)
-        .runner(currentLaunchInfo.runner)
-        .contentToReuse(processHandler == null ? null : descriptor)
-        .build();
-      debugDescriptor = DebuggerPanelsManager.getInstance(project).attachVirtualMachine(debugEnv, debugState, connection, false);
+      ExecutionEnvironment debugEnv =
+          new ExecutionEnvironmentBuilder(currentLaunchInfo.env)
+              .executor(currentLaunchInfo.executor)
+              .runner(currentLaunchInfo.runner)
+              .contentToReuse(processHandler == null ? null : descriptor)
+              .build();
+      debugDescriptor =
+          DebuggerPanelsManager.getInstance(project)
+              .attachVirtualMachine(debugEnv, debugState, connection, false);
       // @formatter:on
-    }
-    catch (ExecutionException e) {
+    } catch (ExecutionException e) {
       printer.stderr("ExecutionException: " + e.getMessage() + '.');
       return null;
     }
 
-    // Based on the above try block, we shouldn't get here unless we have assigned to debugDescriptor
+    // Based on the above try block we shouldn't get here unless we have assigned to debugDescriptor
     assert debugDescriptor != null;
 
     // 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?
+    // 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) {
@@ -197,12 +214,21 @@
     }
 
     RunProfile runProfile = currentLaunchInfo.env.getRunProfile();
-    int uniqueId = runProfile instanceof AndroidRunConfigurationBase ? ((AndroidRunConfigurationBase)runProfile).getUniqueID() : -1;
+    int uniqueId =
+        runProfile instanceof AndroidRunConfigurationBase
+            ? ((AndroidRunConfigurationBase) runProfile).getUniqueID()
+            : -1;
     AndroidSessionInfo value =
-      new AndroidSessionInfo(debugProcessHandler, debugDescriptor, uniqueId, currentLaunchInfo.executor.getId(), false);
+        new AndroidSessionInfo(
+            debugProcessHandler,
+            debugDescriptor,
+            uniqueId,
+            currentLaunchInfo.executor.getId(),
+            false);
     debugProcessHandler.putUserData(AndroidSessionInfo.KEY, value);
     debugProcessHandler.putUserData(AndroidSessionInfo.ANDROID_DEBUG_CLIENT, client);
-    debugProcessHandler.putUserData(AndroidSessionInfo.ANDROID_DEVICE_API_LEVEL, client.getDevice().getVersion());
+    debugProcessHandler.putUserData(
+        AndroidSessionInfo.ANDROID_DEVICE_API_LEVEL, client.getDevice().getVersion());
 
     return debugProcessHandler;
   }
diff --git a/aswb/src/com/google/idea/blaze/android/run/test/InstrumentationRunnerProvider.java b/aswb/src/com/google/idea/blaze/android/run/test/InstrumentationRunnerProvider.java
index 3d7b435..3c90aba 100644
--- a/aswb/src/com/google/idea/blaze/android/run/test/InstrumentationRunnerProvider.java
+++ b/aswb/src/com/google/idea/blaze/android/run/test/InstrumentationRunnerProvider.java
@@ -16,16 +16,13 @@
 package com.google.idea.blaze.android.run.test;
 
 import com.intellij.openapi.extensions.ExtensionPointName;
-
 import javax.annotation.Nullable;
 
-/**
- * Provides a default instrumentation test runner class for android test configurations.
- */
+/** Provides a default instrumentation test runner class for android test configurations. */
 public interface InstrumentationRunnerProvider {
 
   ExtensionPointName<InstrumentationRunnerProvider> EP_NAME =
-    ExtensionPointName.create("com.google.idea.blaze.android.InstrumentationRunnerProvider");
+      ExtensionPointName.create("com.google.idea.blaze.android.InstrumentationRunnerProvider");
 
   @Nullable
   static String getDefaultInstrumentationRunnerClass() {
@@ -40,5 +37,4 @@
 
   @Nullable
   String getInstrumentationRunnerClass();
-
 }
diff --git a/aswb/src/com/google/idea/blaze/android/run/test/StockAndroidTestLaunchTask.java b/aswb/src/com/google/idea/blaze/android/run/test/StockAndroidTestLaunchTask.java
index 10c3fd1..4ec111f 100644
--- a/aswb/src/com/google/idea/blaze/android/run/test/StockAndroidTestLaunchTask.java
+++ b/aswb/src/com/google/idea/blaze/android/run/test/StockAndroidTestLaunchTask.java
@@ -44,7 +44,11 @@
   @NotNull private final String testApplicationId;
   private final boolean waitForDebugger;
 
-  private StockAndroidTestLaunchTask(@NotNull BlazeAndroidTestRunConfigurationState configState, @Nullable String runner, @NotNull String testPackage, boolean waitForDebugger) {
+  private StockAndroidTestLaunchTask(
+      @NotNull BlazeAndroidTestRunConfigurationState configState,
+      @Nullable String runner,
+      @NotNull String testPackage,
+      boolean waitForDebugger) {
     this.configState = configState;
     this.instrumentationTestRunner = runner;
     this.waitForDebugger = waitForDebugger;
@@ -52,15 +56,15 @@
   }
 
   public static LaunchTask getStockTestLaunchTask(
-    @NotNull BlazeAndroidTestRunConfigurationState configState,
-    @NotNull ApplicationIdProvider applicationIdProvider,
-    boolean waitForDebugger,
-    @NotNull AndroidFacet facet,
-    @NotNull LaunchStatus launchStatus
-  ) {
-    String runner = StringUtil.isEmpty(configState.INSTRUMENTATION_RUNNER_CLASS)
-                    ? findInstrumentationRunner(facet)
-                    : configState.INSTRUMENTATION_RUNNER_CLASS;
+      @NotNull BlazeAndroidTestRunConfigurationState configState,
+      @NotNull ApplicationIdProvider applicationIdProvider,
+      boolean waitForDebugger,
+      @NotNull AndroidFacet facet,
+      @NotNull LaunchStatus launchStatus) {
+    String runner =
+        StringUtil.isEmpty(configState.getInstrumentationRunnerClass())
+            ? findInstrumentationRunner(facet)
+            : configState.getInstrumentationRunnerClass();
     String testPackage;
     try {
       testPackage = applicationIdProvider.getTestPackageName();
@@ -68,8 +72,7 @@
         launchStatus.terminateLaunch("Unable to determine test package name");
         return null;
       }
-    }
-    catch (ApkProvisionException e) {
+    } catch (ApkProvisionException e) {
       launchStatus.terminateLaunch("Unable to determine test package name");
       return null;
     }
@@ -97,12 +100,14 @@
   @Nullable
   private static String getRunnerFromManifest(@NotNull final AndroidFacet facet) {
     if (!ApplicationManager.getApplication().isReadAccessAllowed()) {
-      return ApplicationManager.getApplication().runReadAction(new Computable<String>() {
-        @Override
-        public String compute() {
-          return getRunnerFromManifest(facet);
-        }
-      });
+      return ApplicationManager.getApplication()
+          .runReadAction(
+              new Computable<String>() {
+                @Override
+                public String compute() {
+                  return getRunnerFromManifest(facet);
+                }
+              });
     }
 
     Manifest manifest = facet.getManifest();
@@ -119,7 +124,6 @@
     return null;
   }
 
-
   @NotNull
   @Override
   public String getDescription() {
@@ -132,39 +136,44 @@
   }
 
   @Override
-  public boolean perform(@NotNull IDevice device, @NotNull final LaunchStatus launchStatus, @NotNull final ConsolePrinter printer) {
+  public boolean perform(
+      @NotNull IDevice device,
+      @NotNull final LaunchStatus launchStatus,
+      @NotNull final ConsolePrinter printer) {
     printer.stdout("Running tests\n");
 
-    final RemoteAndroidTestRunner runner = new RemoteAndroidTestRunner(testApplicationId, instrumentationTestRunner, device);
-    switch (configState.TESTING_TYPE) {
+    final RemoteAndroidTestRunner runner =
+        new RemoteAndroidTestRunner(testApplicationId, instrumentationTestRunner, device);
+    switch (configState.getTestingType()) {
       case BlazeAndroidTestRunConfigurationState.TEST_ALL_IN_PACKAGE:
-        runner.setTestPackageName(configState.PACKAGE_NAME);
+        runner.setTestPackageName(configState.getPackageName());
         break;
       case BlazeAndroidTestRunConfigurationState.TEST_CLASS:
-        runner.setClassName(configState.CLASS_NAME);
+        runner.setClassName(configState.getClassName());
         break;
       case BlazeAndroidTestRunConfigurationState.TEST_METHOD:
-        runner.setMethodName(configState.CLASS_NAME, configState.METHOD_NAME);
+        runner.setMethodName(configState.getClassName(), configState.getMethodName());
         break;
     }
     runner.setDebug(waitForDebugger);
-    runner.setRunOptions(configState.EXTRA_OPTIONS);
+    runner.setRunOptions(configState.getExtraOptions());
 
     printer.stdout("$ adb shell " + runner.getAmInstrumentCommand());
 
     // run in a separate thread as this will block until the tests complete
-    ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
-      @Override
-      public void run() {
-        try {
-          runner.run(new AndroidTestListener(launchStatus, printer));
-        }
-        catch (Exception e) {
-          LOG.info(e);
-          printer.stderr("Error: Unexpected exception while running tests: " + e);
-        }
-      }
-    });
+    ApplicationManager.getApplication()
+        .executeOnPooledThread(
+            new Runnable() {
+              @Override
+              public void run() {
+                try {
+                  runner.run(new AndroidTestListener(launchStatus, printer));
+                } catch (Exception e) {
+                  LOG.info(e);
+                  printer.stderr("Error: Unexpected exception while running tests: " + e);
+                }
+              }
+            });
 
     return true;
   }
diff --git a/aswb/src/com/google/idea/blaze/android/sdk/AndroidSdkListener.java b/aswb/src/com/google/idea/blaze/android/sdk/AndroidSdkListener.java
index b58f3f5..4a6a6d1 100644
--- a/aswb/src/com/google/idea/blaze/android/sdk/AndroidSdkListener.java
+++ b/aswb/src/com/google/idea/blaze/android/sdk/AndroidSdkListener.java
@@ -19,13 +19,10 @@
 import com.google.idea.blaze.base.settings.Blaze;
 import com.google.idea.blaze.base.sync.status.BlazeSyncStatus;
 import com.intellij.openapi.project.Project;
+import java.io.File;
 import org.jetbrains.annotations.NotNull;
 
-import java.io.File;
-
-/**
- * Listens for android SDK changes, and queues up a blaze sync
- */
+/** Listens for android SDK changes, and queues up a blaze sync */
 public class AndroidSdkListener implements IdeSdks.AndroidSdkEventListener {
 
   @Override
diff --git a/aswb/src/com/google/idea/blaze/android/sdk/SdkUtil.java b/aswb/src/com/google/idea/blaze/android/sdk/SdkUtil.java
index 2c56c29..a48166a 100644
--- a/aswb/src/com/google/idea/blaze/android/sdk/SdkUtil.java
+++ b/aswb/src/com/google/idea/blaze/android/sdk/SdkUtil.java
@@ -26,17 +26,17 @@
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-/**
- * SDK utilities.
- */
+/** SDK utilities. */
 public class SdkUtil {
   @Nullable
   public static AndroidPlatform getAndroidPlatform(@NotNull Project project) {
-    BlazeProjectData blazeProjectData = BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
+    BlazeProjectData blazeProjectData =
+        BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
     if (blazeProjectData == null) {
       return null;
     }
-    AndroidSdkPlatform androidSdkPlatform = AndroidSdkPlatformSyncer.getAndroidSdkPlatform(blazeProjectData);
+    AndroidSdkPlatform androidSdkPlatform =
+        AndroidSdkPlatformSyncer.getAndroidSdkPlatform(blazeProjectData);
     if (androidSdkPlatform == null) {
       return null;
     }
diff --git a/aswb/src/com/google/idea/blaze/android/settings/AswbGlobalSettings.java b/aswb/src/com/google/idea/blaze/android/settings/AswbGlobalSettings.java
index 53992b0..9532859 100644
--- a/aswb/src/com/google/idea/blaze/android/settings/AswbGlobalSettings.java
+++ b/aswb/src/com/google/idea/blaze/android/settings/AswbGlobalSettings.java
@@ -15,17 +15,15 @@
  */
 package com.google.idea.blaze.android.settings;
 
-import com.intellij.openapi.components.*;
+import com.intellij.openapi.components.PersistentStateComponent;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.components.State;
+import com.intellij.openapi.components.Storage;
 import com.intellij.util.xmlb.XmlSerializerUtil;
 import org.jetbrains.annotations.Nullable;
 
-/**
- * Stores aswb global settings.
- */
-@State(
-  name = "AswbGlobalSettings",
-  storages = @Storage(file = StoragePathMacros.APP_CONFIG + "/aswb.global.xml")
-)
+/** Stores aswb global settings. */
+@State(name = "AswbGlobalSettings", storages = @Storage("aswb.global.xml"))
 public class AswbGlobalSettings implements PersistentStateComponent<AswbGlobalSettings> {
 
   private String localSdkLocation;
diff --git a/aswb/src/com/google/idea/blaze/android/sync/AndroidPrefetchFileSource.java b/aswb/src/com/google/idea/blaze/android/sync/AndroidPrefetchFileSource.java
new file mode 100644
index 0000000..58f99de
--- /dev/null
+++ b/aswb/src/com/google/idea/blaze/android/sync/AndroidPrefetchFileSource.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.android.sync;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.idea.blaze.android.sync.model.BlazeAndroidSyncData;
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.prefetch.PrefetchFileSource;
+import com.intellij.openapi.project.Project;
+import java.io.File;
+import java.util.Collection;
+import java.util.Set;
+
+/** Adds the resource directories outside our source roots to prefetch. */
+public class AndroidPrefetchFileSource implements PrefetchFileSource {
+  @Override
+  public void addFilesToPrefetch(
+      Project project, BlazeProjectData blazeProjectData, Collection<File> files) {
+    BlazeAndroidSyncData syncData = blazeProjectData.syncState.get(BlazeAndroidSyncData.class);
+    if (syncData == null) {
+      return;
+    }
+    if (syncData.importResult.resourceLibrary == null) {
+      return;
+    }
+    files.addAll(syncData.importResult.resourceLibrary.sources);
+  }
+
+  @Override
+  public Set<String> prefetchSrcFileExtensions() {
+    return ImmutableSet.of("xml");
+  }
+}
diff --git a/aswb/src/com/google/idea/blaze/android/sync/AndroidSdkPlatformSyncer.java b/aswb/src/com/google/idea/blaze/android/sync/AndroidSdkPlatformSyncer.java
index 2ee6d56..c219263 100644
--- a/aswb/src/com/google/idea/blaze/android/sync/AndroidSdkPlatformSyncer.java
+++ b/aswb/src/com/google/idea/blaze/android/sync/AndroidSdkPlatformSyncer.java
@@ -15,7 +15,11 @@
  */
 package com.google.idea.blaze.android.sync;
 
+import com.android.tools.idea.startup.AndroidStudioInitializer;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
 import com.google.idea.blaze.android.projectview.AndroidSdkPlatformSection;
 import com.google.idea.blaze.android.settings.AswbGlobalSettings;
 import com.google.idea.blaze.android.sync.model.AndroidSdkPlatform;
@@ -23,73 +27,95 @@
 import com.google.idea.blaze.base.model.BlazeProjectData;
 import com.google.idea.blaze.base.projectview.ProjectViewManager;
 import com.google.idea.blaze.base.projectview.ProjectViewSet;
-import com.google.idea.blaze.base.projectview.section.ScalarSection;
 import com.google.idea.blaze.base.scope.BlazeContext;
 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.intellij.openapi.project.Project;
 import com.intellij.openapi.projectRoots.Sdk;
+import java.io.File;
+import java.util.List;
+import javax.annotation.Nullable;
 import org.jetbrains.android.sdk.AndroidPlatform;
 import org.jetbrains.android.sdk.AndroidSdkAdditionalData;
 import org.jetbrains.android.sdk.AndroidSdkUtils;
 
-import javax.annotation.Nullable;
-import java.io.File;
-import java.util.Collection;
-
-/**
- * Calculates AndroidSdkPlatform.
- */
+/** Calculates AndroidSdkPlatform. */
 public class AndroidSdkPlatformSyncer {
   @Nullable
-  static AndroidSdkPlatform getAndroidSdkPlatform(
-    Project project,
-    BlazeContext context,
-    File androidPlatformDirectory) {
+  static AndroidSdkPlatform getAndroidSdkPlatform(Project project, BlazeContext context) {
 
-    String androidSdk = null;
-
-    String localSdkLocation = AswbGlobalSettings.getInstance().getLocalSdkLocation();
-    if (localSdkLocation == null) {
-      IssueOutput
-        .error("Error: No android_sdk synced yet. Please sync SDK following go/aswb-sdk.")
-        .submit(context);
-    }
-
-    ProjectViewSet projectViewSet = ProjectViewManager.getInstance(project).getProjectViewSet();
-    if (projectViewSet != null) {
-      Collection<ScalarSection<String>> androidSdkPlatformSections =
-        projectViewSet.getSections(AndroidSdkPlatformSection.KEY);
-      if (!androidSdkPlatformSections.isEmpty()) {
-        ScalarSection<String> androidSdkPlatformSection = Iterables.getLast(androidSdkPlatformSections);
-        androidSdk = BlazeAndroidSdk.getAndroidSdkLevelFromLocalChannel(
-          localSdkLocation,
-          androidSdkPlatformSection.getValue());
-
-        if (androidSdk == null) {
-          IssueOutput
-            .error("No such android_sdk_platform: " + androidSdkPlatformSection.getValue())
-            .inFile(projectViewSet.getTopLevelProjectViewFile().projectViewFile)
+    final String localSdkLocation;
+    if (AndroidStudioInitializer.isAndroidSdkManagerEnabled()) {
+      Sdk sdk = Iterables.getFirst(AndroidSdkUtils.getAllAndroidSdks(), null);
+      if (sdk == null) {
+        IssueOutput.error(
+                "Error: No Android SDK configured. Please use the SDK manager to configure.")
             .submit(context);
-        }
+        return null;
+      }
+      localSdkLocation = sdk.getHomePath();
+    } else {
+      localSdkLocation = AswbGlobalSettings.getInstance().getLocalSdkLocation();
+      if (localSdkLocation == null) {
+        IssueOutput.error(
+                "Error: No Android SDK synced yet."
+                    + (Blaze.defaultBuildSystem() == BuildSystem.Blaze
+                        ? " Please sync SDK following go/aswb-sdk."
+                        : ""))
+            .submit(context);
+        return null;
       }
     }
 
-    if (androidSdk == null) {
-      androidSdk = BlazeAndroidSdk.getAndroidSdkLevelFromBlazeRc(androidPlatformDirectory);
+    String androidSdkPlatform = null;
+    ProjectViewSet projectViewSet = ProjectViewManager.getInstance(project).getProjectViewSet();
+    if (projectViewSet != null) {
+      androidSdkPlatform = projectViewSet.getScalarValue(AndroidSdkPlatformSection.KEY);
     }
 
+    // This is verified in the project view verification step, but double-check here
+    if (androidSdkPlatform == null) {
+      IssueOutput.error(
+              "No android_sdk_platform set. Please ensure this is set to a platform SDK directory.")
+          .submit(context);
+      return null;
+    }
+
+    String androidSdk =
+        BlazeAndroidSdk.getAndroidSdkLevelFromLocalChannel(localSdkLocation, androidSdkPlatform);
+
     if (androidSdk == null) {
-      IssueOutput
-        .error("Can't determine your SDK. Please sync your SDK by following go/aswb-sdk and try again.")
-        .submit(context);
+      IssueOutput.error(
+              Joiner.on("\n")
+                  .join(
+                      "No such android_sdk_platform: " + androidSdkPlatform,
+                      "Available android_sdk_platforms are: "
+                          + getAvailableSdkPlatforms(localSdkLocation)))
+          .inFile(projectViewSet.getTopLevelProjectViewFile().projectViewFile)
+          .submit(context);
       return null;
     }
 
     Sdk sdk = AndroidSdkUtils.findSuitableAndroidSdk(androidSdk);
     if (sdk == null) {
-      IssueOutput
-        .error("Can't find a matching SDK. Please sync your SDK by following go/aswb-sdk and try again.")
-        .submit(context);
+      ImmutableList.Builder<String> error =
+          ImmutableList.<String>builder()
+              .add(
+                  String.format(
+                      "Can't find a matching SDK "
+                          + "(was looking for '%s' in the '%s' platform directory).",
+                      androidSdk, androidSdkPlatform),
+                  "Available android_sdk_platforms are: "
+                      + getAvailableSdkPlatforms(localSdkLocation));
+      if (Blaze.defaultBuildSystem() == BuildSystem.Blaze) {
+        error.add(
+            "If you have no SDK, please sync your SDK by following go/aswb-sdk and try again. ",
+            "If you have done everything correctly, this can be due to an SDK sync manager bug.",
+            "To workaround, please delete ~/.AndroidStudioWithBlazeXX/system and restart");
+      }
+
+      IssueOutput.error(String.join("\n", error.build())).submit(context);
       return null;
     }
 
@@ -97,8 +123,28 @@
     return new AndroidSdkPlatform(androidSdk, androidSdkApiLevel);
   }
 
+  private static String getAvailableSdkPlatforms(String localSdkDirectoryString) {
+    File localSdkDirectory = new File(localSdkDirectoryString);
+    if (localSdkDirectory.exists()) {
+      File platformDirectory = new File(localSdkDirectory, "platforms");
+      if (platformDirectory.exists()) {
+        File[] children = platformDirectory.listFiles();
+        if (children != null) {
+          List<String> names = Lists.newArrayList();
+          for (File child : children) {
+            if (child.isDirectory()) {
+              names.add('"' + child.getName() + '"');
+            }
+          }
+          return "{" + Joiner.on(", ").join(names) + "}";
+        }
+      }
+    }
+    return "<No platforms found>";
+  }
+
   @Nullable
-  static public AndroidSdkPlatform getAndroidSdkPlatform(BlazeProjectData blazeProjectData) {
+  public static AndroidSdkPlatform getAndroidSdkPlatform(BlazeProjectData blazeProjectData) {
     BlazeAndroidSyncData syncData = blazeProjectData.syncState.get(BlazeAndroidSyncData.class);
     return syncData != null ? syncData.androidSdkPlatform : null;
   }
@@ -107,7 +153,8 @@
     int androidSdkApiLevel = 1;
     Sdk sdk = AndroidSdkUtils.findSuitableAndroidSdk(androidSdk);
     if (sdk != null) {
-      AndroidSdkAdditionalData additionalData = (AndroidSdkAdditionalData)sdk.getSdkAdditionalData();
+      AndroidSdkAdditionalData additionalData =
+          (AndroidSdkAdditionalData) sdk.getSdkAdditionalData();
       if (additionalData != null) {
         AndroidPlatform androidPlatform = additionalData.getAndroidPlatform();
         if (androidPlatform != null) {
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 ea02f53..96df410 100644
--- a/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidJavaSyncAugmenter.java
+++ b/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidJavaSyncAugmenter.java
@@ -16,26 +16,59 @@
 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.experiments.BoolExperiment;
+import com.google.idea.blaze.base.ideinfo.AndroidRuleIdeInfo;
+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.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 implements BlazeJavaSyncAugmenter {
-  private static final BoolExperiment EXCLUDE_ANDROID_BLAZE_JAR = new BoolExperiment("exclude.android.blaze.jar", true);
+/** Augments the java sync process with Android support. */
+public class BlazeAndroidJavaSyncAugmenter extends BlazeJavaSyncAugmenter.Adapter {
+
+  @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) {
+      return;
+    }
+    LibraryArtifact idlJar = androidRuleIdeInfo.idlJar;
+    if (idlJar != null) {
+      genJars.add(new BlazeJarLibrary(idlJar, rule.label));
+    }
+
+    if (BlazeAndroidWorkspaceImporter.shouldGenerateResources(androidRuleIdeInfo)
+        && !BlazeAndroidWorkspaceImporter.shouldGenerateResourceModule(androidRuleIdeInfo)) {
+      // 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);
+      if (!discardResourceJar) {
+        LibraryArtifact resourceJar = androidRuleIdeInfo.resourceJar;
+        if (resourceJar != null) {
+          jars.add(new BlazeJarLibrary(resourceJar, rule.label));
+        }
+      }
+    }
+  }
 
   @Override
   public void addLibraryFilter(Glob.GlobSet excludedLibraries) {
-    if (EXCLUDE_ANDROID_BLAZE_JAR.getValue()) {
-      excludedLibraries.add(new Glob("*/android_blaze.jar")); // This is supplied via the SDK
-    }
+    excludedLibraries.add(new Glob("*/android_blaze.jar")); // This is supplied via the SDK
   }
 
   @Override
@@ -44,11 +77,10 @@
     if (syncData == null) {
       return ImmutableList.of();
     }
-    return syncData.importResult.libraries;
-  }
-
-  @Override
-  public Collection<String> getExternallyAddedLibraries(BlazeProjectData blazeProjectData) {
-    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/BlazeAndroidSdk.java b/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidSdk.java
index 826e1c4..e9e838e 100644
--- a/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidSdk.java
+++ b/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidSdk.java
@@ -19,53 +19,29 @@
 import com.android.sdklib.AndroidTargetHash;
 import com.android.sdklib.AndroidVersion;
 import com.android.sdklib.AndroidVersionHelper;
-import com.android.tools.idea.sdk.IdeSdks;
-import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.diagnostic.Logger;
-
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.Properties;
-
 import javax.annotation.Nullable;
 
-/**
- * Utility methods for handling the android sdk.
- */
+/** Utility methods for handling the android sdk. */
 public final class BlazeAndroidSdk {
   private static final Logger LOG = Logger.getInstance(BlazeAndroidSdk.class);
 
-  private BlazeAndroidSdk() {
-  }
+  private BlazeAndroidSdk() {}
 
-  /**
-   * Reads the android sdk level from your local SDK directory.
-   */
-  public static String getAndroidSdkLevelFromLocalChannel(String localSdkLocation,
-      String androidSdkPlatform) {
-    File androidSdkPlatformsDir = new File(new File(new File(localSdkLocation), "platforms"), androidSdkPlatform);
+  /** Reads the android sdk level from your local SDK directory. */
+  public static String getAndroidSdkLevelFromLocalChannel(
+      String localSdkLocation, String androidSdkPlatform) {
+    File androidSdkPlatformsDir =
+        new File(new File(new File(localSdkLocation), "platforms"), androidSdkPlatform);
     File sourcePropertiesFile = new File(androidSdkPlatformsDir, SdkConstants.FN_SOURCE_PROP);
     return getAndroidSdkLevelFromSourceProperties(sourcePropertiesFile);
   }
 
-  /**
-   * The user's blazerc is read to discover the --android_sdk configuration. The resulting
-   * label can be an indirection (eg. "latest" or "prerelease"), so blaze query is used to
-   * query for "android_blaze.jar", which marks the actual android sdk directory.
-   * <p/>
-   * <p>This directory should have a source.properties file. This is read to discover the
-   * platform API level (eg. "21"). This is hashed according to ADT standards to return
-   * a unique SDK key, eg "android-21".
-   */
-  @Nullable
-  public static String getAndroidSdkLevelFromBlazeRc(
-    File androidPlatformDir) {
-    File sourcePropertiesFile = new File(androidPlatformDir, SdkConstants.FN_SOURCE_PROP);
-    return getAndroidSdkLevelFromSourceProperties(sourcePropertiesFile);
-  }
-
   @Nullable
   public static String getAndroidSdkLevelFromSourceProperties(File sourcePropertiesFile) {
     if (!sourcePropertiesFile.exists()) {
@@ -73,7 +49,7 @@
     }
 
     AndroidVersion androidVersion =
-      readAndroidVersionFromSourcePropertiesFile(sourcePropertiesFile);
+        readAndroidVersionFromSourcePropertiesFile(sourcePropertiesFile);
     if (androidVersion == null) {
       LOG.warn("Could not read source.properties from: " + sourcePropertiesFile);
       return null;
@@ -82,7 +58,8 @@
   }
 
   @Nullable
-  private static AndroidVersion readAndroidVersionFromSourcePropertiesFile(File sourcePropertiesFile) {
+  private static AndroidVersion readAndroidVersionFromSourcePropertiesFile(
+      File sourcePropertiesFile) {
     Properties props = parseProperties(sourcePropertiesFile);
     if (props == null) {
       return null;
@@ -95,8 +72,8 @@
   }
 
   /**
-   * Parses the given file as properties file if it exists.
-   * Returns null if the file does not exist, cannot be parsed or has no properties.
+   * Parses the given file as properties file if it exists. Returns null if the file does not exist,
+   * cannot be parsed or has no properties.
    */
   @Nullable
   private static Properties parseProperties(File propsFile) {
@@ -116,5 +93,4 @@
     }
     return null;
   }
-
 }
diff --git a/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidSyncListener.java b/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidSyncListener.java
index 6fa349a..91d6b57 100644
--- a/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidSyncListener.java
+++ b/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidSyncListener.java
@@ -16,31 +16,15 @@
 package com.google.idea.blaze.android.sync;
 
 import com.android.tools.idea.res.ResourceFolderRegistry;
-import com.google.idea.blaze.base.model.BlazeProjectData;
-import com.google.idea.blaze.base.projectview.ProjectViewSet;
-import com.google.idea.blaze.base.settings.BlazeImportSettings;
 import com.google.idea.blaze.base.sync.SyncListener;
 import com.intellij.openapi.project.DumbService;
 import com.intellij.openapi.project.Project;
 
-/**
- * Android-specific hooks to run after a blaze sync.
- */
-public class BlazeAndroidSyncListener implements SyncListener {
+/** Android-specific hooks to run after a blaze sync. */
+public class BlazeAndroidSyncListener extends SyncListener.Adapter {
   @Override
-  public void onSyncStart(Project project) {
-  }
-
-  @Override
-  public void onSyncComplete(Project project,
-                             BlazeImportSettings importSettings,
-                             ProjectViewSet projectViewSet,
-                             BlazeProjectData blazeProjectData) {
-  }
-
-  @Override
-  public void afterSync(Project project, boolean successful) {
-    if (successful) {
+  public void afterSync(Project project, SyncResult syncResult) {
+    if (syncResult == SyncResult.SUCCESS || syncResult == SyncResult.PARTIAL_SUCCESS) {
       DumbService dumbService = DumbService.getInstance(project);
       dumbService.queueTask(new ResourceFolderRegistry.PopulateCachesTask(project));
     }
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 2760980..03d8f1b 100644
--- a/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidSyncPlugin.java
+++ b/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidSyncPlugin.java
@@ -16,8 +16,8 @@
 package com.google.idea.blaze.android.sync;
 
 import com.google.common.base.Joiner;
+import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.idea.blaze.android.cppapi.NdkSupport;
 import com.google.idea.blaze.android.projectview.AndroidSdkPlatformSection;
@@ -26,15 +26,13 @@
 import com.google.idea.blaze.android.sync.model.BlazeAndroidImportResult;
 import com.google.idea.blaze.android.sync.model.BlazeAndroidSyncData;
 import com.google.idea.blaze.android.sync.projectstructure.BlazeAndroidProjectStructureSyncer;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
 import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.model.RuleMap;
 import com.google.idea.blaze.base.model.SyncState;
-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.WorkspaceRoot;
 import com.google.idea.blaze.base.model.primitives.WorkspaceType;
 import com.google.idea.blaze.base.projectview.ProjectViewSet;
-import com.google.idea.blaze.base.projectview.section.ScalarSection;
 import com.google.idea.blaze.base.projectview.section.SectionParser;
 import com.google.idea.blaze.base.scope.BlazeContext;
 import com.google.idea.blaze.base.scope.Scope;
@@ -58,17 +56,13 @@
 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.Set;
+import javax.annotation.Nullable;
 import org.jetbrains.android.facet.AndroidFacet;
 import org.jetbrains.android.sdk.AndroidSdkUtils;
 
-import javax.annotation.Nullable;
-import java.io.File;
-import java.util.Collection;
-import java.util.Set;
-
-/**
- * ASwB sync plugin.
- */
+/** ASwB sync plugin. */
 public class BlazeAndroidSyncPlugin extends BlazeSyncPlugin.Adapter {
 
   @Nullable
@@ -94,8 +88,7 @@
       case ANDROID_NDK:
         if (NdkSupport.NDK_SUPPORT.getValue()) {
           return ImmutableSet.of(LanguageClass.ANDROID, LanguageClass.JAVA, LanguageClass.C);
-        }
-        else {
+        } else {
           return ImmutableSet.of(LanguageClass.ANDROID, LanguageClass.JAVA);
         }
       default:
@@ -104,43 +97,43 @@
   }
 
   @Override
-  public void updateSyncState(Project project,
-                              BlazeContext context,
-                              WorkspaceRoot workspaceRoot,
-                              ProjectViewSet projectViewSet,
-                              WorkspaceLanguageSettings workspaceLanguageSettings,
-                              BlazeRoots blazeRoots,
-                              @Nullable WorkingSet workingSet,
-                              WorkspacePathResolver workspacePathResolver,
-                              ImmutableMap<Label, RuleIdeInfo> ruleMap,
-                              @Deprecated @Nullable File androidPlatformDirectory,
-                              SyncState.Builder syncStateBuilder,
-                              @Nullable SyncState previousSyncState) {
+  public void updateSyncState(
+      Project project,
+      BlazeContext context,
+      WorkspaceRoot workspaceRoot,
+      ProjectViewSet projectViewSet,
+      WorkspaceLanguageSettings workspaceLanguageSettings,
+      BlazeRoots blazeRoots,
+      @Nullable WorkingSet workingSet,
+      WorkspacePathResolver workspacePathResolver,
+      RuleMap ruleMap,
+      SyncState.Builder syncStateBuilder,
+      @Nullable SyncState previousSyncState) {
     if (!isAndroidWorkspace(workspaceLanguageSettings)) {
       return;
     }
 
-    AndroidSdkPlatform androidSdkPlatform = AndroidSdkPlatformSyncer.getAndroidSdkPlatform(project, context, androidPlatformDirectory);
-    BlazeAndroidWorkspaceImporter workspaceImporter = new BlazeAndroidWorkspaceImporter(
-      project,
-      context,
-      workspaceRoot,
-      projectViewSet,
-      ruleMap
-    );
-    BlazeAndroidImportResult importResult = Scope.push(context, (childContext) -> {
-      childContext.push(new TimingScope("AndroidWorkspaceImporter"));
-      return workspaceImporter.importWorkspace();
-    });
+    AndroidSdkPlatform androidSdkPlatform =
+        AndroidSdkPlatformSyncer.getAndroidSdkPlatform(project, context);
+    BlazeAndroidWorkspaceImporter workspaceImporter =
+        new BlazeAndroidWorkspaceImporter(project, context, workspaceRoot, projectViewSet, ruleMap);
+    BlazeAndroidImportResult importResult =
+        Scope.push(
+            context,
+            (childContext) -> {
+              childContext.push(new TimingScope("AndroidWorkspaceImporter"));
+              return workspaceImporter.importWorkspace();
+            });
     BlazeAndroidSyncData syncData = new BlazeAndroidSyncData(importResult, androidSdkPlatform);
     syncStateBuilder.put(BlazeAndroidSyncData.class, syncData);
   }
 
   @Override
-  public void updateSdk(Project project,
-                        BlazeContext context,
-                        ProjectViewSet projectViewSet,
-                        BlazeProjectData blazeProjectData) {
+  public void updateSdk(
+      Project project,
+      BlazeContext context,
+      ProjectViewSet projectViewSet,
+      BlazeProjectData blazeProjectData) {
     if (!isAndroidWorkspace(blazeProjectData.workspaceLanguageSettings)) {
       return;
     }
@@ -154,53 +147,52 @@
     }
     Sdk sdk = AndroidSdkUtils.findSuitableAndroidSdk(androidSdkPlatform.androidSdk);
     if (sdk == null) {
-      IssueOutput
-        .error(String.format("Android platform '%s' not found.", androidSdkPlatform.androidSdk))
-        .submit(context);
+      IssueOutput.error(
+              String.format("Android platform '%s' not found.", androidSdkPlatform.androidSdk))
+          .submit(context);
       return;
     }
 
-    LanguageLevel javaLanguageLevel = JavaLanguageLevelSection.getLanguageLevel(projectViewSet, LanguageLevel.JDK_1_7);
+    LanguageLevel javaLanguageLevel =
+        JavaLanguageLevelSection.getLanguageLevel(projectViewSet, LanguageLevel.JDK_1_7);
     setProjectSdkAndLanguageLevel(project, sdk, javaLanguageLevel);
   }
 
   @Override
-  public void updateProjectStructure(Project project,
-                                     BlazeContext context,
-                                     WorkspaceRoot workspaceRoot,
-                                     ProjectViewSet projectViewSet,
-                                     BlazeProjectData blazeProjectData,
-                                     @Nullable BlazeProjectData oldBlazeProjectData,
-                                     ModuleEditor moduleEditor,
-                                     Module workspaceModule,
-                                     ModifiableRootModel workspaceModifiableModel) {
+  public void updateProjectStructure(
+      Project project,
+      BlazeContext context,
+      WorkspaceRoot workspaceRoot,
+      ProjectViewSet projectViewSet,
+      BlazeProjectData blazeProjectData,
+      @Nullable BlazeProjectData oldBlazeProjectData,
+      ModuleEditor moduleEditor,
+      Module workspaceModule,
+      ModifiableRootModel workspaceModifiableModel) {
     BlazeAndroidProjectStructureSyncer.updateProjectStructure(
-      project,
-      context,
-      workspaceRoot,
-      projectViewSet,
-      blazeProjectData,
-      moduleEditor,
-      workspaceModule,
-      workspaceModifiableModel,
-      isAndroidWorkspace(blazeProjectData.workspaceLanguageSettings)
-    );
+        project,
+        context,
+        workspaceRoot,
+        projectViewSet,
+        blazeProjectData,
+        moduleEditor,
+        workspaceModule,
+        workspaceModifiableModel,
+        isAndroidWorkspace(blazeProjectData.workspaceLanguageSettings));
   }
 
   @Override
-  public boolean validate(Project project,
-                          BlazeContext context,
-                          BlazeProjectData blazeProjectData) {
+  public boolean validate(
+      Project project, BlazeContext context, BlazeProjectData blazeProjectData) {
     if (!isAndroidWorkspace(blazeProjectData.workspaceLanguageSettings)) {
       return true;
     }
-    
+
     boolean valid = true;
     for (Module module : ModuleManager.getInstance(project).getModules()) {
       AndroidFacet facet = AndroidFacet.getInstance(module);
       if (facet != null && facet.requiresAndroidModel() && facet.getAndroidModel() == null) {
-        IssueOutput.error("Android model missing for module: " + module.getName())
-          .submit(context);
+        IssueOutput.error("Android model missing for module: " + module.getName()).submit(context);
         valid = false;
       }
     }
@@ -208,70 +200,61 @@
   }
 
   @Override
-  public boolean validateProjectView(BlazeContext context,
-                                     ProjectViewSet projectViewSet,
-                                     WorkspaceLanguageSettings workspaceLanguageSettings) {
+  public boolean validateProjectView(
+      BlazeContext context,
+      ProjectViewSet projectViewSet,
+      WorkspaceLanguageSettings workspaceLanguageSettings) {
     if (!isAndroidWorkspace(workspaceLanguageSettings)) {
       return true;
     }
 
-    if (workspaceLanguageSettings.isWorkspaceType(WorkspaceType.ANDROID_NDK) && !NdkSupport.NDK_SUPPORT.getValue()) {
-      IssueOutput
-        .error("Android NDK is not supported yet.")
-        .submit(context);
+    if (workspaceLanguageSettings.isWorkspaceType(WorkspaceType.ANDROID_NDK)
+        && !NdkSupport.NDK_SUPPORT.getValue()) {
+      IssueOutput.error("Android NDK is not supported yet.").submit(context);
       return false;
     }
 
-    Collection<ScalarSection<String>> androidSdkPlatformSections =
-      projectViewSet.getSections(AndroidSdkPlatformSection.KEY);
-    if (androidSdkPlatformSections.isEmpty()) {
-      String error = Joiner.on('\n').join(
-        "You should specify the android SDK platform in your '.asproject' file.",
-        "To set this, first ensure your SDK is up-to-date (go/aswb-sdk)",
-        "Then add an 'android_sdk_platform' line to your .asproject file,",
-        "e.g. 'android_sdk_platform: \"android-N\"', where 'android-N' is a",
-        "platform directory name in your local SDK directory.",
-        "",
-        "NOTE: This will become an error starting from the next release."
-      );
-      IssueOutput
-        .warn(error)
-        .inFile(projectViewSet.getTopLevelProjectViewFile().projectViewFile)
-        .submit(context);
+    String androidSdkPlatform = projectViewSet.getScalarValue(AndroidSdkPlatformSection.KEY);
+    if (Strings.isNullOrEmpty(androidSdkPlatform)) {
+      String error =
+          Joiner.on('\n')
+              .join(
+                  "No android_sdk_platform set.",
+                  "You should specify the android SDK platform in your '.blazeproject' file.",
+                  "To set this add an 'android_sdk_platform' line to your .blazeproject file,",
+                  "e.g. 'android_sdk_platform: \"android-N\"', where 'android-N' is a",
+                  "platform directory name in your local SDK directory.");
+      IssueOutput.error(error)
+          .inFile(projectViewSet.getTopLevelProjectViewFile().projectViewFile)
+          .submit(context);
     }
     return true;
   }
 
   private static void setProjectSdkAndLanguageLevel(
-    final Project project,
-    final Sdk sdk,
-    final LanguageLevel javaLanguageLevel) {
-    UIUtil.invokeAndWaitIfNeeded((Runnable)() -> ApplicationManager.getApplication().runWriteAction(() -> {
-      ProjectRootManagerEx rootManager = ProjectRootManagerEx.getInstanceEx(project);
-      rootManager.setProjectSdk(sdk);
-      LanguageLevelProjectExtension ext = LanguageLevelProjectExtension.getInstance(project);
-      ext.setLanguageLevel(javaLanguageLevel);
-    }));
+      final Project project, final Sdk sdk, final LanguageLevel javaLanguageLevel) {
+    UIUtil.invokeAndWaitIfNeeded(
+        (Runnable)
+            () ->
+                ApplicationManager.getApplication()
+                    .runWriteAction(
+                        () -> {
+                          ProjectRootManagerEx rootManager =
+                              ProjectRootManagerEx.getInstanceEx(project);
+                          rootManager.setProjectSdk(sdk);
+                          LanguageLevelProjectExtension ext =
+                              LanguageLevelProjectExtension.getInstance(project);
+                          ext.setLanguageLevel(javaLanguageLevel);
+                        }));
   }
 
   @Override
   public Collection<SectionParser> getSections() {
-    return ImmutableList.of(
-      AndroidSdkPlatformSection.PARSER
-    );
-  }
-
-  @Override
-  public boolean requiresAndroidSdk(WorkspaceLanguageSettings workspaceLanguageSettings) {
-    return isAndroidWorkspace(workspaceLanguageSettings);
+    return ImmutableList.of(AndroidSdkPlatformSection.PARSER);
   }
 
   private static boolean isAndroidWorkspace(WorkspaceLanguageSettings workspaceLanguageSettings) {
-    return workspaceLanguageSettings.isWorkspaceType(WorkspaceType.ANDROID, WorkspaceType.ANDROID_NDK);
-  }
-
-  @Override
-  public Set<String> prefetchSrcFileExtensions() {
-    return ImmutableSet.of("xml");
+    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 c67da3c..0439e9b 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
@@ -15,16 +15,20 @@
  */
 package com.google.idea.blaze.android.sync.importer;
 
-import com.google.common.collect.*;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ComparisonChain;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
 import com.google.idea.blaze.android.sync.importer.aggregators.TransitiveResourceMap;
 import com.google.idea.blaze.android.sync.model.AndroidResourceModule;
 import com.google.idea.blaze.android.sync.model.BlazeAndroidImportResult;
-import com.google.idea.blaze.base.experiments.BoolExperiment;
+import com.google.idea.blaze.android.sync.model.BlazeResourceLibrary;
 import com.google.idea.blaze.base.ideinfo.AndroidRuleIdeInfo;
 import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
-import com.google.idea.blaze.base.ideinfo.LibraryArtifact;
 import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.model.primitives.Kind;
+import com.google.idea.blaze.base.model.RuleMap;
 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.WorkspaceRoot;
@@ -33,13 +37,8 @@
 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.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 org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
 import java.io.File;
 import java.util.Collection;
 import java.util.Collections;
@@ -47,130 +46,123 @@
 import java.util.Set;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
-/**
- * Builds a BlazeWorkspace.
- */
+/** Builds a BlazeWorkspace. */
 public final class BlazeAndroidWorkspaceImporter {
   private static final Logger LOG = Logger.getInstance(BlazeAndroidWorkspaceImporter.class);
-  private static final BoolExperiment DISCARD_ANDROID_BINARY_RESOURCE_JAR = new BoolExperiment("discard.android.binary.resource.jar", true);
 
   private final Project project;
   private final BlazeContext context;
   private final WorkspaceRoot workspaceRoot;
-  private final ImmutableMap<Label, RuleIdeInfo> ruleMap;
+  private final RuleMap ruleMap;
   private final ProjectViewRuleImportFilter importFilter;
-  private final boolean discardAndroidBinaryResourceJar;
 
   public BlazeAndroidWorkspaceImporter(
-    Project project,
-    BlazeContext context,
-    WorkspaceRoot workspaceRoot,
-    ProjectViewSet projectViewSet,
-    ImmutableMap<Label, RuleIdeInfo> ruleMap) {
+      Project project,
+      BlazeContext context,
+      WorkspaceRoot workspaceRoot,
+      ProjectViewSet projectViewSet,
+      RuleMap ruleMap) {
     this.project = project;
     this.context = context;
     this.workspaceRoot = workspaceRoot;
     this.ruleMap = ruleMap;
     this.importFilter = new ProjectViewRuleImportFilter(project, workspaceRoot, projectViewSet);
-    this.discardAndroidBinaryResourceJar = DISCARD_ANDROID_BINARY_RESOURCE_JAR.getValue();
   }
 
   public BlazeAndroidImportResult importWorkspace() {
-    List<RuleIdeInfo> rules = ruleMap.values()
-      .stream()
-      .filter(rule -> rule.kind.getLanguageClass() == LanguageClass.ANDROID)
-      .filter(importFilter::isSourceRule)
-      .filter(rule -> !importFilter.excludeTarget(rule))
-      .collect(Collectors.toList());
+    List<RuleIdeInfo> rules =
+        ruleMap
+            .rules()
+            .stream()
+            .filter(rule -> rule.kind.getLanguageClass() == LanguageClass.ANDROID)
+            .filter(rule -> rule.androidRuleIdeInfo != null)
+            .filter(importFilter::isSourceRule)
+            .filter(rule -> !importFilter.excludeTarget(rule))
+            .collect(Collectors.toList());
 
     TransitiveResourceMap transitiveResourceMap = new TransitiveResourceMap(ruleMap);
 
     WorkspaceBuilder workspaceBuilder = new WorkspaceBuilder();
 
     for (RuleIdeInfo rule : rules) {
-      addRule(
-        workspaceBuilder,
-        transitiveResourceMap,
-        rule
-      );
+      addSourceRule(workspaceBuilder, transitiveResourceMap, rule);
     }
 
-    ImmutableList<AndroidResourceModule> androidResourceModules = buildAndroidResourceModules(workspaceBuilder);
-    BlazeLibrary resourceLibrary = createResourceLibrary(androidResourceModules);
-    if (resourceLibrary != null) {
-      workspaceBuilder.libraries.add(resourceLibrary);
-    }
+    warnAboutGeneratedResources(workspaceBuilder.generatedResourceLocations);
 
-    return new BlazeAndroidImportResult(
-      androidResourceModules,
-      workspaceBuilder.libraries.build()
-    );
+    ImmutableList<AndroidResourceModule> androidResourceModules =
+        buildAndroidResourceModules(workspaceBuilder);
+    BlazeResourceLibrary resourceLibrary = createResourceLibrary(androidResourceModules);
+
+    return new BlazeAndroidImportResult(androidResourceModules, resourceLibrary);
   }
 
-  private void addRule(
-    WorkspaceBuilder workspaceBuilder,
-    TransitiveResourceMap transitiveResourceMap,
-    RuleIdeInfo rule) {
-
+  private void addSourceRule(
+      WorkspaceBuilder workspaceBuilder,
+      TransitiveResourceMap transitiveResourceMap,
+      RuleIdeInfo rule) {
     AndroidRuleIdeInfo androidRuleIdeInfo = rule.androidRuleIdeInfo;
-    if (androidRuleIdeInfo != null) {
-      // 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
-      if (androidRuleIdeInfo.generateResourceClass && androidRuleIdeInfo.legacyResources == null) {
-        List<ArtifactLocation> nonGeneratedResources = Lists.newArrayList();
-        for (ArtifactLocation artifactLocation : androidRuleIdeInfo.resources) {
-          if (!artifactLocation.isGenerated()) {
-            nonGeneratedResources.add(artifactLocation);
-          }
-        }
+    assert androidRuleIdeInfo != null;
+    if (shouldGenerateResources(androidRuleIdeInfo)
+        && shouldGenerateResourceModule(androidRuleIdeInfo)) {
+      AndroidResourceModule.Builder builder = new AndroidResourceModule.Builder(rule.label);
+      workspaceBuilder.androidResourceModules.add(builder);
 
-        // Only create a resource module if there are any non-generated resources
-        // Empty R classes or ones with only generated sources are added as jars
-        if (!nonGeneratedResources.isEmpty()) {
-          AndroidResourceModule.Builder builder = new AndroidResourceModule.Builder(rule.label);
-          workspaceBuilder.androidResourceModules.add(builder);
-
-          builder.addAllResources(nonGeneratedResources);
-
-          TransitiveResourceMap.TransitiveResourceInfo transitiveResourceInfo = transitiveResourceMap.get(rule.label);
-          for (ArtifactLocation artifactLocation : transitiveResourceInfo.transitiveResources) {
-            if (!artifactLocation.isGenerated()) {
-              builder.addTransitiveResource(artifactLocation);
-            }
-          }
-          for (Label resourceDependency : transitiveResourceInfo.transitiveResourceRules) {
-            if (!resourceDependency.equals(rule.label)) {
-              builder.addTransitiveResourceDependency(resourceDependency);
-            }
-          }
+      for (ArtifactLocation artifactLocation : androidRuleIdeInfo.resources) {
+        if (artifactLocation.isSource()) {
+          builder.addResource(artifactLocation);
         } else {
-          // 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 = discardAndroidBinaryResourceJar && rule.kindIsOneOf(Kind.ANDROID_BINARY, Kind.ANDROID_TEST);
-          if (!discardResourceJar) {
-            LibraryArtifact resourceJar = androidRuleIdeInfo.resourceJar;
-            if (resourceJar != null) {
-              BlazeLibrary library = new BlazeLibrary(LibraryKey.fromJarFile(resourceJar.jar.getFile()), resourceJar);
-              workspaceBuilder.libraries.add(library);
-            }
-          }
+          workspaceBuilder.generatedResourceLocations.add(artifactLocation);
         }
       }
 
-      LibraryArtifact idlJar = androidRuleIdeInfo.idlJar;
-      if (idlJar != null) {
-        BlazeLibrary library = new BlazeLibrary(LibraryKey.fromJarFile(idlJar.jar.getFile()), idlJar);
-        workspaceBuilder.libraries.add(library);
+      TransitiveResourceMap.TransitiveResourceInfo transitiveResourceInfo =
+          transitiveResourceMap.get(rule.label);
+      for (ArtifactLocation artifactLocation : transitiveResourceInfo.transitiveResources) {
+        if (artifactLocation.isSource()) {
+          builder.addTransitiveResource(artifactLocation);
+        } else {
+          workspaceBuilder.generatedResourceLocations.add(artifactLocation);
+        }
       }
+      for (Label resourceDependency : transitiveResourceInfo.transitiveResourceRules) {
+        if (!resourceDependency.equals(rule.label)) {
+          builder.addTransitiveResourceDependency(resourceDependency);
+        }
+      }
+    }
+  }
+
+  public static boolean shouldGenerateResources(AndroidRuleIdeInfo androidRuleIdeInfo) {
+    // 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;
+  }
+
+  public static boolean shouldGenerateResourceModule(AndroidRuleIdeInfo androidRuleIdeInfo) {
+    return androidRuleIdeInfo.resources.stream().anyMatch(ArtifactLocation::isSource);
+  }
+
+  private void warnAboutGeneratedResources(Set<ArtifactLocation> generatedResourceLocations) {
+    for (ArtifactLocation artifactLocation : generatedResourceLocations) {
+      IssueOutput.warn(
+              String.format(
+                  "Dropping generated resource directory '%s', "
+                      + "R classes will not contain resources from this directory",
+                  artifactLocation.getExecutionRootRelativePath()))
+          .submit(context);
     }
   }
 
   @Nullable
-  private BlazeLibrary createResourceLibrary(Collection<AndroidResourceModule> androidResourceModules) {
+  private BlazeResourceLibrary createResourceLibrary(
+      Collection<AndroidResourceModule> androidResourceModules) {
     Set<File> result = Sets.newHashSet();
     for (AndroidResourceModule androidResourceModule : androidResourceModules) {
       result.addAll(androidResourceModule.transitiveResources);
@@ -179,50 +171,57 @@
       result.removeAll(androidResourceModule.resources);
     }
     if (!result.isEmpty()) {
-      return new BlazeLibrary(LibraryKey.forResourceLibrary(),
-                              ImmutableList.copyOf(result.stream().sorted().collect(Collectors.toList())));
+      return new BlazeResourceLibrary(
+          ImmutableList.copyOf(result.stream().sorted().collect(Collectors.toList())));
     }
     return null;
   }
 
   @NotNull
-  private ImmutableList<AndroidResourceModule> buildAndroidResourceModules(WorkspaceBuilder workspaceBuilder) {
+  private ImmutableList<AndroidResourceModule> buildAndroidResourceModules(
+      WorkspaceBuilder workspaceBuilder) {
     // Filter empty resource modules
-    Stream<AndroidResourceModule> androidResourceModuleStream = workspaceBuilder.androidResourceModules
-      .stream()
-      .map(AndroidResourceModule.Builder::build)
-      .filter(androidResourceModule -> !androidResourceModule.isEmpty())
-      .filter(androidResourceModule -> !androidResourceModule.resources.isEmpty());
-    List<AndroidResourceModule> androidResourceModules = androidResourceModuleStream.collect(Collectors.toList());
+    Stream<AndroidResourceModule> androidResourceModuleStream =
+        workspaceBuilder
+            .androidResourceModules
+            .stream()
+            .map(AndroidResourceModule.Builder::build)
+            .filter(androidResourceModule -> !androidResourceModule.isEmpty())
+            .filter(androidResourceModule -> !androidResourceModule.resources.isEmpty());
+    List<AndroidResourceModule> androidResourceModules =
+        androidResourceModuleStream.collect(Collectors.toList());
 
     // Detect, filter, and warn about multiple R classes
-    Multimap<String, AndroidResourceModule> javaPackageToResourceModule = ArrayListMultimap.create();
+    Multimap<String, AndroidResourceModule> javaPackageToResourceModule =
+        ArrayListMultimap.create();
     for (AndroidResourceModule androidResourceModule : androidResourceModules) {
       RuleIdeInfo rule = ruleMap.get(androidResourceModule.label);
       AndroidRuleIdeInfo androidRuleIdeInfo = rule.androidRuleIdeInfo;
       assert androidRuleIdeInfo != null;
-      javaPackageToResourceModule.put(androidRuleIdeInfo.resourceJavaPackage, androidResourceModule);
+      javaPackageToResourceModule.put(
+          androidRuleIdeInfo.resourceJavaPackage, androidResourceModule);
     }
 
     List<AndroidResourceModule> result = Lists.newArrayList();
     for (String resourceJavaPackage : javaPackageToResourceModule.keySet()) {
-      Collection<AndroidResourceModule> androidResourceModulesWithJavaPackage = javaPackageToResourceModule.get(resourceJavaPackage);
+      Collection<AndroidResourceModule> androidResourceModulesWithJavaPackage =
+          javaPackageToResourceModule.get(resourceJavaPackage);
 
       if (androidResourceModulesWithJavaPackage.size() == 1) {
         result.addAll(androidResourceModulesWithJavaPackage);
-      }
-      else {
+      } else {
         StringBuilder messageBuilder = new StringBuilder();
-        messageBuilder.append("Multiple R classes generated with the same java package ").append(resourceJavaPackage).append(".R: ");
+        messageBuilder
+            .append("Multiple R classes generated with the same java package ")
+            .append(resourceJavaPackage)
+            .append(".R: ");
         messageBuilder.append('\n');
         for (AndroidResourceModule androidResourceModule : androidResourceModulesWithJavaPackage) {
           messageBuilder.append("  ").append(androidResourceModule.label).append('\n');
         }
         String message = messageBuilder.toString();
         context.output(new PerformanceWarning(message));
-        IssueOutput
-          .warn(message)
-          .submit(context);
+        IssueOutput.warn(message).submit(context);
 
         result.add(selectBestAndroidResourceModule(androidResourceModulesWithJavaPackage));
       }
@@ -232,19 +231,28 @@
     return ImmutableList.copyOf(result);
   }
 
-  private AndroidResourceModule selectBestAndroidResourceModule(Collection<AndroidResourceModule> androidResourceModulesWithJavaPackage) {
+  private AndroidResourceModule selectBestAndroidResourceModule(
+      Collection<AndroidResourceModule> androidResourceModulesWithJavaPackage) {
     return androidResourceModulesWithJavaPackage
-      .stream()
-      .max((lhs, rhs) -> ComparisonChain.start()
-        .compare(lhs.resources.size(), rhs.resources.size()) // Most resources wins
-        .compare(lhs.transitiveResources.size(), rhs.transitiveResources.size()) // Most transitive resources wins
-        .compare(rhs.label.toString().length(), lhs.label.toString().length()) // Shortest label wins - note lhs, rhs are flipped
-        .result())
-      .get();
+        .stream()
+        .max(
+            (lhs, rhs) ->
+                ComparisonChain.start()
+                    .compare(lhs.resources.size(), rhs.resources.size()) // Most resources wins
+                    .compare(
+                        lhs.transitiveResources.size(),
+                        rhs.transitiveResources.size()) // Most transitive resources wins
+                    .compare(
+                        rhs.label.toString().length(),
+                        lhs.label
+                            .toString()
+                            .length()) // Shortest label wins - note lhs, rhs are flipped
+                    .result())
+        .get();
   }
 
   static class WorkspaceBuilder {
     List<AndroidResourceModule.Builder> androidResourceModules = Lists.newArrayList();
-    ImmutableList.Builder<BlazeLibrary> libraries = ImmutableList.builder();
+    Set<ArtifactLocation> generatedResourceLocations = Sets.newHashSet();
   }
 }
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
index fd2d4d5..3a7b95c 100644
--- 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
@@ -15,21 +15,18 @@
  */
 package com.google.idea.blaze.android.sync.importer.aggregators;
 
-import com.google.common.collect.ImmutableMap;
 import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+import com.google.idea.blaze.base.model.RuleMap;
 import com.google.idea.blaze.base.model.primitives.Label;
-import org.jetbrains.annotations.NotNull;
 
-/**
- * Transitive aggregator for RuleIdeInfo.
- */
-public abstract class RuleIdeInfoTransitiveAggregator<T> extends TransitiveAggregator<RuleIdeInfo, T> {
-  protected RuleIdeInfoTransitiveAggregator(@NotNull ImmutableMap<Label, RuleIdeInfo> ruleMap) {
+/** Transitive aggregator for RuleIdeInfo. */
+public abstract class RuleIdeInfoTransitiveAggregator<T> extends TransitiveAggregator<T> {
+  protected RuleIdeInfoTransitiveAggregator(RuleMap ruleMap) {
     super(ruleMap);
   }
 
   @Override
-  protected Iterable<Label> getDependencies(@NotNull RuleIdeInfo ruleIdeInfo) {
+  protected Iterable<Label> getDependencies(RuleIdeInfo ruleIdeInfo) {
     return ruleIdeInfo.dependencies;
   }
 }
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 526a6b7..5048910 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,41 +16,37 @@
 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.model.RuleMap;
 import com.google.idea.blaze.base.model.primitives.Label;
-import org.jetbrains.annotations.NotNull;
+import java.util.Map;
 import org.jetbrains.annotations.Nullable;
 
-import java.util.Map;
-
-/**
- * Peforms a transitive reduction on the rule
- */
-public abstract class TransitiveAggregator<Rule, T> {
+/** Peforms a transitive reduction on the rule */
+public abstract class TransitiveAggregator<T> {
   private Map<Label, T> labelToResult;
 
-  protected TransitiveAggregator(@NotNull Map<Label, Rule> ruleMap) {
+  protected TransitiveAggregator(RuleMap ruleMap) {
     this.labelToResult = Maps.newHashMap();
-    for (Label label : ruleMap.keySet()) {
+    for (RuleIdeInfo rule : ruleMap.rules()) {
+      Label label = rule.label;
       aggregate(label, ruleMap);
     }
   }
 
-  @NotNull
-  protected T getOrDefault(@NotNull Label key, @NotNull T defaultValue) {
+  protected T getOrDefault(Label key, T defaultValue) {
     T result = labelToResult.get(key);
     return result != null ? result : defaultValue;
   }
 
   @Nullable
-  private T aggregate(
-    @NotNull Label label,
-    @NotNull Map<Label, Rule> ruleMap) {
+  private T aggregate(Label label, RuleMap ruleMap) {
     T result = labelToResult.get(label);
     if (result != null) {
       return result;
     }
 
-    Rule rule = ruleMap.get(label);
+    RuleIdeInfo rule = ruleMap.get(label);
     if (rule == null) {
       return null;
     }
@@ -68,17 +64,11 @@
     return result;
   }
 
-  protected abstract Iterable<Label> getDependencies(@NotNull Rule rule);
+  protected abstract Iterable<Label> getDependencies(RuleIdeInfo rule);
 
-  /**
-   * Creates the initial value for a given rule.
-   */
-  @NotNull
-  protected abstract T createForRule(@NotNull Rule rule);
+  /** Creates the initial value for a given rule. */
+  protected abstract T createForRule(RuleIdeInfo rule);
 
-  /**
-   * Reduces two values, sum + new value. May mutate value in place.
-   */
-  @NotNull
-  protected abstract T reduce(@NotNull T value, @NotNull T dependencyValue);
+  /** 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 9bdd852..f49029d 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
@@ -15,34 +15,32 @@
  */
 package com.google.idea.blaze.android.sync.importer.aggregators;
 
-import com.google.common.collect.ImmutableMap;
 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.base.ideinfo.ArtifactLocation;
 import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+import com.google.idea.blaze.base.model.RuleMap;
 import com.google.idea.blaze.base.model.primitives.Label;
-import org.jetbrains.annotations.NotNull;
-
 import java.util.List;
 import java.util.Set;
 
-/**
- * Computes transitive resources.
- */
-public class TransitiveResourceMap extends RuleIdeInfoTransitiveAggregator<TransitiveResourceMap.TransitiveResourceInfo> {
+/** Computes transitive resources. */
+public class TransitiveResourceMap
+    extends RuleIdeInfoTransitiveAggregator<TransitiveResourceMap.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<Label> transitiveResourceRules = Sets.newHashSet();
   }
 
-  public TransitiveResourceMap(@NotNull ImmutableMap<Label, RuleIdeInfo> ruleMap) {
+  public TransitiveResourceMap(RuleMap ruleMap) {
     super(ruleMap);
   }
 
   @Override
-  protected Iterable<Label> getDependencies(@NotNull RuleIdeInfo ruleIdeInfo) {
+  protected Iterable<Label> getDependencies(RuleIdeInfo ruleIdeInfo) {
     AndroidRuleIdeInfo androidRuleIdeInfo = ruleIdeInfo.androidRuleIdeInfo;
     if (androidRuleIdeInfo != null && androidRuleIdeInfo.legacyResources != null) {
       List<Label> result = Lists.newArrayList(super.getDependencies(ruleIdeInfo));
@@ -52,14 +50,12 @@
     return super.getDependencies(ruleIdeInfo);
   }
 
-  @NotNull
-  public TransitiveResourceInfo get(@NotNull Label label) {
+  public TransitiveResourceInfo get(Label label) {
     return getOrDefault(label, TransitiveResourceInfo.NO_RESOURCES);
   }
 
-  @NotNull
   @Override
-  protected TransitiveResourceInfo createForRule(@NotNull RuleIdeInfo ruleIdeInfo) {
+  protected TransitiveResourceInfo createForRule(RuleIdeInfo ruleIdeInfo) {
     TransitiveResourceInfo result = new TransitiveResourceInfo();
     AndroidRuleIdeInfo androidRuleIdeInfo = ruleIdeInfo.androidRuleIdeInfo;
     if (androidRuleIdeInfo == null) {
@@ -73,9 +69,9 @@
     return result;
   }
 
-  @NotNull
   @Override
-  protected TransitiveResourceInfo reduce(@NotNull TransitiveResourceInfo value, @NotNull TransitiveResourceInfo dependencyValue) {
+  protected TransitiveResourceInfo reduce(
+      TransitiveResourceInfo value, TransitiveResourceInfo dependencyValue) {
     value.transitiveResources.addAll(dependencyValue.transitiveResources);
     value.transitiveResourceRules.addAll(dependencyValue.transitiveResourceRules);
     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 e8c61ff..e4807a9 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
@@ -21,15 +21,18 @@
 import com.google.common.collect.Sets;
 import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
 import com.google.idea.blaze.base.model.primitives.Label;
-import org.jetbrains.annotations.NotNull;
-
-import javax.annotation.concurrent.Immutable;
 import java.io.File;
 import java.io.Serializable;
 import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
+import javax.annotation.concurrent.Immutable;
+import org.jetbrains.annotations.NotNull;
 
+/**
+ * An android resource module. Maps from an android_library's resources to an Android Studio
+ * module/facet.
+ */
 @Immutable
 public final class AndroidResourceModule implements Serializable {
   private static final long serialVersionUID = 5L;
@@ -39,10 +42,11 @@
   public final ImmutableCollection<File> transitiveResources;
   public final ImmutableCollection<Label> transitiveResourceDependencies;
 
-  public AndroidResourceModule(Label label,
-                               ImmutableCollection<File> resources,
-                               ImmutableCollection<File> transitiveResources,
-                               ImmutableCollection<Label> transitiveResourceDependencies) {
+  public AndroidResourceModule(
+      Label label,
+      ImmutableCollection<File> resources,
+      ImmutableCollection<File> transitiveResources,
+      ImmutableCollection<Label> transitiveResourceDependencies) {
     this.label = label;
     this.resources = resources;
     this.transitiveResources = transitiveResources;
@@ -52,11 +56,12 @@
   @Override
   public boolean equals(Object o) {
     if (o instanceof AndroidResourceModule) {
-      AndroidResourceModule that = (AndroidResourceModule)o;
+      AndroidResourceModule that = (AndroidResourceModule) o;
       return Objects.equal(this.label, that.label)
-             && Objects.equal(this.resources, that.resources)
-             && Objects.equal(this.transitiveResources, that.transitiveResources)
-             && Objects.equal(this.transitiveResourceDependencies, that.transitiveResourceDependencies);
+          && Objects.equal(this.resources, that.resources)
+          && Objects.equal(this.transitiveResources, that.transitiveResources)
+          && Objects.equal(
+              this.transitiveResourceDependencies, that.transitiveResourceDependencies);
     }
     return false;
   }
@@ -64,21 +69,26 @@
   @Override
   public int hashCode() {
     return Objects.hashCode(
-      this.label,
-      this.resources,
-      this.transitiveResources,
-      this.transitiveResourceDependencies
-    );
+        this.label, this.resources, this.transitiveResources, this.transitiveResourceDependencies);
   }
 
   @Override
   public String toString() {
-    return "AndroidResourceModule{" + "\n"
-           + "  label: " + label + "\n"
-           + "  resources: " + resources + "\n"
-           + "  transitiveResources: " + transitiveResources + "\n"
-           + "  transitiveResourceDependencies: " + transitiveResourceDependencies + "\n"
-           + '}';
+    return "AndroidResourceModule{"
+        + "\n"
+        + "  label: "
+        + label
+        + "\n"
+        + "  resources: "
+        + resources
+        + "\n"
+        + "  transitiveResources: "
+        + transitiveResources
+        + "\n"
+        + "  transitiveResourceDependencies: "
+        + transitiveResourceDependencies
+        + "\n"
+        + '}';
   }
 
   public static Builder builder(Label label) {
@@ -89,6 +99,7 @@
     return resources.isEmpty() && transitiveResources.isEmpty();
   }
 
+  /** Builder for the resource module */
   public static class Builder {
     private final Label label;
     private final Set<ArtifactLocation> resources = Sets.newHashSet();
@@ -132,25 +143,21 @@
     @NotNull
     public AndroidResourceModule build() {
       return new AndroidResourceModule(
-        label,
-        ImmutableList.copyOf(
-          resources
-            .stream()
-            .map(ArtifactLocation::getFile)
-            .sorted()
-            .collect(Collectors.toList())),
-        ImmutableList.copyOf(
-          transitiveResources
-            .stream()
-            .map(ArtifactLocation::getFile)
-            .sorted()
-            .collect(Collectors.toList())),
-        ImmutableList.copyOf(
-          transitiveResourceDependencies
-            .stream()
-            .sorted()
-            .collect(Collectors.toList()))
-      );
+          label,
+          ImmutableList.copyOf(
+              resources
+                  .stream()
+                  .map(ArtifactLocation::getFile)
+                  .sorted()
+                  .collect(Collectors.toList())),
+          ImmutableList.copyOf(
+              transitiveResources
+                  .stream()
+                  .map(ArtifactLocation::getFile)
+                  .sorted()
+                  .collect(Collectors.toList())),
+          ImmutableList.copyOf(
+              transitiveResourceDependencies.stream().sorted().collect(Collectors.toList())));
     }
   }
 }
diff --git a/aswb/src/com/google/idea/blaze/android/sync/model/AndroidSdkPlatform.java b/aswb/src/com/google/idea/blaze/android/sync/model/AndroidSdkPlatform.java
index f38c80d..644d749 100644
--- a/aswb/src/com/google/idea/blaze/android/sync/model/AndroidSdkPlatform.java
+++ b/aswb/src/com/google/idea/blaze/android/sync/model/AndroidSdkPlatform.java
@@ -15,12 +15,10 @@
  */
 package com.google.idea.blaze.android.sync.model;
 
-import javax.annotation.concurrent.Immutable;
 import java.io.Serializable;
+import javax.annotation.concurrent.Immutable;
 
-/**
- * Information about the android platform selected at sync time.
- */
+/** Information about the android platform selected at sync time. */
 @Immutable
 public class AndroidSdkPlatform implements Serializable {
   public final String androidSdk;
diff --git a/aswb/src/com/google/idea/blaze/android/sync/model/BlazeAndroidImportResult.java b/aswb/src/com/google/idea/blaze/android/sync/model/BlazeAndroidImportResult.java
index e134399..b4750a2 100644
--- a/aswb/src/com/google/idea/blaze/android/sync/model/BlazeAndroidImportResult.java
+++ b/aswb/src/com/google/idea/blaze/android/sync/model/BlazeAndroidImportResult.java
@@ -16,25 +16,22 @@
 package com.google.idea.blaze.android.sync.model;
 
 import com.google.common.collect.ImmutableCollection;
-import com.google.idea.blaze.java.sync.model.BlazeLibrary;
-
-import javax.annotation.concurrent.Immutable;
 import java.io.Serializable;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
 
-/**
- * The result of a blaze import operation.
- */
+/** The result of a blaze import operation. */
 @Immutable
 public class BlazeAndroidImportResult implements Serializable {
-  private static final long serialVersionUID = 1L;
+  private static final long serialVersionUID = 3L;
 
   public final ImmutableCollection<AndroidResourceModule> androidResourceModules;
-  public final ImmutableCollection<BlazeLibrary> libraries;
+  @Nullable public final BlazeResourceLibrary resourceLibrary;
 
   public BlazeAndroidImportResult(
-    ImmutableCollection<AndroidResourceModule> androidResourceModules,
-    ImmutableCollection<BlazeLibrary> libraries) {
+      ImmutableCollection<AndroidResourceModule> androidResourceModules,
+      @Nullable BlazeResourceLibrary resourceLibrary) {
     this.androidResourceModules = androidResourceModules;
-    this.libraries = libraries;
+    this.resourceLibrary = resourceLibrary;
   }
 }
diff --git a/aswb/src/com/google/idea/blaze/android/sync/model/BlazeAndroidSyncData.java b/aswb/src/com/google/idea/blaze/android/sync/model/BlazeAndroidSyncData.java
index c0f529b..6881b71 100644
--- a/aswb/src/com/google/idea/blaze/android/sync/model/BlazeAndroidSyncData.java
+++ b/aswb/src/com/google/idea/blaze/android/sync/model/BlazeAndroidSyncData.java
@@ -15,13 +15,11 @@
  */
 package com.google.idea.blaze.android.sync.model;
 
+import java.io.Serializable;
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.Immutable;
-import java.io.Serializable;
 
-/**
- * Sync data for the Android plugin.
- */
+/** Sync data for the Android plugin. */
 @Immutable
 public class BlazeAndroidSyncData implements Serializable {
   private static final long serialVersionUID = 1L;
@@ -29,8 +27,8 @@
   public final BlazeAndroidImportResult importResult;
   @Nullable public final AndroidSdkPlatform androidSdkPlatform;
 
-  public BlazeAndroidSyncData(BlazeAndroidImportResult importResult,
-                              @Nullable AndroidSdkPlatform androidSdkPlatform) {
+  public BlazeAndroidSyncData(
+      BlazeAndroidImportResult importResult, @Nullable AndroidSdkPlatform androidSdkPlatform) {
     this.importResult = importResult;
     this.androidSdkPlatform = androidSdkPlatform;
   }
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
new file mode 100644
index 0000000..240424e
--- /dev/null
+++ b/aswb/src/com/google/idea/blaze/android/sync/model/BlazeResourceLibrary.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.android.sync.model;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
+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;
+import java.io.File;
+import javax.annotation.concurrent.Immutable;
+
+/** A library that contains sources. */
+@Immutable
+public final class BlazeResourceLibrary extends BlazeLibrary {
+  private static final long serialVersionUID = 1L;
+
+  public final ImmutableList<File> sources;
+
+  public BlazeResourceLibrary(ImmutableList<File> sources) {
+    super(LibraryKey.forResourceLibrary());
+    this.sources = sources;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(super.hashCode(), sources);
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    if (this == other) {
+      return true;
+    }
+    if (!(other instanceof BlazeResourceLibrary)) {
+      return false;
+    }
+
+    BlazeResourceLibrary that = (BlazeResourceLibrary) other;
+
+    return super.equals(other) && Objects.equal(sources, that.sources);
+  }
+
+  @Override
+  public void modifyLibraryModel(Project project, Library.ModifiableModel libraryModel) {
+    for (File file : sources) {
+      libraryModel.addRoot(pathToUrl(file), OrderRootType.SOURCES);
+    }
+  }
+}
diff --git a/aswb/src/com/google/idea/blaze/android/sync/model/idea/BlazeAndroidModel.java b/aswb/src/com/google/idea/blaze/android/sync/model/idea/BlazeAndroidModel.java
index 48b4dcc..7a13e1e 100644
--- a/aswb/src/com/google/idea/blaze/android/sync/model/idea/BlazeAndroidModel.java
+++ b/aswb/src/com/google/idea/blaze/android/sync/model/idea/BlazeAndroidModel.java
@@ -27,13 +27,12 @@
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.vfs.LocalFileSystem;
 import com.intellij.openapi.vfs.VirtualFile;
-import org.jetbrains.android.dom.manifest.Manifest;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
 import java.io.File;
 import java.util.List;
 import java.util.Set;
+import org.jetbrains.android.dom.manifest.Manifest;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 /**
  * Contains Android-Blaze related state necessary for configuring an IDEA project based on a
@@ -51,17 +50,15 @@
   private final String resourceJavaPackage;
   private final int androidSdkApiLevel;
 
-  /**
-   * Creates a new {@link BlazeAndroidModel}.
-   */
+  /** Creates a new {@link BlazeAndroidModel}. */
   public BlazeAndroidModel(
-    Project project,
-    Module module,
-    File rootDirPath,
-    SourceProvider sourceProvider,
-    File moduleManifest,
-    String resourceJavaPackage,
-    int androidSdkApiLevel) {
+      Project project,
+      Module module,
+      File rootDirPath,
+      SourceProvider sourceProvider,
+      File moduleManifest,
+      String resourceJavaPackage,
+      int androidSdkApiLevel) {
     this.project = project;
     this.module = module;
     this.rootDirPath = rootDirPath;
diff --git a/aswb/src/com/google/idea/blaze/android/sync/model/idea/BlazeClassJarProvider.java b/aswb/src/com/google/idea/blaze/android/sync/model/idea/BlazeClassJarProvider.java
index 7719c95..f6372ed 100644
--- a/aswb/src/com/google/idea/blaze/android/sync/model/idea/BlazeClassJarProvider.java
+++ b/aswb/src/com/google/idea/blaze/android/sync/model/idea/BlazeClassJarProvider.java
@@ -21,32 +21,32 @@
 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.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.module.Module;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.vfs.JarFileSystem;
 import com.intellij.openapi.vfs.LocalFileSystem;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.util.containers.OrderedSet;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
 import java.io.File;
 import java.util.List;
+import org.jetbrains.annotations.Nullable;
 
+/** Collects class jars from the user's build. */
 public class BlazeClassJarProvider extends ClassJarProvider {
 
-  private @NotNull final Project project;
+  private final Project project;
 
-  public BlazeClassJarProvider(@NotNull final Project project){
+  public BlazeClassJarProvider(final Project project) {
     this.project = project;
   }
 
   @Override
   @Nullable
-  public VirtualFile findModuleClassFile(@NotNull String className, @NotNull Module module) {
-    BlazeProjectData blazeProjectData = BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
+  public VirtualFile findModuleClassFile(String className, Module module) {
+    BlazeProjectData blazeProjectData =
+        BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
     if (blazeProjectData == null) {
       return null;
     }
@@ -56,12 +56,12 @@
     if (syncData == null) {
       return null;
     }
-    for (File runtimeJar : syncData.importResult.buildOutputJars) {
-      VirtualFile runtimeJarVF = localVfs.findFileByIoFile(runtimeJar);
-      if (runtimeJarVF == null) {
+    for (File classJar : syncData.importResult.buildOutputJars) {
+      VirtualFile classJarVF = localVfs.findFileByIoFile(classJar);
+      if (classJarVF == null) {
         continue;
       }
-      VirtualFile classFile = findClassInJar(runtimeJarVF, classNamePath);
+      VirtualFile classFile = findClassInJar(classJarVF, classNamePath);
       if (classFile != null) {
         return classFile;
       }
@@ -70,9 +70,8 @@
   }
 
   @Nullable
-  private static VirtualFile findClassInJar(@NotNull final VirtualFile runtimeJar,
-                                            @NotNull String classNamePath) {
-    VirtualFile jarRoot = JarFileSystem.getInstance().getJarRootForLocalFile(runtimeJar);
+  private static VirtualFile findClassInJar(final VirtualFile classJar, String classNamePath) {
+    VirtualFile jarRoot = JarFileSystem.getInstance().getJarRootForLocalFile(classJar);
     if (jarRoot == null) {
       return null;
     }
@@ -80,10 +79,10 @@
   }
 
   @Override
-  @NotNull
-  public List<VirtualFile> getModuleExternalLibraries(@NotNull Module module) {
+  public List<VirtualFile> getModuleExternalLibraries(Module module) {
     OrderedSet<VirtualFile> results = new OrderedSet<VirtualFile>();
-    BlazeProjectData blazeProjectData = BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
+    BlazeProjectData blazeProjectData =
+        BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
     if (blazeProjectData == null) {
       return results;
     }
@@ -92,16 +91,13 @@
       return null;
     }
     LocalFileSystem localVfs = LocalFileSystem.getInstance();
-    for (BlazeLibrary blazeLibrary : syncData.importResult.libraries.values()) {
-      LibraryArtifact libraryArtifact = blazeLibrary.getLibraryArtifact();
-      if (libraryArtifact == null) {
+    for (BlazeJarLibrary blazeLibrary : syncData.importResult.libraries.values()) {
+      LibraryArtifact libraryArtifact = blazeLibrary.libraryArtifact;
+      ArtifactLocation classJar = libraryArtifact.classJar;
+      if (classJar == null) {
         continue;
       }
-      ArtifactLocation runtimeJar = libraryArtifact.runtimeJar;
-      if (runtimeJar == null) {
-        continue;
-      }
-      VirtualFile libVF = localVfs.findFileByIoFile(runtimeJar.getFile());
+      VirtualFile libVF = localVfs.findFileByIoFile(classJar.getFile());
       if (libVF == null) {
         continue;
       }
diff --git a/aswb/src/com/google/idea/blaze/android/sync/model/idea/NullClassJarProvider.java b/aswb/src/com/google/idea/blaze/android/sync/model/idea/NullClassJarProvider.java
index 969428f..037f4fa 100644
--- a/aswb/src/com/google/idea/blaze/android/sync/model/idea/NullClassJarProvider.java
+++ b/aswb/src/com/google/idea/blaze/android/sync/model/idea/NullClassJarProvider.java
@@ -19,15 +19,11 @@
 import com.google.common.collect.ImmutableList;
 import com.intellij.openapi.module.Module;
 import com.intellij.openapi.vfs.VirtualFile;
+import java.util.List;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import java.util.List;
-
-/**
- * Returns no class jars. Used to disable the layout editor
- * loading jars.
- */
+/** Returns no class jars. Used to disable the layout editor loading jars. */
 public class NullClassJarProvider extends ClassJarProvider {
   @Nullable
   @Override
diff --git a/aswb/src/com/google/idea/blaze/android/sync/model/idea/SourceProviderImpl.java b/aswb/src/com/google/idea/blaze/android/sync/model/idea/SourceProviderImpl.java
index 427f0fa..2bf8e82 100644
--- a/aswb/src/com/google/idea/blaze/android/sync/model/idea/SourceProviderImpl.java
+++ b/aswb/src/com/google/idea/blaze/android/sync/model/idea/SourceProviderImpl.java
@@ -18,8 +18,6 @@
 
 import com.android.builder.model.SourceProvider;
 import com.google.common.collect.ImmutableList;
-import com.intellij.openapi.diagnostic.Logger;
-
 import java.io.File;
 import java.io.Serializable;
 import java.util.Collection;
@@ -36,9 +34,7 @@
   private final File manifestFile;
   private final Collection<File> resDirs;
 
-  public SourceProviderImpl(String name,
-                            File manifestFile,
-                            Collection<File> resDirs) {
+  public SourceProviderImpl(String name, File manifestFile, Collection<File> resDirs) {
     this.name = name;
     this.manifestFile = manifestFile;
     this.resDirs = resDirs;
diff --git a/aswb/src/com/google/idea/blaze/android/sync/projectstructure/AndroidFacetModuleCustomizer.java b/aswb/src/com/google/idea/blaze/android/sync/projectstructure/AndroidFacetModuleCustomizer.java
index bae295d..62a06a2 100755
--- a/aswb/src/com/google/idea/blaze/android/sync/projectstructure/AndroidFacetModuleCustomizer.java
+++ b/aswb/src/com/google/idea/blaze/android/sync/projectstructure/AndroidFacetModuleCustomizer.java
@@ -22,17 +22,14 @@
 import org.jetbrains.android.facet.AndroidFacet;
 import org.jetbrains.jps.android.model.impl.JpsAndroidModuleProperties;
 
-/**
- * Adds the Android facet to modules imported from {@link AndroidProject}s.
- */
+/** Adds the Android facet to modules imported from {@link AndroidProject}s. */
 public class AndroidFacetModuleCustomizer {
 
   public static void createAndroidFacet(Module module) {
     AndroidFacet facet = AndroidFacet.getInstance(module);
     if (facet != null) {
       configureFacet(facet);
-    }
-    else {
+    } else {
       // Module does not have Android facet. Create one and add it.
       FacetManager facetManager = FacetManager.getInstance(module);
       ModifiableFacetModel model = facetManager.createModifiableModel();
@@ -40,8 +37,7 @@
         facet = facetManager.createFacet(AndroidFacet.getFacetType(), AndroidFacet.NAME, null);
         model.addFacet(facet);
         configureFacet(facet);
-      }
-      finally {
+      } finally {
         model.commit();
       }
     }
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 d411447..5fc8793 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
@@ -21,7 +21,7 @@
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import com.google.idea.blaze.android.resources.LightResourceClassService;
-import com.google.idea.blaze.android.run.BlazeAndroidRunConfiguration;
+import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationHandler;
 import com.google.idea.blaze.android.sync.AndroidSdkPlatformSyncer;
 import com.google.idea.blaze.android.sync.model.AndroidResourceModule;
 import com.google.idea.blaze.android.sync.model.AndroidSdkPlatform;
@@ -50,28 +50,27 @@
 import com.intellij.openapi.module.StdModuleTypes;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.roots.ModifiableRootModel;
-import org.jetbrains.android.facet.AndroidFacet;
-
-import javax.annotation.Nullable;
 import java.io.File;
 import java.util.Map;
 import java.util.Set;
+import javax.annotation.Nullable;
+import org.jetbrains.android.facet.AndroidFacet;
 
-/**
- * Updates the IDE's project structure.
- */
+/** Updates the IDE's project structure. */
 public class BlazeAndroidProjectStructureSyncer {
 
-  public static void updateProjectStructure(Project project,
-                                            BlazeContext context,
-                                            WorkspaceRoot workspaceRoot,
-                                            ProjectViewSet projectViewSet,
-                                            BlazeProjectData blazeProjectData,
-                                            BlazeSyncPlugin.ModuleEditor moduleEditor,
-                                            Module workspaceModule,
-                                            ModifiableRootModel workspaceModifiableModel,
-                                            boolean isAndroidWorkspace) {
-    LightResourceClassService.Builder rClassBuilder = new LightResourceClassService.Builder(project);
+  public static void updateProjectStructure(
+      Project project,
+      BlazeContext context,
+      WorkspaceRoot workspaceRoot,
+      ProjectViewSet projectViewSet,
+      BlazeProjectData blazeProjectData,
+      BlazeSyncPlugin.ModuleEditor moduleEditor,
+      Module workspaceModule,
+      ModifiableRootModel workspaceModifiableModel,
+      boolean isAndroidWorkspace) {
+    LightResourceClassService.Builder rClassBuilder =
+        new LightResourceClassService.Builder(project);
 
     if (isAndroidWorkspace) {
       BlazeAndroidSyncData syncData = blazeProjectData.syncState.get(BlazeAndroidSyncData.class);
@@ -84,17 +83,13 @@
         int totalOrderEntries = 0;
 
         // Create the workspace module
-        updateWorkspaceModule(
-          project,
-          workspaceRoot,
-          workspaceModule,
-          androidSdkPlatform
-        );
+        updateWorkspaceModule(project, workspaceRoot, workspaceModule, androidSdkPlatform);
 
         // Create android resource modules
         // Because we're setting up dependencies, the modules have to exist before we configure them
         Map<Label, AndroidResourceModule> labelToAndroidResourceModule = Maps.newHashMap();
-        for (AndroidResourceModule androidResourceModule : syncData.importResult.androidResourceModules) {
+        for (AndroidResourceModule androidResourceModule :
+            syncData.importResult.androidResourceModules) {
           labelToAndroidResourceModule.put(androidResourceModule.label, androidResourceModule);
           String moduleName = moduleNameForAndroidModule(androidResourceModule.label);
           moduleEditor.createModule(moduleName, StdModuleTypes.JAVA);
@@ -112,14 +107,13 @@
           ModifiableRootModel modifiableRootModel = moduleEditor.editModule(module);
 
           updateAndroidRuleModule(
-            project,
-            workspaceRoot,
-            androidSdkPlatform,
-            rule,
-            module,
-            modifiableRootModel,
-            androidResourceModule
-          );
+              project,
+              workspaceRoot,
+              androidSdkPlatform,
+              rule,
+              module,
+              modifiableRootModel,
+              androidResourceModule);
 
           for (Label resourceDependency : androidResourceModule.transitiveResourceDependencies) {
             if (!labelToAndroidResourceModule.containsKey(resourceDependency)) {
@@ -147,16 +141,18 @@
           if (!(targetExpression instanceof Label)) {
             continue;
           }
-          Label label = (Label)targetExpression;
+          Label label = (Label) targetExpression;
           runConfigurationModuleTargets.add(label);
         }
         // Get any pre-existing targets
-        for (RunConfiguration runConfiguration : RunManager.getInstance(project).getAllConfigurationsList()) {
-          if (!(runConfiguration instanceof BlazeAndroidRunConfiguration)) {
+        for (RunConfiguration runConfiguration :
+            RunManager.getInstance(project).getAllConfigurationsList()) {
+          BlazeAndroidRunConfigurationHandler handler =
+              BlazeAndroidRunConfigurationHandler.getHandlerFrom(runConfiguration);
+          if (handler == null) {
             continue;
           }
-          BlazeAndroidRunConfiguration blazeAndroidRunConfiguration = (BlazeAndroidRunConfiguration)runConfiguration;
-          runConfigurationModuleTargets.add(blazeAndroidRunConfiguration.getTarget());
+          runConfigurationModuleTargets.add(handler.getLabel());
         }
 
         int totalRunConfigurationModules = 0;
@@ -178,23 +174,17 @@
           Module module = moduleEditor.createModule(moduleName, StdModuleTypes.JAVA);
           ModifiableRootModel modifiableRootModel = moduleEditor.editModule(module);
           updateAndroidRuleModule(
-            project,
-            workspaceRoot,
-            androidSdkPlatform,
-            rule,
-            module,
-            modifiableRootModel,
-            null
-          );
+              project, workspaceRoot, androidSdkPlatform, rule, module, modifiableRootModel, null);
           ++totalRunConfigurationModules;
         }
 
-        context.output(new PrintOutput(String.format(
-          "Android resource module count: %d, run config modules: %d, order entries: %d",
-          syncData.importResult.androidResourceModules.size(),
-          totalRunConfigurationModules,
-          totalOrderEntries
-        )));
+        context.output(
+            PrintOutput.log(
+                String.format(
+                    "Android resource module count: %d, run config modules: %d, order entries: %d",
+                    syncData.importResult.androidResourceModules.size(),
+                    totalRunConfigurationModules,
+                    totalOrderEntries)));
       }
     } else {
       AndroidFacetModuleCustomizer.removeAndroidFacet(workspaceModule);
@@ -203,9 +193,7 @@
     LightResourceClassService.getInstance(project).installRClasses(rClassBuilder);
   }
 
-  /**
-   * Ensures a suitable module exists for the given android target.
-   */
+  /** Ensures a suitable module exists for the given android target. */
   @Nullable
   public static Module ensureRunConfigurationModule(Project project, Label target) {
     String moduleName = moduleNameForAndroidModule(target);
@@ -215,11 +203,13 @@
     }
 
     WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProject(project);
-    BlazeProjectData blazeProjectData = BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
+    BlazeProjectData blazeProjectData =
+        BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
     if (blazeProjectData == null) {
       return null;
     }
-    AndroidSdkPlatform androidSdkPlatform = AndroidSdkPlatformSyncer.getAndroidSdkPlatform(blazeProjectData);
+    AndroidSdkPlatform androidSdkPlatform =
+        AndroidSdkPlatformSyncer.getAndroidSdkPlatform(blazeProjectData);
     if (androidSdkPlatform == null) {
       return null;
     }
@@ -230,118 +220,121 @@
     if (rule.androidRuleIdeInfo == null) {
       return null;
     }
+    // We can't run a write action outside the dispatch thread, and can't
+    // invokeAndWait it because the caller may have a read action.
+    if (!ApplicationManager.getApplication().isDispatchThread()) {
+      return null;
+    }
 
-    BlazeSyncPlugin.ModuleEditor moduleEditor = BlazeProjectDataManager.getInstance(project).editModules();
+    BlazeSyncPlugin.ModuleEditor moduleEditor =
+        BlazeProjectDataManager.getInstance(project).editModules();
     Module newModule = moduleEditor.createModule(moduleName, StdModuleTypes.JAVA);
     ModifiableRootModel modifiableRootModel = moduleEditor.editModule(newModule);
 
-    ApplicationManager.getApplication().runWriteAction(() -> {
-      updateAndroidRuleModule(
-        project,
-        workspaceRoot,
-        androidSdkPlatform,
-        rule,
-        newModule,
-        modifiableRootModel,
-        null
-      );
-      moduleEditor.commit();
-    });
+    ApplicationManager.getApplication()
+        .runWriteAction(
+            () -> {
+              updateAndroidRuleModule(
+                  project,
+                  workspaceRoot,
+                  androidSdkPlatform,
+                  rule,
+                  newModule,
+                  modifiableRootModel,
+                  null);
+              moduleEditor.commit();
+            });
     return newModule;
   }
 
   public static String moduleNameForAndroidModule(Label label) {
-    return label.toString()
-      .substring(2) // Skip initial "//"
-      .replace('/', '.')
-      .replace(':', '.');
+    return label
+        .toString()
+        .substring(2) // Skip initial "//"
+        .replace('/', '.')
+        .replace(':', '.');
   }
 
-  /**
-   * Updates the shared workspace module with android info.
-   */
-  private static void updateWorkspaceModule(Project project,
-                                           WorkspaceRoot workspaceRoot,
-                                           Module workspaceModule,
-                                           AndroidSdkPlatform androidSdkPlatform) {
+  /** Updates the shared workspace module with android info. */
+  private static void updateWorkspaceModule(
+      Project project,
+      WorkspaceRoot workspaceRoot,
+      Module workspaceModule,
+      AndroidSdkPlatform androidSdkPlatform) {
     File moduleDirectory = workspaceRoot.directory();
     File manifest = new File(workspaceRoot.directory(), "AndroidManifest.xml");
     String resourceJavaPackage = ":workspace";
     ImmutableList<File> transitiveResources = ImmutableList.of();
 
     createAndroidModel(
-      project,
-      androidSdkPlatform,
-      workspaceModule,
-      moduleDirectory,
-      manifest,
-      resourceJavaPackage,
-      transitiveResources
-    );
+        project,
+        androidSdkPlatform,
+        workspaceModule,
+        moduleDirectory,
+        manifest,
+        resourceJavaPackage,
+        transitiveResources);
   }
 
-  /**
-   * Updates a module from an android rule.
-   */
-  private static void updateAndroidRuleModule(Project project,
-                                              WorkspaceRoot workspaceRoot,
-                                              AndroidSdkPlatform androidSdkPlatform,
-                                              RuleIdeInfo rule,
-                                              Module module,
-                                              ModifiableRootModel modifiableRootModel,
-                                              @Nullable AndroidResourceModule androidResourceModule) {
+  /** Updates a module from an android rule. */
+  private static void updateAndroidRuleModule(
+      Project project,
+      WorkspaceRoot workspaceRoot,
+      AndroidSdkPlatform androidSdkPlatform,
+      RuleIdeInfo rule,
+      Module module,
+      ModifiableRootModel modifiableRootModel,
+      @Nullable AndroidResourceModule androidResourceModule) {
 
-    ImmutableCollection<File> resources = androidResourceModule != null
-                                          ? androidResourceModule.resources
-                                          : ImmutableList.of();
-    ImmutableCollection<File> transitiveResources = androidResourceModule != null
-                                                    ? androidResourceModule.transitiveResources
-                                                    : ImmutableList.of();
+    ImmutableCollection<File> resources =
+        androidResourceModule != null ? androidResourceModule.resources : ImmutableList.of();
+    ImmutableCollection<File> transitiveResources =
+        androidResourceModule != null
+            ? androidResourceModule.transitiveResources
+            : ImmutableList.of();
 
     AndroidRuleIdeInfo androidRuleIdeInfo = rule.androidRuleIdeInfo;
     assert androidRuleIdeInfo != null;
 
     File moduleDirectory = workspaceRoot.fileForPath(rule.label.blazePackage());
     ArtifactLocation manifestArtifactLocation = androidRuleIdeInfo.manifest;
-    File manifest = manifestArtifactLocation != null
-                    ? manifestArtifactLocation.getFile()
-                    : new File(moduleDirectory, "AndroidManifest.xml");
+    File manifest =
+        manifestArtifactLocation != null
+            ? manifestArtifactLocation.getFile()
+            : new File(moduleDirectory, "AndroidManifest.xml");
     String resourceJavaPackage = androidRuleIdeInfo.resourceJavaPackage;
     ResourceModuleContentRootCustomizer.setupContentRoots(modifiableRootModel, resources);
 
     createAndroidModel(
-      project,
-      androidSdkPlatform,
-      module,
-      moduleDirectory,
-      manifest,
-      resourceJavaPackage,
-      transitiveResources
-    );
+        project,
+        androidSdkPlatform,
+        module,
+        moduleDirectory,
+        manifest,
+        resourceJavaPackage,
+        transitiveResources);
   }
 
-  private static void createAndroidModel(Project project,
-                                         AndroidSdkPlatform androidSdkPlatform,
-                                         Module module,
-                                         File moduleDirectory,
-                                         File manifest,
-                                         String resourceJavaPackage,
-                                         ImmutableCollection<File> transitiveResources) {
+  private static void createAndroidModel(
+      Project project,
+      AndroidSdkPlatform androidSdkPlatform,
+      Module module,
+      File moduleDirectory,
+      File manifest,
+      String resourceJavaPackage,
+      ImmutableCollection<File> transitiveResources) {
     AndroidFacetModuleCustomizer.createAndroidFacet(module);
-    SourceProvider sourceProvider = new SourceProviderImpl(
-      module.getName(),
-      manifest,
-      transitiveResources
-    );
-    BlazeAndroidModel androidModel = new BlazeAndroidModel(
-      project,
-      module,
-      moduleDirectory,
-      sourceProvider,
-      manifest,
-      resourceJavaPackage,
-      androidSdkPlatform.androidSdkLevel
-    );
+    SourceProvider sourceProvider =
+        new SourceProviderImpl(module.getName(), manifest, transitiveResources);
+    BlazeAndroidModel androidModel =
+        new BlazeAndroidModel(
+            project,
+            module,
+            moduleDirectory,
+            sourceProvider,
+            manifest,
+            resourceJavaPackage,
+            androidSdkPlatform.androidSdkLevel);
     AndroidFacet facet = AndroidFacet.getInstance(module);
     if (facet != null) {
       facet.setAndroidModel(androidModel);
diff --git a/aswb/src/com/google/idea/blaze/android/sync/projectstructure/ResourceModuleContentRootCustomizer.java b/aswb/src/com/google/idea/blaze/android/sync/projectstructure/ResourceModuleContentRootCustomizer.java
index adf17ff..ea8db29 100644
--- a/aswb/src/com/google/idea/blaze/android/sync/projectstructure/ResourceModuleContentRootCustomizer.java
+++ b/aswb/src/com/google/idea/blaze/android/sync/projectstructure/ResourceModuleContentRootCustomizer.java
@@ -20,17 +20,15 @@
 import com.intellij.openapi.util.io.FileUtil;
 import com.intellij.openapi.vfs.VfsUtilCore;
 import com.intellij.util.io.URLUtil;
+import java.io.File;
+import java.util.Collection;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.jps.model.java.JavaResourceRootType;
 
-import java.io.File;
-import java.util.Collection;
-
-public class ResourceModuleContentRootCustomizer {
+class ResourceModuleContentRootCustomizer {
 
   public static void setupContentRoots(
-    @NotNull ModifiableRootModel model,
-    @NotNull Collection<File> resources) {
+      @NotNull ModifiableRootModel model, @NotNull Collection<File> resources) {
     for (ContentEntry contentEntry : model.getContentEntries()) {
       model.removeContentEntry(contentEntry);
     }
@@ -45,14 +43,10 @@
   private static String pathToUrl(@NotNull String filePath) {
     filePath = FileUtil.toSystemIndependentName(filePath);
     if (filePath.endsWith(".srcjar") || filePath.endsWith(".jar")) {
-      return URLUtil.JAR_PROTOCOL + URLUtil.SCHEME_SEPARATOR +
-             filePath + URLUtil.JAR_SEPARATOR;
-    }
-    else if (filePath.contains("src.jar!")) {
-      return URLUtil.JAR_PROTOCOL + URLUtil.SCHEME_SEPARATOR +
-             filePath;
-    }
-    else {
+      return URLUtil.JAR_PROTOCOL + URLUtil.SCHEME_SEPARATOR + filePath + URLUtil.JAR_SEPARATOR;
+    } else if (filePath.contains("src.jar!")) {
+      return URLUtil.JAR_PROTOCOL + URLUtil.SCHEME_SEPARATOR + filePath;
+    } else {
       return VfsUtilCore.pathToUrl(filePath);
     }
   }
diff --git a/aswb/src/icons/BlazeAndroidIcons.java b/aswb/src/icons/BlazeAndroidIcons.java
index 7f14ec5..957358f 100644
--- a/aswb/src/icons/BlazeAndroidIcons.java
+++ b/aswb/src/icons/BlazeAndroidIcons.java
@@ -16,20 +16,22 @@
 package icons;
 
 import com.intellij.openapi.util.IconLoader;
+import javax.swing.Icon;
 
-import javax.swing.*;
-
-/**
- * Class to manage icons used by the Blaze plugin.
- */
+/** Class to manage icons used by the Blaze plugin. */
 public class BlazeAndroidIcons {
 
-  public static final Icon MobileInstallRun = load("/aswb/resources/icons/mobileInstallRun.png"); // 16x16
-  public static final Icon MobileInstallDebug = load("/aswb/resources/icons/mobileInstallDebug.png"); // 16x16
-  public static final Icon Crow = load("/aswb/resources/icons/crow.png"); // 16x16
-  public static final Icon CrowToolWindow = load("/aswb/resources/icons/crowToolWindow.png"); // 13x13
+  private static final String BASE = "/";
+
+  public static final Icon MobileInstallRun =
+      load("aswb/resources/icons/mobileInstallRun.png"); // 16x16
+  public static final Icon MobileInstallDebug =
+      load("aswb/resources/icons/mobileInstallDebug.png"); // 16x16
+  public static final Icon Crow = load("aswb/resources/icons/crow.png"); // 16x16
+  public static final Icon CrowToolWindow =
+      load("aswb/resources/icons/crowToolWindow.png"); // 13x13
 
   private static Icon load(String path) {
-    return IconLoader.getIcon(path, BlazeAndroidIcons.class);
+    return IconLoader.getIcon(BASE + path, BlazeAndroidIcons.class);
   }
 }
diff --git a/aswb/tests/unittests/com/google/idea/blaze/android/resources/actions/BlazeCreateResourceUtilsTest.java b/aswb/tests/unittests/com/google/idea/blaze/android/resources/actions/BlazeCreateResourceUtilsTest.java
index d2490ed..7341666 100644
--- a/aswb/tests/unittests/com/google/idea/blaze/android/resources/actions/BlazeCreateResourceUtilsTest.java
+++ b/aswb/tests/unittests/com/google/idea/blaze/android/resources/actions/BlazeCreateResourceUtilsTest.java
@@ -15,6 +15,8 @@
  */
 package com.google.idea.blaze.android.resources.actions;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import com.google.idea.blaze.base.BlazeTestCase;
 import com.intellij.mock.MockVirtualFile;
 import com.intellij.openapi.vfs.VirtualFile;
@@ -23,11 +25,7 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Tests for BlazeCreateResourceUtils.
- */
+/** Tests for BlazeCreateResourceUtils. */
 @RunWith(JUnit4.class)
 public class BlazeCreateResourceUtilsTest extends BlazeTestCase {
 
@@ -78,8 +76,9 @@
 
   @Test
   public void getDirectoryFromContextJavaFile() {
-    // This is just the first cut, where it isn't obvious that the A.java file is associated with the
-    // neighboring res directory. We'll have a second pass that looks at the rule map for possible choices.
+    // This is just the first cut, where it isn't obvious that the A.java
+    // file is associated with the neighboring res directory.
+    // We'll have a second pass that looks at the rule map for possible choices.
     VirtualFile dir = BlazeCreateResourceUtils.getResDirFromDataContext(javaFile);
     assertThat(dir).isNull();
   }
diff --git a/aswb/tests/unittests/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationCommonStateTest.java b/aswb/tests/unittests/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationCommonStateTest.java
new file mode 100644
index 0000000..761643d
--- /dev/null
+++ b/aswb/tests/unittests/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationCommonStateTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.common.collect.Lists;
+import com.google.idea.blaze.android.cppapi.NdkSupport;
+import com.google.idea.blaze.base.BlazeTestCase;
+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.jetbrains.annotations.NotNull;
+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 BlazeTestCase {
+  private BlazeAndroidRunConfigurationCommonState commonState;
+
+  @Override
+  protected void initTest(
+      @NotNull Container applicationServices, @NotNull Container projectServices) {
+    super.initTest(applicationServices, projectServices);
+
+    MockExperimentService experimentService = new MockExperimentService();
+    applicationServices.register(ExperimentService.class, experimentService);
+    // BlazeAndroidRunConfigurationCommonState.isNativeDebuggingEnabled() always
+    // returns false if this experiment is false.
+    experimentService.setExperiment(NdkSupport.NDK_SUPPORT, true);
+
+    commonState = new BlazeAndroidRunConfigurationCommonState(ImmutableList.of());
+  }
+
+  @Test
+  public void readAndWriteShouldMatch() throws InvalidDataException, WriteExternalException {
+    commonState.setUserFlags(ImmutableList.of("--flag1", "--flag2"));
+    commonState.setNativeDebuggingEnabled(true);
+
+    Element element = new Element("test");
+    commonState.writeExternal(element);
+    BlazeAndroidRunConfigurationCommonState readCommonState =
+        new BlazeAndroidRunConfigurationCommonState(ImmutableList.of());
+    readCommonState.readExternal(element);
+
+    assertThat(readCommonState.getUserFlags()).containsExactly("--flag1", "--flag2").inOrder();
+    assertThat(readCommonState.isNativeDebuggingEnabled()).isTrue();
+  }
+
+  @Test
+  public void readAndWriteShouldHandleNulls() throws InvalidDataException, WriteExternalException {
+    Element element = new Element("test");
+    commonState.writeExternal(element);
+    BlazeAndroidRunConfigurationCommonState readCommonState =
+        new BlazeAndroidRunConfigurationCommonState(ImmutableList.of());
+    readCommonState.readExternal(element);
+
+    assertThat(readCommonState.getUserFlags()).isEqualTo(commonState.getUserFlags());
+    assertThat(readCommonState.isNativeDebuggingEnabled())
+        .isEqualTo(commonState.isNativeDebuggingEnabled());
+  }
+
+  @Test
+  public void readShouldOmitEmptyFlags() throws InvalidDataException, WriteExternalException {
+    commonState.setUserFlags(Lists.newArrayList("hi ", "", "I'm", " ", "\t", "Josh\r\n", "\n"));
+
+    Element element = new Element("test");
+    commonState.writeExternal(element);
+    BlazeAndroidRunConfigurationCommonState readCommonState =
+        new BlazeAndroidRunConfigurationCommonState(ImmutableList.of());
+    readCommonState.readExternal(element);
+
+    assertThat(readCommonState.getUserFlags()).containsExactly("hi", "I'm", "Josh").inOrder();
+  }
+}
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 107ab27..dddf2f5 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
@@ -15,14 +15,23 @@
  */
 package com.google.idea.blaze.android.sync.importer;
 
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.android.sync.BlazeAndroidJavaSyncAugmenter;
 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.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.experiments.ExperimentService;
-import com.google.idea.blaze.base.experiments.MockExperimentService;
-import com.google.idea.blaze.base.ideinfo.*;
+import com.google.idea.blaze.base.ideinfo.AndroidRuleIdeInfo;
+import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
+import com.google.idea.blaze.base.ideinfo.JavaRuleIdeInfo;
+import com.google.idea.blaze.base.ideinfo.LibraryArtifact;
+import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+import com.google.idea.blaze.base.ideinfo.RuleMapBuilder;
+import com.google.idea.blaze.base.model.RuleMap;
 import com.google.idea.blaze.base.model.primitives.Label;
 import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
@@ -37,45 +46,49 @@
 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;
-import com.google.idea.blaze.java.sync.model.BlazeLibrary;
-import com.google.idea.blaze.java.sync.model.LibraryKey;
+import com.google.idea.blaze.base.sync.projectview.ImportRoots;
+import com.google.idea.blaze.java.sync.model.BlazeJarLibrary;
+import com.google.idea.common.experiments.ExperimentService;
+import com.google.idea.common.experiments.MockExperimentService;
+import java.io.File;
+import java.util.List;
+import java.util.stream.Collectors;
 import org.jetbrains.annotations.NotNull;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
 
-import java.io.File;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.assertEquals;
-
-/**
- * Tests for BlazeAndroidWorkspaceImporter
- */
+/** Tests for BlazeAndroidWorkspaceImporter */
+@RunWith(JUnit4.class)
 public class BlazeAndroidWorkspaceImporterTest extends BlazeTestCase {
 
-  private String FAKE_ROOT = "/root";
+  private static final String FAKE_ROOT = "/root";
   private WorkspaceRoot workspaceRoot = new WorkspaceRoot(new File(FAKE_ROOT));
 
   private static final String FAKE_GEN_ROOT_EXECUTION_PATH_FRAGMENT =
-    "blaze-out/gcc-4.X.Y-crosstool-v17-hybrid-grtev3-k8-fastbuild/bin";
+      "blaze-out/gcc-4.X.Y-crosstool-v17-hybrid-grtev3-k8-fastbuild/bin";
 
   private static final String FAKE_GEN_ROOT =
-    "/abs_root/_blaze_user/8093958afcfde6c33d08b621dfaa4e09/root/"
-    + FAKE_GEN_ROOT_EXECUTION_PATH_FRAGMENT;
+      "/abs_root/_blaze_user/8093958afcfde6c33d08b621dfaa4e09/root/"
+          + FAKE_GEN_ROOT_EXECUTION_PATH_FRAGMENT;
 
-  private static final BlazeImportSettings DUMMY_IMPORT_SETTINGS = new BlazeImportSettings("", "", "", "", "", BuildSystem.Blaze);
+  private static final BlazeImportSettings DUMMY_IMPORT_SETTINGS =
+      new BlazeImportSettings("", "", "", "", "", BuildSystem.Blaze);
 
   private BlazeContext context;
   private ErrorCollector errorCollector = new ErrorCollector();
 
   @Override
-  protected void initTest(@NotNull Container applicationServices, @NotNull Container projectServices) {
+  protected void initTest(
+      @NotNull Container applicationServices, @NotNull Container projectServices) {
     MockExperimentService mockExperimentService = new MockExperimentService();
     applicationServices.register(ExperimentService.class, mockExperimentService);
 
     BlazeExecutor blazeExecutor = new MockBlazeExecutor();
     applicationServices.register(BlazeExecutor.class, blazeExecutor);
 
-    projectServices.register(BlazeImportSettingsManager.class, new BlazeImportSettingsManager(project));
+    projectServices.register(
+        BlazeImportSettingsManager.class, new BlazeImportSettingsManager(project));
     BlazeImportSettingsManager.getInstance(getProject()).setImportSettings(DUMMY_IMPORT_SETTINGS);
 
     context = new BlazeContext();
@@ -83,455 +96,509 @@
   }
 
   BlazeAndroidImportResult importWorkspace(
-    WorkspaceRoot workspaceRoot,
-    RuleMapBuilder ruleMapBuilder,
-    ProjectView projectView) {
+      WorkspaceRoot workspaceRoot, RuleMapBuilder ruleMapBuilder, ProjectView projectView) {
 
     ProjectViewSet projectViewSet = ProjectViewSet.builder().add(projectView).build();
 
-    BlazeAndroidWorkspaceImporter workspaceImporter = new BlazeAndroidWorkspaceImporter(
-      project,
-      context,
-      workspaceRoot,
-      projectViewSet,
-      ruleMapBuilder.build()
-    );
+    BlazeAndroidWorkspaceImporter workspaceImporter =
+        new BlazeAndroidWorkspaceImporter(
+            project, context, workspaceRoot, projectViewSet, ruleMapBuilder.build());
 
     return workspaceImporter.importWorkspace();
   }
 
-  /**
-   * Test that a two packages use the same un-imported android_library
-   */
+  /** Test that a two packages use the same un-imported android_library */
   @Test
   public void testResourceInheritance() {
-    ProjectView projectView = ProjectView.builder()
-      .put(ListSection.builder(DirectorySection.KEY)
-             .add(DirectoryEntry.include(new WorkspacePath("java/com/google/android/apps/example")))
-             .add(DirectoryEntry.include(new WorkspacePath("javatests/com/google/android/apps/example"))))
-      .build();
+    ProjectView projectView =
+        ProjectView.builder()
+            .add(
+                ListSection.builder(DirectorySection.KEY)
+                    .add(DirectoryEntry.include(new WorkspacePath("java/apps/example")))
+                    .add(DirectoryEntry.include(new WorkspacePath("javatests/apps/example"))))
+            .build();
 
-    /**
-     * Deps are project -> lib0 -> lib1 -> shared
-     *          project -> shared
-     */
-
-    RuleMapBuilder response = RuleMapBuilder.builder()
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//java/com/google/android/apps/example/lib0:lib0")
-          .setKind("android_library")
-          .setBuildFile(sourceRoot("java/com/google/android/apps/example/lib0/BUILD"))
-          .addSource(sourceRoot("java/com/google/android/apps/example/lib0/SharedActivity.java"))
-          .setAndroidInfo(AndroidRuleIdeInfo.builder()
-                            .setManifestFile(sourceRoot("java/com/google/android/apps/example/lib0/AndroidManifest.xml"))
-                            .addResource(sourceRoot("java/com/google/android/apps/example/lib0/res"))
+    /** Deps are project -> lib0 -> lib1 -> shared project -> shared */
+    RuleMapBuilder ruleMapBuilder =
+        RuleMapBuilder.builder()
+            .addRule(
+                RuleIdeInfo.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()
+                            .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/com/google/android/apps/example/lib1:lib1")
-          .setJavaInfo(JavaRuleIdeInfo.builder()
-                         .addJar(LibraryArtifact.builder()
-                                   .setJar(genRoot("java/com/google/android/apps/example/lib0/lib0.jar"))
-                                   .setRuntimeJar(genRoot("java/com/google/android/apps/example/lib0/lib0.jar")))))
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//java/com/google/android/apps/example/lib1:lib1")
-          .setKind("android_library")
-          .setBuildFile(sourceRoot("java/com/google/android/apps/example/lib1/BUILD"))
-          .addSource(sourceRoot("java/com/google/android/apps/example/lib1/SharedActivity.java"))
-          .setAndroidInfo(AndroidRuleIdeInfo.builder()
-                            .setManifestFile(sourceRoot("java/com/google/android/apps/example/lib1/AndroidManifest.xml"))
-                            .addResource(sourceRoot("java/com/google/android/apps/example/lib1/res"))
+                    .addDependency("//java/apps/example/lib1:lib1")
+                    .setJavaInfo(
+                        JavaRuleIdeInfo.builder()
+                            .addJar(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(gen("java/apps/example/lib0/lib0.jar"))
+                                    .setClassJar(gen("java/apps/example/lib0/lib0.jar")))))
+            .addRule(
+                RuleIdeInfo.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()
+                            .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/com/google/android/libraries/shared:shared")
-          .setJavaInfo(JavaRuleIdeInfo.builder()
-                         .addJar(LibraryArtifact.builder()
-                                   .setJar(genRoot("java/com/google/android/apps/example/lib1/lib1.jar"))
-                                   .setRuntimeJar(genRoot("java/com/google/android/apps/example/lib1/lib1.jar")))))
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//java/com/google/android/apps/example:example_debug")
-          .setKind("android_binary")
-          .setBuildFile(sourceRoot("java/com/google/android/apps/example/BUILD"))
-          .addSource(sourceRoot("java/com/google/android/apps/example/MainActivity.java"))
-          .setAndroidInfo(AndroidRuleIdeInfo.builder()
-                            .setManifestFile(sourceRoot("java/com/google/android/apps/example/AndroidManifest.xml"))
-                            .addResource(sourceRoot("java/com/google/android/apps/example/res"))
+                    .addDependency("//java/libraries/shared:shared")
+                    .setJavaInfo(
+                        JavaRuleIdeInfo.builder()
+                            .addJar(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(gen("java/apps/example/lib1/lib1.jar"))
+                                    .setClassJar(gen("java/apps/example/lib1/lib1.jar")))))
+            .addRule(
+                RuleIdeInfo.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()
+                            .setManifestFile(source("java/apps/example/AndroidManifest.xml"))
+                            .addResource(source("java/apps/example/res"))
                             .setGenerateResourceClass(true)
                             .setResourceJavaPackage("com.google.android.apps.example"))
-          .addDependency("//java/com/google/android/apps/example/lib0:lib0")
-          .addDependency("//java/com/google/android/libraries/shared:shared")
-          .setJavaInfo(JavaRuleIdeInfo.builder()
-                         .addJar(LibraryArtifact.builder()
-                                   .setJar(genRoot("java/com/google/android/apps/example/example_debug.jar"))
-                                   .setRuntimeJar(genRoot("java/com/google/android/apps/example/example_debug.jar")))))
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//java/com/google/android/libraries/shared:shared")
-          .setBuildFile(sourceRoot("java/com/google/android/libraries/shared/BUILD"))
-          .setKind("android_library")
-          .addSource(sourceRoot("java/com/google/android/libraries/shared/SharedActivity.java"))
-          .setAndroidInfo(AndroidRuleIdeInfo.builder()
-                            .setManifestFile(sourceRoot("java/com/google/android/libraries/shared/AndroidManifest.xml"))
-                            .addResource(sourceRoot("java/com/google/android/libraries/shared/res"))
+                    .addDependency("//java/apps/example/lib0:lib0")
+                    .addDependency("//java/libraries/shared:shared")
+                    .setJavaInfo(
+                        JavaRuleIdeInfo.builder()
+                            .addJar(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(gen("java/apps/example/example_debug.jar"))
+                                    .setClassJar(gen("java/apps/example/example_debug.jar")))))
+            .addRule(
+                RuleIdeInfo.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()
+                            .setManifestFile(source("java/libraries/shared/AndroidManifest.xml"))
+                            .addResource(source("java/libraries/shared/res"))
                             .setGenerateResourceClass(true)
                             .setResourceJavaPackage("com.google.android.libraries.shared"))
-          .setBuildFile(sourceRoot("java/com/google/android/libraries/shared/BUILD"))
-          .setJavaInfo(JavaRuleIdeInfo.builder()
-                         .addJar(LibraryArtifact.builder()
-                                   .setJar(genRoot("java/com/google/android/libraries/shared.jar"))
-                                   .setRuntimeJar(genRoot("java/com/google/android/libraries/shared.jar")))));
+                    .setBuildFile(source("java/libraries/shared/BUILD"))
+                    .setJavaInfo(
+                        JavaRuleIdeInfo.builder()
+                            .addJar(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(gen("java/libraries/shared.jar"))
+                                    .setClassJar(gen("java/libraries/shared.jar")))));
 
-    BlazeAndroidImportResult result = importWorkspace(
-      workspaceRoot,
-      response,
-      projectView
-    );
+    BlazeAndroidImportResult result = importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
     errorCollector.assertNoIssues();
 
-    assertThat(result.androidResourceModules).containsExactly(
-      AndroidResourceModule.builder(new Label("//java/com/google/android/apps/example:example_debug"))
-        .addResourceAndTransitiveResource(sourceRoot("java/com/google/android/apps/example/res"))
-        .addTransitiveResource(sourceRoot("java/com/google/android/apps/example/lib0/res"))
-        .addTransitiveResource(sourceRoot("java/com/google/android/apps/example/lib1/res"))
-        .addTransitiveResource(sourceRoot("java/com/google/android/libraries/shared/res"))
-        .addTransitiveResourceDependency("//java/com/google/android/apps/example/lib0:lib0")
-        .addTransitiveResourceDependency("//java/com/google/android/apps/example/lib1:lib1")
-        .addTransitiveResourceDependency("//java/com/google/android/libraries/shared:shared")
-        .build(),
-      AndroidResourceModule.builder(new Label("//java/com/google/android/apps/example/lib0:lib0"))
-        .addResourceAndTransitiveResource(sourceRoot("java/com/google/android/apps/example/lib0/res"))
-        .addTransitiveResource(sourceRoot("java/com/google/android/apps/example/lib1/res"))
-        .addTransitiveResource(sourceRoot("java/com/google/android/libraries/shared/res"))
-        .addTransitiveResourceDependency("//java/com/google/android/apps/example/lib1:lib1")
-        .addTransitiveResourceDependency("//java/com/google/android/libraries/shared:shared")
-        .build(),
-      AndroidResourceModule.builder(new Label("//java/com/google/android/apps/example/lib1:lib1"))
-        .addResourceAndTransitiveResource(sourceRoot("java/com/google/android/apps/example/lib1/res"))
-        .addTransitiveResource(sourceRoot("java/com/google/android/libraries/shared/res"))
-        .addTransitiveResourceDependency("//java/com/google/android/libraries/shared:shared")
-        .build()
-    );
+    assertThat(result.androidResourceModules)
+        .containsExactly(
+            AndroidResourceModule.builder(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"))
+                .addTransitiveResource(source("java/libraries/shared/res"))
+                .addTransitiveResourceDependency("//java/apps/example/lib0:lib0")
+                .addTransitiveResourceDependency("//java/apps/example/lib1:lib1")
+                .addTransitiveResourceDependency("//java/libraries/shared:shared")
+                .build(),
+            AndroidResourceModule.builder(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"))
+                .addTransitiveResourceDependency("//java/apps/example/lib1:lib1")
+                .addTransitiveResourceDependency("//java/libraries/shared:shared")
+                .build(),
+            AndroidResourceModule.builder(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")
+                .build());
   }
 
-  /**
-   * Test adding empty resource modules as jars.
-   */
+  /** Test adding empty resource modules as jars. */
   @Test
   public void testEmptyResourceModuleIsAddedAsJar() {
-    ProjectView projectView = ProjectView.builder()
-      .put(ListSection.builder(DirectorySection.KEY)
-             .add(DirectoryEntry.include(new WorkspacePath("java/com/google/android/apps/example")))
-             .add(DirectoryEntry.include(new WorkspacePath("javatests/com/google/android/apps/example"))))
-      .build();
+    ProjectView projectView =
+        ProjectView.builder()
+            .add(
+                ListSection.builder(DirectorySection.KEY)
+                    .add(DirectoryEntry.include(new WorkspacePath("java/apps/example")))
+                    .add(DirectoryEntry.include(new WorkspacePath("javatests/apps/example"))))
+            .build();
 
-    /**
-     * Deps are project -> lib0 (no res) -> lib1 (has res)
-     *                                    \
-     *                                     -> lib2 (has res)
-     */
-    RuleMapBuilder response = RuleMapBuilder.builder()
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//java/com/google/android/apps/example/lib0:lib0")
-          .setKind("android_library")
-          .setBuildFile(sourceRoot("java/com/google/android/apps/example/lib0/BUILD"))
-          .addSource(sourceRoot("java/com/google/android/apps/example/lib0/SharedActivity.java"))
-          .setAndroidInfo(AndroidRuleIdeInfo.builder()
-                            .setManifestFile(sourceRoot("java/com/google/android/apps/example/lib0/AndroidManifest.xml"))
+    /** Deps are project -> lib0 (no res) -> lib1 (has res) \ -> lib2 (has res) */
+    RuleMapBuilder response =
+        RuleMapBuilder.builder()
+            .addRule(
+                RuleIdeInfo.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()
+                            .setManifestFile(source("java/apps/example/lib0/AndroidManifest.xml"))
                             .setGenerateResourceClass(true)
                             .setResourceJavaPackage("com.google.android.apps.example.lib0")
-                            .setResourceJar(LibraryArtifact.builder()
-                                              .setJar(genRoot("java/com/google/android/apps/example/lib0/lib0_resources.jar"))
-                                              .setRuntimeJar(genRoot("java/com/google/android/apps/example/lib0/lib0_resources.jar"))))
-          .addDependency("//java/com/google/android/apps/example/lib1:lib1")
-          .addDependency("//java/com/google/android/apps/example/lib2:lib2")
-          .setJavaInfo(JavaRuleIdeInfo.builder()
-                         .addJar(LibraryArtifact.builder()
-                                   .setJar(genRoot("java/com/google/android/apps/example/lib0/lib0.jar"))
-                                   .setRuntimeJar(genRoot("java/com/google/android/apps/example/lib0/lib0.jar")))
-                         .addJar(LibraryArtifact.builder()
-                                   .setJar(genRoot("java/com/google/android/apps/example/lib0/lib0_resources.jar"))
-                                   .setRuntimeJar(genRoot("java/com/google/android/apps/example/lib0/lib0_resources.jar")))))
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//java/com/google/android/apps/example/lib1:lib1")
-          .setKind("android_library")
-          .setBuildFile(sourceRoot("java/com/google/android/apps/example/lib1/BUILD"))
-          .addSource(sourceRoot("java/com/google/android/apps/example/lib1/SharedActivity.java"))
-          .setAndroidInfo(AndroidRuleIdeInfo.builder()
-                            .setManifestFile(sourceRoot("java/com/google/android/apps/example/lib1/AndroidManifest.xml"))
-                            .addResource(sourceRoot("java/com/google/android/apps/example/lib1/res"))
+                            .setResourceJar(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(
+                                        gen("java/apps/example/lib0/lib0_resources.jar"))
+                                    .setClassJar(gen("java/apps/example/lib0/lib0_resources.jar"))))
+                    .addDependency("//java/apps/example/lib1:lib1")
+                    .addDependency("//java/apps/example/lib2:lib2")
+                    .setJavaInfo(
+                        JavaRuleIdeInfo.builder()
+                            .addJar(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(gen("java/apps/example/lib0/lib0.jar"))
+                                    .setClassJar(gen("java/apps/example/lib0/lib0.jar")))
+                            .addJar(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(
+                                        gen("java/apps/example/lib0/lib0_resources.jar"))
+                                    .setClassJar(
+                                        gen("java/apps/example/lib0/lib0_resources.jar")))))
+            .addRule(
+                RuleIdeInfo.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()
+                            .setManifestFile(source("java/apps/example/lib1/AndroidManifest.xml"))
+                            .addResource(source("java/apps/example/lib1/res"))
                             .setGenerateResourceClass(true)
                             .setResourceJavaPackage("com.google.android.apps.example.lib1")
-                            .setResourceJar(LibraryArtifact.builder()
-                                              .setJar(genRoot("java/com/google/android/apps/example/lib1/li11_resources.jar"))
-                                              .setRuntimeJar(genRoot("java/com/google/android/apps/example/lib1/lib1_resources.jar"))))
-          .setJavaInfo(JavaRuleIdeInfo.builder()
-                         .addJar(LibraryArtifact.builder()
-                                   .setJar(genRoot("java/com/google/android/apps/example/lib1/lib1.jar"))
-                                   .setRuntimeJar(genRoot("java/com/google/android/apps/example/lib1/lib1.jar")))
-                         .addJar(LibraryArtifact.builder()
-                                   .setJar(genRoot("java/com/google/android/apps/example/lib1/lib1_resources.jar"))
-                                   .setRuntimeJar(genRoot("java/com/google/android/apps/example/lib1/lib1_resources.jar")))))
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//java/com/google/android/apps/example/lib2:lib2")
-          .setBuildFile(sourceRoot("java/com/google/android/apps/example/lib2/BUILD"))
-          .setKind("android_library")
-          .addSource(sourceRoot("java/com/google/android/apps/example/lib2/SharedActivity.java"))
-          .setAndroidInfo(AndroidRuleIdeInfo.builder()
-                            .setManifestFile(sourceRoot("java/com/google/android/apps/example/lib2/AndroidManifest.xml"))
-                            .addResource(sourceRoot("java/com/google/android/apps/example/lib2/res"))
+                            .setResourceJar(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(
+                                        gen("java/apps/example/lib1/li11_resources.jar"))
+                                    .setClassJar(gen("java/apps/example/lib1/lib1_resources.jar"))))
+                    .setJavaInfo(
+                        JavaRuleIdeInfo.builder()
+                            .addJar(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(gen("java/apps/example/lib1/lib1.jar"))
+                                    .setClassJar(gen("java/apps/example/lib1/lib1.jar")))
+                            .addJar(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(
+                                        gen("java/apps/example/lib1/lib1_resources.jar"))
+                                    .setClassJar(
+                                        gen("java/apps/example/lib1/lib1_resources.jar")))))
+            .addRule(
+                RuleIdeInfo.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()
+                            .setManifestFile(source("java/apps/example/lib2/AndroidManifest.xml"))
+                            .addResource(source("java/apps/example/lib2/res"))
                             .setGenerateResourceClass(true)
                             .setResourceJavaPackage("com.google.android.libraries.example.lib2")
-                            .setResourceJar(LibraryArtifact.builder()
-                                              .setJar(genRoot("java/com/google/android/apps/example/lib2/lib2_resources.jar"))
-                                              .setRuntimeJar(genRoot("java/com/google/android/apps/example/lib2/lib2_resources.jar"))))
-          .setBuildFile(sourceRoot("java/com/google/android/apps/example/lib2/BUILD"))
-          .setJavaInfo(JavaRuleIdeInfo.builder()
-                         .addJar(LibraryArtifact.builder()
-                                   .setJar(genRoot("java/com/google/android/apps/example/lib2/lib2.jar"))
-                                   .setRuntimeJar(genRoot("java/com/google/android/apps/example/lib2/lib2.jar")))
-                         .addJar(LibraryArtifact.builder()
-                                   .setJar(genRoot("java/com/google/android/apps/example/lib2/lib2_resources.jar"))
-                                   .setRuntimeJar(genRoot("java/com/google/android/apps/example/lib2/lib2_resources.jar")))))
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//java/com/google/android/apps/example:example_debug")
-          .setKind("android_binary")
-          .setBuildFile(sourceRoot("java/com/google/android/apps/example/BUILD"))
-          .addSource(sourceRoot("java/com/google/android/apps/example/MainActivity.java"))
-          .setAndroidInfo(AndroidRuleIdeInfo.builder()
-                            .setManifestFile(sourceRoot("java/com/google/android/apps/example/AndroidManifest.xml"))
-                            .addResource(sourceRoot("java/com/google/android/apps/example/res"))
+                            .setResourceJar(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(
+                                        gen("java/apps/example/lib2/lib2_resources.jar"))
+                                    .setClassJar(gen("java/apps/example/lib2/lib2_resources.jar"))))
+                    .setBuildFile(source("java/apps/example/lib2/BUILD"))
+                    .setJavaInfo(
+                        JavaRuleIdeInfo.builder()
+                            .addJar(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(gen("java/apps/example/lib2/lib2.jar"))
+                                    .setClassJar(gen("java/apps/example/lib2/lib2.jar")))
+                            .addJar(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(
+                                        gen("java/apps/example/lib2/lib2_resources.jar"))
+                                    .setClassJar(
+                                        gen("java/apps/example/lib2/lib2_resources.jar")))))
+            .addRule(
+                RuleIdeInfo.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()
+                            .setManifestFile(source("java/apps/example/AndroidManifest.xml"))
+                            .addResource(source("java/apps/example/res"))
                             .setGenerateResourceClass(true)
                             .setResourceJavaPackage("com.google.android.apps.example"))
-          .addDependency("//java/com/google/android/apps/example/lib0:lib0")
-          .setJavaInfo(JavaRuleIdeInfo.builder()
-                         .addJar(LibraryArtifact.builder()
-                                   .setJar(genRoot("java/com/google/android/apps/example/example_debug.jar"))
-                                   .setRuntimeJar(genRoot("java/com/google/android/apps/example/example_debug.jar")))));
+                    .addDependency("//java/apps/example/lib0:lib0")
+                    .setJavaInfo(
+                        JavaRuleIdeInfo.builder()
+                            .addJar(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(gen("java/apps/example/example_debug.jar"))
+                                    .setClassJar(gen("java/apps/example/example_debug.jar")))));
 
-    BlazeAndroidImportResult result = importWorkspace(
-      workspaceRoot,
-      response,
-      projectView
-    );
-    errorCollector.assertNoIssues();
+    RuleMap ruleMap = response.build();
+    BlazeAndroidJavaSyncAugmenter syncAugmenter = new BlazeAndroidJavaSyncAugmenter();
+    List<BlazeJarLibrary> jars = Lists.newArrayList();
+    List<BlazeJarLibrary> genJars = Lists.newArrayList();
+    ImportRoots importRoots =
+        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);
+      }
+    }
 
-    assertThat(result.androidResourceModules).containsExactly(
-      AndroidResourceModule.builder(new Label("//java/com/google/android/apps/example:example_debug"))
-        .addResourceAndTransitiveResource(sourceRoot("java/com/google/android/apps/example/res"))
-        .addTransitiveResource(sourceRoot("java/com/google/android/apps/example/lib1/res"))
-        .addTransitiveResource(sourceRoot("java/com/google/android/apps/example/lib2/res"))
-        .addTransitiveResourceDependency("//java/com/google/android/apps/example/lib0:lib0")
-        .addTransitiveResourceDependency("//java/com/google/android/apps/example/lib1:lib1")
-        .addTransitiveResourceDependency("//java/com/google/android/apps/example/lib2:lib2")
-        .build(),
-      AndroidResourceModule.builder(new Label("//java/com/google/android/apps/example/lib1:lib1"))
-        .addResourceAndTransitiveResource(sourceRoot("java/com/google/android/apps/example/lib1/res"))
-        .build(),
-      AndroidResourceModule.builder(new Label("//java/com/google/android/apps/example/lib2:lib2"))
-        .addResourceAndTransitiveResource(sourceRoot("java/com/google/android/apps/example/lib2/res"))
-        .build()
-    );
-
-    assertEquals(1, result.libraries.size());
-    BlazeLibrary library = result.libraries.iterator().next();
-    assertEquals("lib0_resources.jar", library.getLibraryArtifact().jar.getFile().getName());
+    assertThat(
+            jars.stream()
+                .map(library -> library.libraryArtifact.interfaceJar.getFile().getName())
+                .collect(Collectors.toList()))
+        .containsExactly("lib0_resources.jar");
   }
 
   @Test
   public void testIdlClassJarIsAddedAsLibrary() {
-    ProjectView projectView = ProjectView.builder()
-      .put(ListSection.builder(DirectorySection.KEY)
-             .add(DirectoryEntry.include(new WorkspacePath("example"))))
-      .build();
+    ProjectView projectView =
+        ProjectView.builder()
+            .add(
+                ListSection.builder(DirectorySection.KEY)
+                    .add(DirectoryEntry.include(new WorkspacePath("example"))))
+            .build();
 
-    RuleMapBuilder ruleMapBuilder = RuleMapBuilder.builder()
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//example:lib")
-          .setBuildFile(sourceRoot("example/BUILD"))
-          .setKind("android_binary")
-          .addSource(sourceRoot("example/MainActivity.java"))
-          .setAndroidInfo(AndroidRuleIdeInfo.builder()
+    RuleMapBuilder ruleMapBuilder =
+        RuleMapBuilder.builder()
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setLabel("//example:lib")
+                    .setBuildFile(source("example/BUILD"))
+                    .setKind("android_binary")
+                    .addSource(source("example/MainActivity.java"))
+                    .setAndroidInfo(
+                        AndroidRuleIdeInfo.builder()
                             .setResourceJavaPackage("example")
-                            .setIdlJar(LibraryArtifact.builder()
-                                         .setJar(genRoot("example/libidl.jar"))
-                                         .setSourceJar(genRoot("example/libidl.srcjar"))
-                                         .build())
+                            .setIdlJar(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(gen("example/libidl.jar"))
+                                    .setSourceJar(gen("example/libidl.srcjar"))
+                                    .build())
                             .setHasIdlSources(true)));
 
-    BlazeAndroidImportResult result = importWorkspace(
-      workspaceRoot,
-      ruleMapBuilder,
-      projectView
-    );
-    errorCollector.assertNoIssues();
-
-    assertEquals(1, result.libraries.size());
-
-    BlazeLibrary library = result.libraries.iterator().next();
-    assertEquals("libidl.jar", library.getLibraryArtifact().jar.getFile().getName());
-    assertEquals("libidl.srcjar", library.getLibraryArtifact().sourceJar.getFile().getName());
+    RuleMap ruleMap = ruleMapBuilder.build();
+    BlazeAndroidJavaSyncAugmenter syncAugmenter = new BlazeAndroidJavaSyncAugmenter();
+    List<BlazeJarLibrary> jars = Lists.newArrayList();
+    List<BlazeJarLibrary> genJars = Lists.newArrayList();
+    ImportRoots importRoots =
+        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);
+      }
+    }
+    assertThat(
+            genJars
+                .stream()
+                .map(library -> library.libraryArtifact.interfaceJar.getFile().getName())
+                .collect(Collectors.toList()))
+        .containsExactly("libidl.jar");
   }
 
   @Test
   public void testAndroidResourceImport() {
-    ProjectView projectView = ProjectView.builder()
-      .put(ListSection.builder(DirectorySection.KEY)
-             .add(DirectoryEntry.include(new WorkspacePath("java/com/google/android/example"))))
-      .build();
+    ProjectView projectView =
+        ProjectView.builder()
+            .add(
+                ListSection.builder(DirectorySection.KEY)
+                    .add(DirectoryEntry.include(new WorkspacePath("java/example"))))
+            .build();
 
-    RuleMapBuilder ruleMapBuilder = RuleMapBuilder.builder()
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//java/com/google/android/example:lib")
-          .setBuildFile(sourceRoot("java/com/google/android/example/BUILD"))
-          .setKind("android_library")
-          .setAndroidInfo(AndroidRuleIdeInfo.builder()
-                            .setLegacyResources(new Label("//java/com/google/android/example:resources"))
-                            .setManifestFile(sourceRoot("java/com/google/android/example/AndroidManifest.xml"))
-                            .addResource(sourceRoot("java/com/google/android/example/res"))
+    RuleMapBuilder ruleMapBuilder =
+        RuleMapBuilder.builder()
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setLabel("//java/example:lib")
+                    .setBuildFile(source("java/example/BUILD"))
+                    .setKind("android_library")
+                    .setAndroidInfo(
+                        AndroidRuleIdeInfo.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()
-          .setLabel("//java/com/google/android/example:resources")
-          .setBuildFile(sourceRoot("java/com/google/android/example/BUILD"))
-          .setKind("android_resources")
-          .setAndroidInfo(AndroidRuleIdeInfo.builder()
-                            .setManifestFile(sourceRoot("java/com/google/android/example/AndroidManifest.xml"))
-                            .addResource(sourceRoot("java/com/google/android/example/res"))
+                    .build())
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setLabel("//java/example:resources")
+                    .setBuildFile(source("java/example/BUILD"))
+                    .setKind("android_resources")
+                    .setAndroidInfo(
+                        AndroidRuleIdeInfo.builder()
+                            .setManifestFile(source("java/example/AndroidManifest.xml"))
+                            .addResource(source("java/example/res"))
                             .setGenerateResourceClass(true)
                             .setResourceJavaPackage("com.google.android.example"))
-          .build());
+                    .build());
 
-    BlazeAndroidImportResult result = importWorkspace(
-      workspaceRoot,
-      ruleMapBuilder,
-      projectView
-    );
+    BlazeAndroidImportResult result = importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
     errorCollector.assertNoIssues();
-    assertThat(result.androidResourceModules).containsExactly(
-      AndroidResourceModule.builder(new Label("//java/com/google/android/example:resources"))
-        .addResourceAndTransitiveResource(sourceRoot("java/com/google/android/example/res"))
-        .build()
-    );
+    assertThat(result.androidResourceModules)
+        .containsExactly(
+            AndroidResourceModule.builder(new Label("//java/example:resources"))
+                .addResourceAndTransitiveResource(source("java/example/res"))
+                .build());
   }
 
   @Test
   public void testResourceImportOutsideSourceFilterIsAddedToResourceLibrary() {
-    ProjectView projectView = ProjectView.builder()
-      .put(ListSection.builder(DirectorySection.KEY)
-             .add(DirectoryEntry.include(new WorkspacePath("java/com/google/android/example"))))
-      .build();
+    ProjectView projectView =
+        ProjectView.builder()
+            .add(
+                ListSection.builder(DirectorySection.KEY)
+                    .add(DirectoryEntry.include(new WorkspacePath("java/example"))))
+            .build();
 
-    RuleMapBuilder ruleMapBuilder = RuleMapBuilder.builder()
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//java/com/google/android/example:lib")
-          .setBuildFile(sourceRoot("java/com/google/android/example/BUILD"))
-          .setKind("android_library")
-          .setAndroidInfo(AndroidRuleIdeInfo.builder()
-                            .setManifestFile(sourceRoot("java/com/google/android/example/AndroidManifest.xml"))
-                            .addResource(sourceRoot("java/com/google/android/example/res"))
+    RuleMapBuilder ruleMapBuilder =
+        RuleMapBuilder.builder()
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setLabel("//java/example:lib")
+                    .setBuildFile(source("java/example/BUILD"))
+                    .setKind("android_library")
+                    .setAndroidInfo(
+                        AndroidRuleIdeInfo.builder()
+                            .setManifestFile(source("java/example/AndroidManifest.xml"))
+                            .addResource(source("java/example/res"))
                             .setGenerateResourceClass(true)
                             .setResourceJavaPackage("com.google.android.example"))
-          .addDependency("//java/com/google/android/example2:resources")
-          .build())
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//java/com/google/android/example2:resources")
-          .setBuildFile(sourceRoot("java/com/google/android/example2/BUILD"))
-          .setKind("android_library")
-          .setAndroidInfo(AndroidRuleIdeInfo.builder()
-                            .setManifestFile(sourceRoot("java/com/google/android/example2/AndroidManifest.xml"))
-                            .addResource(sourceRoot("java/com/google/android/example2/res"))
+                    .addDependency("//java/example2:resources")
+                    .build())
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setLabel("//java/example2:resources")
+                    .setBuildFile(source("java/example2/BUILD"))
+                    .setKind("android_library")
+                    .setAndroidInfo(
+                        AndroidRuleIdeInfo.builder()
+                            .setManifestFile(source("java/example2/AndroidManifest.xml"))
+                            .addResource(source("java/example2/res"))
                             .setGenerateResourceClass(true)
                             .setResourceJavaPackage("com.google.android.example2"))
-          .build());
+                    .build());
 
-    BlazeAndroidImportResult result = importWorkspace(
-      workspaceRoot,
-      ruleMapBuilder,
-      projectView
-    );
+    BlazeAndroidImportResult result = importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
     errorCollector.assertNoIssues();
-    BlazeLibrary library = result.libraries.stream()
-      .filter(item -> item.getKey().equals(LibraryKey.forResourceLibrary()))
-      .findAny()
-      .orElse(null);
+    BlazeResourceLibrary library = result.resourceLibrary;
     assertThat(library).isNotNull();
-    assertThat(library.getSources()).containsExactly(
-      new File("/root/java/com/google/android/example2/res")
-    );
+    assertThat(library.sources).containsExactly(new File("/root/java/example2/res"));
   }
 
   @Test
   public void testConflictingResourceRClasses() {
-    ProjectView projectView = ProjectView.builder()
-      .put(ListSection.builder(DirectorySection.KEY)
-             .add(DirectoryEntry.include(new WorkspacePath("java/com/google/android/example"))))
-      .build();
+    ProjectView projectView =
+        ProjectView.builder()
+            .add(
+                ListSection.builder(DirectorySection.KEY)
+                    .add(DirectoryEntry.include(new WorkspacePath("java/example"))))
+            .build();
 
-    RuleMapBuilder ruleMapBuilder = RuleMapBuilder.builder()
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//java/com/google/android/example:lib")
-          .setBuildFile(sourceRoot("java/com/google/android/example/BUILD"))
-          .setKind("android_library")
-          .setAndroidInfo(AndroidRuleIdeInfo.builder()
-                            .setManifestFile(sourceRoot("java/com/google/android/example/AndroidManifest.xml"))
-                            .addResource(sourceRoot("java/com/google/android/example/res"))
+    RuleMapBuilder ruleMapBuilder =
+        RuleMapBuilder.builder()
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setLabel("//java/example:lib")
+                    .setBuildFile(source("java/example/BUILD"))
+                    .setKind("android_library")
+                    .setAndroidInfo(
+                        AndroidRuleIdeInfo.builder()
+                            .setManifestFile(source("java/example/AndroidManifest.xml"))
+                            .addResource(source("java/example/res"))
                             .setGenerateResourceClass(true)
                             .setResourceJavaPackage("com.google.android.example"))
-          .addDependency("//java/com/google/android/example2:resources")
-          .build())
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//java/com/google/android/example:lib2")
-          .setBuildFile(sourceRoot("java/com/google/android/example2/BUILD"))
-          .setKind("android_library")
-          .setAndroidInfo(AndroidRuleIdeInfo.builder()
-                            .setManifestFile(sourceRoot("java/com/google/android/example2/AndroidManifest.xml"))
-                            .addResource(sourceRoot("java/com/google/android/example/res2"))
+                    .addDependency("//java/example2:resources")
+                    .build())
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setLabel("//java/example:lib2")
+                    .setBuildFile(source("java/example2/BUILD"))
+                    .setKind("android_library")
+                    .setAndroidInfo(
+                        AndroidRuleIdeInfo.builder()
+                            .setManifestFile(source("java/example2/AndroidManifest.xml"))
+                            .addResource(source("java/example/res2"))
                             .setGenerateResourceClass(true)
                             .setResourceJavaPackage("com.google.android.example"))
-          .build());
+                    .build());
 
-    BlazeAndroidImportResult result = importWorkspace(
-      workspaceRoot,
-      ruleMapBuilder,
-      projectView
-    );
+    BlazeAndroidImportResult result = importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
     errorCollector.assertIssueContaining("Multiple R classes generated");
 
-    assertThat(result.androidResourceModules).containsExactly(
-      AndroidResourceModule.builder(new Label("//java/com/google/android/example:lib"))
-        .addResourceAndTransitiveResource(sourceRoot("java/com/google/android/example/res"))
-      .build()
-    );
+    assertThat(result.androidResourceModules)
+        .containsExactly(
+            AndroidResourceModule.builder(new Label("//java/example:lib"))
+                .addResourceAndTransitiveResource(source("java/example/res"))
+                .build());
   }
 
-  private ArtifactLocation sourceRoot(String relativePath) {
-    return ArtifactLocation.builder()
-      .setRootPath(FAKE_ROOT)
-      .setRelativePath(relativePath)
-      .setIsSource(true)
-      .build();
+  @Test
+  public void testMixingGeneratedAndNonGeneratedSourcesGeneratesIssue() {
+    ProjectView projectView =
+        ProjectView.builder()
+            .add(
+                ListSection.builder(DirectorySection.KEY)
+                    .add(DirectoryEntry.include(new WorkspacePath("java/example"))))
+            .build();
+
+    RuleMapBuilder ruleMapBuilder =
+        RuleMapBuilder.builder()
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setLabel("//java/example:lib")
+                    .setBuildFile(source("java/example/BUILD"))
+                    .setKind("android_library")
+                    .setAndroidInfo(
+                        AndroidRuleIdeInfo.builder()
+                            .setManifestFile(source("java/example/AndroidManifest.xml"))
+                            .addResource(source("java/example/res"))
+                            .addResource(gen("java/example/res"))
+                            .setGenerateResourceClass(true)
+                            .setResourceJavaPackage("com.google.android.example"))
+                    .build());
+
+    importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
+    errorCollector.assertIssueContaining("Dropping generated resource");
   }
 
-  private static ArtifactLocation genRoot(String relativePath) {
+  private ArtifactLocation source(String relativePath) {
     return ArtifactLocation.builder()
-      .setRootPath(FAKE_GEN_ROOT)
-      .setRootExecutionPathFragment(FAKE_GEN_ROOT_EXECUTION_PATH_FRAGMENT)
-      .setRelativePath(relativePath)
-      .setIsSource(false)
-      .build();
+        .setRootPath(FAKE_ROOT)
+        .setRelativePath(relativePath)
+        .setIsSource(true)
+        .build();
+  }
+
+  private static ArtifactLocation gen(String relativePath) {
+    return ArtifactLocation.builder()
+        .setRootPath(FAKE_GEN_ROOT)
+        .setRootExecutionPathFragment(FAKE_GEN_ROOT_EXECUTION_PATH_FRAGMENT)
+        .setRelativePath(relativePath)
+        .setIsSource(false)
+        .build();
   }
 }
diff --git a/base/BUILD b/base/BUILD
new file mode 100644
index 0000000..add4fdb
--- /dev/null
+++ b/base/BUILD
@@ -0,0 +1,110 @@
+licenses(["notice"])  # Apache 2.0
+
+java_library(
+    name = "base",
+    srcs = glob(["src/**/*.java"]),
+    resources = glob(["resources/**/*"]),
+    visibility = ["//visibility:public"],
+    deps = [
+        "//common/experiments",
+        "//intellij_platform_sdk:plugin_api",
+        "//proto_deps",
+        "@jsr305_annotations//jar",
+    ],
+)
+
+filegroup(
+    name = "plugin_xml",
+    srcs = ["src/META-INF/blaze-base.xml"],
+    visibility = ["//visibility:public"],
+)
+
+java_library(
+    name = "unit_test_utils",
+    testonly = 1,
+    srcs = glob(["tests/utils/unit/**/*.java"]),
+    visibility = ["//visibility:public"],
+    deps = [
+        "//base",
+        "//intellij_platform_sdk:plugin_api_for_tests",
+        "@jsr305_annotations//jar",
+        "@junit//jar",
+    ],
+)
+
+java_library(
+    name = "integration_test_utils",
+    testonly = 1,
+    srcs = glob(["tests/utils/integration/**/*.java"]),
+    visibility = ["//visibility:public"],
+    deps = [
+        ":unit_test_utils",
+        "//base",
+        "//intellij_platform_sdk:plugin_api_for_tests",
+        "//proto_deps",
+        "@jsr305_annotations//jar",
+    ],
+)
+
+load(
+    "//build_defs:build_defs.bzl",
+    "stamped_plugin_xml",
+    "intellij_plugin",
+)
+
+stamped_plugin_xml(
+    name = "base_plugin_xml",
+    plugin_id = "com.google.idea.blaze.base",
+    plugin_name = "com.google.idea.blaze.base",
+    plugin_xml = "plugin_xml",
+)
+
+intellij_plugin(
+    name = "base_integration_test_plugin",
+    testonly = 1,
+    plugin_xml = ":base_plugin_xml",
+    deps = [
+        ":base",
+    ],
+)
+
+load(
+    "//intellij_test:test_defs.bzl",
+    "intellij_integration_test_suite",
+    "intellij_unit_test_suite",
+)
+
+intellij_unit_test_suite(
+    name = "unit_tests",
+    srcs = glob(["tests/unittests/**/*.java"]),
+    test_package_root = "com.google.idea.blaze.base",
+    deps = [
+        ":base",
+        ":unit_test_utils",
+        "//common/experiments",
+        "//common/experiments:unit_test_utils",
+        "//intellij_platform_sdk:plugin_api_for_tests",
+        "//proto_deps",
+        "@jsr305_annotations//jar",
+        "@junit//jar",
+    ],
+)
+
+intellij_integration_test_suite(
+    name = "integration_tests",
+    srcs = glob(["tests/integrationtests/**/*.java"]),
+    required_plugins = "com.google.idea.blaze.base",
+    test_package_root = "com.google.idea.blaze.base",
+    runtime_deps = [
+        ":base_integration_test_plugin",
+    ],
+    deps = [
+        ":base",
+        ":integration_test_utils",
+        ":unit_test_utils",
+        "//intellij_platform_sdk:plugin_api_for_tests",
+        "//proto_deps",
+        "@jsr305_annotations//jar",
+        "@junit//jar",
+    ],
+)
diff --git a/base/lib/proto_deps.jar b/base/lib/proto_deps.jar
new file mode 100755
index 0000000..4943ed3
--- /dev/null
+++ b/base/lib/proto_deps.jar
Binary files differ
diff --git a/blaze-base/resources/binaries/README b/base/resources/binaries/README
similarity index 100%
rename from blaze-base/resources/binaries/README
rename to base/resources/binaries/README
diff --git a/blaze-base/resources/icons/bazel_leaf.png b/base/resources/icons/bazel_leaf.png
similarity index 100%
rename from blaze-base/resources/icons/bazel_leaf.png
rename to base/resources/icons/bazel_leaf.png
Binary files differ
diff --git a/blaze-base/resources/icons/blaze.png b/base/resources/icons/blaze.png
similarity index 100%
rename from blaze-base/resources/icons/blaze.png
rename to base/resources/icons/blaze.png
Binary files differ
diff --git a/blaze-base/resources/icons/blazeToolWindow.png b/base/resources/icons/blazeToolWindow.png
similarity index 100%
rename from blaze-base/resources/icons/blazeToolWindow.png
rename to base/resources/icons/blazeToolWindow.png
Binary files differ
diff --git a/blaze-base/resources/icons/blaze_clean.png b/base/resources/icons/blaze_clean.png
similarity index 100%
rename from blaze-base/resources/icons/blaze_clean.png
rename to base/resources/icons/blaze_clean.png
Binary files differ
diff --git a/blaze-base/resources/icons/blaze_dirty.png b/base/resources/icons/blaze_dirty.png
similarity index 100%
rename from blaze-base/resources/icons/blaze_dirty.png
rename to base/resources/icons/blaze_dirty.png
Binary files differ
diff --git a/blaze-base/resources/icons/blaze_failed.png b/base/resources/icons/blaze_failed.png
similarity index 100%
rename from blaze-base/resources/icons/blaze_failed.png
rename to base/resources/icons/blaze_failed.png
Binary files differ
diff --git a/blaze-base/resources/icons/blaze_slow.png b/base/resources/icons/blaze_slow.png
similarity index 100%
rename from blaze-base/resources/icons/blaze_slow.png
rename to base/resources/icons/blaze_slow.png
Binary files differ
diff --git a/blaze-base/resources/icons/build_editor.png b/base/resources/icons/build_editor.png
similarity index 100%
rename from blaze-base/resources/icons/build_editor.png
rename to base/resources/icons/build_editor.png
Binary files differ
diff --git a/blaze-base/resources/icons/build_file.png b/base/resources/icons/build_file.png
similarity index 100%
rename from blaze-base/resources/icons/build_file.png
rename to base/resources/icons/build_file.png
Binary files differ
diff --git a/blaze-base/resources/icons/build_rule.png b/base/resources/icons/build_rule.png
similarity index 100%
rename from blaze-base/resources/icons/build_rule.png
rename to base/resources/icons/build_rule.png
Binary files differ
diff --git a/blaze-base/scripts/create_bugreport.sh b/base/scripts/create_bugreport.sh
similarity index 100%
rename from blaze-base/scripts/create_bugreport.sh
rename to base/scripts/create_bugreport.sh
diff --git a/base/src/META-INF/blaze-base.xml b/base/src/META-INF/blaze-base.xml
new file mode 100644
index 0000000..606ea98
--- /dev/null
+++ b/base/src/META-INF/blaze-base.xml
@@ -0,0 +1,281 @@
+<!--
+  ~ 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.
+  -->
+<idea-plugin>
+  <actions>
+    <action id="MakeBlazeProject" class="com.google.idea.blaze.base.actions.BlazeMakeProjectAction" use-shortcut-of="CompileDirty" icon="AllIcons.Actions.Compile">
+    </action>
+    <action id="MakeBlazeModule" class="com.google.idea.blaze.base.actions.BlazeCompileFileAction">
+    </action>
+    <action id="Blaze.IncrementalSyncProject" class="com.google.idea.blaze.base.sync.actions.IncrementalSyncProjectAction" icon="BlazeIcons.Blaze">
+    </action>
+    <action id="Blaze.FullSyncProject" class="com.google.idea.blaze.base.sync.actions.FullSyncProjectAction" icon="BlazeIcons.BlazeSlow">
+    </action>
+    <action id="Blaze.SyncWorkingSet" class="com.google.idea.blaze.base.sync.actions.SyncWorkingSetAction" icon="BlazeIcons.Blaze" text="Sync Working Set">
+    </action>
+    <action id="Blaze.ExpandSyncToWorkingSet" class="com.google.idea.blaze.base.sync.actions.ExpandSyncToWorkingSetAction" text="Expand Sync to Working Set">
+    </action>
+    <action id="Blaze.ShowPerformanceWarnings" class="com.google.idea.blaze.base.sync.actions.ShowPerformanceWarningsToggleAction" text="Show Performance Warnings">
+    </action>
+    <action id="Blaze.EditProjectView" class="com.google.idea.blaze.base.settings.ui.EditProjectViewAction" text="Edit Project View..." icon="BlazeIcons.Blaze">
+    </action>
+
+    <action class="com.google.idea.blaze.base.buildmap.OpenCorrespondingBuildFile"
+            id="Blaze.OpenCorrespondingBuildFile"
+            icon="BlazeIcons.Blaze"
+            text="Open Corresponding BUILD File">
+    </action>
+    <action class="com.google.idea.blaze.base.sync.actions.PartialSyncAction"
+            id="Blaze.PartialSync"
+            icon="BlazeIcons.Blaze">
+    </action>
+
+    <group id="Blaze.MainMenuActionGroup" class="com.google.idea.blaze.base.actions.BlazeMenuGroup">
+      <add-to-group group-id="MainMenu" anchor="before" relative-to-action="HelpMenu"/>
+      <reference id="MakeBlazeProject"/>
+      <reference id="MakeBlazeModule"/>
+      <separator/>
+      <reference id="Blaze.EditProjectView"/>
+      <separator/>
+      <reference id="Blaze.IncrementalSyncProject"/>
+      <reference id="Blaze.FullSyncProject"/>
+      <reference id="Blaze.SyncWorkingSet"/>
+      <reference id="Blaze.PartialSync"/>
+      <reference id="Blaze.ExpandSyncToWorkingSet"/>
+      <reference id="Blaze.ShowPerformanceWarnings"/>
+    </group>
+
+    <group id="Blaze.MainToolBarActionGroup">
+      <add-to-group group-id="MainToolBar" anchor="before" relative-to-action="HelpTopics" />
+      <add-to-group group-id="NavBarToolBarOthers" anchor="last"/>
+      <reference id="Blaze.IncrementalSyncProject"/>
+    </group>
+
+    <group id="Blaze.NewActions" text="Edit Blaze structure" description="Create new Blaze packages, rules, etc.">
+      <add-to-group group-id="NewGroup" anchor="first"/>
+      <action id="Blaze.NewPackageAction" class="com.google.idea.blaze.base.ide.NewBlazePackageAction" popup="true"/>
+      <action id="Blaze.NewRuleAction" class="com.google.idea.blaze.base.ide.NewBlazeRuleAction" popup="true"/>
+      <separator/>
+    </group>
+
+    <group id="Blaze.ProjectViewPopupMenu">
+      <add-to-group anchor="after" group-id="ProjectViewPopupMenu" relative-to-action="EditSource"/>
+      <separator/>
+      <reference ref="Blaze.PartialSync"/>
+      <reference ref="Blaze.OpenCorrespondingBuildFile"/>
+    </group>
+
+    <group id="Blaze.EditorTabPopupMenu">
+      <add-to-group anchor="after" group-id="EditorTabPopupMenu" relative-to-action="CopyReference"/>
+      <separator/>
+      <reference ref="Blaze.PartialSync"/>
+      <reference ref="Blaze.OpenCorrespondingBuildFile"/>
+    </group>
+  </actions>
+
+  <extensions defaultExtensionNs="com.intellij">
+    <postStartupActivity implementation="com.google.idea.blaze.base.sync.BlazeSyncStartupActivity"/>
+
+    <toolWindow id="Blaze Console"
+                      anchor="bottom"
+                      secondary="true"
+                      conditionClass="com.google.idea.blaze.base.settings.IsBlazeProjectCondition"
+                      icon="BlazeIcons.BlazeToolWindow"
+                      factoryClass="com.google.idea.blaze.base.console.BlazeConsoleToolWindowFactory"/>
+    <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"/>
+
+    <projectService serviceInterface="com.google.idea.blaze.base.sync.data.BlazeProjectDataManager"
+                    serviceImplementation="com.google.idea.blaze.base.sync.data.BlazeProjectDataManagerImpl"/>
+    <projectService serviceImplementation="com.google.idea.blaze.base.sync.BlazeSyncManager"/>
+    <projectService serviceInterface="com.google.idea.blaze.base.sync.status.BlazeSyncStatus"
+                    serviceImplementation="com.google.idea.blaze.base.sync.status.BlazeSyncStatusImpl"/>
+
+    <applicationService serviceInterface="com.google.idea.blaze.base.async.executor.BlazeExecutor"
+                        serviceImplementation="com.google.idea.blaze.base.async.executor.BlazeExecutorImpl"/>
+    <fileDocumentManagerListener implementation="com.google.idea.blaze.base.buildmodifier.FileSaveHandler" order="first"/>
+    <applicationService serviceInterface="com.google.idea.blaze.base.io.InputStreamProvider"
+                        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.command.info.BlazeInfo"
+                        serviceImplementation="com.google.idea.blaze.base.command.info.BlazeInfoImpl"/>
+
+    <treeStructureProvider implementation="com.google.idea.blaze.base.treeview.BlazeTreeStructureProvider" id="blaze"/>
+
+    <applicationService serviceInterface="com.google.idea.blaze.base.projectview.ProjectViewStorageManager"
+                        serviceImplementation="com.google.idea.blaze.base.projectview.ProjectViewStorageManagerImpl"/>
+    <projectService serviceInterface="com.google.idea.blaze.base.projectview.ProjectViewManager"
+                    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.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 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"/>
+    <applicationService serviceInterface="com.google.idea.blaze.base.lang.buildfile.language.semantics.BuildLanguageSpecProvider"
+                        serviceImplementation="com.google.idea.blaze.base.lang.buildfile.language.semantics.BuildLanguageSpecProviderImpl"/>
+    <applicationService serviceInterface="com.google.idea.blaze.base.sync.projectstructure.ModuleEditorProvider"
+                        serviceImplementation="com.google.idea.blaze.base.sync.projectstructure.ModuleEditorProviderImpl"/>
+    <applicationService serviceInterface="com.google.idea.blaze.base.prefetch.PrefetchService"
+                        serviceImplementation="com.google.idea.blaze.base.prefetch.PrefetchServiceImpl"/>
+    <applicationService serviceImplementation="com.google.idea.blaze.base.wizard2.BlazeWizardUserSettingsStorage"/>
+    <projectService serviceInterface="com.google.idea.blaze.base.sync.workspace.WorkspacePathResolverProvider"
+                    serviceImplementation="com.google.idea.blaze.base.sync.workspace.WorkspacePathResolverProviderImpl"/>
+    <configurationType implementation="com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType"/>
+    <runConfigurationProducer
+        implementation="com.google.idea.blaze.base.run.producers.AllInPackageBlazeConfigurationProducer"
+        order="first"/>
+    <runConfigurationProducer
+        implementation="com.google.idea.blaze.base.run.producers.BlazeBuildFileRunConfigurationProducer"
+        order="first"/>
+    <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"/>
+  </extensions>
+
+  <extensions defaultExtensionNs="com.intellij">
+    <fileTypeFactory implementation="com.google.idea.blaze.base.lang.projectview.language.ProjectViewFileTypeFactory"/>
+    <lang.parserDefinition language="projectview" implementationClass="com.google.idea.blaze.base.lang.projectview.parser.ProjectViewParserDefinition"/>
+    <lang.commenter language="projectview" implementationClass="com.google.idea.blaze.base.lang.projectview.formatting.ProjectViewCommenter"/>
+    <lang.syntaxHighlighterFactory language="projectview" implementationClass="com.google.idea.blaze.base.lang.projectview.highlighting.ProjectViewSyntaxHighlighterFactory"/>
+    <completion.contributor language="projectview" implementationClass="com.google.idea.blaze.base.lang.projectview.completion.ProjectViewKeywordCompletionContributor"/>
+    <completion.contributor language="projectview" implementationClass="com.google.idea.blaze.base.lang.projectview.completion.WorkspaceTypeCompletionContributor"/>
+    <completion.contributor language="projectview" implementationClass="com.google.idea.blaze.base.lang.projectview.completion.AdditionalLanguagesCompletionContributor"/>
+    <enterHandlerDelegate implementation="com.google.idea.blaze.base.lang.projectview.formatting.ProjectViewEnterHandler"/>
+  </extensions>
+
+  <extensions defaultExtensionNs="com.intellij">
+    <fileTypeFactory implementation="com.google.idea.blaze.base.lang.buildfile.language.BuildFileTypeFactory"/>
+    <annotator language="BUILD" implementationClass="com.google.idea.blaze.base.lang.buildfile.validation.HighlightingAnnotator"/>
+    <!--<annotator language="BUILD" implementationClass="com.google.idea.blaze.base.lang.buildfile.validation.ErrorAnnotator"/>-->
+    <annotator language="BUILD" implementationClass="com.google.idea.blaze.base.lang.buildfile.validation.GlobErrorAnnotator"/>
+    <colorSettingsPage implementation="com.google.idea.blaze.base.lang.buildfile.highlighting.BuildColorsPage"/>
+    <projectService serviceImplementation="com.google.idea.blaze.base.lang.buildfile.psi.util.BuildElementGenerator"/>
+    <projectService serviceImplementation="com.google.idea.blaze.base.lang.buildfile.references.BuildReferenceManager"/>
+    <referencesSearch implementation="com.google.idea.blaze.base.lang.buildfile.search.BuildLabelReferenceSearcher"/>
+    <referencesSearch implementation="com.google.idea.blaze.base.lang.buildfile.search.GlobReferenceSearcher"/>
+    <readWriteAccessDetector implementation="com.google.idea.blaze.base.lang.buildfile.findusages.BuildReadWriteAccessDetector"/>
+    <elementDescriptionProvider implementation="com.google.idea.blaze.base.lang.buildfile.findusages.BuildElementDescriptionProvider"/>
+    <usageGroupingRuleProvider implementation="com.google.idea.blaze.base.lang.buildfile.findusages.BuildUsageGroupingRuleProvider"/>
+    <useScopeOptimizer implementation="com.google.idea.blaze.base.lang.buildfile.search.ExcludeBuildFilesScope"/>
+    <targetElementEvaluator language="BUILD" implementationClass="com.google.idea.blaze.base.lang.buildfile.findusages.BuildTargetElementEvaluator"/>
+    <quoteHandler fileType="BUILD" className="com.google.idea.blaze.base.lang.buildfile.editor.BuildQuoteHandler"/>
+    <enterHandlerDelegate implementation="com.google.idea.blaze.base.lang.buildfile.editor.BuildEnterBetweenBracketsHandler" order="before EnterBetweenBracesHandler"/>
+    <enterHandlerDelegate implementation="com.google.idea.blaze.base.lang.buildfile.editor.BuildEnterHandler" order="after EnterBetweenBracesHandler"/>
+    <completion.contributor language="BUILD" implementationClass="com.google.idea.blaze.base.lang.buildfile.completion.ParameterCompletionContributor"/>
+    <completion.contributor language="BUILD" implementationClass="com.google.idea.blaze.base.lang.buildfile.completion.BuiltInFunctionCompletionContributor"/>
+    <completion.contributor language="BUILD" implementationClass="com.google.idea.blaze.base.lang.buildfile.completion.BuiltInFunctionAttributeCompletionContributor"/>
+    <completion.contributor language="BUILD" implementationClass="com.google.idea.blaze.base.lang.buildfile.completion.ArgumentCompletionContributor"/>
+    <langCodeStyleSettingsProvider implementation="com.google.idea.blaze.base.lang.buildfile.formatting.BuildLanguageCodeStyleSettingsProvider"/>
+    <codeStyleSettingsProvider implementation="com.google.idea.blaze.base.lang.buildfile.formatting.BuildCodeStyleSettingsProvider"/>
+    <editor.backspaceModeOverride language="BUILD" implementationClass="com.intellij.codeInsight.editorActions.SmartBackspaceDisabler"/>
+    <filetype.stubBuilder filetype="BUILD" implementationClass="com.google.idea.blaze.base.lang.buildfile.stubs.BuildFileStubBuilder"/>
+  </extensions>
+
+  <extensions defaultExtensionNs="com.intellij.lang">
+    <syntaxHighlighterFactory language="BUILD" implementationClass="com.google.idea.blaze.base.lang.buildfile.highlighting.BuildSyntaxHighlighterFactory"/>
+    <parserDefinition language="BUILD" implementationClass="com.google.idea.blaze.base.lang.buildfile.parser.BuildParserDefinition"/>
+    <namesValidator language="BUILD" implementationClass="com.google.idea.blaze.base.lang.buildfile.refactor.BuildNamesValidator"/>
+    <braceMatcher language="BUILD" implementationClass="com.google.idea.blaze.base.lang.buildfile.formatting.BuildBraceMatcher"/>
+    <commenter language="BUILD" implementationClass="com.google.idea.blaze.base.lang.buildfile.formatting.BuildCommenter"/>
+    <foldingBuilder language="BUILD" implementationClass="com.google.idea.blaze.base.lang.buildfile.formatting.BuildFileFoldingBuilder"/>
+    <psiStructureViewFactory language="BUILD" implementationClass="com.google.idea.blaze.base.lang.buildfile.views.BuildStructureViewFactory"/>
+    <findUsagesProvider language="BUILD" implementationClass="com.google.idea.blaze.base.lang.buildfile.findusages.BuildFindUsagesProvider"/>
+    <refactoringSupport language="BUILD" implementationClass="com.google.idea.blaze.base.lang.buildfile.refactor.BuildRefactoringSupportProvider"/>
+    <documentationProvider language="BUILD" implementationClass="com.google.idea.blaze.base.lang.buildfile.documentation.BuildDocumentationProvider"/>
+  </extensions>
+
+  <extensionPoints>
+    <extensionPoint qualifiedName="com.google.idea.blaze.base.lang.buildfile.DumbAnnotator" interface="com.google.idea.blaze.base.lang.buildfile.validation.BuildAnnotator"/>
+    <extensionPoint qualifiedName="com.google.idea.blaze.base.lang.buildfile.Annotator" interface="com.google.idea.blaze.base.lang.buildfile.validation.BuildAnnotator"/>
+  </extensionPoints>
+
+  <application-components>
+    <component>
+      <implementation-class>com.google.idea.blaze.base.plugin.BlazeSpecificInitializer</implementation-class>
+    </component>
+    <component>
+      <implementation-class>com.google.idea.blaze.base.plugin.dependency.ProjectDependencyMigration</implementation-class>
+    </component>
+    <component>
+      <interface-class>com.google.idea.common.experiments.ExperimentService</interface-class>
+      <implementation-class>com.google.idea.blaze.base.experiments.BlazeExperimentService</implementation-class>
+    </component>
+  </application-components>
+
+  <extensionPoints>
+    <extensionPoint qualifiedName="com.google.idea.blaze.SyncListener" interface="com.google.idea.blaze.base.sync.SyncListener"/>
+    <extensionPoint qualifiedName="com.google.idea.blaze.SyncPlugin" interface="com.google.idea.blaze.base.sync.BlazeSyncPlugin"/>
+    <extensionPoint qualifiedName="com.google.idea.blaze.RuleConfigurationFactory" interface="com.google.idea.blaze.base.run.BlazeRuleConfigurationFactory"/>
+    <extensionPoint qualifiedName="com.google.idea.blaze.Prefetcher"
+                    interface="com.google.idea.blaze.base.prefetch.Prefetcher"/>
+    <extensionPoint qualifiedName="com.google.idea.blaze.PrefetchFileSource"
+                    interface="com.google.idea.blaze.base.prefetch.PrefetchFileSource"/>
+    <extensionPoint qualifiedName="com.google.idea.blaze.PsiFileProvider" interface="com.google.idea.blaze.base.lang.buildfile.search.PsiFileProvider"/>
+    <extensionPoint qualifiedName="com.google.idea.blaze.VcsHandler"
+                    interface="com.google.idea.blaze.base.vcs.BlazeVcsHandler"/>
+    <extensionPoint qualifiedName="com.google.idea.blaze.BlazeWizardOptionProvider"
+                    interface="com.google.idea.blaze.base.wizard2.BlazeWizardOptionProvider"/>
+    <extensionPoint qualifiedName="com.google.idea.blaze.DefaultSdkProvider"
+                    interface="com.google.idea.blaze.base.sync.sdk.DefaultSdkProvider"/>
+    <extensionPoint qualifiedName="com.google.idea.blaze.BuildFlagsProvider" interface="com.google.idea.blaze.base.command.BuildFlagsProvider"/>
+    <extensionPoint qualifiedName="com.google.idea.blaze.BuildSystemProvider" interface="com.google.idea.blaze.base.bazel.BuildSystemProvider"/>
+    <extensionPoint qualifiedName="com.google.idea.blaze.BuildifierBinaryProvider" interface="com.google.idea.blaze.base.buildmodifier.BuildifierBinaryProvider"/>
+    <extensionPoint qualifiedName="com.google.idea.blaze.LoggingService" interface="com.google.idea.blaze.base.metrics.LoggingService"/>
+    <extensionPoint qualifiedName="com.google.idea.blaze.BlazeCommandRunConfigurationHandlerProvider" interface="com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationHandlerProvider"/>
+    <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"/>
+  </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.lang.buildfile.language.semantics.BuildLanguageSpecProviderImpl"/>
+    <SyncListener implementation="com.google.idea.blaze.base.run.BlazeCommandRunConfigurationUpdater"/>
+    <SyncPlugin implementation="com.google.idea.blaze.base.lang.buildfile.sync.BuildLangSyncPlugin"/>
+    <BlazeWizardOptionProvider implementation="com.google.idea.blaze.base.wizard2.BazelWizardOptionProvider"/>
+    <BuildFlagsProvider implementation="com.google.idea.blaze.base.command.BuildFlagsProviderImpl"/>
+    <VcsHandler implementation="com.google.idea.blaze.base.vcs.git.GitBlazeVcsHandler"/>
+    <VcsHandler implementation="com.google.idea.blaze.base.vcs.FallbackBlazeVcsHandler" order="last" id="fallback"/>
+    <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"/>
+  </extensions>
+
+</idea-plugin>
diff --git a/base/src/com/google/idea/blaze/base/actions/BlazeAction.java b/base/src/com/google/idea/blaze/base/actions/BlazeAction.java
new file mode 100644
index 0000000..ed31396
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/actions/BlazeAction.java
@@ -0,0 +1,59 @@
+/*
+ * 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.actions;
+
+import com.google.idea.blaze.base.settings.Blaze;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.project.Project;
+import javax.swing.Icon;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/** Base class action that hides for non-blaze projects. */
+public abstract class BlazeAction extends AnAction {
+  protected BlazeAction() {}
+
+  protected BlazeAction(Icon icon) {
+    super(icon);
+  }
+
+  protected BlazeAction(@Nullable String text) {
+    super(text);
+  }
+
+  protected BlazeAction(@Nullable String text, @Nullable String description, @Nullable Icon icon) {
+    super(text, description, icon);
+  }
+
+  @Override
+  public final void update(AnActionEvent e) {
+    if (!isBlazeProject(e)) {
+      e.getPresentation().setEnabledAndVisible(false);
+      return;
+    }
+
+    e.getPresentation().setEnabledAndVisible(true);
+    doUpdate(e);
+  }
+
+  protected void doUpdate(@NotNull AnActionEvent e) {}
+
+  private static boolean isBlazeProject(@NotNull AnActionEvent e) {
+    Project project = e.getProject();
+    return project != null && Blaze.isBlazeProject(project);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/actions/BlazeCompileFileAction.java b/base/src/com/google/idea/blaze/base/actions/BlazeCompileFileAction.java
new file mode 100644
index 0000000..7a94e64
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/actions/BlazeCompileFileAction.java
@@ -0,0 +1,140 @@
+/*
+ * 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.actions;
+
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.async.executor.BlazeExecutor;
+import com.google.idea.blaze.base.experiments.ExperimentScope;
+import com.google.idea.blaze.base.filecache.FileCaches;
+import com.google.idea.blaze.base.metrics.Action;
+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.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;
+import com.google.idea.blaze.base.scope.scopes.IdeaLogScope;
+import com.google.idea.blaze.base.scope.scopes.IssuesScope;
+import com.google.idea.blaze.base.scope.scopes.LoggedTimingScope;
+import com.google.idea.blaze.base.scope.scopes.NotificationScope;
+import com.google.idea.blaze.base.scope.scopes.TimingScope;
+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.util.SaveUtil;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.CommonDataKeys;
+import com.intellij.openapi.actionSystem.Presentation;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import java.io.File;
+import java.util.List;
+import org.jetbrains.annotations.NotNull;
+
+class BlazeCompileFileAction extends BlazeAction {
+  private static final Logger LOG = Logger.getInstance(BlazeCompileFileAction.class);
+
+  public BlazeCompileFileAction() {
+    super("Compile file");
+  }
+
+  @Override
+  protected void doUpdate(@NotNull AnActionEvent e) {
+    // IntelliJ uses different logic for 1 vs many module selection. When many modules are selected
+    // modules with more than 1 content root are ignored
+    // (ProjectViewImpl#moduleBySingleContentRoot).
+    if (getTargets(e).isEmpty()) {
+      Presentation presentation = e.getPresentation();
+      presentation.setEnabled(false);
+    }
+  }
+
+  @Override
+  public void actionPerformed(AnActionEvent e) {
+    Project project = e.getProject();
+    if (project != null) {
+      ImmutableCollection<Label> targets = getTargets(e);
+      buildSourceFile(project, targets);
+    }
+  }
+
+  private ImmutableCollection<Label> getTargets(AnActionEvent e) {
+    Project project = e.getProject();
+    VirtualFile virtualFile = e.getData(CommonDataKeys.VIRTUAL_FILE);
+    if (project != null && virtualFile != null) {
+      return SourceToRuleMap.getInstance(project)
+          .getTargetsForSourceFile(new File(virtualFile.getPath()));
+    }
+    return ImmutableList.of();
+  }
+
+  private static void buildSourceFile(
+      @NotNull Project project, @NotNull ImmutableCollection<Label> targets) {
+    BlazeProjectData blazeProjectData =
+        BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
+    if (blazeProjectData == null || targets.isEmpty()) {
+      return;
+    }
+    final ProjectViewSet projectViewSet =
+        ProjectViewManager.getInstance(project).getProjectViewSet();
+    if (projectViewSet == null) {
+      return;
+    }
+    BlazeExecutor.submitTask(
+        project,
+        new ScopedTask() {
+          @Override
+          public void execute(@NotNull BlazeContext context) {
+            context
+                .push(new ExperimentScope())
+                .push(new BlazeConsoleScope.Builder(project).build())
+                .push(new IssuesScope(project))
+                .push(new IdeaLogScope())
+                .push(new TimingScope("Make"))
+                .push(new LoggedTimingScope(project, Action.MAKE_MODULE_TOTAL_TIME))
+                .push(
+                    new NotificationScope(
+                        project,
+                        "Make",
+                        "Make module",
+                        "Make module completed successfully",
+                        "Make module failed"));
+
+            WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProject(project);
+
+            BlazeIdeInterface blazeIdeInterface = BlazeIdeInterface.getInstance();
+            List<TargetExpression> targetExpressions = Lists.newArrayList(targets);
+
+            SaveUtil.saveAllFiles();
+            BuildResult buildResult =
+                blazeIdeInterface.resolveIdeArtifacts(
+                    project, context, workspaceRoot, projectViewSet, targetExpressions);
+            FileCaches.refresh(project);
+
+            if (buildResult != BuildResult.SUCCESS) {
+              context.setHasError();
+            }
+          }
+        });
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/actions/BlazeMakeProjectAction.java b/base/src/com/google/idea/blaze/base/actions/BlazeMakeProjectAction.java
new file mode 100644
index 0000000..64964c5
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/actions/BlazeMakeProjectAction.java
@@ -0,0 +1,116 @@
+/*
+ * 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.actions;
+
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.async.executor.BlazeExecutor;
+import com.google.idea.blaze.base.experiments.ExperimentScope;
+import com.google.idea.blaze.base.filecache.FileCaches;
+import com.google.idea.blaze.base.metrics.Action;
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.model.primitives.TargetExpression;
+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.projectview.section.sections.TargetSection;
+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;
+import com.google.idea.blaze.base.scope.scopes.IdeaLogScope;
+import com.google.idea.blaze.base.scope.scopes.IssuesScope;
+import com.google.idea.blaze.base.scope.scopes.LoggedTimingScope;
+import com.google.idea.blaze.base.scope.scopes.NotificationScope;
+import com.google.idea.blaze.base.scope.scopes.TimingScope;
+import com.google.idea.blaze.base.settings.Blaze;
+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.util.SaveUtil;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.project.Project;
+import java.util.List;
+import org.jetbrains.annotations.NotNull;
+
+class BlazeMakeProjectAction extends BlazeAction {
+
+  public BlazeMakeProjectAction() {
+    super("Make Project");
+  }
+
+  @Override
+  public final void actionPerformed(AnActionEvent e) {
+    Project project = e.getProject();
+    if (project != null && Blaze.isBlazeProject(project)) {
+      buildBlazeProject(project);
+    }
+  }
+
+  protected void buildBlazeProject(@NotNull final Project project) {
+
+    BlazeExecutor.submitTask(
+        project,
+        new ScopedTask() {
+          @Override
+          public void execute(@NotNull BlazeContext context) {
+            context
+                .push(new ExperimentScope())
+                .push(new BlazeConsoleScope.Builder(project).build())
+                .push(new IssuesScope(project))
+                .push(new IdeaLogScope())
+                .push(new TimingScope("Make"))
+                .push(new LoggedTimingScope(project, Action.MAKE_PROJECT_TOTAL_TIME))
+                .push(
+                    new NotificationScope(
+                        project,
+                        "Make",
+                        "Make project",
+                        "Make project completed successfully",
+                        "Make project failed"));
+
+            BlazeProjectData blazeProjectData =
+                BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
+            if (blazeProjectData == null) {
+              return;
+            }
+            SaveUtil.saveAllFiles();
+
+            ProjectViewSet projectViewSet =
+                ProjectViewManager.getInstance(project)
+                    .reloadProjectView(context, blazeProjectData.workspacePathResolver);
+            if (projectViewSet == null) {
+              return;
+            }
+
+            WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProject(project);
+
+            List<TargetExpression> targets = Lists.newArrayList();
+            targets.addAll(projectViewSet.listItems(TargetSection.KEY));
+
+            BlazeIdeInterface blazeIdeInterface = BlazeIdeInterface.getInstance();
+
+            BuildResult buildResult =
+                blazeIdeInterface.resolveIdeArtifacts(
+                    project, context, workspaceRoot, projectViewSet, targets);
+            FileCaches.refresh(project);
+
+            if (buildResult != BuildResult.SUCCESS) {
+              context.setHasError();
+              ;
+            }
+          }
+        });
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/actions/BlazeMenuGroup.java b/base/src/com/google/idea/blaze/base/actions/BlazeMenuGroup.java
new file mode 100644
index 0000000..dc10b88
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/actions/BlazeMenuGroup.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.base.actions;
+
+import com.google.idea.blaze.base.settings.Blaze;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.DefaultActionGroup;
+import com.intellij.openapi.project.Project;
+import org.jetbrains.annotations.NotNull;
+
+/** The "Blaze" menu group, only visible in blaze mode */
+public class BlazeMenuGroup extends DefaultActionGroup {
+  @Override
+  public final void update(AnActionEvent e) {
+    if (!isBlazeProject(e)) {
+      e.getPresentation().setEnabledAndVisible(false);
+      return;
+    }
+
+    e.getPresentation().setEnabledAndVisible(true);
+    e.getPresentation().setText(Blaze.buildSystemName(e.getProject()));
+  }
+
+  @Override
+  public boolean isDumbAware() {
+    return true;
+  }
+
+  private static boolean isBlazeProject(@NotNull AnActionEvent e) {
+    Project project = e.getProject();
+    return project != null && Blaze.isBlazeProject(project);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/actions/BlazeToggleAction.java b/base/src/com/google/idea/blaze/base/actions/BlazeToggleAction.java
new file mode 100644
index 0000000..0413761
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/actions/BlazeToggleAction.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.base.actions;
+
+import com.google.idea.blaze.base.settings.Blaze;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.ToggleAction;
+import com.intellij.openapi.project.Project;
+import javax.swing.Icon;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/** Base class toggle action that hides for non-blaze projects. */
+public abstract class BlazeToggleAction extends ToggleAction {
+  protected BlazeToggleAction() {}
+
+  protected BlazeToggleAction(@Nullable String text) {
+    super(text);
+  }
+
+  protected BlazeToggleAction(
+      @Nullable String text, @Nullable String description, @Nullable Icon icon) {
+    super(text, description, icon);
+  }
+
+  @Override
+  public final void update(AnActionEvent e) {
+    if (!isBlazeProject(e)) {
+      e.getPresentation().setEnabledAndVisible(false);
+      return;
+    }
+
+    e.getPresentation().setEnabledAndVisible(true);
+    super.update(e);
+    doUpdate(e);
+  }
+
+  protected void doUpdate(@NotNull AnActionEvent e) {}
+
+  private static boolean isBlazeProject(@NotNull AnActionEvent e) {
+    Project project = e.getProject();
+    return project != null && Blaze.isBlazeProject(project);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/async/AsyncUtil.java b/base/src/com/google/idea/blaze/base/async/AsyncUtil.java
new file mode 100644
index 0000000..153f995
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/async/AsyncUtil.java
@@ -0,0 +1,58 @@
+/*
+ * 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.async;
+
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.util.ui.UIUtil;
+import org.jetbrains.annotations.NotNull;
+
+/** Async utilities. */
+public class AsyncUtil {
+  public static void executeProjectChangeAction(@NotNull final Runnable task) throws Throwable {
+    final ValueHolder<Throwable> error = new ValueHolder<Throwable>();
+
+    executeOnEdt(
+        new Runnable() {
+          @Override
+          public void run() {
+            ApplicationManager.getApplication()
+                .runWriteAction(
+                    new Runnable() {
+                      @Override
+                      public void run() {
+                        try {
+                          task.run();
+                        } catch (Throwable t) {
+                          error.value = t;
+                        }
+                      }
+                    });
+          }
+        });
+
+    if (error.value != null) {
+      throw error.value;
+    }
+  }
+
+  private static void executeOnEdt(@NotNull Runnable task) {
+    if (ApplicationManager.getApplication().isDispatchThread()) {
+      task.run();
+    } else {
+      UIUtil.invokeAndWaitIfNeeded(task);
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/async/FutureUtil.java b/base/src/com/google/idea/blaze/base/async/FutureUtil.java
new file mode 100644
index 0000000..07f3e4e
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/async/FutureUtil.java
@@ -0,0 +1,112 @@
+/*
+ * 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.async;
+
+import com.google.common.util.concurrent.ListenableFuture;
+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;
+import com.google.idea.blaze.base.scope.output.StatusOutput;
+import com.google.idea.blaze.base.scope.scopes.TimingScope;
+import com.intellij.openapi.diagnostic.Logger;
+import java.util.concurrent.ExecutionException;
+
+/** Utilities operating on futures. */
+public class FutureUtil {
+  /** The result of the future operation */
+  public static class FutureResult<T> {
+    private final T result;
+    private final boolean success;
+
+    FutureResult(T result) {
+      this.result = result;
+      this.success = true;
+    }
+
+    FutureResult() {
+      this.result = null;
+      this.success = false;
+    }
+
+    public T result() {
+      return result;
+    }
+
+    public boolean success() {
+      return success;
+    }
+  }
+
+  /** Builder for the future */
+  public static class Builder<T> {
+    private static final Logger LOG = Logger.getInstance(FutureUtil.class);
+    private final BlazeContext context;
+    private final ListenableFuture<T> future;
+    private String timingCategory;
+    private String errorMessage;
+    private String progressMessage;
+
+    Builder(BlazeContext context, ListenableFuture<T> future) {
+      this.context = context;
+      this.future = future;
+    }
+
+    public Builder<T> timed(String timingCategory) {
+      this.timingCategory = timingCategory;
+      return this;
+    }
+
+    public Builder<T> withProgressMessage(String message) {
+      this.progressMessage = message;
+      return this;
+    }
+
+    public Builder<T> onError(String errorMessage) {
+      this.errorMessage = errorMessage;
+      return this;
+    }
+
+    public FutureResult<T> run() {
+      return Scope.push(
+          context,
+          (childContext) -> {
+            if (timingCategory != null) {
+              childContext.push(new TimingScope(timingCategory));
+            }
+            if (progressMessage != null) {
+              childContext.output(new StatusOutput(progressMessage));
+            }
+            try {
+              return new FutureResult<>(future.get());
+            } catch (InterruptedException e) {
+              Thread.currentThread().interrupt();
+              context.setCancelled();
+            } catch (ExecutionException e) {
+              LOG.error(e);
+              if (errorMessage != null) {
+                IssueOutput.error(errorMessage).submit(childContext);
+              }
+              context.setHasError();
+            }
+            return new FutureResult<>();
+          });
+    }
+  }
+
+  public static <T> Builder<T> waitForFuture(BlazeContext context, ListenableFuture<T> future) {
+    return new Builder<>(context, future);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/async/ValueHolder.java b/base/src/com/google/idea/blaze/base/async/ValueHolder.java
new file mode 100644
index 0000000..6b73217
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/async/ValueHolder.java
@@ -0,0 +1,21 @@
+/*
+ * 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.async;
+
+/** Simple wrapper to work around Java's final limitation. */
+public class ValueHolder<T> {
+  public T value;
+}
diff --git a/base/src/com/google/idea/blaze/base/async/executor/BlazeExecutor.java b/base/src/com/google/idea/blaze/base/async/executor/BlazeExecutor.java
new file mode 100644
index 0000000..f9eed17
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/async/executor/BlazeExecutor.java
@@ -0,0 +1,124 @@
+/*
+ * 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.async.executor;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.progress.PerformInBackgroundOption;
+import com.intellij.openapi.progress.ProgressManager;
+import com.intellij.openapi.progress.Progressive;
+import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator;
+import com.intellij.openapi.progress.util.AbstractProgressIndicatorExBase;
+import com.intellij.openapi.progress.util.ProgressWindow;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Computable;
+import com.intellij.util.ui.UIUtil;
+import java.util.concurrent.Callable;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/** Shared thread pool for blaze tasks. */
+public abstract class BlazeExecutor {
+  /** The type of modality used to launch tasks */
+  public enum Modality {
+    MODAL, // This task must start in the foreground and stay there.
+    BACKGROUNDABLE, // This task will start in the foreground, but can be sent to the background.
+    ALWAYS_BACKGROUND // This task will start in the background and stay there.
+  }
+
+  @NotNull
+  public static BlazeExecutor getInstance() {
+    return ServiceManager.getService(BlazeExecutor.class);
+  }
+
+  public abstract <T> ListenableFuture<T> submit(Callable<T> callable);
+
+  public abstract ListeningExecutorService getExecutor();
+
+  public static ListenableFuture<Void> submitTask(
+      @Nullable final Project project, @NotNull final Progressive progressive) {
+    return submitTask(project, "", progressive);
+  }
+
+  public static ListenableFuture<Void> submitTask(
+      @Nullable final Project project,
+      @NotNull final String title,
+      @NotNull final Progressive progressive) {
+    return submitTask(
+        project, title, true /* cancelable */, Modality.ALWAYS_BACKGROUND, progressive);
+  }
+
+  public static ListenableFuture<Void> submitTask(
+      @Nullable final Project project,
+      @NotNull final String title,
+      final boolean cancelable,
+      final Modality modality,
+      @NotNull final Progressive progressive) {
+
+    // The progress indicator must be created on the UI thread.
+    final ProgressWindow indicator =
+        UIUtil.invokeAndWaitIfNeeded(
+            new Computable<ProgressWindow>() {
+              @Override
+              public ProgressWindow compute() {
+                if (modality == Modality.MODAL) {
+                  ProgressWindow indicator = new ProgressWindow(cancelable, project);
+                  indicator.setTitle(title);
+                  return indicator;
+                } else {
+                  PerformInBackgroundOption backgroundOption =
+                      modality == Modality.BACKGROUNDABLE
+                          ? PerformInBackgroundOption.DEAF
+                          : PerformInBackgroundOption.ALWAYS_BACKGROUND;
+                  return new BackgroundableProcessIndicator(
+                      project, title, backgroundOption, "Cancel", "Cancel", cancelable);
+                }
+              }
+            });
+
+    indicator.setIndeterminate(true);
+    indicator.start();
+    final Runnable process =
+        new Runnable() {
+          @Override
+          public void run() {
+            progressive.run(indicator);
+          }
+        };
+    final ListenableFuture<Void> future =
+        getInstance()
+            .submit(
+                new Callable<Void>() {
+                  @Override
+                  public Void call() throws Exception {
+                    ProgressManager.getInstance().runProcess(process, indicator);
+                    return null;
+                  }
+                });
+    if (cancelable) {
+      indicator.addStateDelegate(
+          new AbstractProgressIndicatorExBase() {
+            @Override
+            public void cancel() {
+              super.cancel();
+              future.cancel(true);
+            }
+          });
+    }
+    return future;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/async/executor/BlazeExecutorImpl.java b/base/src/com/google/idea/blaze/base/async/executor/BlazeExecutorImpl.java
new file mode 100644
index 0000000..92b0223
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/async/executor/BlazeExecutorImpl.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.async.executor;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Executors;
+
+/** Executes blaze tasks on the an executor. */
+public class BlazeExecutorImpl extends BlazeExecutor {
+
+  private final ListeningExecutorService executorService =
+      MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(16));
+
+  @Override
+  public <T> ListenableFuture<T> submit(Callable<T> callable) {
+    return executorService.submit(callable);
+  }
+
+  @Override
+  public ListeningExecutorService getExecutor() {
+    return executorService;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/async/executor/TransientExecutor.java b/base/src/com/google/idea/blaze/base/async/executor/TransientExecutor.java
new file mode 100644
index 0000000..027b325
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/async/executor/TransientExecutor.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.async.executor;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/** An executor that grows to a finite number of threads and times them out quickly. */
+public class TransientExecutor extends ThreadPoolExecutor {
+  public TransientExecutor(int maxThreads) {
+    super(maxThreads, maxThreads, 200, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
+    allowCoreThreadTimeOut(true);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/async/process/ExternalTask.java b/base/src/com/google/idea/blaze/base/async/process/ExternalTask.java
new file mode 100644
index 0000000..86b7d50
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/async/process/ExternalTask.java
@@ -0,0 +1,276 @@
+/*
+ * 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.async.process;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
+import com.google.common.io.ByteStreams;
+import com.google.idea.blaze.base.command.BlazeCommand;
+import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.scope.BlazeScope;
+import com.google.idea.blaze.base.scope.Scope;
+import com.google.idea.blaze.base.scope.output.IssueOutput;
+import com.google.idea.blaze.base.scope.output.PrintOutput;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.progress.ProcessCanceledException;
+import com.intellij.util.SystemProperties;
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
+import javax.annotation.Nullable;
+
+/** Invokes an external process */
+public class ExternalTask {
+  private static final Logger LOG = Logger.getInstance(ExternalTask.class);
+
+  static final OutputStream NULL_STREAM = ByteStreams.nullOutputStream();
+
+  /** Builder for an external task */
+  public static class Builder {
+    private final ImmutableList.Builder<String> command = ImmutableList.builder();
+    private final File workingDirectory;
+    private final Map<String, String> environmentVariables = Maps.newHashMap();
+    @Nullable private BlazeContext context;
+    @Nullable private OutputStream stdout;
+    @Nullable private OutputStream stderr;
+    boolean redirectErrorStream = false;
+
+    private Builder(WorkspaceRoot workspaceRoot) {
+      this(workspaceRoot.directory());
+    }
+
+    private Builder(File workingDirectory) {
+      this.workingDirectory = workingDirectory;
+    }
+
+    public Builder arg(String arg) {
+      command.add(arg);
+      return this;
+    }
+
+    public Builder args(String... args) {
+      command.add(args);
+      return this;
+    }
+
+    public Builder args(Collection<String> args) {
+      command.addAll(args);
+      return this;
+    }
+
+    public Builder args(Stream<String> args) {
+      command.addAll(args.iterator());
+      return this;
+    }
+
+    public Builder addBlazeCommand(BlazeCommand command) {
+      this.command.addAll(command.toList());
+      return this;
+    }
+
+    public Builder maybeArg(boolean b, String arg) {
+      if (b) {
+        command.add(arg);
+      }
+      return this;
+    }
+
+    public Builder context(@Nullable BlazeContext context) {
+      this.context = context;
+      return this;
+    }
+
+    public Builder redirectStderr(boolean redirectStderr) {
+      this.redirectErrorStream = redirectStderr;
+      return this;
+    }
+
+    public Builder stdout(@Nullable OutputStream stdout) {
+      this.stdout = stdout;
+      return this;
+    }
+
+    public Builder stderr(@Nullable OutputStream stderr) {
+      this.stderr = stderr;
+      return this;
+    }
+
+    public Builder environmentVar(String key, String value) {
+      environmentVariables.put(key, value);
+      return this;
+    }
+
+    public Builder environmentVars(Map<String, String> values) {
+      environmentVariables.putAll(values);
+      return this;
+    }
+
+    public ExternalTask build() {
+      return new ExternalTask(
+          context,
+          workingDirectory,
+          command.build(),
+          environmentVariables,
+          stdout,
+          stderr,
+          redirectErrorStream);
+    }
+  }
+
+  private final File workingDirectory;
+
+  private final List<String> command;
+
+  private final Map<String, String> environmentVariables;
+
+  @Nullable private final BlazeContext parentContext;
+
+  private final boolean redirectErrorStream;
+
+  private final OutputStream stdout;
+
+  private final OutputStream stderr;
+
+  private ExternalTask(
+      @Nullable BlazeContext context,
+      File workingDirectory,
+      List<String> command,
+      Map<String, String> environmentVariables,
+      @Nullable OutputStream stdout,
+      @Nullable OutputStream stderr,
+      boolean redirectErrorStream) {
+    this.workingDirectory = workingDirectory;
+    this.command = command;
+    this.environmentVariables = environmentVariables;
+    this.parentContext = context;
+    this.redirectErrorStream = redirectErrorStream;
+    this.stdout = stdout != null ? stdout : NULL_STREAM;
+    this.stderr = stderr != null ? stderr : NULL_STREAM;
+  }
+
+  public int run(BlazeScope... scopes) {
+    Integer returnValue =
+        Scope.push(
+            parentContext,
+            context -> {
+              for (BlazeScope scope : scopes) {
+                context.push(scope);
+              }
+              try {
+                return invokeCommand(context);
+              } catch (ProcessCanceledException e) {
+                // Logging a ProcessCanceledException is an IJ error - mark context canceled instead
+                context.setCancelled();
+              }
+              return -1;
+            });
+    return returnValue != null ? returnValue : -1;
+  }
+
+  private static void closeQuietly(OutputStream stream) {
+    try {
+      stream.close();
+    } catch (IOException e) {
+      Throwables.propagate(e);
+    }
+  }
+
+  private int invokeCommand(BlazeContext context) {
+    String executingTasksText =
+        "Command: "
+            + Joiner.on(" ").join(command)
+            + SystemProperties.getLineSeparator()
+            + SystemProperties.getLineSeparator();
+
+    context.output(PrintOutput.log(executingTasksText));
+
+    try {
+      if (context.isEnding()) {
+        return -1;
+      }
+      ProcessBuilder builder =
+          new ProcessBuilder()
+              .command(command)
+              .redirectErrorStream(redirectErrorStream)
+              .directory(workingDirectory);
+      for (Map.Entry<String, String> entry : environmentVariables.entrySet()) {
+        builder.environment().put(entry.getKey(), entry.getValue());
+      }
+
+      try {
+        final Process process = builder.start();
+        Thread shutdownHook = new Thread(process::destroy);
+        try {
+          Runtime.getRuntime().addShutdownHook(shutdownHook);
+          Thread stdoutThread = ProcessUtil.forwardAsync(process.getInputStream(), stdout);
+          Thread stderrThread = null;
+          if (!redirectErrorStream) {
+            stderrThread = ProcessUtil.forwardAsync(process.getErrorStream(), stderr);
+          }
+          process.waitFor();
+          stdoutThread.join();
+          if (!redirectErrorStream) {
+            stderrThread.join();
+          }
+          int exitValue = process.exitValue();
+          if (exitValue != 0) {
+            context.setHasError();
+          }
+          return exitValue;
+        } catch (InterruptedException e) {
+          process.destroy();
+          throw new ProcessCanceledException();
+        } finally {
+          try {
+            Runtime.getRuntime().removeShutdownHook(shutdownHook);
+          } catch (IllegalStateException e) {
+            // we can't remove a shutdown hook if we are shutting down, do nothing about it
+          }
+        }
+      } catch (IOException e) {
+        LOG.warn(e);
+        IssueOutput.error(e.getMessage()).submit(context);
+      }
+    } finally {
+      closeQuietly(stdout);
+      closeQuietly(stderr);
+    }
+    return -1;
+  }
+
+  public static Builder builder() {
+    return new Builder(new File("/"));
+  }
+
+  public static Builder builder(List<String> command) {
+    return builder().args(command);
+  }
+
+  public static Builder builder(File workingDirectory) {
+    return new Builder(workingDirectory);
+  }
+
+  public static Builder builder(WorkspaceRoot workspaceRoot) {
+    return new Builder(workspaceRoot);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/async/process/LineProcessingOutputStream.java b/base/src/com/google/idea/blaze/base/async/process/LineProcessingOutputStream.java
new file mode 100644
index 0000000..8aa44a8
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/async/process/LineProcessingOutputStream.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.base.async.process;
+
+import com.google.common.collect.Lists;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.List;
+import org.jetbrains.annotations.NotNull;
+
+/** An base output stream which marshals output into newline-delimited segments for processing. */
+public final class LineProcessingOutputStream extends OutputStream {
+
+  /** A line processor */
+  public interface LineProcessor {
+    /**
+     * Process a single, complete line of output.
+     *
+     * @return Whether line processing should continue
+     */
+    boolean processLine(@NotNull String line);
+  }
+
+  @NotNull private final StringBuffer stringBuffer = new StringBuffer();
+  private volatile boolean closed;
+  @NotNull private final List<LineProcessor> lineProcessors;
+
+  LineProcessingOutputStream(@NotNull LineProcessor... lineProcessors) {
+    this.lineProcessors = Lists.newArrayList(lineProcessors);
+  }
+
+  public static LineProcessingOutputStream of(@NotNull LineProcessor... lineProcessors) {
+    return new LineProcessingOutputStream(lineProcessors);
+  }
+
+  @Override
+  public synchronized void write(byte[] b, int off, int len) {
+    if (!closed) {
+      String text = new String(b, off, len);
+      stringBuffer.append(text);
+
+      while (true) {
+        int lineBreakIndex = -1;
+        int lineBreakLength = 0;
+        for (int i = 0; i < stringBuffer.length(); ++i) {
+          char c = stringBuffer.charAt(i);
+          if (c == '\r' || c == '\n') {
+            lineBreakIndex = i;
+            lineBreakLength = 1;
+            if (c == '\r'
+                && (i + 1) < stringBuffer.length()
+                && stringBuffer.charAt(i + 1) == '\n') {
+              ++lineBreakLength;
+            }
+            break;
+          }
+        }
+
+        if (lineBreakIndex == -1) {
+          return;
+        }
+
+        String line = stringBuffer.substring(0, lineBreakIndex);
+
+        stringBuffer.delete(0, lineBreakIndex + lineBreakLength);
+
+        for (LineProcessor lineProcessor : lineProcessors) {
+          if (!lineProcessor.processLine(line)) {
+            break;
+          }
+        }
+      }
+    }
+  }
+
+  @Override
+  public void write(int b) throws IOException {
+    write(new byte[] {(byte) b}, 0, 1);
+  }
+
+  @Override
+  public void close() throws IOException {
+    closed = true;
+    super.close();
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/async/process/PrintOutputLineProcessor.java b/base/src/com/google/idea/blaze/base/async/process/PrintOutputLineProcessor.java
new file mode 100644
index 0000000..30b3dd2
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/async/process/PrintOutputLineProcessor.java
@@ -0,0 +1,35 @@
+/*
+ * 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.async.process;
+
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.scope.output.PrintOutput;
+import org.jetbrains.annotations.NotNull;
+
+/** Simple adapter between stdout and context print output. */
+public class PrintOutputLineProcessor implements LineProcessingOutputStream.LineProcessor {
+  private final BlazeContext context;
+
+  public PrintOutputLineProcessor(BlazeContext context) {
+    this.context = context;
+  }
+
+  @Override
+  public boolean processLine(@NotNull String line) {
+    context.output(PrintOutput.output(line));
+    return true;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/async/process/ProcessUtil.java b/base/src/com/google/idea/blaze/base/async/process/ProcessUtil.java
new file mode 100644
index 0000000..01b02b7
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/async/process/ProcessUtil.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.async.process;
+
+import com.intellij.openapi.diagnostic.Logger;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+class ProcessUtil {
+  private static final Logger LOG = Logger.getInstance(ProcessUtil.class);
+
+  public static Thread forwardAsync(final InputStream input, final OutputStream output) {
+    Thread thread =
+        new Thread(
+            new Runnable() {
+              @Override
+              public void run() {
+                int bufferSize = 4096;
+                byte[] buffer = new byte[bufferSize];
+
+                int read = 0;
+                try {
+                  read = input.read(buffer);
+                  while (read != -1) {
+                    output.write(buffer, 0, read);
+                    read = input.read(buffer);
+                  }
+                } catch (IOException e) {
+                  LOG.warn("Error redirecting output", e);
+                }
+              }
+            });
+    thread.start();
+    return thread;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/bazel/BazelBuildSystemProvider.java b/base/src/com/google/idea/blaze/base/bazel/BazelBuildSystemProvider.java
new file mode 100644
index 0000000..a3cee43
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/bazel/BazelBuildSystemProvider.java
@@ -0,0 +1,49 @@
+/*
+ * 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.bazel;
+
+import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.base.lang.buildfile.language.semantics.RuleDefinition;
+import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
+import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
+import javax.annotation.Nullable;
+
+/** Provides the bazel build system name string. */
+public class BazelBuildSystemProvider implements BuildSystemProvider {
+  @Override
+  public BuildSystem buildSystem() {
+    return BuildSystem.Bazel;
+  }
+
+  @Override
+  public WorkspaceRootProvider getWorkspaceRootProvider() {
+    return BazelWorkspaceRootProvider.INSTANCE;
+  }
+
+  @Override
+  public ImmutableList<String> buildArtifactDirectories(WorkspaceRoot root) {
+    String rootDir = root.directory().getName();
+    return ImmutableList.of(
+        "bazel-bin", "bazel-genfiles", "bazel-out", "bazel-testlogs", "bazel-" + rootDir);
+  }
+
+  @Nullable
+  @Override
+  public String getRuleDocumentationUrl(RuleDefinition rule) {
+    // TODO: URL pointing to specific BUILD rule.
+    return "http://www.bazel.io/docs/be/overview.html";
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/bazel/BazelWorkspaceRootProvider.java b/base/src/com/google/idea/blaze/base/bazel/BazelWorkspaceRootProvider.java
new file mode 100644
index 0000000..5167721
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/bazel/BazelWorkspaceRootProvider.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.bazel;
+
+import com.google.idea.blaze.base.io.FileAttributeProvider;
+import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
+import java.io.File;
+import javax.annotation.Nullable;
+
+/** Implementation of WorkspaceHelper. */
+public class BazelWorkspaceRootProvider implements WorkspaceRootProvider {
+
+  public static final BazelWorkspaceRootProvider INSTANCE = new BazelWorkspaceRootProvider();
+
+  private BazelWorkspaceRootProvider() {}
+
+  /** Checks for the existence of a WORKSPACE file in the given directory. */
+  @Override
+  public boolean isWorkspaceRoot(File file) {
+    return FileAttributeProvider.getInstance().isFile(new File(file, "WORKSPACE"));
+  }
+
+  @Nullable
+  @Override
+  public WorkspaceRoot findWorkspaceRoot(File file) {
+    while (file != null) {
+      if (isWorkspaceRoot(file)) {
+        return new WorkspaceRoot(file);
+      }
+      file = file.getParentFile();
+    }
+    return null;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/bazel/BuildSystemProvider.java b/base/src/com/google/idea/blaze/base/bazel/BuildSystemProvider.java
new file mode 100644
index 0000000..42bd43b
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/bazel/BuildSystemProvider.java
@@ -0,0 +1,82 @@
+/*
+ * 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.bazel;
+
+import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.base.lang.buildfile.language.semantics.RuleDefinition;
+import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
+import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.extensions.ExtensionPointName;
+import javax.annotation.Nullable;
+
+/**
+ * Extension points specify the build systems supported by this plugin.<br>
+ * The order of the extension points establishes a priority (highest priority first), for situations
+ * where we don't have an existing project to use for context (e.g. the 'import project' action).
+ */
+public interface BuildSystemProvider {
+
+  ExtensionPointName<BuildSystemProvider> EP_NAME =
+      ExtensionPointName.create("com.google.idea.blaze.BuildSystemProvider");
+
+  static BuildSystemProvider defaultBuildSystem() {
+    return EP_NAME.getExtensions()[0];
+  }
+
+  @Nullable
+  static BuildSystemProvider getBuildSystemProvider(BuildSystem buildSystem) {
+    for (BuildSystemProvider provider : EP_NAME.getExtensions()) {
+      if (provider.buildSystem() == buildSystem) {
+        return provider;
+      }
+    }
+    return null;
+  }
+
+  static boolean isBuildSystemAvailable(BuildSystem buildSystem) {
+    return getBuildSystemProvider(buildSystem) != null;
+  }
+
+  static WorkspaceRootProvider getWorkspaceRootProvider(BuildSystem buildSystem) {
+    BuildSystemProvider provider = getBuildSystemProvider(buildSystem);
+    if (provider == null) {
+      throw new RuntimeException(
+          String.format("Build system '%s' not supported by this plugin", buildSystem));
+    }
+    return provider.getWorkspaceRootProvider();
+  }
+
+  static BuildSystemProvider getInstance() {
+    return ServiceManager.getService(BuildSystemProvider.class);
+  }
+
+  /**
+   * Returns the default build system for this application. This should only be called in situations
+   * where it doesn't make sense to use the current project.<br>
+   * Otherwise, use {@link com.google.idea.blaze.base.settings.Blaze#getBuildSystem}
+   */
+  BuildSystem buildSystem();
+
+  WorkspaceRootProvider getWorkspaceRootProvider();
+
+  /** Directories containing artifacts produced during the build process. */
+  ImmutableList<String> buildArtifactDirectories(WorkspaceRoot root);
+
+  /** The URL providing the built-in BUILD rule's documentation, if one can be found. */
+  @Nullable
+  String getRuleDocumentationUrl(RuleDefinition rule);
+}
diff --git a/base/src/com/google/idea/blaze/base/bazel/WorkspaceRootProvider.java b/base/src/com/google/idea/blaze/base/bazel/WorkspaceRootProvider.java
new file mode 100644
index 0000000..d0604a1
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/bazel/WorkspaceRootProvider.java
@@ -0,0 +1,42 @@
+/*
+ * 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.bazel;
+
+import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
+import java.io.File;
+import javax.annotation.Nullable;
+
+/** Utility methods for working with workspace paths. */
+public interface WorkspaceRootProvider {
+
+  /** Checks whether the given directory is a valid workspace root. */
+  boolean isWorkspaceRoot(File file);
+
+  /**
+   * Checks whether the file or one of its ancestors is a valid workspace root.<br>
+   * This should only be called when no Project is available. Otherwise, use
+   * WorkspaceRoot.isInWorkspace.
+   */
+  default boolean isInWorkspace(File file) {
+    return findWorkspaceRoot(file) != null;
+  }
+
+  /**
+   * If the given file is inside a workspace, returns the workspace root. Otherwise returns null.
+   */
+  @Nullable
+  WorkspaceRoot findWorkspaceRoot(File file);
+}
diff --git a/base/src/com/google/idea/blaze/base/buildmap/FileToBuildMap.java b/base/src/com/google/idea/blaze/base/buildmap/FileToBuildMap.java
new file mode 100644
index 0000000..55020f8
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/buildmap/FileToBuildMap.java
@@ -0,0 +1,58 @@
+/*
+ * 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.buildmap;
+
+import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
+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.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.project.Project;
+import java.io.File;
+import java.util.Collection;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/** Map of file -> BUILD files. */
+public class FileToBuildMap {
+  private final Project project;
+
+  public static FileToBuildMap getInstance(Project project) {
+    return ServiceManager.getService(project, FileToBuildMap.class);
+  }
+
+  public FileToBuildMap(Project project) {
+    this.project = project;
+  }
+
+  public Collection<File> getBuildFilesForFile(File file) {
+    BlazeProjectData blazeProjectData =
+        BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
+    if (blazeProjectData == null) {
+      return ImmutableList.of();
+    }
+    return SourceToRuleMap.getInstance(project)
+        .getTargetsForSourceFile(file)
+        .stream()
+        .map(blazeProjectData.ruleMap::get)
+        .filter(Objects::nonNull)
+        .map((ruleIdeInfo) -> ruleIdeInfo.buildFile)
+        .filter(Objects::nonNull)
+        .map(ArtifactLocation::getFile)
+        .collect(Collectors.toList());
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/buildmap/OpenCorrespondingBuildFile.java b/base/src/com/google/idea/blaze/base/buildmap/OpenCorrespondingBuildFile.java
new file mode 100644
index 0000000..2754da2
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/buildmap/OpenCorrespondingBuildFile.java
@@ -0,0 +1,75 @@
+/*
+ * 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.buildmap;
+
+import com.google.common.collect.Iterables;
+import com.google.idea.blaze.base.actions.BlazeAction;
+import com.google.idea.blaze.base.metrics.Action;
+import com.google.idea.blaze.base.metrics.LoggingService;
+import com.intellij.ide.actions.OpenFileAction;
+import com.intellij.openapi.actionSystem.ActionPlaces;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.CommonDataKeys;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.Presentation;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import java.io.File;
+import java.util.Collection;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+class OpenCorrespondingBuildFile extends BlazeAction {
+  @Override
+  public void actionPerformed(AnActionEvent e) {
+    Project project = e.getProject();
+    if (project == null) {
+      return;
+    }
+    VirtualFile virtualFile = e.getData(CommonDataKeys.VIRTUAL_FILE);
+    File file = getBuildFile(project, virtualFile);
+    if (file == null) {
+      return;
+    }
+    OpenFileAction.openFile(file.getPath(), project);
+    LoggingService.reportEvent(project, Action.OPEN_CORRESPONDING_BUILD_FILE);
+  }
+
+  @Nullable
+  private File getBuildFile(@Nullable Project project, @Nullable VirtualFile virtualFile) {
+    if (project == null) {
+      return null;
+    }
+    if (virtualFile == null) {
+      return null;
+    }
+    File file = new File(virtualFile.getPath());
+    Collection<File> fileInfoList = FileToBuildMap.getInstance(project).getBuildFilesForFile(file);
+    return Iterables.getFirst(fileInfoList, null);
+  }
+
+  @Override
+  protected void doUpdate(@NotNull AnActionEvent e) {
+    Presentation presentation = e.getPresentation();
+    DataContext dataContext = e.getDataContext();
+    VirtualFile virtualFile = CommonDataKeys.VIRTUAL_FILE.getData(dataContext);
+    Project project = CommonDataKeys.PROJECT.getData(dataContext);
+    boolean visible = (project != null && virtualFile != null);
+    boolean enabled = getBuildFile(project, virtualFile) != null;
+    presentation.setVisible(visible || ActionPlaces.isMainMenuOrActionSearch(e.getPlace()));
+    presentation.setEnabled(enabled);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/buildmodifier/BazelBuildifierBinaryProvider.java b/base/src/com/google/idea/blaze/base/buildmodifier/BazelBuildifierBinaryProvider.java
new file mode 100644
index 0000000..25079b2
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/buildmodifier/BazelBuildifierBinaryProvider.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.buildmodifier;
+
+import com.google.idea.blaze.base.util.BlazeHelperBinaryUtil;
+import java.io.File;
+import javax.annotation.Nullable;
+
+/** Provides the bazel buildifier binary. */
+public class BazelBuildifierBinaryProvider implements BuildifierBinaryProvider {
+
+  private static final String BASE = "/";
+  private static final String BUILDIFIER_BINARY_PATH =
+      BASE + "base/resources/binaries/bazel-buildifier";
+
+  @Nullable
+  @Override
+  public File getBuildifierBinary() {
+    return BlazeHelperBinaryUtil.getBlazeHelperBinary(BUILDIFIER_BINARY_PATH);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/buildmodifier/BuildFileFormatter.java b/base/src/com/google/idea/blaze/base/buildmodifier/BuildFileFormatter.java
new file mode 100644
index 0000000..5875466
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/buildmodifier/BuildFileFormatter.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.buildmodifier;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import javax.annotation.Nullable;
+
+/** Formats BUILD files using 'buildifier' */
+public class BuildFileFormatter {
+
+  @Nullable
+  private static File getBuildifierBinary() {
+    for (BuildifierBinaryProvider provider : BuildifierBinaryProvider.EP_NAME.getExtensions()) {
+      File file = provider.getBuildifierBinary();
+      if (file != null) {
+        return file;
+      }
+    }
+    return null;
+  }
+
+  static String formatText(String text) {
+    try {
+      File buildifierBinary = getBuildifierBinary();
+      if (buildifierBinary == null) {
+        return null;
+      }
+      File file = createTempFile(text);
+      formatFile(file, buildifierBinary.getPath());
+      String formattedFile = readFile(file);
+      file.delete();
+      return formattedFile;
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+    return text;
+  }
+
+  private static void formatFile(File file, String buildifierBinaryPath) throws IOException {
+    ProcessBuilder builder = new ProcessBuilder(buildifierBinaryPath, file.getAbsolutePath());
+    try {
+      builder.start().waitFor();
+    } catch (InterruptedException e) {
+      throw new IOException("buildifier execution failed", e);
+    }
+  }
+
+  private static String readFile(File file) throws IOException {
+    try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
+      StringBuilder formattedFile = new StringBuilder();
+      char[] buf = new char[1024];
+      int numRead;
+      while ((numRead = reader.read(buf)) >= 0) {
+        formattedFile.append(buf, 0, numRead);
+      }
+      return formattedFile.toString();
+    }
+  }
+
+  private static File createTempFile(String text) throws IOException {
+    File file = File.createTempFile("ijwb", ".tmp");
+    try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
+      writer.write(text);
+    }
+    return file;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/buildmodifier/BuildFileModifier.java b/base/src/com/google/idea/blaze/base/buildmodifier/BuildFileModifier.java
new file mode 100644
index 0000000..ff20220
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/buildmodifier/BuildFileModifier.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.base.buildmodifier;
+
+import com.google.idea.blaze.base.model.primitives.Kind;
+import com.google.idea.blaze.base.model.primitives.Label;
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.project.Project;
+
+/** Abstraction to modify build files, eg. using buildifier */
+public interface BuildFileModifier {
+
+  static BuildFileModifier getInstance() {
+    return ServiceManager.getService(BuildFileModifier.class);
+  }
+
+  /**
+   * Add a new rule to a build file. The rule name and rule kind must be validated before this
+   * method, but no guarantees are made about actually being able to add this rule to the build
+   * file. An example of why it might fail is the build file might already have a rule with the
+   * requested name.
+   *
+   * @param context Blaze context to operate in
+   * @param newRule new rule to create
+   * @param ruleKind valid kind of rule (android_library, java_library, etc.)
+   * @return true if rule is added to file, false otherwise
+   */
+  boolean addRule(
+      Project project, final BlazeContext context, final Label newRule, final Kind ruleKind);
+}
diff --git a/base/src/com/google/idea/blaze/base/buildmodifier/BuildifierBinaryProvider.java b/base/src/com/google/idea/blaze/base/buildmodifier/BuildifierBinaryProvider.java
new file mode 100644
index 0000000..cb37261
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/buildmodifier/BuildifierBinaryProvider.java
@@ -0,0 +1,30 @@
+/*
+ * 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.buildmodifier;
+
+import com.intellij.openapi.extensions.ExtensionPointName;
+import java.io.File;
+import javax.annotation.Nullable;
+
+/** Provides a buildifier binary. */
+public interface BuildifierBinaryProvider {
+
+  ExtensionPointName<BuildifierBinaryProvider> EP_NAME =
+      ExtensionPointName.create("com.google.idea.blaze.BuildifierBinaryProvider");
+
+  @Nullable
+  File getBuildifierBinary();
+}
diff --git a/base/src/com/google/idea/blaze/base/buildmodifier/FileSaveHandler.java b/base/src/com/google/idea/blaze/base/buildmodifier/FileSaveHandler.java
new file mode 100644
index 0000000..02aa3f2
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/buildmodifier/FileSaveHandler.java
@@ -0,0 +1,66 @@
+/*
+ * 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.buildmodifier;
+
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.DocumentRunnable;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.fileEditor.FileDocumentManagerAdapter;
+import com.intellij.openapi.vfs.VirtualFile;
+
+/** Runs the buildifier command on file save. */
+public class FileSaveHandler extends FileDocumentManagerAdapter {
+
+  @Override
+  public void beforeDocumentSaving(final Document document) {
+    if (!document.isWritable()) {
+      return;
+    }
+    FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance();
+    VirtualFile file = fileDocumentManager.getFile(document);
+    if (file == null || !file.isValid()) {
+      return;
+    }
+
+    if (!isBuildFile(file)) {
+      return;
+    }
+    int lines = document.getLineCount();
+    if (lines > 0) {
+      String text = document.getText();
+      String formattedText = BuildFileFormatter.formatText(text);
+      updateDocument(document, formattedText);
+    }
+  }
+
+  private void updateDocument(final Document document, final String formattedContent) {
+    ApplicationManager.getApplication()
+        .runWriteAction(
+            new DocumentRunnable(document, null) {
+              @Override
+              public void run() {
+                CommandProcessor.getInstance()
+                    .runUndoTransparentAction(() -> document.setText(formattedContent));
+              }
+            });
+  }
+
+  private static boolean isBuildFile(VirtualFile file) {
+    return file.getName().equals("BUILD");
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/buildmodifier/FileSystemModifier.java b/base/src/com/google/idea/blaze/base/buildmodifier/FileSystemModifier.java
new file mode 100644
index 0000000..f3586a2
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/buildmodifier/FileSystemModifier.java
@@ -0,0 +1,37 @@
+/*
+ * 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.buildmodifier;
+
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.project.Project;
+import java.io.File;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/** Modifies the file system. Interface so we can mock it in tests */
+public abstract class FileSystemModifier {
+
+  public static FileSystemModifier getInstance(@NotNull Project project) {
+    return ServiceManager.getService(project, FileSystemModifier.class);
+  }
+
+  @Nullable
+  public abstract File makeWorkspacePathDirs(@NotNull WorkspacePath workspacePath);
+
+  @Nullable
+  public abstract File createFile(@NotNull WorkspacePath parentDir, @NotNull String name);
+}
diff --git a/base/src/com/google/idea/blaze/base/buildmodifier/FileSystemModifierImpl.java b/base/src/com/google/idea/blaze/base/buildmodifier/FileSystemModifierImpl.java
new file mode 100644
index 0000000..0d398f9
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/buildmodifier/FileSystemModifierImpl.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.base.buildmodifier;
+
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
+import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
+import com.intellij.openapi.project.Project;
+import java.io.File;
+import java.io.IOException;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+class FileSystemModifierImpl extends FileSystemModifier {
+
+  private final Project project;
+
+  public FileSystemModifierImpl(@NotNull Project project) {
+    this.project = project;
+  }
+
+  @Nullable
+  @Override
+  public File makeWorkspacePathDirs(@NotNull WorkspacePath workspacePath) {
+    WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProject(project);
+    File dir = workspaceRoot.fileForPath(workspacePath);
+    boolean success = dir.mkdirs();
+    return success ? dir : null;
+  }
+
+  @Nullable
+  @Override
+  public File createFile(@NotNull WorkspacePath parentDirectory, @NotNull String name) {
+    WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProject(project);
+    File dir = workspaceRoot.fileForPath(parentDirectory);
+    File f = new File(dir, name);
+    boolean success;
+    try {
+      success = f.createNewFile();
+    } catch (IOException e) {
+      success = false;
+    }
+    return success ? f : null;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/command/BlazeCommand.java b/base/src/com/google/idea/blaze/base/command/BlazeCommand.java
new file mode 100644
index 0000000..a337472
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/command/BlazeCommand.java
@@ -0,0 +1,147 @@
+/*
+ * 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.command;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.base.model.primitives.TargetExpression;
+import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
+import com.google.idea.blaze.base.settings.BlazeUserSettings;
+import java.util.Arrays;
+import java.util.List;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+/** A command to issue to Blaze/Bazel on the command line. */
+@Immutable
+public final class BlazeCommand {
+
+  private final BuildSystem buildSystem;
+  private final BlazeCommandName name;
+  @Nullable private final String blazeBinary;
+  private final ImmutableList<String> arguments;
+
+  private BlazeCommand(
+      BuildSystem buildSystem,
+      BlazeCommandName name,
+      @Nullable String blazeBinary,
+      ImmutableList<String> arguments) {
+    this.buildSystem = buildSystem;
+    this.name = name;
+    this.blazeBinary = blazeBinary;
+    this.arguments = arguments;
+  }
+
+  public BlazeCommandName getName() {
+    return name;
+  }
+
+  public ImmutableList<String> toList() {
+    String blazeBinary = this.blazeBinary;
+    if (blazeBinary == null) {
+      blazeBinary = getBinaryPath(buildSystem);
+    }
+
+    ImmutableList.Builder<String> commandLine = ImmutableList.builder();
+    commandLine.add(blazeBinary, name.toString());
+    commandLine.addAll(arguments);
+    return commandLine.build();
+  }
+
+  private static String getBinaryPath(BuildSystem buildSystem) {
+    BlazeUserSettings settings = BlazeUserSettings.getInstance();
+    switch (buildSystem) {
+      case Blaze:
+        return settings.getBlazeBinaryPath();
+      case Bazel:
+        return settings.getBazelBinaryPath();
+      default:
+        throw new RuntimeException("Unrecognized build system type: " + buildSystem);
+    }
+  }
+
+  @Override
+  public String toString() {
+    return Joiner.on(' ').join(toList());
+  }
+
+  public static Builder builder(BuildSystem buildSystem, BlazeCommandName name) {
+    return new Builder(buildSystem, name);
+  }
+
+  /** Builder for a blaze command */
+  public static class Builder {
+    private final BuildSystem buildSystem;
+    private final BlazeCommandName name;
+    @Nullable private String blazeBinary;
+    private final ImmutableList.Builder<TargetExpression> targets = ImmutableList.builder();
+    private final ImmutableList.Builder<String> blazeFlags = ImmutableList.builder();
+    private final ImmutableList.Builder<String> exeFlags = ImmutableList.builder();
+
+    public Builder(BuildSystem buildSystem, BlazeCommandName name) {
+      this.buildSystem = buildSystem;
+      this.name = name;
+      // Tell forge what tool we used to call blaze so we can track usage.
+      addBlazeFlags(BlazeFlags.getToolTagFlag());
+    }
+
+    public BlazeCommand build() {
+      ImmutableList.Builder<String> arguments = ImmutableList.builder();
+      arguments.addAll(blazeFlags.build());
+      arguments.add("--");
+
+      // Trust the user's ordering of the targets since order matters to blaze
+      for (TargetExpression targetExpression : targets.build()) {
+        arguments.add(targetExpression.toString());
+      }
+
+      arguments.addAll(exeFlags.build());
+      return new BlazeCommand(buildSystem, name, blazeBinary, arguments.build());
+    }
+
+    public Builder setBlazeBinary(@Nullable String blazeBinary) {
+      this.blazeBinary = blazeBinary;
+      return this;
+    }
+
+    public Builder addTargets(TargetExpression... targets) {
+      return this.addTargets(Arrays.asList(targets));
+    }
+
+    public Builder addTargets(List<? extends TargetExpression> targets) {
+      this.targets.addAll(targets);
+      return this;
+    }
+
+    public Builder addExeFlags(String... flags) {
+      return addExeFlags(Arrays.asList(flags));
+    }
+
+    public Builder addExeFlags(List<String> flags) {
+      this.exeFlags.addAll(flags);
+      return this;
+    }
+
+    public Builder addBlazeFlags(String... flags) {
+      return addBlazeFlags(Arrays.asList(flags));
+    }
+
+    public Builder addBlazeFlags(List<String> flags) {
+      this.blazeFlags.addAll(flags);
+      return this;
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/command/BlazeCommandName.java b/base/src/com/google/idea/blaze/base/command/BlazeCommandName.java
new file mode 100644
index 0000000..f753198
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/command/BlazeCommandName.java
@@ -0,0 +1,83 @@
+/*
+ * 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.command;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
+import java.util.Collection;
+import java.util.concurrent.ConcurrentMap;
+import javax.annotation.concurrent.Immutable;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * A class for Blaze/Bazel command names. We enumerate the commands we use (and that we expect users
+ * to be interested in), but do NOT use an enum because we want to allow users to specify arbitrary
+ * commands.
+ */
+@Immutable
+public final class BlazeCommandName {
+  @NotNull
+  private static final ConcurrentMap<String, BlazeCommandName> knownCommands =
+      Maps.newConcurrentMap();
+
+  public static final BlazeCommandName BUILD = fromString("build");
+  public static final BlazeCommandName TEST = fromString("test");
+  public static final BlazeCommandName MOBILE_INSTALL = fromString("mobile-install");
+  public static final BlazeCommandName RUN = fromString("run");
+  public static final BlazeCommandName QUERY = fromString("query");
+  public static final BlazeCommandName INFO = fromString("info");
+
+  public static BlazeCommandName fromString(@NotNull String name) {
+    knownCommands.putIfAbsent(name, new BlazeCommandName(name));
+    return knownCommands.get(name);
+  }
+
+  private final String name;
+
+  private BlazeCommandName(@NotNull String name) {
+    Preconditions.checkArgument(!name.isEmpty(), "Command should be non-empty.");
+    this.name = name;
+  }
+
+  @Override
+  public String toString() {
+    return name;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (!(o instanceof BlazeCommandName)) {
+      return false;
+    }
+    BlazeCommandName that = (BlazeCommandName) o;
+    return name.equals(that.name);
+  }
+
+  @Override
+  public int hashCode() {
+    return name.hashCode();
+  }
+
+  /**
+   * @return An unmodifiable view of the Blaze commands we know about (including those that the user
+   *     has specified, in addition to those we have hard-coded).
+   */
+  @NotNull
+  public static Collection<BlazeCommandName> knownCommands() {
+    return ImmutableList.copyOf(knownCommands.values());
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/command/BlazeFlags.java b/base/src/com/google/idea/blaze/base/command/BlazeFlags.java
new file mode 100644
index 0000000..f4a84d8
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/command/BlazeFlags.java
@@ -0,0 +1,138 @@
+/*
+ * 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.command;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.projectview.ProjectViewSet;
+import com.google.idea.blaze.base.projectview.section.sections.BuildFlagsSection;
+import com.google.idea.blaze.base.settings.Blaze;
+import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
+import com.intellij.openapi.project.Project;
+import com.intellij.util.PlatformUtils;
+import java.util.Collection;
+import java.util.List;
+import javax.annotation.Nullable;
+
+/** The collection of all the Bazel flag strings we use. */
+public final class BlazeFlags {
+
+  // Build the maximum number of possible dependencies of the project and to show all the build
+  // errors in single go.
+  public static final String KEEP_GOING = "--keep_going";
+  // Tells Blaze to open a debug port and wait for a connection while running tests
+  // It expands to: --test_arg=--wrapper_script_flag=--debug --test_output=streamed
+  //   --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";
+  // 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).
+  public static final String TEST_FILTER = "--test_filter";
+  // Skips checking for output file modifications (reduced statting -> faster).
+  public static final String NO_CHECK_OUTPUTS = "--noexperimental_check_output_files";
+  // Ignores implicit dependencies (e.g. java_library rules depending implicitly on
+  // "//transconsole/tools:aggregate_messages" in order to support translations).
+  public static final String NO_IMPLICIT_DEPS = "--noimplicit_deps";
+  // Ignores host dependencies.
+  public static final String NO_HOST_DEPS = "--nohost_deps";
+  // When used with mobile-install, deploys the an app incrementally.
+  public static final String INCREMENTAL = "--incremental";
+  // When used with mobile-install, deploys the an app incrementally
+  // can be used for API 23 or higher, for which it is preferred to --incremental
+  public static final String SPLIT_APKS = "--split_apks";
+  // Re-run the test even if the results are cached.
+  public static final String NO_CACHE_TEST_RESULTS = "--nocache_test_results";
+  // Avoids node GC between ide_build_info and blaze build
+  public static final String VERSION_WINDOW_FOR_DIRTY_NODE_GC =
+      "--version_window_for_dirty_node_gc=-1";
+
+  public static final String EXPERIMENTAL_SHOW_ARTIFACTS = "--experimental_show_artifacts";
+
+  public static List<String> buildFlags(Project project, ProjectViewSet projectViewSet) {
+    BuildSystem buildSystem = Blaze.getBuildSystem(project);
+    List<String> flags = Lists.newArrayList();
+    for (BuildFlagsProvider buildFlagsProvider : BuildFlagsProvider.EP_NAME.getExtensions()) {
+      buildFlagsProvider.addBuildFlags(buildSystem, projectViewSet, flags);
+    }
+    flags.addAll(projectViewSet.listItems(BuildFlagsSection.KEY));
+    return flags;
+  }
+
+  // Pass-through arg for sending adb options during mobile-install.
+  public static final String ADB_ARG = "--adb_arg=";
+
+  public static ImmutableList<String> adbSerialFlags(String serial) {
+    return ImmutableList.of(ADB_ARG + "-s ", ADB_ARG + serial);
+  }
+
+  // Pass-through arg for sending test arguments.
+  public static final String TEST_ARG = "--test_arg=";
+
+  private static final String TOOL_TAG = "--tool_tag=ijwb:";
+
+  // We add this to every single BlazeCommand instance. It's for tracking usage.
+  public static String getToolTagFlag() {
+    String platformPrefix = PlatformUtils.getPlatformPrefix();
+
+    // IDEA Community Edition is "Idea", whereas IDEA Ultimate Edition is "idea".
+    // That's dumb. Let's make them more useful.
+    if (PlatformUtils.isIdeaCommunity()) {
+      platformPrefix = "IDEA:community";
+    } else if (PlatformUtils.isIdeaUltimate()) {
+      platformPrefix = "IDEA:ultimate";
+    }
+    return TOOL_TAG + platformPrefix;
+  }
+
+  public static String testFilterFlagForClass(String className) {
+    return testFilterFlagForClassAndMethod(className, null);
+  }
+
+  public static String testFilterFlagForClassAndMethod(
+      String className, @Nullable String methodName) {
+    StringBuilder output = new StringBuilder(TEST_FILTER);
+    output.append('=');
+    output.append(className);
+
+    if (!Strings.isNullOrEmpty(methodName)) {
+      output.append('#');
+      output.append(methodName);
+    }
+
+    return output.toString();
+  }
+
+  public static String testFilterFlagForClassAndMethods(
+      String className, Collection<String> methodNames, boolean isJUnit3Class) {
+    if (methodNames.size() == 0) {
+      return testFilterFlagForClass(className);
+    } else if (methodNames.size() == 1) {
+      return testFilterFlagForClassAndMethod(className, methodNames.iterator().next());
+    }
+    String methodNamePattern;
+    if (isJUnit3Class) {
+      methodNamePattern = String.join(",", methodNames);
+    } else {
+      methodNamePattern = String.format("(%s)", String.join("|", methodNames));
+    }
+    return testFilterFlagForClassAndMethod(className, methodNamePattern);
+  }
+
+  private BlazeFlags() {}
+}
diff --git a/base/src/com/google/idea/blaze/base/command/BuildFlagsProvider.java b/base/src/com/google/idea/blaze/base/command/BuildFlagsProvider.java
new file mode 100644
index 0000000..469d663
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/command/BuildFlagsProvider.java
@@ -0,0 +1,30 @@
+/*
+ * 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.command;
+
+import com.google.idea.blaze.base.projectview.ProjectViewSet;
+import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
+import com.intellij.openapi.extensions.ExtensionPointName;
+import java.util.List;
+
+/** Provides additional build flags for bazel/blaze commands. */
+public interface BuildFlagsProvider {
+
+  ExtensionPointName<BuildFlagsProvider> EP_NAME =
+      ExtensionPointName.create("com.google.idea.blaze.BuildFlagsProvider");
+
+  void addBuildFlags(BuildSystem buildSystem, ProjectViewSet projectViewSet, List<String> flags);
+}
diff --git a/base/src/com/google/idea/blaze/base/command/BuildFlagsProviderImpl.java b/base/src/com/google/idea/blaze/base/command/BuildFlagsProviderImpl.java
new file mode 100644
index 0000000..eb4b510
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/command/BuildFlagsProviderImpl.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.base.command;
+
+import static com.google.idea.blaze.base.command.BlazeFlags.NO_CHECK_OUTPUTS;
+import static com.google.idea.blaze.base.command.BlazeFlags.VERSION_WINDOW_FOR_DIRTY_NODE_GC;
+
+import com.google.idea.blaze.base.projectview.ProjectViewSet;
+import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
+import com.google.idea.common.experiments.BoolExperiment;
+import java.util.List;
+
+/** Flags added to blaze/bazel build commands. */
+public class BuildFlagsProviderImpl implements BuildFlagsProvider {
+
+  private static final BoolExperiment EXPERIMENT_USE_VERSION_WINDOW_FOR_DIRTY_NODE_GC =
+      new BoolExperiment("ide_build_info.use_version_window_for_dirty_node_gc", false);
+  private static final BoolExperiment EXPERIMENT_NO_EXPERIMENTAL_CHECK_OUTPUT_FILES =
+      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()) {
+      flags.add(VERSION_WINDOW_FOR_DIRTY_NODE_GC);
+    }
+    if (EXPERIMENT_NO_EXPERIMENTAL_CHECK_OUTPUT_FILES.getValue()) {
+      flags.add(NO_CHECK_OUTPUTS);
+    }
+    flags.add("--curses=no");
+    flags.add("--color=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
new file mode 100644
index 0000000..22c8c6a
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/command/ExperimentalShowArtifactsLineProcessor.java
@@ -0,0 +1,59 @@
+/*
+ * 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.command;
+
+import com.google.idea.blaze.base.async.process.LineProcessingOutputStream;
+import java.io.File;
+import java.util.List;
+import org.jetbrains.annotations.NotNull;
+
+/** Collects the output of --experimental_show_artifacts */
+public class ExperimentalShowArtifactsLineProcessor
+    implements LineProcessingOutputStream.LineProcessor {
+  private static final String OUTPUT_START = "Build artifacts:";
+  private static final String OUTPUT_MARKER = ">>>";
+
+  final List<File> fileList;
+  private final String fileType;
+  boolean insideBuildResult = false;
+
+  public ExperimentalShowArtifactsLineProcessor(List<File> fileList, String fileType) {
+    this.fileList = fileList;
+    this.fileType = fileType;
+  }
+
+  @Override
+  public boolean processLine(@NotNull String line) {
+    if (insideBuildResult) {
+      // Workaround for --experimental_ui: Extra newlines are inserted
+      if (line.isEmpty()) {
+        return false;
+      }
+
+      insideBuildResult = line.startsWith(OUTPUT_MARKER);
+      if (insideBuildResult) {
+        String fileName = line.substring(OUTPUT_MARKER.length());
+        if (fileName.endsWith(fileType)) {
+          fileList.add(new File(fileName));
+        }
+      }
+    }
+    if (!insideBuildResult) {
+      insideBuildResult = line.equals(OUTPUT_START);
+    }
+    return !insideBuildResult;
+  }
+}
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
new file mode 100644
index 0000000..89530b7
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/command/info/BlazeInfo.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.base.command.info;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.util.concurrent.ListenableFuture;
+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.BuildSystem;
+import com.intellij.openapi.components.ServiceManager;
+import java.util.List;
+import javax.annotation.Nullable;
+
+/** Runs the blaze info command. The results may be cached in the workspace. */
+public abstract class BlazeInfo {
+  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 String blazeBinKey(BuildSystem buildSystem) {
+    switch (buildSystem) {
+      case Blaze:
+        return "blaze-bin";
+      case Bazel:
+        return "bazel-bin";
+      default:
+        throw new IllegalArgumentException("Unrecognized build system: " + buildSystem);
+    }
+  }
+
+  public static String blazeGenfilesKey(BuildSystem buildSystem) {
+    switch (buildSystem) {
+      case Blaze:
+        return "blaze-genfiles";
+      case Bazel:
+        return "bazel-genfiles";
+      default:
+        throw new IllegalArgumentException("Unrecognized build system: " + buildSystem);
+    }
+  }
+
+  public static BlazeInfo getInstance() {
+    return ServiceManager.getService(BlazeInfo.class);
+  }
+
+  /**
+   * @param blazeFlags The blaze flags that will be passed to Blaze.
+   * @param key The key passed to blaze info
+   * @return The blaze info value associated with the specified key
+   */
+  public abstract ListenableFuture<String> runBlazeInfo(
+      @Nullable BlazeContext context,
+      BuildSystem buildSystem,
+      WorkspaceRoot workspaceRoot,
+      List<String> blazeFlags,
+      String key);
+
+  /**
+   * @param blazeFlags The blaze flags that will be passed to Blaze.
+   * @param key The key passed to blaze info
+   * @return The blaze info value associated with the specified key
+   */
+  public abstract ListenableFuture<byte[]> runBlazeInfoGetBytes(
+      @Nullable BlazeContext context,
+      BuildSystem buildSystem,
+      WorkspaceRoot workspaceRoot,
+      List<String> blazeFlags,
+      String key);
+
+  /**
+   * This calls blaze info without any specific key so blaze info will return all keys and values
+   * that it has. There could be a performance cost for doing this, so the user should verify that
+   * calling this method is actually faster than calling {@link #runBlazeInfo(WorkspaceRoot, List,
+   * String)}.
+   *
+   * @param blazeFlags The blaze flags that will be passed to Blaze.
+   * @return The blaze info data fields.
+   */
+  public abstract ListenableFuture<ImmutableMap<String, String>> runBlazeInfo(
+      @Nullable BlazeContext context,
+      BuildSystem buildSystem,
+      WorkspaceRoot workspaceRoot,
+      List<String> blazeFlags);
+}
diff --git a/base/src/com/google/idea/blaze/base/command/info/BlazeInfoException.java b/base/src/com/google/idea/blaze/base/command/info/BlazeInfoException.java
new file mode 100644
index 0000000..39294c3
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/command/info/BlazeInfoException.java
@@ -0,0 +1,49 @@
+/*
+ * 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.command.info;
+
+import javax.annotation.concurrent.Immutable;
+
+/** Exception occuring during blaze infoy */
+@Immutable
+public final class BlazeInfoException extends Exception {
+  private final int exitCode;
+  private final String stdout;
+  private final String stderr;
+
+  public BlazeInfoException(int exitCode, String stdout, String stderr) {
+    this.exitCode = exitCode;
+    this.stdout = stdout;
+    this.stderr = stderr;
+  }
+
+  @Override
+  public String getMessage() {
+    return "blaze info failed with exit code: " + exitCode + " and error stream: " + stderr;
+  }
+
+  public int getExitCode() {
+    return exitCode;
+  }
+
+  public String getStdout() {
+    return stdout;
+  }
+
+  public String getStderr() {
+    return stderr;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/command/info/BlazeInfoImpl.java b/base/src/com/google/idea/blaze/base/command/info/BlazeInfoImpl.java
new file mode 100644
index 0000000..cf82fdd
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/command/info/BlazeInfoImpl.java
@@ -0,0 +1,120 @@
+/*
+ * 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.command.info;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.idea.blaze.base.async.executor.BlazeExecutor;
+import com.google.idea.blaze.base.async.process.ExternalTask;
+import com.google.idea.blaze.base.command.BlazeCommand;
+import com.google.idea.blaze.base.command.BlazeCommandName;
+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.BuildSystem;
+import com.intellij.openapi.diagnostic.Logger;
+import java.io.ByteArrayOutputStream;
+import java.util.List;
+import javax.annotation.Nullable;
+
+class BlazeInfoImpl extends BlazeInfo {
+  private static final Logger LOG = Logger.getInstance(BlazeInfoImpl.class);
+
+  @Override
+  public ListenableFuture<String> runBlazeInfo(
+      @Nullable BlazeContext context,
+      BuildSystem buildSystem,
+      WorkspaceRoot workspaceRoot,
+      List<String> blazeFlags,
+      String key) {
+    return BlazeExecutor.getInstance()
+        .submit(
+            () ->
+                runBlazeInfo(buildSystem, workspaceRoot, key, blazeFlags, context)
+                    .toString()
+                    .trim());
+  }
+
+  @Override
+  public ListenableFuture<byte[]> runBlazeInfoGetBytes(
+      @Nullable BlazeContext context,
+      BuildSystem buildSystem,
+      WorkspaceRoot workspaceRoot,
+      List<String> blazeFlags,
+      String key) {
+    return BlazeExecutor.getInstance()
+        .submit(
+            () -> runBlazeInfo(buildSystem, workspaceRoot, key, blazeFlags, context).toByteArray());
+  }
+
+  @Override
+  public ListenableFuture<ImmutableMap<String, String>> runBlazeInfo(
+      @Nullable BlazeContext context,
+      BuildSystem buildSystem,
+      WorkspaceRoot workspaceRoot,
+      List<String> blazeFlags) {
+    return BlazeExecutor.getInstance()
+        .submit(
+            () -> {
+              String blazeInfoString =
+                  runBlazeInfo(buildSystem, workspaceRoot, null /* key */, blazeFlags, context)
+                      .toString()
+                      .trim();
+              return parseBlazeInfoResult(blazeInfoString);
+            });
+  }
+
+  private static ByteArrayOutputStream runBlazeInfo(
+      BuildSystem buildSystem,
+      WorkspaceRoot workspaceRoot,
+      @Nullable String key,
+      List<String> blazeFlags,
+      @Nullable BlazeContext context)
+      throws BlazeInfoException {
+    BlazeCommand.Builder builder = BlazeCommand.builder(buildSystem, BlazeCommandName.INFO);
+    if (key != null) {
+      builder.addBlazeFlags(key);
+    }
+    BlazeCommand command = builder.addBlazeFlags(blazeFlags).build();
+    ByteArrayOutputStream stdout = new ByteArrayOutputStream();
+    ByteArrayOutputStream stderr = new ByteArrayOutputStream();
+    int exitCode =
+        ExternalTask.builder(workspaceRoot)
+            .addBlazeCommand(command)
+            .context(context)
+            .stdout(stdout)
+            .stderr(stderr)
+            .build()
+            .run();
+    if (exitCode != 0) {
+      throw new BlazeInfoException(exitCode, stdout.toString(), stderr.toString());
+    }
+    return stdout;
+  }
+
+  private static ImmutableMap<String, String> parseBlazeInfoResult(String blazeInfoString) {
+    ImmutableMap.Builder<String, String> blazeInfoMapBuilder = ImmutableMap.builder();
+    String[] blazeInfoLines = blazeInfoString.split("\n");
+    for (String blazeInfoLine : blazeInfoLines) {
+      // Just split on the first ":".
+      String[] keyValue = blazeInfoLine.split(":", 2);
+      LOG.assertTrue(keyValue.length == 2, blazeInfoLine);
+      String key = keyValue[0].trim();
+      String value = keyValue[1].trim();
+      blazeInfoMapBuilder.put(key, value);
+    }
+    return blazeInfoMapBuilder.build();
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/console/BlazeConsoleService.java b/base/src/com/google/idea/blaze/base/console/BlazeConsoleService.java
new file mode 100644
index 0000000..2bd7d57
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/console/BlazeConsoleService.java
@@ -0,0 +1,37 @@
+/*
+ * 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.console;
+
+import com.intellij.execution.ui.ConsoleViewContentType;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.project.Project;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/** Prints text to the blaze console. */
+public interface BlazeConsoleService {
+  static BlazeConsoleService getInstance(@NotNull Project project) {
+    return ServiceManager.getService(project, BlazeConsoleService.class);
+  }
+
+  void print(@NotNull String text, @NotNull ConsoleViewContentType contentType);
+
+  void clear();
+
+  void setStopHandler(@Nullable Runnable runnable);
+
+  void activateConsoleWindow();
+}
diff --git a/base/src/com/google/idea/blaze/base/console/BlazeConsoleServiceImpl.java b/base/src/com/google/idea/blaze/base/console/BlazeConsoleServiceImpl.java
new file mode 100644
index 0000000..ba5e3a6
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/console/BlazeConsoleServiceImpl.java
@@ -0,0 +1,58 @@
+/*
+ * 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.console;
+
+import com.intellij.execution.ui.ConsoleViewContentType;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.wm.ToolWindow;
+import com.intellij.openapi.wm.ToolWindowManager;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/** Implementation for BlazeConsoleService */
+public class BlazeConsoleServiceImpl implements BlazeConsoleService {
+  @NotNull private final Project project;
+  @NotNull private final BlazeConsoleView blazeConsoleView;
+
+  BlazeConsoleServiceImpl(@NotNull Project project) {
+    this.project = project;
+    blazeConsoleView = BlazeConsoleView.getInstance(project);
+  }
+
+  @Override
+  public void print(@NotNull String text, @NotNull ConsoleViewContentType contentType) {
+    blazeConsoleView.print(text, contentType);
+  }
+
+  @Override
+  public void clear() {
+    blazeConsoleView.clear();
+  }
+
+  @Override
+  public void setStopHandler(@Nullable Runnable runnable) {
+    blazeConsoleView.setStopHandler(runnable);
+  }
+
+  @Override
+  public void activateConsoleWindow() {
+    ToolWindow toolWindow =
+        ToolWindowManager.getInstance(project).getToolWindow(BlazeConsoleToolWindowFactory.ID);
+    if (toolWindow != null) {
+      toolWindow.activate(null);
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/console/BlazeConsoleToolWindowFactory.java b/base/src/com/google/idea/blaze/base/console/BlazeConsoleToolWindowFactory.java
new file mode 100644
index 0000000..76c1966
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/console/BlazeConsoleToolWindowFactory.java
@@ -0,0 +1,37 @@
+/*
+ * 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.console;
+
+import com.google.idea.blaze.base.settings.Blaze;
+import com.intellij.openapi.project.DumbAware;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.wm.ToolWindow;
+import com.intellij.openapi.wm.ToolWindowFactory;
+import org.jetbrains.annotations.NotNull;
+
+/** Factory for console window. */
+public class BlazeConsoleToolWindowFactory implements DumbAware, ToolWindowFactory {
+
+  public static final String ID = "Blaze Console";
+
+  @Override
+  public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) {
+    String title = Blaze.buildSystemName(project) + " Console";
+    toolWindow.setTitle(title);
+    toolWindow.setStripeTitle(title);
+    BlazeConsoleView.getInstance(project).createToolWindowContent(toolWindow);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/console/BlazeConsoleView.java b/base/src/com/google/idea/blaze/base/console/BlazeConsoleView.java
new file mode 100644
index 0000000..f507a52
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/console/BlazeConsoleView.java
@@ -0,0 +1,155 @@
+/*
+ * 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.console;
+
+import com.intellij.codeEditor.printing.PrintAction;
+import com.intellij.execution.impl.ConsoleViewImpl;
+import com.intellij.execution.ui.ConsoleViewContentType;
+import com.intellij.execution.ui.RunnerLayoutUi;
+import com.intellij.execution.ui.layout.PlaceInGrid;
+import com.intellij.icons.AllIcons;
+import com.intellij.ide.IdeBundle;
+import com.intellij.ide.actions.NextOccurenceToolbarAction;
+import com.intellij.ide.actions.PreviousOccurenceToolbarAction;
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.actionSystem.ActionPlaces;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.DefaultActionGroup;
+import com.intellij.openapi.actionSystem.Presentation;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.project.DumbAwareAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Disposer;
+import com.intellij.openapi.wm.ToolWindow;
+import com.intellij.ui.content.Content;
+import com.intellij.ui.content.ContentFactory;
+import java.awt.BorderLayout;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+class BlazeConsoleView implements Disposable {
+
+  private static final Class<?>[] IGNORED_CONSOLE_ACTION_TYPES = {
+    PreviousOccurenceToolbarAction.class,
+    NextOccurenceToolbarAction.class,
+    ConsoleViewImpl.ClearAllAction.class,
+    PrintAction.class
+  };
+
+  @NotNull private final Project myProject;
+  @NotNull private final ConsoleViewImpl myConsoleView;
+
+  private JPanel myConsolePanel;
+  private volatile Runnable myStopHandler;
+
+  public BlazeConsoleView(@NotNull Project project) {
+    myProject = project;
+    myConsoleView = new ConsoleViewImpl(myProject, false);
+    Disposer.register(this, myConsoleView);
+    setupUI();
+  }
+
+  public static BlazeConsoleView getInstance(@NotNull Project project) {
+    return ServiceManager.getService(project, BlazeConsoleView.class);
+  }
+
+  public void setStopHandler(@Nullable Runnable stopHandler) {
+    myStopHandler = stopHandler;
+  }
+
+  private static boolean shouldIgnoreAction(@NotNull AnAction action) {
+    for (Class<?> actionType : IGNORED_CONSOLE_ACTION_TYPES) {
+      if (actionType.isInstance(action)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  public void createToolWindowContent(@NotNull ToolWindow toolWindow) {
+    //Create runner UI layout
+    RunnerLayoutUi.Factory factory = RunnerLayoutUi.Factory.getInstance(myProject);
+    RunnerLayoutUi layoutUi = factory.create("", "", "session", myProject);
+
+    Content console =
+        layoutUi.createContent(
+            BlazeConsoleToolWindowFactory.ID, myConsoleView.getComponent(), "", null, null);
+    layoutUi.addContent(console, 0, PlaceInGrid.right, false);
+
+    // Adding actions
+    DefaultActionGroup group = new DefaultActionGroup();
+    layoutUi.getOptions().setLeftToolbar(group, ActionPlaces.UNKNOWN);
+
+    AnAction[] consoleActions = myConsoleView.createConsoleActions();
+    for (AnAction action : consoleActions) {
+      if (!shouldIgnoreAction(action)) {
+        group.add(action);
+      }
+    }
+    group.add(new StopAction());
+
+    JComponent layoutComponent = layoutUi.getComponent();
+    myConsolePanel.add(layoutComponent, BorderLayout.CENTER);
+
+    //noinspection ConstantConditions
+    Content content =
+        ContentFactory.SERVICE.getInstance().createContent(layoutComponent, null, true);
+    toolWindow.getContentManager().addContent(content);
+  }
+
+  public void clear() {
+    myConsoleView.clear();
+  }
+
+  public void print(@NotNull String text, @NotNull ConsoleViewContentType contentType) {
+    myConsoleView.print(text, contentType);
+  }
+
+  @Override
+  public void dispose() {}
+
+  private void setupUI() {
+    myConsolePanel = new JPanel();
+    myConsolePanel.setLayout(new BorderLayout(0, 0));
+  }
+
+  private class StopAction extends DumbAwareAction {
+    public StopAction() {
+      super(IdeBundle.message("action.stop"), null, AllIcons.Actions.Suspend);
+    }
+
+    @Override
+    public void actionPerformed(AnActionEvent e) {
+      Runnable handler = myStopHandler;
+      if (handler != null) {
+        handler.run();
+        myStopHandler = null;
+      }
+    }
+
+    @Override
+    public void update(AnActionEvent event) {
+      Presentation presentation = event.getPresentation();
+      boolean isNowVisible = myStopHandler != null;
+      if (presentation.isEnabled() != isNowVisible) {
+        presentation.setEnabled(isNowVisible);
+      }
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/experiments/BlazeExperimentService.java b/base/src/com/google/idea/blaze/base/experiments/BlazeExperimentService.java
new file mode 100644
index 0000000..2b9f1cc
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/experiments/BlazeExperimentService.java
@@ -0,0 +1,25 @@
+/*
+ * 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.experiments;
+
+import com.google.idea.common.experiments.ExperimentServiceImpl;
+
+final class BlazeExperimentService extends ExperimentServiceImpl {
+
+  public BlazeExperimentService() {
+    super("ijwb");
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/experiments/ExperimentScope.java b/base/src/com/google/idea/blaze/base/experiments/ExperimentScope.java
new file mode 100644
index 0000000..5415bf9
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/experiments/ExperimentScope.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.experiments;
+
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.scope.BlazeScope;
+import com.google.idea.common.experiments.ExperimentService;
+import org.jetbrains.annotations.NotNull;
+
+/** Reloads experiments at the start of the scope. */
+public class ExperimentScope implements BlazeScope {
+  @Override
+  public void onScopeBegin(@NotNull BlazeContext context) {
+    ExperimentService.getInstance().startExperimentScope();
+  }
+
+  @Override
+  public void onScopeEnd(@NotNull BlazeContext context) {
+    ExperimentService.getInstance().endExperimentScope();
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/filecache/FileCache.java b/base/src/com/google/idea/blaze/base/filecache/FileCache.java
new file mode 100644
index 0000000..3878916
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/filecache/FileCache.java
@@ -0,0 +1,43 @@
+/*
+ * 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.filecache;
+
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.projectview.ProjectViewSet;
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.sync.BlazeSyncParams;
+import com.intellij.openapi.extensions.ExtensionPointName;
+import com.intellij.openapi.project.Project;
+
+/** A cache of files from the build output. */
+public interface FileCache {
+  ExtensionPointName<FileCache> EP_NAME =
+      ExtensionPointName.create("com.google.idea.blaze.FileCache");
+
+  /** Name of cache. Used for status messages. */
+  String getName();
+
+  /** Called on sync to fully refresh the file cache. */
+  void onSync(
+      Project project,
+      BlazeContext context,
+      ProjectViewSet projectViewSet,
+      BlazeProjectData projectData,
+      BlazeSyncParams.SyncMode syncMode);
+
+  /** Called after a build operation to refresh any updated files. */
+  void refreshFiles(Project project);
+}
diff --git a/base/src/com/google/idea/blaze/base/filecache/FileCaches.java b/base/src/com/google/idea/blaze/base/filecache/FileCaches.java
new file mode 100644
index 0000000..f4c66c1
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/filecache/FileCaches.java
@@ -0,0 +1,63 @@
+/*
+ * 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.filecache;
+
+import com.google.idea.blaze.base.async.executor.BlazeExecutor;
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.projectview.ProjectViewSet;
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.scope.Scope;
+import com.google.idea.blaze.base.scope.output.StatusOutput;
+import com.google.idea.blaze.base.scope.scopes.TimingScope;
+import com.google.idea.blaze.base.sync.BlazeSyncParams;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.LocalFileSystem;
+
+/** Static helper methods to update file caches. */
+public class FileCaches {
+  /** Call on sync. Updates the file cache and deletes any old files. */
+  public static void onSync(
+      Project project,
+      BlazeContext context,
+      ProjectViewSet projectViewSet,
+      BlazeProjectData blazeProjectData,
+      BlazeSyncParams.SyncMode syncMode) {
+    for (FileCache fileCache : FileCache.EP_NAME.getExtensions()) {
+      Scope.push(
+          context,
+          childContext -> {
+            childContext.push(new TimingScope(fileCache.getName()));
+            childContext.output(new StatusOutput("Updating " + fileCache.getName() + "..."));
+            fileCache.onSync(project, context, projectViewSet, blazeProjectData, syncMode);
+          });
+    }
+    LocalFileSystem.getInstance().refresh(true);
+  }
+
+  /** Call at the end of build when you want the IDE to pick up any changes. */
+  public static void refresh(Project project) {
+    BlazeExecutor.submitTask(
+        project,
+        indicator -> {
+          indicator.setIndeterminate(true);
+          for (FileCache fileCache : FileCache.EP_NAME.getExtensions()) {
+            indicator.setText("Updating " + fileCache.getName() + "...");
+            fileCache.refreshFiles(project);
+          }
+          LocalFileSystem.getInstance().refresh(true);
+        });
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/filecache/FileDiffer.java b/base/src/com/google/idea/blaze/base/filecache/FileDiffer.java
new file mode 100644
index 0000000..a76fa8d
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/filecache/FileDiffer.java
@@ -0,0 +1,80 @@
+/*
+ * 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.filecache;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Sets;
+import com.google.idea.blaze.base.io.ModifiedTimeScanner;
+import com.intellij.openapi.diagnostic.Logger;
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.jetbrains.annotations.Nullable;
+
+/** Provides a diffing service for a collection of files. */
+public final class FileDiffer {
+  private static Logger LOG = Logger.getInstance(FileDiffer.class);
+
+  private FileDiffer() {}
+
+  @Nullable
+  public static ImmutableMap<File, Long> updateFiles(
+      @Nullable ImmutableMap<File, Long> oldState,
+      Iterable<File> files,
+      List<File> updatedFiles,
+      List<File> removedFiles) {
+    ImmutableMap<File, Long> newState = readFileState(files);
+    if (newState == null) {
+      return null;
+    }
+    diffState(oldState, newState, updatedFiles, removedFiles);
+    return newState;
+  }
+
+  @Nullable
+  public static ImmutableMap<File, Long> readFileState(Iterable<File> files) {
+    try {
+      return ModifiedTimeScanner.readTimestamps(files);
+    } catch (Exception e) {
+      LOG.error(e);
+      return null;
+    }
+  }
+
+  public static <K, V> void diffState(
+      @Nullable Map<K, V> oldState, Map<K, V> newState, List<K> updated, List<K> removed) {
+    oldState = oldState != null ? oldState : ImmutableMap.of();
+
+    // Find changed/new
+    for (Map.Entry<K, V> entry : newState.entrySet()) {
+      K key = entry.getKey();
+      V value = entry.getValue();
+      V oldValue = oldState.get(key);
+
+      final boolean isUpdated = oldValue == null || !value.equals(oldValue);
+      if (isUpdated) {
+        updated.add(key);
+      }
+    }
+
+    // Find removed
+    Set<K> removedSet = Sets.newHashSet();
+    removedSet.addAll(oldState.keySet());
+    removedSet.removeAll(newState.keySet());
+    removed.addAll(removedSet);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/help/BlazeHelpHandler.java b/base/src/com/google/idea/blaze/base/help/BlazeHelpHandler.java
new file mode 100644
index 0000000..9397a89
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/help/BlazeHelpHandler.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.help;
+
+import com.intellij.openapi.components.ServiceManager;
+
+/** Handles help requests. */
+public interface BlazeHelpHandler {
+  static BlazeHelpHandler getInstance() {
+    return ServiceManager.getService(BlazeHelpHandler.class);
+  }
+
+  void handleHelp(String urlFragment);
+}
diff --git a/base/src/com/google/idea/blaze/base/help/BlazeHelpHandlerImpl.java b/base/src/com/google/idea/blaze/base/help/BlazeHelpHandlerImpl.java
new file mode 100644
index 0000000..e9a23b6
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/help/BlazeHelpHandlerImpl.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.help;
+
+import com.intellij.ide.BrowserUtil;
+
+class BlazeHelpHandlerImpl implements BlazeHelpHandler {
+  private static final String URL_BASE = "https://ij.bazel.io/";
+
+  @Override
+  public void handleHelp(String urlFragment) {
+    BrowserUtil.browse(URL_BASE + urlFragment + ".html");
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/ide/NewBlazePackageAction.java b/base/src/com/google/idea/blaze/base/ide/NewBlazePackageAction.java
new file mode 100644
index 0000000..3780bf1
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/ide/NewBlazePackageAction.java
@@ -0,0 +1,244 @@
+/*
+ * 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.ide;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.actions.BlazeAction;
+import com.google.idea.blaze.base.buildmodifier.BuildFileModifier;
+import com.google.idea.blaze.base.buildmodifier.FileSystemModifier;
+import com.google.idea.blaze.base.metrics.Action;
+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.WorkspacePath;
+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.scope.BlazeContext;
+import com.google.idea.blaze.base.scope.Scope;
+import com.google.idea.blaze.base.scope.ScopedOperation;
+import com.google.idea.blaze.base.scope.output.PrintOutput;
+import com.google.idea.blaze.base.scope.output.StatusOutput;
+import com.google.idea.blaze.base.scope.scopes.LoggedTimingScope;
+import com.google.idea.blaze.base.settings.Blaze;
+import com.google.idea.blaze.base.sync.projectview.ImportRoots;
+import com.intellij.history.LocalHistory;
+import com.intellij.history.LocalHistoryAction;
+import com.intellij.ide.IdeView;
+import com.intellij.ide.util.DirectoryChooserUtil;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.CommonDataKeys;
+import com.intellij.openapi.actionSystem.LangDataKeys;
+import com.intellij.openapi.actionSystem.Presentation;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.DumbAware;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiManager;
+import com.intellij.util.PlatformIcons;
+import java.io.File;
+import java.util.List;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
+import org.jetbrains.annotations.NotNull;
+
+class NewBlazePackageAction extends BlazeAction implements DumbAware {
+  private static final Logger LOG = Logger.getInstance(NewBlazePackageAction.class);
+
+  private static final String BUILD_FILE_NAME = "BUILD";
+
+  public NewBlazePackageAction() {
+    super();
+  }
+
+  @Override
+  public void actionPerformed(AnActionEvent event) {
+    final IdeView view = event.getData(LangDataKeys.IDE_VIEW);
+    final Project project = event.getData(CommonDataKeys.PROJECT);
+    Scope.root(
+        new ScopedOperation() {
+          @Override
+          public void execute(@NotNull final BlazeContext context) {
+            context.push(new LoggedTimingScope(project, Action.CREATE_BLAZE_PACKAGE));
+
+            if (view == null || project == null) {
+              return;
+            }
+            PsiDirectory directory = getOrChooseDirectory(project, view);
+
+            if (directory == null) {
+              return;
+            }
+
+            NewBlazePackageDialog newBlazePackageDialog =
+                new NewBlazePackageDialog(project, directory);
+            boolean isOk = newBlazePackageDialog.showAndGet();
+            if (!isOk) {
+              return;
+            }
+
+            final Label newRule = newBlazePackageDialog.getNewRule();
+            final Kind newRuleKind = newBlazePackageDialog.getNewRuleKind();
+            // If we returned OK, we should have a non null result
+            LOG.assertTrue(newRule != null);
+            LOG.assertTrue(newRuleKind != null);
+
+            context.output(
+                new StatusOutput(
+                    String.format("Setting up a new %s package", Blaze.buildSystemName(project))));
+
+            boolean success = createPackageOnDisk(project, context, newRule, newRuleKind);
+
+            if (!success) {
+              return;
+            }
+
+            File newDirectory =
+                WorkspaceRoot.fromProject(project).fileForPath(newRule.blazePackage());
+            VirtualFile virtualFile = VfsUtil.findFileByIoFile(newDirectory, true);
+            // We just created this file, it should exist
+            LOG.assertTrue(virtualFile != null);
+            PsiFile psiFile = PsiManager.getInstance(project).findFile(virtualFile);
+            view.selectElement(psiFile);
+          }
+        });
+  }
+
+  private static Boolean createPackageOnDisk(
+      @NotNull Project project,
+      @NotNull BlazeContext context,
+      @NotNull Label newRule,
+      @NotNull Kind ruleKind) {
+    LocalHistoryAction action;
+
+    String actionName =
+        String.format(
+            "Creating %s package: %s", Blaze.buildSystemName(project), newRule.toString());
+    LocalHistory localHistory = LocalHistory.getInstance();
+    action = localHistory.startAction(actionName);
+
+    // Create the package + BUILD file + rule
+    FileSystemModifier fileSystemModifier = FileSystemModifier.getInstance(project);
+    WorkspacePath newWorkspacePath = newRule.blazePackage();
+    File newDirectory = fileSystemModifier.makeWorkspacePathDirs(newWorkspacePath);
+    if (newDirectory == null) {
+      String errorMessage =
+          "Could not create new package directory: " + newWorkspacePath.toString();
+      context.output(PrintOutput.error(errorMessage));
+      return false;
+    }
+    File buildFile = fileSystemModifier.createFile(newWorkspacePath, BUILD_FILE_NAME);
+    if (buildFile == null) {
+      String errorMessage =
+          "Could not create new BUILD file in package: " + newWorkspacePath.toString();
+      context.output(PrintOutput.error(errorMessage));
+      return false;
+    }
+    BuildFileModifier buildFileModifier = BuildFileModifier.getInstance();
+    buildFileModifier.addRule(project, context, newRule, ruleKind);
+    action.finish();
+
+    return true;
+  }
+
+  @Override
+  protected void doUpdate(@NotNull AnActionEvent event) {
+    Presentation presentation = event.getPresentation();
+    if (isEnabled(event)) {
+      String text = String.format("New %s Package", Blaze.buildSystemName(event.getProject()));
+      presentation.setEnabledAndVisible(true);
+      presentation.setText(text);
+      presentation.setDescription(text);
+      presentation.setIcon(PlatformIcons.PACKAGE_ICON);
+    } else {
+      presentation.setEnabledAndVisible(false);
+    }
+  }
+
+  private boolean isEnabled(AnActionEvent event) {
+    Project project = event.getProject();
+    IdeView view = event.getData(LangDataKeys.IDE_VIEW);
+    if (project == null || view == null) {
+      return false;
+    }
+
+    List<PsiDirectory> directories = filterDirectories(project, view.getDirectories());
+    if (directories.isEmpty()) {
+      return false;
+    }
+
+    return true;
+  }
+
+  /** Filter out directories that do not live under the project's directories. */
+  private static List<PsiDirectory> filterDirectories(Project project, PsiDirectory[] directories) {
+    if (directories.length == 0) {
+      return ImmutableList.of();
+    }
+    ProjectViewSet projectViewSet = ProjectViewManager.getInstance(project).getProjectViewSet();
+    if (projectViewSet == null) {
+      return ImmutableList.of();
+    }
+    WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProject(project);
+    ImportRoots importRoots =
+        ImportRoots.builder(workspaceRoot, Blaze.getBuildSystem(project))
+            .add(projectViewSet)
+            .build();
+    return Lists.newArrayList(directories)
+        .stream()
+        .filter(directory -> isUnderProjectViewDirectory(workspaceRoot, importRoots, directory))
+        .collect(Collectors.toList());
+  }
+
+  private static boolean isUnderProjectViewDirectory(
+      WorkspaceRoot workspaceRoot, ImportRoots importRoots, PsiDirectory directory) {
+    VirtualFile virtualFile = directory.getVirtualFile();
+    // Ignore jars, etc. and their contents, which are in an ArchiveFileSystem.
+    if (!(virtualFile.isInLocalFileSystem())) {
+      return false;
+    }
+    if (!workspaceRoot.isInWorkspace(virtualFile)) {
+      return false;
+    }
+    WorkspacePath workspacePath = workspaceRoot.workspacePathFor(virtualFile);
+    return importRoots
+        .rootDirectories()
+        .stream()
+        .anyMatch(
+            importRoot ->
+                FileUtil.isAncestor(
+                    importRoot.relativePath(), workspacePath.relativePath(), false));
+  }
+
+  @Nullable
+  private static PsiDirectory getOrChooseDirectory(Project project, IdeView view) {
+    List<PsiDirectory> dirs = filterDirectories(project, view.getDirectories());
+    if (dirs.size() == 0) {
+      return null;
+    }
+    if (dirs.size() == 1) {
+      return dirs.get(0);
+    } else {
+      return DirectoryChooserUtil.selectDirectory(
+          project, dirs.toArray(new PsiDirectory[dirs.size()]), null, "");
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/ide/NewBlazePackageDialog.java b/base/src/com/google/idea/blaze/base/ide/NewBlazePackageDialog.java
new file mode 100644
index 0000000..14e02d4
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/ide/NewBlazePackageDialog.java
@@ -0,0 +1,144 @@
+/*
+ * 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.ide;
+
+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.WorkspacePath;
+import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
+import com.google.idea.blaze.base.ui.BlazeValidationError;
+import com.google.idea.blaze.base.ui.UiUtil;
+import com.intellij.CommonBundle;
+import com.intellij.ide.IdeBundle;
+import com.intellij.ide.actions.CreateElementActionBase;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.ui.ValidationInfo;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.ui.components.JBLabel;
+import com.intellij.ui.components.JBTextField;
+import com.intellij.util.IncorrectOperationException;
+import java.awt.GridBagLayout;
+import java.io.File;
+import java.util.List;
+import javax.swing.Icon;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+class NewBlazePackageDialog extends DialogWrapper {
+  private static final Logger LOG = Logger.getInstance(NewBlazePackageDialog.class);
+
+  @NotNull private final Project project;
+  @NotNull private final PsiDirectory parentDirectory;
+
+  @Nullable private Label newRule;
+  @Nullable private Kind newRuleKind;
+
+  private static final int UI_INDENT_LEVEL = 0;
+  private static final int TEXT_FIELD_LENGTH = 40;
+  @NotNull private final JPanel component = new JPanel(new GridBagLayout());
+  @NotNull private final JBLabel packageLabel = new JBLabel("Package name:");
+  @NotNull private final JBTextField packageNameField = new JBTextField(TEXT_FIELD_LENGTH);
+  @NotNull private final NewRuleUI newRuleUI = new NewRuleUI(TEXT_FIELD_LENGTH);
+
+  public NewBlazePackageDialog(@NotNull Project project, @NotNull PsiDirectory currentDirectory) {
+    super(project);
+    this.project = project;
+    this.parentDirectory = currentDirectory;
+
+    initializeUI();
+  }
+
+  private void initializeUI() {
+    component.add(packageLabel);
+    component.add(packageNameField, UiUtil.getFillLineConstraints(UI_INDENT_LEVEL));
+    newRuleUI.fillUI(component, UI_INDENT_LEVEL);
+    UiUtil.fillBottom(component);
+    init();
+  }
+
+  @Nullable
+  @Override
+  protected JComponent createCenterPanel() {
+    return component;
+  }
+
+  @Nullable
+  @Override
+  protected ValidationInfo doValidate() {
+    String packageName = packageNameField.getText();
+    if (packageName == null) {
+      return new ValidationInfo("Internal error, package was null");
+    }
+    if (packageName.length() == 0) {
+      return new ValidationInfo(
+          IdeBundle.message("error.name.should.be.specified"), packageNameField);
+    }
+    List<BlazeValidationError> errors = Lists.newArrayList();
+    if (!Label.validatePackagePath(packageName, errors)) {
+      BlazeValidationError validationResult = errors.get(0);
+      return new ValidationInfo(validationResult.getError(), packageNameField);
+    }
+
+    return newRuleUI.validate();
+  }
+
+  @Override
+  protected void doOKAction() {
+    WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProject(project);
+    LOG.assertTrue(parentDirectory.getVirtualFile().isInLocalFileSystem());
+    File parentDirectoryFile = new File(parentDirectory.getVirtualFile().getPath());
+    String newPackageName = packageNameField.getText();
+    File newPackageDirectory = new File(parentDirectoryFile, newPackageName);
+    WorkspacePath newPackagePath = workspaceRoot.workspacePathFor(newPackageDirectory);
+
+    RuleName newRuleName = newRuleUI.getRuleName();
+    Label newRule = new Label(newPackagePath, newRuleName);
+    Kind ruleKind = newRuleUI.getSelectedRuleKind();
+    try {
+      parentDirectory.checkCreateSubdirectory(newPackageName);
+    } catch (IncorrectOperationException ex) {
+      showErrorDialog(CreateElementActionBase.filterMessage(ex.getMessage()));
+      // do not close the dialog
+      return;
+    }
+    this.newRule = newRule;
+    this.newRuleKind = ruleKind;
+    super.doOKAction();
+  }
+
+  private void showErrorDialog(@NotNull String message) {
+    String title = CommonBundle.getErrorTitle();
+    Icon icon = Messages.getErrorIcon();
+    Messages.showMessageDialog(component, message, title, icon);
+  }
+
+  @Nullable
+  public Label getNewRule() {
+    return newRule;
+  }
+
+  @Nullable
+  public Kind getNewRuleKind() {
+    return newRuleKind;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/ide/NewBlazeRuleAction.java b/base/src/com/google/idea/blaze/base/ide/NewBlazeRuleAction.java
new file mode 100644
index 0000000..a8aaee8
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/ide/NewBlazeRuleAction.java
@@ -0,0 +1,91 @@
+/*
+ * 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.ide;
+
+import com.google.idea.blaze.base.actions.BlazeAction;
+import com.google.idea.blaze.base.experiments.ExperimentScope;
+import com.google.idea.blaze.base.metrics.Action;
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.scope.Scope;
+import com.google.idea.blaze.base.scope.ScopedOperation;
+import com.google.idea.blaze.base.scope.scopes.BlazeConsoleScope;
+import com.google.idea.blaze.base.scope.scopes.IdeaLogScope;
+import com.google.idea.blaze.base.scope.scopes.IssuesScope;
+import com.google.idea.blaze.base.scope.scopes.LoggedTimingScope;
+import com.google.idea.blaze.base.settings.Blaze;
+import com.intellij.openapi.actionSystem.ActionPlaces;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.CommonDataKeys;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.Presentation;
+import com.intellij.openapi.project.DumbAware;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import javax.annotation.Nullable;
+import org.jetbrains.annotations.NotNull;
+
+class NewBlazeRuleAction extends BlazeAction implements DumbAware {
+
+  public NewBlazeRuleAction() {
+    super();
+  }
+
+  @Override
+  public void actionPerformed(AnActionEvent event) {
+    final Project project = event.getData(CommonDataKeys.PROJECT);
+    if (project == null) {
+      return;
+    }
+    final VirtualFile virtualFile = event.getData(CommonDataKeys.VIRTUAL_FILE);
+    if (virtualFile == null) {
+      return;
+    }
+
+    Scope.root(
+        new ScopedOperation() {
+          @Override
+          public void execute(@NotNull BlazeContext context) {
+            context
+                .push(new ExperimentScope())
+                .push(new BlazeConsoleScope.Builder(project).build())
+                .push(new IssuesScope(project))
+                .push(new IdeaLogScope())
+                .push(new LoggedTimingScope(project, Action.CREATE_BLAZE_RULE));
+            NewBlazeRuleDialog newBlazeRuleDialog =
+                new NewBlazeRuleDialog(context, project, virtualFile);
+            newBlazeRuleDialog.show();
+          }
+        });
+  }
+
+  @Override
+  protected void doUpdate(@NotNull AnActionEvent event) {
+    Presentation presentation = event.getPresentation();
+    DataContext dataContext = event.getDataContext();
+    VirtualFile file = CommonDataKeys.VIRTUAL_FILE.getData(dataContext);
+    Project project = CommonDataKeys.PROJECT.getData(dataContext);
+    boolean enabled = (project != null && file != null && file.getName().equals("BUILD"));
+    presentation.setVisible(enabled || ActionPlaces.isMainMenuOrActionSearch(event.getPlace()));
+    presentation.setEnabled(enabled);
+    presentation.setText(getText(project));
+  }
+
+  private static String getText(@Nullable Project project) {
+    String buildSystem = Blaze.buildSystemName(project);
+    return String.format("New %s Rule", buildSystem);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/ide/NewBlazeRuleDialog.java b/base/src/com/google/idea/blaze/base/ide/NewBlazeRuleDialog.java
new file mode 100644
index 0000000..64dcd3d
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/ide/NewBlazeRuleDialog.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.base.ide;
+
+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.WorkspacePath;
+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.ui.UiUtil;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.openapi.ui.ValidationInfo;
+import com.intellij.openapi.vfs.VirtualFile;
+import java.awt.Dimension;
+import java.awt.GridBagLayout;
+import java.io.File;
+import javax.annotation.Nullable;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+
+class NewBlazeRuleDialog extends DialogWrapper {
+  private static final Logger LOG = Logger.getInstance(NewBlazeRuleDialog.class);
+
+  private static final int UI_INDENT = 0;
+  private static final int TEXT_BOX_WIDTH = 40;
+
+  private final BlazeContext context;
+  private final Project project;
+  private final VirtualFile buildFile;
+  private final String buildSystemName;
+
+  private JPanel component = new JPanel(new GridBagLayout());
+  private final NewRuleUI newRuleUI = new NewRuleUI(TEXT_BOX_WIDTH);
+  private static final Dimension componentSize = new Dimension(500, 500);
+
+  public NewBlazeRuleDialog(BlazeContext context, Project project, VirtualFile buildFile) {
+    super(project);
+    this.context = context;
+    this.project = project;
+    this.buildFile = buildFile;
+    this.buildSystemName = Blaze.buildSystemName(project);
+    initComponent();
+  }
+
+  private void initComponent() {
+    setTitle(String.format("Create a New %s Rule", buildSystemName));
+    setOKButtonText("Create");
+    setCancelButtonText("Cancel");
+
+    component.setPreferredSize(componentSize);
+    component.setMinimumSize(componentSize);
+
+    newRuleUI.fillUI(component, UI_INDENT);
+    UiUtil.fillBottom(component);
+
+    init();
+  }
+
+  @Nullable
+  @Override
+  protected JComponent createCenterPanel() {
+    return component;
+  }
+
+  @Nullable
+  @Override
+  protected ValidationInfo doValidate() {
+    return newRuleUI.validate();
+  }
+
+  @Override
+  protected void doOKAction() {
+    RuleName ruleName = 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);
+    BuildFileModifier buildFileModifier = BuildFileModifier.getInstance();
+    boolean success = buildFileModifier.addRule(project, context, newRule, ruleKind);
+
+    if (success) {
+      super.doOKAction();
+    } else {
+      super.setErrorText(
+          String.format("Could not create new rule, see %s Console for details", buildSystemName));
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/ide/NewRuleUI.java b/base/src/com/google/idea/blaze/base/ide/NewRuleUI.java
new file mode 100644
index 0000000..f602bc2
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/ide/NewRuleUI.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.ide;
+
+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.ui.BlazeValidationError;
+import com.google.idea.blaze.base.ui.UiUtil;
+import com.intellij.ide.IdeBundle;
+import com.intellij.openapi.ui.ComboBox;
+import com.intellij.openapi.ui.ValidationInfo;
+import com.intellij.ui.components.JBLabel;
+import com.intellij.ui.components.JBTextField;
+import java.util.Collection;
+import java.util.List;
+import javax.swing.JPanel;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+final class NewRuleUI {
+
+  private static final String[] POSSIBLE_RULES = {
+    "android_library", "java_library", "cc_library", "cc_binary", "proto_library"
+  };
+
+  @NotNull private final ComboBox ruleComboBox = new ComboBox(POSSIBLE_RULES);
+  @NotNull private final JBLabel ruleNameLabel = new JBLabel("Rule name:");
+  @NotNull private final JBTextField ruleNameField;
+
+  public NewRuleUI(int textFieldLength) {
+    this.ruleNameField = new JBTextField(textFieldLength);
+  }
+
+  public void fillUI(@NotNull JPanel component, int indentLevel) {
+    component.add(ruleNameLabel);
+    component.add(ruleNameField, UiUtil.getFillLineConstraints(indentLevel));
+    component.add(ruleComboBox, UiUtil.getFillLineConstraints(indentLevel));
+  }
+
+  @NotNull
+  public Kind getSelectedRuleKind() {
+    return Kind.fromString((String) ruleComboBox.getSelectedItem());
+  }
+
+  @NotNull
+  public RuleName getRuleName() {
+    return RuleName.create(ruleNameField.getText());
+  }
+
+  @Nullable
+  public ValidationInfo validate() {
+    String ruleName = ruleNameField.getText();
+    List<BlazeValidationError> errors = Lists.newArrayList();
+    if (!validateRuleName(ruleName, errors)) {
+      BlazeValidationError issue = errors.get(0);
+      return new ValidationInfo(issue.getError(), ruleNameField);
+    }
+    return null;
+  }
+
+  private static boolean validateRuleName(
+      @NotNull String inputString, @Nullable Collection<BlazeValidationError> errors) {
+    if (inputString.length() == 0) {
+      BlazeValidationError.collect(
+          errors, new BlazeValidationError(IdeBundle.message("error.name.should.be.specified")));
+      return false;
+    }
+
+    return RuleName.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/AndroidRuleIdeInfo.java
new file mode 100644
index 0000000..31d7d60
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/ideinfo/AndroidRuleIdeInfo.java
@@ -0,0 +1,130 @@
+/*
+ * 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.Lists;
+import com.google.idea.blaze.base.model.primitives.Label;
+import java.io.Serializable;
+import java.util.Collection;
+import org.jetbrains.annotations.Nullable;
+
+/** Ide info specific to android rules. */
+public final class AndroidRuleIdeInfo implements Serializable {
+  private static final long serialVersionUID = 5L;
+
+  public final Collection<ArtifactLocation> resources;
+  @Nullable public final ArtifactLocation manifest;
+  @Nullable public final LibraryArtifact idlJar;
+  @Nullable public final LibraryArtifact resourceJar;
+  public final boolean hasIdlSources;
+  @Nullable public final String resourceJavaPackage;
+  public boolean generateResourceClass;
+  @Nullable public Label legacyResources;
+
+  public AndroidRuleIdeInfo(
+      Collection<ArtifactLocation> resources,
+      @Nullable String resourceJavaPackage,
+      boolean generateResourceClass,
+      @Nullable ArtifactLocation manifest,
+      @Nullable LibraryArtifact idlJar,
+      @Nullable LibraryArtifact resourceJar,
+      boolean hasIdlSources,
+      @Nullable Label legacyResources) {
+    this.resources = resources;
+    this.resourceJavaPackage = resourceJavaPackage;
+    this.generateResourceClass = generateResourceClass;
+    this.manifest = manifest;
+    this.idlJar = idlJar;
+    this.resourceJar = resourceJar;
+    this.hasIdlSources = hasIdlSources;
+    this.legacyResources = legacyResources;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  /** Builder for android rule */
+  public static class Builder {
+    private Collection<ArtifactLocation> resources = Lists.newArrayList();
+    private ArtifactLocation manifest;
+    private LibraryArtifact idlJar;
+    private LibraryArtifact resourceJar;
+    private boolean hasIdlSources;
+    private String resourceJavaPackage;
+    private boolean generateResourceClass;
+    private Label legacyResources;
+
+    public Builder setManifestFile(ArtifactLocation artifactLocation) {
+      this.manifest = artifactLocation;
+      return this;
+    }
+
+    public Builder addResource(ArtifactLocation artifactLocation) {
+      this.resources.add(artifactLocation);
+      return this;
+    }
+
+    public Builder setIdlJar(LibraryArtifact idlJar) {
+      this.idlJar = idlJar;
+      return this;
+    }
+
+    public Builder setHasIdlSources(boolean hasIdlSources) {
+      this.hasIdlSources = hasIdlSources;
+      return this;
+    }
+
+    public Builder setResourceJar(LibraryArtifact.Builder resourceJar) {
+      this.resourceJar = resourceJar.build();
+      return this;
+    }
+
+    public Builder setResourceJavaPackage(@Nullable String resourceJavaPackage) {
+      this.resourceJavaPackage = resourceJavaPackage;
+      return this;
+    }
+
+    public Builder setGenerateResourceClass(boolean generateResourceClass) {
+      this.generateResourceClass = generateResourceClass;
+      return this;
+    }
+
+    public Builder setLegacyResources(@Nullable Label legacyResources) {
+      this.legacyResources = legacyResources;
+      return this;
+    }
+
+    public AndroidRuleIdeInfo build() {
+      if (!resources.isEmpty() || manifest != null) {
+        if (!generateResourceClass) {
+          throw new IllegalStateException(
+              "Must set generateResourceClass if manifest or resources set");
+        }
+      }
+
+      return new AndroidRuleIdeInfo(
+          resources,
+          resourceJavaPackage,
+          generateResourceClass,
+          manifest,
+          idlJar,
+          resourceJar,
+          hasIdlSources,
+          legacyResources);
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/ideinfo/ArtifactLocation.java b/base/src/com/google/idea/blaze/base/ideinfo/ArtifactLocation.java
new file mode 100644
index 0000000..ac2c392
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/ideinfo/ArtifactLocation.java
@@ -0,0 +1,130 @@
+/*
+ * 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.File;
+import java.io.Serializable;
+import java.nio.file.Paths;
+
+/** Represents a blaze-produced artifact. */
+public final class ArtifactLocation implements Serializable {
+  private static final long serialVersionUID = 2L;
+
+  public final String rootPath;
+  public final String rootExecutionPathFragment;
+  public final String relativePath;
+  public final boolean isSource;
+
+  private ArtifactLocation(
+      String rootPath, String rootExecutionPathFragment, String relativePath, boolean isSource) {
+    this.rootPath = rootPath;
+    this.rootExecutionPathFragment = rootExecutionPathFragment;
+    this.relativePath = relativePath;
+    this.isSource = isSource;
+  }
+
+  /** Returns the root path of the artifact, eg. blaze-out */
+  public String getRootPath() {
+    return rootPath;
+  }
+
+  /** Gets the path relative to the root path. */
+  public String getRelativePath() {
+    return relativePath;
+  }
+
+  public boolean isSource() {
+    return isSource;
+  }
+
+  public boolean isGenerated() {
+    return !isSource;
+  }
+
+  public File getFile() {
+    return new File(getRootPath(), getRelativePath());
+  }
+
+  /**
+   * Returns rootExecutionPathFragment + relativePath. For source artifacts, this is simply
+   * relativePath
+   */
+  public String getExecutionRootRelativePath() {
+    return Paths.get(rootExecutionPathFragment, relativePath).toString();
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  /** Builder for an artifact location */
+  public static class Builder {
+    String rootPath;
+    String relativePath;
+    String rootExecutionPathFragment = "";
+    boolean isSource;
+
+    public Builder setRootPath(String rootPath) {
+      this.rootPath = rootPath;
+      return this;
+    }
+
+    public Builder setRelativePath(String relativePath) {
+      this.relativePath = relativePath;
+      return this;
+    }
+
+    public Builder setRootExecutionPathFragment(String rootExecutionPathFragment) {
+      this.rootExecutionPathFragment = rootExecutionPathFragment;
+      return this;
+    }
+
+    public Builder setIsSource(boolean isSource) {
+      this.isSource = isSource;
+      return this;
+    }
+
+    public ArtifactLocation build() {
+      return new ArtifactLocation(rootPath, rootExecutionPathFragment, relativePath, isSource);
+    }
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    ArtifactLocation that = (ArtifactLocation) o;
+    return Objects.equal(rootPath, that.rootPath)
+        && Objects.equal(rootExecutionPathFragment, that.rootExecutionPathFragment)
+        && Objects.equal(relativePath, that.relativePath)
+        && Objects.equal(isSource, that.isSource);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(rootPath, rootExecutionPathFragment, relativePath, isSource);
+  }
+
+  @Override
+  public String toString() {
+    return getFile().toString();
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/ideinfo/CRuleIdeInfo.java b/base/src/com/google/idea/blaze/base/ideinfo/CRuleIdeInfo.java
new file mode 100644
index 0000000..954ab66
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/ideinfo/CRuleIdeInfo.java
@@ -0,0 +1,123 @@
+/*
+ * 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 com.google.idea.blaze.base.model.primitives.ExecutionRootPath;
+import java.io.Serializable;
+
+/** Sister class to {@link JavaRuleIdeInfo} */
+public class CRuleIdeInfo implements Serializable {
+  private static final long serialVersionUID = 6L;
+
+  public final ImmutableList<ArtifactLocation> sources;
+
+  // From the cpp compilation context provider.
+  // These should all be for the entire transitive closure.
+  public final ImmutableList<ExecutionRootPath> transitiveIncludeDirectories;
+  public final ImmutableList<ExecutionRootPath> transitiveQuoteIncludeDirectories;
+  public final ImmutableList<String> transitiveDefines;
+  public final ImmutableList<ExecutionRootPath> transitiveSystemIncludeDirectories;
+
+  public CRuleIdeInfo(
+      ImmutableList<ArtifactLocation> sources,
+      ImmutableList<ExecutionRootPath> transitiveIncludeDirectories,
+      ImmutableList<ExecutionRootPath> transitiveQuoteIncludeDirectories,
+      ImmutableList<String> transitiveDefines,
+      ImmutableList<ExecutionRootPath> transitiveSystemIncludeDirectories) {
+    this.sources = sources;
+    this.transitiveIncludeDirectories = transitiveIncludeDirectories;
+    this.transitiveQuoteIncludeDirectories = transitiveQuoteIncludeDirectories;
+    this.transitiveDefines = transitiveDefines;
+    this.transitiveSystemIncludeDirectories = transitiveSystemIncludeDirectories;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  /** Builder for c rule info */
+  public static class Builder {
+    private final ImmutableList.Builder<ArtifactLocation> sources = ImmutableList.builder();
+
+    private final ImmutableList.Builder<ExecutionRootPath> transitiveIncludeDirectories =
+        ImmutableList.builder();
+    private final ImmutableList.Builder<ExecutionRootPath> transitiveQuoteIncludeDirectories =
+        ImmutableList.builder();
+    private final ImmutableList.Builder<String> transitiveDefines = ImmutableList.builder();
+    private final ImmutableList.Builder<ExecutionRootPath> transitiveSystemIncludeDirectories =
+        ImmutableList.builder();
+
+    public Builder addSources(Iterable<ArtifactLocation> sources) {
+      this.sources.addAll(sources);
+      return this;
+    }
+
+    public Builder addTransitiveIncludeDirectories(
+        Iterable<ExecutionRootPath> transitiveIncludeDirectories) {
+      this.transitiveIncludeDirectories.addAll(transitiveIncludeDirectories);
+      return this;
+    }
+
+    public Builder addTransitiveQuoteIncludeDirectories(
+        Iterable<ExecutionRootPath> transitiveQuoteIncludeDirectories) {
+      this.transitiveQuoteIncludeDirectories.addAll(transitiveQuoteIncludeDirectories);
+      return this;
+    }
+
+    public Builder addTransitiveDefines(Iterable<String> transitiveDefines) {
+      this.transitiveDefines.addAll(transitiveDefines);
+      return this;
+    }
+
+    public Builder addTransitiveSystemIncludeDirectories(
+        Iterable<ExecutionRootPath> transitiveSystemIncludeDirectories) {
+      this.transitiveSystemIncludeDirectories.addAll(transitiveSystemIncludeDirectories);
+      return this;
+    }
+
+    public CRuleIdeInfo build() {
+      return new CRuleIdeInfo(
+          sources.build(),
+          transitiveIncludeDirectories.build(),
+          transitiveQuoteIncludeDirectories.build(),
+          transitiveDefines.build(),
+          transitiveSystemIncludeDirectories.build());
+    }
+  }
+
+  @Override
+  public String toString() {
+    return "CRuleIdeInfo{"
+        + "\n"
+        + "  sources="
+        + sources
+        + "\n"
+        + "  transitiveIncludeDirectories="
+        + transitiveIncludeDirectories
+        + "\n"
+        + "  transitiveQuoteIncludeDirectories="
+        + transitiveQuoteIncludeDirectories
+        + "\n"
+        + "  transitiveDefines="
+        + transitiveDefines
+        + "\n"
+        + "  transitiveSystemIncludeDirectories="
+        + transitiveSystemIncludeDirectories
+        + "\n"
+        + '}';
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/ideinfo/CToolchainIdeInfo.java b/base/src/com/google/idea/blaze/base/ideinfo/CToolchainIdeInfo.java
new file mode 100644
index 0000000..867b1fe
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/ideinfo/CToolchainIdeInfo.java
@@ -0,0 +1,227 @@
+/*
+ * 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.ImmutableList;
+import com.google.idea.blaze.base.model.primitives.ExecutionRootPath;
+import java.io.Serializable;
+
+/** Sister class to {@link JavaRuleIdeInfo} */
+public class CToolchainIdeInfo implements Serializable {
+  private static final long serialVersionUID = 3L;
+
+  public final ImmutableList<String> baseCompilerOptions;
+  public final ImmutableList<String> cCompilerOptions;
+  public final ImmutableList<String> cppCompilerOptions;
+  public final ImmutableList<String> linkOptions;
+  public final ImmutableList<ExecutionRootPath> builtInIncludeDirectories;
+  public final ExecutionRootPath cppExecutable;
+  public final ExecutionRootPath preprocessorExecutable;
+  public final String targetName;
+
+  public final ImmutableList<String> unfilteredCompilerOptions;
+  public final ImmutableList<ExecutionRootPath> unfilteredToolchainSystemIncludes;
+
+  public CToolchainIdeInfo(
+      ImmutableList<String> baseCompilerOptions,
+      ImmutableList<String> cCompilerOptions,
+      ImmutableList<String> cppCompilerOptions,
+      ImmutableList<String> linkOptions,
+      ImmutableList<ExecutionRootPath> builtInIncludeDirectories,
+      ExecutionRootPath cppExecutable,
+      ExecutionRootPath preprocessorExecutable,
+      String targetName,
+      ImmutableList<String> unfilteredCompilerOptions,
+      ImmutableList<ExecutionRootPath> unfilteredToolchainSystemIncludes) {
+    this.baseCompilerOptions = baseCompilerOptions;
+    this.cCompilerOptions = cCompilerOptions;
+    this.cppCompilerOptions = cppCompilerOptions;
+    this.linkOptions = linkOptions;
+    this.builtInIncludeDirectories = builtInIncludeDirectories;
+    this.cppExecutable = cppExecutable;
+    this.preprocessorExecutable = preprocessorExecutable;
+    this.targetName = targetName;
+    this.unfilteredCompilerOptions = unfilteredCompilerOptions;
+    this.unfilteredToolchainSystemIncludes = unfilteredToolchainSystemIncludes;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  /** Builder for c toolchain */
+  public static class Builder {
+    private final ImmutableList.Builder<String> baseCompilerOptions = ImmutableList.builder();
+    private final ImmutableList.Builder<String> cCompilerOptions = ImmutableList.builder();
+    private final ImmutableList.Builder<String> cppCompilerOptions = ImmutableList.builder();
+    private final ImmutableList.Builder<String> linkOptions = ImmutableList.builder();
+
+    private final ImmutableList.Builder<ExecutionRootPath> builtInIncludeDirectories =
+        ImmutableList.builder();
+
+    ExecutionRootPath cppExecutable;
+    ExecutionRootPath preprocessorExecutable;
+
+    String targetName = "";
+
+    private final ImmutableList.Builder<String> unfilteredCompilerOptions = ImmutableList.builder();
+    private final ImmutableList.Builder<ExecutionRootPath> unfilteredToolchainSystemIncludes =
+        ImmutableList.builder();
+
+    public Builder addBaseCompilerOptions(Iterable<String> baseCompilerOptions) {
+      this.baseCompilerOptions.addAll(baseCompilerOptions);
+      return this;
+    }
+
+    public Builder addCCompilerOptions(Iterable<String> cCompilerOptions) {
+      this.cCompilerOptions.addAll(cCompilerOptions);
+      return this;
+    }
+
+    public Builder addCppCompilerOptions(Iterable<String> cppCompilerOptions) {
+      this.cppCompilerOptions.addAll(cppCompilerOptions);
+      return this;
+    }
+
+    public Builder addLinkOptions(Iterable<String> linkOptions) {
+      this.linkOptions.addAll(linkOptions);
+      return this;
+    }
+
+    public Builder addBuiltInIncludeDirectories(
+        Iterable<ExecutionRootPath> builtInIncludeDirectories) {
+      this.builtInIncludeDirectories.addAll(builtInIncludeDirectories);
+      return this;
+    }
+
+    public Builder setCppExecutable(ExecutionRootPath cppExecutable) {
+      this.cppExecutable = cppExecutable;
+      return this;
+    }
+
+    public Builder setPreprocessorExecutable(ExecutionRootPath preprocessorExecutable) {
+      this.preprocessorExecutable = preprocessorExecutable;
+      return this;
+    }
+
+    public Builder setTargetName(String targetName) {
+      this.targetName = targetName;
+      return this;
+    }
+
+    public Builder addUnfilteredCompilerOptions(Iterable<String> unfilteredCompilerOptions) {
+      this.unfilteredCompilerOptions.addAll(unfilteredCompilerOptions);
+      return this;
+    }
+
+    public Builder addUnfilteredToolchainSystemIncludes(
+        Iterable<ExecutionRootPath> unfilteredToolchainSystemIncludes) {
+      this.unfilteredToolchainSystemIncludes.addAll(unfilteredToolchainSystemIncludes);
+      return this;
+    }
+
+    public CToolchainIdeInfo build() {
+      return new CToolchainIdeInfo(
+          baseCompilerOptions.build(),
+          cCompilerOptions.build(),
+          cppCompilerOptions.build(),
+          linkOptions.build(),
+          builtInIncludeDirectories.build(),
+          cppExecutable,
+          preprocessorExecutable,
+          targetName,
+          unfilteredCompilerOptions.build(),
+          unfilteredToolchainSystemIncludes.build());
+    }
+  }
+
+  @Override
+  public String toString() {
+    return "CToolchainIdeInfo{"
+        + "\n"
+        + "  baseCompilerOptions="
+        + baseCompilerOptions
+        + "\n"
+        + "  cCompilerOptions="
+        + cCompilerOptions
+        + "\n"
+        + "  cppCompilerOptions="
+        + cppCompilerOptions
+        + "\n"
+        + "  linkOptions="
+        + linkOptions
+        + "\n"
+        + "  builtInIncludeDirectories="
+        + builtInIncludeDirectories
+        + "\n"
+        + "  cppExecutable='"
+        + cppExecutable
+        + '\''
+        + "\n"
+        + "  preprocessorExecutable='"
+        + preprocessorExecutable
+        + '\''
+        + "\n"
+        + "  targetName='"
+        + targetName
+        + '\''
+        + "\n"
+        + "  unfilteredCompilerOptions="
+        + unfilteredCompilerOptions
+        + "\n"
+        + "  unfilteredToolchainSystemIncludes="
+        + unfilteredToolchainSystemIncludes
+        + "\n"
+        + '}';
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    CToolchainIdeInfo that = (CToolchainIdeInfo) o;
+    return Objects.equal(baseCompilerOptions, that.baseCompilerOptions)
+        && Objects.equal(cCompilerOptions, that.cCompilerOptions)
+        && Objects.equal(cppCompilerOptions, that.cppCompilerOptions)
+        && Objects.equal(linkOptions, that.linkOptions)
+        && Objects.equal(builtInIncludeDirectories, that.builtInIncludeDirectories)
+        && Objects.equal(cppExecutable, that.cppExecutable)
+        && Objects.equal(preprocessorExecutable, that.preprocessorExecutable)
+        && Objects.equal(targetName, that.targetName)
+        && Objects.equal(unfilteredCompilerOptions, that.unfilteredCompilerOptions)
+        && Objects.equal(unfilteredToolchainSystemIncludes, that.unfilteredToolchainSystemIncludes);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(
+        baseCompilerOptions,
+        cCompilerOptions,
+        cppCompilerOptions,
+        linkOptions,
+        builtInIncludeDirectories,
+        cppExecutable,
+        preprocessorExecutable,
+        targetName,
+        unfilteredCompilerOptions,
+        unfilteredToolchainSystemIncludes);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/ideinfo/JavaRuleIdeInfo.java b/base/src/com/google/idea/blaze/base/ideinfo/JavaRuleIdeInfo.java
new file mode 100644
index 0000000..35f5a15
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/ideinfo/JavaRuleIdeInfo.java
@@ -0,0 +1,91 @@
+/*
+ * 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;
+import java.util.Collection;
+import org.jetbrains.annotations.Nullable;
+
+/** Ide info specific to java rules. */
+public final class JavaRuleIdeInfo implements Serializable {
+  private static final long serialVersionUID = 2L;
+
+  /**
+   * The main jar(s) produced by this java rule.
+   *
+   * <p>Usually this will be a single jar, but java_imports support importing multiple jars.
+   */
+  public final Collection<LibraryArtifact> jars;
+
+  /** A jar containing annotation processing. */
+  public final Collection<LibraryArtifact> generatedJars;
+
+  /**
+   * A jar containing code from *only* generated sources, iff the rule contains both generated and
+   * non-generated sources.
+   */
+  @Nullable public final LibraryArtifact filteredGenJar;
+
+  /** File containing a map from .java files to their corresponding package. */
+  @Nullable public final ArtifactLocation packageManifest;
+
+  /** File containing dependencies. */
+  @Nullable public final ArtifactLocation jdepsFile;
+
+  public JavaRuleIdeInfo(
+      Collection<LibraryArtifact> jars,
+      Collection<LibraryArtifact> generatedJars,
+      @Nullable LibraryArtifact filteredGenJar,
+      @Nullable ArtifactLocation packageManifest,
+      @Nullable ArtifactLocation jdepsFile) {
+    this.jars = jars;
+    this.generatedJars = generatedJars;
+    this.packageManifest = packageManifest;
+    this.jdepsFile = jdepsFile;
+    this.filteredGenJar = filteredGenJar;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  /** Builder for java info */
+  public static class Builder {
+    ImmutableList.Builder<LibraryArtifact> jars = ImmutableList.builder();
+    ImmutableList.Builder<LibraryArtifact> generatedJars = ImmutableList.builder();
+    LibraryArtifact filteredGenJar;
+
+    public Builder addJar(LibraryArtifact.Builder jar) {
+      jars.add(jar.build());
+      return this;
+    }
+
+    public Builder addGeneratedJar(LibraryArtifact.Builder jar) {
+      generatedJars.add(jar.build());
+      return this;
+    }
+
+    public Builder setFilteredGenJar(LibraryArtifact.Builder jar) {
+      this.filteredGenJar = jar.build();
+      return this;
+    }
+
+    public JavaRuleIdeInfo build() {
+      return new JavaRuleIdeInfo(jars.build(), generatedJars.build(), filteredGenJar, null, null);
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/ideinfo/JavaToolchainIdeInfo.java b/base/src/com/google/idea/blaze/base/ideinfo/JavaToolchainIdeInfo.java
new file mode 100644
index 0000000..b9a9dca
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/ideinfo/JavaToolchainIdeInfo.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.ideinfo;
+
+import java.io.Serializable;
+
+/** Represents the java_toolchain class */
+public class JavaToolchainIdeInfo implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  public final String sourceVersion;
+  public final String targetVersion;
+
+  public JavaToolchainIdeInfo(String sourceVersion, String targetVersion) {
+    this.sourceVersion = sourceVersion;
+    this.targetVersion = targetVersion;
+  }
+
+  @Override
+  public String toString() {
+    return "JavaToolchainIdeInfo{"
+        + "\n"
+        + "  sourceVersion="
+        + sourceVersion
+        + "\n"
+        + "  targetVersion="
+        + targetVersion
+        + "\n"
+        + '}';
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  /** Builder for java toolchain info */
+  public static class Builder {
+    String sourceVersion;
+    String targetVersion;
+
+    public Builder setSourceVersion(String sourceVersion) {
+      this.sourceVersion = sourceVersion;
+      return this;
+    }
+
+    public Builder setTargetVersion(String targetVersion) {
+      this.targetVersion = targetVersion;
+      return this;
+    }
+
+    public JavaToolchainIdeInfo build() {
+      return new JavaToolchainIdeInfo(sourceVersion, targetVersion);
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/ideinfo/LibraryArtifact.java b/base/src/com/google/idea/blaze/base/ideinfo/LibraryArtifact.java
new file mode 100644
index 0000000..f661e5a
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/ideinfo/LibraryArtifact.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.base.ideinfo;
+
+import com.google.common.base.Objects;
+import java.io.Serializable;
+import org.jetbrains.annotations.Nullable;
+
+/** Represents a jar artifact. */
+public class LibraryArtifact implements Serializable {
+  private static final long serialVersionUID = 2L;
+
+  @Nullable public final ArtifactLocation interfaceJar;
+  @Nullable public final ArtifactLocation classJar;
+  @Nullable public final ArtifactLocation sourceJar;
+
+  public LibraryArtifact(
+      @Nullable ArtifactLocation interfaceJar,
+      @Nullable ArtifactLocation classJar,
+      @Nullable ArtifactLocation sourceJar) {
+    if (interfaceJar == null && classJar == null) {
+      throw new IllegalArgumentException("Interface and class jars cannot both be null.");
+    }
+
+    this.interfaceJar = interfaceJar;
+    this.classJar = classJar;
+    this.sourceJar = sourceJar;
+  }
+
+  /**
+   * Returns the best jar to add to IntelliJ.
+   *
+   * <p>We prefer the interface jar if one exists, otherwise the class jar.
+   */
+  public ArtifactLocation jarForIntellijLibrary() {
+    if (interfaceJar != null) {
+      return interfaceJar;
+    }
+    return classJar;
+  }
+
+  @Override
+  public String toString() {
+    return String.format("jar=%s, ijar=%s, srcjar=%s", classJar, interfaceJar, sourceJar);
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    LibraryArtifact that = (LibraryArtifact) o;
+    return Objects.equal(interfaceJar, that.interfaceJar)
+        && Objects.equal(classJar, that.classJar)
+        && Objects.equal(sourceJar, that.sourceJar);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(interfaceJar, classJar, sourceJar);
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  /** Builder for library artifacts */
+  public static class Builder {
+    private ArtifactLocation interfaceJar;
+    private ArtifactLocation classJar;
+    private ArtifactLocation sourceJar;
+
+    public Builder setInterfaceJar(ArtifactLocation artifactLocation) {
+      this.interfaceJar = artifactLocation;
+      return this;
+    }
+
+    public Builder setClassJar(@Nullable ArtifactLocation artifactLocation) {
+      this.classJar = artifactLocation;
+      return this;
+    }
+
+    public Builder setSourceJar(@Nullable ArtifactLocation artifactLocation) {
+      this.sourceJar = artifactLocation;
+      return this;
+    }
+
+    public LibraryArtifact build() {
+      return new LibraryArtifact(interfaceJar, classJar, sourceJar);
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/ideinfo/ProtoLibraryLegacyInfo.java b/base/src/com/google/idea/blaze/base/ideinfo/ProtoLibraryLegacyInfo.java
new file mode 100644
index 0000000..464ba20
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/ideinfo/ProtoLibraryLegacyInfo.java
@@ -0,0 +1,91 @@
+/*
+ * 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;
+import java.util.Collection;
+
+/**
+ * Proto library info for legacy proto libraries.
+ *
+ * <p>Replicates blaze semantics.
+ */
+public class ProtoLibraryLegacyInfo implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  /** The api flavour used by the proto_library rule */
+  public enum ApiFlavor {
+    VERSION_1,
+    MUTABLE,
+    IMMUTABLE,
+    BOTH,
+    NONE,
+  }
+
+  public final ApiFlavor apiFlavor;
+
+  public final Collection<LibraryArtifact> jarsV1;
+  public final Collection<LibraryArtifact> jarsMutable;
+  public final Collection<LibraryArtifact> jarsImmutable;
+
+  public ProtoLibraryLegacyInfo(
+      ApiFlavor apiFlavor,
+      Collection<LibraryArtifact> jarsV1,
+      Collection<LibraryArtifact> jarsMutable,
+      Collection<LibraryArtifact> jarsImmutable) {
+    this.apiFlavor = apiFlavor;
+    this.jarsV1 = jarsV1;
+    this.jarsMutable = jarsMutable;
+    this.jarsImmutable = jarsImmutable;
+  }
+
+  public static Builder builder(ApiFlavor apiFlavor) {
+    return new Builder(apiFlavor);
+  }
+
+  /** Builder for proto library legacy info */
+  public static class Builder {
+    private final ApiFlavor apiFlavor;
+    private ImmutableList.Builder<LibraryArtifact> jarsV1 = ImmutableList.builder();
+    private ImmutableList.Builder<LibraryArtifact> jarsMutable = ImmutableList.builder();
+    private ImmutableList.Builder<LibraryArtifact> jarsImmutable = ImmutableList.builder();
+
+    Builder(ApiFlavor apiFlavor) {
+      this.apiFlavor = apiFlavor;
+    }
+
+    public Builder addJarV1(LibraryArtifact.Builder library) {
+      jarsV1.add(library.build());
+      return this;
+    }
+
+    public Builder addJarMutable(LibraryArtifact.Builder library) {
+      jarsMutable.add(library.build());
+      return this;
+    }
+
+    public Builder addJarImmutable(LibraryArtifact.Builder library) {
+      jarsImmutable.add(library.build());
+      return this;
+    }
+
+    public ProtoLibraryLegacyInfo build() {
+      return new ProtoLibraryLegacyInfo(
+          apiFlavor, jarsV1.build(), jarsMutable.build(), jarsImmutable.build());
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/ideinfo/RuleIdeInfo.java b/base/src/com/google/idea/blaze/base/ideinfo/RuleIdeInfo.java
new file mode 100644
index 0000000..a791efc
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/ideinfo/RuleIdeInfo.java
@@ -0,0 +1,238 @@
+/*
+ * 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.Lists;
+import com.google.idea.blaze.base.model.primitives.Kind;
+import com.google.idea.blaze.base.model.primitives.Label;
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import javax.annotation.Nullable;
+
+/** Simple implementation of RuleIdeInfo. */
+public final class RuleIdeInfo implements Serializable {
+  private static final long serialVersionUID = 10L;
+
+  public final Label label;
+  public final Kind kind;
+  @Nullable public final ArtifactLocation buildFile;
+  public final Collection<Label> dependencies;
+  public final Collection<Label> runtimeDeps;
+  public final Collection<String> tags;
+  public final Collection<ArtifactLocation> sources;
+  @Nullable public final CRuleIdeInfo cRuleIdeInfo;
+  @Nullable public final CToolchainIdeInfo cToolchainIdeInfo;
+  @Nullable public final JavaRuleIdeInfo javaRuleIdeInfo;
+  @Nullable public final AndroidRuleIdeInfo androidRuleIdeInfo;
+  @Nullable public final TestIdeInfo testIdeInfo;
+  @Nullable public final ProtoLibraryLegacyInfo protoLibraryLegacyInfo;
+  @Nullable public final JavaToolchainIdeInfo javaToolchainIdeInfo;
+
+  public RuleIdeInfo(
+      Label label,
+      Kind kind,
+      @Nullable ArtifactLocation buildFile,
+      Collection<Label> dependencies,
+      Collection<Label> runtimeDeps,
+      Collection<String> tags,
+      Collection<ArtifactLocation> sources,
+      @Nullable CRuleIdeInfo cRuleIdeInfo,
+      @Nullable CToolchainIdeInfo cToolchainIdeInfo,
+      @Nullable JavaRuleIdeInfo javaRuleIdeInfo,
+      @Nullable AndroidRuleIdeInfo androidRuleIdeInfo,
+      @Nullable TestIdeInfo testIdeInfo,
+      @Nullable ProtoLibraryLegacyInfo protoLibraryLegacyInfo,
+      @Nullable JavaToolchainIdeInfo javaToolchainIdeInfo) {
+    this.label = label;
+    this.kind = kind;
+    this.buildFile = buildFile;
+    this.dependencies = dependencies;
+    this.runtimeDeps = runtimeDeps;
+    this.tags = tags;
+    this.sources = sources;
+    this.cRuleIdeInfo = cRuleIdeInfo;
+    this.cToolchainIdeInfo = cToolchainIdeInfo;
+    this.javaRuleIdeInfo = javaRuleIdeInfo;
+    this.androidRuleIdeInfo = androidRuleIdeInfo;
+    this.testIdeInfo = testIdeInfo;
+    this.protoLibraryLegacyInfo = protoLibraryLegacyInfo;
+    this.javaToolchainIdeInfo = javaToolchainIdeInfo;
+  }
+
+  @Override
+  public String toString() {
+    return label.toString();
+  }
+
+  /** Returns whether this rule is one of the kinds. */
+  public boolean kindIsOneOf(Kind... kinds) {
+    return kindIsOneOf(Arrays.asList(kinds));
+  }
+
+  /** Returns whether this rule is one of the kinds. */
+  public boolean kindIsOneOf(List<Kind> kinds) {
+    if (kind != null) {
+      return kind.isOneOf(kinds);
+    }
+    return false;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  /** Builder for rule ide info */
+  public static class Builder {
+    private Label label;
+    private Kind kind;
+    private ArtifactLocation buildFile;
+    private final List<Label> dependencies = Lists.newArrayList();
+    private final List<Label> runtimeDeps = 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 CToolchainIdeInfo cToolchainIdeInfo;
+    private JavaRuleIdeInfo javaRuleIdeInfo;
+    private AndroidRuleIdeInfo androidRuleIdeInfo;
+    private TestIdeInfo testIdeInfo;
+    private ProtoLibraryLegacyInfo protoLibraryLegacyInfo;
+    private JavaToolchainIdeInfo javaToolchainIdeInfo;
+
+    public Builder setLabel(String label) {
+      return setLabel(new Label(label));
+    }
+
+    public Builder setLabel(Label label) {
+      this.label = label;
+      return this;
+    }
+
+    public Builder setBuildFile(ArtifactLocation buildFile) {
+      this.buildFile = buildFile;
+      return this;
+    }
+
+    public Builder setKind(String kind) {
+      return setKind(Kind.fromString(kind));
+    }
+
+    public Builder setKind(Kind kind) {
+      this.kind = kind;
+      return this;
+    }
+
+    public Builder addSource(ArtifactLocation source) {
+      this.sources.add(source);
+      return this;
+    }
+
+    public Builder addSource(ArtifactLocation.Builder source) {
+      return addSource(source.build());
+    }
+
+    public Builder setJavaInfo(JavaRuleIdeInfo.Builder builder) {
+      javaRuleIdeInfo = builder.build();
+      return this;
+    }
+
+    public Builder setCInfo(CRuleIdeInfo cInfo) {
+      this.cRuleIdeInfo = cInfo;
+      return this;
+    }
+
+    public Builder setCInfo(CRuleIdeInfo.Builder cInfo) {
+      return setCInfo(cInfo.build());
+    }
+
+    public Builder setCToolchainInfo(CToolchainIdeInfo info) {
+      this.cToolchainIdeInfo = info;
+      return this;
+    }
+
+    public Builder setCToolchainInfo(CToolchainIdeInfo.Builder info) {
+      return setCToolchainInfo(info.build());
+    }
+
+    public Builder setAndroidInfo(AndroidRuleIdeInfo androidInfo) {
+      this.androidRuleIdeInfo = androidInfo;
+      return this;
+    }
+
+    public Builder setAndroidInfo(AndroidRuleIdeInfo.Builder androidInfo) {
+      return setAndroidInfo(androidInfo.build());
+    }
+
+    public Builder setTestInfo(TestIdeInfo.Builder testInfo) {
+      this.testIdeInfo = testInfo.build();
+      return this;
+    }
+
+    public Builder setProtoLibraryLegacyInfo(
+        ProtoLibraryLegacyInfo.Builder protoLibraryLegacyInfo) {
+      this.protoLibraryLegacyInfo = protoLibraryLegacyInfo.build();
+      return this;
+    }
+
+    public Builder setJavaToolchainIdeInfo(JavaToolchainIdeInfo.Builder javaToolchainIdeInfo) {
+      this.javaToolchainIdeInfo = javaToolchainIdeInfo.build();
+      return this;
+    }
+
+    public Builder addTag(String s) {
+      this.tags.add(s);
+      return this;
+    }
+
+    public Builder addDependency(String s) {
+      return addDependency(new Label(s));
+    }
+
+    public Builder addDependency(Label label) {
+      this.dependencies.add(label);
+      return this;
+    }
+
+    public Builder addRuntimeDep(String s) {
+      return addRuntimeDep(new Label(s));
+    }
+
+    public Builder addRuntimeDep(Label label) {
+      this.runtimeDeps.add(label);
+      return this;
+    }
+
+    public RuleIdeInfo build() {
+      return new RuleIdeInfo(
+          label,
+          kind,
+          buildFile,
+          dependencies,
+          runtimeDeps,
+          tags,
+          sources,
+          cRuleIdeInfo,
+          cToolchainIdeInfo,
+          javaRuleIdeInfo,
+          androidRuleIdeInfo,
+          testIdeInfo,
+          protoLibraryLegacyInfo,
+          javaToolchainIdeInfo);
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/ideinfo/Tags.java b/base/src/com/google/idea/blaze/base/ideinfo/Tags.java
new file mode 100644
index 0000000..6f07e33
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/ideinfo/Tags.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+/** 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 RULE_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";
+
+  /** Ignores the target. */
+  public static final String RULE_TAG_EXCLUDE_TARGET = "intellij-exclude-target";
+}
diff --git a/base/src/com/google/idea/blaze/base/ideinfo/TestIdeInfo.java b/base/src/com/google/idea/blaze/base/ideinfo/TestIdeInfo.java
new file mode 100644
index 0000000..9257344
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/ideinfo/TestIdeInfo.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.ideinfo;
+
+import java.io.Serializable;
+import javax.annotation.Nullable;
+
+/** Test info. */
+public class TestIdeInfo implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  /** The "size" attribute from test rules */
+  public enum TestSize {
+    SMALL,
+    MEDIUM,
+    LARGE,
+    ENORMOUS
+  }
+
+  // Rules are "medium" test size by default
+  public static final TestSize DEFAULT_RULE_TEST_SIZE = TestSize.MEDIUM;
+
+  // Non-annotated methods and classes are "small" by default
+  public static final TestSize DEFAULT_NON_ANNOTATED_TEST_SIZE = TestSize.SMALL;
+
+  public final TestSize testSize;
+
+  public TestIdeInfo(TestSize testSize) {
+    this.testSize = testSize;
+  }
+
+  @Nullable
+  public static TestSize getTestSize(RuleIdeInfo rule) {
+    TestIdeInfo testIdeInfo = rule.testIdeInfo;
+    if (testIdeInfo == null) {
+      return null;
+    }
+    return testIdeInfo.testSize;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  /** Builder for test info */
+  public static class Builder {
+    private TestSize testSize = DEFAULT_RULE_TEST_SIZE;
+
+    public Builder setTestSize(TestSize testSize) {
+      this.testSize = testSize;
+      return this;
+    }
+
+    public TestIdeInfo build() {
+      return new TestIdeInfo(testSize);
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/io/FileAttributeProvider.java b/base/src/com/google/idea/blaze/base/io/FileAttributeProvider.java
new file mode 100644
index 0000000..69f8465
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/io/FileAttributeProvider.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.io;
+
+import com.intellij.openapi.components.ServiceManager;
+import java.io.File;
+
+/** Simple file system checks (existence, isDirectory) */
+public class FileAttributeProvider {
+
+  public static FileAttributeProvider getInstance() {
+    return ServiceManager.getService(FileAttributeProvider.class);
+  }
+
+  public boolean exists(File file) {
+    return file.exists();
+  }
+
+  public boolean isDirectory(File file) {
+    return file.isDirectory();
+  }
+
+  public boolean isFile(File file) {
+    return file.isFile();
+  }
+
+  public long getFileModifiedTime(File file) {
+    return file.lastModified();
+  }
+
+  public long getFileSize(File file) {
+    return file.length();
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/io/FileAttributeScanner.java b/base/src/com/google/idea/blaze/base/io/FileAttributeScanner.java
new file mode 100644
index 0000000..bcb30aa
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/io/FileAttributeScanner.java
@@ -0,0 +1,69 @@
+/*
+ * 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.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.async.executor.BlazeExecutor;
+import java.io.File;
+import java.util.List;
+
+/** Reads file attributes from a list files in parallel. */
+public class FileAttributeScanner {
+
+  interface AttributeReader<T> {
+    T getAttribute(File file);
+
+    boolean isValid(T attribute);
+  }
+
+  public static <T> ImmutableMap<File, T> readAttributes(
+      Iterable<File> fileList, AttributeReader<T> attributeReader, BlazeExecutor executor)
+      throws Exception {
+    List<ListenableFuture<FilePair<T>>> futures = Lists.newArrayList();
+    for (File file : fileList) {
+      futures.add(
+          executor.submit(
+              () -> {
+                T attribute = attributeReader.getAttribute(file);
+                if (attributeReader.isValid(attribute)) {
+                  return new FilePair<>(file, attribute);
+                }
+                return null;
+              }));
+    }
+
+    ImmutableMap.Builder<File, T> result = ImmutableMap.builder();
+    for (FilePair<T> filePair : Futures.allAsList(futures).get()) {
+      if (filePair != null) {
+        result.put(filePair.file, filePair.attribute);
+      }
+    }
+    return result.build();
+  }
+
+  private static class FilePair<T> {
+    public final File file;
+    public final T attribute;
+
+    public FilePair(File file, T attribute) {
+      this.file = file;
+      this.attribute = attribute;
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/io/FileSizeScanner.java b/base/src/com/google/idea/blaze/base/io/FileSizeScanner.java
new file mode 100644
index 0000000..6930825
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/io/FileSizeScanner.java
@@ -0,0 +1,49 @@
+/*
+ * 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.ImmutableMap;
+import com.google.idea.blaze.base.async.executor.BlazeExecutor;
+import java.io.File;
+
+/** Reads the file sizes from a list of files. */
+public class FileSizeScanner {
+
+  private static final class FileSizeReader implements FileAttributeScanner.AttributeReader<Long> {
+
+    private final FileAttributeProvider attributeProvider;
+
+    FileSizeReader(FileAttributeProvider attributeProvider) {
+      this.attributeProvider = attributeProvider;
+    }
+
+    @Override
+    public Long getAttribute(File file) {
+      return attributeProvider.getFileSize(file);
+    }
+
+    @Override
+    public boolean isValid(Long timestamp) {
+      return timestamp != 0;
+    }
+  }
+
+  public static ImmutableMap<File, Long> readFilesizes(Iterable<File> fileList) throws Exception {
+    final FileSizeReader fileSizeReader = new FileSizeReader(FileAttributeProvider.getInstance());
+    return FileAttributeScanner.readAttributes(
+        fileList, fileSizeReader, BlazeExecutor.getInstance());
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/io/InputStreamProvider.java b/base/src/com/google/idea/blaze/base/io/InputStreamProvider.java
new file mode 100644
index 0000000..24607f9
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/io/InputStreamProvider.java
@@ -0,0 +1,31 @@
+/*
+ * 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.intellij.openapi.components.ServiceManager;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+/** Provides input streams for files. */
+public interface InputStreamProvider {
+
+  static InputStreamProvider getInstance() {
+    return ServiceManager.getService(InputStreamProvider.class);
+  }
+
+  InputStream getFile(File file) throws IOException;
+}
diff --git a/base/src/com/google/idea/blaze/base/io/InputStreamProviderImpl.java b/base/src/com/google/idea/blaze/base/io/InputStreamProviderImpl.java
new file mode 100644
index 0000000..1d5507a
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/io/InputStreamProviderImpl.java
@@ -0,0 +1,31 @@
+/*
+ * 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 java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import org.jetbrains.annotations.NotNull;
+
+/** Default implementation of InputStreamProvider. */
+final class InputStreamProviderImpl implements InputStreamProvider {
+
+  @Override
+  public InputStream getFile(@NotNull File file) throws FileNotFoundException {
+    return new FileInputStream(file);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/io/ModifiedTimeScanner.java b/base/src/com/google/idea/blaze/base/io/ModifiedTimeScanner.java
new file mode 100644
index 0000000..d7c66db
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/io/ModifiedTimeScanner.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.io;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.idea.blaze.base.async.executor.BlazeExecutor;
+import java.io.File;
+
+/** Reads the last modified times from a list of files. */
+public class ModifiedTimeScanner {
+
+  private static final class TimestampReader implements FileAttributeScanner.AttributeReader<Long> {
+
+    private final FileAttributeProvider attributeProvider;
+
+    TimestampReader(FileAttributeProvider attributeProvider) {
+      this.attributeProvider = attributeProvider;
+    }
+
+    @Override
+    public Long getAttribute(File file) {
+      return attributeProvider.getFileModifiedTime(file);
+    }
+
+    @Override
+    public boolean isValid(Long timestamp) {
+      return timestamp != 0;
+    }
+  }
+
+  public static ImmutableMap<File, Long> readTimestamps(Iterable<File> fileList) throws Exception {
+    final TimestampReader timestampReader =
+        new TimestampReader(FileAttributeProvider.getInstance());
+    return FileAttributeScanner.readAttributes(
+        fileList, timestampReader, BlazeExecutor.getInstance());
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/io/VfsWorkspaceScanner.java b/base/src/com/google/idea/blaze/base/io/VfsWorkspaceScanner.java
new file mode 100644
index 0000000..de12e98
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/io/VfsWorkspaceScanner.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.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
new file mode 100644
index 0000000..4fdc691
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/io/WorkspaceScanner.java
@@ -0,0 +1,29 @@
+/*
+ * 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
new file mode 100644
index 0000000..57fcbbd
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/issueparser/BlazeIssueParser.java
@@ -0,0 +1,337 @@
+/*
+ * 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.issueparser;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+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.ProjectViewManager;
+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.Section;
+import com.google.idea.blaze.base.projectview.section.SectionKey;
+import com.google.idea.blaze.base.projectview.section.sections.TargetSection;
+import com.google.idea.blaze.base.scope.output.IssueOutput;
+import com.intellij.openapi.project.Project;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/** Parses blaze output for compile errors. */
+public class BlazeIssueParser {
+
+  private static class ParseResult {
+
+    public static final ParseResult NEEDS_MORE_INPUT = new ParseResult(true, null);
+
+    public static final ParseResult NO_RESULT = new ParseResult(false, null);
+
+    private boolean needsMoreInput;
+    @Nullable private IssueOutput output;
+
+    private ParseResult(boolean needsMoreInput, IssueOutput output) {
+      this.needsMoreInput = needsMoreInput;
+      this.output = output;
+    }
+
+    public static ParseResult needsMoreInput() {
+      return NEEDS_MORE_INPUT;
+    }
+
+    public static ParseResult output(IssueOutput output) {
+      return new ParseResult(false, output);
+    }
+
+    public static ParseResult noResult() {
+      return NO_RESULT;
+    }
+  }
+
+  interface Parser {
+    @NotNull
+    ParseResult parse(@NotNull String currentLine, @NotNull List<String> previousLines);
+  }
+
+  abstract static class SingleLineParser implements Parser {
+    @NotNull Pattern pattern;
+
+    SingleLineParser(@NotNull String regex) {
+      pattern = Pattern.compile(regex);
+    }
+
+    @Override
+    public ParseResult parse(
+        @NotNull String currentLine, @NotNull List<String> multilineMatchResult) {
+      checkState(
+          multilineMatchResult.isEmpty(), "SingleLineParser recieved multiple lines of input");
+      return parse(currentLine);
+    }
+
+    ParseResult parse(@NotNull String line) {
+      Matcher matcher = pattern.matcher(line);
+      if (matcher.find()) {
+        return ParseResult.output(createIssue(matcher));
+      }
+      return ParseResult.noResult();
+    }
+
+    @Nullable
+    protected abstract IssueOutput createIssue(@NotNull Matcher matcher);
+  }
+
+  static class CompileParser extends SingleLineParser {
+    @NotNull private final WorkspaceRoot workspaceRoot;
+
+    public CompileParser(@NotNull WorkspaceRoot workspaceRoot) {
+      super("(.*?):([0-9]+):([0-9]+:)? (error|warning): (.*)");
+      this.workspaceRoot = workspaceRoot;
+    }
+
+    @Override
+    protected IssueOutput createIssue(@NotNull Matcher matcher) {
+      final File file;
+      try {
+        String fileName = matcher.group(1);
+        final WorkspacePath workspacePath;
+        if (fileName.startsWith("//depot/google3/")) {
+          workspacePath = new WorkspacePath(fileName.substring("//depot/google3/".length()));
+        } else if (fileName.startsWith("/")) {
+          workspacePath = workspaceRoot.workspacePathFor(new File(fileName));
+        } else {
+          workspacePath = new WorkspacePath(fileName);
+        }
+        file = workspaceRoot.fileForPath(workspacePath);
+      } catch (IllegalArgumentException e) {
+        // Ignore -- malformed error message
+        return null;
+      }
+
+      IssueOutput.Category type =
+          matcher.group(4).equals("error")
+              ? IssueOutput.Category.ERROR
+              : IssueOutput.Category.WARNING;
+      return IssueOutput.issue(type, matcher.group(5))
+          .inFile(file)
+          .onLine(Integer.parseInt(matcher.group(2)))
+          .build();
+    }
+  }
+
+  static class TracebackParser implements Parser {
+    private static final Pattern PATTERN =
+        Pattern.compile(
+            "(ERROR): (.*?):([0-9]+):([0-9]+): (Traceback \\(most recent call last\\):)");
+
+    @NotNull
+    @Override
+    public ParseResult parse(@NotNull String currentLine, @NotNull List<String> previousLines) {
+      if (previousLines.isEmpty()) {
+        if (PATTERN.matcher(currentLine).find()) {
+          return ParseResult.needsMoreInput();
+        } else {
+          return ParseResult.noResult();
+        }
+      }
+
+      if (currentLine.startsWith("\t")) {
+        return ParseResult.needsMoreInput();
+      } else {
+        Matcher matcher = PATTERN.matcher(previousLines.get(0));
+        checkState(
+            matcher.find(), "Found a match in the first line previously, but now it isn't there.");
+        StringBuilder message = new StringBuilder(matcher.group(5));
+        for (int i = 1; i < previousLines.size(); ++i) {
+          message.append(System.lineSeparator()).append(previousLines.get(i));
+        }
+        message.append(System.lineSeparator()).append(currentLine);
+        return ParseResult.output(
+            IssueOutput.error(message.toString())
+                .inFile(new File(matcher.group(2)))
+                .onLine(Integer.parseInt(matcher.group(3)))
+                .build());
+      }
+    }
+  }
+
+  static class BuildParser extends SingleLineParser {
+    BuildParser() {
+      super("(ERROR): (.*?):([0-9]+):([0-9]+): (.*)");
+    }
+
+    @Override
+    protected IssueOutput createIssue(@NotNull Matcher matcher) {
+      return IssueOutput.error(matcher.group(5))
+          .inFile(new File(matcher.group(2)))
+          .onLine(Integer.parseInt(matcher.group(3)))
+          .build();
+    }
+  }
+
+  static class LinelessBuildParser extends SingleLineParser {
+    LinelessBuildParser() {
+      super("(ERROR): (.*?):char offsets [0-9]+--[0-9]+: (.*)");
+    }
+
+    @Override
+    protected IssueOutput createIssue(@NotNull Matcher matcher) {
+      return IssueOutput.error(matcher.group(3)).inFile(new File(matcher.group(2))).build();
+    }
+  }
+
+  static class ProjectViewLabelParser extends SingleLineParser {
+
+    @Nullable private final ProjectViewSet projectViewSet;
+
+    ProjectViewLabelParser(@Nullable ProjectViewSet projectViewSet) {
+      super("no such target '(.*)': target .*? not declared in package .*? defined by");
+      this.projectViewSet = projectViewSet;
+    }
+
+    @Override
+    protected IssueOutput createIssue(@NotNull Matcher matcher) {
+      File file = null;
+      if (projectViewSet != null) {
+        String targetString = matcher.group(1);
+        final TargetExpression targetExpression = TargetExpression.fromString(targetString);
+        file =
+            projectViewFileWithSection(
+                projectViewSet,
+                TargetSection.KEY,
+                new Predicate<ListSection<TargetExpression>>() {
+                  @Override
+                  public boolean apply(@NotNull ListSection<TargetExpression> targetSection) {
+                    return targetSection.items().contains(targetExpression);
+                  }
+                });
+      }
+
+      return IssueOutput.error(matcher.group(0)).inFile(file).build();
+    }
+  }
+
+  static class InvalidTargetProjectViewPackageParser extends SingleLineParser {
+    @Nullable private final ProjectViewSet projectViewSet;
+
+    InvalidTargetProjectViewPackageParser(@Nullable ProjectViewSet projectViewSet, String regex) {
+      super(regex);
+      this.projectViewSet = projectViewSet;
+    }
+
+    @Override
+    protected IssueOutput createIssue(@NotNull Matcher matcher) {
+      File file = null;
+      if (projectViewSet != null) {
+        final String packageString = matcher.group(1);
+        file =
+            projectViewFileWithSection(
+                projectViewSet,
+                TargetSection.KEY,
+                targetSection -> {
+                  for (TargetExpression targetExpression : targetSection.items()) {
+                    if (targetExpression.toString().startsWith("//" + packageString + ":")) {
+                      return true;
+                    }
+                  }
+                  return false;
+                });
+      }
+
+      return IssueOutput.error(matcher.group(0)).inFile(file).build();
+    }
+  }
+
+  @Nullable
+  private static <T, SectionType extends Section<T>> File projectViewFileWithSection(
+      @NotNull ProjectViewSet projectViewSet,
+      @NotNull SectionKey<T, SectionType> key,
+      @NotNull Predicate<SectionType> predicate) {
+    for (ProjectViewSet.ProjectViewFile projectViewFile : projectViewSet.getProjectViewFiles()) {
+      ImmutableList<SectionType> sections = projectViewFile.projectView.getSectionsOfType(key);
+      for (SectionType section : sections) {
+        if (predicate.apply(section)) {
+          return projectViewFile.projectViewFile;
+        }
+      }
+    }
+    return null;
+  }
+
+  @NotNull private List<Parser> parsers = Lists.newArrayList();
+  /**
+   * The parser that requested more lines of input during the last call to {@link
+   * #parseIssue(String)}.
+   */
+  @Nullable private Parser multilineMatchingParser;
+
+  @NotNull private List<String> multilineMatchResult = new ArrayList<>();
+
+  public BlazeIssueParser(@Nullable Project project, @NotNull WorkspaceRoot workspaceRoot) {
+
+    ProjectViewSet projectViewSet =
+        project != null ? ProjectViewManager.getInstance(project).getProjectViewSet() : null;
+
+    parsers.add(new CompileParser(workspaceRoot));
+    parsers.add(new TracebackParser());
+    parsers.add(new BuildParser());
+    parsers.add(new LinelessBuildParser());
+    parsers.add(new ProjectViewLabelParser(projectViewSet));
+    parsers.add(
+        new InvalidTargetProjectViewPackageParser(
+            projectViewSet, "no such package '(.*)': BUILD file not found on package path"));
+    parsers.add(
+        new InvalidTargetProjectViewPackageParser(
+            projectViewSet, "no targets found beneath '(.*)'"));
+    parsers.add(
+        new InvalidTargetProjectViewPackageParser(
+            projectViewSet, "ERROR: invalid target format '(.*)'"));
+  }
+
+  @Nullable
+  public IssueOutput parseIssue(String line) {
+
+    List<Parser> parsers = this.parsers;
+    if (multilineMatchingParser != null) {
+      parsers = Lists.newArrayList(multilineMatchingParser);
+    }
+
+    for (Parser parser : parsers) {
+      ParseResult issue = parser.parse(line, multilineMatchResult);
+      if (issue.needsMoreInput) {
+        multilineMatchingParser = parser;
+        multilineMatchResult.add(line);
+        return null;
+      } else {
+        multilineMatchingParser = null;
+        multilineMatchResult = new ArrayList<>();
+      }
+
+      if (issue.output != null) {
+        return issue.output;
+      }
+    }
+
+    return null;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/issueparser/IssueOutputLineProcessor.java b/base/src/com/google/idea/blaze/base/issueparser/IssueOutputLineProcessor.java
new file mode 100644
index 0000000..dbb313e
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/issueparser/IssueOutputLineProcessor.java
@@ -0,0 +1,62 @@
+/*
+ * 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.issueparser;
+
+import com.google.idea.blaze.base.async.process.LineProcessingOutputStream;
+import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
+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.PrintOutput;
+import com.google.idea.blaze.base.scope.output.PrintOutput.OutputType;
+import com.intellij.openapi.project.Project;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Forwards output to PrintOutputs, colored by whether or not an issue is found per-line.
+ *
+ * <p>Also creates IssueOutput if issues are found.
+ */
+public class IssueOutputLineProcessor implements LineProcessingOutputStream.LineProcessor {
+
+  @NotNull private final BlazeContext context;
+
+  @NotNull private final BlazeIssueParser blazeIssueParser;
+
+  public IssueOutputLineProcessor(
+      @Nullable Project project,
+      @NotNull BlazeContext context,
+      @NotNull WorkspaceRoot workspaceRoot) {
+    this.context = context;
+    this.blazeIssueParser = new BlazeIssueParser(project, workspaceRoot);
+  }
+
+  @Override
+  public boolean processLine(@NotNull String line) {
+    IssueOutput issue = blazeIssueParser.parseIssue(line);
+    if (issue != null) {
+      if (issue.getCategory() == IssueOutput.Category.ERROR) {
+        context.setHasError();
+      }
+      context.output(issue);
+    }
+
+    OutputType outputType = issue == null ? OutputType.NORMAL : OutputType.ERROR;
+
+    context.output(new PrintOutput(line, outputType));
+    return true;
+  }
+}
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
new file mode 100644
index 0000000..561bf04
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/actions/BuildFileModifierImpl.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.actions;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.base.buildmodifier.BuildFileModifier;
+import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
+import com.google.idea.blaze.base.lang.buildfile.psi.Expression;
+import com.google.idea.blaze.base.lang.buildfile.psi.FuncallExpression;
+import com.google.idea.blaze.base.lang.buildfile.psi.util.BuildElementGenerator;
+import com.google.idea.blaze.base.lang.buildfile.references.BuildReferenceManager;
+import com.google.idea.blaze.base.model.primitives.Kind;
+import com.google.idea.blaze.base.model.primitives.Label;
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.intellij.openapi.command.WriteCommandAction;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Computable;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.psi.PsiElement;
+import java.io.File;
+
+/** Implementation of BuildFileModifier. Modifies the PSI tree directly. */
+public class BuildFileModifierImpl implements BuildFileModifier {
+
+  private static final Logger LOG = Logger.getInstance(BuildFileModifierImpl.class);
+
+  @Override
+  public boolean addRule(Project project, BlazeContext context, Label newRule, Kind ruleKind) {
+    return WriteCommandAction.runWriteCommandAction(
+        project,
+        (Computable<Boolean>)
+            () -> {
+              BuildReferenceManager manager = BuildReferenceManager.getInstance(project);
+              File file = manager.resolvePackage(newRule.blazePackage());
+              if (file == null) {
+                return null;
+              }
+              LocalFileSystem.getInstance().refreshIoFiles(ImmutableList.of(file));
+              BuildFile buildFile = manager.resolveBlazePackage(newRule.blazePackage());
+              if (buildFile == null) {
+                LOG.error("No BUILD file found at location: " + newRule.blazePackage());
+                return false;
+              }
+              buildFile.add(createRule(project, ruleKind, newRule.ruleName().toString()));
+              return true;
+            });
+  }
+
+  private PsiElement createRule(Project project, Kind ruleKind, String ruleName) {
+    String text =
+        Joiner.on("\n").join(ruleKind.toString() + "(", "    name = \"" + ruleName + "\"", ")");
+    Expression expr = BuildElementGenerator.getInstance(project).createExpressionFromText(text);
+    assert (expr instanceof FuncallExpression);
+    return expr;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/completion/ArgumentCompletionContributor.java b/base/src/com/google/idea/blaze/base/lang/buildfile/completion/ArgumentCompletionContributor.java
new file mode 100644
index 0000000..f4e420f
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/completion/ArgumentCompletionContributor.java
@@ -0,0 +1,83 @@
+/*
+ * 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.completion;
+
+import static com.intellij.patterns.PlatformPatterns.psiComment;
+import static com.intellij.patterns.PlatformPatterns.psiElement;
+
+import com.google.idea.blaze.base.lang.buildfile.language.BuildFileLanguage;
+import com.google.idea.blaze.base.lang.buildfile.lexer.BuildToken;
+import com.google.idea.blaze.base.lang.buildfile.lexer.TokenKind;
+import com.google.idea.blaze.base.lang.buildfile.psi.Argument;
+import com.google.idea.blaze.base.lang.buildfile.psi.ReferenceExpression;
+import com.intellij.codeInsight.completion.AutoCompletionContext;
+import com.intellij.codeInsight.completion.AutoCompletionDecision;
+import com.intellij.codeInsight.completion.CompletionContributor;
+import com.intellij.codeInsight.completion.CompletionParameters;
+import com.intellij.codeInsight.completion.CompletionProvider;
+import com.intellij.codeInsight.completion.CompletionResultSet;
+import com.intellij.codeInsight.completion.CompletionType;
+import com.intellij.codeInsight.lookup.LookupElement;
+import com.intellij.psi.util.PsiTreeUtil;
+import com.intellij.util.ProcessingContext;
+
+/**
+ * We can't rely solely keyword arg references, because as the user is typing a new keyword arg, the
+ * PsiElement will be a ReferenceExpression (with different completion results not relevant to
+ * keyword args).
+ */
+public class ArgumentCompletionContributor extends CompletionContributor {
+
+  @Override
+  public AutoCompletionDecision handleAutoCompletionPossibility(AutoCompletionContext context) {
+    // auto-insert the obvious only case; else show other cases.
+    final LookupElement[] items = context.getItems();
+    if (items.length == 1) {
+      return AutoCompletionDecision.insertItem(items[0]);
+    }
+    return AutoCompletionDecision.SHOW_LOOKUP;
+  }
+
+  public ArgumentCompletionContributor() {
+    extend(
+        CompletionType.BASIC,
+        psiElement()
+            .withLanguage(BuildFileLanguage.INSTANCE)
+            .withElementType(BuildToken.fromKind(TokenKind.IDENTIFIER))
+            .withParents(ReferenceExpression.class, Argument.Positional.class)
+            .andNot(psiComment())
+            .andNot(psiElement().afterLeaf("="))
+            .andNot(psiElement().afterLeaf(psiElement(BuildToken.fromKind(TokenKind.IDENTIFIER)))),
+        new CompletionProvider<CompletionParameters>() {
+          @Override
+          protected void addCompletions(
+              CompletionParameters parameters,
+              ProcessingContext context,
+              CompletionResultSet result) {
+            Argument.Positional arg =
+                PsiTreeUtil.getParentOfType(parameters.getPosition(), Argument.Positional.class);
+            if (arg != null) {
+              Object[] lookups = arg.getReference().getVariants();
+              for (Object lookup : lookups) {
+                if (lookup instanceof LookupElement) {
+                  result.addElement((LookupElement) lookup);
+                }
+              }
+            }
+          }
+        });
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/completion/BuildLookupElement.java b/base/src/com/google/idea/blaze/base/lang/buildfile/completion/BuildLookupElement.java
new file mode 100644
index 0000000..cc05ea4
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/completion/BuildLookupElement.java
@@ -0,0 +1,107 @@
+/*
+ * 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.completion;
+
+import com.google.idea.blaze.base.lang.buildfile.references.QuoteType;
+import com.intellij.codeInsight.completion.InsertionContext;
+import com.intellij.codeInsight.lookup.LookupElement;
+import com.intellij.codeInsight.lookup.LookupElementPresentation;
+import com.intellij.openapi.editor.Document;
+import com.intellij.psi.PsiElement;
+import javax.annotation.Nullable;
+import javax.swing.Icon;
+
+/**
+ * Handles some boilerplate, and allows lazy calculation of some expensive components, which aren't
+ * required if the element is filtered out by IJ.
+ */
+public abstract class BuildLookupElement extends LookupElement {
+
+  public static final BuildLookupElement[] EMPTY_ARRAY = new BuildLookupElement[0];
+
+  protected final String baseName;
+  protected final QuoteType quoteWrapping;
+  protected final boolean wrapWithQuotes;
+
+  public BuildLookupElement(String baseName, QuoteType quoteWrapping) {
+    this.baseName = baseName;
+    this.quoteWrapping = quoteWrapping;
+    this.wrapWithQuotes = quoteWrapping != QuoteType.NoQuotes;
+  }
+
+  @Override
+  public String getLookupString() {
+    return quoteWrapping.wrap(baseName);
+  }
+
+  @Nullable
+  public abstract Icon getIcon();
+
+  protected String getItemText() {
+    return baseName;
+  }
+
+  @Nullable
+  protected String getTypeText() {
+    return null;
+  }
+
+  @Nullable
+  protected String getTailText() {
+    return null;
+  }
+
+  @Override
+  public void renderElement(LookupElementPresentation presentation) {
+    presentation.setItemText(getItemText());
+    presentation.setTailText(getTailText());
+    presentation.setTypeText(getTypeText());
+    presentation.setIcon(getIcon());
+  }
+
+  /**
+   * If we're wrapping with quotes, handle the (very common) case where we have a closing quote
+   * after the caret -- we want to remove this quote.
+   *
+   * @param context
+   */
+  @Override
+  public void handleInsert(InsertionContext context) {
+    if (!wrapWithQuotes) {
+      super.handleInsert(context);
+      return;
+    }
+    Document document = context.getDocument();
+    context.commitDocument();
+    PsiElement suffix = context.getFile().findElementAt(context.getTailOffset());
+    if (suffix.getText().startsWith(quoteWrapping.quoteString)) {
+      int offset = suffix.getTextOffset();
+      document.deleteString(offset, offset + 1);
+      context.commitDocument();
+    }
+    if (caretInsideQuotes()) {
+      context.getEditor().getCaretModel().moveCaretRelatively(-1, 0, false, false, true);
+    }
+  }
+
+  /**
+   * If true, and we're wrapping with quotes, the caret is moved inside the closing quote after the
+   * insert operation is performed.
+   */
+  protected boolean caretInsideQuotes() {
+    return false;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/completion/BuiltInFunctionAttributeCompletionContributor.java b/base/src/com/google/idea/blaze/base/lang/buildfile/completion/BuiltInFunctionAttributeCompletionContributor.java
new file mode 100644
index 0000000..1b16003
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/completion/BuiltInFunctionAttributeCompletionContributor.java
@@ -0,0 +1,102 @@
+/*
+ * 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.completion;
+
+import static com.intellij.patterns.PlatformPatterns.psiComment;
+import static com.intellij.patterns.PlatformPatterns.psiElement;
+
+import com.google.idea.blaze.base.lang.buildfile.language.BuildFileLanguage;
+import com.google.idea.blaze.base.lang.buildfile.language.semantics.BuildLanguageSpec;
+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.lexer.BuildToken;
+import com.google.idea.blaze.base.lang.buildfile.lexer.TokenKind;
+import com.google.idea.blaze.base.lang.buildfile.psi.Argument;
+import com.google.idea.blaze.base.lang.buildfile.psi.FuncallExpression;
+import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
+import com.intellij.codeInsight.completion.AutoCompletionContext;
+import com.intellij.codeInsight.completion.AutoCompletionDecision;
+import com.intellij.codeInsight.completion.CompletionContributor;
+import com.intellij.codeInsight.completion.CompletionParameters;
+import com.intellij.codeInsight.completion.CompletionProvider;
+import com.intellij.codeInsight.completion.CompletionResultSet;
+import com.intellij.codeInsight.completion.CompletionType;
+import com.intellij.codeInsight.lookup.LookupElement;
+import com.intellij.codeInsight.lookup.LookupElementBuilder;
+import com.intellij.icons.AllIcons;
+import com.intellij.psi.PsiElement;
+import com.intellij.util.ProcessingContext;
+import javax.annotation.Nullable;
+
+/** Known attributes for built-in blaze functions. */
+public class BuiltInFunctionAttributeCompletionContributor extends CompletionContributor {
+
+  @Override
+  public AutoCompletionDecision handleAutoCompletionPossibility(AutoCompletionContext context) {
+    // auto-insert the obvious only case; else show other cases.
+    final LookupElement[] items = context.getItems();
+    if (items.length == 1) {
+      return AutoCompletionDecision.insertItem(items[0]);
+    }
+    return AutoCompletionDecision.SHOW_LOOKUP;
+  }
+
+  public BuiltInFunctionAttributeCompletionContributor() {
+    extend(
+        CompletionType.BASIC,
+        psiElement()
+            .withLanguage(BuildFileLanguage.INSTANCE)
+            .inside(psiElement(FuncallExpression.class))
+            .andNot(psiComment())
+            .andNot(psiElement().afterLeaf("."))
+            .andOr(
+                psiElement().withSuperParent(2, FuncallExpression.class),
+                psiElement()
+                    .withSuperParent(2, Argument.class)
+                    .andNot(psiElement().afterLeaf("="))
+                    .andNot(
+                        psiElement()
+                            .afterLeaf(psiElement(BuildToken.fromKind(TokenKind.IDENTIFIER))))),
+        new CompletionProvider<CompletionParameters>() {
+          @Override
+          protected void addCompletions(
+              CompletionParameters parameters,
+              ProcessingContext context,
+              CompletionResultSet result) {
+            BuildLanguageSpec spec =
+                BuildLanguageSpecProvider.getInstance()
+                    .getLanguageSpec(parameters.getPosition().getProject());
+            if (spec == null) {
+              return;
+            }
+            RuleDefinition rule = spec.getRule(getEnclosingFuncallName(parameters.getPosition()));
+            if (rule == null) {
+              return;
+            }
+            for (String attributeName : rule.getKnownAttributeNames()) {
+              result.addElement(
+                  LookupElementBuilder.create(attributeName).withIcon(AllIcons.Nodes.Parameter));
+            }
+          }
+        });
+  }
+
+  @Nullable
+  private static String getEnclosingFuncallName(PsiElement element) {
+    FuncallExpression funcall = PsiUtils.getParentOfType(element, FuncallExpression.class);
+    return funcall != null ? funcall.getFunctionName() : null;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/completion/BuiltInFunctionCompletionContributor.java b/base/src/com/google/idea/blaze/base/lang/buildfile/completion/BuiltInFunctionCompletionContributor.java
new file mode 100644
index 0000000..bf37676
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/completion/BuiltInFunctionCompletionContributor.java
@@ -0,0 +1,95 @@
+/*
+ * 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.completion;
+
+import static com.intellij.patterns.PlatformPatterns.psiComment;
+import static com.intellij.patterns.PlatformPatterns.psiElement;
+
+import com.google.idea.blaze.base.lang.buildfile.language.BuildFileLanguage;
+import com.google.idea.blaze.base.lang.buildfile.language.semantics.BuildLanguageSpec;
+import com.google.idea.blaze.base.lang.buildfile.language.semantics.BuildLanguageSpecProvider;
+import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
+import com.google.idea.blaze.base.lang.buildfile.psi.FunctionStatement;
+import com.google.idea.blaze.base.lang.buildfile.psi.ReferenceExpression;
+import com.google.idea.blaze.base.lang.buildfile.psi.StatementList;
+import com.intellij.codeInsight.completion.AutoCompletionContext;
+import com.intellij.codeInsight.completion.AutoCompletionDecision;
+import com.intellij.codeInsight.completion.CompletionContributor;
+import com.intellij.codeInsight.completion.CompletionParameters;
+import com.intellij.codeInsight.completion.CompletionProvider;
+import com.intellij.codeInsight.completion.CompletionResultSet;
+import com.intellij.codeInsight.completion.CompletionType;
+import com.intellij.codeInsight.completion.util.ParenthesesInsertHandler;
+import com.intellij.codeInsight.lookup.LookupElement;
+import com.intellij.codeInsight.lookup.LookupElementBuilder;
+import com.intellij.util.ProcessingContext;
+import icons.BlazeIcons;
+
+/** Completes built-in blaze function names. */
+public class BuiltInFunctionCompletionContributor extends CompletionContributor {
+
+  @Override
+  public AutoCompletionDecision handleAutoCompletionPossibility(AutoCompletionContext context) {
+    // auto-insert the obvious only case; else show other cases.
+    final LookupElement[] items = context.getItems();
+    if (items.length == 1) {
+      return AutoCompletionDecision.insertItem(items[0]);
+    }
+    return AutoCompletionDecision.SHOW_LOOKUP;
+  }
+
+  public BuiltInFunctionCompletionContributor() {
+    extend(
+        CompletionType.BASIC,
+        psiElement()
+            .withLanguage(BuildFileLanguage.INSTANCE)
+            .andNot(psiComment())
+            .andOr(
+                // Handles only top-level rules, and rules inside a function statement.
+                // There are several other possibilities (e.g. inside top-level list comprehension),
+                // but leaving out less common cases to avoid cluttering the autocomplete
+                // suggestions when it's not valid to enter a rule.
+                psiElement()
+                    .withParents(
+                        ReferenceExpression.class,
+                        BuildFile.class), // leaf node => BuildReference => BuildFile
+                psiElement()
+                    .inside(
+                        psiElement(StatementList.class).inside(psiElement(FunctionStatement.class)))
+                    .afterLeaf(
+                        psiElement().withText(".").afterLeaf(psiElement().withText("native")))),
+        new CompletionProvider<CompletionParameters>() {
+          @Override
+          protected void addCompletions(
+              CompletionParameters parameters,
+              ProcessingContext context,
+              CompletionResultSet result) {
+            BuildLanguageSpec spec =
+                BuildLanguageSpecProvider.getInstance()
+                    .getLanguageSpec(parameters.getPosition().getProject());
+            if (spec == null) {
+              return;
+            }
+            for (String ruleName : spec.getKnownRuleNames()) {
+              result.addElement(
+                  LookupElementBuilder.create(ruleName)
+                      .withIcon(BlazeIcons.BuildRule)
+                      .withInsertHandler(ParenthesesInsertHandler.getInstance(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
new file mode 100644
index 0000000..ba6364d
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/completion/CompletionResultsProcessor.java
@@ -0,0 +1,63 @@
+/*
+ * 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.completion;
+
+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.references.QuoteType;
+import com.intellij.codeInsight.lookup.LookupElement;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiNamedElement;
+import com.intellij.util.Processor;
+import java.util.Collection;
+import java.util.Map;
+
+/** Collects completion results, removing duplicate entries. */
+public class CompletionResultsProcessor implements Processor<BuildElement> {
+
+  private final Map<String, LookupElement> results = Maps.newHashMap();
+  private final PsiElement originalElement;
+  private final QuoteType quoteType;
+
+  public CompletionResultsProcessor(PsiElement originalElement, QuoteType quoteType) {
+    this.originalElement = originalElement;
+    this.quoteType = quoteType;
+  }
+
+  @Override
+  public boolean process(BuildElement buildElement) {
+    if (buildElement == originalElement) {
+      return true;
+    }
+    if (buildElement instanceof StringLiteral) {
+      StringLiteral literal = (StringLiteral) buildElement;
+      results.put(
+          literal.getStringContents(),
+          new StringLiteralReferenceLookupElement((StringLiteral) buildElement, quoteType));
+    } else if (buildElement instanceof PsiNamedElement) {
+      PsiNamedElement namedElement = (PsiNamedElement) buildElement;
+      results.put(
+          namedElement.getName(),
+          new NamedBuildLookupElement((PsiNamedElement) buildElement, quoteType));
+    }
+    return true;
+  }
+
+  public Collection<LookupElement> getResults() {
+    return results.values();
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/completion/FilePathLookupElement.java b/base/src/com/google/idea/blaze/base/lang/buildfile/completion/FilePathLookupElement.java
new file mode 100644
index 0000000..61d40e9
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/completion/FilePathLookupElement.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.lang.buildfile.completion;
+
+import com.google.idea.blaze.base.lang.buildfile.references.QuoteType;
+import com.intellij.openapi.util.NullableLazyValue;
+import javax.annotation.Nullable;
+import javax.swing.Icon;
+
+/** Code completion support for package paths. */
+public class FilePathLookupElement extends BuildLookupElement {
+
+  private final String itemText;
+  private final NullableLazyValue<Icon> icon;
+
+  public FilePathLookupElement(
+      String fullLabel, String itemText, QuoteType quoteWrapping, NullableLazyValue<Icon> icon) {
+    super(fullLabel, quoteWrapping);
+    this.itemText = itemText;
+    this.icon = icon;
+  }
+
+  @Override
+  protected String getItemText() {
+    return itemText;
+  }
+
+  @Nullable
+  @Override
+  public Icon getIcon() {
+    return icon.getValue();
+  }
+
+  @Override
+  protected boolean caretInsideQuotes() {
+    // after completing, leave the caret inside the closing quote, so the user can
+    // continue typing the path.
+    return true;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/completion/FilterPatterns.java b/base/src/com/google/idea/blaze/base/lang/buildfile/completion/FilterPatterns.java
new file mode 100644
index 0000000..d888efb
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/completion/FilterPatterns.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.base.lang.buildfile.completion;
+
+import static com.intellij.patterns.PlatformPatterns.psiElement;
+
+import com.intellij.patterns.ElementPattern;
+import com.intellij.patterns.PatternCondition;
+import com.intellij.psi.PsiElement;
+import com.intellij.util.ProcessingContext;
+import org.jetbrains.annotations.NotNull;
+
+/** Filter patterns used by completion contributors. */
+public class FilterPatterns {
+
+  public static final ElementPattern<PsiElement> indexInParentsChildren(final int childIndex) {
+    return psiElement()
+        .with(
+            new PatternCondition<PsiElement>("isIndexInParentsChildren") {
+              @Override
+              public boolean accepts(@NotNull PsiElement psiElement, ProcessingContext context) {
+                final PsiElement parent = psiElement.getParent();
+                if (parent != null) {
+                  final PsiElement[] children = parent.getChildren();
+                  if (childIndex < children.length && psiElement.equals(children[childIndex])) {
+                    return true;
+                  }
+                }
+                return false;
+              }
+            });
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/completion/LabelRuleLookupElement.java b/base/src/com/google/idea/blaze/base/lang/buildfile/completion/LabelRuleLookupElement.java
new file mode 100644
index 0000000..dc7dc46
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/completion/LabelRuleLookupElement.java
@@ -0,0 +1,102 @@
+/*
+ * 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.completion;
+
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.lang.buildfile.language.semantics.BuildLanguageSpec;
+import com.google.idea.blaze.base.lang.buildfile.language.semantics.BuildLanguageSpecProvider;
+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.references.LabelUtils;
+import com.google.idea.blaze.base.lang.buildfile.references.QuoteType;
+import java.util.List;
+import java.util.Objects;
+import javax.annotation.Nullable;
+import javax.swing.Icon;
+
+/**
+ * Given a label fragment containing a (possibly implicit) package path, provides a lookup element
+ * to a rule target in that package.
+ */
+public class LabelRuleLookupElement extends BuildLookupElement {
+
+  public static BuildLookupElement[] collectAllRules(
+      BuildFile file,
+      String originalString,
+      String packagePrefix,
+      @Nullable String excluded,
+      QuoteType quoteType) {
+    if (packagePrefix.startsWith("//") || originalString.startsWith(":")) {
+      packagePrefix += ":";
+    }
+
+    String ruleFragment = LabelUtils.getRuleComponent(originalString);
+    List<BuildLookupElement> lookups = Lists.newArrayList();
+    // TODO: Handle rules generated via functions? (e.g. via blaze sync)
+    BuildLanguageSpec spec =
+        BuildLanguageSpecProvider.getInstance().getLanguageSpec(file.getProject());
+    for (FuncallExpression target : file.findChildrenByClass(FuncallExpression.class)) {
+      String targetName = target.getName();
+      if (targetName == null
+          || Objects.equals(target.getName(), excluded)
+          || !targetName.startsWith(ruleFragment)) {
+        continue;
+      }
+      String ruleType = target.getFunctionName();
+      if (ruleType == null || (spec != null && !spec.hasRule(ruleType))) {
+        continue;
+      }
+      lookups.add(
+          new LabelRuleLookupElement(packagePrefix, target, targetName, ruleType, quoteType));
+    }
+    return lookups.isEmpty()
+        ? BuildLookupElement.EMPTY_ARRAY
+        : lookups.toArray(new BuildLookupElement[lookups.size()]);
+  }
+
+  private final FuncallExpression target;
+  private final String targetName;
+  private final String ruleType;
+
+  private LabelRuleLookupElement(
+      String packagePrefix,
+      FuncallExpression target,
+      String targetName,
+      String ruleType,
+      QuoteType quoteType) {
+    super(packagePrefix + targetName, quoteType);
+    this.target = target;
+    this.targetName = targetName;
+    this.ruleType = ruleType;
+
+    assert (packagePrefix.isEmpty() || packagePrefix.endsWith(":"));
+  }
+
+  @Override
+  public Icon getIcon() {
+    return target.getIcon(0);
+  }
+
+  @Override
+  protected String getTypeText() {
+    return ruleType;
+  }
+
+  @Override
+  protected String getItemText() {
+    return targetName;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/completion/NamedBuildLookupElement.java b/base/src/com/google/idea/blaze/base/lang/buildfile/completion/NamedBuildLookupElement.java
new file mode 100644
index 0000000..4b7c656
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/completion/NamedBuildLookupElement.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.base.lang.buildfile.completion;
+
+import com.google.idea.blaze.base.lang.buildfile.references.QuoteType;
+import com.intellij.psi.PsiNamedElement;
+import javax.annotation.Nullable;
+import javax.swing.Icon;
+
+/** Generic implementation for {@link com.intellij.psi.PsiNamedElement}s */
+public class NamedBuildLookupElement extends BuildLookupElement {
+
+  private final PsiNamedElement element;
+
+  public NamedBuildLookupElement(PsiNamedElement element, QuoteType quoteType) {
+    super(element.getName(), quoteType);
+    this.element = element;
+  }
+
+  @Nullable
+  @Override
+  public Icon getIcon() {
+    return element.getIcon(0);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/completion/ParameterCompletionContributor.java b/base/src/com/google/idea/blaze/base/lang/buildfile/completion/ParameterCompletionContributor.java
new file mode 100644
index 0000000..7f92201
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/completion/ParameterCompletionContributor.java
@@ -0,0 +1,58 @@
+/*
+ * 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.completion;
+
+import static com.intellij.patterns.PlatformPatterns.psiElement;
+
+import com.google.idea.blaze.base.lang.buildfile.psi.ParameterList;
+import com.intellij.codeInsight.completion.CompletionContributor;
+import com.intellij.codeInsight.completion.CompletionParameters;
+import com.intellij.codeInsight.completion.CompletionProvider;
+import com.intellij.codeInsight.completion.CompletionResultSet;
+import com.intellij.codeInsight.completion.CompletionType;
+import com.intellij.codeInsight.lookup.LookupElementBuilder;
+import com.intellij.icons.AllIcons;
+import com.intellij.util.ProcessingContext;
+
+/** {@link CompletionContributor} for starred function parameters. */
+public class ParameterCompletionContributor extends CompletionContributor {
+
+  public ParameterCompletionContributor() {
+    extend(
+        CompletionType.BASIC,
+        psiElement().inside(ParameterList.class).afterLeaf("*"),
+        new ParameterCompletionProvider("args"));
+    extend(
+        CompletionType.BASIC,
+        psiElement().inside(ParameterList.class).afterLeaf("**"),
+        new ParameterCompletionProvider("kwargs"));
+  }
+
+  private static class ParameterCompletionProvider
+      extends CompletionProvider<CompletionParameters> {
+    private String myName;
+
+    private ParameterCompletionProvider(String name) {
+      myName = name;
+    }
+
+    @Override
+    protected void addCompletions(
+        CompletionParameters parameters, ProcessingContext context, CompletionResultSet result) {
+      result.addElement(LookupElementBuilder.create(myName).withIcon(AllIcons.Nodes.Parameter));
+    }
+  }
+}
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/StringLiteralReferenceLookupElement.java
new file mode 100644
index 0000000..af85c1c
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/completion/StringLiteralReferenceLookupElement.java
@@ -0,0 +1,52 @@
+/*
+ * 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.completion;
+
+import com.google.idea.blaze.base.lang.buildfile.psi.StringLiteral;
+import com.google.idea.blaze.base.lang.buildfile.references.QuoteType;
+import com.intellij.openapi.util.NullableLazyValue;
+import com.intellij.psi.PsiElement;
+import javax.annotation.Nullable;
+import javax.swing.Icon;
+
+/**
+ * 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 {
+
+  private final StringLiteral literal;
+  private NullableLazyValue<PsiElement> referencedElement =
+      new NullableLazyValue<PsiElement>() {
+        @Nullable
+        @Override
+        protected PsiElement compute() {
+          return literal.getReferencedElement();
+        }
+      };
+
+  public StringLiteralReferenceLookupElement(StringLiteral literal, QuoteType quoteType) {
+    super(literal.getStringContents(), quoteType);
+    this.literal = literal;
+  }
+
+  @Nullable
+  @Override
+  public Icon getIcon() {
+    PsiElement ref = referencedElement.getValue();
+    return ref != null ? ref.getIcon(0) : null;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/documentation/BuildDocumentationProvider.java b/base/src/com/google/idea/blaze/base/lang/buildfile/documentation/BuildDocumentationProvider.java
new file mode 100644
index 0000000..8c96eec
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/documentation/BuildDocumentationProvider.java
@@ -0,0 +1,141 @@
+/*
+ * 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.documentation;
+
+import com.google.idea.blaze.base.lang.buildfile.language.semantics.BuildLanguageSpec;
+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.lang.buildfile.psi.DocStringOwner;
+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.ParameterList;
+import com.google.idea.blaze.base.lang.buildfile.psi.StringLiteral;
+import com.google.idea.blaze.base.settings.Blaze;
+import com.intellij.codeInsight.documentation.DocumentationManagerProtocol;
+import com.intellij.lang.documentation.AbstractDocumentationProvider;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiManager;
+import javax.annotation.Nullable;
+
+/** Provides quick docs for some BUILD elements. */
+public class BuildDocumentationProvider extends AbstractDocumentationProvider {
+
+  private static final String LINK_TYPE_FILE = "#file#";
+
+  @Nullable
+  @Override
+  public String generateDoc(PsiElement element, @Nullable PsiElement originalElement) {
+    if (element instanceof DocStringOwner) {
+      return buildDocs((DocStringOwner) element);
+    }
+    if (element instanceof FuncallExpression) {
+      return docsForBuiltInRule(
+          element.getProject(), ((FuncallExpression) element).getFunctionName());
+    }
+    return null;
+  }
+
+  /** Returns the corresponding built-in rule in the BUILD file language, if one exists. */
+  @Nullable
+  private static RuleDefinition getBuiltInRule(Project project, @Nullable String ruleName) {
+    BuildLanguageSpec spec = BuildLanguageSpecProvider.getInstance().getLanguageSpec(project);
+    return spec != null ? spec.getRule(ruleName) : null;
+  }
+
+  @Nullable
+  private static String docsForBuiltInRule(Project project, @Nullable String ruleName) {
+    RuleDefinition rule = getBuiltInRule(project, ruleName);
+    if (rule == null) {
+      return null;
+    }
+    String link = Blaze.getBuildSystemProvider(project).getRuleDocumentationUrl(rule);
+    if (link == null) {
+      return null;
+    }
+    return String.format(
+        "External documentation for %s:<br><a href=\"%s\">%s</a>", rule.name, link, link);
+  }
+
+  private static void describeFile(PsiFile file, StringBuilder builder, boolean linkToFile) {
+    if (!(file instanceof BuildFile)) {
+      return;
+    }
+    BuildFile buildFile = (BuildFile) file;
+    String name = buildFile.getBuildLabel();
+    if (name == null) {
+      // fall back to qualitative description
+      name = buildFile.getPresentableText();
+    }
+    if (linkToFile) {
+      builder
+          .append("<a href=\"")
+          .append(DocumentationManagerProtocol.PSI_ELEMENT_PROTOCOL)
+          .append(LINK_TYPE_FILE)
+          .append("\">")
+          .append(name)
+          .append("</a>");
+    } else {
+      builder.append(String.format("<b>%s</b>", name));
+    }
+    builder.append("<br><br>");
+  }
+
+  private static String buildDocs(DocStringOwner element) {
+    StringBuilder docs = new StringBuilder();
+    describeFile(element.getContainingFile(), docs, !(element instanceof BuildFile));
+
+    if (element instanceof FunctionStatement) {
+      describeFunction((FunctionStatement) element, docs);
+    }
+    StringLiteral docString = element.getDocString();
+    if (docString != null) {
+      docs.append(DocStringFormatter.formatDocString(docString, element));
+    }
+    return wrapDocInHtml(docs.toString());
+  }
+
+  private static void describeFunction(FunctionStatement function, StringBuilder builder) {
+    // just show the function declaration verbatim, including the parameter list.
+    ParameterList paramList = function.getParameterList();
+    if (paramList == null) {
+      return;
+    }
+    builder
+        .append("def ")
+        .append("<b>")
+        .append(function.getName())
+        .append("</b>")
+        .append(paramList.getNode().getChars())
+        .append("<br><br>");
+  }
+
+  private static String wrapDocInHtml(String doc) {
+    return "<html><body><code>" + doc + "</code></body></html>";
+  }
+
+  @Nullable
+  @Override
+  public PsiElement getDocumentationElementForLink(
+      PsiManager psiManager, String link, PsiElement context) {
+    if (link.equals(LINK_TYPE_FILE)) {
+      return context.getContainingFile();
+    }
+    return null;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/documentation/DocStringFormatter.java b/base/src/com/google/idea/blaze/base/lang/buildfile/documentation/DocStringFormatter.java
new file mode 100644
index 0000000..fe36a72
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/documentation/DocStringFormatter.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.lang.buildfile.documentation;
+
+import com.google.idea.blaze.base.lang.buildfile.psi.DocStringOwner;
+import com.google.idea.blaze.base.lang.buildfile.psi.StringLiteral;
+import com.intellij.openapi.util.text.StringUtil;
+
+/** Formats docstrings for use in quick doc pop-ups. */
+public class DocStringFormatter {
+
+  /** For now, present docstring almost verbatim, with a minimal indentation handling. */
+  public static String formatDocString(StringLiteral docstring, DocStringOwner owner) {
+    // TODO: Handle Google python docstring style specifically.
+    // (see https://google.github.io/styleguide/pyguide.html#Comments)
+    String raw = docstring.getStringContents();
+    String[] lines = raw.split("\n");
+    StringBuilder output = new StringBuilder();
+    output.append("<pre>");
+
+    int initialIndent = -1;
+    for (String line : lines) {
+      if (initialIndent == -1 && line.startsWith(" ")) {
+        initialIndent = StringUtil.countChars(line, ' ', 0, true);
+      }
+      line = trimStart(line, initialIndent);
+      if (!line.isEmpty()) {
+        output.append(line);
+      }
+      output.append("<br>");
+    }
+    output.append("</pre>");
+    return output.toString();
+  }
+
+  private static String trimStart(String string, int trimLimit) {
+    int index = 0;
+    while (index < trimLimit && index < string.length() && string.charAt(index) == ' ') {
+      index++;
+    }
+    return string.substring(index);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/editor/BuildEnterBetweenBracketsHandler.java b/base/src/com/google/idea/blaze/base/lang/buildfile/editor/BuildEnterBetweenBracketsHandler.java
new file mode 100644
index 0000000..e750b5a
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/editor/BuildEnterBetweenBracketsHandler.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.base.lang.buildfile.editor;
+
+import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
+import com.intellij.codeInsight.editorActions.enter.EnterBetweenBracesHandler;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.util.Ref;
+import com.intellij.psi.PsiFile;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Extends IntelliJ's {@link EnterBetweenBracesHandler}, including square brackets as a valid brace
+ * pair type.
+ */
+public class BuildEnterBetweenBracketsHandler extends EnterBetweenBracesHandler {
+  @Override
+  public Result preprocessEnter(
+      @NotNull PsiFile file,
+      @NotNull Editor editor,
+      @NotNull Ref<Integer> caretOffsetRef,
+      @NotNull Ref<Integer> caretAdvance,
+      @NotNull DataContext dataContext,
+      EditorActionHandler originalHandler) {
+    if (!(file instanceof BuildFile)) {
+      return Result.Continue;
+    }
+    return super.preprocessEnter(
+        file, editor, caretOffsetRef, caretAdvance, dataContext, originalHandler);
+  }
+
+  @Override
+  protected boolean isBracePair(char c1, char c2) {
+    return c1 == '[' && c2 == ']';
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/editor/BuildEnterHandler.java b/base/src/com/google/idea/blaze/base/lang/buildfile/editor/BuildEnterHandler.java
new file mode 100644
index 0000000..39eb5f7
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/editor/BuildEnterHandler.java
@@ -0,0 +1,273 @@
+/*
+ * 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.editor;
+
+import com.google.idea.blaze.base.lang.buildfile.lexer.BuildToken;
+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.BuildListType;
+import com.google.idea.blaze.base.lang.buildfile.psi.Parameter;
+import com.google.idea.blaze.base.lang.buildfile.psi.PassStatement;
+import com.google.idea.blaze.base.lang.buildfile.psi.ReturnStatement;
+import com.google.idea.blaze.base.lang.buildfile.psi.StatementListContainer;
+import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
+import com.intellij.codeInsight.editorActions.enter.EnterHandlerDelegateAdapter;
+import com.intellij.ide.DataManager;
+import com.intellij.injected.editor.EditorWindow;
+import com.intellij.lang.injection.InjectedLanguageManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.LogicalPosition;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actions.SplitLineAction;
+import com.intellij.openapi.util.Ref;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.psi.PsiDocumentManager;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiFileSystemItem;
+import com.intellij.psi.PsiWhiteSpace;
+import com.intellij.psi.codeStyle.CodeStyleSettings;
+import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
+import com.intellij.psi.codeStyle.CommonCodeStyleSettings.IndentOptions;
+import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
+import com.intellij.util.text.CharArrayUtil;
+import javax.annotation.Nullable;
+
+/**
+ * Inserts indents as appropriate when enter is pressed.<br>
+ * This is a substitute for implementing a full FormattingModel for the BUILD language. If we ever
+ * decide to do that, this code should be removed.
+ */
+public class BuildEnterHandler extends EnterHandlerDelegateAdapter {
+
+  @Override
+  public Result preprocessEnter(
+      PsiFile file,
+      Editor editor,
+      Ref<Integer> caretOffset,
+      Ref<Integer> caretAdvance,
+      DataContext dataContext,
+      EditorActionHandler originalHandler) {
+    int offset = caretOffset.get();
+    if (editor instanceof EditorWindow) {
+      file = InjectedLanguageManager.getInstance(file.getProject()).getTopLevelFile(file);
+      editor = InjectedLanguageUtil.getTopLevelEditor(editor);
+      offset = editor.getCaretModel().getOffset();
+    }
+    if (!isApplicable(file, dataContext)) {
+      return Result.Continue;
+    }
+
+    // Previous enter handler's (e.g. EnterBetweenBracesHandler) can introduce a mismatch
+    // between the editor's caret model and the offset we've been provided with.
+    editor.getCaretModel().moveToOffset(offset);
+
+    Document doc = editor.getDocument();
+    PsiDocumentManager.getInstance(file.getProject()).commitDocument(doc);
+
+    CodeStyleSettings currentSettings = CodeStyleSettingsManager.getSettings(file.getProject());
+    IndentOptions indentOptions = currentSettings.getIndentOptions(file.getFileType());
+
+    Integer indent = determineIndent(file, editor, offset, indentOptions);
+    if (indent == null) {
+      return Result.Continue;
+    }
+
+    removeTrailingWhitespace(doc, file, offset);
+    originalHandler.execute(editor, editor.getCaretModel().getCurrentCaret(), dataContext);
+    LogicalPosition position = editor.getCaretModel().getLogicalPosition();
+    if (position.column == indent) {
+      return Result.Stop;
+    }
+    if (position.column > indent) {
+      //default enter handler has added too many spaces -- remove them
+      int excess = position.column - indent;
+      doc.deleteString(
+          editor.getCaretModel().getOffset() - excess, editor.getCaretModel().getOffset());
+    } else if (position.column < indent) {
+      String spaces = StringUtil.repeatSymbol(' ', indent - position.column);
+      doc.insertString(editor.getCaretModel().getOffset(), spaces);
+    }
+    editor.getCaretModel().moveToLogicalPosition(new LogicalPosition(position.line, indent));
+    return Result.Stop;
+  }
+
+  private static void removeTrailingWhitespace(Document doc, PsiFile file, int offset) {
+    CharSequence chars = doc.getCharsSequence();
+    int start = offset;
+    while (offset < chars.length() && chars.charAt(offset) == ' ') {
+      PsiElement element = file.findElementAt(offset);
+      if (element == null || !(element instanceof PsiWhiteSpace)) {
+        break;
+      }
+      offset++;
+    }
+    if (start != offset) {
+      doc.deleteString(start, offset);
+    }
+  }
+
+  private static boolean isApplicable(PsiFile file, DataContext dataContext) {
+    if (!(file instanceof BuildFile)) {
+      return false;
+    }
+    Boolean isSplitLine =
+        DataManager.getInstance().loadFromDataContext(dataContext, SplitLineAction.SPLIT_LINE_KEY);
+    if (isSplitLine != null) {
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * Returns null if an appropriate indent cannot be found. In that case we do nothing, and pass it
+   * along to the next EnterHandler.
+   */
+  @Nullable
+  private static Integer determineIndent(
+      PsiFile file, Editor editor, int offset, IndentOptions indentOptions) {
+    if (offset == 0) {
+      return null;
+    }
+    Document doc = editor.getDocument();
+    PsiElement element = getRelevantElement(file, doc, offset);
+    PsiElement parent = element != null ? element.getParent() : null;
+    if (parent == null) {
+      return null;
+    }
+    if (endsBlock(element)) {
+      // current line indent subtract block indent
+      return Math.max(0, getIndent(doc, element) - indentOptions.INDENT_SIZE);
+    }
+
+    if (parent instanceof BuildListType) {
+      BuildListType list = (BuildListType) parent;
+      if (endsList(list, element) && element.getTextOffset() < offset) {
+        return null;
+      }
+      int listOffset = list.getStartOffset();
+      LogicalPosition caretPosition = editor.getCaretModel().getLogicalPosition();
+      LogicalPosition listStart = editor.offsetToLogicalPosition(listOffset);
+      if (listStart.line != caretPosition.line) {
+        // take the minimum of the current line's indent and the current caret position
+        return indentOfLineUpToCaret(doc, caretPosition.line, offset);
+      }
+      BuildElement firstChild = ((BuildListType) parent).getFirstElement();
+      if (firstChild != null && firstChild.getNode().getStartOffset() < offset) {
+        return getIndent(doc, firstChild);
+      }
+      return lineIndent(doc, listStart.line) + additionalIndent(parent, indentOptions);
+    }
+    if (parent instanceof StatementListContainer && afterColon(doc, offset)) {
+      return getIndent(doc, parent) + additionalIndent(parent, indentOptions);
+    }
+    return null;
+  }
+
+  private static int additionalIndent(PsiElement parent, IndentOptions indentOptions) {
+    return parent instanceof StatementListContainer
+        ? indentOptions.INDENT_SIZE
+        : indentOptions.CONTINUATION_INDENT_SIZE;
+  }
+
+  private static int lineIndent(Document doc, int line) {
+    int startOffset = doc.getLineStartOffset(line);
+    int indentOffset = CharArrayUtil.shiftForward(doc.getCharsSequence(), startOffset, " \t");
+    return indentOffset - startOffset;
+  }
+
+  private static int getIndent(Document doc, PsiElement element) {
+    int offset = element.getNode().getStartOffset();
+    int lineNumber = doc.getLineNumber(offset);
+    return offset - doc.getLineStartOffset(lineNumber);
+  }
+
+  private static int indentOfLineUpToCaret(Document doc, int line, int caretOffset) {
+    int startOffset = doc.getLineStartOffset(line);
+    int indentOffset = CharArrayUtil.shiftForward(doc.getCharsSequence(), startOffset, " \t");
+    return Math.min(indentOffset, caretOffset) - startOffset;
+  }
+
+  private static boolean endsList(BuildListType list, PsiElement element) {
+    String text = element.getText();
+    return text.length() == 1 && list.getEndChars().contains(text.charAt(0));
+  }
+
+  private static boolean endsBlock(PsiElement element) {
+    return element instanceof ReturnStatement || element instanceof PassStatement;
+  }
+
+  private static PsiElement getBlockEndingParent(PsiElement element) {
+    while (element != null && !(element instanceof PsiFileSystemItem)) {
+      if (endsBlock(element)) {
+        return element;
+      }
+      element = element.getParent();
+    }
+    return null;
+  }
+
+  @Nullable
+  private static PsiElement getRelevantElement(PsiFile file, Document doc, int offset) {
+    if (offset == 0) {
+      return null;
+    }
+    if (offset == doc.getTextLength()) {
+      offset--;
+    }
+    PsiElement element = file.findElementAt(offset);
+    while (element != null && isWhiteSpace(element)) {
+      element = PsiUtils.getPreviousNodeInTree(element);
+    }
+    PsiElement blockTerminator = getBlockEndingParent(element);
+    if (blockTerminator != null
+        && blockTerminator.getTextRange().getEndOffset() == element.getTextRange().getEndOffset()) {
+      return blockTerminator;
+    }
+    while (element != null && skipElement(element, offset)) {
+      element = element.getParent();
+    }
+    return element;
+  }
+
+  private static boolean isWhiteSpace(PsiElement element) {
+    if (element instanceof PsiWhiteSpace) {
+      return true;
+    }
+    return BuildToken.WHITESPACE_AND_NEWLINE.contains(element.getNode().getElementType());
+  }
+
+  private static boolean skipElement(PsiElement element, int offset) {
+    PsiElement parent = element.getParent();
+    if (parent == null || parent.getNode() == null || parent instanceof PsiFileSystemItem) {
+      return false;
+    }
+    TextRange childRange = element.getNode().getTextRange();
+    return childRange.equals(parent.getNode().getTextRange())
+        || childRange.getStartOffset() == offset
+            && (parent instanceof Argument || parent instanceof Parameter);
+  }
+
+  private static boolean afterColon(Document doc, int offset) {
+    CharSequence text = doc.getCharsSequence();
+    int previousOffset = CharArrayUtil.shiftBackward(text, offset - 1, " \t");
+    return text.charAt(previousOffset) == ':';
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/editor/BuildQuoteHandler.java b/base/src/com/google/idea/blaze/base/lang/buildfile/editor/BuildQuoteHandler.java
new file mode 100644
index 0000000..c47f707
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/editor/BuildQuoteHandler.java
@@ -0,0 +1,140 @@
+/*
+ * 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.editor;
+
+import com.google.idea.blaze.base.lang.buildfile.lexer.BuildToken;
+import com.google.idea.blaze.base.lang.buildfile.lexer.TokenKind;
+import com.intellij.codeInsight.editorActions.MultiCharQuoteHandler;
+import com.intellij.codeInsight.editorActions.SimpleTokenSetQuoteHandler;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.highlighter.HighlighterIterator;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.psi.tree.IElementType;
+import javax.annotation.Nullable;
+
+/** Provides quote auto-closing support. */
+public class BuildQuoteHandler extends SimpleTokenSetQuoteHandler implements MultiCharQuoteHandler {
+
+  public BuildQuoteHandler() {
+    super(BuildToken.fromKind(TokenKind.STRING));
+  }
+
+  @Override
+  public boolean isOpeningQuote(HighlighterIterator iterator, int offset) {
+    if (!myLiteralTokenSet.contains(iterator.getTokenType())) {
+      return false;
+    }
+    int start = iterator.getStart();
+    if (offset == start) {
+      return true;
+    }
+    final Document document = iterator.getDocument();
+    if (document == null) {
+      return false;
+    }
+    CharSequence text = document.getCharsSequence();
+    char theQuote = text.charAt(offset);
+    if (offset >= 2
+        && text.charAt(offset - 1) == theQuote
+        && text.charAt(offset - 2) == theQuote
+        && (offset < 3 || text.charAt(offset - 3) != theQuote)) {
+      if (super.isOpeningQuote(iterator, offset)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private static int getLiteralStartOffset(CharSequence text, int start) {
+    char c = Character.toUpperCase(text.charAt(start));
+    if (c == 'U' || c == 'B') {
+      start++;
+      c = Character.toUpperCase(text.charAt(start));
+    }
+    if (c == 'R') {
+      start++;
+    }
+    return start;
+  }
+
+  @Override
+  protected boolean isNonClosedLiteral(HighlighterIterator iterator, CharSequence chars) {
+    int end = iterator.getEnd();
+    if (getLiteralStartOffset(chars, iterator.getStart()) >= end - 1) {
+      return true;
+    }
+    char endSymbol = chars.charAt(end - 1);
+    if (endSymbol != '"' && endSymbol != '\'') {
+      return true;
+    }
+
+    //for triple quoted string
+    if (end >= 3
+        && (endSymbol == chars.charAt(end - 2))
+        && (chars.charAt(end - 2) == chars.charAt(end - 3))
+        && (end < 4 || chars.charAt(end - 4) != endSymbol)) {
+      return true;
+    }
+
+    return false;
+  }
+
+  @Override
+  public boolean isClosingQuote(HighlighterIterator iterator, int offset) {
+    final IElementType tokenType = iterator.getTokenType();
+    if (!myLiteralTokenSet.contains(tokenType)) {
+      return false;
+    }
+    int start = iterator.getStart();
+    int end = iterator.getEnd();
+    if (end - start >= 1 && offset == end - 1) {
+      return true; // single quote
+    }
+    if (end - start < 3 || offset < end - 3) {
+      return false;
+    }
+    // check for triple quote
+    Document doc = iterator.getDocument();
+    if (doc == null) {
+      return false;
+    }
+    CharSequence chars = doc.getCharsSequence();
+    char quote = chars.charAt(start);
+    boolean tripleQuote = quote == chars.charAt(start + 1) && quote == chars.charAt(start + 2);
+    if (!tripleQuote) {
+      return false;
+    }
+    for (int i = offset; i < Math.min(offset + 2, end); i++) {
+      if (quote != chars.charAt(i)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  @Nullable
+  @Override
+  public CharSequence getClosingQuote(HighlighterIterator iterator, int offset) {
+    char theQuote = iterator.getDocument().getCharsSequence().charAt(offset - 1);
+    if (super.isOpeningQuote(iterator, offset - 1)) {
+      return String.valueOf(theQuote);
+    }
+    if (super.isOpeningQuote(iterator, offset - 3)) {
+      return StringUtil.repeat(String.valueOf(theQuote), 3);
+    }
+    return null;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/findusages/BuildElementDescriptionProvider.java b/base/src/com/google/idea/blaze/base/lang/buildfile/findusages/BuildElementDescriptionProvider.java
new file mode 100644
index 0000000..a5e8984
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/findusages/BuildElementDescriptionProvider.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.base.lang.buildfile.findusages;
+
+import com.google.idea.blaze.base.lang.buildfile.psi.BuildElement;
+import com.intellij.psi.ElementDescriptionLocation;
+import com.intellij.psi.ElementDescriptionProvider;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiNameIdentifierOwner;
+import com.intellij.usageView.UsageViewLongNameLocation;
+import com.intellij.usageView.UsageViewShortNameLocation;
+import javax.annotation.Nullable;
+
+/** Controls text shown for target in the 'find usages' dialog. */
+public class BuildElementDescriptionProvider implements ElementDescriptionProvider {
+  @Nullable
+  @Override
+  public String getElementDescription(PsiElement element, ElementDescriptionLocation location) {
+    if (!(element instanceof BuildElement)) {
+      return null;
+    }
+    if (location instanceof UsageViewLongNameLocation) {
+      return ((BuildElement) element).getPresentableText();
+    }
+    if (location instanceof UsageViewShortNameLocation) {
+      if (element instanceof PsiNameIdentifierOwner) {
+        // this is used by rename operations, so needs to be accurate
+        return ((PsiNameIdentifierOwner) element).getName();
+      }
+      if (element instanceof PsiFile) {
+        return ((PsiFile) element).getName();
+      }
+      return ((BuildElement) element).getPresentableText();
+    }
+    return null;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/findusages/BuildFileGroupingRuleProvider.java b/base/src/com/google/idea/blaze/base/lang/buildfile/findusages/BuildFileGroupingRuleProvider.java
new file mode 100644
index 0000000..84d16cb
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/findusages/BuildFileGroupingRuleProvider.java
@@ -0,0 +1,92 @@
+/*
+ * 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.findusages;
+
+import com.google.idea.blaze.base.lang.buildfile.language.BuildFileType;
+import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.usages.Usage;
+import com.intellij.usages.UsageGroup;
+import com.intellij.usages.UsageView;
+import com.intellij.usages.impl.FileStructureGroupRuleProvider;
+import com.intellij.usages.impl.rules.FileGroupingRule;
+import com.intellij.usages.rules.UsageGroupingRule;
+import com.intellij.usages.rules.UsageInFile;
+import javax.swing.Icon;
+
+/**
+ * Allows us to customize the filename string in the 'find usages' dialog, rather than displaying
+ * them all as 'BUILD'.
+ */
+public class BuildFileGroupingRuleProvider implements FileStructureGroupRuleProvider {
+
+  public static UsageGroupingRule getGroupingRule(Project project) {
+    return new BuildFileGroupingRule(project);
+  }
+
+  @Override
+  public UsageGroupingRule getUsageGroupingRule(Project project) {
+    return getGroupingRule(project);
+  }
+
+  private static class BuildFileGroupingRule extends FileGroupingRule {
+
+    private final Project project;
+
+    BuildFileGroupingRule(Project project) {
+      super(project);
+      this.project = project;
+    }
+
+    @Override
+    public UsageGroup groupUsage(Usage usage) {
+      if (!(usage instanceof UsageInFile)) {
+        return null;
+      }
+      final VirtualFile virtualFile = ((UsageInFile) usage).getFile();
+      if (virtualFile.getFileType() != BuildFileType.INSTANCE) {
+        return null;
+      }
+      return new FileUsageGroup(project, virtualFile) {
+        String name;
+
+        @Override
+        public void update() {
+          if (isValid()) {
+            super.update();
+            name = BuildFile.getBuildFileString(project, virtualFile.getPath());
+          }
+        }
+
+        @Override
+        public String getPresentableName() {
+          return name;
+        }
+
+        @Override
+        public String getText(UsageView view) {
+          return name;
+        }
+
+        @Override
+        public Icon getIcon(boolean isOpen) {
+          return null; // already shown by default usage group (which we can't remove...)
+        }
+      };
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/findusages/BuildFindUsagesProvider.java b/base/src/com/google/idea/blaze/base/lang/buildfile/findusages/BuildFindUsagesProvider.java
new file mode 100644
index 0000000..31eac32
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/findusages/BuildFindUsagesProvider.java
@@ -0,0 +1,110 @@
+/*
+ * 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.findusages;
+
+import com.google.idea.blaze.base.lang.buildfile.lexer.BuildLexer;
+import com.google.idea.blaze.base.lang.buildfile.lexer.BuildLexerBase.LexerMode;
+import com.google.idea.blaze.base.lang.buildfile.lexer.BuildToken;
+import com.google.idea.blaze.base.lang.buildfile.lexer.TokenKind;
+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.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.intellij.lang.HelpID;
+import com.intellij.lang.cacheBuilder.DefaultWordsScanner;
+import com.intellij.lang.cacheBuilder.WordsScanner;
+import com.intellij.lang.findUsages.FindUsagesProvider;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiNamedElement;
+import com.intellij.psi.tree.TokenSet;
+
+/**
+ * Required for highlighting references (among other things we don't currently support). Currently
+ * only used by the fallback 'DefaultFindUsagesHandlerFactory'.
+ */
+public class BuildFindUsagesProvider implements FindUsagesProvider {
+
+  @Override
+  public boolean canFindUsagesFor(PsiElement psiElement) {
+    return psiElement instanceof FuncallExpression
+        || psiElement instanceof PsiNamedElement
+        || psiElement instanceof ReferenceExpression;
+  }
+
+  @Override
+  public String getHelpId(PsiElement psiElement) {
+    if (psiElement instanceof FunctionStatement) {
+      return "reference.dialogs.findUsages.method";
+    }
+    if (psiElement instanceof TargetExpression
+        || psiElement instanceof Parameter
+        || psiElement instanceof ReferenceExpression) {
+      return "reference.dialogs.findUsages.variable";
+    }
+    // typically build rules and imported Skylark functions, but also all other function calls
+    return HelpID.FIND_OTHER_USAGES;
+  }
+
+  @Override
+  public String getType(PsiElement element) {
+    if (element instanceof FunctionStatement) {
+      return "function";
+    }
+    if (element instanceof Parameter) {
+      return "parameter";
+    }
+    if (element instanceof ReferenceExpression || element instanceof TargetExpression) {
+      return "variable";
+    }
+    if (element instanceof Argument.Keyword) {
+      return "keyword argument";
+    }
+    if (element instanceof FuncallExpression) {
+      return "rule";
+    }
+    return "";
+  }
+
+  /** Controls text shown for target element in the 'find usages' dialog */
+  @Override
+  public String getDescriptiveName(PsiElement element) {
+    if (element instanceof BuildElement) {
+      return ((BuildElement) element).getPresentableText();
+    }
+    return element.toString();
+  }
+
+  @Override
+  public String getNodeText(PsiElement element, boolean useFullName) {
+    return getDescriptiveName(element);
+  }
+
+  @Override
+  public WordsScanner getWordsScanner() {
+    return new DefaultWordsScanner(
+        new BuildLexer(LexerMode.SyntaxHighlighting),
+        tokenSet(TokenKind.IDENTIFIER),
+        tokenSet(TokenKind.COMMENT),
+        tokenSet(TokenKind.STRING));
+  }
+
+  private static TokenSet tokenSet(TokenKind token) {
+    return TokenSet.create(BuildToken.fromKind(token));
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/findusages/BuildReadWriteAccessDetector.java b/base/src/com/google/idea/blaze/base/lang/buildfile/findusages/BuildReadWriteAccessDetector.java
new file mode 100644
index 0000000..8f01178
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/findusages/BuildReadWriteAccessDetector.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.lang.buildfile.findusages;
+
+import com.google.idea.blaze.base.lang.buildfile.psi.AugmentedAssignmentStatement;
+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.intellij.codeInsight.highlighting.ReadWriteAccessDetector;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiReference;
+
+/** Used by find usages tools. */
+public class BuildReadWriteAccessDetector extends ReadWriteAccessDetector {
+  @Override
+  public boolean isReadWriteAccessible(PsiElement element) {
+    return element instanceof TargetExpression || element instanceof ReferenceExpression;
+  }
+
+  @Override
+  public boolean isDeclarationWriteAccess(PsiElement element) {
+    return element instanceof TargetExpression;
+  }
+
+  @Override
+  public Access getReferenceAccess(PsiElement referencedElement, PsiReference reference) {
+    return getExpressionAccess(reference.getElement());
+  }
+
+  @Override
+  public Access getExpressionAccess(PsiElement expression) {
+    if (isDeclarationWriteAccess(expression)) {
+      return Access.Write;
+    }
+    if (expression instanceof ReferenceExpression) {
+      if (PsiUtils.getParentOfType(expression, AugmentedAssignmentStatement.class) != null) {
+        return Access.ReadWrite;
+      }
+    }
+    return Access.Read;
+  }
+}
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
new file mode 100644
index 0000000..bc96e3d
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/findusages/BuildTargetElementEvaluator.java
@@ -0,0 +1,117 @@
+/*
+ * 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.findusages;
+
+import com.google.idea.blaze.base.lang.buildfile.psi.Argument.Keyword;
+import com.google.idea.blaze.base.lang.buildfile.psi.ArgumentList;
+import com.google.idea.blaze.base.lang.buildfile.psi.FuncallExpression;
+import com.google.idea.blaze.base.lang.buildfile.psi.StringLiteral;
+import com.intellij.codeInsight.TargetElementEvaluatorEx2;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiReference;
+import com.intellij.psi.impl.source.resolve.reference.impl.PsiMultiReference;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Objects;
+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).
+ */
+public class BuildTargetElementEvaluator extends TargetElementEvaluatorEx2 {
+
+  @Override
+  public boolean includeSelfInGotoImplementation(PsiElement element) {
+    return false;
+  }
+
+  /** Returns null in the cases where we're happy with the default behavior. */
+  @Nullable
+  @Override
+  public PsiElement getElementByReference(PsiReference ref, int flags) {
+    if (!(ref instanceof PsiMultiReference) || !(ref.getElement() instanceof StringLiteral)) {
+      return null;
+    }
+    // choose the outer-most reference
+    PsiReference[] refs = ((PsiMultiReference) ref).getReferences().clone();
+    Arrays.sort(refs, COMPARATOR);
+    return refs[0].resolve();
+  }
+
+  private static final Comparator<PsiReference> COMPARATOR =
+      new Comparator<PsiReference>() {
+        @Override
+        public int compare(final PsiReference ref1, final PsiReference ref2) {
+          boolean resolves1 = ref1.resolve() != null;
+          boolean resolves2 = ref2.resolve() != null;
+          if (resolves1 && !resolves2) {
+            return -1;
+          }
+          if (!resolves1 && resolves2) {
+            return 1;
+          }
+
+          final TextRange range1 = ref1.getRangeInElement();
+          final TextRange range2 = ref2.getRangeInElement();
+
+          if (TextRange.areSegmentsEqual(range1, range2)) {
+            return 0;
+          }
+          if (range1.getStartOffset() >= range2.getStartOffset()
+              && range1.getEndOffset() <= range2.getEndOffset()) {
+            return 1;
+          }
+          if (range2.getStartOffset() >= range1.getStartOffset()
+              && range2.getEndOffset() <= range1.getEndOffset()) {
+            return -1;
+          }
+
+          return 0;
+        }
+      };
+
+  /** Redirect 'name' funcall argument values to the funcall expression (b/29088829). */
+  @Nullable
+  @Override
+  public PsiElement getNamedElement(PsiElement element) {
+    return getParentFuncallIfNameString(element);
+  }
+
+  @Nullable
+  private static FuncallExpression getParentFuncallIfNameString(PsiElement element) {
+    PsiElement parent = element.getParent();
+    if (!(parent instanceof StringLiteral)) {
+      return null;
+    }
+    parent = parent.getParent();
+    if (!(parent instanceof Keyword)) {
+      return null;
+    }
+    if (!Objects.equals(((Keyword) parent).getName(), "name")) {
+      return null;
+    }
+    parent = parent.getParent();
+    if (!(parent instanceof ArgumentList)) {
+      return null;
+    }
+    parent = parent.getParent();
+    return parent instanceof FuncallExpression ? (FuncallExpression) parent : null;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/findusages/BuildUsageGroupingRuleProvider.java b/base/src/com/google/idea/blaze/base/lang/buildfile/findusages/BuildUsageGroupingRuleProvider.java
new file mode 100644
index 0000000..265202c
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/findusages/BuildUsageGroupingRuleProvider.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.base.lang.buildfile.findusages;
+
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.usages.UsageView;
+import com.intellij.usages.rules.UsageGroupingRule;
+import com.intellij.usages.rules.UsageGroupingRuleProvider;
+
+/**
+ * This is a gross hack. We want to always include file paths for BUILD files in the 'find usages'
+ * dialog.<br>
+ * This achieves that by inserting an additional UsageGroupingRule for each file usage, regardless
+ * of whether we're grouping by file structure
+ */
+public class BuildUsageGroupingRuleProvider implements UsageGroupingRuleProvider {
+  @Override
+  public UsageGroupingRule[] getActiveRules(Project project) {
+    return new UsageGroupingRule[] {BuildFileGroupingRuleProvider.getGroupingRule(project)};
+  }
+
+  @Override
+  public AnAction[] createGroupingActions(UsageView view) {
+    return new AnAction[0];
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/formatting/BuildBraceMatcher.java b/base/src/com/google/idea/blaze/base/lang/buildfile/formatting/BuildBraceMatcher.java
new file mode 100644
index 0000000..10d7edd
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/formatting/BuildBraceMatcher.java
@@ -0,0 +1,75 @@
+/*
+ * 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.formatting;
+
+import com.google.idea.blaze.base.lang.buildfile.lexer.BuildToken;
+import com.google.idea.blaze.base.lang.buildfile.lexer.TokenKind;
+import com.intellij.lang.BracePair;
+import com.intellij.lang.PairedBraceMatcher;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.tree.IElementType;
+import com.intellij.psi.tree.TokenSet;
+import java.util.Arrays;
+import javax.annotation.Nullable;
+
+/**
+ * This adds a close brace automatically once an opening brace is typed by the user in the editor.
+ */
+public class BuildBraceMatcher implements PairedBraceMatcher {
+
+  private static final BracePair[] PAIRS =
+      new BracePair[] {
+        new BracePair(
+            BuildToken.fromKind(TokenKind.LPAREN), BuildToken.fromKind(TokenKind.RPAREN), true),
+        new BracePair(
+            BuildToken.fromKind(TokenKind.LBRACKET), BuildToken.fromKind(TokenKind.RBRACKET), true),
+        new BracePair(
+            BuildToken.fromKind(TokenKind.LBRACE), BuildToken.fromKind(TokenKind.RBRACE), true)
+      };
+
+  private static final TokenSet BRACES_ALLOWED_BEFORE =
+      tokenSet(
+          TokenKind.NEWLINE,
+          TokenKind.WHITESPACE,
+          TokenKind.COMMENT,
+          TokenKind.COLON,
+          TokenKind.COMMA,
+          TokenKind.RPAREN,
+          TokenKind.RBRACKET,
+          TokenKind.RBRACE,
+          TokenKind.LBRACE);
+
+  @Override
+  public BracePair[] getPairs() {
+    return PAIRS;
+  }
+
+  @Override
+  public boolean isPairedBracesAllowedBeforeType(
+      IElementType lbraceType, @Nullable IElementType contextType) {
+    return contextType == null || BRACES_ALLOWED_BEFORE.contains(contextType);
+  }
+
+  @Override
+  public int getCodeConstructStart(PsiFile file, int openingBraceOffset) {
+    return openingBraceOffset;
+  }
+
+  private static TokenSet tokenSet(TokenKind... kind) {
+    return TokenSet.create(
+        Arrays.stream(kind).map(BuildToken::fromKind).toArray(IElementType[]::new));
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/formatting/BuildCodeStyleSettingsProvider.java b/base/src/com/google/idea/blaze/base/lang/buildfile/formatting/BuildCodeStyleSettingsProvider.java
new file mode 100644
index 0000000..a61a0bd
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/formatting/BuildCodeStyleSettingsProvider.java
@@ -0,0 +1,62 @@
+/*
+ * 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.formatting;
+
+import com.google.idea.blaze.base.lang.buildfile.language.BuildFileLanguage;
+import com.google.idea.blaze.base.lang.buildfile.language.BuildFileType;
+import com.intellij.application.options.CodeStyleAbstractConfigurable;
+import com.intellij.application.options.CodeStyleAbstractPanel;
+import com.intellij.application.options.TabbedLanguageCodeStylePanel;
+import com.intellij.openapi.options.Configurable;
+import com.intellij.psi.codeStyle.CodeStyleSettings;
+import com.intellij.psi.codeStyle.CodeStyleSettingsProvider;
+import com.intellij.psi.codeStyle.LanguageCodeStyleSettingsProvider;
+import javax.annotation.Nullable;
+
+/** Separate configurable code-style settings for BUILD language. */
+public class BuildCodeStyleSettingsProvider extends CodeStyleSettingsProvider {
+
+  @Override
+  public Configurable createSettingsPage(
+      CodeStyleSettings settings, CodeStyleSettings originalSettings) {
+    return new CodeStyleAbstractConfigurable(
+        settings, originalSettings, BuildFileType.INSTANCE.getDescription()) {
+      @Override
+      protected CodeStyleAbstractPanel createPanel(final CodeStyleSettings settings) {
+        return new TabbedLanguageCodeStylePanel(
+            BuildFileLanguage.INSTANCE, getCurrentSettings(), settings) {
+          @Override
+          protected void initTabs(CodeStyleSettings settings) {
+            LanguageCodeStyleSettingsProvider provider =
+                LanguageCodeStyleSettingsProvider.forLanguage(getDefaultLanguage());
+            addIndentOptionsTab(settings);
+          }
+        };
+      }
+
+      @Override
+      public String getHelpTopic() {
+        return null;
+      }
+    };
+  }
+
+  @Nullable
+  @Override
+  public String getConfigurableDisplayName() {
+    return BuildFileType.INSTANCE.getDescription();
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/formatting/BuildCommenter.java b/base/src/com/google/idea/blaze/base/lang/buildfile/formatting/BuildCommenter.java
new file mode 100644
index 0000000..34af8b9
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/formatting/BuildCommenter.java
@@ -0,0 +1,92 @@
+/*
+ * 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.formatting;
+
+import com.google.idea.blaze.base.lang.buildfile.lexer.BuildToken;
+import com.google.idea.blaze.base.lang.buildfile.lexer.TokenKind;
+import com.intellij.lang.CodeDocumentationAwareCommenter;
+import com.intellij.psi.PsiComment;
+import com.intellij.psi.tree.IElementType;
+import org.jetbrains.annotations.Nullable;
+
+/** Supports (un)commenting lines via IntelliJ */
+public class BuildCommenter implements CodeDocumentationAwareCommenter {
+
+  @Nullable
+  @Override
+  public String getLineCommentPrefix() {
+    return "#";
+  }
+
+  @Nullable
+  @Override
+  public String getBlockCommentPrefix() {
+    return null;
+  }
+
+  @Nullable
+  @Override
+  public String getBlockCommentSuffix() {
+    return null;
+  }
+
+  @Nullable
+  @Override
+  public String getCommentedBlockCommentPrefix() {
+    return null;
+  }
+
+  @Nullable
+  @Override
+  public String getCommentedBlockCommentSuffix() {
+    return null;
+  }
+
+  @Override
+  public IElementType getLineCommentTokenType() {
+    return BuildToken.fromKind(TokenKind.COMMENT);
+  }
+
+  @Override
+  public IElementType getBlockCommentTokenType() {
+    return null;
+  }
+
+  @Override
+  public IElementType getDocumentationCommentTokenType() {
+    return null;
+  }
+
+  @Override
+  public String getDocumentationCommentPrefix() {
+    return null;
+  }
+
+  @Override
+  public String getDocumentationCommentLinePrefix() {
+    return null;
+  }
+
+  @Override
+  public String getDocumentationCommentSuffix() {
+    return null;
+  }
+
+  @Override
+  public boolean isDocumentationComment(PsiComment element) {
+    return false;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/formatting/BuildFileFoldingBuilder.java b/base/src/com/google/idea/blaze/base/lang/buildfile/formatting/BuildFileFoldingBuilder.java
new file mode 100644
index 0000000..f627347
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/formatting/BuildFileFoldingBuilder.java
@@ -0,0 +1,140 @@
+/*
+ * 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.formatting;
+
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.lang.buildfile.lexer.BuildToken;
+import com.google.idea.blaze.base.lang.buildfile.lexer.TokenKind;
+import com.google.idea.blaze.base.lang.buildfile.psi.BuildElementTypes;
+import com.google.idea.blaze.base.lang.buildfile.psi.FuncallExpression;
+import com.google.idea.blaze.base.lang.buildfile.psi.LoadStatement;
+import com.intellij.lang.ASTNode;
+import com.intellij.lang.FileASTNode;
+import com.intellij.lang.folding.FoldingBuilder;
+import com.intellij.lang.folding.FoldingDescriptor;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.TokenType;
+import com.intellij.psi.tree.IElementType;
+import java.util.List;
+import javax.annotation.Nullable;
+
+/** Simple code block folding for BUILD files. */
+public class BuildFileFoldingBuilder implements FoldingBuilder {
+
+  /** Currently only folding top-level nodes. */
+  @Override
+  public FoldingDescriptor[] buildFoldRegions(ASTNode node, Document document) {
+    List<FoldingDescriptor> descriptors = Lists.newArrayList();
+    if (node instanceof FileASTNode) {
+      for (ASTNode child : node.getChildren(null)) {
+        addDescriptors(descriptors, child);
+      }
+    } else if (isTopLevel(node)) {
+      addDescriptors(descriptors, node);
+    }
+    return descriptors.toArray(new FoldingDescriptor[0]);
+  }
+
+  /** Only folding top-level statements */
+  private void addDescriptors(List<FoldingDescriptor> descriptors, ASTNode node) {
+    IElementType type = node.getElementType();
+    if (type == BuildElementTypes.FUNCTION_STATEMENT) {
+      ASTNode colon = node.findChildByType(BuildToken.fromKind(TokenKind.COLON));
+      if (colon == null) {
+        return;
+      }
+      ASTNode stmtList = node.findChildByType(BuildElementTypes.STATEMENT_LIST);
+      if (stmtList == null) {
+        return;
+      }
+      int start = colon.getStartOffset() + 1;
+      int end = endOfList(stmtList);
+      descriptors.add(new FoldingDescriptor(node, range(start, end)));
+
+    } else if (type == BuildElementTypes.FUNCALL_EXPRESSION
+        || type == BuildElementTypes.LOAD_STATEMENT) {
+      ASTNode listNode =
+          type == BuildElementTypes.FUNCALL_EXPRESSION
+              ? node.findChildByType(BuildElementTypes.ARGUMENT_LIST)
+              : node;
+      if (listNode == null) {
+        return;
+      }
+      ASTNode lParen = listNode.findChildByType(BuildToken.fromKind(TokenKind.LPAREN));
+      ASTNode rParen = listNode.findChildByType(BuildToken.fromKind(TokenKind.RPAREN));
+      if (lParen == null || rParen == null) {
+        return;
+      }
+      int start = lParen.getStartOffset() + 1;
+      int end = rParen.getTextRange().getEndOffset() - 1;
+      descriptors.add(new FoldingDescriptor(node, range(start, end)));
+    }
+  }
+
+  private static TextRange range(int start, int end) {
+    if (start >= end) {
+      return new TextRange(start, start + 1);
+    }
+    return new TextRange(start, end);
+  }
+
+  /**
+   * Don't include whitespace and newlines at the end of the function.<br>
+   * Could do this in the lexer instead, with additional look-ahead checks.
+   */
+  private int endOfList(ASTNode stmtList) {
+    ASTNode child = stmtList.getLastChildNode();
+    while (child != null) {
+      IElementType type = child.getElementType();
+      if (type != TokenType.WHITE_SPACE && type != BuildToken.fromKind(TokenKind.NEWLINE)) {
+        return child.getTextRange().getEndOffset();
+      }
+      child = child.getTreePrev();
+    }
+    return stmtList.getTextRange().getEndOffset();
+  }
+
+  private boolean isTopLevel(ASTNode node) {
+    return node.getTreeParent() instanceof FileASTNode;
+  }
+
+  @Override
+  @Nullable
+  public String getPlaceholderText(ASTNode node) {
+    PsiElement psi = node.getPsi();
+    if (psi instanceof FuncallExpression) {
+      FuncallExpression expr = (FuncallExpression) psi;
+      String name = expr.getNameArgumentValue();
+      if (name != null) {
+        return "name = \"" + name + "\"...";
+      }
+    }
+    if (psi instanceof LoadStatement) {
+      String fileName = ((LoadStatement) psi).getImportedPath();
+      if (fileName != null) {
+        return "\"" + fileName + "\"...";
+      }
+    }
+    return "...";
+  }
+
+  @Override
+  public boolean isCollapsedByDefault(ASTNode node) {
+    return false;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/formatting/BuildLanguageCodeStyleSettingsProvider.java b/base/src/com/google/idea/blaze/base/lang/buildfile/formatting/BuildLanguageCodeStyleSettingsProvider.java
new file mode 100644
index 0000000..b2295af
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/formatting/BuildLanguageCodeStyleSettingsProvider.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.lang.buildfile.formatting;
+
+import com.google.idea.blaze.base.lang.buildfile.language.BuildFileLanguage;
+import com.intellij.application.options.IndentOptionsEditor;
+import com.intellij.application.options.SmartIndentOptionsEditor;
+import com.intellij.lang.Language;
+import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
+import com.intellij.psi.codeStyle.LanguageCodeStyleSettingsProvider;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/** Allows BUILD language-specific code style settings */
+public class BuildLanguageCodeStyleSettingsProvider extends LanguageCodeStyleSettingsProvider {
+
+  @Override
+  public Language getLanguage() {
+    return BuildFileLanguage.INSTANCE;
+  }
+
+  @Override
+  public IndentOptionsEditor getIndentOptionsEditor() {
+    return new SmartIndentOptionsEditor();
+  }
+
+  @Override
+  public String getCodeSample(@NotNull SettingsType settingsType) {
+    return "";
+  }
+
+  @Nullable
+  @Override
+  public CommonCodeStyleSettings getDefaultCommonSettings() {
+    CommonCodeStyleSettings defaultSettings =
+        new CommonCodeStyleSettings(BuildFileLanguage.INSTANCE);
+    CommonCodeStyleSettings.IndentOptions indentOptions = defaultSettings.initIndentOptions();
+    indentOptions.TAB_SIZE = 2;
+    indentOptions.INDENT_SIZE = 2;
+    indentOptions.CONTINUATION_INDENT_SIZE = 4;
+    return defaultSettings;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/globbing/UnixGlob.java b/base/src/com/google/idea/blaze/base/lang/buildfile/globbing/UnixGlob.java
new file mode 100644
index 0000000..321908b
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/globbing/UnixGlob.java
@@ -0,0 +1,762 @@
+/*
+ * 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.globbing;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
+import com.google.common.base.Throwables;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Ordering;
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.ForwardingListenableFuture;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
+import com.google.idea.blaze.base.io.FileAttributeProvider;
+import com.google.idea.blaze.base.lang.buildfile.validation.GlobPatternValidator;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.progress.ProgressManager;
+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 java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Predicate;
+import java.util.regex.Pattern;
+import javax.annotation.Nullable;
+
+/**
+ * Implementation of a subset of UNIX-style file globbing, expanding "*" and "?" as wildcards, but
+ * not [a-z] ranges.
+ *
+ * <p><code>**</code> gets special treatment in include patterns. If it is used as a complete path
+ * segment it matches the filenames in subdirectories recursively.
+ *
+ * <p>Largely copied from {@link com.google.devtools.build.lib.vfs.UnixGlob}
+ */
+public final class UnixGlob {
+  private UnixGlob() {}
+
+  private static List<File> globInternal(
+      File base,
+      Collection<String> patterns,
+      Collection<String> excludePatterns,
+      boolean excludeDirectories,
+      Predicate<File> dirPred,
+      ThreadPoolExecutor threadPool)
+      throws IOException, InterruptedException {
+
+    GlobVisitor visitor = (threadPool == null) ? new GlobVisitor() : new GlobVisitor(threadPool);
+    return visitor.glob(base, patterns, excludePatterns, excludeDirectories, dirPred);
+  }
+
+  private static Future<List<File>> globAsyncInternal(
+      File base,
+      Collection<String> patterns,
+      Collection<String> excludePatterns,
+      boolean excludeDirectories,
+      Predicate<File> dirPred,
+      ThreadPoolExecutor threadPool) {
+    Preconditions.checkNotNull(threadPool, "%s %s", base, patterns);
+    try {
+      return new GlobVisitor(threadPool)
+          .globAsync(base, patterns, excludePatterns, excludeDirectories, dirPred);
+    } catch (IOException e) {
+      // We are evaluating asynchronously, so no exceptions should be thrown until the future is
+      // retrieved.
+      throw new IllegalStateException(e);
+    }
+  }
+
+  /**
+   * Checks that each pattern is valid, splits it into segments and checks that each segment
+   * contains only valid wildcards.
+   *
+   * @return list of segment arrays
+   */
+  private static List<String[]> checkAndSplitPatterns(Collection<String> patterns) {
+    List<String[]> list = Lists.newArrayListWithCapacity(patterns.size());
+    for (String pattern : patterns) {
+      String error = GlobPatternValidator.validate(pattern);
+      if (error != null) {
+        throw new IllegalArgumentException(error);
+      }
+      Iterable<String> segments = Splitter.on('/').split(pattern);
+      list.add(Iterables.toArray(segments, String.class));
+    }
+    return list;
+  }
+
+  private static boolean excludedOnMatch(
+      File path, List<String[]> excludePatterns, int idx, Cache<String, Pattern> cache) {
+    for (String[] excludePattern : excludePatterns) {
+      String text = path.getName();
+      if (idx == excludePattern.length && matches(excludePattern[idx - 1], text, cache)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Returns the exclude patterns in {@code excludePatterns} which could apply to the children of
+   * {@code base}
+   *
+   * @param idx index into {@code excludePatterns} for the part of the pattern which might match
+   *     {@code base}
+   */
+  private static List<String[]> getRelevantExcludes(
+      final File base,
+      List<String[]> excludePatterns,
+      final int idx,
+      final Cache<String, Pattern> cache) {
+
+    if (excludePatterns.isEmpty()) {
+      return excludePatterns;
+    }
+    List<String[]> list = new ArrayList<>();
+    for (String[] patterns : excludePatterns) {
+      if (excludePatternMatches(patterns, idx, base, cache)) {
+        list.add(patterns);
+      }
+    }
+    return list;
+  }
+
+  /**
+   * @param patterns a list of patterns
+   * @param idx index into {@code patterns}
+   */
+  private static boolean excludePatternMatches(
+      String[] patterns, int idx, File base, Cache<String, Pattern> cache) {
+    if (idx == 0) {
+      return true;
+    }
+    String text = base.getName();
+    return patterns.length > idx && matches(patterns[idx - 1], text, cache);
+  }
+
+  /** Calls {@link #matches(String, String, Cache) matches(pattern, str, null)} */
+  public static boolean matches(String pattern, String str) {
+    return matches(pattern, str, null);
+  }
+
+  /**
+   * Returns whether {@code str} matches the glob pattern {@code pattern}. This method may use the
+   * {@code patternCache} to speed up the matching process.
+   *
+   * @param pattern a glob pattern
+   * @param str the string to match
+   * @param patternCache a cache from patterns to compiled Pattern objects, or {@code null} to skip
+   *     caching
+   */
+  public static boolean matches(String pattern, String str, Cache<String, Pattern> patternCache) {
+    if (pattern.length() == 0 || str.length() == 0) {
+      return false;
+    }
+
+    // Common case: **
+    if (pattern.equals("**")) {
+      return true;
+    }
+
+    // Common case: *
+    if (pattern.equals("*")) {
+      return true;
+    }
+
+    // If a filename starts with '.', this char must be matched explicitly.
+    if (str.charAt(0) == '.' && pattern.charAt(0) != '.') {
+      return false;
+    }
+
+    // Common case: *.xyz
+    if (pattern.charAt(0) == '*' && pattern.lastIndexOf('*') == 0) {
+      return str.endsWith(pattern.substring(1));
+    }
+    // Common case: xyz*
+    int lastIndex = pattern.length() - 1;
+    // The first clause of this if statement is unnecessary, but is an
+    // optimization--charAt runs faster than indexOf.
+    if (pattern.charAt(lastIndex) == '*' && pattern.indexOf('*') == lastIndex) {
+      return str.startsWith(pattern.substring(0, lastIndex));
+    }
+
+    Pattern regex = patternCache == null ? null : patternCache.getIfPresent(pattern);
+    if (regex == null) {
+      regex = makePatternFromWildcard(pattern);
+      if (patternCache != null) {
+        patternCache.put(pattern, regex);
+      }
+    }
+    return regex.matcher(str).matches();
+  }
+
+  /**
+   * Returns a regular expression implementing a matcher for "pattern", in which "*" and "?" are
+   * wildcards.
+   *
+   * <p>e.g. "foo*bar?.java" -> "foo.*bar.\\.java"
+   */
+  private static Pattern makePatternFromWildcard(String pattern) {
+    StringBuilder regexp = new StringBuilder();
+    for (int i = 0, len = pattern.length(); i < len; i++) {
+      char c = pattern.charAt(i);
+      switch (c) {
+        case '*':
+          int toIncrement = 0;
+          if (len > i + 1 && pattern.charAt(i + 1) == '*') {
+            // The pattern '**' is interpreted to match 0 or more directory separators, not 1 or
+            // more. We skip the next * and then find a trailing/leading '/' and get rid of it.
+            toIncrement = 1;
+            if (len > i + 2 && pattern.charAt(i + 2) == '/') {
+              // We have '**/' -- skip the '/'.
+              toIncrement = 2;
+            } else if (len == i + 2 && i > 0 && pattern.charAt(i - 1) == '/') {
+              // We have '/**' -- remove the '/'.
+              regexp.delete(regexp.length() - 1, regexp.length());
+            }
+          }
+          regexp.append(".*");
+          i += toIncrement;
+          break;
+        case '?':
+          regexp.append('.');
+          break;
+          //escape the regexp special characters that are allowed in wildcards
+        case '^':
+        case '$':
+        case '|':
+        case '+':
+        case '{':
+        case '}':
+        case '[':
+        case ']':
+        case '\\':
+        case '.':
+          regexp.append('\\');
+          regexp.append(c);
+          break;
+        default:
+          regexp.append(c);
+          break;
+      }
+    }
+    return Pattern.compile(regexp.toString());
+  }
+
+  public static Builder forPath(File path) {
+    return new Builder(path);
+  }
+
+  /** Builder class for UnixGlob. */
+  public static class Builder {
+    private File base;
+    private List<String> patterns;
+    private List<String> excludes;
+    private boolean excludeDirectories;
+    private Predicate<File> pathFilter;
+    private ThreadPoolExecutor threadPool;
+
+    /** Creates a glob builder with the given base path. */
+    public Builder(File base) {
+      this.base = base;
+      this.patterns = Lists.newArrayList();
+      this.excludes = Lists.newArrayList();
+      this.excludeDirectories = false;
+      this.pathFilter = file -> true;
+    }
+
+    /**
+     * Adds a pattern to include to the glob builder.
+     *
+     * <p>For a description of the syntax of the patterns, see {@link UnixGlob}.
+     */
+    public Builder addPattern(String pattern) {
+      this.patterns.add(pattern);
+      return this;
+    }
+
+    /**
+     * Adds a pattern to include to the glob builder.
+     *
+     * <p>For a description of the syntax of the patterns, see {@link UnixGlob}.
+     */
+    public Builder addPatterns(String... patterns) {
+      Collections.addAll(this.patterns, patterns);
+      return this;
+    }
+
+    /**
+     * Adds a pattern to include to the glob builder.
+     *
+     * <p>For a description of the syntax of the patterns, see {@link UnixGlob}.
+     */
+    public Builder addPatterns(Collection<String> patterns) {
+      this.patterns.addAll(patterns);
+      return this;
+    }
+
+    /**
+     * Adds patterns to exclude from the results to the glob builder.
+     *
+     * <p>For a description of the syntax of the patterns, see {@link UnixGlob}.
+     */
+    public Builder addExcludes(String... excludes) {
+      Collections.addAll(this.excludes, excludes);
+      return this;
+    }
+
+    /**
+     * Adds patterns to exclude from the results to the glob builder.
+     *
+     * <p>For a description of the syntax of the patterns, see {@link UnixGlob}.
+     */
+    public Builder addExcludes(Collection<String> excludes) {
+      this.excludes.addAll(excludes);
+      return this;
+    }
+
+    /** If set to true, directories are not returned in the glob result. */
+    public Builder setExcludeDirectories(boolean excludeDirectories) {
+      this.excludeDirectories = excludeDirectories;
+      return this;
+    }
+
+    /**
+     * Sets the threadpool to use for parallel glob evaluation. If unset, evaluation is done
+     * in-thread.
+     */
+    public Builder setThreadPool(ThreadPoolExecutor pool) {
+      this.threadPool = pool;
+      return this;
+    }
+
+    /**
+     * If set, the given predicate is called for every directory encountered. If it returns false,
+     * the corresponding item is not returned in the output and directories are not traversed
+     * either.
+     */
+    public Builder setDirectoryFilter(Predicate<File> pathFilter) {
+      this.pathFilter = pathFilter;
+      return this;
+    }
+
+    /**
+     * Executes the glob.
+     *
+     * @throws InterruptedException if the thread is interrupted.
+     */
+    public List<File> glob() throws IOException, InterruptedException {
+      return globInternal(base, patterns, excludes, excludeDirectories, pathFilter, threadPool);
+    }
+
+    /**
+     * Executes the glob asynchronously. {@link #setThreadPool} must have been called already with a
+     * non-null argument.
+     *
+     * @param checkForInterrupt if the returned future may throw InterruptedException.
+     */
+    public Future<List<File>> globAsync() {
+      return globAsyncInternal(
+          base, patterns, excludes, excludeDirectories, pathFilter, threadPool);
+    }
+  }
+
+  /** Adapts the result of the glob visitation as a Future. */
+  private static class GlobFuture extends ForwardingListenableFuture<List<File>> {
+    private final GlobVisitor visitor;
+    private final SettableFuture<List<File>> delegate = SettableFuture.create();
+
+    public GlobFuture(GlobVisitor visitor) {
+      this.visitor = visitor;
+    }
+
+    @Override
+    public List<File> get() throws InterruptedException, ExecutionException {
+      return super.get();
+    }
+
+    @Override
+    protected ListenableFuture<List<File>> delegate() {
+      return delegate;
+    }
+
+    public void setException(IOException exception) {
+      delegate.setException(exception);
+    }
+
+    public void set(List<File> paths) {
+      delegate.set(paths);
+    }
+
+    @Override
+    public boolean cancel(boolean mayInterruptIfRunning) {
+      // Best-effort interrupt of the in-flight visitation.
+      visitor.cancel();
+      return true;
+    }
+
+    public void markCanceled() {
+      super.cancel(true);
+    }
+  }
+
+  /**
+   * GlobVisitor executes a glob using parallelism, which is useful when the glob() requires many
+   * readdir() calls on high latency filesystems.
+   */
+  private static final class GlobVisitor {
+    // These collections are used across workers and must therefore be thread-safe.
+    private final Collection<File> results = Sets.newConcurrentHashSet();
+    private final Cache<String, Pattern> cache =
+        CacheBuilder.newBuilder()
+            .build(
+                new CacheLoader<String, Pattern>() {
+                  @Override
+                  public Pattern load(String wildcard) {
+                    return makePatternFromWildcard(wildcard);
+                  }
+                });
+
+    private final GlobFuture result;
+    private final ThreadPoolExecutor executor;
+    private final AtomicLong pendingOps = new AtomicLong(0);
+    private final AtomicReference<IOException> failure = new AtomicReference<>();
+    private final FileAttributeProvider fileAttributeProvider = FileAttributeProvider.getInstance();
+    private volatile boolean canceled = false;
+
+    public GlobVisitor(ThreadPoolExecutor executor) {
+      this.executor = executor;
+      this.result = new GlobFuture(this);
+    }
+
+    public GlobVisitor() {
+      this(null);
+    }
+
+    /**
+     * Performs wildcard globbing: returns the sorted list of filenames that match any of {@code
+     * patterns} relative to {@code base}, but which do not match {@code excludePatterns}.
+     * Directories are traversed if and only if they match {@code dirPred}. The predicate is also
+     * called for the root of the traversal.
+     *
+     * <p>Patterns may include "*" and "?", but not "[a-z]".
+     *
+     * <p><code>**</code> gets special treatment in include patterns. If it is used as a complete
+     * path segment it matches the filenames in subdirectories recursively.
+     *
+     * @throws IllegalArgumentException if any glob or exclude pattern {@linkplain
+     *     #checkPatternForError(String) contains errors} or if any exclude pattern segment contains
+     *     <code>**</code> or if any include pattern segment contains <code>**</code> but not equal
+     *     to it.
+     */
+    public List<File> glob(
+        File base,
+        Collection<String> patterns,
+        Collection<String> excludePatterns,
+        boolean excludeDirectories,
+        Predicate<File> dirPred)
+        throws IOException, InterruptedException {
+      try {
+        return globAsync(base, patterns, excludePatterns, excludeDirectories, dirPred).get();
+      } catch (ExecutionException e) {
+        Throwable cause = e.getCause();
+        Throwables.propagateIfPossible(cause, IOException.class);
+        throw new RuntimeException(e);
+      }
+    }
+
+    public Future<List<File>> globAsync(
+        File base,
+        Collection<String> patterns,
+        Collection<String> excludePatterns,
+        boolean excludeDirectories,
+        Predicate<File> dirPred)
+        throws IOException {
+
+      if (!fileAttributeProvider.exists(base) || patterns.isEmpty()) {
+        return Futures.immediateFuture(Collections.emptyList());
+      }
+      boolean baseIsDirectory = fileAttributeProvider.isDirectory(base);
+
+      List<String[]> splitPatterns = checkAndSplitPatterns(patterns);
+      List<String[]> splitExcludes = checkAndSplitPatterns(excludePatterns);
+
+      // We do a dumb loop, even though it will likely duplicate work
+      // (e.g., readdir calls). In order to optimize, we would need
+      // to keep track of which patterns shared sub-patterns and which did not
+      // (for example consider the glob [*/*.java, sub/*.java, */*.txt]).
+      pendingOps.incrementAndGet();
+      try {
+        for (String[] splitPattern : splitPatterns) {
+          queueGlob(
+              base,
+              baseIsDirectory,
+              splitPattern,
+              0,
+              excludeDirectories,
+              splitExcludes,
+              0,
+              results,
+              cache,
+              dirPred);
+        }
+      } finally {
+        decrementAndCheckDone();
+      }
+
+      return result;
+    }
+
+    private void queueGlob(
+        File base,
+        boolean baseIsDirectory,
+        String[] patternParts,
+        int idx,
+        boolean excludeDirectories,
+        List<String[]> excludePatterns,
+        int excludeIdx,
+        Collection<File> results,
+        Cache<String, Pattern> cache,
+        Predicate<File> dirPred)
+        throws IOException {
+      enqueue(
+          () -> {
+            try {
+              reallyGlob(
+                  base,
+                  baseIsDirectory,
+                  patternParts,
+                  idx,
+                  excludeDirectories,
+                  excludePatterns,
+                  excludeIdx,
+                  results,
+                  cache,
+                  dirPred);
+            } catch (IOException e) {
+              failure.set(e);
+            }
+          });
+    }
+
+    protected void enqueue(final Runnable r) {
+      pendingOps.incrementAndGet();
+
+      Runnable wrapped =
+          () -> {
+            try {
+              if (!canceled && failure.get() == null) {
+                r.run();
+              }
+            } finally {
+              decrementAndCheckDone();
+            }
+          };
+
+      if (executor == null) {
+        wrapped.run();
+      } else {
+        executor.execute(wrapped);
+      }
+    }
+
+    protected void cancel() {
+      this.canceled = true;
+    }
+
+    private void decrementAndCheckDone() {
+      if (pendingOps.decrementAndGet() == 0) {
+        // We get to 0 iff we are done all the relevant work. This is because we always increment
+        // the pending ops count as we're enqueuing, and don't decrement until the task is complete
+        // (which includes accounting for any additional tasks that one enqueues).
+        if (canceled) {
+          result.markCanceled();
+        } else if (failure.get() != null) {
+          result.setException(failure.get());
+        } else {
+          result.set(Ordering.<File>natural().immutableSortedCopy(results));
+        }
+      }
+    }
+
+    /**
+     * Expressed in Haskell:
+     *
+     * <pre>
+     *  reallyGlob base []     = { base }
+     *  reallyGlob base [x:xs] = union { reallyGlob(f, xs) | f results "base/x" }
+     * </pre>
+     */
+    private void reallyGlob(
+        File base,
+        boolean baseIsDirectory,
+        String[] patternParts,
+        int idx,
+        boolean excludeDirectories,
+        List<String[]> excludePatterns,
+        int excludeIdx,
+        Collection<File> results,
+        Cache<String, Pattern> cache,
+        Predicate<File> dirPred)
+        throws IOException {
+      ProgressManager.checkCanceled();
+      if (baseIsDirectory && !dirPred.test(base)) {
+        return;
+      }
+
+      if (idx == patternParts.length) { // Base case.
+        if (!(excludeDirectories && baseIsDirectory)
+            && !excludedOnMatch(base, excludePatterns, excludeIdx, cache)) {
+          results.add(base);
+        }
+        return;
+      }
+
+      if (!baseIsDirectory) {
+        // Nothing to find here.
+        return;
+      }
+
+      List<String[]> relevantExcludes =
+          getRelevantExcludes(base, excludePatterns, excludeIdx, cache);
+      final String pattern = patternParts[idx];
+
+      // ** is special: it can match nothing at all.
+      // For example, x/** matches x, **/y matches y, and x/**/y matches x/y.
+      if ("**".equals(pattern)) {
+        queueGlob(
+            base,
+            baseIsDirectory,
+            patternParts,
+            idx + 1,
+            excludeDirectories,
+            excludePatterns,
+            excludeIdx,
+            results,
+            cache,
+            dirPred);
+      }
+
+      if (!pattern.contains("*") && !pattern.contains("?")) {
+        // We do not need to do a readdir in this case, just a stat.
+        File child = new File(base, pattern);
+        boolean childIsDir = fileAttributeProvider.isDirectory(child);
+        if (!childIsDir && !fileAttributeProvider.isFile(child)) {
+          // The file is a dangling symlink, fifo, does not exist, etc.
+          return;
+        }
+
+        queueGlob(
+            child,
+            childIsDir,
+            patternParts,
+            idx + 1,
+            excludeDirectories,
+            relevantExcludes,
+            excludeIdx + 1,
+            results,
+            cache,
+            dirPred);
+        return;
+      }
+
+      File[] children = getChildren(base);
+      if (children == null) {
+        return;
+      }
+      for (File child : children) {
+        boolean childIsDir = fileAttributeProvider.isDirectory(child);
+
+        if ("**".equals(pattern)) {
+          // Recurse without shifting the pattern.
+          if (childIsDir) {
+            queueGlob(
+                child,
+                childIsDir,
+                patternParts,
+                idx,
+                excludeDirectories,
+                relevantExcludes,
+                excludeIdx + 1,
+                results,
+                cache,
+                dirPred);
+          }
+        }
+        if (matches(pattern, child.getName(), cache)) {
+          // Recurse and consume one segment of the pattern.
+          if (childIsDir) {
+            queueGlob(
+                child,
+                childIsDir,
+                patternParts,
+                idx + 1,
+                excludeDirectories,
+                relevantExcludes,
+                excludeIdx + 1,
+                results,
+                cache,
+                dirPred);
+          } else {
+            // Instead of using an async call, just repeat the base case above.
+            if (idx + 1 == patternParts.length
+                && !excludedOnMatch(child, relevantExcludes, excludeIdx + 1, cache)) {
+              results.add(child);
+            }
+          }
+        }
+      }
+    }
+
+    private static VirtualFileSystem getFileSystem() {
+      if (ApplicationManager.getApplication().isUnitTestMode()) {
+        return TempFileSystem.getInstance();
+      }
+      return LocalFileSystem.getInstance();
+    }
+
+    @Nullable
+    private File[] getChildren(File file) {
+      if (!ApplicationManager.getApplication().isUnitTestMode()) {
+        return file.listFiles();
+      }
+      TempFileSystem fs = TempFileSystem.getInstance();
+      VirtualFile vf = fs.findFileByIoFile(file);
+      VirtualFile[] children = vf.getChildren();
+      if (children == null) {
+        return null;
+      }
+      return Arrays.stream(children).map(VirtualFile::getPath).map(File::new).toArray(File[]::new);
+    }
+  }
+}
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
new file mode 100644
index 0000000..b348256
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/highlighting/BuildColorsPage.java
@@ -0,0 +1,114 @@
+/*
+ * 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 com.google.common.collect.ImmutableMap;
+import com.google.idea.blaze.base.lang.buildfile.language.BuildFileType;
+import com.google.idea.blaze.base.settings.Blaze;
+import com.intellij.application.options.colors.InspectionColorSettingsPage;
+import com.intellij.openapi.editor.colors.TextAttributesKey;
+import com.intellij.openapi.fileTypes.SyntaxHighlighter;
+import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory;
+import com.intellij.openapi.options.colors.AttributesDescriptor;
+import com.intellij.openapi.options.colors.ColorDescriptor;
+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;
+
+/** Allows user to customize colors. */
+public class BuildColorsPage
+    implements ColorSettingsPage, InspectionColorSettingsPage, DisplayPrioritySortable {
+  private static final AttributesDescriptor[] ATTRS =
+      new AttributesDescriptor[] {
+        new AttributesDescriptor("Keyword", BuildSyntaxHighlighter.BUILD_KEYWORD),
+        new AttributesDescriptor("String", BuildSyntaxHighlighter.BUILD_STRING),
+        new AttributesDescriptor("Number", BuildSyntaxHighlighter.BUILD_NUMBER),
+        new AttributesDescriptor("Line Comment", BuildSyntaxHighlighter.BUILD_LINE_COMMENT),
+        new AttributesDescriptor("Operation Sign", BuildSyntaxHighlighter.BUILD_OPERATION_SIGN),
+        new AttributesDescriptor("Parentheses", BuildSyntaxHighlighter.BUILD_PARENS),
+        new AttributesDescriptor("Brackets", BuildSyntaxHighlighter.BUILD_BRACKETS),
+        new AttributesDescriptor("Braces", BuildSyntaxHighlighter.BUILD_BRACES),
+        new AttributesDescriptor("Comma", BuildSyntaxHighlighter.BUILD_COMMA),
+        new AttributesDescriptor("Dot", BuildSyntaxHighlighter.BUILD_DOT),
+        new AttributesDescriptor("Function definition", BuildSyntaxHighlighter.BUILD_FN_DEFINITION),
+        new AttributesDescriptor("Parameter", BuildSyntaxHighlighter.BUILD_PARAMETER),
+        new AttributesDescriptor("Keyword argument", BuildSyntaxHighlighter.BUILD_KEYWORD_ARG),
+      };
+
+  private static final Map<String, TextAttributesKey> ourTagToDescriptorMap =
+      ImmutableMap.<String, TextAttributesKey>builder()
+          .put("funcDef", BuildSyntaxHighlighter.BUILD_FN_DEFINITION)
+          .put("param", BuildSyntaxHighlighter.BUILD_PARAMETER)
+          .put("kwarg", BuildSyntaxHighlighter.BUILD_KEYWORD_ARG)
+          .put("comma", BuildSyntaxHighlighter.BUILD_COMMA)
+          .put("number", BuildSyntaxHighlighter.BUILD_NUMBER)
+          .build();
+
+  @Override
+  public String getDisplayName() {
+    return Blaze.defaultBuildSystemName() + " BUILD files";
+  }
+
+  @Override
+  public Icon getIcon() {
+    return BuildFileType.INSTANCE.getIcon();
+  }
+
+  @Override
+  public AttributesDescriptor[] getAttributeDescriptors() {
+    return ATTRS;
+  }
+
+  @Override
+  public ColorDescriptor[] getColorDescriptors() {
+    return ColorDescriptor.EMPTY_ARRAY;
+  }
+
+  @Override
+  public SyntaxHighlighter getHighlighter() {
+    final SyntaxHighlighter highlighter =
+        SyntaxHighlighterFactory.getSyntaxHighlighter(BuildFileType.INSTANCE, null, null);
+    assert highlighter != null;
+    return highlighter;
+  }
+
+  @Override
+  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"
+        + "\n"
+        + "java_library(\n"
+        + "    <kwarg>name</kwarg> = \"lib\",\n"
+        + "    <kwarg>srcs</kwarg> = glob([\"**/*.java\"]),\n"
+        + ")\n";
+  }
+
+  @Override
+  public Map<String, TextAttributesKey> getAdditionalHighlightingTagToDescriptorMap() {
+    return ourTagToDescriptorMap;
+  }
+
+  @Override
+  public DisplayPriority getPriority() {
+    return PlatformUtils.isPyCharm()
+        ? DisplayPriority.KEY_LANGUAGE_SETTINGS
+        : 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
new file mode 100644
index 0000000..b14e89b
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/highlighting/BuildSyntaxHighlighter.java
@@ -0,0 +1,111 @@
+/*
+ * 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.intellij.openapi.editor.DefaultLanguageHighlighterColors.BRACES;
+import static com.intellij.openapi.editor.DefaultLanguageHighlighterColors.BRACKETS;
+import static com.intellij.openapi.editor.DefaultLanguageHighlighterColors.COMMA;
+import static com.intellij.openapi.editor.DefaultLanguageHighlighterColors.DOT;
+import static com.intellij.openapi.editor.DefaultLanguageHighlighterColors.FUNCTION_DECLARATION;
+import static com.intellij.openapi.editor.DefaultLanguageHighlighterColors.KEYWORD;
+import static com.intellij.openapi.editor.DefaultLanguageHighlighterColors.LINE_COMMENT;
+import static com.intellij.openapi.editor.DefaultLanguageHighlighterColors.NUMBER;
+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.SEMICOLON;
+import static com.intellij.openapi.editor.DefaultLanguageHighlighterColors.STRING;
+
+import com.google.common.collect.Maps;
+import com.google.idea.blaze.base.lang.buildfile.lexer.BuildLexer;
+import com.google.idea.blaze.base.lang.buildfile.lexer.BuildLexerBase;
+import com.google.idea.blaze.base.lang.buildfile.lexer.BuildToken;
+import com.google.idea.blaze.base.lang.buildfile.lexer.TokenKind;
+import com.intellij.lexer.Lexer;
+import com.intellij.openapi.editor.colors.TextAttributesKey;
+import com.intellij.openapi.fileTypes.SyntaxHighlighterBase;
+import com.intellij.psi.tree.IElementType;
+import java.util.Map;
+
+/**
+ * This class maps tokens to highlighting attributes. Each attribute contains the font properties.
+ */
+public class BuildSyntaxHighlighter extends SyntaxHighlighterBase {
+
+  public static final TextAttributesKey BUILD_KEYWORD = key("BUILD.MODIFIER", KEYWORD);
+  public static final TextAttributesKey BUILD_STRING = key("BUILD.STRING", STRING);
+  public static final TextAttributesKey BUILD_NUMBER = key("BUILD.NUMBER", NUMBER);
+  public static final TextAttributesKey BUILD_LINE_COMMENT =
+      key("BUILD.LINE_COMMENT", LINE_COMMENT);
+  public static final TextAttributesKey BUILD_BRACES = key("BUILD.BRACES", BRACES);
+  public static final TextAttributesKey BUILD_PARENS = key("BUILD.PARENS", PARENTHESES);
+  public static final TextAttributesKey BUILD_BRACKETS = key("BUILD.BRACKETS", BRACKETS);
+  public static final TextAttributesKey BUILD_OPERATION_SIGN =
+      key("BUILD.OPERATION_SIGN", OPERATION_SIGN);
+  public static final TextAttributesKey BUILD_DOT = key("BUILD.DOT", DOT);
+  public static final TextAttributesKey BUILD_SEMICOLON = key("BUILD.SEMICOLON", SEMICOLON);
+  public static final TextAttributesKey BUILD_COMMA = key("BUILD.COMMA", COMMA);
+  public static final TextAttributesKey BUILD_PARAMETER = key("BUILD.PARAMETER", PARAMETER);
+  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);
+
+  private static TextAttributesKey key(String name, TextAttributesKey fallbackKey) {
+    return TextAttributesKey.createTextAttributesKey(name, fallbackKey);
+  }
+
+  private static final Map<IElementType, TextAttributesKey> keys = Maps.newHashMap();
+
+  static {
+    addAttribute(TokenKind.COMMENT, BUILD_LINE_COMMENT);
+    addAttribute(TokenKind.INT, BUILD_NUMBER);
+    addAttribute(TokenKind.STRING, BUILD_STRING);
+
+    addAttribute(TokenKind.LBRACE, BUILD_BRACES);
+    addAttribute(TokenKind.RBRACE, BUILD_BRACES);
+    addAttribute(TokenKind.LBRACKET, BUILD_BRACKETS);
+    addAttribute(TokenKind.RBRACKET, BUILD_BRACKETS);
+
+    addAttribute(TokenKind.LPAREN, BUILD_PARENS);
+    addAttribute(TokenKind.RPAREN, BUILD_PARENS);
+
+    addAttribute(TokenKind.DOT, BUILD_DOT);
+    addAttribute(TokenKind.SEMI, BUILD_SEMICOLON);
+    addAttribute(TokenKind.COMMA, BUILD_COMMA);
+
+    for (TokenKind kind : TokenKind.OPERATIONS) {
+      addAttribute(kind, BUILD_OPERATION_SIGN);
+    }
+
+    for (TokenKind kind : TokenKind.KEYWORDS) {
+      addAttribute(kind, BUILD_KEYWORD);
+    }
+  }
+
+  private static void addAttribute(TokenKind kind, TextAttributesKey key) {
+    keys.put(BuildToken.fromKind(kind), key);
+  }
+
+  @Override
+  public Lexer getHighlightingLexer() {
+    return new BuildLexer(BuildLexerBase.LexerMode.SyntaxHighlighting);
+  }
+
+  @Override
+  public TextAttributesKey[] getTokenHighlights(IElementType iElementType) {
+    return pack(keys.get(iElementType));
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/highlighting/BuildSyntaxHighlighterFactory.java b/base/src/com/google/idea/blaze/base/lang/buildfile/highlighting/BuildSyntaxHighlighterFactory.java
new file mode 100644
index 0000000..7f6ffc7
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/highlighting/BuildSyntaxHighlighterFactory.java
@@ -0,0 +1,30 @@
+/*
+ * 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 com.intellij.openapi.fileTypes.SyntaxHighlighter;
+import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+
+/** Factory for BuildSyntaxHighlighter */
+public class BuildSyntaxHighlighterFactory extends SyntaxHighlighterFactory {
+
+  @Override
+  public SyntaxHighlighter getSyntaxHighlighter(Project project, VirtualFile virtualFile) {
+    return new BuildSyntaxHighlighter();
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/language/BuildFileLanguage.java b/base/src/com/google/idea/blaze/base/lang/buildfile/language/BuildFileLanguage.java
new file mode 100644
index 0000000..357791d
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/language/BuildFileLanguage.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.base.lang.buildfile.language;
+
+import com.intellij.lang.Language;
+
+/** BUILD file language */
+public class BuildFileLanguage extends Language {
+
+  public static final BuildFileLanguage INSTANCE = new BuildFileLanguage();
+
+  private BuildFileLanguage() {
+    super("BUILD", "text/python");
+  }
+
+  @Override
+  public String getDisplayName() {
+    return "BUILD file";
+  }
+
+  @Override
+  public boolean isCaseSensitive() {
+    return true;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/language/BuildFileType.java b/base/src/com/google/idea/blaze/base/lang/buildfile/language/BuildFileType.java
new file mode 100644
index 0000000..aa084bc
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/language/BuildFileType.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.lang.buildfile.language;
+
+import com.google.idea.blaze.base.settings.Blaze;
+import com.intellij.openapi.fileTypes.LanguageFileType;
+import icons.BlazeIcons;
+import javax.annotation.Nullable;
+import javax.swing.Icon;
+
+/** BUILD file type */
+public class BuildFileType extends LanguageFileType {
+
+  public static final BuildFileType INSTANCE = new BuildFileType();
+
+  private BuildFileType() {
+    super(BuildFileLanguage.INSTANCE);
+  }
+
+  @Override
+  public String getName() {
+    // Warning: this is conflated with Language.myID in several places...
+    // They must be identical.
+    return BuildFileLanguage.INSTANCE.getID();
+  }
+
+  @Override
+  public String getDescription() {
+    return Blaze.defaultBuildSystemName() + " BUILD language";
+  }
+
+  @Override
+  public String getDefaultExtension() {
+    return "";
+  }
+
+  @Override
+  @Nullable
+  public Icon getIcon() {
+    return BlazeIcons.BuildFile;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/language/BuildFileTypeFactory.java b/base/src/com/google/idea/blaze/base/lang/buildfile/language/BuildFileTypeFactory.java
new file mode 100644
index 0000000..32243bc
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/language/BuildFileTypeFactory.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+import com.google.common.collect.ImmutableList;
+import com.intellij.openapi.fileTypes.ExactFileNameMatcher;
+import com.intellij.openapi.fileTypes.ExtensionFileNameMatcher;
+import com.intellij.openapi.fileTypes.FileNameMatcher;
+import com.intellij.openapi.fileTypes.FileTypeConsumer;
+import com.intellij.openapi.fileTypes.FileTypeFactory;
+import org.jetbrains.annotations.NotNull;
+
+/** Factory for BuildFileType */
+public class BuildFileTypeFactory extends FileTypeFactory {
+
+  private static ImmutableList<FileNameMatcher> DEFAULT_ASSOCIATIONS =
+      ImmutableList.of(new ExactFileNameMatcher("BUILD"), new ExtensionFileNameMatcher("bzl"));
+
+  @Override
+  public void createFileTypes(@NotNull final FileTypeConsumer consumer) {
+    consumer.consume(BuildFileType.INSTANCE, DEFAULT_ASSOCIATIONS.toArray(new FileNameMatcher[0]));
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/language/semantics/AttributeDefinition.java b/base/src/com/google/idea/blaze/base/lang/buildfile/language/semantics/AttributeDefinition.java
new file mode 100644
index 0000000..2d1c5ea
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/language/semantics/AttributeDefinition.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.language.semantics;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import com.google.repackaged.devtools.build.lib.query2.proto.proto2api.Build;
+import java.io.Serializable;
+import javax.annotation.Nullable;
+
+/** Simple implementation of AttributeDefinition, from build.proto */
+public class AttributeDefinition implements Serializable {
+
+  public static AttributeDefinition fromProto(Build.AttributeDefinition attr) {
+    return new AttributeDefinition(
+        attr.getName(),
+        attr.getType(),
+        attr.getMandatory(),
+        attr.hasDocumentation() ? attr.getDocumentation() : null,
+        getAllowedRuleClasses(attr));
+  }
+
+  @Nullable
+  private static ImmutableList<String> getAllowedRuleClasses(Build.AttributeDefinition attr) {
+    if (!attr.hasAllowedRuleClasses()) {
+      return null;
+    }
+    return ImmutableList.copyOf(attr.getAllowedRuleClasses().getAllowedRuleClassList());
+  }
+
+  public final String name;
+  public final Build.Attribute.Discriminator type;
+  public final boolean mandatory;
+  public final String documentation;
+
+  // the names of rules allowed in this attribute, or null if any rules are allowed.
+  @Nullable private final ImmutableList<String> allowedRuleClasses;
+
+  @VisibleForTesting
+  public AttributeDefinition(
+      String name,
+      Build.Attribute.Discriminator type,
+      boolean mandatory,
+      String documentation,
+      @Nullable ImmutableList<String> allowedRuleClasses) {
+
+    this.name = name;
+    this.type = type;
+    this.mandatory = mandatory;
+    this.documentation = documentation;
+    this.allowedRuleClasses = allowedRuleClasses;
+  }
+
+  /**
+   * Only relevant for attributes of type LABEL and LABEL_LIST. Some such attributes can only
+   * contain certain rule types.
+   */
+  public boolean isRuleTypeAllowed(RuleDefinition rule) {
+    return allowedRuleClasses == null || allowedRuleClasses.contains(rule.name);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/language/semantics/BuildLanguageSpec.java b/base/src/com/google/idea/blaze/base/lang/buildfile/language/semantics/BuildLanguageSpec.java
new file mode 100644
index 0000000..824c262
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/language/semantics/BuildLanguageSpec.java
@@ -0,0 +1,64 @@
+/*
+ * 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.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.repackaged.devtools.build.lib.query2.proto.proto2api.Build;
+import java.io.Serializable;
+import javax.annotation.Nullable;
+
+/**
+ * Specification of the BUILD language, as provided by "blaze info build-language".
+ *
+ * <p>This constitutes a set of rules, along with their supported attributes, and other useful
+ * information. We query this once per blaze workspace (it won't change unless the blaze binary is
+ * also changed).
+ *
+ * <p>This rule list is not exhaustive; it's intended to give information about known rules, not
+ * enumerate all possibilities.
+ */
+public class BuildLanguageSpec implements Serializable {
+
+  public static BuildLanguageSpec fromProto(Build.BuildLanguage proto) {
+    ImmutableMap.Builder<String, RuleDefinition> builder = ImmutableMap.builder();
+    for (Build.RuleDefinition rule : proto.getRuleList()) {
+      builder.put(rule.getName(), RuleDefinition.fromProto(rule));
+    }
+    return new BuildLanguageSpec(builder.build());
+  }
+
+  public final ImmutableMap<String, RuleDefinition> rules;
+
+  @VisibleForTesting
+  public BuildLanguageSpec(ImmutableMap<String, RuleDefinition> rules) {
+    this.rules = rules;
+  }
+
+  public ImmutableSet<String> getKnownRuleNames() {
+    return rules.keySet();
+  }
+
+  public boolean hasRule(@Nullable String ruleName) {
+    return getRule(ruleName) != null;
+  }
+
+  @Nullable
+  public RuleDefinition getRule(@Nullable String ruleName) {
+    return ruleName != null ? rules.get(ruleName) : null;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/language/semantics/BuildLanguageSpecProvider.java b/base/src/com/google/idea/blaze/base/lang/buildfile/language/semantics/BuildLanguageSpecProvider.java
new file mode 100644
index 0000000..d8ea988
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/language/semantics/BuildLanguageSpecProvider.java
@@ -0,0 +1,35 @@
+/*
+ * 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.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.project.Project;
+import javax.annotation.Nullable;
+
+/** Provides a BuildLanguageSpec for the given project. */
+public interface BuildLanguageSpecProvider {
+
+  static BuildLanguageSpecProvider getInstance() {
+    return ServiceManager.getService(BuildLanguageSpecProvider.class);
+  }
+
+  /**
+   * Returns null if cancelled or unsuccessful. Also returns null if no WorkspaceRoot can be found
+   * for the project (i.e. because BlazeImportSettings has not been set).
+   */
+  @Nullable
+  BuildLanguageSpec getLanguageSpec(Project project);
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/language/semantics/BuildLanguageSpecProviderImpl.java b/base/src/com/google/idea/blaze/base/lang/buildfile/language/semantics/BuildLanguageSpecProviderImpl.java
new file mode 100644
index 0000000..43ea22a
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/language/semantics/BuildLanguageSpecProviderImpl.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.base.lang.buildfile.language.semantics;
+
+import com.google.common.collect.Maps;
+import com.google.idea.blaze.base.lang.buildfile.sync.LanguageSpecResult;
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.projectview.ProjectViewSet;
+import com.google.idea.blaze.base.settings.BlazeImportSettings;
+import com.google.idea.blaze.base.sync.SyncListener;
+import com.intellij.openapi.project.Project;
+import java.util.Map;
+
+/** Calls 'blaze info build-language', to retrieve the language spec. */
+public class BuildLanguageSpecProviderImpl extends SyncListener.Adapter
+    implements BuildLanguageSpecProvider {
+
+  private static final Map<Project, LanguageSpecResult> calculatedSpecs = Maps.newHashMap();
+
+  @Override
+  public BuildLanguageSpec getLanguageSpec(Project project) {
+    LanguageSpecResult result = calculatedSpecs.get(project);
+    return result != null ? result.spec : null;
+  }
+
+  @Override
+  public void onSyncComplete(
+      Project project,
+      BlazeImportSettings importSettings,
+      ProjectViewSet projectViewSet,
+      BlazeProjectData blazeProjectData,
+      SyncResult syncResult) {
+    LanguageSpecResult spec = blazeProjectData.syncState.get(LanguageSpecResult.class);
+    if (spec != null) {
+      calculatedSpecs.put(project, spec);
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/language/semantics/RuleDefinition.java b/base/src/com/google/idea/blaze/base/lang/buildfile/language/semantics/RuleDefinition.java
new file mode 100644
index 0000000..089b598
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/language/semantics/RuleDefinition.java
@@ -0,0 +1,64 @@
+/*
+ * 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.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.repackaged.devtools.build.lib.query2.proto.proto2api.Build;
+import java.io.Serializable;
+import javax.annotation.Nullable;
+
+/** Simple implementation of RuleDefinition, from build.proto */
+public class RuleDefinition implements Serializable {
+
+  /** This isn't included in the proto -- all other documented attributes seem to be. */
+  private static final AttributeDefinition NAME_ATTRIBUTE =
+      new AttributeDefinition("name", Build.Attribute.Discriminator.STRING, true, null, null);
+
+  public static RuleDefinition fromProto(Build.RuleDefinition rule) {
+    ImmutableMap.Builder<String, AttributeDefinition> map = ImmutableMap.builder();
+    for (Build.AttributeDefinition attr : rule.getAttributeList()) {
+      map.put(attr.getName(), AttributeDefinition.fromProto(attr));
+    }
+    map.put(NAME_ATTRIBUTE.name, NAME_ATTRIBUTE);
+    return new RuleDefinition(
+        rule.getName(), map.build(), rule.hasDocumentation() ? rule.getDocumentation() : null);
+  }
+
+  public final String name;
+  /** This map is not exhaustive; it only contains documented attributes. */
+  public final ImmutableMap<String, AttributeDefinition> attributes;
+
+  @Nullable public final String documentation;
+
+  public RuleDefinition(
+      String name,
+      ImmutableMap<String, AttributeDefinition> attributes,
+      @Nullable String documentation) {
+    this.name = name;
+    this.attributes = attributes;
+    this.documentation = documentation;
+  }
+
+  public ImmutableSet<String> getKnownAttributeNames() {
+    return attributes.keySet();
+  }
+
+  @Nullable
+  public AttributeDefinition getAttribute(@Nullable String attributeName) {
+    return attributeName != null ? attributes.get(attributeName) : null;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/lexer/BuildLexer.java b/base/src/com/google/idea/blaze/base/lang/buildfile/lexer/BuildLexer.java
new file mode 100644
index 0000000..e026d32
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/lexer/BuildLexer.java
@@ -0,0 +1,127 @@
+/*
+ * 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.lexer;
+
+import com.google.idea.blaze.base.lang.buildfile.lexer.BuildLexerBase.LexerMode;
+import com.intellij.lexer.LexerBase;
+import java.util.Iterator;
+import java.util.List;
+
+/** Implementation of LexerBase using BuildLexerBase to tokenize the input. */
+public class BuildLexer extends LexerBase {
+
+  private final LexerMode mode;
+
+  private int offsetEnd;
+  private int offsetStart;
+  private CharSequence buffer;
+  private Iterator<Token> tokens;
+  private Token currentToken;
+  private int currentState;
+
+  public BuildLexer(LexerMode mode) {
+    this.mode = mode;
+  }
+
+  @Override
+  public void start(CharSequence charSequence, int startOffset, int endOffset, int initialState) {
+    buffer = charSequence;
+    this.offsetEnd = endOffset;
+    this.offsetStart = startOffset;
+
+    BuildLexerBase lexer =
+        new BuildLexerBase(charSequence.subSequence(startOffset, endOffset), initialState, mode);
+    checkNoCharactersMissing(
+        charSequence.subSequence(startOffset, endOffset).length(), lexer.getTokens());
+    tokens = lexer.getTokens().iterator();
+    currentToken = null;
+    if (tokens.hasNext()) {
+      currentToken = tokens.next();
+    }
+    currentState = lexer.getOpenParenStackDepth();
+  }
+
+  /** Temporary debugging code. We need to tokenize every character in the input string. */
+  private void checkNoCharactersMissing(int totalLength, List<Token> tokens) {
+    if (!tokens.isEmpty() && tokens.get(tokens.size() - 1).right != totalLength) {
+      String error =
+          String.format(
+              "Lengths don't match: %s instead of %s",
+              tokens.get(tokens.size() - 1).right, totalLength);
+      throw new RuntimeException(error);
+    }
+    int start = 0;
+    for (int i = 0; i < tokens.size(); i++) {
+      Token token = tokens.get(i);
+      if (token.left != start) {
+        throw new RuntimeException("Gap/inconsistency at: " + start);
+      }
+      start = token.right;
+    }
+  }
+
+  @Override
+  public int getState() {
+    return currentState;
+  }
+
+  @Override
+  public BuildToken getTokenType() {
+    if (currentToken != null) {
+      return BuildToken.fromKind(currentToken.kind);
+    }
+    return null;
+  }
+
+  @Override
+  public int getTokenStart() {
+    if (currentToken == null) {
+      return 0;
+    }
+    return currentToken.left + offsetStart;
+  }
+
+  @Override
+  public int getTokenEnd() {
+    if (currentToken == null) {
+      return 0;
+    }
+    return currentToken.right + offsetStart;
+  }
+
+  @Override
+  public void advance() {
+    if (tokens.hasNext()) {
+      currentToken = tokens.next();
+    } else {
+      currentToken = null;
+    }
+  }
+
+  public TokenKind getTokenKind() {
+    return currentToken.kind;
+  }
+
+  @Override
+  public CharSequence getBufferSequence() {
+    return buffer;
+  }
+
+  @Override
+  public int getBufferEnd() {
+    return offsetEnd;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/lexer/BuildLexerBase.java b/base/src/com/google/idea/blaze/base/lang/buildfile/lexer/BuildLexerBase.java
new file mode 100644
index 0000000..4970281
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/lexer/BuildLexerBase.java
@@ -0,0 +1,849 @@
+/*
+ * 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.lexer;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+import javax.annotation.Nullable;
+
+/**
+ * A tokenizer for the BUILD language.
+ *
+ * <p>Copied from blaze/bazel's lexer. The differences are: 1. Blaze's lexer isn't 'faithful', in
+ * that it reorders characters, skips characters, and adds ghost characters. We can't do that,
+ * because we need to match the editor's view of the document. 2. Blaze's lexer only lexes entire
+ * files (it can't incrementally lex part of a file, starting from a given indent stack depth).
+ */
+public class BuildLexerBase {
+
+  /**
+   * When tokenizing for the purposes of parsing, we handle indentation.<br>
+   * For syntax highlighting, we need to tokenize every character, and don't care about indentation.
+   */
+  public enum LexerMode {
+    Parsing,
+    SyntaxHighlighting
+  }
+
+  // Characters that can come immediately prior to an '=' character to generate
+  // a different token
+  private static final Map<Character, TokenKind> EQUAL_TOKENS =
+      ImmutableMap.<Character, TokenKind>builder()
+          .put('=', TokenKind.EQUALS_EQUALS)
+          .put('!', TokenKind.NOT_EQUALS)
+          .put('>', TokenKind.GREATER_EQUALS)
+          .put('<', TokenKind.LESS_EQUALS)
+          .put('+', TokenKind.PLUS_EQUALS)
+          .put('-', TokenKind.MINUS_EQUALS)
+          .put('*', TokenKind.STAR_EQUALS)
+          .put('/', TokenKind.SLASH_EQUALS)
+          .put('%', TokenKind.PERCENT_EQUALS)
+          .build();
+
+  private final LexerMode mode;
+
+  // Input buffer and position
+  private final char[] buffer;
+  private int pos;
+
+  private final List<Token> tokens;
+
+  // The number of unclosed open-parens ("(", '{', '[') at the current point in
+  // the stream. Whitespace is handled differently when this is nonzero.
+  private int openParenStackDepth = 0;
+
+  // The stack of enclosing indentation levels; always contains '0' at the
+  // bottom.
+  private final Stack<Integer> indentStack = new Stack<>();
+
+  private boolean containsErrors;
+
+  /**
+   * Constructs a lexer which tokenizes the contents of the specified InputBuffer. Any errors during
+   * lexing are reported on "handler".
+   */
+  public BuildLexerBase(CharSequence input, int initialStackDepth, LexerMode mode) {
+    this.buffer = input.toString().toCharArray();
+    // Empirical measurements show roughly 1 token per 8 characters in buffer.
+    this.tokens = Lists.newArrayListWithExpectedSize(buffer.length / 8);
+    this.pos = 0;
+    this.openParenStackDepth = initialStackDepth;
+    this.mode = mode;
+
+    indentStack.push(0);
+    tokenize();
+  }
+
+  /** The number of unclosed open-parens ("(", '{', '[') at the end of this string. */
+  public int getOpenParenStackDepth() {
+    return openParenStackDepth;
+  }
+
+  /**
+   * Returns true if there were errors during scanning of this input file or string. The
+   * BuildLexerBase may attempt to recover from errors, but clients should not rely on the results
+   * of scanning if this flag is set.
+   */
+  public boolean containsErrors() {
+    return containsErrors;
+  }
+
+  /** Returns the (mutable) list of tokens generated by the BuildLexerBase. */
+  public List<Token> getTokens() {
+    return tokens;
+  }
+
+  private void popParen() {
+    if (openParenStackDepth == 0) {
+      error("indentation error");
+    } else {
+      openParenStackDepth--;
+    }
+  }
+
+  private void error(String message) {
+    error(message, pos - 1, pos - 1);
+  }
+
+  protected void error(String message, int start, int end) {
+    this.containsErrors = true;
+  }
+
+  /** invariant: symbol positions are half-open intervals. */
+  private void addToken(TokenKind kind, int left, int right) {
+    addToken(kind, left, right, null);
+  }
+
+  private void addToken(TokenKind kind, int left, int right, @Nullable Object value) {
+    tokens.add(new Token(kind, left, right, value));
+  }
+
+  /**
+   * Parses an end-of-line sequence, handling statement indentation correctly.
+   *
+   * <p>UNIX newlines are assumed (LF). Carriage returns are always ignored.
+   *
+   * <p>ON ENTRY: 'pos' is the index of the char after '\n'. ON EXIT: 'pos' is the index of the next
+   * non-space char after '\n'.
+   */
+  protected void newline() {
+    if (mode == LexerMode.SyntaxHighlighting) {
+      addToken(TokenKind.NEWLINE, pos - 1, pos);
+      return;
+    }
+    if (openParenStackDepth > 0) {
+      newlineInsideExpression(); // in an expression: ignore space
+    } else {
+      newlineOutsideExpression(); // generate NEWLINE/INDENT/DEDENT tokens
+    }
+  }
+
+  private void newlineInsideExpression() {
+    int oldPos = pos - 1;
+    while (pos < buffer.length) {
+      switch (buffer[pos]) {
+        case ' ':
+        case '\t':
+        case '\r':
+          pos++;
+          break;
+        default:
+          // ignored by the parser
+          addToken(TokenKind.WHITESPACE, oldPos, pos);
+          return;
+      }
+    }
+    addToken(TokenKind.WHITESPACE, oldPos, pos);
+  }
+
+  /**
+   * Handle INDENT and DEDENT within statements.
+   *
+   * <p>Note these tokens have zero length -- this is because we can have an arbitrary number of
+   * dedents squeezed into some number of characters, and the size of all the lexical elements must
+   * match the number of characters in the file.
+   */
+  private void newlineOutsideExpression() {
+    int oldPos = pos - 1;
+    if (pos > 1) { // skip over newline at start of file
+      addToken(TokenKind.NEWLINE, oldPos, pos);
+      oldPos = pos;
+    }
+
+    // we're in a stmt: suck up space at beginning of next line
+    int indentLen = 0;
+    while (pos < buffer.length) {
+      char c = buffer[pos];
+      if (c == ' ') {
+        indentLen++;
+        pos++;
+      } else if (c == '\t') {
+        indentLen += 8 - indentLen % 8;
+        pos++;
+      } else if (c == '\n') { // entirely blank line: ignore
+        indentLen = 0;
+        pos++;
+      } else if (c == '#') { // line containing only indented comment
+        if (oldPos != pos) {
+          addToken(TokenKind.WHITESPACE, oldPos, pos);
+          oldPos = pos;
+        }
+        while (pos < buffer.length && c != '\n') {
+          c = buffer[pos++];
+        }
+        addToken(TokenKind.COMMENT, oldPos, pos - 1, bufferSlice(oldPos, pos - 1));
+        oldPos = pos - 1;
+        indentLen = 0;
+      } else { // printing character
+        break;
+      }
+    }
+
+    if (oldPos != pos) {
+      addToken(TokenKind.WHITESPACE, oldPos, pos);
+    }
+    if (pos == buffer.length) {
+      indentLen = 0;
+    } // trailing space on last line
+
+    int peekedIndent = indentStack.peek();
+    if (peekedIndent < indentLen) { // push a level
+      indentStack.push(indentLen);
+      addToken(TokenKind.INDENT, pos, pos);
+
+    } else if (peekedIndent > indentLen) { // pop one or more levels
+      while (peekedIndent > indentLen) {
+        indentStack.pop();
+        addToken(TokenKind.DEDENT, pos, pos);
+        peekedIndent = indentStack.peek();
+      }
+
+      if (peekedIndent < indentLen) {
+        error("indentation error");
+      }
+    }
+  }
+
+  /** Collapse adjacent whitespace characters into a single token */
+  private void addWhitespace() {
+    int oldPos = pos - 1;
+    while (pos < buffer.length) {
+      switch (buffer[pos]) {
+        case ' ':
+        case '\t':
+        case '\r':
+          pos++;
+          break;
+        default:
+          addToken(TokenKind.WHITESPACE, oldPos, pos, bufferSlice(oldPos, pos));
+          return;
+      }
+    }
+    addToken(TokenKind.WHITESPACE, oldPos, pos, bufferSlice(oldPos, pos));
+  }
+
+  /**
+   * Returns true if current position is in the middle of a triple quote delimiter (3 x quot), and
+   * advances 'pos' by two if so.
+   */
+  private boolean skipTripleQuote(char quot) {
+    if (pos + 1 < buffer.length && buffer[pos] == quot && buffer[pos + 1] == quot) {
+      pos += 2;
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  /**
+   * Scans a string literal delimited by 'quot', containing escape sequences.
+   *
+   * <p>ON ENTRY: 'pos' is 1 + the index of the first delimiter ON EXIT: 'pos' is 1 + the index of
+   * the last delimiter.
+   */
+  private void escapedStringLiteral(char quot, boolean isRaw) {
+    int oldPos = isRaw ? pos - 2 : pos - 1;
+    boolean inTripleQuote = skipTripleQuote(quot);
+
+    // more expensive second choice that expands escaped into a buffer
+    StringBuilder literal = new StringBuilder();
+    while (pos < buffer.length) {
+      char c = buffer[pos];
+      pos++;
+      switch (c) {
+        case '\n':
+          if (inTripleQuote) {
+            literal.append(c);
+            break;
+          } else {
+            error("unterminated string literal at eol", oldPos, pos);
+            addToken(TokenKind.STRING, oldPos, pos - 1, literal.toString());
+            newline();
+            return;
+          }
+        case '\\':
+          if (pos == buffer.length) {
+            error("unterminated string literal at eof", oldPos, pos);
+            addToken(TokenKind.STRING, oldPos, pos - 1, literal.toString());
+            return;
+          }
+          if (isRaw) {
+            // Insert \ and the following character.
+            // As in Python, it means that a raw string can never end with a single \.
+            literal.append('\\');
+            literal.append(buffer[pos]);
+            pos++;
+            break;
+          }
+          c = buffer[pos];
+          pos++;
+          switch (c) {
+            case '\n':
+              // ignore end of line character
+              break;
+            case 'n':
+              literal.append('\n');
+              break;
+            case 'r':
+              literal.append('\r');
+              break;
+            case 't':
+              literal.append('\t');
+              break;
+            case '\\':
+              literal.append('\\');
+              break;
+            case '\'':
+              literal.append('\'');
+              break;
+            case '"':
+              literal.append('"');
+              break;
+            case '0':
+            case '1':
+            case '2':
+            case '3':
+            case '4':
+            case '5':
+            case '6':
+            case '7':
+              { // octal escape
+                int octal = c - '0';
+                if (pos < buffer.length) {
+                  c = buffer[pos];
+                  if (c >= '0' && c <= '7') {
+                    pos++;
+                    octal = (octal << 3) | (c - '0');
+                    if (pos < buffer.length) {
+                      c = buffer[pos];
+                      if (c >= '0' && c <= '7') {
+                        pos++;
+                        octal = (octal << 3) | (c - '0');
+                      }
+                    }
+                  }
+                }
+                literal.append((char) (octal & 0xff));
+                break;
+              }
+            case 'a':
+            case 'b':
+            case 'f':
+            case 'N':
+            case 'u':
+            case 'U':
+            case 'v':
+            case 'x':
+              // exists in Python but not implemented in Blaze => error
+              error("escape sequence not implemented: \\" + c, oldPos, pos);
+              break;
+            default:
+              // unknown char escape => "\literal"
+              literal.append('\\');
+              literal.append(c);
+              break;
+          }
+          break;
+        case '\'':
+        case '"':
+          if (c != quot || (inTripleQuote && !skipTripleQuote(quot))) {
+            // Non-matching quote, treat it like a regular char.
+            literal.append(c);
+          } else {
+            // Matching close-delimiter, all done.
+            addToken(TokenKind.STRING, oldPos, pos, literal.toString());
+            return;
+          }
+          break;
+        default:
+          literal.append(c);
+          break;
+      }
+    }
+    error("unterminated string literal at eof", oldPos, pos);
+    addToken(TokenKind.STRING, oldPos, pos, literal.toString());
+  }
+
+  /**
+   * Scans a string literal delimited by 'quot'.
+   *
+   * <ul>
+   * <li> ON ENTRY: 'pos' is 1 + the index of the first delimiter
+   * <li> ON EXIT: 'pos' is 1 + the index of the last delimiter.
+   * </ul>
+   *
+   * @param isRaw if true, do not escape the string.
+   */
+  private void addStringLiteral(char quot, boolean isRaw) {
+    int oldPos = isRaw ? pos - 2 : pos - 1;
+    int start = pos;
+
+    // Don't even attempt to parse triple-quotes here.
+    if (skipTripleQuote(quot)) {
+      pos -= 2;
+      escapedStringLiteral(quot, isRaw);
+      return;
+    }
+
+    // first quick optimistic scan for a simple non-escaped string
+    while (pos < buffer.length) {
+      char c = buffer[pos++];
+      switch (c) {
+        case '\n':
+          error("unterminated string literal at eol", oldPos, pos);
+          addToken(TokenKind.STRING, oldPos, pos - 1, bufferSlice(start, pos - 1));
+          newline();
+          return;
+        case '\\':
+          if (isRaw) {
+            // skip the next character
+            pos++;
+            break;
+          }
+          // oops, hit an escape, need to start over & build a new string buffer
+          pos = oldPos + 1;
+          escapedStringLiteral(quot, false);
+          return;
+        case '\'':
+        case '"':
+          if (c == quot) {
+            // close-quote, all done.
+            addToken(TokenKind.STRING, oldPos, pos, bufferSlice(start, pos - 1));
+            return;
+          }
+      }
+    }
+
+    error("unterminated string literal at eof", oldPos, pos);
+    addToken(TokenKind.STRING, oldPos, pos, bufferSlice(start, pos));
+  }
+
+  private static final ImmutableMap<String, TokenKind> KEYWORD_MAP = createKeywordMap();
+
+  private static ImmutableMap<String, TokenKind> createKeywordMap() {
+    ImmutableMap.Builder<String, TokenKind> builder = ImmutableMap.builder();
+    for (TokenKind kind : TokenKind.KEYWORDS) {
+      builder.put(kind.toString(), kind);
+    }
+    return builder.build();
+  }
+
+  private TokenKind getTokenKindForIdentfier(String id) {
+    TokenKind kind = KEYWORD_MAP.get(id);
+    return kind == null ? TokenKind.IDENTIFIER : kind;
+  }
+
+  private String scanIdentifier() {
+    int oldPos = pos - 1;
+    while (pos < buffer.length) {
+      switch (buffer[pos]) {
+        case '_':
+        case 'a':
+        case 'b':
+        case 'c':
+        case 'd':
+        case 'e':
+        case 'f':
+        case 'g':
+        case 'h':
+        case 'i':
+        case 'j':
+        case 'k':
+        case 'l':
+        case 'm':
+        case 'n':
+        case 'o':
+        case 'p':
+        case 'q':
+        case 'r':
+        case 's':
+        case 't':
+        case 'u':
+        case 'v':
+        case 'w':
+        case 'x':
+        case 'y':
+        case 'z':
+        case 'A':
+        case 'B':
+        case 'C':
+        case 'D':
+        case 'E':
+        case 'F':
+        case 'G':
+        case 'H':
+        case 'I':
+        case 'J':
+        case 'K':
+        case 'L':
+        case 'M':
+        case 'N':
+        case 'O':
+        case 'P':
+        case 'Q':
+        case 'R':
+        case 'S':
+        case 'T':
+        case 'U':
+        case 'V':
+        case 'W':
+        case 'X':
+        case 'Y':
+        case 'Z':
+        case '0':
+        case '1':
+        case '2':
+        case '3':
+        case '4':
+        case '5':
+        case '6':
+        case '7':
+        case '8':
+        case '9':
+          pos++;
+          break;
+        default:
+          return bufferSlice(oldPos, pos);
+      }
+    }
+    return bufferSlice(oldPos, pos);
+  }
+
+  /**
+   * Scans an identifier or keyword.
+   *
+   * <p>ON ENTRY: 'pos' is 1 + the index of the first char in the identifier. ON EXIT: 'pos' is 1 +
+   * the index of the last char in the identifier.
+   *
+   * @return the identifier or keyword token.
+   */
+  private void addIdentifierOrKeyword() {
+    int oldPos = pos - 1;
+    String id = scanIdentifier();
+    TokenKind kind = getTokenKindForIdentfier(id);
+    addToken(kind, oldPos, pos, (kind == TokenKind.IDENTIFIER) ? id : null);
+  }
+
+  private String scanInteger() {
+    int oldPos = pos - 1;
+    while (pos < buffer.length) {
+      char c = buffer[pos];
+      switch (c) {
+        case 'X':
+        case 'x':
+        case 'a':
+        case 'A':
+        case 'b':
+        case 'B':
+        case 'c':
+        case 'C':
+        case 'd':
+        case 'D':
+        case 'e':
+        case 'E':
+        case 'f':
+        case 'F':
+        case '0':
+        case '1':
+        case '2':
+        case '3':
+        case '4':
+        case '5':
+        case '6':
+        case '7':
+        case '8':
+        case '9':
+          pos++;
+          break;
+        default:
+          return bufferSlice(oldPos, pos);
+      }
+    }
+    return bufferSlice(oldPos, pos);
+  }
+
+  /**
+   * Scans an addInteger literal.
+   *
+   * <p>ON ENTRY: 'pos' is 1 + the index of the first char in the literal. ON EXIT: 'pos' is 1 + the
+   * index of the last char in the literal.
+   */
+  private void addInteger() {
+    int oldPos = pos - 1;
+    String literal = scanInteger();
+
+    final String substring;
+    final int radix;
+    if (literal.startsWith("0x") || literal.startsWith("0X")) {
+      radix = 16;
+      substring = literal.substring(2);
+    } else if (literal.startsWith("0") && literal.length() > 1) {
+      radix = 8;
+      substring = literal.substring(1);
+    } else {
+      radix = 10;
+      substring = literal;
+    }
+
+    int value = 0;
+    try {
+      value = Integer.parseInt(substring, radix);
+    } catch (NumberFormatException e) {
+      error("invalid base-" + radix + " integer constant: " + literal);
+    }
+
+    addToken(TokenKind.INT, oldPos, pos, value);
+  }
+
+  /**
+   * Tokenizes a two-char operator.
+   *
+   * @return true if it tokenized an operator
+   */
+  private boolean tokenizeTwoChars() {
+    if (pos + 2 >= buffer.length) {
+      return false;
+    }
+    char c1 = buffer[pos];
+    char c2 = buffer[pos + 1];
+    TokenKind tok = null;
+    if (c2 == '=') {
+      tok = EQUAL_TOKENS.get(c1);
+    } else if (c2 == '*' && c1 == '*') {
+      tok = TokenKind.STAR_STAR;
+    }
+    if (tok == null) {
+      return false;
+    }
+    addToken(tok, pos, pos + 2);
+    return true;
+  }
+
+  /** Performs tokenization of the character buffer of file contents provided to the constructor. */
+  private void tokenize() {
+    while (pos < buffer.length) {
+      if (tokenizeTwoChars()) {
+        pos += 2;
+        continue;
+      }
+      char c = buffer[pos];
+      pos++;
+      switch (c) {
+        case '{':
+          {
+            addToken(TokenKind.LBRACE, pos - 1, pos);
+            openParenStackDepth++;
+            break;
+          }
+        case '}':
+          {
+            addToken(TokenKind.RBRACE, pos - 1, pos);
+            popParen();
+            break;
+          }
+        case '(':
+          {
+            addToken(TokenKind.LPAREN, pos - 1, pos);
+            openParenStackDepth++;
+            break;
+          }
+        case ')':
+          {
+            addToken(TokenKind.RPAREN, pos - 1, pos);
+            popParen();
+            break;
+          }
+        case '[':
+          {
+            addToken(TokenKind.LBRACKET, pos - 1, pos);
+            openParenStackDepth++;
+            break;
+          }
+        case ']':
+          {
+            addToken(TokenKind.RBRACKET, pos - 1, pos);
+            popParen();
+            break;
+          }
+        case '>':
+          {
+            addToken(TokenKind.GREATER, pos - 1, pos);
+            break;
+          }
+        case '<':
+          {
+            addToken(TokenKind.LESS, pos - 1, pos);
+            break;
+          }
+        case ':':
+          {
+            addToken(TokenKind.COLON, pos - 1, pos);
+            break;
+          }
+        case ',':
+          {
+            addToken(TokenKind.COMMA, pos - 1, pos);
+            break;
+          }
+        case '+':
+          {
+            addToken(TokenKind.PLUS, pos - 1, pos);
+            break;
+          }
+        case '-':
+          {
+            addToken(TokenKind.MINUS, pos - 1, pos);
+            break;
+          }
+        case '|':
+          {
+            addToken(TokenKind.PIPE, pos - 1, pos);
+            break;
+          }
+        case '=':
+          {
+            addToken(TokenKind.EQUALS, pos - 1, pos);
+            break;
+          }
+        case '%':
+          {
+            addToken(TokenKind.PERCENT, pos - 1, pos);
+            break;
+          }
+        case '/':
+          {
+            addToken(TokenKind.SLASH, pos - 1, pos);
+            break;
+          }
+        case ';':
+          {
+            addToken(TokenKind.SEMI, pos - 1, pos);
+            break;
+          }
+        case '.':
+          {
+            addToken(TokenKind.DOT, pos - 1, pos);
+            break;
+          }
+        case '*':
+          {
+            addToken(TokenKind.STAR, pos - 1, pos);
+            break;
+          }
+        case ' ':
+        case '\t':
+        case '\r':
+          {
+            addWhitespace();
+            break;
+          }
+        case '\\':
+          {
+            // Backslash character is valid only at the end of a line (or in a string)
+            if (pos + 1 < buffer.length && buffer[pos] == '\n') {
+              // treat end of line backslash and newline char as whitespace
+              // (they're ignored by the parser)
+              pos++;
+              addToken(TokenKind.WHITESPACE, pos - 2, pos, Character.toString(c));
+            } else {
+              addToken(TokenKind.ILLEGAL, pos - 1, pos, Character.toString(c));
+            }
+            break;
+          }
+        case '\n':
+          {
+            newline();
+            break;
+          }
+        case '#':
+          {
+            int oldPos = pos - 1;
+            while (pos < buffer.length) {
+              c = buffer[pos];
+              if (c == '\n') {
+                break;
+              } else {
+                pos++;
+              }
+            }
+            addToken(TokenKind.COMMENT, oldPos, pos, bufferSlice(oldPos, pos));
+            break;
+          }
+        case '\'':
+        case '\"':
+          {
+            addStringLiteral(c, false);
+            break;
+          }
+        default:
+          {
+            // detect raw strings, e.g. r"str"
+            if (c == 'r' && pos < buffer.length && (buffer[pos] == '\'' || buffer[pos] == '\"')) {
+              c = buffer[pos];
+              pos++;
+              addStringLiteral(c, true);
+              break;
+            }
+
+            if (Character.isDigit(c)) {
+              addInteger();
+            } else if (Character.isJavaIdentifierStart(c) && c != '$') {
+              addIdentifierOrKeyword();
+            } else {
+              // Some characters in Python are not recognized in Blaze syntax (e.g. '!')
+              addToken(TokenKind.ILLEGAL, pos - 1, pos, Character.toString(c));
+              error("invalid character: '" + c + "'");
+            }
+            break;
+          } // default
+      } // switch
+    } // while
+  }
+
+  /**
+   * Returns parts of the source buffer based on offsets
+   *
+   * @param start the beginning offset for the slice
+   * @param end the offset immediately following the slice
+   * @return the text at offset start with length end - start
+   */
+  private String bufferSlice(int start, int end) {
+    return new String(this.buffer, start, end - start);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/lexer/BuildToken.java b/base/src/com/google/idea/blaze/base/lang/buildfile/lexer/BuildToken.java
new file mode 100644
index 0000000..afe9252
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/lexer/BuildToken.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.lang.buildfile.lexer;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.idea.blaze.base.lang.buildfile.language.BuildFileType;
+import com.intellij.psi.tree.IElementType;
+import com.intellij.psi.tree.TokenSet;
+
+/** The IElementTypes used by the BUILD language */
+public class BuildToken extends IElementType {
+
+  private static ImmutableMap<TokenKind, BuildToken> types = createMap();
+
+  private static ImmutableMap<TokenKind, BuildToken> createMap() {
+    ImmutableMap.Builder<TokenKind, BuildToken> builder = ImmutableMap.builder();
+    for (TokenKind kind : TokenKind.values()) {
+      builder.put(kind, new BuildToken(kind));
+    }
+    return builder.build();
+  }
+
+  public static BuildToken fromKind(TokenKind kind) {
+    return types.get(kind);
+  }
+
+  public static final BuildToken IDENTIFIER = fromKind(TokenKind.IDENTIFIER);
+
+  public static final TokenSet WHITESPACE_AND_NEWLINE =
+      TokenSet.create(fromKind(TokenKind.WHITESPACE), fromKind(TokenKind.NEWLINE));
+
+  public final TokenKind kind;
+
+  private BuildToken(TokenKind kind) {
+    super(kind.name(), BuildFileType.INSTANCE.getLanguage());
+    this.kind = kind;
+  }
+
+  @Override
+  public String toString() {
+    return kind.toString();
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/lexer/Token.java b/base/src/com/google/idea/blaze/base/lang/buildfile/lexer/Token.java
new file mode 100644
index 0000000..42a05f6
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/lexer/Token.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.base.lang.buildfile.lexer;
+
+/**
+ * A Token represents an actual lexeme; that is, a lexical unit, its location in the input text, its
+ * lexical kind, and any associated value.
+ */
+public class Token {
+
+  public final TokenKind kind;
+  public final int left;
+  public final int right;
+  public final Object value;
+
+  public Token(TokenKind kind, int left, int right) {
+    this(kind, left, right, null);
+  }
+
+  public Token(TokenKind kind, int left, int right, Object value) {
+    this.kind = kind;
+    this.left = left;
+    this.right = right;
+    this.value = value;
+  }
+
+  /**
+   * Constructs an easy-to-read string representation of token, suitable for use in user error
+   * messages.
+   */
+  @Override
+  public String toString() {
+    if (kind == TokenKind.STRING) {
+      return "\"" + value + "\"";
+    }
+    return value == null ? kind.toString() : value.toString();
+  }
+}
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
new file mode 100644
index 0000000..c33754f
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/lexer/TokenKind.java
@@ -0,0 +1,140 @@
+/*
+ * 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.lexer;
+
+import com.google.common.collect.ImmutableSet;
+
+/** A TokenKind is an enumeration of each different kind of lexical symbol. */
+public enum TokenKind {
+  ASSERT("assert"),
+  AND("and"),
+  AS("as"),
+  BREAK("break"),
+  CLASS("class"),
+  COLON(":"),
+  COMMA(","),
+  COMMENT("comment"),
+  CONTINUE("continue"),
+  DEF("def"),
+  DEL("del"),
+  DOT("."),
+  ELIF("elif"),
+  ELSE("else"),
+  EOF("EOF"),
+  EQUALS("="),
+  EQUALS_EQUALS("=="),
+  EXCEPT("except"),
+  FALSE("False"),
+  FINALLY("finally"),
+  FOR("for"),
+  FROM("from"),
+  GLOBAL("global"),
+  GREATER(">"),
+  GREATER_EQUALS(">="),
+  IDENTIFIER("identifier"),
+  IF("if"),
+  ILLEGAL("illegal character"),
+  IMPORT("import"),
+  IN("in"),
+  INDENT("indent"),
+  INT("integer"),
+  IS("is"),
+  LAMBDA("lambda"),
+  LBRACE("{"),
+  LBRACKET("["),
+  LESS("<"),
+  LESS_EQUALS("<="),
+  LOAD("load"),
+  LPAREN("("),
+  MINUS("-"),
+  NEWLINE("newline"),
+  NONLOCAL("nonlocal"),
+  NOT("not"),
+  NOT_EQUALS("!="),
+  NOT_IN("not in"), // used internally by the parser; not directly created by the lexer
+  OR("or"),
+  DEDENT("dedent"),
+  PASS("pass"),
+  PERCENT("%"),
+  PIPE("|"),
+  PLUS("+"),
+  PLUS_EQUALS("+="),
+  MINUS_EQUALS("-="),
+  STAR_EQUALS("*="),
+  SLASH_EQUALS("/="),
+  PERCENT_EQUALS("%="),
+  RAISE("raise"),
+  RBRACE("}"),
+  RBRACKET("]"),
+  RETURN("return"),
+  RPAREN(")"),
+  SEMI(";"),
+  SLASH("/"),
+  STAR("*"),
+  STAR_STAR("**"),
+  STRING("string"),
+  TRUE("True"),
+  TRY("try"),
+  WHILE("while"),
+  WITH("with"),
+  YIELD("yield"),
+  // We need to tokenize all characters.
+  // Whitespace will be used for all tokens which should be ignored by the parser.
+  WHITESPACE("whitespace");
+
+  private final String name;
+
+  TokenKind(String name) {
+    this.name = name;
+  }
+
+  /**
+   * This is a user-friendly name. For keywords (if, yield, True, etc.), it's also the exact
+   * character sequence used by the lexer.
+   */
+  @Override
+  public String toString() {
+    return name;
+  }
+
+  public static 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);
+
+  public static ImmutableSet<TokenKind> OPERATIONS =
+      ImmutableSet.of(
+          AND,
+          EQUALS_EQUALS,
+          GREATER,
+          GREATER_EQUALS,
+          IN,
+          LESS,
+          LESS_EQUALS,
+          MINUS,
+          NOT_EQUALS,
+          NOT_IN,
+          OR,
+          PERCENT,
+          SLASH,
+          PLUS,
+          PIPE,
+          STAR);
+
+  public static 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
new file mode 100644
index 0000000..c195cc1
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/parser/BuildParserDefinition.java
@@ -0,0 +1,117 @@
+/*
+ * 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.parser;
+
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.lang.buildfile.lexer.BuildLexer;
+import com.google.idea.blaze.base.lang.buildfile.lexer.BuildLexerBase;
+import com.google.idea.blaze.base.lang.buildfile.lexer.BuildToken;
+import com.google.idea.blaze.base.lang.buildfile.lexer.TokenKind;
+import com.google.idea.blaze.base.lang.buildfile.psi.BuildElementType;
+import com.google.idea.blaze.base.lang.buildfile.psi.BuildElementTypes;
+import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
+import com.google.idea.common.experiments.DeveloperFlag;
+import com.intellij.extapi.psi.ASTWrapperPsiElement;
+import com.intellij.lang.ASTNode;
+import com.intellij.lang.ParserDefinition;
+import com.intellij.lang.PsiBuilder;
+import com.intellij.lang.PsiParser;
+import com.intellij.lexer.Lexer;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.FileViewProvider;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.impl.source.resolve.FileContextUtil;
+import com.intellij.psi.tree.IElementType;
+import com.intellij.psi.tree.IFileElementType;
+import com.intellij.psi.tree.TokenSet;
+
+/** Defines the BUILD file parser */
+public class BuildParserDefinition implements ParserDefinition {
+
+  private static final DeveloperFlag DEBUG = new DeveloperFlag("build.file.debug.mode");
+
+  @Override
+  public Lexer createLexer(Project project) {
+    return new BuildLexer(BuildLexerBase.LexerMode.Parsing);
+  }
+
+  @Override
+  public PsiParser createParser(Project project) {
+    return new BuildParser();
+  }
+
+  @Override
+  public IFileElementType getFileNodeType() {
+    return BuildElementTypes.BUILD_FILE;
+  }
+
+  @Override
+  public TokenSet getWhitespaceTokens() {
+    return convert(TokenKind.WHITESPACE, TokenKind.ILLEGAL);
+  }
+
+  @Override
+  public TokenSet getCommentTokens() {
+    return convert(TokenKind.COMMENT);
+  }
+
+  @Override
+  public TokenSet getStringLiteralElements() {
+    return convert(TokenKind.STRING);
+  }
+
+  @Override
+  public PsiElement createElement(ASTNode node) {
+    IElementType type = node.getElementType();
+    if (type instanceof BuildElementType) {
+      return ((BuildElementType) type).createElement(node);
+    }
+    return new ASTWrapperPsiElement(node);
+  }
+
+  @Override
+  public PsiFile createFile(FileViewProvider viewProvider) {
+    return new BuildFile(viewProvider);
+  }
+
+  @Override
+  public SpaceRequirements spaceExistanceTypeBetweenTokens(ASTNode left, ASTNode right) {
+    return SpaceRequirements.MAY;
+  }
+
+  private static TokenSet convert(TokenKind... blazeTokens) {
+    return TokenSet.create(
+        Lists.newArrayList(blazeTokens)
+            .stream()
+            .map(BuildToken::fromKind)
+            .toArray(IElementType[]::new));
+  }
+
+  private static class BuildParser implements PsiParser {
+    @Override
+    public ASTNode parse(IElementType root, PsiBuilder builder) {
+      if (DEBUG.getValue()) {
+        System.err.println(builder.getUserDataUnprotected(FileContextUtil.CONTAINING_FILE_KEY));
+      }
+      PsiBuilder.Marker rootMarker = builder.mark();
+      ParsingContext context = new ParsingContext(builder);
+      context.statementParser.parseFileInput();
+      rootMarker.done(root);
+      return builder.getTreeBuilt();
+    }
+  }
+}
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
new file mode 100644
index 0000000..9c20fd4
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/parser/ExpressionParsing.java
@@ -0,0 +1,533 @@
+/*
+ * 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.parser;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.idea.blaze.base.lang.buildfile.lexer.TokenKind;
+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);
+
+  private static final ImmutableSet<TokenKind> DICT_TERMINATOR_SET =
+      ImmutableSet.of(TokenKind.EOF, TokenKind.RBRACE, TokenKind.SEMI);
+
+  private static final ImmutableSet<TokenKind> EXPR_LIST_TERMINATOR_SET =
+      ImmutableSet.of(
+          TokenKind.EOF,
+          TokenKind.NEWLINE,
+          TokenKind.EQUALS,
+          TokenKind.RBRACE,
+          TokenKind.RBRACKET,
+          TokenKind.RPAREN,
+          TokenKind.SEMI);
+
+  private static final ImmutableSet<TokenKind> EXPR_TERMINATOR_SET =
+      ImmutableSet.of(
+          TokenKind.EOF,
+          TokenKind.COLON,
+          TokenKind.COMMA,
+          TokenKind.FOR,
+          TokenKind.MINUS,
+          TokenKind.PERCENT,
+          TokenKind.PLUS,
+          TokenKind.RBRACKET,
+          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);
+
+  /**
+   * Highest precedence goes last. Based on:
+   * http://docs.python.org/2/reference/expressions.html#operator-precedence
+   */
+  private static final List<EnumSet<TokenKind>> OPERATOR_PRECEDENCE =
+      ImmutableList.of(
+          EnumSet.of(TokenKind.OR),
+          EnumSet.of(TokenKind.AND),
+          EnumSet.of(TokenKind.NOT),
+          EnumSet.of(
+              TokenKind.EQUALS_EQUALS,
+              TokenKind.NOT_EQUALS,
+              TokenKind.LESS,
+              TokenKind.LESS_EQUALS,
+              TokenKind.GREATER,
+              TokenKind.GREATER_EQUALS,
+              TokenKind.IN,
+              TokenKind.NOT_IN),
+          EnumSet.of(TokenKind.PIPE),
+          EnumSet.of(TokenKind.MINUS, TokenKind.PLUS),
+          EnumSet.of(TokenKind.SLASH, TokenKind.STAR, TokenKind.PERCENT));
+
+  public ExpressionParsing(ParsingContext context) {
+    super(context);
+  }
+
+  public void parseExpression(boolean insideParens) {
+    // handle lists without parens (e.g. 'a,b,c = 1')
+    PsiBuilder.Marker marker = insideParens ? null : builder.mark();
+    parseNonTupleExpression();
+    if (currentToken() == TokenKind.COMMA) {
+      parseExpressionList();
+      if (marker != null) {
+        marker.done(BuildElementTypes.LIST_LITERAL);
+      }
+    } else if (marker != null) {
+      marker.drop();
+    }
+  }
+
+  // expr_list ::= ( ',' expr )* ','?
+  private void parseExpressionList() {
+    while (matches(TokenKind.COMMA)) {
+      if (atAnyOfTokens(EXPR_LIST_TERMINATOR_SET)) {
+        break;
+      }
+      parseNonTupleExpression();
+    }
+  }
+
+  protected void parseNonTupleExpression() {
+    parseNonTupleExpression(0);
+    // don't bother including conditional expressions for now,
+    //just include their components serially
+    if (matches(TokenKind.IF)) {
+      parseNonTupleExpression(0);
+      if (matches(TokenKind.ELSE)) {
+        parseNonTupleExpression();
+      }
+    }
+  }
+
+  private void parseNonTupleExpression(int prec) {
+    if (prec >= OPERATOR_PRECEDENCE.size()) {
+      parsePrimaryWithSuffix();
+      return;
+    }
+    if (currentToken() == TokenKind.NOT && OPERATOR_PRECEDENCE.get(prec).contains(TokenKind.NOT)) {
+      // special case handling of multi-token 'NOT IN' binary operator
+      if (kindFromElement(builder.lookAhead(1)) != TokenKind.IN) {
+        // skip the 'not' -- no need for a specific 'not' expression
+        builder.advanceLexer();
+        parseNonTupleExpression(prec + 1);
+        return;
+      }
+    }
+    parseBinOpExpression(prec);
+  }
+
+  /**
+   * binop_expression ::= binop_expression OP binop_expression | parsePrimaryWithSuffix This
+   * function takes care of precedence between operators (see OPERATOR_PRECEDENCE for the order),
+   * and it assumes left-to-right associativity.
+   */
+  private void parseBinOpExpression(int prec) {
+    PsiBuilder.Marker marker = builder.mark();
+    parseNonTupleExpression(prec + 1);
+
+    while (true) {
+      if (!atBinaryOperator(prec)) {
+        marker.drop();
+        return;
+      }
+      parseNonTupleExpression(prec + 1);
+      marker.done(BuildElementTypes.BINARY_OP_EXPRESSION);
+      marker = marker.precede();
+    }
+  }
+
+  /**
+   * Consumes current token iff it's a binary operator at the given precedence level (with
+   * special-case handling of 'NOT' 'IN' double token binary operator)
+   */
+  private boolean atBinaryOperator(int prec) {
+    if (matchesAnyOf(OPERATOR_PRECEDENCE.get(prec))) {
+      return true;
+    }
+    if (matchesSequence(TokenKind.NOT, TokenKind.IN)) {
+      return true;
+    }
+    return false;
+  }
+
+  // primary_with_suffix ::= primary (selector_suffix | substring_suffix)*
+  private void parsePrimaryWithSuffix() {
+    PsiBuilder.Marker marker = builder.mark();
+    parsePrimary();
+    while (true) {
+      if (matches(TokenKind.DOT)) {
+        marker = parseSelectorSuffix(marker);
+      } else if (matches(TokenKind.LBRACKET)) {
+        marker = parseSubstringSuffix(marker);
+      } else {
+        break;
+      }
+    }
+    marker.drop();
+  }
+
+  // selector_suffix ::= '.' IDENTIFIER [funcall_suffix]
+  private PsiBuilder.Marker parseSelectorSuffix(PsiBuilder.Marker marker) {
+    if (!atToken(TokenKind.IDENTIFIER)) {
+      builder.error("expected identifier after dot");
+      syncPast(EXPR_TERMINATOR_SET);
+      return marker;
+    }
+    parseTargetOrReferenceIdentifier();
+    if (atToken(TokenKind.LPAREN)) {
+      parseFuncallSuffix();
+      marker.done(BuildElementTypes.FUNCALL_EXPRESSION);
+    } else {
+      marker.done(BuildElementTypes.DOT_EXPRESSION);
+    }
+    return marker.precede();
+  }
+
+  // substring_suffix ::= '[' expression? ':' expression? ':' expression? ']'
+  private PsiBuilder.Marker parseSubstringSuffix(PsiBuilder.Marker marker) {
+    if (!atToken(TokenKind.COLON)) {
+      PsiBuilder.Marker pos = builder.mark();
+      parseExpression(false);
+      pos.done(BuildElementTypes.POSITIONAL);
+    }
+    while (!matches(TokenKind.RBRACKET)) {
+      if (expect(TokenKind.COLON)) {
+        if (!atAnyOfTokens(TokenKind.COLON, TokenKind.RBRACKET)) {
+          parseNonTupleExpression();
+        }
+      } else {
+        syncPast(EXPR_LIST_TERMINATOR_SET);
+        break;
+      }
+    }
+    marker.done(BuildElementTypes.FUNCALL_EXPRESSION);
+    return marker.precede();
+  }
+
+  private void parseTargetOrReferenceIdentifier() {
+    if (!atToken(TokenKind.IDENTIFIER)) {
+      builder.error("expected an identifier");
+      return;
+    }
+    // TODO: handle assigning to a list of targets (e.g. "a,b = 1")
+    TokenKind next = kindFromElement(builder.lookAhead(1));
+    if (next == TokenKind.EQUALS || next == TokenKind.IN) {
+      buildTokenElement(BuildElementTypes.TARGET_EXPRESSION);
+    } else {
+      buildTokenElement(BuildElementTypes.REFERENCE_EXPRESSION);
+    }
+  }
+
+  private void parsePrimary() {
+    TokenKind current = currentToken();
+    switch (current) {
+      case INT:
+        buildTokenElement(BuildElementTypes.INTEGER_LITERAL);
+        return;
+      case STRING:
+        parseStringLiteral(true);
+        return;
+      case IDENTIFIER:
+        PsiBuilder.Marker marker = builder.mark();
+        String tokenText = builder.getTokenText();
+        parseTargetOrReferenceIdentifier();
+        if (atToken(TokenKind.LPAREN)) {
+          parseFuncallSuffix();
+          marker.done(getFuncallExpressionType(tokenText));
+        } else {
+          marker.drop();
+        }
+        return;
+      case TRUE: // intentional fall-through -- both treated as vanilla identifiers
+      case FALSE:
+        buildTokenElement(BuildElementTypes.BOOLEAN_LITERAL);
+        return;
+      case LBRACKET:
+        parseListMaker();
+        return;
+      case LBRACE:
+        parseDictExpression();
+        return;
+      case LPAREN:
+        marker = builder.mark();
+        builder.advanceLexer();
+        if (matches(TokenKind.RPAREN)) {
+          marker.done(BuildElementTypes.LIST_LITERAL);
+          return;
+        }
+        parseExpression(true);
+        expect(TokenKind.RPAREN, true);
+        marker.done(BuildElementTypes.LIST_LITERAL);
+        return;
+      case MINUS:
+        marker = builder.mark();
+        builder.advanceLexer();
+        parsePrimaryWithSuffix();
+        marker.done(BuildElementTypes.POSITIONAL);
+        return;
+      default:
+        builder.error("expected an expression");
+        syncPast(EXPR_TERMINATOR_SET);
+    }
+  }
+
+  /** funcall_suffix ::= '(' arg_list? ')' arg_list ::= ((arg ',')* arg ','? )? */
+  private void parseFuncallSuffix() {
+    PsiBuilder.Marker mark = builder.mark();
+    expect(TokenKind.LPAREN, true);
+    if (matches(TokenKind.RPAREN)) {
+      mark.done(BuildElementTypes.ARGUMENT_LIST);
+      return;
+    }
+    parseFuncallArgument();
+    while (!atAnyOfTokens(FUNCALL_TERMINATOR_SET)) {
+      expect(TokenKind.COMMA);
+      if (atAnyOfTokens(FUNCALL_TERMINATOR_SET)) {
+        break;
+      }
+      parseFuncallArgument();
+    }
+    expect(TokenKind.RPAREN, true);
+    mark.done(BuildElementTypes.ARGUMENT_LIST);
+  }
+
+  private BuildElementType getFuncallExpressionType(String functionName) {
+    if ("glob".equals(functionName)) {
+      return BuildElementTypes.GLOB_EXPRESSION;
+    }
+    return BuildElementTypes.FUNCALL_EXPRESSION;
+  }
+
+  protected void parseFunctionParameters() {
+    if (atToken(TokenKind.RPAREN)) {
+      return;
+    }
+    parseFunctionParameter();
+    while (!atAnyOfTokens(FUNCALL_TERMINATOR_SET)) {
+      expect(TokenKind.COMMA);
+      if (atAnyOfTokens(FUNCALL_TERMINATOR_SET)) {
+        break;
+      }
+      parseFunctionParameter();
+    }
+  }
+
+  // arg ::= IDENTIFIER '=' nontupleexpr
+  //       | expr
+  //       | *args
+  //       | **kwargs
+  private void parseFuncallArgument() {
+    PsiBuilder.Marker marker = builder.mark();
+    if (matches(TokenKind.STAR_STAR)) {
+      parseNonTupleExpression();
+      marker.done(BuildElementTypes.STAR_STAR);
+      return;
+    }
+    if (matches(TokenKind.STAR)) {
+      parseNonTupleExpression();
+      marker.done(BuildElementTypes.STAR);
+      return;
+    }
+    if (matchesSequence(TokenKind.IDENTIFIER, TokenKind.EQUALS)) {
+      parseNonTupleExpression();
+      marker.done(BuildElementTypes.KEYWORD);
+      return;
+    }
+    parseNonTupleExpression();
+    marker.done(BuildElementTypes.POSITIONAL);
+  }
+
+  /** arg ::= IDENTIFIER ['=' nontupleexpr] */
+  private void parseFunctionParameter() {
+    PsiBuilder.Marker marker = builder.mark();
+    if (matches(TokenKind.STAR_STAR)) {
+      expectIdentifier("invalid parameter name");
+      marker.done(BuildElementTypes.PARAM_STAR_STAR);
+      return;
+    }
+    if (matches(TokenKind.STAR)) {
+      if (atToken(TokenKind.IDENTIFIER)) {
+        builder.advanceLexer();
+      }
+      marker.done(BuildElementTypes.PARAM_STAR);
+      return;
+    }
+    expectIdentifier("invalid parameter name");
+    if (matches(TokenKind.EQUALS)) {
+      parseNonTupleExpression();
+      marker.done(BuildElementTypes.PARAM_OPTIONAL);
+      return;
+    }
+    marker.done(BuildElementTypes.PARAM_MANDATORY);
+  }
+
+  protected void expectIdentifier(String error) {
+    expect(TokenKind.IDENTIFIER, error, true);
+  }
+
+  // list_maker ::= '[' ']'
+  //               |'[' expr ']'
+  //               |'[' expr expr_list ']'
+  //               |'[' expr ('FOR' loop_variables 'IN' expr)+ ']'
+  private void parseListMaker() {
+    PsiBuilder.Marker marker = builder.mark();
+    expect(TokenKind.LBRACKET);
+    if (matches(TokenKind.RBRACKET)) {
+      marker.done(BuildElementTypes.LIST_LITERAL);
+      return;
+    }
+    parseNonTupleExpression();
+    switch (currentToken()) {
+      case RBRACKET:
+        builder.advanceLexer();
+        marker.done(BuildElementTypes.LIST_LITERAL);
+        return;
+      case FOR:
+        parseComprehensionSuffix(TokenKind.RBRACKET);
+        marker.done(BuildElementTypes.LIST_COMPREHENSION_EXPR);
+        return;
+      case COMMA:
+        parseExpressionList();
+        if (!matches(TokenKind.RBRACKET)) {
+          builder.error("expected 'for' or ']'");
+          syncPast(LIST_TERMINATOR_SET);
+        }
+        marker.done(BuildElementTypes.LIST_LITERAL);
+        return;
+      default:
+        builder.error("expected ',', 'for' or ']'");
+        syncPast(LIST_TERMINATOR_SET);
+        marker.done(BuildElementTypes.LIST_LITERAL);
+    }
+  }
+
+  // dict_expression ::= '{' '}'
+  //                    |'{' dict_entry_list '}'
+  //                    |'{' dict_entry 'FOR' loop_variables 'IN' expr '}'
+  private void parseDictExpression() {
+    PsiBuilder.Marker marker = builder.mark();
+    expect(TokenKind.LBRACE, true);
+    if (matches(TokenKind.RBRACE)) {
+      marker.done(BuildElementTypes.DICTIONARY_LITERAL);
+      return;
+    }
+    parseDictEntry();
+    if (currentToken() == TokenKind.FOR) {
+      parseComprehensionSuffix(TokenKind.RBRACE);
+      marker.done(BuildElementTypes.LIST_COMPREHENSION_EXPR);
+      return;
+    }
+    if (matches(TokenKind.COMMA)) {
+      parseDictEntryList();
+    }
+    expect(TokenKind.RBRACE, true);
+    marker.done(BuildElementTypes.DICTIONARY_LITERAL);
+  }
+
+  // dict_entry_list ::= ( (dict_entry ',')* dict_entry ','? )?
+  private void parseDictEntryList() {
+    if (atAnyOfTokens(DICT_TERMINATOR_SET)) {
+      return;
+    }
+    parseDictEntry();
+    while (matches(TokenKind.COMMA)) {
+      if (atAnyOfTokens(DICT_TERMINATOR_SET)) {
+        return;
+      }
+      parseDictEntry();
+    }
+  }
+
+  // dict_entry ::= nontupleexpr ':' nontupleexpr
+  private void parseDictEntry() {
+    PsiBuilder.Marker marker = builder.mark();
+    parseNonTupleExpression();
+    expect(TokenKind.COLON);
+    parseNonTupleExpression();
+    marker.done(BuildElementTypes.DICTIONARY_ENTRY_LITERAL);
+  }
+
+  // comprehension_suffix ::= 'FOR' loop_variables 'IN' expr comprehension_suffix
+  //                        | 'IF' expr comprehension_suffix
+  //                        | ']'
+  private void parseComprehensionSuffix(TokenKind closingBracket) {
+    while (true) {
+      if (matches(TokenKind.FOR)) {
+        parseForLoopVariables();
+        expect(TokenKind.IN);
+        parseNonTupleExpression(0);
+      } else if (matches(TokenKind.IF)) {
+        parseExpression(true);
+      } else if (matches(closingBracket)) {
+        return;
+      } else {
+        builder.error("expected " + closingBracket + ", 'for' or 'if'");
+        syncPast(EXPR_LIST_TERMINATOR_SET);
+        return;
+      }
+    }
+  }
+
+  // Equivalent to 'exprlist' rule in Python grammar.
+  // loop_variables ::= primary_with_suffix ( ',' primary_with_suffix )* ','?
+  protected void parseForLoopVariables() {
+    PsiBuilder.Marker marker = builder.mark();
+    parsePrimaryWithSuffix();
+    if (currentToken() != TokenKind.COMMA) {
+      marker.drop();
+      return;
+    }
+    while (matches(TokenKind.COMMA)) {
+      if (atAnyOfTokens(EXPR_LIST_TERMINATOR_SET)) {
+        break;
+      }
+      parsePrimaryWithSuffix();
+    }
+    marker.done(BuildElementTypes.LIST_LITERAL);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/parser/Parsing.java b/base/src/com/google/idea/blaze/base/lang/buildfile/parser/Parsing.java
new file mode 100644
index 0000000..7b852b5
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/parser/Parsing.java
@@ -0,0 +1,262 @@
+/*
+ * 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.parser;
+
+import com.google.idea.blaze.base.lang.buildfile.lexer.BuildToken;
+import com.google.idea.blaze.base.lang.buildfile.lexer.TokenKind;
+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.psi.tree.IElementType;
+import java.util.EnumSet;
+import java.util.Set;
+import javax.annotation.Nullable;
+
+/** Base class for BUILD file component parsers */
+public abstract class Parsing {
+
+  // Keywords that exist in Python which we don't parse.
+  protected static final EnumSet<TokenKind> FORBIDDEN_KEYWORDS =
+      EnumSet.of(
+          TokenKind.AS,
+          TokenKind.ASSERT,
+          TokenKind.DEL,
+          TokenKind.EXCEPT,
+          TokenKind.FINALLY,
+          TokenKind.FROM,
+          TokenKind.GLOBAL,
+          TokenKind.IMPORT,
+          TokenKind.IS,
+          TokenKind.LAMBDA,
+          TokenKind.NONLOCAL,
+          TokenKind.RAISE,
+          TokenKind.TRY,
+          TokenKind.WITH,
+          TokenKind.WHILE,
+          TokenKind.YIELD);
+
+  protected ParsingContext context;
+  protected PsiBuilder builder;
+
+  public Parsing(ParsingContext context) {
+    this.context = context;
+    this.builder = context.builder;
+  }
+
+  protected ExpressionParsing getExpressionParser() {
+    return context.expressionParser;
+  }
+
+  /** @return true if a string was parsed */
+  protected boolean parseStringLiteral(boolean alwaysConsume) {
+    if (currentToken() != TokenKind.STRING) {
+      expect(TokenKind.STRING, alwaysConsume);
+      return false;
+    }
+    buildTokenElement(BuildElementTypes.STRING_LITERAL);
+    if (currentToken() == TokenKind.STRING) {
+      builder.error("implicit string concatenation is forbidden; use the '+' operator");
+    }
+    return true;
+  }
+
+  protected void buildTokenElement(BuildElementType type) {
+    PsiBuilder.Marker marker = builder.mark();
+    builder.advanceLexer();
+    marker.done(type);
+  }
+
+  /**
+   * Consume tokens until we reach the first token that has a kind that is in the set of
+   * terminatingTokens.
+   */
+  protected void syncTo(Set<TokenKind> terminatingTokens) {
+    // read past the problematic token
+    while (!atAnyOfTokens(terminatingTokens)) {
+      builder.advanceLexer();
+    }
+  }
+
+  /**
+   * Consume tokens until we consume the first token that has a kind that is in the set of
+   * terminatingTokens.
+   */
+  protected void syncPast(Set<TokenKind> terminatingTokens) {
+    // read past the problematic token
+    while (!matchesAnyOf(terminatingTokens)) {
+      builder.advanceLexer();
+    }
+  }
+
+  /**
+   * Consumes the current token iff it's one of the expected types.<br>
+   * Otherwise, returns false and reports an error.
+   */
+  protected boolean expect(TokenKind kind) {
+    return expect(kind, false);
+  }
+
+  /**
+   * Consumes the current token if 'alwaysConsume' is true or if it's one of the expected types.<br>
+   * Otherwise, returns false and reports an error.
+   */
+  protected boolean expect(TokenKind kind, boolean alwaysConsume) {
+    return expect(kind, String.format("'%s' expected", kind), alwaysConsume);
+  }
+
+  /**
+   * Consumes the current token if 'alwaysConsume' is true or if it's one of the expected types.<br>
+   * Otherwise, returns false and reports an error.
+   */
+  protected boolean expect(TokenKind kind, String message, boolean alwaysConsume) {
+    TokenKind current = currentToken();
+    if (current == kind || alwaysConsume) {
+      builder.advanceLexer();
+    }
+    if (current != kind) {
+      builder.error(message);
+      return false;
+    }
+    return true;
+  }
+
+  /** Checks if we're at the current sequence of tokens. If so, consumes them. */
+  protected boolean matchesSequence(TokenKind... kinds) {
+    PsiBuilder.Marker marker = builder.mark();
+    for (TokenKind kind : kinds) {
+      if (!matches(kind)) {
+        marker.rollbackTo();
+        return false;
+      }
+    }
+    marker.drop();
+    return true;
+  }
+
+  /** Consumes the current token iff it matches the expected type. Otherwise, returns false */
+  protected boolean matches(TokenKind kind) {
+    if (currentToken() == kind) {
+      builder.advanceLexer();
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * Consumes the current token iff it matches one of the expected types. Otherwise, returns false
+   */
+  protected boolean matchesAnyOf(TokenKind... kinds) {
+    TokenKind current = currentToken();
+    for (TokenKind kind : kinds) {
+      if (kind == current) {
+        builder.advanceLexer();
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /** Consumes the current token iff it's one of the expected types. Otherwise, returns false */
+  protected boolean matchesAnyOf(Set<TokenKind> kinds) {
+    if (kinds.contains(currentToken())) {
+      builder.advanceLexer();
+      return true;
+    }
+    return false;
+  }
+
+  /** Checks if the upcoming sequence of tokens match that expected. Doesn't advance the parser. */
+  protected boolean atTokenSequence(TokenKind... kinds) {
+    for (int i = 0; i < kinds.length; i++) {
+      if (kindFromElement(builder.lookAhead(i)) != kinds[i]) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /** Checks if the current token matches the expected kind. Doesn't advance the parser. */
+  protected boolean atToken(TokenKind kind) {
+    return currentToken() == kind;
+  }
+
+  /**
+   * Checks if the current token matches any one of the expected kinds. Doesn't advance the parser.
+   */
+  protected boolean atAnyOfTokens(TokenKind... kinds) {
+    TokenKind current = currentToken();
+    for (TokenKind kind : kinds) {
+      if (current == kind) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Checks if the current token matches any one of the expected kinds. Doesn't advance the parser.
+   */
+  protected boolean atAnyOfTokens(Set<TokenKind> kinds) {
+    return kinds.contains(currentToken());
+  }
+
+  @Nullable
+  protected TokenKind currentToken() {
+    return builder.eof() ? TokenKind.EOF : kindFromElement(builder.getTokenType());
+  }
+
+  @Nullable
+  protected TokenKind kindFromElement(IElementType type) {
+    if (type == null) {
+      return null;
+    }
+    if (!(type instanceof BuildToken)) {
+      throw new RuntimeException("Invalid type: " + type + " of class " + type.getClass());
+    }
+    TokenKind kind = ((BuildToken) type).kind;
+    checkForbiddenKeywords(kind);
+    return kind;
+  }
+
+  private void checkForbiddenKeywords(TokenKind kind) {
+    if (!FORBIDDEN_KEYWORDS.contains(kind)) {
+      return;
+    }
+    builder.error(forbiddenKeywordError(kind));
+  }
+
+  protected String forbiddenKeywordError(TokenKind kind) {
+    assert FORBIDDEN_KEYWORDS.contains(kind);
+    switch (kind) {
+      case ASSERT:
+        return "'assert' not supported, use 'fail' instead";
+      case TRY:
+        return "'try' not supported, all exceptions are fatal";
+      case IMPORT:
+        return "'import' not supported, use 'load' instead";
+      case IS:
+        return "'is' not supported, use '==' instead";
+      case LAMBDA:
+        return "'lambda' not supported, declare a function instead";
+      case RAISE:
+        return "'raise' not supported, use 'fail' instead";
+      case WHILE:
+        return "'while' not supported, use 'for' instead";
+      default:
+        return "keyword '" + kind + "' not supported";
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/parser/ParsingContext.java b/base/src/com/google/idea/blaze/base/lang/buildfile/parser/ParsingContext.java
new file mode 100644
index 0000000..43b2ffe
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/parser/ParsingContext.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.base.lang.buildfile.parser;
+
+import com.intellij.lang.PsiBuilder;
+
+/** Shared context between BUILD file parsing components */
+public class ParsingContext {
+
+  public final StatementParsing statementParser;
+  public final ExpressionParsing expressionParser;
+  public final PsiBuilder builder;
+
+  public ParsingContext(final PsiBuilder builder) {
+    this.builder = builder;
+    statementParser = new StatementParsing(this);
+    expressionParser = new ExpressionParsing(this);
+  }
+}
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
new file mode 100644
index 0000000..90ad5ff
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/parser/StatementParsing.java
@@ -0,0 +1,223 @@
+/*
+ * 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.parser;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.idea.blaze.base.lang.buildfile.lexer.TokenKind;
+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);
+  }
+
+  /** Called at the start of parsing. Parses an entire file */
+  public void parseFileInput() {
+    builder.setDebugMode(ApplicationManager.getApplication().isUnitTestMode());
+    while (!builder.eof()) {
+      if (matches(TokenKind.NEWLINE)) {
+        continue;
+      }
+      parseTopLevelStatement();
+    }
+  }
+
+  // Unlike in Python grammar, 'load' and 'def' are only allowed as a top-level statement
+  public void parseTopLevelStatement() {
+    if (currentToken() == TokenKind.LOAD) {
+      parseLoadStatement();
+    } else if (currentToken() == TokenKind.DEF) {
+      parseFunctionDefStatement();
+    } else {
+      parseStatement();
+    }
+  }
+
+  // simple_stmt | compound_stmt
+  public void parseStatement() {
+    TokenKind current = currentToken();
+    if (current == TokenKind.IF) {
+      parseIfStatement();
+    } else if (current == TokenKind.FOR) {
+      parseForStatement();
+    } else if (FORBIDDEN_KEYWORDS.contains(current)) {
+      PsiBuilder.Marker mark = builder.mark();
+      syncTo(STATEMENT_TERMINATOR_SET);
+      mark.error(forbiddenKeywordError(current));
+      builder.advanceLexer();
+    } else {
+      parseSimpleStatement();
+    }
+  }
+
+  // func_def_stmt ::= DEF IDENTIFIER funcall_suffix ':' suite
+  private void parseFunctionDefStatement() {
+    PsiBuilder.Marker marker = builder.mark();
+    expect(TokenKind.DEF);
+    getExpressionParser().expectIdentifier("expected a function name");
+    PsiBuilder.Marker listMarker = builder.mark();
+    expect(TokenKind.LPAREN);
+    getExpressionParser().parseFunctionParameters();
+    expect(TokenKind.RPAREN, true);
+    listMarker.done(BuildElementTypes.PARAMETER_LIST);
+    expect(TokenKind.COLON);
+    parseSuite();
+    marker.done(BuildElementTypes.FUNCTION_STATEMENT);
+  }
+
+  // load '(' STRING (',' [IDENTIFIER '='] STRING)* [','] ')'
+  private void parseLoadStatement() {
+    PsiBuilder.Marker marker = builder.mark();
+    expect(TokenKind.LOAD);
+    expect(TokenKind.LPAREN);
+    parseStringLiteral(false);
+    // Not implementing [IDENTIFIER EQUALS] option -- not a documented feature,
+    // so wait for users to complain
+    boolean hasSymbols = false;
+    while (!matches(TokenKind.RPAREN) && !matchesAnyOf(STATEMENT_TERMINATOR_SET)) {
+      expect(TokenKind.COMMA);
+      if (matches(TokenKind.RPAREN) || matchesAnyOf(STATEMENT_TERMINATOR_SET)) {
+        break;
+      }
+      hasSymbols |= parseStringLiteral(true);
+    }
+    if (!hasSymbols) {
+      builder.error("'load' statements must include at least one loaded function");
+    }
+    marker.done(BuildElementTypes.LOAD_STATEMENT);
+  }
+
+  /** if_stmt ::= IF expr ':' suite (ELIF expr ':' suite)* [ELSE ':' suite] */
+  private void parseIfStatement() {
+    PsiBuilder.Marker marker = builder.mark();
+    parseIfStatementPart(TokenKind.IF, BuildElementTypes.IF_PART, true);
+    while (currentToken() == TokenKind.ELIF) {
+      parseIfStatementPart(TokenKind.ELIF, BuildElementTypes.ELSE_IF_PART, true);
+    }
+    if (currentToken() == TokenKind.ELSE) {
+      parseIfStatementPart(TokenKind.ELSE, BuildElementTypes.ELSE_PART, false);
+    }
+    marker.done(BuildElementTypes.IF_STATEMENT);
+  }
+
+  // cond_stmts ::= [EL]IF expr ':' suite
+  private void parseIfStatementPart(TokenKind tokenKind, IElementType type, boolean conditional) {
+    PsiBuilder.Marker marker = builder.mark();
+    expect(tokenKind);
+    if (conditional) {
+      getExpressionParser().parseNonTupleExpression();
+    }
+    expect(TokenKind.COLON);
+    parseSuite();
+    marker.done(type);
+  }
+
+  // for_stmt ::= FOR IDENTIFIER IN expr ':' suite
+  private void parseForStatement() {
+    PsiBuilder.Marker marker = builder.mark();
+    expect(TokenKind.FOR);
+    getExpressionParser().parseForLoopVariables();
+    expect(TokenKind.IN);
+    getExpressionParser().parseExpression(false);
+    expect(TokenKind.COLON);
+    parseSuite();
+    marker.done(BuildElementTypes.FOR_STATEMENT);
+  }
+
+  // simple_stmt ::= small_stmt (';' small_stmt)* ';'? NEWLINE
+  private void parseSimpleStatement() {
+    parseSmallStatementOrPass();
+    while (matches(TokenKind.SEMI)) {
+      if (matches(TokenKind.NEWLINE)) {
+        return;
+      }
+      parseSmallStatementOrPass();
+    }
+    if (!builder.eof()) {
+      expect(TokenKind.NEWLINE);
+    }
+  }
+
+  // small_stmt | 'pass'
+  private void parseSmallStatementOrPass() {
+    if (currentToken() == TokenKind.PASS) {
+      buildTokenElement(BuildElementTypes.PASS_STATMENT);
+      return;
+    }
+    parseSmallStatement();
+  }
+
+  private void parseSmallStatement() {
+    if (currentToken() == TokenKind.RETURN) {
+      parseReturnStatement();
+      return;
+    }
+    if (atAnyOfTokens(TokenKind.BREAK, TokenKind.CONTINUE)) {
+      buildTokenElement(BuildElementTypes.FLOW_STATEMENT);
+      return;
+    }
+    PsiBuilder.Marker refMarker = builder.mark();
+    getExpressionParser().parseExpression(false);
+    if (matches(TokenKind.EQUALS)) {
+      getExpressionParser().parseExpression(false);
+      refMarker.done(BuildElementTypes.ASSIGNMENT_STATEMENT);
+    } else if (matchesAnyOf(TokenKind.AUGMENTED_ASSIGNMENT_OPS)) {
+      getExpressionParser().parseExpression(false);
+      refMarker.done(BuildElementTypes.AUGMENTED_ASSIGNMENT);
+    } else {
+      refMarker.drop();
+    }
+  }
+
+  private void parseReturnStatement() {
+    PsiBuilder.Marker marker = builder.mark();
+    expect(TokenKind.RETURN);
+    if (!STATEMENT_TERMINATOR_SET.contains(currentToken())) {
+      getExpressionParser().parseExpression(false);
+    }
+    marker.done(BuildElementTypes.RETURN_STATEMENT);
+  }
+
+  // suite ::= simple_stmt | (NEWLINE INDENT stmt+ DEDENT)
+  private void parseSuite() {
+    if (!matches(TokenKind.NEWLINE)) {
+      parseSimpleStatement();
+      return;
+    }
+    PsiBuilder.Marker marker = builder.mark();
+    if (expect(TokenKind.INDENT)) {
+      while (!builder.eof() && !matches(TokenKind.DEDENT)) {
+        parseStatement();
+      }
+    }
+    marker.done(BuildElementTypes.STATEMENT_LIST);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/Argument.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/Argument.java
new file mode 100644
index 0000000..67ff005
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/Argument.java
@@ -0,0 +1,121 @@
+/*
+ * 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.lexer.BuildToken;
+import com.google.idea.blaze.base.lang.buildfile.lexer.TokenKind;
+import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
+import com.google.idea.blaze.base.lang.buildfile.references.ArgumentReference;
+import com.google.idea.blaze.base.lang.buildfile.references.KeywordArgumentReference;
+import com.intellij.lang.ASTNode;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.psi.PsiReference;
+import com.intellij.psi.tree.IElementType;
+import javax.annotation.Nullable;
+
+/** PSI element for an argument, passed via a function call. */
+public abstract class Argument extends BuildElementImpl {
+
+  public static final Argument[] EMPTY_ARRAY = new Argument[0];
+
+  public Argument(ASTNode node) {
+    super(node);
+  }
+
+  @Override
+  protected void acceptVisitor(BuildElementVisitor visitor) {
+    visitor.visitArgument(this);
+  }
+
+  /** The value passed by this argument */
+  @Nullable
+  public Expression getValue() {
+    // for *args, **kwargs, this should be 'args' or 'kwargs' identifiers.
+    // otherwise the expression after the (optional) '='
+    ASTNode node = getNode().getLastChildNode();
+    while (node != null) {
+      IElementType type = node.getElementType();
+      if (BuildElementTypes.EXPRESSIONS.contains(type)) {
+        return (Expression) node.getPsi();
+      }
+      if (type == BuildToken.fromKind(TokenKind.EQUALS)
+          || type == BuildToken.fromKind(TokenKind.STAR)
+          || type == BuildToken.fromKind(TokenKind.STAR_STAR)) {
+        break;
+      }
+      node = node.getTreePrev();
+    }
+    return null;
+  }
+
+  /** Keyword AST node */
+  public static class Keyword extends Argument {
+    public Keyword(ASTNode node) {
+      super(node);
+    }
+
+    @Override
+    protected void acceptVisitor(BuildElementVisitor visitor) {
+      visitor.visitKeywordArgument(this);
+    }
+
+    @Nullable
+    public ASTNode getNameNode() {
+      return getNode().findChildByType(BuildToken.IDENTIFIER);
+    }
+
+    @Override
+    @Nullable
+    public String getName() {
+      ASTNode node = getNameNode();
+      return node != null ? node.getText() : null;
+    }
+
+    @Override
+    public KeywordArgumentReference getReference() {
+      ASTNode keywordNode = getNameNode();
+      if (keywordNode != null) {
+        TextRange range = PsiUtils.childRangeInParent(getTextRange(), keywordNode.getTextRange());
+        return new KeywordArgumentReference(this, range);
+      }
+      return null;
+    }
+  }
+
+  /** A positional argument */
+  public static class Positional extends Argument {
+    public Positional(ASTNode node) {
+      super(node);
+    }
+
+    @Override
+    public PsiReference getReference() {
+      return new ArgumentReference<>(this, getTextRange(), true);
+    }
+  }
+
+  static class Star extends Argument {
+    public Star(ASTNode node) {
+      super(node);
+    }
+  }
+
+  static class StarStar extends Argument {
+    public StarStar(ASTNode node) {
+      super(node);
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/ArgumentList.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/ArgumentList.java
new file mode 100644
index 0000000..97dc353
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/ArgumentList.java
@@ -0,0 +1,64 @@
+/*
+ * 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.common.collect.ImmutableList;
+import com.intellij.lang.ASTNode;
+import javax.annotation.Nullable;
+
+/** Argument list of a function call */
+public class ArgumentList extends BuildListType<Argument> {
+
+  public ArgumentList(ASTNode astNode) {
+    super(astNode, Argument.class);
+  }
+
+  @Override
+  protected void acceptVisitor(BuildElementVisitor visitor) {
+    visitor.visitFuncallArgList(this);
+  }
+
+  public Argument[] getArguments() {
+    return getElements();
+  }
+
+  @Nullable
+  public Argument.Keyword getKeywordArgument(String name) {
+    ASTNode node = getNode().getFirstChildNode();
+    while (node != null) {
+      if (node.getElementType() == BuildElementTypes.KEYWORD) {
+        Argument.Keyword arg = (Argument.Keyword) node.getPsi();
+        String keyword = arg.getName();
+        if (keyword != null && keyword.equals(name)) {
+          return arg;
+        }
+      }
+      node = node.getTreeNext();
+    }
+    return null;
+  }
+
+  @Nullable
+  public Expression getKeywordArgumentValue(String name) {
+    Argument.Keyword keyword = getKeywordArgument(name);
+    return keyword != null ? keyword.getValue() : null;
+  }
+
+  @Override
+  public ImmutableList<Character> getEndChars() {
+    return ImmutableList.of(')');
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/AssignmentStatement.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/AssignmentStatement.java
new file mode 100644
index 0000000..fc0d2e1
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/AssignmentStatement.java
@@ -0,0 +1,58 @@
+/*
+ * 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;
+import com.intellij.util.PlatformIcons;
+import javax.annotation.Nullable;
+import javax.swing.Icon;
+
+/** PSI element for an assignment statement [expr ASSIGN_OP expr] */
+public class AssignmentStatement extends BuildElementImpl implements Statement {
+
+  public AssignmentStatement(ASTNode astNode) {
+    super(astNode);
+  }
+
+  /** Returns the LHS of the assignment */
+  @Nullable
+  public TargetExpression getLeftHandSideExpression() {
+    return findChildByClass(TargetExpression.class);
+  }
+
+  /** Returns the RHS of the assignment */
+  @Nullable
+  public Expression getAssignedValue() {
+    return childToPsi(BuildElementTypes.EXPRESSIONS, 1);
+  }
+
+  @Override
+  protected void acceptVisitor(BuildElementVisitor visitor) {
+    visitor.visitAssignmentStatement(this);
+  }
+
+  @Nullable
+  @Override
+  public String getName() {
+    TargetExpression target = getLeftHandSideExpression();
+    return target != null ? target.getName() : super.getName();
+  }
+
+  @Override
+  public Icon getIcon(int flags) {
+    return PlatformIcons.FIELD_ICON;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/AugmentedAssignmentStatement.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/AugmentedAssignmentStatement.java
new file mode 100644
index 0000000..5d2384d
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/AugmentedAssignmentStatement.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.base.lang.buildfile.psi;
+
+import com.intellij.lang.ASTNode;
+import javax.annotation.Nullable;
+
+/** PSI element for an augmented assignment statement [expr += expr] */
+public class AugmentedAssignmentStatement extends BuildElementImpl implements Statement {
+
+  public AugmentedAssignmentStatement(ASTNode astNode) {
+    super(astNode);
+  }
+
+  /** Returns the LHS of the assignment */
+  @Nullable
+  public TargetExpression getLeftHandSideExpression() {
+    return childToPsi(BuildElementTypes.EXPRESSIONS, 0);
+  }
+
+  /** Returns the RHS of the assignment */
+  @Nullable
+  public Expression getAssignedValue() {
+    return childToPsi(BuildElementTypes.EXPRESSIONS, 1);
+  }
+
+  @Override
+  protected void acceptVisitor(BuildElementVisitor visitor) {
+    visitor.visitAugmentedAssignmentStatement(this);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/BinaryOpExpression.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/BinaryOpExpression.java
new file mode 100644
index 0000000..76a4387
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/BinaryOpExpression.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.base.lang.buildfile.psi;
+
+import com.intellij.lang.ASTNode;
+import javax.annotation.Nullable;
+
+/** PSI element for an binary operation expression [expr BIN_OP expr] */
+public class BinaryOpExpression extends BuildElementImpl implements Expression {
+
+  public BinaryOpExpression(ASTNode astNode) {
+    super(astNode);
+  }
+
+  @Override
+  protected void acceptVisitor(BuildElementVisitor visitor) {
+    visitor.visitBinaryOpExpression(this);
+  }
+
+  /** Returns the LHS of the expression */
+  @Nullable
+  public Expression getLhs() {
+    return childToPsi(BuildElementTypes.EXPRESSIONS, 0);
+  }
+
+  /** Returns the RHS of the expression */
+  @Nullable
+  public Expression getRhs() {
+    return childToPsi(BuildElementTypes.EXPRESSIONS, 1);
+  }
+}
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
new file mode 100644
index 0000000..112f400
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/BooleanLiteral.java
@@ -0,0 +1,31 @@
+/*
+ * 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/BuildElement.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildElement.java
new file mode 100644
index 0000000..35a67c5
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildElement.java
@@ -0,0 +1,48 @@
+/*
+ * 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.search.BlazePackage;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
+import com.intellij.psi.NavigatablePsiElement;
+import com.intellij.psi.PsiElement;
+import javax.annotation.Nullable;
+
+/** Base class for all BUILD file PSI elements */
+public interface BuildElement extends NavigatablePsiElement {
+
+  @Nullable
+  static BuildElement asBuildElement(PsiElement psiElement) {
+    return psiElement instanceof BuildElement ? (BuildElement) psiElement : null;
+  }
+
+  Statement[] EMPTY_ARRAY = new Statement[0];
+
+  String getPresentableText();
+
+  @Nullable
+  PsiElement getReferencedElement();
+
+  <P extends PsiElement> P[] childrenOfClass(Class<P> psiClass);
+
+  @Nullable
+  <P extends PsiElement> P firstChildOfClass(Class<P> psiClass);
+
+  WorkspacePath getWorkspacePath();
+
+  @Nullable
+  BlazePackage getBlazePackage();
+}
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
new file mode 100644
index 0000000..baf786d
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildElementImpl.java
@@ -0,0 +1,179 @@
+/*
+ * 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.search.BlazePackage;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
+import com.intellij.extapi.psi.ASTWrapperPsiElement;
+import com.intellij.lang.ASTNode;
+import com.intellij.navigation.ItemPresentation;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiElementVisitor;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiReference;
+import com.intellij.psi.tree.IElementType;
+import com.intellij.psi.tree.TokenSet;
+import java.lang.reflect.Array;
+import java.util.Arrays;
+import javax.annotation.Nullable;
+import javax.swing.Icon;
+
+/** Base PSI class for the BUILD language */
+public abstract class BuildElementImpl extends ASTWrapperPsiElement implements BuildElement {
+
+  public BuildElementImpl(ASTNode astNode) {
+    super(astNode);
+  }
+
+  public <P extends PsiElement> P getPsiChild(IElementType type, Class<P> psiClass) {
+    ASTNode childNode = getNode().findChildByType(type);
+    return childNode != null ? (P) childNode.getPsi() : null;
+  }
+
+  @Override
+  public <P extends PsiElement> P[] childrenOfClass(Class<P> psiClass) {
+    return findChildrenByClass(psiClass);
+  }
+
+  @Nullable
+  @Override
+  public <P extends PsiElement> P firstChildOfClass(Class<P> psiClass) {
+    return findChildByClass(psiClass);
+  }
+
+  /**
+   * Returns the BuildElement child at the specified index, where index is calculated after
+   * filtering out non-BuildElement children.
+   *
+   * @return null if index >= number of BuildElement children
+   */
+  @Nullable
+  protected BuildElement getBuildElementChild(int index) {
+    BuildElement[] children = buildElementChildren();
+    return children.length <= index ? null : children[index];
+  }
+
+  protected BuildElement[] buildElementChildren() {
+    return Arrays.stream(getNode().getChildren(null))
+        .map(ASTNode::getPsi)
+        .filter(psiElement -> psiElement instanceof BuildElement)
+        .toArray(BuildElement[]::new);
+  }
+
+  protected <T extends BuildElement> T[] childrenToPsi(TokenSet filterSet, T[] array) {
+    final ASTNode[] nodes = getNode().getChildren(filterSet);
+    T[] psiElements = (T[]) Array.newInstance(array.getClass().getComponentType(), nodes.length);
+    for (int i = 0; i < nodes.length; i++) {
+      psiElements[i] = (T) nodes[i].getPsi();
+    }
+    return psiElements;
+  }
+
+  @Nullable
+  protected <T extends BuildElement> T childToPsi(TokenSet filterSet, int index) {
+    final ASTNode[] nodes = getNode().getChildren(filterSet);
+    if (nodes.length <= index) {
+      return null;
+    }
+    return (T) nodes[index].getPsi();
+  }
+
+  @Nullable
+  protected IElementType getParentType() {
+    ASTNode node = getNode().getTreeParent();
+    return node != null ? node.getElementType() : null;
+  }
+
+  public String nonNullName() {
+    String name = getName();
+    return name != null ? name : "<unnamed>";
+  }
+
+  @Override
+  public String getPresentableText() {
+    return nonNullName();
+  }
+
+  @Override
+  public String toString() {
+    return super.toString() + ": " + getPresentableText();
+  }
+
+  @Override
+  public void accept(PsiElementVisitor visitor) {
+    if (visitor instanceof BuildElementVisitor) {
+      acceptVisitor(((BuildElementVisitor) visitor));
+    } else {
+      super.accept(visitor);
+    }
+  }
+
+  protected abstract void acceptVisitor(BuildElementVisitor visitor);
+
+  @Nullable
+  @Override
+  public PsiElement getReferencedElement() {
+    PsiReference[] refs = getReferences();
+    for (PsiReference ref : refs) {
+      PsiElement element = ref.resolve();
+      if (element != null) {
+        return element;
+      }
+    }
+    return null;
+  }
+
+  @Override
+  public ItemPresentation getPresentation() {
+    final BuildElement element = this;
+    return new ItemPresentation() {
+      @Override
+      public String getPresentableText() {
+        return element.getPresentableText();
+      }
+
+      @Override
+      public String getLocationString() {
+        return null;
+      }
+
+      @Override
+      public Icon getIcon(boolean unused) {
+        return element.getIcon(0);
+      }
+    };
+  }
+
+  @Nullable
+  @Override
+  public WorkspacePath getWorkspacePath() {
+    BuildFile file = (BuildFile) getContainingFile();
+    return file.getWorkspacePath();
+  }
+
+  @Nullable
+  @Override
+  public BlazePackage getBlazePackage() {
+    PsiFile file = getContainingFile();
+    return file != null ? BlazePackage.getContainingPackage(file) : null;
+  }
+
+  @Nullable
+  @Override
+  public BuildFile getContainingFile() {
+    return (BuildFile) super.getContainingFile();
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildElementType.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildElementType.java
new file mode 100644
index 0000000..f46e3e8
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildElementType.java
@@ -0,0 +1,49 @@
+/*
+ * 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.language.BuildFileType;
+import com.intellij.lang.ASTNode;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.tree.IElementType;
+import java.lang.reflect.Constructor;
+
+/**
+ * IElementTypes used in the AST by the parser (as opposed to the types used by the lexer).<br>
+ * Modelled on IntelliJ's java and python language support conventions.
+ */
+public class BuildElementType extends IElementType {
+
+  private static final Class[] PARAMETER_TYPES = new Class[] {ASTNode.class};
+  private final Class<? extends PsiElement> psiElementClass;
+  private Constructor<? extends PsiElement> constructor;
+
+  public BuildElementType(String name, Class<? extends PsiElement> psiElementClass) {
+    super(name, BuildFileType.INSTANCE.getLanguage());
+    this.psiElementClass = psiElementClass;
+  }
+
+  public PsiElement createElement(ASTNode node) {
+    try {
+      if (constructor == null) {
+        constructor = psiElementClass.getConstructor(PARAMETER_TYPES);
+      }
+      return constructor.newInstance(node);
+    } catch (Exception e) {
+      throw new IllegalStateException("No necessary constructor for " + node.getElementType(), e);
+    }
+  }
+}
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
new file mode 100644
index 0000000..e19ba6c
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildElementTypes.java
@@ -0,0 +1,117 @@
+/*
+ * 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.language.BuildFileType;
+import com.intellij.psi.tree.IFileElementType;
+import com.intellij.psi.tree.TokenSet;
+
+/** Collects the types used by the PsiBuilder to construct the AST */
+public interface BuildElementTypes {
+
+  IFileElementType BUILD_FILE = new IFileElementType(BuildFileType.INSTANCE.getLanguage());
+
+  // Statements
+  BuildElementType RETURN_STATEMENT = new BuildElementType("return", ReturnStatement.class);
+  BuildElementType PASS_STATMENT = new BuildElementType("pass", PassStatement.class);
+  BuildElementType ASSIGNMENT_STATEMENT =
+      new BuildElementType("assignment", AssignmentStatement.class);
+  BuildElementType AUGMENTED_ASSIGNMENT =
+      new BuildElementType("aug_assign", AugmentedAssignmentStatement.class);
+  BuildElementType FLOW_STATEMENT = new BuildElementType("flow", FlowStatement.class);
+  BuildElementType LOAD_STATEMENT = new BuildElementType("load", LoadStatement.class);
+  BuildElementType FUNCTION_STATEMENT =
+      new BuildElementType("function_def", FunctionStatement.class);
+  BuildElementType FOR_STATEMENT = new BuildElementType("for", ForStatement.class);
+  BuildElementType IF_STATEMENT = new BuildElementType("if", IfStatement.class);
+
+  BuildElementType IF_PART = new BuildElementType("if_part", IfPart.class);
+  BuildElementType ELSE_IF_PART = new BuildElementType("else_if_part", ElseIfPart.class);
+  BuildElementType ELSE_PART = new BuildElementType("else_part", ElsePart.class);
+
+  BuildElementType STATEMENT_LIST = new BuildElementType("stmt_list", StatementList.class);
+
+  // passed arguments
+  BuildElementType ARGUMENT_LIST = new BuildElementType("arg_list", ArgumentList.class);
+  BuildElementType KEYWORD = new BuildElementType("keyword", Argument.Keyword.class);
+  BuildElementType POSITIONAL = new BuildElementType("positional", Argument.Positional.class);
+  BuildElementType STAR = new BuildElementType("*", Argument.Star.class);
+  BuildElementType STAR_STAR = new BuildElementType("**", Argument.StarStar.class);
+
+  // parameters
+  BuildElementType PARAMETER_LIST = new BuildElementType("parameter_list", ParameterList.class);
+  BuildElementType PARAM_OPTIONAL =
+      new BuildElementType("optional_param", Parameter.Optional.class);
+  BuildElementType PARAM_MANDATORY =
+      new BuildElementType("mandatory_param", Parameter.Mandatory.class);
+  BuildElementType PARAM_STAR = new BuildElementType("*", Parameter.Star.class);
+  BuildElementType PARAM_STAR_STAR = new BuildElementType("**", Parameter.StarStar.class);
+
+  // Expressions
+  BuildElementType DICTIONARY_LITERAL = new BuildElementType("dict", DictionaryLiteral.class);
+  BuildElementType DICTIONARY_ENTRY_LITERAL =
+      new BuildElementType("dict_entry", DictionaryEntryLiteral.class);
+  BuildElementType BINARY_OP_EXPRESSION =
+      new BuildElementType("binary_op", BinaryOpExpression.class);
+  BuildElementType FUNCALL_EXPRESSION =
+      new BuildElementType("function_call", FuncallExpression.class);
+  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 =
+      new BuildElementType("reference", ReferenceExpression.class);
+  BuildElementType TARGET_EXPRESSION = new BuildElementType("target", TargetExpression.class);
+  BuildElementType LIST_COMPREHENSION_EXPR =
+      new BuildElementType("list_comp", ListComprehensionExpression.class);
+
+  TokenSet EXPRESSIONS =
+      TokenSet.create(
+          FUNCALL_EXPRESSION,
+          DICTIONARY_LITERAL,
+          DICTIONARY_ENTRY_LITERAL,
+          BINARY_OP_EXPRESSION,
+          DOT_EXPRESSION,
+          STRING_LITERAL,
+          INTEGER_LITERAL,
+          BOOLEAN_LITERAL,
+          LIST_LITERAL,
+          REFERENCE_EXPRESSION,
+          TARGET_EXPRESSION,
+          LIST_COMPREHENSION_EXPR,
+          GLOB_EXPRESSION);
+
+  TokenSet STATEMENTS =
+      TokenSet.create(
+          RETURN_STATEMENT,
+          PASS_STATMENT,
+          ASSIGNMENT_STATEMENT,
+          FLOW_STATEMENT,
+          LOAD_STATEMENT,
+          FUNCTION_STATEMENT,
+          FOR_STATEMENT,
+          IF_STATEMENT);
+
+  TokenSet ARGUMENTS = TokenSet.create(KEYWORD, POSITIONAL, STAR, STAR_STAR);
+
+  TokenSet PARAMETERS =
+      TokenSet.create(PARAM_OPTIONAL, PARAM_MANDATORY, PARAM_STAR, PARAM_STAR_STAR);
+
+  TokenSet STRINGS = TokenSet.create(STRING_LITERAL);
+  TokenSet FUNCTIONS = TokenSet.create(FUNCTION_STATEMENT);
+}
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
new file mode 100644
index 0000000..f62775b
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildElementVisitor.java
@@ -0,0 +1,146 @@
+/*
+ * 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.psi.PsiElementVisitor;
+
+/** Visitor for BUILD file PSI nodes */
+public class BuildElementVisitor extends PsiElementVisitor {
+
+  public void visitAssignmentStatement(AssignmentStatement node) {
+    visitElement(node);
+  }
+
+  public void visitAugmentedAssignmentStatement(AugmentedAssignmentStatement node) {
+    visitElement(node);
+  }
+
+  public void visitReturnStatement(ReturnStatement node) {
+    visitElement(node);
+  }
+
+  public void visitArgument(Argument node) {
+    visitElement(node);
+  }
+
+  public void visitKeywordArgument(Argument.Keyword node) {
+    visitElement(node);
+  }
+
+  public void visitParameter(Parameter node) {
+    visitElement(node);
+  }
+
+  public void visitLoadStatement(LoadStatement node) {
+    visitElement(node);
+  }
+
+  public void visitIfStatement(IfStatement node) {
+    visitElement(node);
+  }
+
+  public void visitIfPart(IfPart node) {
+    visitElement(node);
+  }
+
+  public void visitElsePart(ElsePart node) {
+    visitElement(node);
+  }
+
+  public void visitElseIfPart(ElseIfPart node) {
+    visitElement(node);
+  }
+
+  public void visitFunctionStatement(FunctionStatement node) {
+    visitElement(node);
+  }
+
+  public void visitFuncallExpression(FuncallExpression node) {
+    visitElement(node);
+  }
+
+  public void visitForStatement(ForStatement node) {
+    visitElement(node);
+  }
+
+  public void visitFlowStatement(FlowStatement node) {
+    visitElement(node);
+  }
+
+  public void visitDotExpression(DotExpression node) {
+    visitElement(node);
+  }
+
+  public void visitDictionaryLiteral(DictionaryLiteral node) {
+    visitElement(node);
+  }
+
+  public void visitDictionaryEntryLiteral(DictionaryEntryLiteral node) {
+    visitElement(node);
+  }
+
+  public void visitBinaryOpExpression(BinaryOpExpression node) {
+    visitElement(node);
+  }
+
+  public void visitStringLiteral(StringLiteral node) {
+    visitElement(node);
+  }
+
+  public void visitIntegerLiteral(IntegerLiteral node) {
+    visitElement(node);
+  }
+
+  public void visitBooleanLiteral(BooleanLiteral node) {
+    visitElement(node);
+  }
+
+  public void visitListLiteral(ListLiteral node) {
+    visitElement(node);
+  }
+
+  public void visitStatementList(StatementList node) {
+    visitElement(node);
+  }
+
+  public void visitFuncallArgList(ArgumentList node) {
+    visitElement(node);
+  }
+
+  public void visitReferenceExpression(ReferenceExpression node) {
+    visitElement(node);
+  }
+
+  public void visitTargetExpression(TargetExpression node) {
+    visitElement(node);
+  }
+
+  public void visitListComprehensionSuffix(ListComprehensionExpression node) {
+    visitElement(node);
+  }
+
+  public void visitFunctionParameterList(ParameterList node) {
+    visitElement(node);
+  }
+
+  public void visitGlobExpression(GlobExpression node) {
+    visitElement(node);
+  }
+
+  public void visitPassStatement(PassStatement 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
new file mode 100644
index 0000000..348f8a6
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildFile.java
@@ -0,0 +1,314 @@
+/*
+ * 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.common.collect.Lists;
+import com.google.idea.blaze.base.lang.buildfile.language.BuildFileType;
+import com.google.idea.blaze.base.lang.buildfile.references.BuildReferenceManager;
+import com.google.idea.blaze.base.lang.buildfile.references.QuoteType;
+import com.google.idea.blaze.base.lang.buildfile.search.BlazePackage;
+import com.google.idea.blaze.base.lang.buildfile.search.ResolveUtil;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
+import com.intellij.extapi.psi.PsiFileBase;
+import com.intellij.lang.ASTNode;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.FileViewProvider;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiNamedElement;
+import com.intellij.psi.tree.TokenSet;
+import com.intellij.util.PathUtil;
+import com.intellij.util.Processor;
+import icons.BlazeIcons;
+import java.io.File;
+import java.util.List;
+import javax.annotation.Nullable;
+import javax.swing.Icon;
+
+/** Build file PSI element */
+public class BuildFile extends PsiFileBase implements BuildElement, DocStringOwner {
+
+  /** The blaze file type */
+  public enum BlazeFileType {
+    SkylarkExtension,
+    BuildPackage // "BUILD", plus hacks such as "BUILD.tools", "BUILD.bazel"
+  }
+
+  @Nullable
+  public static WorkspacePath getWorkspacePath(Project project, String filePath) {
+    return BuildReferenceManager.getInstance(project).getWorkspaceRelativePath(filePath);
+  }
+
+  public static String getBuildFileString(Project project, String filePath) {
+    WorkspacePath workspacePath = getWorkspacePath(project, PathUtil.getParentPath(filePath));
+    if (workspacePath == null) {
+      return "BUILD file: " + filePath;
+    }
+    String fileName = PathUtil.getFileName(filePath);
+    if (fileName.startsWith("BUILD")) {
+      return "//" + workspacePath + "/" + fileName;
+    }
+    return "//" + workspacePath + ":" + fileName;
+  }
+
+  public BuildFile(FileViewProvider viewProvider) {
+    super(viewProvider, BuildFileType.INSTANCE.getLanguage());
+  }
+
+  @Override
+  public FileType getFileType() {
+    return BuildFileType.INSTANCE;
+  }
+
+  public BlazeFileType getBlazeFileType() {
+    String fileName = getFileName();
+    if (fileName.startsWith("BUILD")) {
+      return BlazeFileType.BuildPackage;
+    }
+    return BlazeFileType.SkylarkExtension;
+  }
+
+  @Nullable
+  @Override
+  public StringLiteral getDocString() {
+    if (getBlazeFileType() != BlazeFileType.SkylarkExtension) {
+      return null;
+    }
+    for (PsiElement cur = getFirstChild(); cur != null; cur = cur.getNextSibling()) {
+      if (cur instanceof StringLiteral
+          && ((StringLiteral) cur).getQuoteType() == QuoteType.TripleDouble) {
+        return (StringLiteral) cur;
+      }
+      if (cur instanceof BuildElement) {
+        return null;
+      }
+    }
+    return null;
+  }
+
+  @Nullable
+  @Override
+  public BlazePackage getBlazePackage() {
+    return BlazePackage.getContainingPackage(this);
+  }
+
+  public String getFileName() {
+    return getViewProvider().getVirtualFile().getName();
+  }
+
+  public String getFilePath() {
+    return getOriginalFile().getViewProvider().getVirtualFile().getPath();
+  }
+
+  public File getFile() {
+    return new File(getFilePath());
+  }
+
+  @Nullable
+  @Override
+  public WorkspacePath getWorkspacePath() {
+    return getWorkspacePath(getProject(), getFilePath());
+  }
+
+  /**
+   * The workspace path of the containing blaze package (this is always the parent directory for
+   * BUILD files, but may be a more distant ancestor for Skylark extensions)
+   */
+  @Nullable
+  public WorkspacePath getPackageWorkspacePath() {
+    BlazePackage parentPackage = getBlazePackage();
+    if (parentPackage == null) {
+      return null;
+    }
+    String filePath = parentPackage.buildFile.getFilePath();
+    return filePath != null
+        ? getWorkspacePath(getProject(), PathUtil.getParentPath(filePath))
+        : null;
+  }
+
+  @Nullable
+  public String getWorkspaceRelativePackagePath() {
+    WorkspacePath packagePath = getPackageWorkspacePath();
+    return packagePath != null ? packagePath.relativePath() : null;
+  }
+
+  /** The path for this file, formatted as a BUILD label. */
+  @Nullable
+  public String getBuildLabel() {
+    BlazePackage containingPackage = getBlazePackage();
+    return containingPackage != null
+        ? containingPackage.getBuildLabelForChild(getFilePath())
+        : null;
+  }
+
+  /** Finds a top-level rule with a "name" keyword argument with the given value. */
+  @Nullable
+  public FuncallExpression findRule(String name) {
+    for (FuncallExpression expr : findChildrenByClass(FuncallExpression.class)) {
+      String ruleName = expr.getNameArgumentValue();
+      if (name.equals(ruleName)) {
+        return expr;
+      }
+    }
+    return null;
+  }
+
+  /** .bzl files referenced in 'load' statements */
+  @Nullable
+  public String[] getImportedPaths() {
+    ASTNode[] loadStatements =
+        getNode().getChildren(TokenSet.create(BuildElementTypes.LOAD_STATEMENT));
+    if (loadStatements.length == 0) {
+      return null;
+    }
+    List<String> importedPaths = Lists.newArrayListWithCapacity(loadStatements.length);
+    for (int i = 0; i < loadStatements.length; i++) {
+      String path = ((LoadStatement) loadStatements[i].getPsi()).getImportedPath();
+      if (path != null) {
+        importedPaths.add(path);
+      }
+    }
+    return importedPaths.toArray(new String[importedPaths.size()]);
+  }
+
+  @Nullable
+  public FunctionStatement findDeclaredFunction(String name) {
+    for (FunctionStatement fn : getFunctionDeclarations()) {
+      if (name.equals(fn.getName())) {
+        return fn;
+      }
+    }
+    return null;
+  }
+
+  @Nullable
+  public TargetExpression findTopLevelVariable(String name) {
+    return ResolveUtil.searchChildAssignmentStatements(this, name);
+  }
+
+  @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();
+          return element instanceof FunctionStatement ? (FunctionStatement) element : null;
+        }
+      }
+    }
+    return null;
+  }
+
+  public BuildElement findSymbolInScope(String name) {
+    BuildElement[] resultHolder = new BuildElement[1];
+    Processor<BuildElement> processor =
+        buildElement -> {
+          if (buildElement instanceof StringLiteral) {
+            buildElement = BuildElement.asBuildElement(buildElement.getReferencedElement());
+          }
+          if (buildElement instanceof PsiNamedElement && name.equals(buildElement.getName())) {
+            resultHolder[0] = buildElement;
+            return false;
+          }
+          return true;
+        };
+    searchSymbolsInScope(processor, null);
+    return resultHolder[0];
+  }
+
+  /**
+   * Iterates over all top-level assignment statements, function definitions and loaded symbols.
+   *
+   * @return false if searching was stopped (e.g. because the desired element was found).
+   */
+  public boolean searchSymbolsInScope(
+      Processor<BuildElement> processor, @Nullable PsiElement stopAtElement) {
+    for (BuildElement child : findChildrenByClass(BuildElement.class)) {
+      if (child == stopAtElement) {
+        break;
+      }
+      if (child instanceof AssignmentStatement) {
+        TargetExpression target = ((AssignmentStatement) child).getLeftHandSideExpression();
+        if (target != null && !processor.process(target)) {
+          return false;
+        }
+      } else if (child instanceof FunctionStatement) {
+        if (!processor.process(child)) {
+          return false;
+        }
+      }
+    }
+    // search nested load statements last (breadth-first search)
+    for (BuildElement child : findChildrenByClass(BuildElement.class)) {
+      if (child == stopAtElement) {
+        break;
+      }
+      if (child instanceof LoadStatement) {
+        for (StringLiteral importedSymbol : ((LoadStatement) child).getImportedSymbolElements()) {
+          if (!processor.process(importedSymbol)) {
+            return false;
+          }
+        }
+      }
+    }
+    return true;
+  }
+
+  /** Searches functions declared in this file, then loaded Skylark extensions, if relevant. */
+  @Nullable
+  public FunctionStatement findFunctionInScope(String name) {
+    FunctionStatement localFn = findDeclaredFunction(name);
+    if (localFn != null) {
+      return localFn;
+    }
+    return findLoadedFunction(name);
+  }
+
+  public FunctionStatement[] getFunctionDeclarations() {
+    return findChildrenByClass(FunctionStatement.class);
+  }
+
+  @Override
+  public Icon getIcon(int flags) {
+    return BlazeIcons.BuildFile;
+  }
+
+  @Override
+  public String getPresentableText() {
+    return toString();
+  }
+
+  @Override
+  public String toString() {
+    return getBuildFileString(getProject(), getFilePath());
+  }
+
+  @Nullable
+  @Override
+  public PsiElement getReferencedElement() {
+    return null;
+  }
+
+  @Override
+  public <P extends PsiElement> P[] childrenOfClass(Class<P> psiClass) {
+    return findChildrenByClass(psiClass);
+  }
+
+  @Override
+  public <P extends PsiElement> P firstChildOfClass(Class<P> psiClass) {
+    return findChildByClass(psiClass);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildListType.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildListType.java
new file mode 100644
index 0000000..67b6263
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildListType.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.lang.buildfile.psi;
+
+import com.google.common.collect.ImmutableList;
+import com.intellij.lang.ASTNode;
+import javax.annotation.Nullable;
+
+/** Common interface for BUILD psi elements containing a list / sequence of child elements. */
+public abstract class BuildListType<E extends BuildElement> extends BuildElementImpl {
+
+  private final Class<E> elementClass;
+
+  public BuildListType(ASTNode astNode, Class<E> elementClass) {
+    super(astNode);
+    this.elementClass = elementClass;
+  }
+
+  public E[] getElements() {
+    return findChildrenByClass(elementClass);
+  }
+
+  @Nullable
+  public E getFirstElement() {
+    return findChildByClass(elementClass);
+  }
+
+  public boolean isEmpty() {
+    return getFirstElement() != null;
+  }
+
+  /**
+   * The offset into the document at which child elements start. For lists wrapped in braces, this
+   * is the offset after the opening brace. For statement lists, this is the offset after the colon.
+   */
+  public int getStartOffset() {
+    return getNode().getStartOffset() + 1;
+  }
+
+  /** The character(s) which can end this list */
+  public abstract ImmutableList<Character> getEndChars();
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/DictionaryEntryLiteral.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/DictionaryEntryLiteral.java
new file mode 100644
index 0000000..37c2684
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/DictionaryEntryLiteral.java
@@ -0,0 +1,31 @@
+/*
+ * 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 element for an dictionary entry literal (dictEntry ::= nonTupleExpr ':' nonTupleExpr) */
+public class DictionaryEntryLiteral extends BuildElementImpl implements LiteralExpression {
+
+  public DictionaryEntryLiteral(ASTNode astNode) {
+    super(astNode);
+  }
+
+  @Override
+  protected void acceptVisitor(BuildElementVisitor visitor) {
+    visitor.visitDictionaryEntryLiteral(this);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/DictionaryLiteral.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/DictionaryLiteral.java
new file mode 100644
index 0000000..db57f21
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/DictionaryLiteral.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.base.lang.buildfile.psi;
+
+import com.google.common.collect.ImmutableList;
+import com.intellij.lang.ASTNode;
+
+/** PSI element for an dictionary literal. */
+public class DictionaryLiteral extends BuildListType<DictionaryEntryLiteral>
+    implements LiteralExpression {
+
+  public DictionaryLiteral(ASTNode astNode) {
+    super(astNode, DictionaryEntryLiteral.class);
+  }
+
+  @Override
+  protected void acceptVisitor(BuildElementVisitor visitor) {
+    visitor.visitDictionaryLiteral(this);
+  }
+
+  @Override
+  public ImmutableList<Character> getEndChars() {
+    return ImmutableList.of('}');
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/DocStringOwner.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/DocStringOwner.java
new file mode 100644
index 0000000..fa67c5b
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/DocStringOwner.java
@@ -0,0 +1,25 @@
+/*
+ * 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 javax.annotation.Nullable;
+
+/** BUILD psi element which can have docstring. */
+public interface DocStringOwner extends BuildElement {
+
+  @Nullable
+  StringLiteral getDocString();
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/DotExpression.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/DotExpression.java
new file mode 100644
index 0000000..093cc82
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/DotExpression.java
@@ -0,0 +1,31 @@
+/*
+ * 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 element for a dot expression. */
+public class DotExpression extends BuildElementImpl implements Expression {
+
+  public DotExpression(ASTNode astNode) {
+    super(astNode);
+  }
+
+  @Override
+  protected void acceptVisitor(BuildElementVisitor visitor) {
+    visitor.visitDotExpression(this);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/ElseIfPart.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/ElseIfPart.java
new file mode 100644
index 0000000..7e717ec
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/ElseIfPart.java
@@ -0,0 +1,31 @@
+/*
+ * 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 element for an elif part of an IfStatement. */
+public class ElseIfPart extends BuildElementImpl implements Statement, StatementListContainer {
+
+  public ElseIfPart(ASTNode astNode) {
+    super(astNode);
+  }
+
+  @Override
+  protected void acceptVisitor(BuildElementVisitor visitor) {
+    visitor.visitElseIfPart(this);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/ElsePart.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/ElsePart.java
new file mode 100644
index 0000000..5fae25f
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/ElsePart.java
@@ -0,0 +1,31 @@
+/*
+ * 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 element for an else part of an IfStatement. */
+public class ElsePart extends BuildElementImpl implements Statement, StatementListContainer {
+
+  public ElsePart(ASTNode astNode) {
+    super(astNode);
+  }
+
+  @Override
+  protected void acceptVisitor(BuildElementVisitor visitor) {
+    visitor.visitElsePart(this);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/Expression.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/Expression.java
new file mode 100644
index 0000000..dd0ddfa
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/Expression.java
@@ -0,0 +1,22 @@
+/*
+ * 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;
+
+/** Interface for all expression PSI elements in a BUILD file */
+public interface Expression extends BuildElement {
+
+  Expression[] EMPTY_ARRAY = new Expression[0];
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/FlowStatement.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/FlowStatement.java
new file mode 100644
index 0000000..8f0ee93
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/FlowStatement.java
@@ -0,0 +1,31 @@
+/*
+ * 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 element for a flow statement. */
+public class FlowStatement extends BuildElementImpl implements Statement {
+
+  public FlowStatement(ASTNode astNode) {
+    super(astNode);
+  }
+
+  @Override
+  protected void acceptVisitor(BuildElementVisitor visitor) {
+    visitor.visitFlowStatement(this);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/ForStatement.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/ForStatement.java
new file mode 100644
index 0000000..1d85bf3
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/ForStatement.java
@@ -0,0 +1,58 @@
+/*
+ * 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.common.collect.Lists;
+import com.google.idea.blaze.base.lang.buildfile.lexer.BuildToken;
+import com.google.idea.blaze.base.lang.buildfile.lexer.TokenKind;
+import com.intellij.lang.ASTNode;
+import com.intellij.psi.PsiElement;
+import java.util.List;
+
+/** PSI element for a for statement. */
+public class ForStatement extends BuildElementImpl implements Statement, StatementListContainer {
+
+  public ForStatement(ASTNode astNode) {
+    super(astNode);
+  }
+
+  @Override
+  protected void acceptVisitor(BuildElementVisitor visitor) {
+    visitor.visitForStatement(this);
+  }
+
+  public List<Expression> getForLoopVariables() {
+    List<Expression> loopVariableExpressions = Lists.newArrayList();
+    for (PsiElement child : getChildren()) {
+      if (child.getNode().getElementType() == BuildToken.fromKind(TokenKind.IN)) {
+        return loopVariableExpressions;
+      }
+      if (child instanceof Expression) {
+        loopVariableExpressions.add((Expression) child);
+      } else if (child instanceof ListLiteral) {
+        for (Expression expr : ((ListLiteral) child).childrenOfClass(Expression.class)) {
+          loopVariableExpressions.add(expr);
+        }
+      }
+    }
+    return loopVariableExpressions;
+  }
+
+  @Override
+  public String getPresentableText() {
+    return "for loop";
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/FuncallExpression.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/FuncallExpression.java
new file mode 100644
index 0000000..154d5d9
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/FuncallExpression.java
@@ -0,0 +1,205 @@
+/*
+ * 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.language.semantics.BuildLanguageSpec;
+import com.google.idea.blaze.base.lang.buildfile.language.semantics.BuildLanguageSpecProvider;
+import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
+import com.google.idea.blaze.base.lang.buildfile.references.FuncallReference;
+import com.google.idea.blaze.base.lang.buildfile.references.LabelUtils;
+import com.google.idea.blaze.base.model.primitives.Label;
+import com.intellij.lang.ASTNode;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiNameIdentifierOwner;
+import com.intellij.util.IncorrectOperationException;
+import com.intellij.util.Processor;
+import icons.BlazeIcons;
+import javax.annotation.Nullable;
+import javax.swing.Icon;
+
+/**
+ * PSI element for an function call.<br>
+ * Could be a top-level rule, Skylark function reference, or general some other python function call
+ */
+public class FuncallExpression extends BuildElementImpl
+    implements Expression, PsiNameIdentifierOwner {
+
+  public FuncallExpression(ASTNode astNode) {
+    super(astNode);
+  }
+
+  @Override
+  protected void acceptVisitor(BuildElementVisitor visitor) {
+    visitor.visitFuncallExpression(this);
+  }
+
+  /** The name of the function being called. */
+  @Nullable
+  public String getFunctionName() {
+    ASTNode node = getFunctionNameNode();
+    return node != null ? node.getText() : null;
+  }
+
+  @Override
+  @Nullable
+  public String getName() {
+    return getNameArgumentValue();
+  }
+
+  @Nullable
+  @Override
+  public PsiElement getNameIdentifier() {
+    Argument.Keyword name = getNameArgument();
+    return name != null ? name.getValue() : null;
+  }
+
+  @Override
+  public PsiElement setName(String name) throws IncorrectOperationException {
+    StringLiteral nameNode = getNameArgumentValueNode();
+    if (nameNode == null) {
+      return this;
+    }
+    ASTNode newChild = PsiUtils.createNewLabel(getProject(), name);
+    nameNode.getNode().replaceChild(nameNode.getNode().getFirstChildNode(), newChild);
+    return this;
+  }
+
+  /** The function name */
+  @Nullable
+  public ASTNode getFunctionNameNode() {
+    PsiElement argList = getArgList();
+    if (argList != null) {
+      // We want the reference expr directly prior to the open parenthesis.
+      // This accounts for Skylark native.rule calls.
+      PsiElement prev = argList.getPrevSibling();
+      if (prev instanceof ReferenceExpression) {
+        return prev.getNode();
+      }
+    }
+    return getNode().findChildByType(BuildElementTypes.REFERENCE_EXPRESSION);
+  }
+
+  /** Top-level funcalls are almost always BUILD rules. */
+  public boolean isTopLevel() {
+    ASTNode parent = getNode().getTreeParent();
+    return parent == null || parent.getElementType() == BuildElementTypes.BUILD_FILE;
+  }
+
+  public boolean mightBeBuildRule() {
+    return isTopLevel() || getNameArgument() != null;
+  }
+
+  @Nullable
+  public Label resolveBuildLabel() {
+    BuildFile containingFile = getContainingFile();
+    if (containingFile == null
+        || containingFile.getBlazeFileType() == BuildFile.BlazeFileType.SkylarkExtension) {
+      return null;
+    }
+    return LabelUtils.createLabelFromRuleName(getBlazePackage(), getNameArgumentValue());
+  }
+
+  @Nullable
+  public ArgumentList getArgList() {
+    return findChildByType(BuildElementTypes.ARGUMENT_LIST);
+  }
+
+  public Argument[] getArguments() {
+    ArgumentList argList = getArgList();
+    return argList != null ? argList.getArguments() : Argument.EMPTY_ARRAY;
+  }
+
+  /** Keyword argument with name "name", if one is present. */
+  @Nullable
+  public Argument.Keyword getNameArgument() {
+    return getKeywordArgument("name");
+  }
+
+  public Argument.Keyword getKeywordArgument(String name) {
+    ArgumentList argList = getArgList();
+    return argList != null ? argList.getKeywordArgument(name) : null;
+  }
+
+  /** StringLiteral value of keyword argument with name "name", if one is present. */
+  @Nullable
+  public StringLiteral getNameArgumentValueNode() {
+    Argument.Keyword name = getNameArgument();
+    Expression expr = name != null ? name.getValue() : null;
+    if (expr instanceof StringLiteral) {
+      return ((StringLiteral) expr);
+    }
+    return null;
+  }
+
+  /** Value of keyword argument with name "name", if one is present. */
+  @Nullable
+  public String getNameArgumentValue() {
+    StringLiteral node = getNameArgumentValueNode();
+    return node != null ? node.getStringContents() : null;
+  }
+
+  @Override
+  public Icon getIcon(int flags) {
+    return mightBeBuildRule() ? BlazeIcons.BuildRule : null;
+  }
+
+  @Override
+  public String getPresentableText() {
+    String name = getFunctionName();
+    if (name == null) {
+      return super.getPresentableText();
+    }
+    String targetName = getNameArgumentValue();
+    return targetName != null ? name + "(\"" + targetName + "\")" : name;
+  }
+
+  @Override
+  @Nullable
+  public FuncallReference getReference() {
+    ASTNode nameNode = getFunctionNameNode();
+    if (nameNode == null) {
+      return null;
+    }
+    BuildLanguageSpec spec = BuildLanguageSpecProvider.getInstance().getLanguageSpec(getProject());
+    if (spec != null && spec.hasRule(nameNode.getText())) {
+      // don't try to follow references to built-in rules
+      return null;
+    }
+    TextRange range = PsiUtils.childRangeInParent(getTextRange(), nameNode.getTextRange());
+    return new FuncallReference(this, range);
+  }
+
+  /**
+   * Searches all StringLiteral children of this element, for one which references the desired
+   * target expression.
+   */
+  @Nullable
+  public StringLiteral findChildReferenceToTarget(final FuncallExpression targetRule) {
+    final StringLiteral[] child = new StringLiteral[1];
+    Processor<StringLiteral> processor =
+        stringLiteral -> {
+          PsiElement ref = stringLiteral.getReferencedElement();
+          if (targetRule.equals(ref)) {
+            child[0] = stringLiteral;
+            return false;
+          }
+          return true;
+        };
+    PsiUtils.processChildrenOfType(this, processor, StringLiteral.class);
+    return child[0];
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/FunctionStatement.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/FunctionStatement.java
new file mode 100644
index 0000000..0dce2fe
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/FunctionStatement.java
@@ -0,0 +1,77 @@
+/*
+ * 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.references.QuoteType;
+import com.intellij.lang.ASTNode;
+import com.intellij.psi.PsiElement;
+import com.intellij.util.PlatformIcons;
+import javax.swing.Icon;
+import org.jetbrains.annotations.Nullable;
+
+/** PSI element for a function definition statement. */
+public class FunctionStatement extends NamedBuildElement
+    implements Statement, StatementListContainer, DocStringOwner {
+
+  public FunctionStatement(ASTNode astNode) {
+    super(astNode);
+  }
+
+  @Override
+  protected void acceptVisitor(BuildElementVisitor visitor) {
+    visitor.visitFunctionStatement(this);
+  }
+
+  @Nullable
+  @Override
+  public Icon getIcon(int flags) {
+    return PlatformIcons.FUNCTION_ICON;
+  }
+
+  @Nullable
+  public ParameterList getParameterList() {
+    return getPsiChild(BuildElementTypes.PARAMETER_LIST, ParameterList.class);
+  }
+
+  public Parameter[] getParameters() {
+    ParameterList list = getParameterList();
+    return list != null ? list.getElements() : Parameter.EMPTY_ARRAY;
+  }
+
+  @Override
+  public String getPresentableText() {
+    return nonNullName() + getParameterList().getPresentableText();
+  }
+
+  @Nullable
+  @Override
+  public StringLiteral getDocString() {
+    StatementList stmtList = getStatementList();
+    if (stmtList == null) {
+      return null;
+    }
+    for (PsiElement cur = stmtList.getFirstChild(); cur != null; cur = cur.getNextSibling()) {
+      if (cur instanceof StringLiteral
+          && ((StringLiteral) cur).getQuoteType() == QuoteType.TripleDouble) {
+        return (StringLiteral) cur;
+      }
+      if (cur instanceof BuildElement) {
+        return null;
+      }
+    }
+    return null;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/GlobExpression.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/GlobExpression.java
new file mode 100644
index 0000000..e31338a
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/GlobExpression.java
@@ -0,0 +1,123 @@
+/*
+ * 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.references.GlobReference;
+import com.intellij.lang.ASTNode;
+import com.intellij.openapi.util.TextRange;
+import javax.annotation.Nullable;
+
+/** PSI element for a glob expression. */
+public class GlobExpression extends BuildElementImpl implements Expression {
+
+  public GlobExpression(ASTNode astNode) {
+    super(astNode);
+  }
+
+  @Override
+  protected void acceptVisitor(BuildElementVisitor visitor) {
+    visitor.visitGlobExpression(this);
+  }
+
+  @Nullable
+  public ArgumentList getArgList() {
+    return findChildByType(BuildElementTypes.ARGUMENT_LIST);
+  }
+
+  public Argument[] getArguments() {
+    ArgumentList argList = getArgList();
+    return argList != null ? argList.getArguments() : Argument.EMPTY_ARRAY;
+  }
+
+  @Nullable
+  public Argument.Keyword getKeywordArgument(String name) {
+    ArgumentList list = getArgList();
+    return list != null ? list.getKeywordArgument(name) : null;
+  }
+
+  @Nullable
+  public Expression getIncludes() {
+    Argument arg = getKeywordArgument("include");
+    if (arg == null) {
+      Argument[] allArgs = getArguments();
+      if (allArgs.length != 0 && allArgs[0] instanceof Argument.Positional) {
+        arg = allArgs[0];
+      }
+    }
+    return getArgValue(arg);
+  }
+
+  @Nullable
+  public Expression getExcludes() {
+    return getArgValue(getKeywordArgument("exclude"));
+  }
+
+  @Nullable
+  private static Expression getArgValue(@Nullable Argument arg) {
+    return arg != null ? arg.getValue() : null;
+  }
+
+  public boolean areDirectoriesExcluded() {
+    Argument.Keyword arg = getKeywordArgument("exclude_directories");
+    if (arg != null) {
+      // '0' and '1' are the only accepted values
+      Expression value = arg.getValue();
+      return value == null || !value.getText().equals("0");
+    }
+    return true;
+  }
+
+  @Nullable
+  public ASTNode getGlobFuncallElement() {
+    return getNode().findChildByType(BuildElementTypes.REFERENCE_EXPRESSION);
+  }
+
+  private volatile GlobReference reference = null;
+
+  @Override
+  public GlobReference getReference() {
+    GlobReference ref = reference;
+    if (ref != null) {
+      return ref;
+    }
+    synchronized (this) {
+      if (reference == null) {
+        reference = new GlobReference(this);
+      }
+      return reference;
+    }
+  }
+
+  /**
+   * The text range within the glob expression used for references. This is the text the user needs
+   * to click on for navigation support, and also the destination when finding usages in a glob.
+   */
+  public TextRange getReferenceTextRange() {
+    // Ideally, this would be either the full range of the expression,
+    // or the range of the specific pattern matching
+    // a given file. However, that leads to conflicts with the individual string references,
+    // causing unnecessary and expensive de-globbing.
+    // e.g. while typing the glob patterns, IJ will be looking for code-completion possibilities,
+    // and need to de-glob to do this
+    // (due to a lack of communication between the different code-completion components).
+
+    return new TextRange(0, 4);
+  }
+
+  public boolean matches(String packageRelativePath, boolean isDirectory) {
+    return getReference().matches(packageRelativePath, isDirectory);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/IfPart.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/IfPart.java
new file mode 100644
index 0000000..75f257f
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/IfPart.java
@@ -0,0 +1,31 @@
+/*
+ * 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 element for an if part of an IfStatement. */
+public class IfPart extends BuildElementImpl implements Statement, StatementListContainer {
+
+  public IfPart(ASTNode astNode) {
+    super(astNode);
+  }
+
+  @Override
+  protected void acceptVisitor(BuildElementVisitor visitor) {
+    visitor.visitIfPart(this);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/IfStatement.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/IfStatement.java
new file mode 100644
index 0000000..2c244cd
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/IfStatement.java
@@ -0,0 +1,31 @@
+/*
+ * 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 element for an if statement. */
+public class IfStatement extends BuildElementImpl implements Statement {
+
+  public IfStatement(ASTNode astNode) {
+    super(astNode);
+  }
+
+  @Override
+  protected void acceptVisitor(BuildElementVisitor visitor) {
+    visitor.visitIfStatement(this);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/IntegerLiteral.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/IntegerLiteral.java
new file mode 100644
index 0000000..8aa30e5
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/IntegerLiteral.java
@@ -0,0 +1,31 @@
+/*
+ * 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 integer literal expressions */
+public class IntegerLiteral extends BuildElementImpl implements LiteralExpression {
+
+  public IntegerLiteral(ASTNode astNode) {
+    super(astNode);
+  }
+
+  @Override
+  protected void acceptVisitor(BuildElementVisitor visitor) {
+    visitor.visitIntegerLiteral(this);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/ListComprehensionExpression.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/ListComprehensionExpression.java
new file mode 100644
index 0000000..094b61e
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/ListComprehensionExpression.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.lang.buildfile.psi;
+
+import com.intellij.lang.ASTNode;
+
+/**
+ * PSI element for a list comprehension expression (comprehension suffix of: '[' expr ('FOR'
+ * loop_variables 'IN' expr)+ ']')
+ */
+public class ListComprehensionExpression extends BuildElementImpl implements Expression {
+
+  public ListComprehensionExpression(ASTNode astNode) {
+    super(astNode);
+  }
+
+  @Override
+  protected void acceptVisitor(BuildElementVisitor visitor) {
+    visitor.visitListComprehensionSuffix(this);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/ListLiteral.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/ListLiteral.java
new file mode 100644
index 0000000..65fe842
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/ListLiteral.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.lang.buildfile.psi;
+
+import com.google.common.collect.ImmutableList;
+import com.intellij.lang.ASTNode;
+
+/** PSI element for list and tuple literals */
+public class ListLiteral extends BuildListType<Expression> implements LiteralExpression {
+
+  public ListLiteral(ASTNode astNode) {
+    super(astNode, Expression.class);
+  }
+
+  @Override
+  protected void acceptVisitor(BuildElementVisitor visitor) {
+    visitor.visitListLiteral(this);
+  }
+
+  @Override
+  public String getPresentableText() {
+    return "list";
+  }
+
+  public Expression[] getChildExpressions() {
+    return findChildrenByClass(Expression.class);
+  }
+
+  @Override
+  public Expression[] getElements() {
+    return getChildExpressions();
+  }
+
+  @Override
+  public boolean isEmpty() {
+    return findChildByClass(Expression.class) != null;
+  }
+
+  @Override
+  public ImmutableList<Character> getEndChars() {
+    return ImmutableList.of(')', ']');
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/LiteralExpression.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/LiteralExpression.java
new file mode 100644
index 0000000..1331f2b
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/LiteralExpression.java
@@ -0,0 +1,19 @@
+/*
+ * 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;
+
+/** Interface for literal expressions. */
+public interface LiteralExpression extends Expression {}
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
new file mode 100644
index 0000000..cd92715
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/LoadStatement.java
@@ -0,0 +1,102 @@
+/*
+ * 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.references.LabelUtils;
+import com.intellij.lang.ASTNode;
+import com.intellij.util.PlatformIcons;
+import java.util.Arrays;
+import javax.annotation.Nullable;
+import javax.swing.Icon;
+
+/** PSI element for a load statement. */
+public class LoadStatement extends BuildElementImpl implements Statement {
+
+  public LoadStatement(ASTNode astNode) {
+    super(astNode);
+  }
+
+  @Override
+  protected void acceptVisitor(BuildElementVisitor visitor) {
+    visitor.visitLoadStatement(this);
+  }
+
+  @Nullable
+  public ASTNode getImportNode() {
+    return getNode().findChildByType(BuildElementTypes.STRING_LITERAL);
+  }
+
+  @Nullable
+  public StringLiteral getImportPsiElement() {
+    return findChildByType(BuildElementTypes.STRING_LITERAL);
+  }
+
+  @Nullable
+  public String getImportedPath() {
+    ASTNode firstString = getImportNode();
+    return firstString != null ? StringLiteral.stripQuotes(firstString.getText()) : null;
+  }
+
+  /** The string nodes referencing imported functions. */
+  public FunctionStatement[] getImportedFunctionReferences() {
+    return Arrays.stream(getChildStrings())
+        .skip(1)
+        .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))
+        .map(ASTNode::getPsi)
+        .toArray(StringLiteral[]::new);
+  }
+
+  @Override
+  public Icon getIcon(int flags) {
+    return PlatformIcons.IMPORT_ICON;
+  }
+
+  @Override
+  public String getPresentableText() {
+    String path = LabelUtils.getNiceSkylarkFileName(getImportedPath());
+    return path != null ? "load: " + path : "load";
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/NamedBuildElement.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/NamedBuildElement.java
new file mode 100644
index 0000000..a50c15a
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/NamedBuildElement.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.psi;
+
+import com.google.idea.blaze.base.lang.buildfile.lexer.BuildToken;
+import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
+import com.intellij.lang.ASTNode;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiNameIdentifierOwner;
+import javax.annotation.Nullable;
+
+/** Base class for PsiNamedElements in BUILD files. */
+public abstract class NamedBuildElement extends BuildElementImpl implements PsiNameIdentifierOwner {
+
+  public NamedBuildElement(ASTNode astNode) {
+    super(astNode);
+  }
+
+  @Nullable
+  public ASTNode getNameNode() {
+    return getNode().findChildByType(BuildToken.IDENTIFIER);
+  }
+
+  @Override
+  @Nullable
+  public String getName() {
+    ASTNode node = getNameNode();
+    return node != null ? node.getText() : null;
+  }
+
+  @Override
+  @Nullable
+  public PsiElement getNameIdentifier() {
+    final ASTNode nameNode = getNameNode();
+    return nameNode != null ? nameNode.getPsi() : null;
+  }
+
+  @Override
+  public PsiElement setName(String name) {
+    final ASTNode nameElement = PsiUtils.createNewName(getProject(), name);
+    final ASTNode nameNode = getNameNode();
+    if (nameNode != null) {
+      getNode().replaceChild(nameNode, nameElement);
+    }
+    return this;
+  }
+
+  @Override
+  public int getTextOffset() {
+    final ASTNode name = getNameNode();
+    return name != null ? name.getStartOffset() : super.getTextOffset();
+  }
+
+  @Override
+  public String toString() {
+    return super.toString() + "('" + getName() + "')";
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/Parameter.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/Parameter.java
new file mode 100644
index 0000000..c873bb6
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/Parameter.java
@@ -0,0 +1,122 @@
+/*
+ * 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.lexer.BuildToken;
+import com.google.idea.blaze.base.lang.buildfile.lexer.TokenKind;
+import com.intellij.icons.AllIcons;
+import com.intellij.lang.ASTNode;
+import javax.swing.Icon;
+import org.jetbrains.annotations.Nullable;
+
+/** PSI nodes for parameters in a function declaration */
+public abstract class Parameter extends NamedBuildElement {
+
+  public static final Parameter[] EMPTY_ARRAY = new Parameter[0];
+
+  public Parameter(ASTNode node) {
+    super(node);
+  }
+
+  public boolean hasDefaultValue() {
+    return false;
+  }
+
+  @Nullable
+  @Override
+  public Icon getIcon(int flags) {
+    return AllIcons.Nodes.Parameter;
+  }
+
+  /** Includes stars where relevant. */
+  public String getPresentableName() {
+    return getName();
+  }
+
+  static class Optional extends Parameter {
+    public Optional(ASTNode node) {
+      super(node);
+    }
+
+    @Override
+    protected void acceptVisitor(BuildElementVisitor visitor) {
+      visitor.visitParameter(this);
+    }
+
+    public Expression getDefaultValue() {
+      ASTNode node = getNode().getLastChildNode();
+      while (node != null) {
+        if (BuildElementTypes.EXPRESSIONS.contains(node.getElementType())) {
+          return (Expression) node.getPsi();
+        }
+        if (node.getElementType() == BuildToken.fromKind(TokenKind.EQUALS)) {
+          break;
+        }
+        node = node.getTreePrev();
+      }
+      return null;
+    }
+
+    @Override
+    public boolean hasDefaultValue() {
+      return true;
+    }
+  }
+
+  static class Mandatory extends Parameter {
+    public Mandatory(ASTNode node) {
+      super(node);
+    }
+
+    @Override
+    protected void acceptVisitor(BuildElementVisitor visitor) {
+      visitor.visitParameter(this);
+    }
+  }
+
+  static class Star extends Parameter {
+    public Star(ASTNode node) {
+      super(node);
+    }
+
+    @Override
+    protected void acceptVisitor(BuildElementVisitor visitor) {
+      visitor.visitParameter(this);
+    }
+
+    @Override
+    public String getPresentableName() {
+      return "*" + getName();
+    }
+  }
+
+  /** */
+  public static class StarStar extends Parameter {
+    public StarStar(ASTNode node) {
+      super(node);
+    }
+
+    @Override
+    protected void acceptVisitor(BuildElementVisitor visitor) {
+      visitor.visitParameter(this);
+    }
+
+    @Override
+    public String getPresentableName() {
+      return "**" + getName();
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/ParameterList.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/ParameterList.java
new file mode 100644
index 0000000..99074d5
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/ParameterList.java
@@ -0,0 +1,72 @@
+/*
+ * 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.common.collect.ImmutableList;
+import com.intellij.lang.ASTNode;
+import java.util.StringJoiner;
+import javax.annotation.Nullable;
+
+/** Parameter list in a function declaration */
+public class ParameterList extends BuildListType<Parameter> {
+
+  public ParameterList(ASTNode astNode) {
+    super(astNode, Parameter.class);
+  }
+
+  @Override
+  protected void acceptVisitor(BuildElementVisitor visitor) {
+    visitor.visitFunctionParameterList(this);
+  }
+
+  @Nullable
+  public Parameter findParameterByName(String name) {
+    ASTNode node = getNode().getFirstChildNode();
+    while (node != null) {
+      if (node.getElementType() == BuildElementTypes.PARAM_OPTIONAL
+          || node.getElementType() == BuildElementTypes.PARAM_MANDATORY) {
+        Parameter param = (Parameter) node.getPsi();
+        if (name.equals(param.getName())) {
+          return param;
+        }
+      }
+      node = node.getTreeNext();
+    }
+    return null;
+  }
+
+  public boolean hasStarStar() {
+    return !findChildrenByType(BuildElementTypes.PARAM_STAR_STAR).isEmpty();
+  }
+
+  public boolean hasStar() {
+    return !findChildrenByType(BuildElementTypes.PARAM_STAR).isEmpty();
+  }
+
+  @Override
+  public String getPresentableText() {
+    StringJoiner joiner = new StringJoiner(", ", "(", ")");
+    for (Parameter param : getElements()) {
+      joiner.add(param.getPresentableName());
+    }
+    return joiner.toString();
+  }
+
+  @Override
+  public ImmutableList<Character> getEndChars() {
+    return ImmutableList.of(')');
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/PassStatement.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/PassStatement.java
new file mode 100644
index 0000000..eeb9ae3
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/PassStatement.java
@@ -0,0 +1,31 @@
+/*
+ * 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 element for pass statement. */
+public class PassStatement extends BuildElementImpl implements Statement {
+
+  public PassStatement(ASTNode node) {
+    super(node);
+  }
+
+  @Override
+  protected void acceptVisitor(BuildElementVisitor visitor) {
+    visitor.visitPassStatement(this);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/ReferenceExpression.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/ReferenceExpression.java
new file mode 100644
index 0000000..287402c
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/ReferenceExpression.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.lang.buildfile.psi;
+
+import com.google.idea.blaze.base.lang.buildfile.lexer.BuildToken;
+import com.google.idea.blaze.base.lang.buildfile.references.LocalReference;
+import com.intellij.lang.ASTNode;
+import com.intellij.psi.PsiReference;
+import com.intellij.psi.tree.IElementType;
+import org.jetbrains.annotations.Nullable;
+
+/** References a PsiNamedElement */
+public class ReferenceExpression extends BuildElementImpl implements Expression {
+
+  public ReferenceExpression(ASTNode astNode) {
+    super(astNode);
+  }
+
+  @Override
+  protected void acceptVisitor(BuildElementVisitor visitor) {
+    visitor.visitReferenceExpression(this);
+  }
+
+  @Nullable
+  public ASTNode getNameElement() {
+    return getNode().findChildByType(BuildToken.IDENTIFIER);
+  }
+
+  @Nullable
+  public String getReferencedName() {
+    ASTNode node = getNameElement();
+    return node != null ? node.getText() : null;
+  }
+
+  @Override
+  public PsiReference getReference() {
+    IElementType parentType = getParentType();
+    // function names are resolved by the parent funcall node
+    if (BuildElementTypes.FUNCALL_EXPRESSION.equals(parentType) && !beforeDot(getNode())) {
+      return null;
+    }
+    if (BuildElementTypes.GLOB_EXPRESSION.equals(parentType)) {
+      return null;
+    }
+    if (BuildElementTypes.DOT_EXPRESSION.equals(parentType) && !beforeDot(getNode())) {
+      return null;
+    }
+    return new LocalReference(this);
+  }
+
+  @Override
+  public String getName() {
+    return getReferencedName();
+  }
+
+  private static boolean beforeDot(ASTNode node) {
+    ASTNode prev = node.getTreeNext();
+    return prev != null && prev.getText().equals(".");
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/ReturnStatement.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/ReturnStatement.java
new file mode 100644
index 0000000..a334c00
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/ReturnStatement.java
@@ -0,0 +1,37 @@
+/*
+ * 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;
+import javax.annotation.Nullable;
+
+/** A wrapper Statement class for return expressions. */
+public class ReturnStatement extends BuildElementImpl implements Statement {
+
+  public ReturnStatement(ASTNode node) {
+    super(node);
+  }
+
+  @Nullable
+  public Expression getReturnExpression() {
+    return childToPsi(BuildElementTypes.EXPRESSIONS, 0);
+  }
+
+  @Override
+  protected void acceptVisitor(BuildElementVisitor visitor) {
+    visitor.visitReturnStatement(this);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/Statement.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/Statement.java
new file mode 100644
index 0000000..9d45385
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/Statement.java
@@ -0,0 +1,22 @@
+/*
+ * 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;
+
+/** Base class for all statements nodes in the PSI tree */
+public interface Statement extends BuildElement {
+
+  Statement[] EMPTY_ARRAY = new Statement[0];
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/StatementList.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/StatementList.java
new file mode 100644
index 0000000..22c02db
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/StatementList.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.lang.buildfile.psi;
+
+import com.google.common.collect.ImmutableList;
+import com.intellij.lang.ASTNode;
+import com.intellij.psi.PsiElement;
+
+/** PSI element for a list of statements */
+public class StatementList extends BuildListType<Statement> {
+
+  public StatementList(ASTNode astNode) {
+    super(astNode, Statement.class);
+  }
+
+  @Override
+  protected void acceptVisitor(BuildElementVisitor visitor) {
+    visitor.visitStatementList(this);
+  }
+
+  @Override
+  public int getStartOffset() {
+    PsiElement prevSibling = getPrevSibling();
+    while (prevSibling != null) {
+      if (prevSibling.getText().equals(":")) {
+        return prevSibling.getNode().getStartOffset() + 1;
+      }
+      prevSibling = prevSibling.getPrevSibling();
+    }
+    return getNode().getStartOffset();
+  }
+
+  @Override
+  public ImmutableList<Character> getEndChars() {
+    return ImmutableList.of();
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/StatementListContainer.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/StatementListContainer.java
new file mode 100644
index 0000000..4da6412
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/StatementListContainer.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.lang.buildfile.psi;
+
+import javax.annotation.Nullable;
+
+/** Anything of the form type ':' suite */
+public interface StatementListContainer extends BuildElement {
+
+  @Nullable
+  default StatementList getStatementList() {
+    return firstChildOfClass(StatementList.class);
+  }
+}
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
new file mode 100644
index 0000000..9afb380
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/StringLiteral.java
@@ -0,0 +1,146 @@
+/*
+ * 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.references.LabelReference;
+import com.google.idea.blaze.base.lang.buildfile.references.LoadedSymbolReference;
+import com.google.idea.blaze.base.lang.buildfile.references.PackageReferenceFragment;
+import com.google.idea.blaze.base.lang.buildfile.references.QuoteType;
+import com.intellij.lang.ASTNode;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiReference;
+import javax.annotation.Nullable;
+
+/** PSI node for string literal expressions */
+public class StringLiteral extends BuildElementImpl implements LiteralExpression {
+
+  /**
+   * Removes the leading and trailing quotes. Naive implementation intended for resolving references
+   * (in which case escaped characters, raw strings, etc. are unlikely).
+   */
+  public static String stripQuotes(String string) {
+    // TODO: Handle escaped characters, etc. here?
+    // (extract logic from BuildLexerBase.addStringLiteral)
+    if (string.startsWith("\"\"\"")) {
+      return string.length() <= 3 ? "" : string.substring(3, endTrimIndex(string, '"', 3));
+    }
+    if (string.startsWith("'''")) {
+      return string.length() <= 3 ? "" : string.substring(3, endTrimIndex(string, '\'', 3));
+    }
+    if (string.startsWith("\"")) {
+      return string.substring(1, endTrimIndex(string, '"', 1));
+    }
+    if (string.startsWith("'")) {
+      return string.substring(1, endTrimIndex(string, '\'', 1));
+    }
+    return string;
+  }
+
+  private static int endTrimIndex(String string, char quoteChar, int numberQuoteChars) {
+    int trimIndex = string.length();
+    for (int i = 1;
+        i <= Math.min(numberQuoteChars, string.length() - numberQuoteChars);
+        i++, trimIndex--) {
+      if (string.charAt(string.length() - i) != quoteChar) {
+        break;
+      }
+    }
+    return trimIndex;
+  }
+
+  public static QuoteType getQuoteType(@Nullable String rawText) {
+    if (rawText == null) {
+      return QuoteType.NoQuotes;
+    }
+    if (rawText.startsWith("\"\"\"")) {
+      return QuoteType.TripleDouble;
+    }
+    if (rawText.startsWith("'''")) {
+      return QuoteType.TripleSingle;
+    }
+    if (rawText.startsWith("'")) {
+      return QuoteType.Single;
+    }
+    if (rawText.startsWith("\"")) {
+      return QuoteType.Double;
+    }
+    return QuoteType.NoQuotes;
+  }
+
+  public StringLiteral(ASTNode astNode) {
+    super(astNode);
+  }
+
+  @Override
+  protected void acceptVisitor(BuildElementVisitor visitor) {
+    visitor.visitStringLiteral(this);
+  }
+
+  /** Removes the leading and trailing quotes */
+  public String getStringContents() {
+    return stripQuotes(getText());
+  }
+
+  public QuoteType getQuoteType() {
+    return getQuoteType(getText());
+  }
+
+  /**
+   * Labels are taken to reference: - the actual target they reference - the BUILD package specified
+   * before the colon (only if explicitly present)
+   */
+  @Override
+  public PsiReference[] getReferences() {
+    PsiReference primaryReference = getReference();
+    if (primaryReference instanceof LabelReference) {
+      return new PsiReference[] {
+        primaryReference, new PackageReferenceFragment((LabelReference) primaryReference)
+      };
+    }
+    return primaryReference != null
+        ? new PsiReference[] {primaryReference}
+        : PsiReference.EMPTY_ARRAY;
+  }
+
+  /** The primary reference -- this is the target referenced by the full label */
+  @Nullable
+  @Override
+  public PsiReference getReference() {
+    PsiElement parent = getParent();
+    if (parent instanceof LoadStatement) {
+      LoadStatement load = (LoadStatement) parent;
+      StringLiteral importNode = load.getImportPsiElement();
+      if (importNode == null) {
+        return null;
+      }
+      LabelReference importReference = new LabelReference(importNode, false);
+      if (this.equals(importNode)) {
+        return importReference;
+      }
+      return new LoadedSymbolReference(this, importReference);
+    }
+    return new LabelReference(this, true);
+  }
+
+  public boolean insideLoadStatement() {
+    return getParentType() == BuildElementTypes.LOAD_STATEMENT;
+  }
+
+  @Override
+  public String getPresentableText() {
+    return getText();
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/TargetExpression.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/TargetExpression.java
new file mode 100644
index 0000000..405d0de
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/TargetExpression.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.lang.buildfile.psi;
+
+import com.google.idea.blaze.base.lang.buildfile.references.TargetReference;
+import com.intellij.lang.ASTNode;
+import com.intellij.psi.PsiReference;
+import com.intellij.util.PlatformIcons;
+import javax.swing.Icon;
+import org.jetbrains.annotations.Nullable;
+
+/** References a PsiNamedElement */
+public class TargetExpression extends NamedBuildElement implements Expression {
+
+  public TargetExpression(ASTNode astNode) {
+    super(astNode);
+  }
+
+  @Override
+  protected void acceptVisitor(BuildElementVisitor visitor) {
+    visitor.visitTargetExpression(this);
+  }
+
+  @Override
+  public PsiReference getReference() {
+    return new TargetReference(this);
+  }
+
+  @Nullable
+  @Override
+  public Icon getIcon(int flags) {
+    return PlatformIcons.VARIABLE_ICON;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/util/BuildElementGenerator.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/util/BuildElementGenerator.java
new file mode 100644
index 0000000..4c3e3c2
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/util/BuildElementGenerator.java
@@ -0,0 +1,93 @@
+/*
+ * 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.util;
+
+import com.google.idea.blaze.base.lang.buildfile.language.BuildFileLanguage;
+import com.google.idea.blaze.base.lang.buildfile.language.BuildFileType;
+import com.google.idea.blaze.base.lang.buildfile.lexer.BuildToken;
+import com.google.idea.blaze.base.lang.buildfile.lexer.TokenKind;
+import com.google.idea.blaze.base.lang.buildfile.psi.Argument;
+import com.google.idea.blaze.base.lang.buildfile.psi.Expression;
+import com.google.idea.blaze.base.lang.buildfile.psi.FuncallExpression;
+import com.intellij.lang.ASTNode;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiFileFactory;
+import com.intellij.psi.impl.PsiFileFactoryImpl;
+import com.intellij.testFramework.LightVirtualFile;
+
+/** Creates dummy BuildElements, e.g. for renaming purposes. */
+public class BuildElementGenerator {
+
+  public static BuildElementGenerator getInstance(Project project) {
+    return ServiceManager.getService(project, BuildElementGenerator.class);
+  }
+
+  private static final String DUMMY_FILENAME = "dummy.bzl";
+
+  private final Project project;
+
+  public BuildElementGenerator(Project project) {
+    this.project = project;
+  }
+
+  public PsiFile createDummyFile(String contents) {
+    PsiFileFactory factory = PsiFileFactory.getInstance(project);
+    LightVirtualFile virtualFile =
+        new LightVirtualFile(DUMMY_FILENAME, BuildFileType.INSTANCE, contents);
+    PsiFile psiFile =
+        ((PsiFileFactoryImpl) factory)
+            .trySetupPsiForFile(virtualFile, BuildFileLanguage.INSTANCE, false, true);
+    assert psiFile != null;
+    return psiFile;
+  }
+
+  public ASTNode createNameIdentifier(String name) {
+    PsiFile dummyFile = createDummyFile(name);
+    ASTNode referenceNode = dummyFile.getNode().getFirstChildNode();
+    ASTNode nameNode = referenceNode.getFirstChildNode();
+    if (nameNode.getElementType() != BuildToken.IDENTIFIER) {
+      throw new RuntimeException(
+          "Expecting an IDENTIFIER node directly below the BuildFile PSI element");
+    }
+    return nameNode;
+  }
+
+  public ASTNode createStringNode(String contents) {
+    PsiFile dummyFile = createDummyFile('"' + contents + '"');
+    ASTNode literalNode = dummyFile.getNode().getFirstChildNode();
+    ASTNode stringNode = literalNode.getFirstChildNode();
+    assert (stringNode.getElementType() == BuildToken.fromKind(TokenKind.STRING));
+    return stringNode;
+  }
+
+  public Argument.Keyword createKeywordArgument(String keyword, String value) {
+    String dummyText = String.format("foo(%s = \"%s\")", keyword, value);
+    FuncallExpression funcall = (FuncallExpression) createExpressionFromText(dummyText);
+    return (Argument.Keyword) funcall.getArguments()[0];
+  }
+
+  public Expression createExpressionFromText(String text) {
+    PsiFile dummyFile = createDummyFile(text);
+    PsiElement element = dummyFile.getFirstChild();
+    if (element instanceof Expression) {
+      return (Expression) element;
+    }
+    throw new RuntimeException("Could not parse text as expression: '" + text + "'");
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/util/PsiUtils.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/util/PsiUtils.java
new file mode 100644
index 0000000..229220b
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/util/PsiUtils.java
@@ -0,0 +1,186 @@
+/*
+ * 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.util;
+
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.lang.buildfile.psi.AssignmentStatement;
+import com.google.idea.blaze.base.lang.buildfile.psi.Expression;
+import com.google.idea.blaze.base.lang.buildfile.psi.ReferenceExpression;
+import com.google.idea.blaze.base.lang.buildfile.psi.TargetExpression;
+import com.intellij.lang.ASTNode;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.util.CommonProcessors;
+import com.intellij.util.Processor;
+import java.util.List;
+import javax.annotation.Nullable;
+
+/** Utility methods for working with PSI elements */
+public class PsiUtils {
+
+  public static ASTNode createNewName(Project project, String name) {
+    return BuildElementGenerator.getInstance(project).createNameIdentifier(name);
+  }
+
+  public static ASTNode createNewLabel(Project project, String labelString) {
+    return BuildElementGenerator.getInstance(project).createStringNode(labelString);
+  }
+
+  @Nullable
+  public static PsiElement getPreviousNodeInTree(PsiElement element) {
+    PsiElement prevSibling = null;
+    while (element != null && (prevSibling = element.getPrevSibling()) == null) {
+      element = element.getParent();
+    }
+    return prevSibling != null ? lastElementInSubtree(prevSibling) : null;
+  }
+
+  /** The last element in the tree rooted at the given element. */
+  public static PsiElement lastElementInSubtree(PsiElement element) {
+    PsiElement lastChild;
+    while ((lastChild = element.getLastChild()) != null) {
+      element = lastChild;
+    }
+    return element;
+  }
+
+  /**
+   * Walks up PSI tree, looking for a parent of the specified class. Stops searching when it reaches
+   * a parent of type PsiDirectory.
+   */
+  @Nullable
+  public static <T extends PsiElement> T getParentOfType(PsiElement element, Class<T> psiClass) {
+    PsiElement parent = element.getParent();
+    while (parent != null && !(parent instanceof PsiDirectory)) {
+      if (psiClass.isInstance(parent)) {
+        return (T) parent;
+      }
+      parent = parent.getParent();
+    }
+    return null;
+  }
+
+  @Nullable
+  public static <T extends PsiElement> T findFirstChildOfClassRecursive(
+      PsiElement parent, Class<T> psiClass) {
+    List<T> holder = Lists.newArrayListWithExpectedSize(1);
+    Processor<T> getFirst =
+        t -> {
+          holder.add(t);
+          return false;
+        };
+    processChildrenOfType(parent, getFirst, psiClass);
+    return holder.isEmpty() ? null : holder.get(0);
+  }
+
+  @Nullable
+  public static <T extends PsiElement> T findLastChildOfClassRecursive(
+      PsiElement parent, Class<T> psiClass) {
+    List<T> holder = Lists.newArrayListWithExpectedSize(1);
+    Processor<T> getFirst =
+        t -> {
+          holder.add(t);
+          return false;
+        };
+    processChildrenOfType(parent, getFirst, psiClass, true);
+    return holder.isEmpty() ? null : holder.get(0);
+  }
+
+  public static <T extends PsiElement> List<T> findAllChildrenOfClassRecursive(
+      PsiElement parent, Class<T> psiClass) {
+    List<T> result = Lists.newArrayList();
+    processChildrenOfType(parent, new CommonProcessors.CollectProcessor(result), psiClass);
+    return result;
+  }
+
+  /**
+   * Walk through entire PSI tree rooted at 'element', processing all children of the given type.
+   *
+   * @return true if processing was stopped by the processor
+   */
+  public static <T extends PsiElement> boolean processChildrenOfType(
+      PsiElement element, Processor<T> processor, Class<T> psiClass) {
+    return processChildrenOfType(element, processor, psiClass, false);
+  }
+
+  /**
+   * Walk through entire PSI tree rooted at 'element', processing all children of the given type.
+   *
+   * @return true if processing was stopped by the processor
+   */
+  private static <T extends PsiElement> boolean processChildrenOfType(
+      PsiElement element, Processor<T> processor, Class<T> psiClass, boolean reverseOrder) {
+    PsiElement child = reverseOrder ? element.getLastChild() : element.getFirstChild();
+    while (child != null) {
+      if (psiClass.isInstance(child)) {
+        if (!processor.process((T) child)) {
+          return true;
+        }
+      }
+      if (processChildrenOfType(child, processor, psiClass, reverseOrder)) {
+        return true;
+      }
+      child = reverseOrder ? child.getPrevSibling() : child.getNextSibling();
+    }
+    return false;
+  }
+
+  public static TextRange childRangeInParent(TextRange parentRange, TextRange childRange) {
+    return childRange.shiftRight(-parentRange.getStartOffset());
+  }
+
+  @Nullable
+  public static String getFilePath(@Nullable PsiFile file) {
+    VirtualFile virtualFile = file != null ? file.getVirtualFile() : null;
+    return virtualFile != null ? virtualFile.getPath() : null;
+  }
+
+  /**
+   * For ReferenceExpressions, follows the chain of references until it hits a
+   * non-ReferenceExpression. For other types, returns the input expression.
+   */
+  public static PsiElement getReferencedTarget(Expression expr) {
+    PsiElement element = expr;
+    while (element instanceof ReferenceExpression) {
+      PsiElement referencedElement = ((ReferenceExpression) element).getReferencedElement();
+      if (referencedElement == null) {
+        return element;
+      }
+      element = referencedElement;
+    }
+    return element;
+  }
+
+  /**
+   * For ReferenceExpressions, follows the chain of references until it hits a
+   * non-ReferenceExpression, then evaluates the value of that target. For other types, returns the
+   * input expression.
+   */
+  public static PsiElement getReferencedTargetValue(Expression expr) {
+    PsiElement element = getReferencedTarget(expr);
+    if (element instanceof TargetExpression) {
+      PsiElement parent = element.getParent();
+      if (parent instanceof AssignmentStatement) {
+        return ((AssignmentStatement) parent).getAssignedValue();
+      }
+    }
+    return element;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/refactor/BuildNamesValidator.java b/base/src/com/google/idea/blaze/base/lang/buildfile/refactor/BuildNamesValidator.java
new file mode 100644
index 0000000..f85425d
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/refactor/BuildNamesValidator.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.base.lang.buildfile.refactor;
+
+import com.google.idea.blaze.base.lang.buildfile.lexer.BuildLexer;
+import com.google.idea.blaze.base.lang.buildfile.lexer.BuildLexerBase;
+import com.google.idea.blaze.base.lang.buildfile.lexer.TokenKind;
+import com.intellij.lang.refactoring.NamesValidator;
+import com.intellij.openapi.project.Project;
+
+/** Used for rename validation */
+public class BuildNamesValidator implements NamesValidator {
+
+  @Override
+  public boolean isKeyword(String s, Project project) {
+    return false;
+  }
+
+  @Override
+  public boolean isIdentifier(String s, Project project) {
+    BuildLexer lexer = new BuildLexer(BuildLexerBase.LexerMode.Parsing);
+    lexer.start(s);
+    return lexer.getTokenEnd() == s.length() && lexer.getTokenKind() == TokenKind.IDENTIFIER;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/refactor/BuildRefactoringSupportProvider.java b/base/src/com/google/idea/blaze/base/lang/buildfile/refactor/BuildRefactoringSupportProvider.java
new file mode 100644
index 0000000..8ede27b
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/refactor/BuildRefactoringSupportProvider.java
@@ -0,0 +1,35 @@
+/*
+ * 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.refactor;
+
+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.TargetExpression;
+import com.intellij.lang.refactoring.RefactoringSupportProvider;
+import com.intellij.psi.PsiElement;
+import org.jetbrains.annotations.NotNull;
+
+/** Supports 'safe delete' */
+public class BuildRefactoringSupportProvider extends RefactoringSupportProvider {
+
+  @Override
+  public boolean isSafeDeleteAvailable(@NotNull PsiElement element) {
+    // basically a promise that 'find usages' works for this element
+    return element instanceof FunctionStatement
+        || element instanceof TargetExpression
+        || element instanceof FuncallExpression;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/references/ArgumentReference.java b/base/src/com/google/idea/blaze/base/lang/buildfile/references/ArgumentReference.java
new file mode 100644
index 0000000..bb740a4
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/references/ArgumentReference.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.lang.buildfile.references;
+
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.lang.buildfile.completion.NamedBuildLookupElement;
+import com.google.idea.blaze.base.lang.buildfile.psi.Argument;
+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.intellij.codeInsight.lookup.LookupElement;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiReferenceBase;
+import com.intellij.psi.util.PsiTreeUtil;
+import java.util.List;
+import javax.annotation.Nullable;
+
+/**
+ * Only keyword arguments resolve, but we include this class for code completion purposes. As the
+ * user is typing a keyword arg, they'll start with a positional arg element.
+ */
+public class ArgumentReference<T extends Argument> extends PsiReferenceBase<T> {
+
+  public ArgumentReference(T element, TextRange rangeInElement, boolean soft) {
+    super(element, rangeInElement, false);
+  }
+
+  @Nullable
+  protected FunctionStatement resolveFunction() {
+    FuncallExpression call = PsiTreeUtil.getParentOfType(myElement, FuncallExpression.class);
+    if (call == null) {
+      return null;
+    }
+    PsiElement callee = call.getReferencedElement();
+    return callee instanceof FunctionStatement ? (FunctionStatement) callee : null;
+  }
+
+  @Nullable
+  @Override
+  public PsiElement resolve() {
+    return null;
+  }
+
+  @Override
+  public Object[] getVariants() {
+    FunctionStatement function = resolveFunction();
+    if (function == null) {
+      return EMPTY_ARRAY;
+    }
+    List<LookupElement> params = Lists.newArrayList();
+    for (Parameter param : function.getParameters()) {
+      params.add(new NamedBuildLookupElement(param, QuoteType.NoQuotes));
+    }
+    return params.toArray();
+  }
+}
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
new file mode 100644
index 0000000..454ab79
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/references/BuildReferenceManager.java
@@ -0,0 +1,248 @@
+/*
+ * 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.references;
+
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.io.FileAttributeProvider;
+import com.google.idea.blaze.base.lang.buildfile.completion.BuildLookupElement;
+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.WorkspacePath;
+import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver;
+import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolverProvider;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.progress.ProgressManager;
+import com.intellij.openapi.project.Project;
+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.PsiFile;
+import com.intellij.psi.PsiFileSystemItem;
+import com.intellij.psi.PsiManager;
+import com.intellij.util.PathUtil;
+import java.io.File;
+import java.util.List;
+import javax.annotation.Nullable;
+
+/** Handles reference caching and resolving labels to PSI elements. */
+public class BuildReferenceManager {
+
+  public static BuildReferenceManager getInstance(Project project) {
+    return ServiceManager.getService(project, BuildReferenceManager.class);
+  }
+
+  private final Project project;
+
+  public BuildReferenceManager(Project project) {
+    this.project = project;
+  }
+
+  /** Finds the PSI element associated with the given label. */
+  @Nullable
+  public PsiElement resolveLabel(Label label) {
+    return resolveLabel(label.blazePackage(), label.ruleName(), false);
+  }
+
+  /** Finds the PSI element associated with the given label. */
+  @Nullable
+  public PsiElement resolveLabel(
+      WorkspacePath packagePath, RuleName ruleName, boolean excludeRules) {
+    File packageDir = resolvePackage(packagePath);
+    if (packageDir == null) {
+      return null;
+    }
+
+    if (!excludeRules) {
+      FuncallExpression target = findRule(packageDir, ruleName);
+      if (target != null) {
+        return target;
+      }
+    }
+
+    // try a direct file reference (e.g. ":a.java")
+    File fullFile = new File(packageDir, ruleName.toString());
+    if (FileAttributeProvider.getInstance().exists(fullFile)) {
+      return resolveFile(fullFile);
+    }
+
+    return null;
+  }
+
+  private FuncallExpression findRule(File packageDir, RuleName ruleName) {
+    BuildFile psiFile = findBuildFile(packageDir);
+    return psiFile != null ? psiFile.findRule(ruleName.toString()) : null;
+  }
+
+  @Nullable
+  public PsiFileSystemItem resolveFile(File file) {
+    VirtualFile vf = getFileSystem().findFileByPath(file.getPath());
+    if (vf == null) {
+      return null;
+    }
+    PsiManager manager = PsiManager.getInstance(project);
+    return vf.isDirectory() ? manager.findDirectory(vf) : manager.findFile(vf);
+  }
+
+  @Nullable
+  public File resolvePackage(@Nullable WorkspacePath packagePath) {
+    return resolveWorkspaceRelativePath(packagePath != null ? packagePath.relativePath() : null);
+  }
+
+  @Nullable
+  private File resolveWorkspaceRelativePath(@Nullable String relativePath) {
+    WorkspacePathResolver pathResolver = getWorkspacePathResolver();
+    if (pathResolver == null || relativePath == null) {
+      return null;
+    }
+    return pathResolver.resolveToFile(relativePath);
+  }
+
+  @Nullable
+  private WorkspacePathResolver getWorkspacePathResolver() {
+    return WorkspacePathResolverProvider.getInstance(project).getPathResolver();
+  }
+
+  /**
+   * Finds all child directories. If exactly one is found, continue traversing (and appending to
+   * LookupElement string) until there are multiple options.<br>
+   * Used for package path completion suggestions.
+   */
+  public BuildLookupElement[] resolvePackageLookupElements(FileLookupData lookupData) {
+    String relativePath = lookupData.filePathFragment;
+    File file = resolveWorkspaceRelativePath(relativePath);
+
+    FileAttributeProvider provider = FileAttributeProvider.getInstance();
+    String pathFragment = "";
+    if (file == null || (!provider.isDirectory(file) && !relativePath.endsWith("/"))) {
+      // we might be partway through a file name. Try the parent directory
+      relativePath = PathUtil.getParentPath(relativePath);
+      file = resolveWorkspaceRelativePath(relativePath);
+      pathFragment =
+          StringUtil.trimStart(lookupData.filePathFragment.substring(relativePath.length()), "/");
+    }
+    if (file == null || !provider.isDirectory(file)) {
+      return BuildLookupElement.EMPTY_ARRAY;
+    }
+    VirtualFile vf = getFileSystem().findFileByPath(file.getPath());
+    if (vf == null || !vf.isDirectory()) {
+      return BuildLookupElement.EMPTY_ARRAY;
+    }
+    BuildLookupElement[] uniqueLookup = new BuildLookupElement[1];
+    while (true) {
+      VirtualFile[] children = vf.getChildren();
+      if (children == null || children.length == 0) {
+        return uniqueLookup[0] != null ? uniqueLookup : BuildLookupElement.EMPTY_ARRAY;
+      }
+      List<VirtualFile> validChildren = Lists.newArrayListWithCapacity(children.length);
+      for (VirtualFile child : children) {
+        ProgressManager.checkCanceled();
+        if (child.getName().startsWith(pathFragment) && lookupData.acceptFile(child)) {
+          validChildren.add(child);
+        }
+      }
+      if (validChildren.isEmpty()) {
+        return uniqueLookup[0] != null ? uniqueLookup : BuildLookupElement.EMPTY_ARRAY;
+      }
+      if (validChildren.size() > 1) {
+        return uniqueLookup[0] != null ? uniqueLookup : lookupsForFiles(validChildren, lookupData);
+      }
+      // continue traversing while there's only one option
+      uniqueLookup[0] = lookupForFile(validChildren.get(0), lookupData);
+      pathFragment = "";
+      vf = validChildren.get(0);
+    }
+  }
+
+  private BuildLookupElement[] lookupsForFiles(List<VirtualFile> files, FileLookupData lookupData) {
+    BuildLookupElement[] lookups = new BuildLookupElement[files.size()];
+    for (int i = 0; i < files.size(); i++) {
+      lookups[i] = lookupForFile(files.get(i), lookupData);
+    }
+    return lookups;
+  }
+
+  private BuildLookupElement lookupForFile(VirtualFile file, FileLookupData lookupData) {
+    WorkspacePath workspacePath = getWorkspaceRelativePath(file.getPath());
+    return lookupData.lookupElementForFile(project, file, workspacePath);
+  }
+
+  @Nullable
+  public BuildFile resolveBlazePackage(String workspaceRelativePath) {
+    workspaceRelativePath = StringUtil.trimStart(workspaceRelativePath, "//");
+    return resolveBlazePackage(WorkspacePath.createIfValid(workspaceRelativePath));
+  }
+
+  @Nullable
+  public BuildFile resolveBlazePackage(@Nullable WorkspacePath path) {
+    return findBuildFile(resolvePackage(path));
+  }
+
+  @Nullable
+  private BuildFile findBuildFile(@Nullable File packageDirectory) {
+    FileAttributeProvider provider = FileAttributeProvider.getInstance();
+    if (packageDirectory == null || !provider.isDirectory(packageDirectory)) {
+      return null;
+    }
+    File buildFile = new File(packageDirectory, "BUILD");
+    if (!provider.exists(buildFile)) {
+      return null;
+    }
+    VirtualFile vf = getFileSystem().findFileByPath(buildFile.getPath());
+    if (vf == null) {
+      return null;
+    }
+    PsiFile psiFile = PsiManager.getInstance(project).findFile(vf);
+    return psiFile instanceof BuildFile ? (BuildFile) psiFile : null;
+  }
+
+  /**
+   * For files references, returns the parent directory.<br>
+   * For rule references, return the blaze package directory.
+   */
+  @Nullable
+  public File resolveParentDirectory(@Nullable Label label) {
+    return label != null ? resolveParentDirectory(label.blazePackage(), label.ruleName()) : null;
+  }
+
+  @Nullable
+  private File resolveParentDirectory(WorkspacePath packagePath, RuleName ruleName) {
+    File packageFile = resolvePackage(packagePath);
+    if (packageFile == null) {
+      return null;
+    }
+    String rulePathParent = PathUtil.getParentPath(ruleName.toString());
+    return new File(packageFile, rulePathParent);
+  }
+
+  @Nullable
+  public WorkspacePath getWorkspaceRelativePath(String absolutePath) {
+    WorkspacePathResolver pathResolver = getWorkspacePathResolver();
+    return pathResolver != null ? pathResolver.getWorkspacePath(new File(absolutePath)) : null;
+  }
+
+  private static VirtualFileSystem getFileSystem() {
+    if (ApplicationManager.getApplication().isUnitTestMode()) {
+      return TempFileSystem.getInstance();
+    }
+    return LocalFileSystem.getInstance();
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/references/FileLookupData.java b/base/src/com/google/idea/blaze/base/lang/buildfile/references/FileLookupData.java
new file mode 100644
index 0000000..c6fe9ed
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/references/FileLookupData.java
@@ -0,0 +1,204 @@
+/*
+ * 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.references;
+
+import com.google.idea.blaze.base.lang.buildfile.completion.FilePathLookupElement;
+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.BlazePackage;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
+import com.intellij.icons.AllIcons;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.NullableLazyValue;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileFilter;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiManager;
+import com.intellij.util.PathUtil;
+import com.intellij.util.PlatformIcons;
+import icons.BlazeIcons;
+import javax.swing.Icon;
+import org.jetbrains.annotations.Nullable;
+
+/** The data relevant to finding file lookups. */
+public class FileLookupData {
+
+  /** The type of path string format */
+  public enum PathFormat {
+    /** BUILD label without a leading '//', which can only reference targets in the same package. */
+    PackageLocal,
+    /** a BUILD label with leading '//', which can reference targets in other packages. */
+    NonLocal,
+    /** a path string which can reference any files, and has no leading '//'. */
+    NonLocalWithoutInitialBackslashes
+  }
+
+  @Nullable
+  public static FileLookupData nonLocalFileLookup(String originalLabel, StringLiteral element) {
+    return nonLocalFileLookup(
+        originalLabel, element.getContainingFile(), element.getQuoteType(), PathFormat.NonLocal);
+  }
+
+  @Nullable
+  public static FileLookupData nonLocalFileLookup(
+      String originalLabel,
+      @Nullable BuildFile containingFile,
+      QuoteType quoteType,
+      PathFormat pathFormat) {
+    if (originalLabel.indexOf(':') != -1) {
+      // it's a package-local reference
+      return null;
+    }
+    // handle the single '/' case by calling twice.
+    String relativePath = StringUtil.trimStart(StringUtil.trimStart(originalLabel, "/"), "/");
+    if (relativePath.startsWith("/")) {
+      return null;
+    }
+    return new FileLookupData(
+        originalLabel, containingFile, null, relativePath, pathFormat, quoteType, null);
+  }
+
+  @Nullable
+  public static FileLookupData packageLocalFileLookup(String originalLabel, StringLiteral element) {
+    if (originalLabel.startsWith("/")) {
+      return null;
+    }
+    BlazePackage blazePackage = element.getBlazePackage();
+    BuildFile baseBuildFile = blazePackage != null ? blazePackage.buildFile : null;
+    return packageLocalFileLookup(originalLabel, element, baseBuildFile, null);
+  }
+
+  @Nullable
+  public static FileLookupData packageLocalFileLookup(
+      String originalLabel,
+      StringLiteral element,
+      @Nullable BuildFile basePackage,
+      @Nullable VirtualFileFilter fileFilter) {
+    String basePackagePath =
+        basePackage != null ? basePackage.getWorkspaceRelativePackagePath() : null;
+    if (basePackagePath == null) {
+      return null;
+    }
+    String filePath = basePackagePath + "/" + LabelUtils.getRuleComponent(originalLabel);
+    return new FileLookupData(
+        originalLabel,
+        basePackage,
+        basePackagePath,
+        filePath,
+        PathFormat.PackageLocal,
+        element.getQuoteType(),
+        fileFilter);
+  }
+
+  private final String originalLabel;
+  private final BuildFile containingFile;
+  @Nullable private final String containingPackage;
+  public final String filePathFragment;
+  public final PathFormat pathFormat;
+  private final QuoteType quoteType;
+  @Nullable private final VirtualFileFilter fileFilter;
+
+  private FileLookupData(
+      String originalLabel,
+      @Nullable BuildFile containingFile,
+      @Nullable String containingPackage,
+      String filePathFragment,
+      PathFormat pathFormat,
+      QuoteType quoteType,
+      @Nullable VirtualFileFilter fileFilter) {
+
+    this.originalLabel = originalLabel;
+    this.containingFile = containingFile;
+    this.containingPackage = containingPackage;
+    this.fileFilter = fileFilter;
+    this.filePathFragment = filePathFragment;
+    this.pathFormat = pathFormat;
+    this.quoteType = quoteType;
+
+    assert (pathFormat != PathFormat.PackageLocal
+        || (containingPackage != null && containingFile != null));
+  }
+
+  public boolean acceptFile(VirtualFile file) {
+    if (fileFilter != null && !fileFilter.accept(file)) {
+      return false;
+    }
+    if (pathFormat != PathFormat.PackageLocal) {
+      return file.isDirectory();
+    }
+    if (file.equals(containingFile.getOriginalFile().getVirtualFile())) {
+      return false;
+    }
+    boolean blazePackage = file.findChild("BUILD") != null;
+    return !blazePackage;
+  }
+
+  public FilePathLookupElement lookupElementForFile(
+      Project project, VirtualFile file, @Nullable WorkspacePath workspacePath) {
+    NullableLazyValue<Icon> icon =
+        new NullableLazyValue<Icon>() {
+          @Override
+          protected Icon compute() {
+            if (file.findChild("BUILD") != null) {
+              return BlazeIcons.BuildFile;
+            }
+            if (file.isDirectory()) {
+              return PlatformIcons.FOLDER_ICON;
+            }
+            PsiFile psiFile = PsiManager.getInstance(project).findFile(file);
+            return psiFile != null ? psiFile.getIcon(0) : AllIcons.FileTypes.Any_type;
+          }
+        };
+    String fullLabel =
+        workspacePath != null ? getFullLabel(workspacePath.relativePath()) : file.getPath();
+    String itemText = workspacePath != null ? getItemText(workspacePath.relativePath()) : fullLabel;
+    return new FilePathLookupElement(fullLabel, itemText, quoteType, icon);
+  }
+
+  private String getFullLabel(String relativePath) {
+    if (pathFormat != PathFormat.PackageLocal) {
+      if (pathFormat == PathFormat.NonLocal) {
+        relativePath = "//" + relativePath;
+      }
+      return relativePath;
+    }
+    String prefix;
+    int colonIndex = originalLabel.indexOf(':');
+    if (originalLabel.startsWith("/")) {
+      prefix = colonIndex == -1 ? originalLabel + ":" : originalLabel.substring(0, colonIndex + 1);
+    } else {
+      prefix = originalLabel.substring(0, colonIndex + 1);
+    }
+    return prefix + getItemText(relativePath);
+  }
+
+  private String getItemText(String relativePath) {
+    if (pathFormat == PathFormat.PackageLocal) {
+      return StringUtil.trimStart(relativePath.substring(containingPackage.length()), "/");
+    }
+    String parentPath = PathUtil.getParentPath(relativePath);
+    while (!parentPath.isEmpty()) {
+      if (filePathFragment.startsWith(parentPath + "/")) {
+        return StringUtil.trimStart(relativePath, parentPath + "/");
+      } else if (filePathFragment.startsWith(parentPath)) {
+        return StringUtil.trimStart(relativePath, parentPath);
+      }
+      parentPath = PathUtil.getParentPath(parentPath);
+    }
+    return relativePath;
+  }
+}
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
new file mode 100644
index 0000000..7f6cca8
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/references/FuncallReference.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.base.lang.buildfile.references;
+
+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.intellij.lang.ASTNode;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiReferenceBase;
+import com.intellij.util.IncorrectOperationException;
+import javax.annotation.Nullable;
+
+/** Reference from a function call to the function declaration */
+public class FuncallReference extends PsiReferenceBase<FuncallExpression> {
+
+  public FuncallReference(FuncallExpression element, TextRange rangeInElement) {
+    super(element, rangeInElement, /*soft*/ true);
+  }
+
+  @Nullable
+  @Override
+  public FunctionStatement resolve() {
+    String functionName = myElement.getFunctionName();
+    BuildFile file = (BuildFile) myElement.getContainingFile();
+    if (functionName == null || file == null) {
+      return null;
+    }
+    return file.findFunctionInScope(functionName);
+  }
+
+  @Override
+  public Object[] getVariants() {
+    return EMPTY_ARRAY;
+  }
+
+  @Override
+  public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException {
+    ASTNode oldNode = myElement.getFunctionNameNode();
+    if (oldNode != null) {
+      ASTNode newNode = PsiUtils.createNewName(myElement.getProject(), newElementName);
+      myElement.getNode().replaceChild(oldNode, newNode);
+    }
+    return myElement;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/references/GlobReference.java b/base/src/com/google/idea/blaze/base/lang/buildfile/references/GlobReference.java
new file mode 100644
index 0000000..c880fb8
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/references/GlobReference.java
@@ -0,0 +1,212 @@
+/*
+ * 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.references;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.io.FileAttributeProvider;
+import com.google.idea.blaze.base.lang.buildfile.globbing.UnixGlob;
+import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
+import com.google.idea.blaze.base.lang.buildfile.psi.Expression;
+import com.google.idea.blaze.base.lang.buildfile.psi.GlobExpression;
+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.psi.util.PsiUtils;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiElementResolveResult;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiFileSystemItem;
+import com.intellij.psi.ResolveResult;
+import com.intellij.psi.impl.source.resolve.reference.impl.PsiPolyVariantCachingReference;
+import com.intellij.util.IncorrectOperationException;
+import java.io.File;
+import java.util.List;
+import java.util.function.Predicate;
+
+/** References from a glob to a list of files contained in the same blaze package. */
+public class GlobReference extends PsiPolyVariantCachingReference {
+
+  private static final Logger LOG = Logger.getInstance(GlobReference.class);
+
+  private final GlobExpression element;
+
+  public GlobReference(GlobExpression element) {
+    this.element = element;
+  }
+
+  /**
+   * Returns true iff the complete, resolved glob references the specified file.
+   *
+   * <p>In particular, it's not concerned with individual patterns referencing the file, only
+   * whether the overall glob does (i.e. returns false if the file is explicitly excluded).
+   */
+  public boolean matches(String packageRelativePath, boolean isDirectory) {
+    if (isDirectory && element.areDirectoriesExcluded()) {
+      return false;
+    }
+    for (String exclude : resolveListContents(element.getExcludes())) {
+      if (UnixGlob.matches(exclude, packageRelativePath)) {
+        return false;
+      }
+    }
+    for (String include : resolveListContents(element.getIncludes())) {
+      if (UnixGlob.matches(include, packageRelativePath)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Returns true iff an include pattern *without wildcards* matches the given path and it's not
+   * excluded.
+   */
+  public boolean matchesDirectly(String packageRelativePath, boolean isDirectory) {
+    if (isDirectory && element.areDirectoriesExcluded()) {
+      return false;
+    }
+    for (String exclude : resolveListContents(element.getExcludes())) {
+      if (UnixGlob.matches(exclude, packageRelativePath)) {
+        return false;
+      }
+    }
+    for (String include : resolveListContents(element.getIncludes())) {
+      if (!hasWildcard(include) && UnixGlob.matches(include, packageRelativePath)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private static boolean hasWildcard(String pattern) {
+    return pattern.contains("*");
+  }
+
+  @Override
+  protected ResolveResult[] resolveInner(boolean incompleteCode, PsiFile containingFile) {
+    File containingDirectory = ((BuildFile) containingFile).getFile().getParentFile();
+    if (containingDirectory == null) {
+      return ResolveResult.EMPTY_ARRAY;
+    }
+    List<String> includes = resolveListContents(element.getIncludes());
+    List<String> excludes = resolveListContents(element.getExcludes());
+    boolean directoriesExcluded = element.areDirectoriesExcluded();
+    if (includes.isEmpty()) {
+      return ResolveResult.EMPTY_ARRAY;
+    }
+
+    try {
+      List<File> files =
+          UnixGlob.forPath(containingDirectory)
+              .addPatterns(includes)
+              .addExcludes(excludes)
+              .setExcludeDirectories(directoriesExcluded)
+              .setDirectoryFilter(directoryFilter(containingDirectory.getPath()))
+              .glob();
+      List<ResolveResult> results = Lists.newArrayListWithCapacity(files.size());
+      for (File file : files) {
+        PsiFileSystemItem psiFile =
+            BuildReferenceManager.getInstance(element.getProject()).resolveFile(file);
+        if (psiFile != null) {
+          results.add(new PsiElementResolveResult(psiFile));
+        }
+      }
+      return results.toArray(ResolveResult.EMPTY_ARRAY);
+
+    } catch (Exception e) {
+      return ResolveResult.EMPTY_ARRAY;
+    }
+  }
+
+  private static Predicate<File> directoryFilter(String base) {
+    return file -> {
+      if (base.equals(file.getPath())) {
+        return true;
+      }
+      File child = new File(file, "BUILD");
+      FileAttributeProvider attributeProvider = FileAttributeProvider.getInstance();
+      return !attributeProvider.exists(child) || attributeProvider.isDirectory(child);
+    };
+  }
+
+  private static List<String> resolveListContents(Expression expr) {
+    if (expr == null) {
+      return ImmutableList.of();
+    }
+    PsiElement rootElement = PsiUtils.getReferencedTargetValue(expr);
+    if (!(rootElement instanceof ListLiteral)) {
+      return ImmutableList.of();
+    }
+    Expression[] children = ((ListLiteral) rootElement).getElements();
+    List<String> strings = Lists.newArrayListWithCapacity(children.length);
+    for (Expression child : children) {
+      if (child instanceof StringLiteral) {
+        strings.add(((StringLiteral) child).getStringContents());
+      }
+    }
+    return strings;
+  }
+
+  @Override
+  public GlobExpression getElement() {
+    return element;
+  }
+
+  @Override
+  public TextRange getRangeInElement() {
+    return element.getReferenceTextRange();
+  }
+
+  @Override
+  public boolean isSoft() {
+    return true;
+  }
+
+  @Override
+  public Object[] getVariants() {
+    return EMPTY_ARRAY;
+  }
+
+  @Override
+  public String getCanonicalText() {
+    return getValue();
+  }
+
+  @Override
+  public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException {
+    return element;
+  }
+
+  @Override
+  public PsiElement bindToElement(PsiElement element) throws IncorrectOperationException {
+    return this.element;
+  }
+
+  public String getValue() {
+    String text = element.getText();
+    final TextRange range = getRangeInElement();
+    try {
+      return range.substring(text);
+    } catch (StringIndexOutOfBoundsException e) {
+      LOG.error(
+          "Wrong range in reference " + this + ": " + range + ". Reference text: '" + text + "'",
+          e);
+      return text;
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/references/KeywordArgumentReference.java b/base/src/com/google/idea/blaze/base/lang/buildfile/references/KeywordArgumentReference.java
new file mode 100644
index 0000000..194bfe7
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/references/KeywordArgumentReference.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.lang.buildfile.references;
+
+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.util.PsiUtils;
+import com.intellij.lang.ASTNode;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.psi.PsiElement;
+import com.intellij.util.IncorrectOperationException;
+import javax.annotation.Nullable;
+
+/**
+ * Reference from keyword argument to a named function parameter. TODO: This is soft, because we
+ * can't always find the function. However we should implement error highlighting for imported
+ * Skylark functions.
+ */
+public class KeywordArgumentReference extends ArgumentReference<Argument.Keyword> {
+
+  public KeywordArgumentReference(Argument.Keyword element, TextRange rangeInElement) {
+    super(element, rangeInElement, false);
+  }
+
+  /**
+   * Find the referenced function. If it has a keyword parameter with matching name, return that.
+   * Otherwise if it has a **kwargs param, return that. Else return the function itself.
+   */
+  @Nullable
+  @Override
+  public PsiElement resolve() {
+    String keyword = myElement.getName();
+    if (keyword == null) {
+      return null;
+    }
+    FunctionStatement function = resolveFunction();
+    if (function == null) {
+      return null;
+    }
+    Parameter.StarStar kwargsParameter = null;
+    for (Parameter param : function.getParameters()) {
+      if (param instanceof Parameter.StarStar) {
+        kwargsParameter = (Parameter.StarStar) param;
+        continue;
+      }
+      if (keyword.equals(param.getName())) {
+        return param;
+      }
+    }
+    if (kwargsParameter != null) {
+      return kwargsParameter;
+    }
+    return null;
+  }
+
+  @Override
+  public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException {
+    ASTNode oldNode = myElement.getNameNode();
+    if (oldNode != null) {
+      ASTNode newNode = PsiUtils.createNewName(myElement.getProject(), newElementName);
+      myElement.getNode().replaceChild(oldNode, newNode);
+    }
+    return myElement;
+  }
+}
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
new file mode 100644
index 0000000..75c2aba
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/references/LabelReference.java
@@ -0,0 +1,262 @@
+/*
+ * 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.references;
+
+import com.google.idea.blaze.base.lang.buildfile.completion.BuildLookupElement;
+import com.google.idea.blaze.base.lang.buildfile.completion.LabelRuleLookupElement;
+import com.google.idea.blaze.base.lang.buildfile.psi.Argument;
+import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
+import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile.BlazeFileType;
+import com.google.idea.blaze.base.lang.buildfile.psi.FuncallExpression;
+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.psi.util.PsiUtils;
+import com.google.idea.blaze.base.lang.buildfile.search.BlazePackage;
+import com.google.idea.blaze.base.lang.buildfile.search.ResolveUtil;
+import com.google.idea.blaze.base.model.primitives.Label;
+import com.intellij.lang.ASTNode;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.vfs.VirtualFileFilter;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiReferenceBase;
+import com.intellij.util.ArrayUtil;
+import com.intellij.util.IncorrectOperationException;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/** Converts a blaze label into an absolute path, then resolves that path to a PsiElements */
+public class LabelReference extends PsiReferenceBase<StringLiteral> {
+
+  public LabelReference(StringLiteral element, boolean soft) {
+    super(element, new TextRange(0, element.getTextLength()), soft);
+  }
+
+  @Nullable
+  @Override
+  public PsiElement resolve() {
+    /* Possibilities:
+     * - target
+     * - data file (.java, .txt, etc.)
+     * - glob contents (not yet handling globs)
+     */
+    return resolveTarget(myElement.getStringContents());
+  }
+
+  @Nullable
+  private PsiElement resolveTarget(String labelString) {
+    Label label = getLabel(labelString);
+    if (label == null) {
+      return null;
+    }
+    if (!validLabelLocation(myElement)) {
+      return null;
+    }
+    if (!labelString.startsWith("//") && insideSkylarkExtension(myElement)) {
+      return getReferenceManager().resolveLabel(label.blazePackage(), label.ruleName(), true);
+    }
+    return getReferenceManager().resolveLabel(label);
+  }
+
+  /**
+   * Hack: don't include 'name' keyword arguments -- they'll be a reference to the enclosing
+   * function call / rule, and show up as unnecessary references to that rule.
+   */
+  private static boolean validLabelLocation(StringLiteral element) {
+    PsiElement parent = element.getParent();
+    if (parent instanceof Argument.Keyword) {
+      String argName = ((Argument.Keyword) parent).getName();
+      if ("name".equals(argName)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  @NotNull
+  @Override
+  public Object[] getVariants() {
+    if (!validLabelLocation(myElement)) {
+      return EMPTY_ARRAY;
+    }
+    String labelString = LabelUtils.trimToDummyIdentifier(myElement.getStringContents());
+    return ArrayUtil.mergeArrays(getRuleLookups(labelString), getFileLookups(labelString));
+  }
+
+  private BuildLookupElement[] getRuleLookups(String labelString) {
+    if (labelString.endsWith("/")
+        || (labelString.startsWith("/") && !labelString.contains(":"))
+        || skylarkExtensionReference(myElement)) {
+      return BuildLookupElement.EMPTY_ARRAY;
+    }
+    String packagePrefix = LabelUtils.getPackagePathComponent(labelString);
+    BuildFile referencedBuildFile =
+        LabelUtils.getReferencedBuildFile(myElement.getContainingFile(), packagePrefix);
+    if (referencedBuildFile == null) {
+      return BuildLookupElement.EMPTY_ARRAY;
+    }
+    String self = null;
+    if (referencedBuildFile == myElement.getContainingFile()) {
+      FuncallExpression funcall = PsiUtils.getParentOfType(myElement, FuncallExpression.class);
+      if (funcall != null) {
+        self = funcall.getName();
+      }
+    }
+    return LabelRuleLookupElement.collectAllRules(
+        referencedBuildFile, labelString, packagePrefix, self, myElement.getQuoteType());
+  }
+
+  private BuildLookupElement[] getFileLookups(String labelString) {
+    if (labelString.startsWith("//") || labelString.equals("/")) {
+      return getNonLocalFileLookups(labelString);
+    }
+    return getPackageLocalFileLookups(labelString);
+  }
+
+  private BuildLookupElement[] getNonLocalFileLookups(String labelString) {
+    BuildLookupElement[] skylarkExtLookups = getSkylarkExtensionLookups(labelString);
+    FileLookupData lookupData = FileLookupData.nonLocalFileLookup(labelString, myElement);
+    BuildLookupElement[] packageLookups =
+        lookupData != null
+            ? getReferenceManager().resolvePackageLookupElements(lookupData)
+            : BuildLookupElement.EMPTY_ARRAY;
+    return ArrayUtil.mergeArrays(skylarkExtLookups, packageLookups);
+  }
+
+  private BuildLookupElement[] getPackageLocalFileLookups(String labelString) {
+    if (skylarkExtensionReference(myElement)) {
+      return getSkylarkExtensionLookups(labelString);
+    }
+    FileLookupData lookupData = FileLookupData.packageLocalFileLookup(labelString, myElement);
+    return lookupData != null
+        ? getReferenceManager().resolvePackageLookupElements(lookupData)
+        : BuildLookupElement.EMPTY_ARRAY;
+  }
+
+  private BuildLookupElement[] getSkylarkExtensionLookups(String labelString) {
+    if (!skylarkExtensionReference(myElement)) {
+      return BuildLookupElement.EMPTY_ARRAY;
+    }
+    String packagePrefix = LabelUtils.getPackagePathComponent(labelString);
+    BuildFile parentFile = myElement.getContainingFile();
+    if (parentFile == null) {
+      return BuildLookupElement.EMPTY_ARRAY;
+    }
+    BlazePackage containingPackage = BlazePackage.getContainingPackage(parentFile);
+    if (containingPackage == null) {
+      return BuildLookupElement.EMPTY_ARRAY;
+    }
+    BuildFile referencedBuildFile =
+        LabelUtils.getReferencedBuildFile(containingPackage.buildFile, packagePrefix);
+
+    // Directories before the colon are already covered.
+    // We're only concerned with package-local directories.
+    boolean hasColon = labelString.indexOf(':') != -1;
+    VirtualFileFilter filter =
+        file ->
+            ("bzl".equals(file.getExtension()) && !file.getPath().equals(parentFile.getFilePath()))
+                || (hasColon && file.isDirectory());
+    FileLookupData lookupData =
+        FileLookupData.packageLocalFileLookup(labelString, myElement, referencedBuildFile, filter);
+
+    return lookupData != null
+        ? getReferenceManager().resolvePackageLookupElements(lookupData)
+        : BuildLookupElement.EMPTY_ARRAY;
+  }
+
+  private BuildReferenceManager getReferenceManager() {
+    return BuildReferenceManager.getInstance(myElement.getProject());
+  }
+
+  @Override
+  public PsiElement bindToElement(PsiElement element) throws IncorrectOperationException {
+    PsiFile file = ResolveUtil.asFileSearch(element);
+    if (file == null) {
+      return super.bindToElement(element);
+    }
+    if (file.equals(resolve())) {
+      return myElement;
+    }
+    BlazePackage currentPackageDir = myElement.getBlazePackage();
+    if (currentPackageDir == null) {
+      return myElement;
+    }
+    BlazePackage newPackageDir = BlazePackage.getContainingPackage(file);
+    if (!currentPackageDir.equals(newPackageDir)) {
+      return myElement;
+    }
+
+    String newRuleName =
+        newPackageDir.getPackageRelativePath(file.getViewProvider().getVirtualFile().getPath());
+    return handleRename(newRuleName);
+  }
+
+  @Override
+  public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException {
+    String currentString = myElement.getStringContents();
+    Label label = getLabel(currentString);
+    if (label == null) {
+      return myElement;
+    }
+    String ruleName = label.ruleName().toString();
+    String newRuleName = newElementName;
+
+    // handle subdirectories
+    int lastSlashIndex = ruleName.lastIndexOf('/');
+    if (lastSlashIndex != -1) {
+      newRuleName = ruleName.substring(0, lastSlashIndex + 1) + newElementName;
+    }
+
+    String packageString = LabelUtils.getPackagePathComponent(currentString);
+    if (packageString.isEmpty() && !currentString.contains(":")) {
+      return handleRename(newRuleName);
+    }
+    return handleRename(packageString + ":" + newRuleName);
+  }
+
+  private PsiElement handleRename(String newStringContents) {
+    ASTNode node = myElement.getNode();
+    node.replaceChild(
+        node.getFirstChildNode(),
+        PsiUtils.createNewLabel(myElement.getProject(), newStringContents));
+    return myElement;
+  }
+
+  @Nullable
+  private Label getLabel(String labelString) {
+    if (labelString.indexOf('*') != -1) {
+      // don't even try to handle globs, yet.
+      return null;
+    }
+    BlazePackage blazePackage = myElement.getBlazePackage();
+    return LabelUtils.createLabelFromString(
+        blazePackage != null ? blazePackage.buildFile : null, labelString);
+  }
+
+  private static boolean skylarkExtensionReference(StringLiteral element) {
+    PsiElement parent = element.getParent();
+    if (!(parent instanceof LoadStatement)) {
+      return false;
+    }
+    return ((LoadStatement) parent).getImportPsiElement() == element;
+  }
+
+  private static boolean insideSkylarkExtension(StringLiteral element) {
+    BuildFile containingFile = element.getContainingFile();
+    return containingFile != null
+        && containingFile.getBlazeFileType() == BlazeFileType.SkylarkExtension;
+  }
+}
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
new file mode 100644
index 0000000..251baed
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/references/LabelUtils.java
@@ -0,0 +1,173 @@
+/*
+ * 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.references;
+
+import com.google.common.collect.Lists;
+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.WorkspacePath;
+import com.intellij.codeInsight.completion.CompletionUtilCore;
+import com.intellij.util.PathUtil;
+import java.util.List;
+import javax.annotation.Nullable;
+
+/** Utility methods for working with blaze labels. */
+public class LabelUtils {
+
+  /** Label referring to the given file, or null if it cannot be determined. */
+  @Nullable
+  public static Label createLabelForFile(BlazePackage blazePackage, @Nullable String filePath) {
+    if (blazePackage == null || filePath == null) {
+      return null;
+    }
+    String relativeFilePath = blazePackage.getPackageRelativePath(filePath);
+    if (relativeFilePath == null) {
+      return null;
+    }
+    return createLabelFromRuleName(blazePackage, relativeFilePath);
+  }
+
+  /**
+   * Returns null if this is not a valid Label (if either the package path or rule name are invalid)
+   */
+  @Nullable
+  public static Label createLabelFromRuleName(
+      @Nullable BlazePackage blazePackage, @Nullable String ruleName) {
+    if (blazePackage == null || ruleName == null) {
+      return null;
+    }
+    WorkspacePath packagePath = blazePackage.buildFile.getPackageWorkspacePath();
+    RuleName name = RuleName.createIfValid(ruleName);
+    if (packagePath == null || name == null) {
+      return null;
+    }
+    // TODO: Is Label too inefficient?
+    // (validation done twice,creating List during constructor,
+    // re-parsing to extract the package/rule each time)
+    return new Label(packagePath, name);
+  }
+
+  /**
+   * Canonicalizes the label (to the form //packagePath:packageRelativeTarget). Returns null if the
+   * string does not represent a valid label.
+   */
+  @Nullable
+  public static Label createLabelFromString(
+      @Nullable BuildFile file, @Nullable String labelString) {
+    if (labelString == null) {
+      return null;
+    }
+    int colonIndex = labelString.indexOf(':');
+    if (labelString.startsWith("//")) {
+      if (colonIndex == -1) {
+        // add the implicit rule name
+        labelString += ":" + PathUtil.getFileName(labelString);
+      }
+      return Label.createIfValid(labelString);
+    }
+    WorkspacePath packagePath = file != null ? file.getPackageWorkspacePath() : null;
+    if (packagePath == null) {
+      return null;
+    }
+    String localPath = colonIndex == -1 ? labelString : labelString.substring(1);
+    return Label.createIfValid("//" + packagePath.relativePath() + ":" + localPath);
+  }
+
+  /** The blaze file referenced by the label. */
+  @Nullable
+  public static BuildFile getReferencedBuildFile(
+      @Nullable BuildFile containingFile, String packagePathComponent) {
+    if (containingFile == null) {
+      return null;
+    }
+    if (!packagePathComponent.startsWith("//")) {
+      return containingFile;
+    }
+    return BuildReferenceManager.getInstance(containingFile.getProject())
+        .resolveBlazePackage(packagePathComponent);
+  }
+
+  public static String getRuleComponent(String labelString) {
+    if (labelString.startsWith("/")) {
+      int colonIndex = labelString.indexOf(':');
+      return colonIndex == -1 ? "" : labelString.substring(colonIndex + 1);
+    }
+    return labelString.startsWith(":") ? labelString.substring(1) : labelString;
+  }
+
+  public static String getPackagePathComponent(String labelString) {
+    if (!labelString.startsWith("//")) {
+      return "";
+    }
+    int colonIndex = labelString.indexOf(':');
+    return colonIndex == -1 ? labelString : labelString.substring(0, colonIndex);
+  }
+
+  /** 'load' reference. Of the form [path][/ or :][extra_path/]file_name.bzl */
+  @Nullable
+  public static String getNiceSkylarkFileName(@Nullable String path) {
+    if (path == null) {
+      return null;
+    }
+    int colonIndex = path.lastIndexOf(":");
+    if (colonIndex != -1) {
+      path = path.substring(colonIndex + 1);
+    }
+    int lastSlash = path.lastIndexOf("/");
+    if (lastSlash == -1) {
+      return path;
+    }
+    return path.substring(lastSlash + 1);
+  }
+
+  /**
+   * All the possible strings which could resolve to the given target.
+   *
+   * @param includePackageLocalLabels if true, include strings omitting the package path
+   */
+  public static List<String> getAllValidLabelStrings(
+      Label label, boolean includePackageLocalLabels) {
+    List<String> strings = Lists.newArrayList();
+    strings.add(label.toString());
+    String packagePath = label.blazePackage().relativePath();
+    if (packagePath.isEmpty()) {
+      return strings;
+    }
+    String ruleName = label.ruleName().toString();
+    if (PathUtil.getFileName(packagePath).equals(ruleName)) {
+      strings.add("//" + packagePath); // implicit rule name equal to package name
+    }
+    if (includePackageLocalLabels) {
+      strings.add(":" + ruleName);
+      strings.add(ruleName);
+    }
+    return strings;
+  }
+
+  /**
+   * IntelliJ inserts an identifier string at the caret position during code completion.<br>
+   * We're only interested in the portion of the string before the caret, so trim the rest.
+   */
+  public static String trimToDummyIdentifier(String string) {
+    int dummyIdentifierIndex = string.indexOf(CompletionUtilCore.DUMMY_IDENTIFIER);
+    if (dummyIdentifierIndex == -1) {
+      dummyIdentifierIndex = string.indexOf(CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED);
+    }
+    return dummyIdentifierIndex == -1 ? string : string.substring(0, dummyIdentifierIndex);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/references/LoadedSymbolReference.java b/base/src/com/google/idea/blaze/base/lang/buildfile/references/LoadedSymbolReference.java
new file mode 100644
index 0000000..219d843
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/references/LoadedSymbolReference.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.lang.buildfile.references;
+
+import com.google.idea.blaze.base.lang.buildfile.completion.CompletionResultsProcessor;
+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.StringLiteral;
+import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
+import com.intellij.lang.ASTNode;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiReferenceBase;
+import com.intellij.util.IncorrectOperationException;
+import javax.annotation.Nullable;
+
+/** References from load statement string to a function or variable in a Skylark extension */
+public class LoadedSymbolReference extends PsiReferenceBase<StringLiteral> {
+
+  private final LabelReference bzlFileReference;
+
+  public LoadedSymbolReference(StringLiteral element, LabelReference bzlFileReference) {
+    super(element, new TextRange(0, element.getTextLength()), /*soft*/ false);
+    this.bzlFileReference = bzlFileReference;
+  }
+
+  @Nullable
+  @Override
+  public BuildElement resolve() {
+    PsiElement bzlFile = bzlFileReference.resolve();
+    if (!(bzlFile instanceof BuildFile)) {
+      return null;
+    }
+    return ((BuildFile) bzlFile).findSymbolInScope(myElement.getStringContents());
+  }
+
+  @Override
+  public Object[] getVariants() {
+    PsiElement bzlFile = bzlFileReference.resolve();
+    if (!(bzlFile instanceof BuildFile)) {
+      return EMPTY_ARRAY;
+    }
+    CompletionResultsProcessor processor =
+        new CompletionResultsProcessor(myElement, myElement.getQuoteType());
+    ((BuildFile) bzlFile).searchSymbolsInScope(processor, null);
+    return processor.getResults().toArray();
+  }
+
+  @Override
+  public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException {
+    ASTNode newNode = PsiUtils.createNewLabel(myElement.getProject(), newElementName);
+    myElement.getNode().replaceChild(myElement.getNode().getFirstChildNode(), newNode);
+    return myElement;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/references/LocalReference.java b/base/src/com/google/idea/blaze/base/lang/buildfile/references/LocalReference.java
new file mode 100644
index 0000000..85808b1
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/references/LocalReference.java
@@ -0,0 +1,66 @@
+/*
+ * 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.references;
+
+import com.google.idea.blaze.base.lang.buildfile.completion.CompletionResultsProcessor;
+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.lang.buildfile.search.ResolveUtil;
+import com.intellij.lang.ASTNode;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiReferenceBase;
+import com.intellij.util.IncorrectOperationException;
+import javax.annotation.Nullable;
+
+/**
+ * Reference from a ReferenceExpression to a PsiNamedElement in the same scope. This includes
+ * symbols accessible via 'load' statments.
+ */
+public class LocalReference extends PsiReferenceBase<ReferenceExpression> {
+
+  public LocalReference(ReferenceExpression element) {
+    super(element, new TextRange(0, element.getTextLength()), /*soft*/ false);
+  }
+
+  @Nullable
+  @Override
+  public PsiElement resolve() {
+    String referencedName = myElement.getReferencedName();
+    if (referencedName == null) {
+      return null;
+    }
+    return ResolveUtil.findInScope(myElement, referencedName);
+  }
+
+  @Override
+  public Object[] getVariants() {
+    CompletionResultsProcessor processor =
+        new CompletionResultsProcessor(myElement, QuoteType.NoQuotes);
+    ResolveUtil.searchInScope(myElement, processor);
+    return processor.getResults().toArray();
+  }
+
+  @Override
+  public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException {
+    ASTNode oldNode = myElement.getNameElement();
+    if (oldNode != null) {
+      ASTNode newNode = PsiUtils.createNewName(myElement.getProject(), newElementName);
+      myElement.getNode().replaceChild(oldNode, newNode);
+    }
+    return myElement;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/references/PackageReferenceFragment.java b/base/src/com/google/idea/blaze/base/lang/buildfile/references/PackageReferenceFragment.java
new file mode 100644
index 0000000..6dba496
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/references/PackageReferenceFragment.java
@@ -0,0 +1,107 @@
+/*
+ * 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.references;
+
+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.primitives.WorkspacePath;
+import com.intellij.lang.ASTNode;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiReferenceBase;
+import com.intellij.util.IncorrectOperationException;
+import com.intellij.util.PathUtil;
+import javax.annotation.Nullable;
+
+/** The label component preceeding the colon. */
+public class PackageReferenceFragment extends PsiReferenceBase<StringLiteral> {
+
+  public PackageReferenceFragment(LabelReference labelReference) {
+    super(labelReference.getElement(), labelReference.getRangeInElement(), labelReference.isSoft());
+  }
+
+  @Nullable
+  private WorkspacePath getWorkspacePath(String labelString) {
+    if (!labelString.startsWith("//")) {
+      return null;
+    }
+    int colonIndex = labelString.indexOf(':');
+    int endIndex = colonIndex != -1 ? colonIndex : labelString.length();
+    return WorkspacePath.createIfValid(labelString.substring(2, endIndex));
+  }
+
+  @Override
+  public TextRange getRangeInElement() {
+    String rawText = myElement.getText();
+    boolean valid = getWorkspacePath(myElement.getStringContents()) != null;
+    if (!valid) {
+      return TextRange.EMPTY_RANGE;
+    }
+    int endIndex = rawText.indexOf(':');
+    if (endIndex == -1) {
+      endIndex = rawText.length() - 1;
+    }
+    return new TextRange(1, endIndex);
+  }
+
+  @Nullable
+  @Override
+  public BuildFile resolve() {
+    WorkspacePath workspacePath = getWorkspacePath(myElement.getStringContents());
+    return BuildReferenceManager.getInstance(myElement.getProject())
+        .resolveBlazePackage(workspacePath);
+  }
+
+  @Override
+  public Object[] getVariants() {
+    return EMPTY_ARRAY;
+  }
+
+  @Override
+  public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException {
+    return myElement; // renaming a BUILD file has no effect on the package label fragments
+  }
+
+  @Override
+  public PsiElement bindToElement(PsiElement element) throws IncorrectOperationException {
+    if (!(element instanceof BuildFile)) {
+      return super.bindToElement(element);
+    }
+    if (element.equals(resolve())) {
+      return myElement;
+    }
+    WorkspacePath newPath = ((BuildFile) element).getPackageWorkspacePath();
+    if (newPath == null) {
+      return myElement;
+    }
+    String labelString = myElement.getStringContents();
+    int colonIndex = labelString.indexOf(':');
+    if (colonIndex != -1) {
+      return handleRename("//" + newPath + labelString.substring(colonIndex));
+    }
+    // need to assume there's an implicit rule name
+    return handleRename("//" + newPath + ":" + PathUtil.getFileName(labelString));
+  }
+
+  private PsiElement handleRename(String newStringContents) {
+    ASTNode node = myElement.getNode();
+    node.replaceChild(
+        node.getFirstChildNode(),
+        PsiUtils.createNewLabel(myElement.getProject(), newStringContents));
+    return myElement;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/references/QuoteType.java b/base/src/com/google/idea/blaze/base/lang/buildfile/references/QuoteType.java
new file mode 100644
index 0000000..316f0d1
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/references/QuoteType.java
@@ -0,0 +1,35 @@
+/*
+ * 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.references;
+
+/** The type of quotes surrounding a PSI element. */
+public enum QuoteType {
+  Single("'"),
+  Double("\""),
+  TripleSingle("'''"),
+  TripleDouble("\"\"\""),
+  NoQuotes("");
+
+  public final String quoteString;
+
+  QuoteType(String quoteString) {
+    this.quoteString = quoteString;
+  }
+
+  public String wrap(String unquotedText) {
+    return quoteString + unquotedText + quoteString;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/references/TargetReference.java b/base/src/com/google/idea/blaze/base/lang/buildfile/references/TargetReference.java
new file mode 100644
index 0000000..52ee6da
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/references/TargetReference.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.lang.buildfile.references;
+
+import com.google.idea.blaze.base.lang.buildfile.completion.CompletionResultsProcessor;
+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.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;
+
+/**
+ * A reference to an earlier declaration of this symbol (to handle cases where a symbol is the
+ * target of multiple assignment statements).
+ */
+public class TargetReference extends PsiReferenceBase<TargetExpression> {
+
+  public TargetReference(TargetExpression element) {
+    super(element, new TextRange(0, element.getTextLength()), /*soft*/ true);
+  }
+
+  @Nullable
+  @Override
+  public PsiElement resolve() {
+    String referencedName = myElement.getName();
+    if (referencedName == null) {
+      return null;
+    }
+    PsiNamedElement target = ResolveUtil.findInScope(myElement, referencedName);
+    return target != null ? target : null;
+  }
+
+  @Override
+  public Object[] getVariants() {
+    CompletionResultsProcessor processor =
+        new CompletionResultsProcessor(myElement, QuoteType.NoQuotes);
+    ResolveUtil.searchInScope(myElement, processor);
+    return processor.getResults().toArray();
+  }
+
+  @Override
+  public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException {
+    ASTNode oldNode = myElement.getNameNode();
+    if (oldNode != null) {
+      ASTNode newNode = PsiUtils.createNewName(myElement.getProject(), newElementName);
+      myElement.getNode().replaceChild(oldNode, newNode);
+    }
+    return myElement;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/search/BlazePackage.java b/base/src/com/google/idea/blaze/base/lang/buildfile/search/BlazePackage.java
new file mode 100644
index 0000000..6c2e3a3
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/search/BlazePackage.java
@@ -0,0 +1,187 @@
+/*
+ * 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.search;
+
+import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
+import com.google.idea.blaze.base.settings.Blaze;
+import com.intellij.history.core.Paths;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PackagePrefixFileSystemItem;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiFileSystemItem;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.util.PathUtil;
+import com.intellij.util.Processor;
+import java.util.Objects;
+import javax.annotation.Nullable;
+
+/** Defines the files accessible by a given blaze package. */
+public class BlazePackage {
+
+  @Nullable
+  public static BlazePackage getContainingPackage(PsiFileSystemItem file) {
+    if (file instanceof PsiFile) {
+      file = ((PsiFile) file).getOriginalFile();
+    }
+    if (file instanceof BuildFile && file.getName().equals("BUILD")) {
+      return new BlazePackage((BuildFile) file);
+    }
+    return getContainingPackage(getPsiDirectory(file));
+  }
+
+  @Nullable
+  private static PsiDirectory getPsiDirectory(PsiFileSystemItem file) {
+    if (file instanceof PsiDirectory) {
+      return (PsiDirectory) file;
+    }
+    if (file instanceof PsiFile) {
+      return ((PsiFile) file).getContainingDirectory();
+    }
+    if (file instanceof PackagePrefixFileSystemItem) {
+      return ((PackagePrefixFileSystemItem) file).getDirectory();
+    }
+    return null;
+  }
+
+  @Nullable
+  public static BlazePackage getContainingPackage(@Nullable PsiDirectory dir) {
+    while (dir != null) {
+      PsiFile buildFile = dir.findFile("BUILD");
+      if (buildFile != null) {
+        return buildFile instanceof BuildFile ? new BlazePackage((BuildFile) buildFile) : null;
+      }
+      dir = dir.getParentDirectory();
+    }
+    return null;
+  }
+
+  public final BuildFile buildFile;
+
+  private BlazePackage(BuildFile buildFile) {
+    this.buildFile = buildFile;
+  }
+
+  @Nullable
+  public PsiDirectory getContainingDirectory() {
+    return buildFile.getParent();
+  }
+
+  /**
+   * The search scope corresponding to this package (i.e. not crossing package boundaries).
+   *
+   * @param onlyBlazeFiles if true, the scope is limited to BUILD and Skylark files.
+   */
+  public GlobalSearchScope getSearchScope(boolean onlyBlazeFiles) {
+    return new BlazePackageSearchScope(this, onlyBlazeFiles);
+  }
+
+  /**
+   * Returns the file path relative to this blaze package, or null if it does lie inside this
+   * package
+   */
+  @Nullable
+  public String getPackageRelativePath(String filePath) {
+    String packageFilePath = PathUtil.getParentPath(buildFile.getFilePath());
+    return Paths.relativeIfUnder(filePath, packageFilePath);
+  }
+
+  /** Formats the child file path as a BUILD label (i.e. "//package_path[:relative_path]") */
+  @Nullable
+  public String getBuildLabelForChild(String filePath) {
+    WorkspacePath packagePath = buildFile.getPackageWorkspacePath();
+    if (packagePath == null) {
+      return null;
+    }
+    String relativePath = getPackageRelativePath(filePath);
+    if (relativePath == null) {
+      return null;
+    }
+    if (relativePath.isEmpty()) {
+      return "//" + packagePath;
+    }
+    return "//" + packagePath + ":" + relativePath;
+  }
+
+  /**
+   * The path from the blaze package directory to the child file, or null if the package directory
+   * is not an ancestor of the provided file.
+   */
+  @Nullable
+  public String getRelativePathToChild(@Nullable VirtualFile child) {
+    if (child == null) {
+      return null;
+    }
+    String packagePath = PathUtil.getParentPath(buildFile.getFilePath());
+    return Paths.relativeIfUnder(child.getPath(), packagePath);
+  }
+
+  /**
+   * Walks the directory tree, processing all files accessible by this package (i.e. not processing
+   * child packages).
+   */
+  public void processPackageFiles(Processor<PsiFile> processor) {
+    PsiDirectory dir = getContainingDirectory();
+    if (dir == null) {
+      return;
+    }
+    processPackageFiles(processor, dir);
+  }
+
+  private static void processPackageFiles(Processor<PsiFile> processor, PsiDirectory directory) {
+    processDirectory(processor, directory);
+    for (PsiDirectory child : directory.getSubdirectories()) {
+      if (!isBlazePackage(child)) {
+        processPackageFiles(processor, directory);
+      }
+    }
+  }
+
+  private static boolean isBlazePackage(PsiDirectory directory) {
+    return directory.findFile("BUILD") != null;
+  }
+
+  private static void processDirectory(Processor<PsiFile> processor, PsiDirectory directory) {
+    for (PsiFile file : directory.getFiles()) {
+      processor.process(file);
+    }
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (!(obj instanceof BlazePackage)) {
+      return false;
+    }
+    if (obj == this) {
+      return true;
+    }
+    BlazePackage that = (BlazePackage) obj;
+    return Objects.equals(buildFile.getFilePath(), that.buildFile.getFilePath());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(buildFile.getFilePath());
+  }
+
+  @Override
+  public String toString() {
+    return String.format(
+        "%s package: %s",
+        Blaze.buildSystemName(buildFile.getProject()), buildFile.getPackageWorkspacePath());
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/search/BlazePackageSearchScope.java b/base/src/com/google/idea/blaze/base/lang/buildfile/search/BlazePackageSearchScope.java
new file mode 100644
index 0000000..d5f6606
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/search/BlazePackageSearchScope.java
@@ -0,0 +1,104 @@
+/*
+ * 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.search;
+
+import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
+import com.google.idea.blaze.base.settings.Blaze;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiManager;
+import com.intellij.psi.search.GlobalSearchScope;
+import java.util.Objects;
+import org.jetbrains.annotations.NotNull;
+
+/** A scope limited to a single blaze/bazel package, which doesn't cross package boundaries. */
+public class BlazePackageSearchScope extends GlobalSearchScope {
+
+  private final BlazePackage blazePackage;
+  private final boolean onlyBlazeFiles;
+
+  public BlazePackageSearchScope(BlazePackage blazePackage, boolean onlyBlazeFiles) {
+    super(blazePackage.buildFile.getProject());
+    this.blazePackage = blazePackage;
+    this.onlyBlazeFiles = onlyBlazeFiles;
+  }
+
+  @Override
+  public boolean contains(@NotNull VirtualFile file) {
+    PsiFile psiFile = PsiManager.getInstance(getProject()).findFile(file);
+    if (onlyBlazeFiles && !(psiFile instanceof BuildFile)) {
+      return false;
+    }
+    return blazePackage.equals(BlazePackage.getContainingPackage(psiFile));
+  }
+
+  @Override
+  public int compare(@NotNull VirtualFile file1, @NotNull VirtualFile file2) {
+    return 0;
+  }
+
+  @Override
+  public boolean isSearchInModuleContent(@NotNull Module aModule) {
+    return true;
+  }
+
+  @Override
+  public boolean isSearchInLibraries() {
+    return false;
+  }
+
+  @Override
+  public String toString() {
+    return String.format(
+        "%s directory scope: %s",
+        Blaze.buildSystemName(getProject()), blazePackage.buildFile.getPackageWorkspacePath());
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (!(obj instanceof BlazePackageSearchScope)) {
+      return false;
+    }
+    if (obj == this) {
+      return true;
+    }
+    BlazePackageSearchScope other = (BlazePackageSearchScope) obj;
+    return blazePackage.equals(other.blazePackage) && onlyBlazeFiles == other.onlyBlazeFiles;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(blazePackage, onlyBlazeFiles);
+  }
+
+  @Override
+  public String getDisplayName() {
+    return blazePackage.toString();
+  }
+
+  @Override
+  public GlobalSearchScope uniteWith(@NotNull GlobalSearchScope scope) {
+    if (scope instanceof BlazePackageSearchScope) {
+      BlazePackageSearchScope other = (BlazePackageSearchScope) scope;
+      if (!blazePackage.equals(other.blazePackage)) {
+        return GlobalSearchScope.EMPTY_SCOPE;
+      }
+      return onlyBlazeFiles ? this : other;
+    }
+    return super.uniteWith(scope);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/search/BuildLabelReferenceSearcher.java b/base/src/com/google/idea/blaze/base/lang/buildfile/search/BuildLabelReferenceSearcher.java
new file mode 100644
index 0000000..688cc95
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/search/BuildLabelReferenceSearcher.java
@@ -0,0 +1,164 @@
+/*
+ * 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.search;
+
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.lang.buildfile.language.BuildFileType;
+import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
+import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile.BlazeFileType;
+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.references.LabelUtils;
+import com.google.idea.blaze.base.model.primitives.Label;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
+import com.intellij.openapi.application.QueryExecutorBase;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiReference;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.psi.search.LocalSearchScope;
+import com.intellij.psi.search.SearchScope;
+import com.intellij.psi.search.UsageSearchContext;
+import com.intellij.psi.search.searches.ReferencesSearch.SearchParameters;
+import com.intellij.util.Processor;
+import java.util.List;
+import javax.annotation.Nullable;
+
+/** String search for label references in BUILD files */
+public class BuildLabelReferenceSearcher extends QueryExecutorBase<PsiReference, SearchParameters> {
+
+  public BuildLabelReferenceSearcher() {
+    super(true);
+  }
+
+  @Override
+  public void processQuery(SearchParameters params, Processor<PsiReference> consumer) {
+    PsiElement element = params.getElementToSearch();
+    if (element instanceof FunctionStatement) {
+      String fnName = ((FunctionStatement) element).getName();
+      if (fnName != null) {
+        searchForString(params, element, fnName);
+      }
+      return;
+    }
+    PsiFile file = ResolveUtil.asFileSearch(element);
+    if (file != null) {
+      processFileReferences(params, file);
+      return;
+    }
+
+    if (!(element instanceof FuncallExpression)) {
+      return;
+    }
+
+    Label label = ((FuncallExpression) element).resolveBuildLabel();
+    PsiFile localFile = element.getContainingFile();
+    if (label == null || localFile == null) {
+      return;
+    }
+    List<String> stringsToSearch = LabelUtils.getAllValidLabelStrings(label, true);
+    for (String string : stringsToSearch) {
+      if (string.startsWith("//")) {
+        searchForString(params, element, string);
+      } else {
+        // only a valid reference from local package -- restrict the search scope accordingly
+        SearchScope scope = limitScopeToFile(params.getScopeDeterminedByUser(), localFile);
+        if (scope != null) {
+          searchForString(params, scope, element, string);
+        }
+      }
+    }
+  }
+
+  /** Find all references to the given file within BUILD files. */
+  private void processFileReferences(SearchParameters params, PsiFile file) {
+    if (file instanceof BuildFile) {
+      BuildFile buildFile = (BuildFile) file;
+      processBuildFileReferences(params, buildFile);
+      if (buildFile.getBlazeFileType() == BlazeFileType.BuildPackage) {
+        return;
+      }
+      // for skylark extensions, we also check for package-local references, below
+    }
+    BlazePackage blazePackage = BlazePackage.getContainingPackage(file);
+    PsiDirectory directory = blazePackage != null ? blazePackage.getContainingDirectory() : null;
+    if (directory == null) {
+      return;
+    }
+    Label label = LabelUtils.createLabelForFile(blazePackage, PsiUtils.getFilePath(file));
+    if (label == null) {
+      return;
+    }
+
+    // files can only be directly referenced in the containing blaze package
+    List<String> stringsToSearch = LabelUtils.getAllValidLabelStrings(label, true);
+    SearchScope scope =
+        params.getScopeDeterminedByUser().intersectWith(blazePackage.getSearchScope(true));
+
+    for (String string : stringsToSearch) {
+      searchForString(params, scope, file, string);
+    }
+  }
+
+  /** Find references to both the file itself, and build targets defined in the file. */
+  private void processBuildFileReferences(SearchParameters params, BuildFile file) {
+    WorkspacePath workspacePath = file.getPackageWorkspacePath();
+    if (workspacePath == null) {
+      return;
+    }
+    List<String> stringsToSearch = Lists.newArrayList();
+    if (file.getBlazeFileType() == BlazeFileType.BuildPackage) {
+      stringsToSearch.add("//" + workspacePath);
+    } else {
+      stringsToSearch.add("//" + workspacePath + ":" + file.getName());
+      stringsToSearch.add(
+          "//" + workspacePath + "/" + file.getName()); // deprecated load/subinclude format
+    }
+    for (String string : stringsToSearch) {
+      searchForString(params, file, string);
+    }
+  }
+
+  /**
+   * Search for package-local references.<br>
+   * Returns null if the resulting scope is empty
+   */
+  @Nullable
+  private static SearchScope limitScopeToFile(SearchScope scope, PsiFile file) {
+    if (scope instanceof LocalSearchScope) {
+      return ((LocalSearchScope) scope).isInScope(file.getVirtualFile())
+          ? new LocalSearchScope(file)
+          : null;
+    }
+    return scope.intersectWith(new LocalSearchScope(file));
+  }
+
+  private static void searchForString(SearchParameters params, PsiElement element, String string) {
+    searchForString(params, params.getScopeDeterminedByUser(), element, string);
+  }
+
+  private static void searchForString(
+      SearchParameters params, SearchScope scope, PsiElement element, String string) {
+    if (scope instanceof GlobalSearchScope) {
+      scope =
+          GlobalSearchScope.getScopeRestrictedByFileTypes(
+              (GlobalSearchScope) scope, BuildFileType.INSTANCE);
+    }
+    params.getOptimizer().searchWord(string, scope, UsageSearchContext.IN_STRINGS, true, element);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/search/ExcludeBuildFilesScope.java b/base/src/com/google/idea/blaze/base/lang/buildfile/search/ExcludeBuildFilesScope.java
new file mode 100644
index 0000000..3fa294b
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/search/ExcludeBuildFilesScope.java
@@ -0,0 +1,48 @@
+/*
+ * 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.search;
+
+import com.google.idea.blaze.base.lang.buildfile.language.BuildFileType;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFileSystemItem;
+import com.intellij.psi.search.EverythingGlobalScope;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.psi.search.UseScopeOptimizer;
+import javax.annotation.Nullable;
+
+/**
+ * Causes all calls to PsiSearchHelper.getUseScope to exclude BUILD files, when searching for files.
+ * <br>
+ * BUILD file / BUILD package references are handled by a separate reference searcher.
+ *
+ * <p>This is a hack, but greatly improves efficiency. The reasoning behind this: - BUILD files have
+ * very strict file reference patterns, and very narrow direct reference scopes (a package can't
+ * directly reference files in another package). - IJ *constantly* performs global searches on
+ * strings when manipulating files (e.g. searching for file uses for highlighting, rename, move
+ * operations). This causes us to re-parse every BUILD file in the project, multiple times.
+ */
+public class ExcludeBuildFilesScope extends UseScopeOptimizer {
+
+  @Nullable
+  @Override
+  public GlobalSearchScope getScopeToExclude(PsiElement element) {
+    if (element instanceof PsiFileSystemItem) {
+      return GlobalSearchScope.getScopeRestrictedByFileTypes(
+          new EverythingGlobalScope(), BuildFileType.INSTANCE);
+    }
+    return null;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/search/FindUsages.java b/base/src/com/google/idea/blaze/base/lang/buildfile/search/FindUsages.java
new file mode 100644
index 0000000..4a05e5c
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/search/FindUsages.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.base.lang.buildfile.search;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiReference;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.psi.search.PsiSearchHelper;
+import com.intellij.psi.search.SearchScope;
+import com.intellij.psi.search.searches.ReferencesSearch;
+
+/** Utility methods for finding all references to a PsiElement */
+public class FindUsages {
+
+  public static PsiReference[] findAllReferences(PsiElement element) {
+    return findReferencesInScope(element, GlobalSearchScope.allScope(element.getProject()));
+  }
+
+  /**
+   * Search scope taken from PsiSearchHelper::getUseScope, which incorporates UseScopeEnlarger /
+   * UseScopeOptimizer EPs.
+   */
+  public static PsiReference[] findReferencesInElementScope(PsiElement element) {
+    return findReferencesInScope(
+        element, PsiSearchHelper.SERVICE.getInstance(element.getProject()).getUseScope(element));
+  }
+
+  public static PsiReference[] findReferencesInScope(PsiElement element, SearchScope scope) {
+    return ReferencesSearch.search(element, scope, true).toArray(new PsiReference[0]);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/search/GlobReferenceSearcher.java b/base/src/com/google/idea/blaze/base/lang/buildfile/search/GlobReferenceSearcher.java
new file mode 100644
index 0000000..ac77523
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/search/GlobReferenceSearcher.java
@@ -0,0 +1,93 @@
+/*
+ * 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.search;
+
+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.intellij.openapi.application.QueryExecutorBase;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFileSystemItem;
+import com.intellij.psi.PsiReference;
+import com.intellij.psi.PsiReferenceBase;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.psi.search.LocalSearchScope;
+import com.intellij.psi.search.SearchScope;
+import com.intellij.psi.search.searches.ReferencesSearch.SearchParameters;
+import com.intellij.util.IncorrectOperationException;
+import com.intellij.util.Processor;
+import java.util.List;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Searches for references to a file in globs. These aren't picked up by a standard string search,
+ * and are only evaluated on demand, so we can't just check a reference cache.
+ *
+ * <p>Unlike resolving a glob, this requires no file system calls (beyond finding the parent blaze
+ * package), because we're only interested in a single file, which is already known to exist.
+ *
+ * <p>This is always a local search (as glob references can't cross package boundaries).
+ */
+public class GlobReferenceSearcher extends QueryExecutorBase<PsiReference, SearchParameters> {
+
+  public GlobReferenceSearcher() {
+    super(true);
+  }
+
+  @Override
+  public void processQuery(SearchParameters queryParameters, Processor<PsiReference> consumer) {
+    PsiFileSystemItem file =
+        ResolveUtil.asFileSystemItemSearch(queryParameters.getElementToSearch());
+    if (file == null) {
+      return;
+    }
+    BlazePackage containingPackage = BlazePackage.getContainingPackage(file);
+    if (containingPackage == null || !inScope(queryParameters, containingPackage.buildFile)) {
+      return;
+    }
+    String relativePath = containingPackage.getRelativePathToChild(file.getVirtualFile());
+    if (relativePath == null) {
+      return;
+    }
+
+    List<GlobExpression> globs =
+        PsiUtils.findAllChildrenOfClassRecursive(containingPackage.buildFile, GlobExpression.class);
+    for (GlobExpression glob : globs) {
+      if (glob.matches(relativePath, file.isDirectory())) {
+        consumer.process(globReference(glob, file));
+      }
+    }
+  }
+
+  private static PsiReference globReference(GlobExpression glob, PsiFileSystemItem file) {
+    return new PsiReferenceBase.Immediate<GlobExpression>(
+        glob, glob.getReferenceTextRange(), file) {
+      @Override
+      public PsiElement bindToElement(@NotNull PsiElement element)
+          throws IncorrectOperationException {
+        return glob;
+      }
+    };
+  }
+
+  private static boolean inScope(SearchParameters queryParameters, BuildFile buildFile) {
+    SearchScope scope = queryParameters.getScopeDeterminedByUser();
+    if (scope instanceof GlobalSearchScope) {
+      return ((GlobalSearchScope) scope).contains(buildFile.getVirtualFile());
+    }
+    return ((LocalSearchScope) scope).isInScope(buildFile.getVirtualFile());
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/search/PsiFileProvider.java b/base/src/com/google/idea/blaze/base/lang/buildfile/search/PsiFileProvider.java
new file mode 100644
index 0000000..164046e
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/search/PsiFileProvider.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.lang.buildfile.search;
+
+import com.intellij.openapi.extensions.ExtensionPointName;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import javax.annotation.Nullable;
+
+/**
+ * Checks if the PsiElement represents a top-level PsiFile (e.g. a top-level java PsiClass can be
+ * interchanged with the corresponding PsiFile, when searching for usages).
+ */
+public interface PsiFileProvider {
+
+  ExtensionPointName<PsiFileProvider> EP_NAME =
+      ExtensionPointName.create("com.google.idea.blaze.PsiFileProvider");
+
+  @Nullable
+  PsiFile asFileSearch(PsiElement elementToSearch);
+}
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
new file mode 100644
index 0000000..e7854c5
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/search/ResolveUtil.java
@@ -0,0 +1,168 @@
+/*
+ * 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.search;
+
+import com.google.idea.blaze.base.lang.buildfile.psi.AssignmentStatement;
+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.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.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;
+import com.intellij.psi.PsiFileSystemItem;
+import com.intellij.psi.PsiNamedElement;
+import com.intellij.util.Processor;
+import javax.annotation.Nullable;
+
+/** Utilities methods for resolving references */
+public class ResolveUtil {
+
+  /** Walks up PSI tree of local file, checking PsiNamedElements */
+  public static void searchInScope(PsiElement originalElement, Processor<BuildElement> processor) {
+    // TODO: Handle list comprehension (where variable is defined *later* in the code)
+    boolean topLevelScope = true;
+    PsiElement element = originalElement;
+    while (!(element instanceof PsiFileSystemItem)) {
+      PsiElement parent = element.getParent();
+      if (parent instanceof BuildFile) {
+        if (!((BuildFile) parent).searchSymbolsInScope(processor, topLevelScope ? element : null)) {
+          return;
+        }
+      } else if (parent instanceof FunctionStatement) {
+        topLevelScope = false;
+        for (Parameter param : ((FunctionStatement) parent).getParameters()) {
+          if (!processor.process(param)) {
+            return;
+          }
+        }
+      } else if (parent instanceof ForStatement) {
+        for (Expression expr : ((ForStatement) parent).getForLoopVariables()) {
+          if (expr instanceof TargetExpression && !processor.process(expr)) {
+            return;
+          }
+        }
+      } else if (parent instanceof StatementList) {
+        if (!visitChildAssignmentStatements((BuildElement) parent, (Processor) processor)) {
+          return;
+        }
+      }
+      element = parent;
+    }
+  }
+
+  /** Walks up PSI tree of local file, checking PsiNamedElements */
+  @Nullable
+  public static PsiNamedElement findInScope(PsiElement element, String name) {
+    PsiNamedElement[] resultHolder = new PsiNamedElement[1];
+    Processor<BuildElement> processor =
+        buildElement -> {
+          if (buildElement == element) {
+            return true;
+          }
+          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;
+                return false;
+              }
+            }
+          }
+          return true;
+        };
+    searchInScope(element, processor);
+    return resultHolder[0];
+  }
+
+  /** @return false if processing was stopped */
+  public static boolean visitChildAssignmentStatements(
+      BuildElement parent, Processor<TargetExpression> processor) {
+    for (AssignmentStatement stmt : parent.childrenOfClass(AssignmentStatement.class)) {
+      TargetExpression target = stmt.getLeftHandSideExpression();
+      if (target != null && !processor.process(target)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  @Nullable
+  public static TargetExpression searchChildAssignmentStatements(BuildElement parent, String name) {
+    TargetExpression[] resultHolder = new TargetExpression[1];
+    visitChildAssignmentStatements(
+        parent,
+        targetExpr -> {
+          if (name.equals(targetExpr.getName())) {
+            resultHolder[0] = targetExpr;
+            return false;
+          }
+          return true;
+        });
+    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.
+   */
+  @Nullable
+  public static PsiFileSystemItem asFileSystemItemSearch(PsiElement elementToSearch) {
+    if (elementToSearch instanceof PsiFileSystemItem) {
+      return (PsiFileSystemItem) elementToSearch;
+    }
+    return asFileSearch(elementToSearch);
+  }
+
+  /**
+   * Checks if the element we're searching for is represented by a file.<br>
+   * e.g. a java class PSI element, or an actual PsiFile element.
+   */
+  @Nullable
+  public static PsiFile asFileSearch(PsiElement elementToSearch) {
+    if (elementToSearch instanceof PsiFile) {
+      return (PsiFile) elementToSearch;
+    }
+    for (PsiFileProvider provider : PsiFileProvider.EP_NAME.getExtensions()) {
+      PsiFile file = provider.asFileSearch(elementToSearch);
+      if (file != null) {
+        return file;
+      }
+    }
+    return null;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/stubs/BuildFileStubBuilder.java b/base/src/com/google/idea/blaze/base/lang/buildfile/stubs/BuildFileStubBuilder.java
new file mode 100644
index 0000000..73d1e58
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/stubs/BuildFileStubBuilder.java
@@ -0,0 +1,43 @@
+/*
+ * 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.stubs;
+
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.stubs.BinaryFileStubBuilder;
+import com.intellij.psi.stubs.Stub;
+import com.intellij.util.indexing.FileContent;
+import org.jetbrains.annotations.Nullable;
+
+/** Empty stub builder to suppress errors when IntelliJ is looking for stubs. */
+public class BuildFileStubBuilder implements BinaryFileStubBuilder {
+  private static final int STUB_VERSION = 0;
+
+  @Override
+  public boolean acceptsFile(VirtualFile file) {
+    return false;
+  }
+
+  @Nullable
+  @Override
+  public Stub buildStubTree(FileContent fileContent) {
+    return null;
+  }
+
+  @Override
+  public int getStubVersion() {
+    return STUB_VERSION;
+  }
+}
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
new file mode 100644
index 0000000..5bd69c7
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/sync/BuildLangSyncPlugin.java
@@ -0,0 +1,124 @@
+/*
+ * 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.sync;
+
+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.lang.buildfile.language.semantics.BuildLanguageSpec;
+import com.google.idea.blaze.base.model.RuleMap;
+import com.google.idea.blaze.base.model.SyncState;
+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.Scope;
+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.projectview.WorkspaceLanguageSettings;
+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.repackaged.devtools.build.lib.query2.proto.proto2api.Build;
+import com.google.repackaged.protobuf.InvalidProtocolBufferException;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import java.util.concurrent.ExecutionException;
+import javax.annotation.Nullable;
+
+/** Updates the language specification during the blaze sync process */
+public class BuildLangSyncPlugin extends BlazeSyncPlugin.Adapter {
+
+  private static final Logger LOG = Logger.getInstance(BuildLangSyncPlugin.class);
+
+  @Override
+  public void updateSyncState(
+      Project project,
+      BlazeContext context,
+      WorkspaceRoot workspaceRoot,
+      ProjectViewSet projectViewSet,
+      WorkspaceLanguageSettings workspaceLanguageSettings,
+      BlazeRoots blazeRoots,
+      @Nullable WorkingSet workingSet,
+      WorkspacePathResolver workspacePathResolver,
+      RuleMap ruleMap,
+      SyncState.Builder syncStateBuilder,
+      @Nullable SyncState previousSyncState) {
+
+    LanguageSpecResult spec =
+        getBuildLanguageSpec(project, workspaceRoot, previousSyncState, context);
+    if (spec != null) {
+      syncStateBuilder.put(LanguageSpecResult.class, spec);
+    }
+  }
+
+  @Nullable
+  private static LanguageSpecResult getBuildLanguageSpec(
+      Project project,
+      WorkspaceRoot workspace,
+      @Nullable SyncState previousSyncState,
+      BlazeContext parentContext) {
+    LanguageSpecResult oldResult =
+        previousSyncState != null ? previousSyncState.get(LanguageSpecResult.class) : null;
+    if (oldResult != null && !oldResult.shouldRecalculateSpec()) {
+      return oldResult;
+    }
+    LanguageSpecResult result =
+        Scope.push(
+            parentContext,
+            (context) -> {
+              context.push(new TimingScope("BUILD language spec"));
+              BuildLanguageSpec spec = parseLanguageSpec(project, workspace, context);
+              if (spec != null) {
+                return new LanguageSpecResult(spec, System.currentTimeMillis());
+              }
+              return null;
+            });
+    return result != null ? result : oldResult;
+  }
+
+  @Nullable
+  private static BuildLanguageSpec parseLanguageSpec(
+      Project project, WorkspaceRoot workspace, BlazeContext context) {
+    try {
+      // it's wasteful converting to a string and back, but uses existing code,
+      // and has a very minor cost (this is only run once per workspace)
+      ListenableFuture<byte[]> future =
+          BlazeInfo.getInstance()
+              .runBlazeInfoGetBytes(
+                  context,
+                  Blaze.getBuildSystem(project),
+                  workspace,
+                  ImmutableList.of(),
+                  BlazeInfo.BUILD_LANGUAGE);
+
+      return BuildLanguageSpec.fromProto(Build.BuildLanguage.parseFrom(future.get()));
+
+    } catch (InterruptedException e) {
+      Thread.currentThread().interrupt();
+      return null;
+    } catch (ExecutionException | InvalidProtocolBufferException | NullPointerException e) {
+      if (!ApplicationManager.getApplication().isUnitTestMode()) {
+        LOG.error(e);
+      }
+      return null;
+    } catch (Throwable e) {
+      LOG.error(e);
+      return null;
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/sync/LanguageSpecResult.java b/base/src/com/google/idea/blaze/base/lang/buildfile/sync/LanguageSpecResult.java
new file mode 100644
index 0000000..49da1b6
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/sync/LanguageSpecResult.java
@@ -0,0 +1,37 @@
+/*
+ * 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.sync;
+
+import com.google.idea.blaze.base.lang.buildfile.language.semantics.BuildLanguageSpec;
+import java.io.Serializable;
+
+/** The BUILD language specifications, serialized along with the sync data. */
+public class LanguageSpecResult implements Serializable {
+
+  private static final long ONE_DAY_IN_MILLISECONDS = 1000 * 60 * 60 * 24;
+
+  public final BuildLanguageSpec spec;
+  public final long timestampMillis;
+
+  public LanguageSpecResult(BuildLanguageSpec spec, long timestampMillis) {
+    this.spec = spec;
+    this.timestampMillis = timestampMillis;
+  }
+
+  public boolean shouldRecalculateSpec() {
+    return System.currentTimeMillis() - timestampMillis > ONE_DAY_IN_MILLISECONDS;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/validation/BuildAnnotator.java b/base/src/com/google/idea/blaze/base/lang/buildfile/validation/BuildAnnotator.java
new file mode 100644
index 0000000..84028f8
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/validation/BuildAnnotator.java
@@ -0,0 +1,45 @@
+/*
+ * 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.validation;
+
+import com.google.idea.blaze.base.lang.buildfile.psi.BuildElementVisitor;
+import com.intellij.lang.annotation.AnnotationHolder;
+import com.intellij.lang.annotation.Annotator;
+import com.intellij.psi.PsiElement;
+
+/** Base class for Annotator implementations using type-specific methods in BuildElementVisitor */
+public abstract class BuildAnnotator extends BuildElementVisitor implements Annotator {
+
+  private volatile AnnotationHolder holder;
+
+  protected AnnotationHolder getHolder() {
+    return holder;
+  }
+
+  @Override
+  public synchronized void annotate(PsiElement element, AnnotationHolder holder) {
+    this.holder = holder;
+    try {
+      element.accept(this);
+    } finally {
+      this.holder = null;
+    }
+  }
+
+  protected void markError(PsiElement element, String message) {
+    getHolder().createErrorAnnotation(element, message);
+  }
+}
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
new file mode 100644
index 0000000..58e7476
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/validation/ErrorAnnotator.java
@@ -0,0 +1,92 @@
+/*
+ * 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.validation;
+
+import com.google.idea.blaze.base.lang.buildfile.psi.Argument;
+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.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;
+
+/**
+ * Additional error annotations, post parsing.
+ *
+ * <p>This has been turned off because it's unusable. BuildFile is re-parsed *every* time it's
+ * touched, and is never cached. Until this is fixed, we can't run any annotators touching the file.
+ *
+ * <p>One option: try moving all expensive checks to 'visitFile', so they're not run in parallel
+ */
+public class ErrorAnnotator extends BuildAnnotator {
+
+  @Override
+  public void visitLoadStatement(LoadStatement node) {
+    StringLiteral[] strings = node.getChildStrings();
+    if (strings.length == 0) {
+      return;
+    }
+    PsiElement skylarkRef = new LabelReference(strings[0], false).resolve();
+    if (skylarkRef == null) {
+      markError(strings[0], "Cannot find this Skylark module");
+      return;
+    }
+    if (!(skylarkRef instanceof BuildFile)) {
+      markError(strings[0], strings[0].getText() + " is not a Skylark module");
+      return;
+    }
+    if (strings.length == 1) {
+      markError(node, "No definitions imported from Skylark module");
+      return;
+    }
+    BuildFile skylarkModule = (BuildFile) skylarkRef;
+    for (int i = 1; i < strings.length; i++) {
+      String text = strings[i].getStringContents();
+      FunctionStatement fn = skylarkModule.findDeclaredFunction(text);
+      if (fn == null) {
+        markError(
+            strings[i],
+            "Function '" + text + "' not found in Skylark module " + skylarkModule.getFileName());
+      }
+    }
+  }
+
+  @Override
+  public void visitFuncallExpression(FuncallExpression node) {
+    FunctionStatement function = (FunctionStatement) node.getReferencedElement();
+    if (function == null) {
+      // likely a built-in rule. We don't yet recognize these.
+      return;
+    }
+    // check keyword args match function parameters
+    ParameterList params = function.getParameterList();
+    if (params == null || params.hasStarStar()) {
+      return;
+    }
+    for (Argument arg : node.getArguments()) {
+      if (arg instanceof Argument.Keyword) {
+        String name = arg.getName();
+        if (name != null && params.findParameterByName(name) == null) {
+          markError(
+              arg,
+              "No parameter found in '" + node.getFunctionName() + "' with name '" + name + "'");
+        }
+      }
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/validation/GlobErrorAnnotator.java b/base/src/com/google/idea/blaze/base/lang/buildfile/validation/GlobErrorAnnotator.java
new file mode 100644
index 0000000..9cf5e7b
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/validation/GlobErrorAnnotator.java
@@ -0,0 +1,144 @@
+/*
+ * 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.validation;
+
+import com.google.idea.blaze.base.lang.buildfile.psi.Argument;
+import com.google.idea.blaze.base.lang.buildfile.psi.Expression;
+import com.google.idea.blaze.base.lang.buildfile.psi.FunctionStatement;
+import com.google.idea.blaze.base.lang.buildfile.psi.GlobExpression;
+import com.google.idea.blaze.base.lang.buildfile.psi.ListLiteral;
+import com.google.idea.blaze.base.lang.buildfile.psi.LiteralExpression;
+import com.google.idea.blaze.base.lang.buildfile.psi.LoadStatement;
+import com.google.idea.blaze.base.lang.buildfile.psi.ReferenceExpression;
+import com.google.idea.blaze.base.lang.buildfile.psi.StringLiteral;
+import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
+import com.intellij.psi.PsiElement;
+import javax.annotation.Nullable;
+
+/** Checks that glob expressions are valid */
+public class GlobErrorAnnotator extends BuildAnnotator {
+
+  @Override
+  public void visitGlobExpression(GlobExpression node) {
+    Argument[] args = node.getArguments();
+    boolean hasIncludes = false;
+    for (int i = 0; i < args.length; i++) {
+      Argument arg = args[i];
+      String name = arg instanceof Argument.Keyword ? arg.getName() : null;
+      if ("include".equals(name) || (arg instanceof Argument.Positional && i == 0)) {
+        hasIncludes = checkIncludes(arg.getValue());
+      } else if ("exclude".equals(name)) {
+        checkListContents("exclude", arg.getValue());
+      } else if ("exclude_directories".equals(name)) {
+        checkExcludeDirsNode(arg);
+      } else {
+        markError(arg, "Unrecognized glob argument");
+      }
+    }
+    if (!hasIncludes) {
+      markError(node, "Glob expression must contain at least one included string");
+    }
+  }
+
+  private void checkExcludeDirsNode(Argument arg) {
+    Expression value = arg.getValue();
+    if (value == null || !(value.getText().equals("0") || value.getText().equals("1"))) {
+      markError(arg, "exclude_directories parameter to glob must be 0 or 1");
+    }
+  }
+
+  /** @return true if glob contains at least one included string */
+  private boolean checkIncludes(@Nullable Expression expr) {
+    return checkListContents("include", expr);
+  }
+
+  /**
+   * @return false if 'expr' is known with certainty not to be a list containing at least one string
+   */
+  private boolean checkListContents(String keyword, @Nullable Expression expr) {
+    if (expr == null) {
+      return false;
+    }
+    PsiElement rootElement = PsiUtils.getReferencedTargetValue(expr);
+    if (rootElement instanceof ListLiteral) {
+      return validatePatternList(keyword, ((ListLiteral) rootElement).getChildExpressions());
+    }
+    if (rootElement instanceof ReferenceExpression || !possiblyValidListLiteral(rootElement)) {
+      markError(expr, "Glob parameter '" + keyword + "' must be a list of strings");
+      return false;
+    }
+    // might possibly be a list, default to not showing any errors
+    return true;
+  }
+
+  /** @return false if 'expr' is known with certainty not to contain at least one string */
+  private boolean validatePatternList(String keyword, Expression[] expressions) {
+    boolean possiblyHasString = false;
+    for (Expression expr : expressions) {
+      PsiElement rootElement = PsiUtils.getReferencedTargetValue(expr);
+      if (rootElement instanceof ReferenceExpression || !possiblyValidStringLiteral(rootElement)) {
+        markError(expr, "Glob parameter '" + keyword + "' must be a list of strings");
+      } else {
+        possiblyHasString = true;
+        if (rootElement instanceof StringLiteral) {
+          validatePattern((StringLiteral) rootElement);
+        }
+      }
+    }
+    return possiblyHasString;
+  }
+
+  private void validatePattern(StringLiteral pattern) {
+    String error = GlobPatternValidator.validate(pattern.getStringContents());
+    if (error != null) {
+      markError(pattern, error);
+    }
+  }
+
+  /** Returns false iff we know with certainty that the element cannot resolve to a list literal. */
+  private static boolean possiblyValidListLiteral(PsiElement element) {
+    if (element instanceof ListLiteral || element instanceof GlobExpression) {
+      return true; // these evaluate directly to list literals
+    }
+    if (element instanceof LiteralExpression) {
+      return false; // all other literals cannot evaluate to a ListLiteral
+    }
+    if (element instanceof LoadStatement || element instanceof FunctionStatement) {
+      return false;
+    }
+    // everything else treated as possibly evaluating to a list
+    return true;
+  }
+
+  /**
+   * Returns false iff we know with certainty that the element cannot resolve to a string literal.
+   */
+  private static boolean possiblyValidStringLiteral(PsiElement element) {
+    if (element instanceof StringLiteral) {
+      return true;
+    }
+    if (element instanceof LiteralExpression) {
+      return false; // all other literals cannot evaluate to a StringLiteral
+    }
+    if (element instanceof LoadStatement
+        || element instanceof FunctionStatement
+        || element instanceof GlobExpression) {
+      return false;
+    }
+    // everything else treated as possibly evaluating to a string
+    return true;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/validation/GlobPatternValidator.java b/base/src/com/google/idea/blaze/base/lang/buildfile/validation/GlobPatternValidator.java
new file mode 100644
index 0000000..c82d736
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/validation/GlobPatternValidator.java
@@ -0,0 +1,77 @@
+/*
+ * 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.validation;
+
+import com.google.common.base.Splitter;
+import javax.annotation.Nullable;
+
+/**
+ * Support for resolving globs.
+ *
+ * <p>
+ */
+public class GlobPatternValidator {
+
+  /**
+   * Validate a single glob pattern. If it's invalid, returns an error message. Otherwise, returns
+   * null.
+   *
+   * <p>
+   */
+  @Nullable
+  public static String validate(String pattern) {
+    String error = checkPatternForError(pattern);
+    if (error != null) {
+      return "Invalid glob pattern: " + error;
+    }
+    return null;
+  }
+
+  @Nullable
+  private static String checkPatternForError(String pattern) {
+    if (pattern.isEmpty()) {
+      return "pattern cannot be empty";
+    }
+    if (pattern.charAt(0) == '/') {
+      return "pattern cannot be absolute";
+    }
+    for (int i = 0; i < pattern.length(); i++) {
+      char c = pattern.charAt(i);
+      switch (c) {
+        case '(':
+        case ')':
+        case '{':
+        case '}':
+        case '[':
+        case ']':
+          return "illegal character '" + c + "'";
+      }
+    }
+    Iterable<String> segments = Splitter.on('/').split(pattern);
+    for (String segment : segments) {
+      if (segment.isEmpty()) {
+        return "empty segment not permitted";
+      }
+      if (segment.equals(".") || segment.equals("..")) {
+        return "segment '" + segment + "' not permitted";
+      }
+      if (segment.contains("**") && !segment.equals("**")) {
+        return "recursive wildcard must be its own segment";
+      }
+    }
+    return null;
+  }
+}
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
new file mode 100644
index 0000000..c2a3f40
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/validation/HighlightingAnnotator.java
@@ -0,0 +1,60 @@
+/*
+ * 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.validation;
+
+import com.google.idea.blaze.base.lang.buildfile.highlighting.BuildSyntaxHighlighter;
+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.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
+ */
+public class HighlightingAnnotator extends BuildAnnotator {
+
+  @Override
+  public void visitParameter(Parameter node) {
+    FunctionStatement function = PsiTreeUtil.getParentOfType(node, FunctionStatement.class);
+    if (function != null) {
+      PsiElement anchor = node.hasDefaultValue() ? node.getFirstChild() : node;
+      final Annotation annotation = getHolder().createInfoAnnotation(anchor, null);
+      annotation.setTextAttributes(BuildSyntaxHighlighter.BUILD_PARAMETER);
+    }
+  }
+
+  @Override
+  public void visitKeywordArgument(Argument.Keyword node) {
+    ASTNode keywordNode = node.getNameNode();
+    if (keywordNode != null) {
+      Annotation annotation = getHolder().createInfoAnnotation(keywordNode, null);
+      annotation.setTextAttributes(BuildSyntaxHighlighter.BUILD_KEYWORD_ARG);
+    }
+  }
+
+  @Override
+  public void visitFunctionStatement(FunctionStatement node) {
+    ASTNode nameNode = node.getNameNode();
+    if (nameNode != null) {
+      Annotation annotation = getHolder().createInfoAnnotation(nameNode, null);
+      annotation.setTextAttributes(BuildSyntaxHighlighter.BUILD_FN_DEFINITION);
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/views/BuildStructureViewElement.java b/base/src/com/google/idea/blaze/base/lang/buildfile/views/BuildStructureViewElement.java
new file mode 100644
index 0000000..1f71b8c
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/views/BuildStructureViewElement.java
@@ -0,0 +1,59 @@
+/*
+ * 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.views;
+
+import com.google.common.collect.ImmutableList;
+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.ListLiteral;
+import com.intellij.ide.structureView.StructureViewTreeElement;
+import com.intellij.ide.structureView.impl.common.PsiTreeElementBase;
+import java.util.Collection;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/** Handles nodes in Structure View. */
+public class BuildStructureViewElement extends PsiTreeElementBase<BuildElement> {
+
+  private final BuildElement element;
+
+  public BuildStructureViewElement(BuildElement element) {
+    super(element);
+    this.element = element;
+  }
+
+  @NotNull
+  @Override
+  public Collection<StructureViewTreeElement> getChildrenBase() {
+    if (element instanceof ListLiteral) {}
+
+    if (!(element instanceof BuildFile)) {
+      // TODO: show inner build rules in Skylark .bzl extensions
+      return ImmutableList.of();
+    }
+    ImmutableList.Builder<StructureViewTreeElement> builder = ImmutableList.builder();
+    for (BuildElement child : ((BuildFile) element).findChildrenByClass(BuildElement.class)) {
+      builder.add(new BuildStructureViewElement(child));
+    }
+    return builder.build();
+  }
+
+  @Nullable
+  @Override
+  public String getPresentableText() {
+    return element.getPresentableText();
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/views/BuildStructureViewFactory.java b/base/src/com/google/idea/blaze/base/lang/buildfile/views/BuildStructureViewFactory.java
new file mode 100644
index 0000000..0d66994
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/views/BuildStructureViewFactory.java
@@ -0,0 +1,42 @@
+/*
+ * 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.views;
+
+import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
+import com.intellij.ide.structureView.StructureViewBuilder;
+import com.intellij.ide.structureView.StructureViewModel;
+import com.intellij.ide.structureView.TreeBasedStructureViewBuilder;
+import com.intellij.lang.PsiStructureViewFactory;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.psi.PsiFile;
+import javax.annotation.Nullable;
+
+/** PsiStructureViewFactory implementation */
+public class BuildStructureViewFactory implements PsiStructureViewFactory {
+  @Override
+  @Nullable
+  public StructureViewBuilder getStructureViewBuilder(final PsiFile psiFile) {
+    if (!(psiFile instanceof BuildFile)) {
+      return null;
+    }
+    return new TreeBasedStructureViewBuilder() {
+      @Override
+      public StructureViewModel createStructureViewModel(@Nullable Editor editor) {
+        return new BuildStructureViewModel((BuildFile) psiFile, editor);
+      }
+    };
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/views/BuildStructureViewModel.java b/base/src/com/google/idea/blaze/base/lang/buildfile/views/BuildStructureViewModel.java
new file mode 100644
index 0000000..b0f53f6
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/views/BuildStructureViewModel.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.views;
+
+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.TargetExpression;
+import com.intellij.ide.structureView.StructureViewModel;
+import com.intellij.ide.structureView.StructureViewModelBase;
+import com.intellij.ide.structureView.StructureViewTreeElement;
+import com.intellij.ide.util.treeView.smartTree.Sorter;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.psi.PsiFile;
+import javax.annotation.Nullable;
+
+/**
+ * Implements structure view for a BUILD file. TODO: Include inner build rules for Skylark files
+ * (when we can identify them -- e.g. via list of blaze rule types)
+ */
+public class BuildStructureViewModel extends StructureViewModelBase
+    implements StructureViewModel.ElementInfoProvider, StructureViewModel.ExpandInfoProvider {
+
+  public BuildStructureViewModel(BuildFile psiFile, @Nullable Editor editor) {
+    this(psiFile, editor, new BuildStructureViewElement(psiFile));
+    withSorters(Sorter.ALPHA_SORTER);
+    withSuitableClasses(FunctionStatement.class, LoadStatement.class, FuncallExpression.class);
+  }
+
+  public BuildStructureViewModel(
+      PsiFile file, @Nullable Editor editor, StructureViewTreeElement element) {
+    super(file, editor, element);
+  }
+
+  @Override
+  public boolean isAlwaysShowsPlus(StructureViewTreeElement element) {
+    final Object value = element.getValue();
+    return value instanceof BuildFile;
+  }
+
+  @Override
+  public boolean isAlwaysLeaf(StructureViewTreeElement element) {
+    return element.getValue() instanceof TargetExpression;
+  }
+
+  @Override
+  public boolean shouldEnterElement(Object element) {
+    return element instanceof BuildFile; // only show top-level elements
+  }
+
+  @Override
+  public boolean isAutoExpand(StructureViewTreeElement element) {
+    return element.getValue() instanceof PsiFile;
+  }
+
+  @Override
+  public boolean isSmartExpand() {
+    return false;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/projectview/completion/AdditionalLanguagesCompletionContributor.java b/base/src/com/google/idea/blaze/base/lang/projectview/completion/AdditionalLanguagesCompletionContributor.java
new file mode 100644
index 0000000..4cf2f36
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/projectview/completion/AdditionalLanguagesCompletionContributor.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.projectview.completion;
+
+import static com.intellij.patterns.PlatformPatterns.psiElement;
+
+import com.google.idea.blaze.base.lang.projectview.language.ProjectViewLanguage;
+import com.google.idea.blaze.base.lang.projectview.psi.ProjectViewPsiListSection;
+import com.google.idea.blaze.base.model.primitives.LanguageClass;
+import com.google.idea.blaze.base.projectview.section.sections.AdditionalLanguagesSection;
+import com.intellij.codeInsight.completion.AutoCompletionContext;
+import com.intellij.codeInsight.completion.AutoCompletionDecision;
+import com.intellij.codeInsight.completion.CompletionContributor;
+import com.intellij.codeInsight.completion.CompletionParameters;
+import com.intellij.codeInsight.completion.CompletionProvider;
+import com.intellij.codeInsight.completion.CompletionResultSet;
+import com.intellij.codeInsight.completion.CompletionType;
+import com.intellij.codeInsight.lookup.LookupElement;
+import com.intellij.codeInsight.lookup.LookupElementBuilder;
+import com.intellij.patterns.StandardPatterns;
+import com.intellij.util.ProcessingContext;
+
+/** Code completion for additional language types. */
+public class AdditionalLanguagesCompletionContributor extends CompletionContributor {
+
+  @Override
+  public AutoCompletionDecision handleAutoCompletionPossibility(AutoCompletionContext context) {
+    // auto-insert the obvious only case; else show other cases.
+    final LookupElement[] items = context.getItems();
+    if (items.length == 1) {
+      return AutoCompletionDecision.insertItem(items[0]);
+    }
+    return AutoCompletionDecision.SHOW_LOOKUP;
+  }
+
+  public AdditionalLanguagesCompletionContributor() {
+    extend(
+        CompletionType.BASIC,
+        psiElement()
+            .withLanguage(ProjectViewLanguage.INSTANCE)
+            .inside(
+                psiElement(ProjectViewPsiListSection.class)
+                    .withText(
+                        StandardPatterns.string()
+                            .startsWith(AdditionalLanguagesSection.KEY.getName()))),
+        new CompletionProvider<CompletionParameters>() {
+          @Override
+          protected void addCompletions(
+              CompletionParameters parameters,
+              ProcessingContext context,
+              CompletionResultSet result) {
+            for (LanguageClass type : LanguageClass.values()) {
+              result.addElement(LookupElementBuilder.create(type.getName()));
+            }
+          }
+        });
+  }
+}
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
new file mode 100644
index 0000000..f1bf85a
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/projectview/completion/ProjectViewKeywordCompletionContributor.java
@@ -0,0 +1,124 @@
+/*
+ * 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.completion;
+
+import static com.intellij.patterns.PlatformPatterns.psiElement;
+
+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;
+import com.google.idea.blaze.base.projectview.section.ListSectionParser;
+import com.google.idea.blaze.base.projectview.section.ScalarSectionParser;
+import com.google.idea.blaze.base.projectview.section.SectionParser;
+import com.google.idea.blaze.base.projectview.section.sections.Sections;
+import com.intellij.codeInsight.completion.AutoCompletionContext;
+import com.intellij.codeInsight.completion.AutoCompletionDecision;
+import com.intellij.codeInsight.completion.CompletionContributor;
+import com.intellij.codeInsight.completion.CompletionParameters;
+import com.intellij.codeInsight.completion.CompletionProvider;
+import com.intellij.codeInsight.completion.CompletionResultSet;
+import com.intellij.codeInsight.completion.CompletionType;
+import com.intellij.codeInsight.completion.InsertHandler;
+import com.intellij.codeInsight.completion.InsertionContext;
+import com.intellij.codeInsight.lookup.LookupElement;
+import com.intellij.codeInsight.lookup.LookupElementBuilder;
+import com.intellij.lang.ASTNode;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.util.ProcessingContext;
+import java.util.List;
+import org.jetbrains.annotations.Nullable;
+
+/** Completes project view section names. */
+public class ProjectViewKeywordCompletionContributor extends CompletionContributor {
+
+  @Override
+  public AutoCompletionDecision handleAutoCompletionPossibility(AutoCompletionContext context) {
+    // auto-insert the obvious only case; else show other cases.
+    final LookupElement[] items = context.getItems();
+    if (items.length == 1) {
+      return AutoCompletionDecision.insertItem(items[0]);
+    }
+    return AutoCompletionDecision.SHOW_LOOKUP;
+  }
+
+  public ProjectViewKeywordCompletionContributor() {
+    extend(
+        CompletionType.BASIC,
+        psiElement()
+            .withLanguage(ProjectViewLanguage.INSTANCE)
+            .withElementType(ProjectViewTokenType.IDENTIFIERS)
+            .andOr(psiElement().afterLeaf("\n"), psiElement().afterLeaf(psiElement().isNull())),
+        new CompletionProvider<CompletionParameters>() {
+          @Override
+          protected void addCompletions(
+              CompletionParameters parameters,
+              ProcessingContext context,
+              CompletionResultSet result) {
+            result.addAllElements(keywordLookups);
+          }
+        });
+  }
+
+  private static final List<LookupElement> keywordLookups = getLookups();
+
+  private static List<LookupElement> getLookups() {
+    ImmutableList.Builder<LookupElement> list = ImmutableList.builder();
+    for (SectionParser parser : Sections.getUndeprecatedParsers()) {
+      list.add(forSectionParser(parser));
+    }
+    return list.build();
+  }
+
+  private static LookupElement forSectionParser(SectionParser parser) {
+    return LookupElementBuilder.create(parser.getName()).withInsertHandler(insertDivider(parser));
+  }
+
+  private static InsertHandler<LookupElement> insertDivider(SectionParser parser) {
+    return (context, item) -> {
+      Editor editor = context.getEditor();
+      Document document = editor.getDocument();
+      context.commitDocument();
+
+      String nextTokenText = findNextTokenText(context);
+      if (nextTokenText == null || nextTokenText == "\n") {
+        document.insertString(context.getTailOffset(), getDivider(parser));
+        editor.getCaretModel().moveToOffset(context.getTailOffset());
+      }
+    };
+  }
+
+  private static String getDivider(SectionParser parser) {
+    if (parser instanceof ListSectionParser) {
+      return ":\n  ";
+    }
+    char div = ((ScalarSectionParser) parser).getDivider();
+    return div == ' ' ? String.valueOf(div) : (div + " ");
+  }
+
+  @Nullable
+  protected static String findNextTokenText(final InsertionContext context) {
+    final PsiFile file = context.getFile();
+    PsiElement element = file.findElementAt(context.getTailOffset());
+    while (element != null && element.getTextLength() == 0) {
+      ASTNode next = element.getNode().getTreeNext();
+      element = next != null ? next.getPsi() : null;
+    }
+    return element != null ? element.getText() : null;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/projectview/completion/WorkspaceTypeCompletionContributor.java b/base/src/com/google/idea/blaze/base/lang/projectview/completion/WorkspaceTypeCompletionContributor.java
new file mode 100644
index 0000000..47b45e9
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/projectview/completion/WorkspaceTypeCompletionContributor.java
@@ -0,0 +1,67 @@
+/*
+ * 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.completion;
+
+import static com.intellij.patterns.PlatformPatterns.psiElement;
+
+import com.google.idea.blaze.base.lang.projectview.language.ProjectViewLanguage;
+import com.google.idea.blaze.base.lang.projectview.psi.ProjectViewPsiScalarSection;
+import com.google.idea.blaze.base.model.primitives.WorkspaceType;
+import com.google.idea.blaze.base.projectview.section.sections.WorkspaceTypeSection;
+import com.intellij.codeInsight.completion.AutoCompletionContext;
+import com.intellij.codeInsight.completion.AutoCompletionDecision;
+import com.intellij.codeInsight.completion.CompletionContributor;
+import com.intellij.codeInsight.completion.CompletionParameters;
+import com.intellij.codeInsight.completion.CompletionProvider;
+import com.intellij.codeInsight.completion.CompletionResultSet;
+import com.intellij.codeInsight.completion.CompletionType;
+import com.intellij.codeInsight.lookup.LookupElement;
+import com.intellij.codeInsight.lookup.LookupElementBuilder;
+import com.intellij.util.ProcessingContext;
+
+/** Code completion for workspace types. */
+public class WorkspaceTypeCompletionContributor extends CompletionContributor {
+
+  @Override
+  public AutoCompletionDecision handleAutoCompletionPossibility(AutoCompletionContext context) {
+    // auto-insert the obvious only case; else show other cases.
+    final LookupElement[] items = context.getItems();
+    if (items.length == 1) {
+      return AutoCompletionDecision.insertItem(items[0]);
+    }
+    return AutoCompletionDecision.SHOW_LOOKUP;
+  }
+
+  public WorkspaceTypeCompletionContributor() {
+    extend(
+        CompletionType.BASIC,
+        psiElement()
+            .withLanguage(ProjectViewLanguage.INSTANCE)
+            .inside(ProjectViewPsiScalarSection.class)
+            .afterLeaf(psiElement().withText(":").afterLeaf(WorkspaceTypeSection.KEY.getName())),
+        new CompletionProvider<CompletionParameters>() {
+          @Override
+          protected void addCompletions(
+              CompletionParameters parameters,
+              ProcessingContext context,
+              CompletionResultSet result) {
+            for (WorkspaceType type : WorkspaceType.values()) {
+              result.addElement(LookupElementBuilder.create(type.getName()));
+            }
+          }
+        });
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/projectview/formatting/ProjectViewCommenter.java b/base/src/com/google/idea/blaze/base/lang/projectview/formatting/ProjectViewCommenter.java
new file mode 100644
index 0000000..09aaa68
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/projectview/formatting/ProjectViewCommenter.java
@@ -0,0 +1,91 @@
+/*
+ * 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.formatting;
+
+import com.google.idea.blaze.base.lang.projectview.lexer.ProjectViewTokenType;
+import com.intellij.lang.CodeDocumentationAwareCommenter;
+import com.intellij.psi.PsiComment;
+import com.intellij.psi.tree.IElementType;
+import org.jetbrains.annotations.Nullable;
+
+/** Supports (un)commenting lines via IntelliJ */
+public class ProjectViewCommenter implements CodeDocumentationAwareCommenter {
+
+  @Nullable
+  @Override
+  public String getLineCommentPrefix() {
+    return "#";
+  }
+
+  @Nullable
+  @Override
+  public String getBlockCommentPrefix() {
+    return null;
+  }
+
+  @Nullable
+  @Override
+  public String getBlockCommentSuffix() {
+    return null;
+  }
+
+  @Nullable
+  @Override
+  public String getCommentedBlockCommentPrefix() {
+    return null;
+  }
+
+  @Nullable
+  @Override
+  public String getCommentedBlockCommentSuffix() {
+    return null;
+  }
+
+  @Override
+  public IElementType getLineCommentTokenType() {
+    return ProjectViewTokenType.COMMENT;
+  }
+
+  @Override
+  public IElementType getBlockCommentTokenType() {
+    return null;
+  }
+
+  @Override
+  public IElementType getDocumentationCommentTokenType() {
+    return null;
+  }
+
+  @Override
+  public String getDocumentationCommentPrefix() {
+    return null;
+  }
+
+  @Override
+  public String getDocumentationCommentLinePrefix() {
+    return null;
+  }
+
+  @Override
+  public String getDocumentationCommentSuffix() {
+    return null;
+  }
+
+  @Override
+  public boolean isDocumentationComment(PsiComment element) {
+    return false;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/projectview/formatting/ProjectViewEnterHandler.java b/base/src/com/google/idea/blaze/base/lang/projectview/formatting/ProjectViewEnterHandler.java
new file mode 100644
index 0000000..58988c1
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/projectview/formatting/ProjectViewEnterHandler.java
@@ -0,0 +1,102 @@
+/*
+ * 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.formatting;
+
+import com.google.idea.blaze.base.lang.projectview.lexer.ProjectViewTokenType;
+import com.google.idea.blaze.base.lang.projectview.psi.ProjectViewPsiFile;
+import com.google.idea.blaze.base.projectview.section.SectionParser;
+import com.intellij.codeInsight.editorActions.enter.EnterHandlerDelegateAdapter;
+import com.intellij.ide.DataManager;
+import com.intellij.injected.editor.EditorWindow;
+import com.intellij.lang.ASTNode;
+import com.intellij.lang.injection.InjectedLanguageManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.LogicalPosition;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actions.SplitLineAction;
+import com.intellij.openapi.util.Ref;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.psi.PsiDocumentManager;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiWhiteSpace;
+import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
+
+/** Inserts indents as appropriate when enter is pressed. */
+public class ProjectViewEnterHandler extends EnterHandlerDelegateAdapter {
+
+  @Override
+  public Result preprocessEnter(
+      PsiFile file,
+      Editor editor,
+      Ref<Integer> caretOffset,
+      Ref<Integer> caretAdvance,
+      DataContext dataContext,
+      EditorActionHandler originalHandler) {
+    int offset = caretOffset.get();
+    if (editor instanceof EditorWindow) {
+      file = InjectedLanguageManager.getInstance(file.getProject()).getTopLevelFile(file);
+      editor = InjectedLanguageUtil.getTopLevelEditor(editor);
+      offset = editor.getCaretModel().getOffset();
+    }
+    if (!isApplicable(file, dataContext) || !insertIndent(file, offset)) {
+      return Result.Continue;
+    }
+    int indent = SectionParser.INDENT;
+
+    editor.getCaretModel().moveToOffset(offset);
+    Document doc = editor.getDocument();
+    PsiDocumentManager.getInstance(file.getProject()).commitDocument(doc);
+
+    originalHandler.execute(editor, editor.getCaretModel().getCurrentCaret(), dataContext);
+    LogicalPosition position = editor.getCaretModel().getLogicalPosition();
+    if (position.column < indent) {
+      String spaces = StringUtil.repeatSymbol(' ', indent - position.column);
+      doc.insertString(editor.getCaretModel().getOffset(), spaces);
+    }
+    editor.getCaretModel().moveToLogicalPosition(new LogicalPosition(position.line, indent));
+    return Result.Stop;
+  }
+
+  private static boolean isApplicable(PsiFile file, DataContext dataContext) {
+    if (!(file instanceof ProjectViewPsiFile)) {
+      return false;
+    }
+    Boolean isSplitLine =
+        DataManager.getInstance().loadFromDataContext(dataContext, SplitLineAction.SPLIT_LINE_KEY);
+    if (isSplitLine != null) {
+      return false;
+    }
+    return true;
+  }
+
+  private static boolean insertIndent(PsiFile file, int offset) {
+    if (offset == 0) {
+      return false;
+    }
+    PsiElement element = file.findElementAt(offset - 1);
+    while (element != null && element instanceof PsiWhiteSpace) {
+      element = element.getPrevSibling();
+    }
+    if (element == null || element.getText() != ":") {
+      return false;
+    }
+    ASTNode prev = element.getNode().getTreePrev();
+    return prev != null && prev.getElementType() == ProjectViewTokenType.LIST_KEYWORD;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/projectview/highlighting/ProjectViewSyntaxHighlighter.java b/base/src/com/google/idea/blaze/base/lang/projectview/highlighting/ProjectViewSyntaxHighlighter.java
new file mode 100644
index 0000000..01ec2fd
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/projectview/highlighting/ProjectViewSyntaxHighlighter.java
@@ -0,0 +1,54 @@
+/*
+ * 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.highlighting;
+
+import static com.intellij.openapi.editor.DefaultLanguageHighlighterColors.IDENTIFIER;
+import static com.intellij.openapi.editor.DefaultLanguageHighlighterColors.KEYWORD;
+import static com.intellij.openapi.editor.DefaultLanguageHighlighterColors.LINE_COMMENT;
+import static com.intellij.openapi.editor.DefaultLanguageHighlighterColors.SEMICOLON;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.idea.blaze.base.lang.projectview.lexer.ProjectViewLexer;
+import com.google.idea.blaze.base.lang.projectview.lexer.ProjectViewTokenType;
+import com.intellij.lexer.Lexer;
+import com.intellij.openapi.editor.colors.TextAttributesKey;
+import com.intellij.openapi.fileTypes.SyntaxHighlighterBase;
+import com.intellij.psi.tree.IElementType;
+import java.util.Map;
+
+/**
+ * This class maps tokens to highlighting attributes. Each attribute contains the font properties.
+ */
+public class ProjectViewSyntaxHighlighter extends SyntaxHighlighterBase {
+
+  private static final Map<IElementType, TextAttributesKey> keys =
+      ImmutableMap.of(
+          ProjectViewTokenType.COMMENT, LINE_COMMENT,
+          ProjectViewTokenType.COLON, SEMICOLON,
+          ProjectViewTokenType.IDENTIFIER, IDENTIFIER,
+          ProjectViewTokenType.LIST_KEYWORD, KEYWORD,
+          ProjectViewTokenType.SCALAR_KEYWORD, KEYWORD);
+
+  @Override
+  public Lexer getHighlightingLexer() {
+    return new ProjectViewLexer();
+  }
+
+  @Override
+  public TextAttributesKey[] getTokenHighlights(IElementType iElementType) {
+    return pack(keys.get(iElementType));
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/projectview/highlighting/ProjectViewSyntaxHighlighterFactory.java b/base/src/com/google/idea/blaze/base/lang/projectview/highlighting/ProjectViewSyntaxHighlighterFactory.java
new file mode 100644
index 0000000..96c4b25
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/projectview/highlighting/ProjectViewSyntaxHighlighterFactory.java
@@ -0,0 +1,30 @@
+/*
+ * 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.highlighting;
+
+import com.intellij.openapi.fileTypes.SyntaxHighlighter;
+import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+
+/** Factory for BuildSyntaxHighlighter */
+public class ProjectViewSyntaxHighlighterFactory extends SyntaxHighlighterFactory {
+
+  @Override
+  public SyntaxHighlighter getSyntaxHighlighter(Project project, VirtualFile virtualFile) {
+    return new ProjectViewSyntaxHighlighter();
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/projectview/language/ProjectViewFileType.java b/base/src/com/google/idea/blaze/base/lang/projectview/language/ProjectViewFileType.java
new file mode 100644
index 0000000..558a60e
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/projectview/language/ProjectViewFileType.java
@@ -0,0 +1,58 @@
+/*
+ * 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.language;
+
+import com.google.idea.blaze.base.settings.Blaze;
+import com.intellij.openapi.fileTypes.LanguageFileType;
+import icons.BlazeIcons;
+import javax.annotation.Nullable;
+import javax.swing.Icon;
+
+/** Blaze project view file type */
+public class ProjectViewFileType extends LanguageFileType {
+
+  public static final ProjectViewFileType INSTANCE = new ProjectViewFileType();
+
+  private ProjectViewFileType() {
+    super(ProjectViewLanguage.INSTANCE);
+  }
+
+  @Override
+  public String getName() {
+    // Warning: this is conflated with Language.myID in several places...
+    // They must be identical.
+    return ProjectViewLanguage.INSTANCE.getID();
+  }
+
+  @Override
+  public String getDescription() {
+    return Blaze.defaultBuildSystemName() + " project view files";
+  }
+
+  @Override
+  public String getDefaultExtension() {
+    // Ideally we'd return a build-system specific extension here, but that would require
+    // a hack to guess the current project, or choosing either the blaze or bazel
+    // extension. Instead don't specify a default extension.
+    return "";
+  }
+
+  @Override
+  @Nullable
+  public Icon getIcon() {
+    return BlazeIcons.Blaze;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/projectview/language/ProjectViewFileTypeFactory.java b/base/src/com/google/idea/blaze/base/lang/projectview/language/ProjectViewFileTypeFactory.java
new file mode 100644
index 0000000..5e8408a
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/projectview/language/ProjectViewFileTypeFactory.java
@@ -0,0 +1,37 @@
+/*
+ * 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.language;
+
+import com.google.idea.blaze.base.projectview.ProjectViewStorageManager;
+import com.intellij.openapi.fileTypes.ExtensionFileNameMatcher;
+import com.intellij.openapi.fileTypes.FileNameMatcher;
+import com.intellij.openapi.fileTypes.FileTypeConsumer;
+import com.intellij.openapi.fileTypes.FileTypeFactory;
+import org.jetbrains.annotations.NotNull;
+
+/** Factory for ProjectViewFileType */
+public class ProjectViewFileTypeFactory extends FileTypeFactory {
+
+  @Override
+  public void createFileTypes(@NotNull final FileTypeConsumer consumer) {
+    FileNameMatcher[] matchers =
+        ProjectViewStorageManager.VALID_EXTENSIONS
+            .stream()
+            .map(ExtensionFileNameMatcher::new)
+            .toArray(ExtensionFileNameMatcher[]::new);
+    consumer.consume(ProjectViewFileType.INSTANCE, matchers);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/projectview/language/ProjectViewKeywords.java b/base/src/com/google/idea/blaze/base/lang/projectview/language/ProjectViewKeywords.java
new file mode 100644
index 0000000..9c7cf8d
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/projectview/language/ProjectViewKeywords.java
@@ -0,0 +1,62 @@
+/*
+ * 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.language;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.idea.blaze.base.projectview.section.ListSectionParser;
+import com.google.idea.blaze.base.projectview.section.ScalarSectionParser;
+import com.google.idea.blaze.base.projectview.section.SectionParser;
+import com.google.idea.blaze.base.projectview.section.SectionParser.ItemType;
+import com.google.idea.blaze.base.projectview.section.sections.Sections;
+
+/** Section parser keywords accepted in project view files. */
+public class ProjectViewKeywords {
+
+  public static final ImmutableMap<String, ListSectionParser> LIST_KEYWORD_MAP =
+      getListKeywordMap();
+  public static final ImmutableMap<String, ScalarSectionParser> SCALAR_KEYWORD_MAP =
+      getScalarKeywordMap();
+  public static final ImmutableMap<String, ItemType> ITEM_TYPES = getItemTypes();
+
+  private static ImmutableMap<String, ListSectionParser> getListKeywordMap() {
+    ImmutableMap.Builder<String, ListSectionParser> builder = ImmutableMap.builder();
+    for (SectionParser parser : Sections.getParsers()) {
+      if (parser instanceof ListSectionParser) {
+        builder.put(parser.getName(), (ListSectionParser) parser);
+      }
+    }
+    return builder.build();
+  }
+
+  /** We get the parser so we have access to both the keyword and the divider char. */
+  private static ImmutableMap<String, ScalarSectionParser> getScalarKeywordMap() {
+    ImmutableMap.Builder<String, ScalarSectionParser> builder = ImmutableMap.builder();
+    for (SectionParser parser : Sections.getParsers()) {
+      if (parser instanceof ScalarSectionParser) {
+        builder.put(parser.getName(), (ScalarSectionParser) parser);
+      }
+    }
+    return builder.build();
+  }
+
+  private static ImmutableMap<String, ItemType> getItemTypes() {
+    ImmutableMap.Builder<String, ItemType> builder = ImmutableMap.builder();
+    for (SectionParser parser : Sections.getParsers()) {
+      builder.put(parser.getName(), parser.getItemType());
+    }
+    return builder.build();
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/projectview/language/ProjectViewLanguage.java b/base/src/com/google/idea/blaze/base/lang/projectview/language/ProjectViewLanguage.java
new file mode 100644
index 0000000..efde5bf
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/projectview/language/ProjectViewLanguage.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.lang.projectview.language;
+
+import com.google.idea.blaze.base.settings.Blaze;
+import com.intellij.lang.Language;
+
+/** Blaze project file language */
+public class ProjectViewLanguage extends Language {
+
+  public static final ProjectViewLanguage INSTANCE = new ProjectViewLanguage();
+
+  private ProjectViewLanguage() {
+    super("projectview");
+  }
+
+  @Override
+  public String getDisplayName() {
+    return Blaze.defaultBuildSystemName() + " project view";
+  }
+
+  @Override
+  public boolean isCaseSensitive() {
+    return true;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/projectview/lexer/ProjectViewLexer.java b/base/src/com/google/idea/blaze/base/lang/projectview/lexer/ProjectViewLexer.java
new file mode 100644
index 0000000..6000fad
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/projectview/lexer/ProjectViewLexer.java
@@ -0,0 +1,116 @@
+/*
+ * 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.lexer;
+
+import com.google.idea.blaze.base.lang.projectview.lexer.ProjectViewLexerBase.Token;
+import com.intellij.lexer.LexerBase;
+import com.intellij.psi.tree.IElementType;
+import java.util.Iterator;
+import java.util.List;
+
+/** Implementation of LexerBase using BuildLexerBase to tokenize the input. */
+public class ProjectViewLexer extends LexerBase {
+
+  private int offsetEnd;
+  private int offsetStart;
+  private CharSequence buffer;
+  private Iterator<Token> tokens;
+  private Token currentToken;
+
+  @Override
+  public void start(CharSequence charSequence, int startOffset, int endOffset, int initialState) {
+    buffer = charSequence;
+    this.offsetEnd = endOffset;
+    this.offsetStart = startOffset;
+
+    ProjectViewLexerBase lexer =
+        new ProjectViewLexerBase(charSequence.subSequence(startOffset, endOffset));
+    checkNoCharactersMissing(
+        charSequence.subSequence(startOffset, endOffset).length(), lexer.getTokens());
+    tokens = lexer.getTokens().iterator();
+    currentToken = null;
+    if (tokens.hasNext()) {
+      currentToken = tokens.next();
+    }
+  }
+
+  /** Temporary debugging code. We need to tokenize every character in the input string. */
+  private static void checkNoCharactersMissing(int totalLength, List<Token> tokens) {
+    if (!tokens.isEmpty() && tokens.get(tokens.size() - 1).right != totalLength) {
+      String error =
+          String.format(
+              "Lengths don't match: %s instead of %s",
+              tokens.get(tokens.size() - 1).right, totalLength);
+      throw new RuntimeException(error);
+    }
+    int start = 0;
+    for (int i = 0; i < tokens.size(); i++) {
+      Token token = tokens.get(i);
+      if (token.left != start) {
+        throw new RuntimeException("Gap/inconsistency at: " + start);
+      }
+      start = token.right;
+    }
+  }
+
+  @Override
+  public int getState() {
+    return 0;
+  }
+
+  @Override
+  public IElementType getTokenType() {
+    if (currentToken != null) {
+      return currentToken.type;
+    }
+    return null;
+  }
+
+  @Override
+  public int getTokenStart() {
+    if (currentToken == null) {
+      return 0;
+    }
+    return currentToken.left + offsetStart;
+  }
+
+  @Override
+  public int getTokenEnd() {
+    if (currentToken == null) {
+      return 0;
+    }
+    return currentToken.right + offsetStart;
+  }
+
+  @Override
+  public void advance() {
+    if (tokens.hasNext()) {
+      currentToken = tokens.next();
+    } else {
+      currentToken = null;
+    }
+  }
+
+  @Override
+  public CharSequence getBufferSequence() {
+    return buffer;
+  }
+
+  @Override
+  public int getBufferEnd() {
+    return offsetEnd;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/projectview/lexer/ProjectViewLexerBase.java b/base/src/com/google/idea/blaze/base/lang/projectview/lexer/ProjectViewLexerBase.java
new file mode 100644
index 0000000..e840dcb
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/projectview/lexer/ProjectViewLexerBase.java
@@ -0,0 +1,156 @@
+/*
+ * 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.lexer;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.lang.projectview.language.ProjectViewKeywords;
+import java.util.List;
+
+/** Lexer for project view files. */
+public class ProjectViewLexerBase {
+
+  @VisibleForTesting
+  static class Token {
+    final ProjectViewTokenType type;
+    final int left;
+    final int right;
+
+    private Token(ProjectViewTokenType type, int left, int right) {
+      this.type = type;
+      this.left = left;
+      this.right = right;
+    }
+  }
+
+  private final List<Token> tokens;
+
+  // Input buffer and position
+  private final char[] buffer;
+  private int pos;
+
+  private int identifierStart = -1;
+  private boolean lineHasPrecedingNonWhitespaceChar = false;
+
+  public ProjectViewLexerBase(CharSequence input) {
+    this.buffer = input.toString().toCharArray();
+    this.tokens = Lists.newArrayList();
+    this.pos = 0;
+    tokenize();
+  }
+
+  public List<Token> getTokens() {
+    return tokens;
+  }
+
+  /** Performs tokenization of the character buffer of file contents provided to the constructor. */
+  private void tokenize() {
+    while (pos < buffer.length) {
+      char c = buffer[pos];
+      pos++;
+      switch (c) {
+        case '\n':
+          addPrecedingIdentifier(pos - 1);
+          tokens.add(new Token(ProjectViewTokenType.NEWLINE, pos - 1, pos));
+          lineHasPrecedingNonWhitespaceChar = false;
+          break;
+        case ' ':
+        case '\t':
+        case '\r':
+          addPrecedingIdentifier(pos - 1);
+          handleWhitespace();
+          break;
+        case ':':
+          addPrecedingIdentifier(pos - 1);
+          tokens.add(new Token(ProjectViewTokenType.COLON, pos - 1, pos));
+          break;
+        case '#':
+          if (!lineHasPrecedingNonWhitespaceChar) {
+            addPrecedingIdentifier(pos - 1);
+            addCommentLine(pos - 1);
+            break;
+          }
+          // otherwise '#' treated as part of the identifier; intentional fall-through
+        default:
+          lineHasPrecedingNonWhitespaceChar = true;
+          // all other characters combined into an 'identifier' lexical token
+          if (identifierStart == -1) {
+            identifierStart = pos - 1;
+          }
+      }
+    }
+    addPrecedingIdentifier(pos);
+  }
+
+  private void addPrecedingIdentifier(int end) {
+    if (identifierStart != -1) {
+      tokens.add(new Token(getIdentifierToken(identifierStart, end), identifierStart, end));
+      identifierStart = -1;
+    }
+  }
+
+  private void addCommentLine(int start) {
+    while (pos < buffer.length) {
+      char c = buffer[pos];
+      if (c == '\n') {
+        break;
+      }
+      pos++;
+    }
+    tokens.add(new Token(ProjectViewTokenType.COMMENT, start, pos));
+  }
+
+  /**
+   * If the whitespace is followed by an end-of-line comment or a newline, it's combined with those
+   * tokens.
+   */
+  private void handleWhitespace() {
+    int oldPos = pos - 1;
+    while (pos < buffer.length) {
+      char c = buffer[pos];
+      switch (c) {
+        case ' ':
+        case '\t':
+        case '\r':
+          pos++;
+          break;
+        default:
+          if (lineHasPrecedingNonWhitespaceChar || c == '#' || c == '\n') {
+            tokens.add(new Token(ProjectViewTokenType.WHITESPACE, oldPos, pos));
+          } else {
+            tokens.add(new Token(ProjectViewTokenType.INDENT, oldPos, pos));
+          }
+          return;
+      }
+    }
+    tokens.add(new Token(ProjectViewTokenType.WHITESPACE, oldPos, pos));
+  }
+
+  private ProjectViewTokenType getIdentifierToken(int start, int end) {
+    String string = bufferSlice(start, end);
+    if (ProjectViewKeywords.LIST_KEYWORD_MAP.keySet().contains(string)) {
+      return ProjectViewTokenType.LIST_KEYWORD;
+    }
+    if (ProjectViewKeywords.SCALAR_KEYWORD_MAP.keySet().contains(string)) {
+      return ProjectViewTokenType.SCALAR_KEYWORD;
+    }
+    return ProjectViewTokenType.IDENTIFIER;
+  }
+
+  private String bufferSlice(int start, int end) {
+    return new String(this.buffer, start, end - start);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/projectview/lexer/ProjectViewTokenType.java b/base/src/com/google/idea/blaze/base/lang/projectview/lexer/ProjectViewTokenType.java
new file mode 100644
index 0000000..5b9e09e
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/projectview/lexer/ProjectViewTokenType.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.lang.projectview.lexer;
+
+import com.google.idea.blaze.base.lang.projectview.language.ProjectViewLanguage;
+import com.intellij.psi.tree.IElementType;
+import com.intellij.psi.tree.TokenSet;
+
+/** Lexical elements for the project view language. */
+public class ProjectViewTokenType extends IElementType {
+
+  // only start-of-line (ignoring whitespace) comments are valid
+  public static final ProjectViewTokenType COMMENT = create("comment");
+  public static final ProjectViewTokenType WHITESPACE = create("whitespace");
+  public static final ProjectViewTokenType NEWLINE = create("newline");
+  public static final ProjectViewTokenType COLON = create(":");
+
+  // any amount of whitespace at the start of a line, followed by a non-'#', non-newline character
+  public static final ProjectViewTokenType INDENT = create("indent");
+
+  // all remaining characters that aren't preceded by a start-of-line comments
+  public static final ProjectViewTokenType IDENTIFIER = create("identifier");
+
+  public static final ProjectViewTokenType LIST_KEYWORD = create("list_keyword");
+  public static final ProjectViewTokenType SCALAR_KEYWORD = create("scalar_keyword");
+
+  private static ProjectViewTokenType create(String debugName) {
+    return new ProjectViewTokenType(debugName);
+  }
+
+  private ProjectViewTokenType(String debugName) {
+    super(debugName, ProjectViewLanguage.INSTANCE);
+  }
+
+  public static final TokenSet IDENTIFIERS =
+      TokenSet.create(IDENTIFIER, LIST_KEYWORD, SCALAR_KEYWORD);
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/projectview/parser/ProjectViewParserDefinition.java b/base/src/com/google/idea/blaze/base/lang/projectview/parser/ProjectViewParserDefinition.java
new file mode 100644
index 0000000..702b80b
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/projectview/parser/ProjectViewParserDefinition.java
@@ -0,0 +1,93 @@
+/*
+ * 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.parser;
+
+import com.google.idea.blaze.base.lang.projectview.lexer.ProjectViewLexer;
+import com.google.idea.blaze.base.lang.projectview.lexer.ProjectViewTokenType;
+import com.google.idea.blaze.base.lang.projectview.psi.ProjectViewElementType;
+import com.google.idea.blaze.base.lang.projectview.psi.ProjectViewElementTypes;
+import com.google.idea.blaze.base.lang.projectview.psi.ProjectViewPsiFile;
+import com.intellij.extapi.psi.ASTWrapperPsiElement;
+import com.intellij.lang.ASTNode;
+import com.intellij.lang.ParserDefinition;
+import com.intellij.lang.PsiBuilder;
+import com.intellij.lang.PsiParser;
+import com.intellij.lexer.Lexer;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.FileViewProvider;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.tree.IElementType;
+import com.intellij.psi.tree.IFileElementType;
+import com.intellij.psi.tree.TokenSet;
+
+/** Defines the project view file parser */
+public class ProjectViewParserDefinition implements ParserDefinition {
+
+  @Override
+  public Lexer createLexer(Project project) {
+    return new ProjectViewLexer();
+  }
+
+  @Override
+  public PsiParser createParser(Project project) {
+    return (root, builder) -> {
+      PsiBuilder.Marker rootMarker = builder.mark();
+      new ProjectViewPsiParser(builder).parseFile();
+      rootMarker.done(root);
+      return builder.getTreeBuilt();
+    };
+  }
+
+  @Override
+  public IFileElementType getFileNodeType() {
+    return ProjectViewElementTypes.FILE;
+  }
+
+  @Override
+  public TokenSet getWhitespaceTokens() {
+    return TokenSet.create(ProjectViewTokenType.WHITESPACE);
+  }
+
+  @Override
+  public TokenSet getCommentTokens() {
+    return TokenSet.create(ProjectViewTokenType.COMMENT);
+  }
+
+  @Override
+  public TokenSet getStringLiteralElements() {
+    return TokenSet.EMPTY;
+  }
+
+  @Override
+  public PsiElement createElement(ASTNode node) {
+    IElementType type = node.getElementType();
+    if (type instanceof ProjectViewElementType) {
+      return ((ProjectViewElementType) type).createElement(node);
+    }
+    return new ASTWrapperPsiElement(node);
+  }
+
+  @Override
+  public PsiFile createFile(FileViewProvider viewProvider) {
+    return new ProjectViewPsiFile(viewProvider);
+  }
+
+  @Override
+  public SpaceRequirements spaceExistanceTypeBetweenTokens(ASTNode left, ASTNode right) {
+    return null;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/projectview/parser/ProjectViewPsiParser.java b/base/src/com/google/idea/blaze/base/lang/projectview/parser/ProjectViewPsiParser.java
new file mode 100644
index 0000000..2703bd2
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/projectview/parser/ProjectViewPsiParser.java
@@ -0,0 +1,199 @@
+/*
+ * 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.parser;
+
+import com.google.idea.blaze.base.lang.projectview.language.ProjectViewKeywords;
+import com.google.idea.blaze.base.lang.projectview.lexer.ProjectViewTokenType;
+import com.google.idea.blaze.base.lang.projectview.psi.ProjectViewElementType;
+import com.google.idea.blaze.base.lang.projectview.psi.ProjectViewElementTypes;
+import com.google.idea.blaze.base.projectview.section.ScalarSectionParser;
+import com.intellij.lang.PsiBuilder;
+import com.intellij.openapi.application.ApplicationManager;
+import javax.annotation.Nullable;
+
+/** Project view psi parser. */
+public class ProjectViewPsiParser {
+
+  private final PsiBuilder builder;
+
+  public ProjectViewPsiParser(PsiBuilder builder) {
+    this.builder = builder;
+  }
+
+  public void parseFile() {
+    builder.setDebugMode(ApplicationManager.getApplication().isUnitTestMode());
+    while (!builder.eof()) {
+      if (matches(ProjectViewTokenType.NEWLINE)) {
+        continue;
+      }
+      parseSection();
+    }
+  }
+
+  /** A block is one of: - scalar section - list section */
+  private void parseSection() {
+    PsiBuilder.Marker marker = builder.mark();
+    if (matches(ProjectViewTokenType.LIST_KEYWORD)) {
+      expect(ProjectViewTokenType.COLON);
+      skipPastNewline();
+      parseListItems();
+      marker.done(ProjectViewElementTypes.LIST_SECTION);
+      return;
+    }
+    if (currentToken() == ProjectViewTokenType.SCALAR_KEYWORD) {
+      ScalarSectionParser parser =
+          ProjectViewKeywords.SCALAR_KEYWORD_MAP.get(builder.getTokenText());
+      if (parser != null) {
+        parseScalarSection(parser);
+        marker.done(ProjectViewElementTypes.SCALAR_SECTION);
+        return;
+      }
+    }
+    // handle each of the error cases
+    if (matches(ProjectViewTokenType.INDENT)) {
+      skipBlockAndError(
+          marker, "Invalid indentation. Indented lines must be preceded by a list keyword");
+      return;
+    }
+    if (matches(ProjectViewTokenType.COLON)) {
+      skipBlockAndError(marker, "Invalid section: lines cannot begin with a colon.");
+      return;
+    }
+    skipBlockAndError(marker, "Unrecognized keyword: " + builder.getTokenText());
+  }
+
+  private void parseListItems() {
+    while (!builder.eof()) {
+      if (matches(ProjectViewTokenType.NEWLINE)) {
+        continue;
+      }
+      if (!matches(ProjectViewTokenType.INDENT)) {
+        return;
+      }
+      PsiBuilder.Marker marker = builder.mark();
+      skipToNewlineToken();
+      marker.done(ProjectViewElementTypes.LIST_ITEM);
+      builder.advanceLexer();
+    }
+  }
+
+  private void parseScalarSection(ScalarSectionParser parser) {
+    boolean whitespaceDivider = builder.rawLookup(1) == ProjectViewTokenType.WHITESPACE;
+    builder.advanceLexer();
+
+    char divider = parser.getDivider();
+    if (divider == ' ') {
+      if (!whitespaceDivider) {
+        builder.error("Whitespace divider expected after '" + parser.getName() + "'");
+        builder.advanceLexer();
+      }
+      parseScalarItem();
+      return;
+    }
+    if (whitespaceDivider || !Character.toString(divider).equals(builder.getTokenText())) {
+      builder.error(String.format("'%s' expected", divider));
+    }
+    if (!whitespaceDivider) {
+      builder.advanceLexer();
+    }
+    parseScalarItem();
+  }
+
+  private void parseScalarItem() {
+    PsiBuilder.Marker marker = builder.mark();
+    skipToNewlineToken();
+    marker.done(ProjectViewElementTypes.SCALAR_ITEM);
+    builder.advanceLexer();
+  }
+
+  /** Consumes the current token iff it matches the expected type. Otherwise, returns false */
+  private boolean matches(ProjectViewTokenType kind) {
+    if (currentToken() == kind) {
+      builder.advanceLexer();
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * Consumes the current token if it's of the expected type. Otherwise, returns false and reports
+   * an error.
+   */
+  private boolean expect(ProjectViewTokenType kind) {
+    if (matches(kind)) {
+      return true;
+    }
+    builder.error(String.format("'%s' expected", kind));
+    return false;
+  }
+
+  /** Checks if the upcoming sequence of tokens match that expected. Doesn't advance the parser. */
+  private boolean atTokenSequence(ProjectViewTokenType... kinds) {
+    for (int i = 0; i < kinds.length; i++) {
+      if (builder.lookAhead(i) != kinds[i]) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  @Nullable
+  private ProjectViewTokenType currentToken() {
+    return (ProjectViewTokenType) builder.getTokenType();
+  }
+
+  private void skipBlockAndError(PsiBuilder.Marker marker, String message) {
+    skipToNextBlock();
+    marker.error(message);
+  }
+
+  /** Skip to the start of the next unindented line */
+  private void skipToNextBlock() {
+    while (!builder.eof()) {
+      if (atTokenSequence(ProjectViewTokenType.NEWLINE, ProjectViewTokenType.IDENTIFIER)) {
+        builder.advanceLexer();
+        return;
+      }
+      builder.advanceLexer();
+    }
+  }
+
+  /** Skip to the start of the next line */
+  private void skipPastNewline() {
+    while (!builder.eof()) {
+      if (matches(ProjectViewTokenType.NEWLINE)) {
+        return;
+      }
+      builder.advanceLexer();
+    }
+  }
+
+  /** Skip to the end of the current line */
+  private void skipToNewlineToken() {
+    while (!builder.eof()) {
+      if (currentToken() == ProjectViewTokenType.NEWLINE) {
+        return;
+      }
+      builder.advanceLexer();
+    }
+  }
+
+  private void buildTokenElement(ProjectViewElementType type) {
+    PsiBuilder.Marker marker = builder.mark();
+    builder.advanceLexer();
+    marker.done(type);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewElementType.java b/base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewElementType.java
new file mode 100644
index 0000000..b5c1ced
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewElementType.java
@@ -0,0 +1,49 @@
+/*
+ * 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.psi;
+
+import com.google.idea.blaze.base.lang.projectview.language.ProjectViewFileType;
+import com.intellij.lang.ASTNode;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.tree.IElementType;
+import java.lang.reflect.Constructor;
+
+/**
+ * IElementTypes used in the AST by the parser (as opposed to the types used by the lexer).<br>
+ * Modelled on IntelliJ's core language conventions.
+ */
+public class ProjectViewElementType extends IElementType {
+
+  private static final Class[] PARAMETER_TYPES = new Class[] {ASTNode.class};
+  private final Class<? extends PsiElement> psiElementClass;
+  private Constructor<? extends PsiElement> constructor;
+
+  public ProjectViewElementType(String name, Class<? extends PsiElement> psiElementClass) {
+    super(name, ProjectViewFileType.INSTANCE.getLanguage());
+    this.psiElementClass = psiElementClass;
+  }
+
+  public PsiElement createElement(ASTNode node) {
+    try {
+      if (constructor == null) {
+        constructor = psiElementClass.getConstructor(PARAMETER_TYPES);
+      }
+      return constructor.newInstance(node);
+    } catch (Exception e) {
+      throw new IllegalStateException("No necessary constructor for " + node.getElementType(), e);
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewElementTypes.java b/base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewElementTypes.java
new file mode 100644
index 0000000..29b2ebb
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewElementTypes.java
@@ -0,0 +1,35 @@
+/*
+ * 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.psi;
+
+import com.google.idea.blaze.base.lang.projectview.language.ProjectViewFileType;
+import com.intellij.psi.tree.IFileElementType;
+
+/** Collects the types used by the PsiBuilder to construct the AST */
+public interface ProjectViewElementTypes {
+
+  IFileElementType FILE = new IFileElementType(ProjectViewFileType.INSTANCE.getLanguage());
+
+  ProjectViewElementType LIST_SECTION =
+      new ProjectViewElementType("list_section", ProjectViewPsiListSection.class);
+  ProjectViewElementType SCALAR_SECTION =
+      new ProjectViewElementType("scalar_section", ProjectViewPsiScalarSection.class);
+
+  ProjectViewElementType LIST_ITEM =
+      new ProjectViewElementType("list_item", ProjectViewPsiListItem.class);
+  ProjectViewElementType SCALAR_ITEM =
+      new ProjectViewElementType("scalar_item", ProjectViewPsiScalarItem.class);
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiElement.java b/base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiElement.java
new file mode 100644
index 0000000..b64e6a6
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiElement.java
@@ -0,0 +1,37 @@
+/*
+ * 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.psi;
+
+import com.intellij.extapi.psi.ASTWrapperPsiElement;
+import com.intellij.lang.ASTNode;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiReference;
+
+/** Base psi element for project view files. */
+public abstract class ProjectViewPsiElement extends ASTWrapperPsiElement {
+  public ProjectViewPsiElement(ASTNode node) {
+    super(node);
+  }
+
+  @Override
+  public PsiReference[] getReferences() {
+    return PsiReference.EMPTY_ARRAY;
+  }
+
+  public <P extends PsiElement> P[] childrenOfClass(Class<P> psiClass) {
+    return findChildrenByClass(psiClass);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiFile.java b/base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiFile.java
new file mode 100644
index 0000000..2d754a1
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiFile.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.lang.projectview.psi;
+
+import com.google.idea.blaze.base.lang.projectview.language.ProjectViewFileType;
+import com.intellij.extapi.psi.PsiFileBase;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.psi.FileViewProvider;
+
+/** PSI file for project view file. */
+public class ProjectViewPsiFile extends PsiFileBase {
+
+  public ProjectViewPsiFile(FileViewProvider viewProvider) {
+    super(viewProvider, ProjectViewFileType.INSTANCE.getLanguage());
+  }
+
+  @Override
+  public FileType getFileType() {
+    return ProjectViewFileType.INSTANCE;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiListItem.java b/base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiListItem.java
new file mode 100644
index 0000000..9f383aa
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiListItem.java
@@ -0,0 +1,26 @@
+/*
+ * 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.psi;
+
+import com.intellij.lang.ASTNode;
+
+/** Psi element for a list item. */
+public class ProjectViewPsiListItem extends ProjectViewPsiSectionItem {
+
+  public ProjectViewPsiListItem(ASTNode node) {
+    super(node);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiListSection.java b/base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiListSection.java
new file mode 100644
index 0000000..d75547a
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiListSection.java
@@ -0,0 +1,26 @@
+/*
+ * 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.psi;
+
+import com.intellij.lang.ASTNode;
+
+/** Psi element for list section. */
+public class ProjectViewPsiListSection extends ProjectViewPsiElement {
+
+  public ProjectViewPsiListSection(ASTNode node) {
+    super(node);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiScalarItem.java b/base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiScalarItem.java
new file mode 100644
index 0000000..de2f3a4
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiScalarItem.java
@@ -0,0 +1,26 @@
+/*
+ * 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.psi;
+
+import com.intellij.lang.ASTNode;
+
+/** Psi element for a scalar item. */
+public class ProjectViewPsiScalarItem extends ProjectViewPsiSectionItem {
+
+  public ProjectViewPsiScalarItem(ASTNode node) {
+    super(node);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiScalarSection.java b/base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiScalarSection.java
new file mode 100644
index 0000000..6f03ff4
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiScalarSection.java
@@ -0,0 +1,26 @@
+/*
+ * 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.psi;
+
+import com.intellij.lang.ASTNode;
+
+/** Psi element for scalar section. */
+public class ProjectViewPsiScalarSection extends ProjectViewPsiElement {
+
+  public ProjectViewPsiScalarSection(ASTNode node) {
+    super(node);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiSectionItem.java b/base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiSectionItem.java
new file mode 100644
index 0000000..2ec19be
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiSectionItem.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.lang.projectview.psi;
+
+import com.google.idea.blaze.base.lang.buildfile.references.FileLookupData.PathFormat;
+import com.google.idea.blaze.base.lang.projectview.language.ProjectViewKeywords;
+import com.google.idea.blaze.base.lang.projectview.lexer.ProjectViewTokenType;
+import com.google.idea.blaze.base.lang.projectview.references.ProjectViewLabelReference;
+import com.google.idea.blaze.base.projectview.section.SectionParser.ItemType;
+import com.intellij.lang.ASTNode;
+import com.intellij.psi.PsiReference;
+import com.intellij.psi.impl.SharedPsiElementImplUtil;
+import javax.annotation.Nullable;
+
+/** Psi element for a list or scalar item. */
+public abstract class ProjectViewPsiSectionItem extends ProjectViewPsiElement {
+
+  public ProjectViewPsiSectionItem(ASTNode node) {
+    super(node);
+  }
+
+  @Override
+  public PsiReference[] getReferences() {
+    return SharedPsiElementImplUtil.getReferences(this);
+  }
+
+  @Override
+  public PsiReference getReference() {
+    ASTNode identifier = getNode().findChildByType(ProjectViewTokenType.IDENTIFIER);
+    PathFormat pathFormat = getLabelType();
+    if (identifier != null && pathFormat != null) {
+      return new ProjectViewLabelReference(this, pathFormat);
+    }
+    return null;
+  }
+
+  @Nullable
+  public PathFormat getLabelType() {
+    ASTNode parent = getNode().getTreeParent();
+    ASTNode identifier = parent != null ? parent.getFirstChildNode() : null;
+    if (identifier == null) {
+      return null;
+    }
+    ItemType itemType = ProjectViewKeywords.ITEM_TYPES.get(identifier.getText());
+    if (itemType == null) {
+      return null;
+    }
+    switch (itemType) {
+      case Label:
+        return PathFormat.NonLocal;
+      case FileSystemItem:
+        return PathFormat.NonLocalWithoutInitialBackslashes;
+      default:
+        return null;
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/projectview/psi/util/ProjectViewElementGenerator.java b/base/src/com/google/idea/blaze/base/lang/projectview/psi/util/ProjectViewElementGenerator.java
new file mode 100644
index 0000000..ac82870
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/projectview/psi/util/ProjectViewElementGenerator.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.lang.projectview.psi.util;
+
+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.lang.projectview.psi.ProjectViewPsiElement;
+import com.google.idea.blaze.base.lang.projectview.psi.ProjectViewPsiSectionItem;
+import com.intellij.lang.ASTNode;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiFileFactory;
+import com.intellij.psi.impl.PsiFileFactoryImpl;
+import com.intellij.testFramework.LightVirtualFile;
+import javax.annotation.Nullable;
+
+/** Creates dummy BuildElements, e.g. for renaming purposes. */
+public class ProjectViewElementGenerator {
+
+  private static final String DUMMY_FILENAME = "dummy.bazelproject";
+
+  private static PsiFile createDummyFile(Project project, String contents) {
+    PsiFileFactory factory = PsiFileFactory.getInstance(project);
+    LightVirtualFile virtualFile =
+        new LightVirtualFile(DUMMY_FILENAME, ProjectViewFileType.INSTANCE, contents);
+    PsiFile psiFile =
+        ((PsiFileFactoryImpl) factory)
+            .trySetupPsiForFile(virtualFile, ProjectViewLanguage.INSTANCE, false, true);
+    assert psiFile != null;
+    return psiFile;
+  }
+
+  @Nullable
+  public static ASTNode createReplacementItemNode(
+      ProjectViewPsiSectionItem sectionItem, String newStringContents) {
+    TextRange itemRange = sectionItem.getTextRange();
+    ProjectViewPsiElement parent = (ProjectViewPsiElement) sectionItem.getParent();
+    if (parent == null) {
+      return sectionItem.getNode();
+    }
+    int startOffset = sectionItem.getStartOffsetInParent();
+    String originalSectionText = parent.getText();
+    String newSectionText =
+        StringUtil.replaceSubstring(
+            originalSectionText,
+            new TextRange(startOffset, startOffset + itemRange.getLength()),
+            newStringContents);
+    PsiFile dummyFile = createDummyFile(sectionItem.getProject(), newSectionText);
+    PsiElement leafElement = dummyFile.findElementAt(startOffset);
+    return leafElement != null ? leafElement.getParent().getNode() : null;
+  }
+}
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
new file mode 100644
index 0000000..bcb06d0
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/projectview/references/ProjectViewLabelReference.java
@@ -0,0 +1,136 @@
+/*
+ * 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 com.google.idea.blaze.base.lang.buildfile.completion.BuildLookupElement;
+import com.google.idea.blaze.base.lang.buildfile.completion.LabelRuleLookupElement;
+import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
+import com.google.idea.blaze.base.lang.buildfile.references.BuildReferenceManager;
+import com.google.idea.blaze.base.lang.buildfile.references.FileLookupData;
+import com.google.idea.blaze.base.lang.buildfile.references.FileLookupData.PathFormat;
+import com.google.idea.blaze.base.lang.buildfile.references.LabelUtils;
+import com.google.idea.blaze.base.lang.buildfile.references.QuoteType;
+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.intellij.lang.ASTNode;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiReferenceBase;
+import com.intellij.util.ArrayUtil;
+import com.intellij.util.IncorrectOperationException;
+import javax.annotation.Nullable;
+
+/** A blaze label reference. */
+public class ProjectViewLabelReference extends PsiReferenceBase<ProjectViewPsiSectionItem> {
+
+  private final PathFormat pathFormat;
+
+  public ProjectViewLabelReference(ProjectViewPsiSectionItem element, PathFormat pathFormat) {
+    super(element, new TextRange(0, element.getTextLength()));
+    this.pathFormat = pathFormat;
+  }
+
+  @Nullable
+  @Override
+  public PsiElement resolve() {
+    Label label = getLabel(myElement.getText());
+    if (label == null) {
+      return null;
+    }
+    return BuildReferenceManager.getInstance(myElement.getProject()).resolveLabel(label);
+  }
+
+  @Nullable
+  private static Label getLabel(@Nullable String labelString) {
+    if (labelString == null || !labelString.startsWith("//") || labelString.indexOf('*') != -1) {
+      return null;
+    }
+    return LabelUtils.createLabelFromString(null, labelString);
+  }
+
+  @Override
+  public Object[] getVariants() {
+    String labelString = LabelUtils.trimToDummyIdentifier(myElement.getText());
+    return ArrayUtil.mergeArrays(getRuleLookups(labelString), getFileLookups(labelString));
+  }
+
+  private BuildLookupElement[] getRuleLookups(String labelString) {
+    if (!labelString.startsWith("//") || !labelString.contains(":")) {
+      return BuildLookupElement.EMPTY_ARRAY;
+    }
+    String packagePrefix = LabelUtils.getPackagePathComponent(labelString);
+    BuildFile referencedBuildFile =
+        BuildReferenceManager.getInstance(myElement.getProject())
+            .resolveBlazePackage(packagePrefix);
+    if (referencedBuildFile == null) {
+      return BuildLookupElement.EMPTY_ARRAY;
+    }
+    return LabelRuleLookupElement.collectAllRules(
+        referencedBuildFile, labelString, packagePrefix, null, QuoteType.NoQuotes);
+  }
+
+  private BuildLookupElement[] getFileLookups(String labelString) {
+    if (pathFormat == PathFormat.NonLocalWithoutInitialBackslashes) {
+      labelString = StringUtil.trimStart(labelString, "-");
+    }
+    FileLookupData lookupData =
+        FileLookupData.nonLocalFileLookup(labelString, null, QuoteType.NoQuotes, pathFormat);
+    if (lookupData == null) {
+      return BuildLookupElement.EMPTY_ARRAY;
+    }
+    return BuildReferenceManager.getInstance(myElement.getProject())
+        .resolvePackageLookupElements(lookupData);
+  }
+
+  @Override
+  public PsiElement bindToElement(PsiElement element) throws IncorrectOperationException {
+    return myElement;
+  }
+
+  @Override
+  public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException {
+    String currentString = myElement.getText();
+    Label label = getLabel(currentString);
+    if (label == null) {
+      return myElement;
+    }
+    String ruleName = label.ruleName().toString();
+    String newRuleName = newElementName;
+
+    // handle subdirectories
+    int lastSlashIndex = ruleName.lastIndexOf('/');
+    if (lastSlashIndex != -1) {
+      newRuleName = ruleName.substring(0, lastSlashIndex + 1) + newElementName;
+    }
+
+    String packageString = LabelUtils.getPackagePathComponent(currentString);
+    if (packageString.isEmpty() && !currentString.contains(":")) {
+      return handleRename(newRuleName);
+    }
+    return handleRename(packageString + ":" + newRuleName);
+  }
+
+  private PsiElement handleRename(String newStringContents) {
+    ASTNode replacement =
+        ProjectViewElementGenerator.createReplacementItemNode(myElement, newStringContents);
+    if (replacement != null) {
+      myElement.getNode().replaceAllChildrenToChildrenOf(replacement);
+    }
+    return myElement;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/metrics/Action.java b/base/src/com/google/idea/blaze/base/metrics/Action.java
new file mode 100644
index 0000000..9292cdc
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/metrics/Action.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.metrics;
+
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * An item that can be logged. All actions contain a name that is used as a primary key. The name
+ * should be immutable forever to keep the logs sane.
+ *
+ * <p>The name used by each {@link Action} should be [a-zA-Z0-9]* to keep things robust since
+ * various log back ends may have different rules about what may or may not be in a key.
+ *
+ * <p>Do not use any of the following retired values for enums: INDEX_TOTAL_TIME("index")
+ * REBUILD_TOTAL_TIME("rtt") SYNC_SAVE_FILES("ssf") SYNC_COMPUTE_MODULE_DIFF("scmd")
+ * RUN_TOTAL_TIME("ttrp") DEBUG_TOTAL_TIME("ttsbp") RUN_TOTAL_TIME_FOR_ANDROID_TEST("ttrpat")
+ * DEBUG_TOTAL_TIME_FOR_ANDROID_TEST("ttsbpat") IMPORT_TOTAL_TIME("tip")
+ * IDE_BUILD_INFO_RESPONSE("ibi") RULES_EXTRACTION("re") BLAZE_MODULES_CREATION("mvc")
+ * INTELLIJ_MODULE_CREATION("imc") SYNC_RESET_PROJECT("srp")
+ *
+ * <p>
+ */
+public enum Action {
+  MAKE_PROJECT_TOTAL_TIME("mtt"),
+  MAKE_MODULE_TOTAL_TIME("mmtt"),
+
+  SYNC_TOTAL_TIME("stt"),
+  SYNC_IMPORT_DATA_TIME("sidt"),
+  BLAZE_BUILD_DURING_SYNC("bb"),
+  BLAZE_BUILD("bld"),
+
+  APK_BUILD_AND_INSTALL("apkbi"),
+
+  BLAZE_COMMAND_USAGE("ttrpbc"),
+
+  OPEN_IN_CODESEARCH("oics"),
+  COPY_GOOGLE3_PATH("cg3p"),
+  OPEN_CORRESPONDING_BUILD_FILE("ocbf"),
+
+  CREATE_BLAZE_RULE("cbr"),
+  CREATE_BLAZE_PACKAGE("cbp"),
+
+  SYNC_SDK("ssdk"),
+
+  C_RESOLVE_FILE("crf"),
+  BLAZE_CLION_TEST_RUN("ctr"),
+  BLAZE_CLION_TEST_DEBUG("ctd");
+
+  @NotNull @NonNls private final String name;
+
+  Action(@NotNull String name) {
+    this.name = name;
+  }
+
+  @NotNull
+  public String getName() {
+    return name;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/metrics/LoggingService.java b/base/src/com/google/idea/blaze/base/metrics/LoggingService.java
new file mode 100644
index 0000000..47a03df
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/metrics/LoggingService.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.metrics;
+
+import com.intellij.openapi.extensions.ExtensionPointName;
+import com.intellij.openapi.project.Project;
+import javax.annotation.Nullable;
+
+/**
+ * Logging service that handles logging timing, hit, and other events to an external sink for later
+ * analysis.
+ */
+public interface LoggingService {
+
+  ExtensionPointName<LoggingService> EP_NAME =
+      ExtensionPointName.create("com.google.idea.blaze.LoggingService");
+
+  /**
+   * Report a value for an event to the available logging services.
+   *
+   * @param variable The variable to report to. Once a value is selected for a logical measurement,
+   *     the variable's name should never change, even if the colloquial name for the variable
+   *     changes.
+   */
+  static void reportEvent(Project project, Action variable) {
+    reportEvent(project, variable, 0);
+  }
+
+  /**
+   * Report a value for an event to the available logging services.
+   *
+   * @param variable The variable to report to. Once a value is selected for a logical measurement,
+   *     the variable's name should never change, even if the colloquial name for the variable
+   *     changes.
+   * @param value should be >= 0, set the value to 0 if the value is meaningless
+   */
+  static void reportEvent(Project project, Action variable, long value) {
+    for (LoggingService service : EP_NAME.getExtensions()) {
+      service.doReportEvent(project, variable, value);
+    }
+  }
+
+  /**
+   * Report a value for an event to the logging service
+   *
+   * @param variable The variable to report to. Once a value is selected for a logical measurement,
+   *     the variable's name should never change, even if the colloquial name for the variable
+   *     changes.
+   * @param value should be >= 0, set the value to 0 if the value is meaningless
+   */
+  void doReportEvent(@Nullable Project project, Action variable, long value);
+}
diff --git a/base/src/com/google/idea/blaze/base/model/BlazeProjectData.java b/base/src/com/google/idea/blaze/base/model/BlazeProjectData.java
new file mode 100644
index 0000000..d4e84f7
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/model/BlazeProjectData.java
@@ -0,0 +1,63 @@
+/*
+ * 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;
+
+import com.google.common.collect.ImmutableMultimap;
+import com.google.idea.blaze.base.model.primitives.Label;
+import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
+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 java.io.Serializable;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+/** The top-level object serialized to cache. */
+@Immutable
+public class BlazeProjectData implements Serializable {
+  private static final long serialVersionUID = 21L;
+
+  public final long syncTime;
+  public final RuleMap ruleMap;
+  public final BlazeRoots blazeRoots;
+  @Nullable public final WorkingSet workingSet;
+  public final WorkspacePathResolver workspacePathResolver;
+  public final WorkspaceLanguageSettings workspaceLanguageSettings;
+  public final SyncState syncState;
+  public final ImmutableMultimap<Label, Label> reverseDependencies;
+  @Nullable public final String vcsName;
+
+  public BlazeProjectData(
+      long syncTime,
+      RuleMap ruleMap,
+      BlazeRoots blazeRoots,
+      @Nullable WorkingSet workingSet,
+      WorkspacePathResolver workspacePathResolver,
+      WorkspaceLanguageSettings workspaceLangaugeSettings,
+      SyncState syncState,
+      ImmutableMultimap<Label, Label> reverseDependencies,
+      String vcsName) {
+    this.syncTime = syncTime;
+    this.ruleMap = ruleMap;
+    this.blazeRoots = blazeRoots;
+    this.workingSet = workingSet;
+    this.workspacePathResolver = workspacePathResolver;
+    this.workspaceLanguageSettings = workspaceLangaugeSettings;
+    this.syncState = syncState;
+    this.reverseDependencies = reverseDependencies;
+    this.vcsName = vcsName;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/model/BlazeWorkspaceType.java b/base/src/com/google/idea/blaze/base/model/BlazeWorkspaceType.java
new file mode 100644
index 0000000..d57270a
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/model/BlazeWorkspaceType.java
@@ -0,0 +1,22 @@
+/*
+ * 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/base/src/com/google/idea/blaze/base/model/RuleMap.java b/base/src/com/google/idea/blaze/base/model/RuleMap.java
new file mode 100644
index 0000000..0a46316
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/model/RuleMap.java
@@ -0,0 +1,49 @@
+/*
+ * 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;
+
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableMap;
+import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+import com.google.idea.blaze.base.model.primitives.Label;
+import java.io.Serializable;
+
+/** Map of configured targets (and soon aspects). */
+public class RuleMap implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  private final ImmutableMap<Label, RuleIdeInfo> ruleMap;
+
+  public RuleMap(ImmutableMap<Label, RuleIdeInfo> ruleMap) {
+    this.ruleMap = ruleMap;
+  }
+
+  public RuleIdeInfo get(Label label) {
+    return ruleMap.get(label);
+  }
+
+  public boolean contains(Label label) {
+    return ruleMap.containsKey(label);
+  }
+
+  public ImmutableCollection<RuleIdeInfo> rules() {
+    return ruleMap.values();
+  }
+
+  public ImmutableMap<Label, RuleIdeInfo> map() {
+    return ruleMap;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/model/SyncState.java b/base/src/com/google/idea/blaze/base/model/SyncState.java
new file mode 100644
index 0000000..ecdbc5e
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/model/SyncState.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.model;
+
+import com.google.common.collect.ImmutableMap;
+import java.io.Serializable;
+import java.util.Map;
+import javax.annotation.Nullable;
+
+/** Used to save arbitrary state with the sync task. */
+public class SyncState implements Serializable {
+  private static final long serialVersionUID = 1L;
+  private final ImmutableMap<String, Serializable> syncStateMap;
+
+  @SuppressWarnings("unchecked")
+  @Nullable
+  public <T extends Serializable> T get(Class<T> klass) {
+    return (T) syncStateMap.get(klass.getName());
+  }
+
+  /** Builder for a sync state */
+  public static class Builder {
+    ImmutableMap.Builder<Class, Serializable> syncStateMap = ImmutableMap.builder();
+
+    public <K extends Serializable, V extends K> Builder put(Class<K> klass, V instance) {
+      syncStateMap.put(klass, instance);
+      return this;
+    }
+
+    public SyncState build() {
+      return new SyncState(syncStateMap.build());
+    }
+  }
+
+  SyncState(ImmutableMap<Class, Serializable> syncStateMap) {
+    ImmutableMap.Builder<String, Serializable> extraProjectSyncStateMap = ImmutableMap.builder();
+    for (Map.Entry<Class, Serializable> entry : syncStateMap.entrySet()) {
+      extraProjectSyncStateMap.put(entry.getKey().getName(), entry.getValue());
+    }
+    this.syncStateMap = extraProjectSyncStateMap.build();
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/model/primitives/ExecutionRootPath.java b/base/src/com/google/idea/blaze/base/model/primitives/ExecutionRootPath.java
new file mode 100644
index 0000000..1a7f550
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/model/primitives/ExecutionRootPath.java
@@ -0,0 +1,144 @@
+/*
+ * 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 com.google.common.base.Objects;
+import com.intellij.openapi.util.io.FileUtil;
+import java.io.File;
+import java.io.Serializable;
+import javax.annotation.Nullable;
+
+/**
+ * An absolute or relative path returned from Blaze. If it is a relative path, it is relative to the
+ * execution root.
+ */
+public final class ExecutionRootPath implements Serializable {
+  public static final long serialVersionUID = 3L;
+
+  private final File path;
+
+  public ExecutionRootPath(String path) {
+    this.path = new File(path);
+  }
+
+  public ExecutionRootPath(File path) {
+    this.path = path;
+  }
+
+  public File getAbsoluteOrRelativeFile() {
+    return path;
+  }
+
+  public File getFileRootedAt(File absoluteRoot) {
+    if (path.isAbsolute()) {
+      return path;
+    }
+    return new File(absoluteRoot, path.getPath());
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    ExecutionRootPath that = (ExecutionRootPath) o;
+    return Objects.equal(path, that.path);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(path);
+  }
+
+  @Override
+  public String toString() {
+    return "ExecutionRootPath{" + "path='" + path + '\'' + '}';
+  }
+
+  /**
+   * Returns the relative {@link ExecutionRootPath} if {@code root} is an ancestor of {@code path}
+   * otherwise returns null.
+   */
+  @Nullable
+  public static ExecutionRootPath createAncestorRelativePath(File root, File path) {
+    // We cannot find the relative path between an absolute and relative path.
+    // The underlying code will make the relative path absolute
+    // by rooting it at the current working directory which is almost never what you want.
+    if (root.isAbsolute() != path.isAbsolute()) {
+      return null;
+    }
+    if (!isAncestor(root.getPath(), path.getPath(), false /* strict */)) {
+      return null;
+    }
+    String relativePath = FileUtil.getRelativePath(root, path);
+    if (relativePath == null) {
+      return null;
+    }
+    return new ExecutionRootPath(new File(relativePath));
+  }
+
+  /**
+   * @param possibleParent
+   * @param possibleChild
+   * @param strict if {@code false} then this method returns {@code true} if {@code possibleParent}
+   *     equals to {@code possibleChild}.
+   */
+  public static boolean isAncestor(
+      ExecutionRootPath possibleParent, ExecutionRootPath possibleChild, boolean strict) {
+    return isAncestor(
+        possibleParent.getAbsoluteOrRelativeFile().getPath(),
+        possibleChild.getAbsoluteOrRelativeFile().getPath(),
+        strict);
+  }
+
+  /**
+   * @param possibleParentPath
+   * @param possibleChild
+   * @param strict if {@code false} then this method returns {@code true} if {@code possibleParent}
+   *     equals to {@code possibleChild}.
+   */
+  public static boolean isAncestor(
+      String possibleParentPath, ExecutionRootPath possibleChild, boolean strict) {
+    return isAncestor(
+        possibleParentPath, possibleChild.getAbsoluteOrRelativeFile().getPath(), strict);
+  }
+
+  /**
+   * @param possibleParent
+   * @param possibleChildPath
+   * @param strict if {@code false} then this method returns {@code true} if {@code possibleParent}
+   *     equals to {@code possibleChild}.
+   */
+  public static boolean isAncestor(
+      ExecutionRootPath possibleParent, String possibleChildPath, boolean strict) {
+    return isAncestor(
+        possibleParent.getAbsoluteOrRelativeFile().getPath(), possibleChildPath, strict);
+  }
+
+  /**
+   * @param possibleParentPath
+   * @param possibleChildPath
+   * @param strict if {@code false} then this method returns {@code true} if {@code possibleParent}
+   *     equals to {@code possibleChild}.
+   */
+  public static boolean isAncestor(
+      String possibleParentPath, String possibleChildPath, boolean strict) {
+    return FileUtil.isAncestor(possibleParentPath, possibleChildPath, strict);
+  }
+}
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
new file mode 100644
index 0000000..96bf0ab
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/model/primitives/Kind.java
@@ -0,0 +1,93 @@
+/*
+ * 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 com.google.common.collect.ImmutableMap;
+import java.util.Arrays;
+import java.util.List;
+
+/** Wrapper around a string for a blaze kind (android_library, android_test...) */
+public enum Kind {
+  ANDROID_BINARY("android_binary", LanguageClass.ANDROID),
+  ANDROID_LIBRARY("android_library", LanguageClass.ANDROID),
+  ANDROID_TEST("android_test", LanguageClass.ANDROID),
+  ANDROID_ROBOLECTRIC_TEST("android_robolectric_test", LanguageClass.ANDROID),
+  JAVA_LIBRARY("java_library", LanguageClass.JAVA),
+  JAVA_TEST("java_test", LanguageClass.JAVA),
+  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
+  JAVA_PLUGIN("java_plugin", LanguageClass.JAVA),
+  ANDROID_RESOURCES("android_resources", LanguageClass.ANDROID),
+  CC_LIBRARY("cc_library", LanguageClass.C),
+  CC_BINARY("cc_binary", LanguageClass.C),
+  CC_TEST("cc_test", LanguageClass.C),
+  CC_INC_LIBRARY("cc_inc_library", LanguageClass.C),
+  CC_TOOLCHAIN("cc_toolchain", LanguageClass.C),
+  JAVA_WRAP_CC("java_wrap_cc", LanguageClass.JAVA),
+  GWT_APPLICATION("gwt_application", LanguageClass.JAVA),
+  GWT_HOST("gwt_host", LanguageClass.JAVA),
+  GWT_MODULE("gwt_module", LanguageClass.JAVA),
+  GWT_TEST("gwt_test", LanguageClass.JAVA),
+  ;
+
+  static final ImmutableMap<String, Kind> STRING_TO_KIND = makeStringToKindMap();
+
+  private static ImmutableMap<String, Kind> makeStringToKindMap() {
+    ImmutableMap.Builder<String, Kind> result = ImmutableMap.builder();
+    for (Kind kind : Kind.values()) {
+      result.put(kind.toString(), kind);
+    }
+    return result.build();
+  }
+
+  public static Kind fromString(String kindString) {
+    return STRING_TO_KIND.get(kindString);
+  }
+
+  private final String kind;
+  private final LanguageClass languageClass;
+
+  Kind(String kind, LanguageClass languageClass) {
+    this.kind = kind;
+    this.languageClass = languageClass;
+  }
+
+  @Override
+  public String toString() {
+    return kind;
+  }
+
+  public LanguageClass getLanguageClass() {
+    return languageClass;
+  }
+
+  public boolean isOneOf(Kind... kinds) {
+    return isOneOf(Arrays.asList(kinds));
+  }
+
+  public boolean isOneOf(List<Kind> kinds) {
+    for (Kind kind : kinds) {
+      if (this.equals(kind)) {
+        return true;
+      }
+    }
+    return false;
+  }
+}
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
new file mode 100644
index 0000000..532c347
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/model/primitives/Label.java
@@ -0,0 +1,150 @@
+/*
+ * 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 com.google.common.collect.Lists;
+import com.google.idea.blaze.base.ui.BlazeValidationError;
+import com.intellij.openapi.diagnostic.Logger;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.List;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+/** Wrapper around a string for a blaze label (//package:rule). */
+@Immutable
+public final class Label extends TargetExpression {
+  private static final Logger LOG = Logger.getInstance(Label.class);
+
+  public static final Comparator<Label> COMPARATOR =
+      (o1, o2) -> String.CASE_INSENSITIVE_ORDER.compare(o1.toString(), o2.toString());
+
+  public static final long serialVersionUID = 2L;
+
+  /** Silently returns null if this is not a valid Label */
+  @Nullable
+  public static Label createIfValid(String label) {
+    if (validate(label)) {
+      return new Label(label);
+    }
+    return null;
+  }
+
+  public Label(String label) {
+    super(label);
+    List<BlazeValidationError> errors = Lists.newArrayList();
+    if (!validate(label, errors)) {
+      BlazeValidationError.throwError(errors);
+    }
+  }
+
+  public Label(WorkspacePath packageName, RuleName newRuleName) {
+    this("//" + packageName.toString() + ":" + newRuleName.toString());
+  }
+
+  public static boolean validate(String label) {
+    return validate(label, null);
+  }
+
+  public static boolean validate(String label, @Nullable Collection<BlazeValidationError> errors) {
+    int colonIndex = label.indexOf(':');
+    if (label.startsWith("//") && colonIndex >= 0) {
+      String packageName = label.substring("//".length(), colonIndex);
+      if (!validatePackagePath(packageName, errors)) {
+        return false;
+      }
+      String ruleName = label.substring(colonIndex + 1);
+      if (!RuleName.validate(ruleName, errors)) {
+        return false;
+      }
+      return true;
+    }
+    if (label.startsWith("@") && colonIndex >= 0) {
+      // a bazel-specific label pointing to a different repository
+      int slashIndex = label.indexOf("//");
+      if (slashIndex >= 0) {
+        return validate(label.substring(slashIndex), errors);
+      }
+    }
+    if (errors != null) {
+      errors.add(new BlazeValidationError("Not a valid label, no target name found: " + label));
+    }
+    return false;
+  }
+
+  /**
+   * Extract the rule name from a label. The rule name follows a colon at the end of the label.
+   *
+   * @return the rule name
+   */
+  public RuleName ruleName() {
+    String labelStr = toString();
+    int colonLocation = labelStr.lastIndexOf(':');
+    int ruleNameStart = colonLocation + 1;
+    String ruleNameStr = labelStr.substring(ruleNameStart);
+    return RuleName.create(ruleNameStr);
+  }
+
+  /**
+   * Return the workspace path for the package label for the given label. For example, if the
+   * package is //j/c/g/a/apps/docs:release, it returns j/c/g/a/apps/docs.
+   */
+  public WorkspacePath blazePackage() {
+    String labelStr = toString();
+    int startIndex = labelStr.indexOf("//") + "//".length();
+    int colonIndex = labelStr.lastIndexOf(':');
+    LOG.assertTrue(colonIndex >= 0);
+    return new WorkspacePath(labelStr.substring(startIndex, colonIndex));
+  }
+
+  public static boolean validatePackagePath(String path) {
+    return validatePackagePath(path, null);
+  }
+
+  public static boolean validatePackagePath(
+      String path, @Nullable Collection<BlazeValidationError> errors) {
+    // Empty packages are legal but not recommended
+    if (path.isEmpty()) {
+      return true;
+    }
+
+    if (path.charAt(0) == '/') {
+      BlazeValidationError.collect(
+          errors,
+          new BlazeValidationError(
+              "Invalid package name: " + path + "\n" + "Package names may not start with \"/\"."));
+      return false;
+    }
+    if (path.contains("//")) {
+      BlazeValidationError.collect(
+          errors,
+          new BlazeValidationError(
+              "Invalid package name: "
+                  + path
+                  + "\n "
+                  + "package names may not contain \"//\" path separators."));
+      return false;
+    }
+    if (path.endsWith("/")) {
+      BlazeValidationError.collect(
+          errors,
+          new BlazeValidationError(
+              "Invalid package name: " + path + "\n " + "package names may not end with \"/\""));
+      return false;
+    }
+    return true;
+  }
+}
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
new file mode 100644
index 0000000..4ea1df8
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/model/primitives/LanguageClass.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.base.model.primitives;
+
+/** Language classes. */
+public enum LanguageClass {
+  GENERIC("generic"),
+  C("c"),
+  JAVA("java"),
+  ANDROID("android"),
+  JAVASCRIPT("javascript"),
+  TYPESCRIPT("typescript"),
+  DART("dart");
+
+  private final String name;
+
+  LanguageClass(String name) {
+    this.name = name;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public static LanguageClass fromString(String name) {
+    for (LanguageClass ruleClass : LanguageClass.values()) {
+      if (ruleClass.name.equals(name)) {
+        return ruleClass;
+      }
+    }
+    return null;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/model/primitives/RuleName.java b/base/src/com/google/idea/blaze/base/model/primitives/RuleName.java
new file mode 100644
index 0000000..b535a2c
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/model/primitives/RuleName.java
@@ -0,0 +1,196 @@
+/*
+ * 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 com.google.common.base.Objects;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.ui.BlazeValidationError;
+import java.util.Collection;
+import java.util.List;
+import java.util.regex.Pattern;
+import javax.annotation.Nullable;
+
+/** The rule name part of a label */
+public final class RuleName {
+
+  // This is a subset of the allowable target names in Blaze
+  private static final String ALNUM_REGEX_STR = "[a-zA-Z0-9]*";
+  private static final Pattern ALNUM_REGEX = Pattern.compile(ALNUM_REGEX_STR);
+
+  // Rule names must be alpha-numeric or consist of the following allowed chars:
+  // (note, rule names can also contain '/'; we handle that case separately)
+  private static final ImmutableSet<Character> ALLOWED_META =
+      ImmutableSet.of('+', '_', ',', '=', '-', '.', '@', '~');
+
+  private final String name;
+
+  private RuleName(String ruleName) {
+    this.name = ruleName;
+  }
+
+  /** Silently returns null if the string is not a valid rule name. */
+  @Nullable
+  public static RuleName createIfValid(String ruleName) {
+    if (validate(ruleName, null)) {
+      return new RuleName(ruleName);
+    }
+    return null;
+  }
+
+  public static RuleName create(String ruleName) {
+    List<BlazeValidationError> errors = Lists.newArrayList();
+    if (!validate(ruleName, errors)) {
+      BlazeValidationError.throwError(errors);
+    }
+    return new RuleName(ruleName);
+  }
+
+  /** Validates a rule name using the same logic as Blaze */
+  public static boolean validate(String ruleName) {
+    return validate(ruleName, null);
+  }
+
+  /** Validates a rule name using the same logic as Blaze */
+  public static boolean validate(
+      String ruleName, @Nullable Collection<BlazeValidationError> errors) {
+    if (ruleName.isEmpty()) {
+      BlazeValidationError.collect(
+          errors, new BlazeValidationError("target names cannot be empty"));
+      return false;
+    }
+    // Forbidden start chars:
+    if (ruleName.charAt(0) == '/') {
+      BlazeValidationError.collect(
+          errors,
+          new BlazeValidationError(
+              "Invalid target name: " + ruleName + "\n" + "target names may not start with \"/\""));
+      return false;
+    } else if (ruleName.charAt(0) == '.') {
+      if (ruleName.startsWith("../") || ruleName.equals("..")) {
+        BlazeValidationError.collect(
+            errors,
+            new BlazeValidationError(
+                "Invalid target name: "
+                    + ruleName
+                    + "\n"
+                    + "target names may not contain up-level references \"..\""));
+        return false;
+      } else if (ruleName.equals(".")) {
+        return true;
+      } else if (ruleName.startsWith("./")) {
+        BlazeValidationError.collect(
+            errors,
+            new BlazeValidationError(
+                "Invalid target name: "
+                    + ruleName
+                    + "\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);
+      if (ALLOWED_META.contains(c)) {
+        continue;
+      }
+      if (c == '/') {
+        // Forbidden substrings: "/../", "/./", "//"
+        if (ruleName.contains("/../")) {
+          BlazeValidationError.collect(
+              errors,
+              new BlazeValidationError(
+                  "Invalid target name: "
+                      + ruleName
+                      + "\n"
+                      + "target names may not contain up-level references \"..\""));
+          return false;
+        } else if (ruleName.contains("/./")) {
+          BlazeValidationError.collect(
+              errors,
+              new BlazeValidationError(
+                  "Invalid target name: "
+                      + ruleName
+                      + "\n"
+                      + "target names may not contain \".\" as a path segment"));
+          return false;
+        } else if (ruleName.contains("//")) {
+          BlazeValidationError.collect(
+              errors,
+              new BlazeValidationError(
+                  "Invalid target name: "
+                      + ruleName
+                      + "\n"
+                      + "target names may not contain \"//\" path separators"));
+          return false;
+        }
+        continue;
+      }
+      boolean isAlnum = ALNUM_REGEX.matcher(String.valueOf(c)).matches();
+      if (!isAlnum) {
+        BlazeValidationError.collect(
+            errors,
+            new BlazeValidationError(
+                "Invalid target name: " + ruleName + "\n" + "target names may not contain " + c));
+        return false;
+      }
+    }
+
+    // Forbidden end chars:
+    if (ruleName.endsWith("/..")) {
+      BlazeValidationError.collect(
+          errors,
+          new BlazeValidationError(
+              "Invalid target name: "
+                  + ruleName
+                  + "\n"
+                  + "target names may not contain up-level references \"..\""));
+      return false;
+    } else if (ruleName.endsWith("/.")) {
+      return true;
+    } else if (ruleName.endsWith("/")) {
+      BlazeValidationError.collect(
+          errors,
+          new BlazeValidationError(
+              "Invalid target name: " + ruleName + "\n" + "target names may not end with \"/\""));
+      return false;
+    }
+    return true;
+  }
+
+  @Override
+  public String toString() {
+    return name;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(name);
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (obj instanceof RuleName) {
+      RuleName that = (RuleName) obj;
+      return Objects.equal(name, that.name);
+    }
+    return false;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/model/primitives/TargetExpression.java b/base/src/com/google/idea/blaze/base/model/primitives/TargetExpression.java
new file mode 100644
index 0000000..ea3d847
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/model/primitives/TargetExpression.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.model.primitives;
+
+import com.google.common.base.Preconditions;
+import java.io.Serializable;
+
+/**
+ * An interface for objects that represent targets you could pass to Blaze on the command line. See
+ * {@link com.google.idea.blaze.base.model.primitives.Label},
+ */
+public class TargetExpression implements Serializable, Comparable<TargetExpression> {
+  public static final long serialVersionUID = 1L;
+
+  private final String expression;
+
+  /**
+   * @return A Label instance if the expression is a valid label, or a TargetExpression instance if
+   *     it is not.
+   */
+  public static TargetExpression fromString(String expression) {
+    return Label.validate(expression) ? new Label(expression) : new TargetExpression(expression);
+  }
+
+  TargetExpression(String expression) {
+    // TODO(joshgiles): Validation/canonicalization for target expressions.
+    // For reference, handled in Blaze/Bazel in TargetPattern.java.
+    Preconditions.checkArgument(!expression.isEmpty(), "Target should be non-empty.");
+    this.expression = expression;
+  }
+
+  @Override
+  public String toString() {
+    return expression;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (!(o instanceof TargetExpression)) {
+      return false;
+    }
+    TargetExpression that = (TargetExpression) o;
+    return expression.equals(that.expression);
+  }
+
+  @Override
+  public int hashCode() {
+    return expression.hashCode();
+  }
+
+  /** All targets in all packages below the given path */
+  public static TargetExpression allFromPackageRecursive(WorkspacePath localPackage) {
+    if (localPackage.relativePath().isEmpty()) {
+      // localPackage is the workspace root
+      return new TargetExpression("//...:all");
+    }
+    return new TargetExpression("//" + localPackage.relativePath() + "/...:all");
+  }
+
+  public static TargetExpression allFromPackageNonRecursive(WorkspacePath localPackage) {
+    return new TargetExpression("//" + localPackage.relativePath() + ":all");
+  }
+
+  @Override
+  public int compareTo(TargetExpression o) {
+    return expression.compareTo(o.expression);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/model/primitives/WorkspacePath.java b/base/src/com/google/idea/blaze/base/model/primitives/WorkspacePath.java
new file mode 100644
index 0000000..3bb2ccd
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/model/primitives/WorkspacePath.java
@@ -0,0 +1,122 @@
+/*
+ * 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 com.google.idea.blaze.base.ui.BlazeValidationError;
+import java.io.Serializable;
+import java.util.Collection;
+import javax.annotation.concurrent.Immutable;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Represents a path relative to the workspace root. The path component separator is Blaze specific.
+ *
+ * <p>A {@link WorkspacePath} is *not* necessarily a valid package name/path. The primary reason is
+ * because it could represent a file and files don't have to follow the same conventions as package
+ * names.
+ */
+@Immutable
+public class WorkspacePath implements Serializable {
+  public static final long serialVersionUID = 1L;
+
+  /** Silently returns null if this is not a valid workspace path. */
+  @Nullable
+  public static WorkspacePath createIfValid(String relativePath) {
+    if (validate(relativePath)) {
+      return new WorkspacePath(relativePath);
+    }
+    return null;
+  }
+
+  private static final char BLAZE_COMPONENT_SEPARATOR = '/';
+
+  @NotNull private final String relativePath;
+
+  /**
+   * @param relativePath relative path that must use the Blaze specific separator char to separate
+   *     path components
+   */
+  public WorkspacePath(@NotNull String relativePath) {
+    if (!validate(relativePath)) {
+      throw new IllegalArgumentException("Invalid workspace path: " + relativePath);
+    }
+    this.relativePath = relativePath;
+  }
+
+  public WorkspacePath(@NotNull WorkspacePath parentPath, @NotNull String childPath) {
+    this(parentPath.relativePath() + BLAZE_COMPONENT_SEPARATOR + childPath);
+  }
+
+  public static boolean validate(@NotNull String relativePath) {
+    return validate(relativePath, null);
+  }
+
+  public static boolean validate(
+      @NotNull String relativePath, @Nullable Collection<BlazeValidationError> errors) {
+    if (relativePath.startsWith("/")) {
+      BlazeValidationError.collect(
+          errors,
+          new BlazeValidationError("Workspace path may not start with '/': " + relativePath));
+      return false;
+    }
+
+    if (relativePath.endsWith("/")) {
+      BlazeValidationError.collect(
+          errors, new BlazeValidationError("Workspace path may not end with '/': " + relativePath));
+      return false;
+    }
+
+    if (relativePath.indexOf(':') >= 0) {
+      BlazeValidationError.collect(
+          errors, new BlazeValidationError("Workspace path may not contain ':': " + relativePath));
+      return false;
+    }
+
+    return true;
+  }
+
+  public boolean isWorkspaceRoot() {
+    return relativePath.isEmpty();
+  }
+
+  @Override
+  public String toString() {
+    return relativePath;
+  }
+
+  public String relativePath() {
+    return relativePath;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || this.getClass() != o.getClass()) {
+      return false;
+    }
+
+    WorkspacePath that = (WorkspacePath) o;
+    return relativePath.equals(that.relativePath);
+  }
+
+  @Override
+  public int hashCode() {
+    return relativePath.hashCode();
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/model/primitives/WorkspaceRoot.java b/base/src/com/google/idea/blaze/base/model/primitives/WorkspaceRoot.java
new file mode 100644
index 0000000..b4a0a13
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/model/primitives/WorkspaceRoot.java
@@ -0,0 +1,145 @@
+/*
+ * 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 com.google.idea.blaze.base.settings.Blaze;
+import com.google.idea.blaze.base.settings.BlazeImportSettings;
+import com.google.idea.blaze.base.settings.BlazeImportSettingsManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import java.io.File;
+import java.io.Serializable;
+import javax.annotation.Nullable;
+
+/** Represents a workspace root */
+public class WorkspaceRoot implements Serializable {
+  public static final long serialVersionUID = 1L;
+
+  private final File directory;
+
+  public WorkspaceRoot(File directory) {
+    this.directory = directory;
+  }
+
+  /**
+   * Get the workspace root for a project
+   *
+   * @param blazeSettings settings for the project in question
+   * @return the path to workspace root that is used for the project
+   */
+  public static WorkspaceRoot fromImportSettings(BlazeImportSettings blazeSettings) {
+    return new WorkspaceRoot(new File(blazeSettings.getWorkspaceRoot()));
+  }
+
+  /**
+   * Tries to load the import settings for the given project and get the workspace root directory.
+   * <br>
+   * Unlike {@link #fromProject}, it will silently return null if this is not a blaze project.
+   */
+  @Nullable
+  public static WorkspaceRoot fromProjectSafe(Project project) {
+    if (Blaze.isBlazeProject(project)) {
+      return fromProject(project);
+    }
+    return null;
+  }
+
+  /**
+   * Tries to load the import settings for the given project and get the workspace root directory.
+   */
+  public static WorkspaceRoot fromProject(Project project) {
+    BlazeImportSettings importSettings =
+        BlazeImportSettingsManager.getInstance(project).getImportSettings();
+    if (importSettings == null) {
+      throw new IllegalStateException("null BlazeImportSettings.");
+    }
+    return fromImportSettings(importSettings);
+  }
+
+  public File fileForPath(WorkspacePath workspacePath) {
+    return new File(directory, workspacePath.relativePath());
+  }
+
+  public File directory() {
+    return directory;
+  }
+
+  public WorkspacePath workspacePathFor(VirtualFile file) {
+    return workspacePathFor(file.getPath());
+  }
+
+  public boolean isInWorkspace(VirtualFile file) {
+    return isInWorkspace(file.getPath());
+  }
+
+  /**
+   * Returns the WorkspacePath for the given absolute file, if it's a child of this WorkspaceRoot.
+   * Otherwise returns null.
+   */
+  @Nullable
+  public WorkspacePath workspacePathForSafe(File absoluteFile) {
+    if (isInWorkspace(absoluteFile)) {
+      return workspacePathFor(absoluteFile);
+    }
+    return null;
+  }
+
+  public WorkspacePath workspacePathFor(File file) {
+    return workspacePathFor(file.getPath());
+  }
+
+  public boolean isInWorkspace(File file) {
+    return isInWorkspace(file.getPath());
+  }
+
+  private WorkspacePath workspacePathFor(String path) {
+    if (!isInWorkspace(path)) {
+      throw new IllegalArgumentException("File is not under this workspace");
+    }
+    if (directory.getPath().length() == path.length()) {
+      return new WorkspacePath("");
+    }
+    return new WorkspacePath(path.substring(directory.getPath().length() + 1));
+  }
+
+  private boolean isInWorkspace(String path) {
+    return FileUtil.isAncestor(directory.getPath(), path, false);
+  }
+
+  @Override
+  public String toString() {
+    return directory.toString();
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+
+    WorkspaceRoot that = (WorkspaceRoot) o;
+    return directory.equals(that.directory);
+  }
+
+  @Override
+  public int hashCode() {
+    return directory.hashCode();
+  }
+}
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
new file mode 100644
index 0000000..1e69fce
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/model/primitives/WorkspaceType.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.base.model.primitives;
+
+/**
+ * Workspace types.
+ *
+ * <p>If the user doesn't specify a workspace, she gets the highest supported workspace type by 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");
+
+  private final String name;
+  private final LanguageClass[] languages;
+
+  WorkspaceType(String name, LanguageClass... languages) {
+    this.name = name;
+    this.languages = languages;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public LanguageClass[] getLanguages() {
+    return languages;
+  }
+
+  public static WorkspaceType fromString(String name) {
+    for (WorkspaceType ruleClass : WorkspaceType.values()) {
+      if (ruleClass.name.equals(name)) {
+        return ruleClass;
+      }
+    }
+    return null;
+  }
+
+  @Override
+  public String toString() {
+    return name;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/plugin/BlazeActionRemover.java b/base/src/com/google/idea/blaze/base/plugin/BlazeActionRemover.java
new file mode 100644
index 0000000..17e6043
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/plugin/BlazeActionRemover.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.plugin;
+
+import com.google.idea.blaze.base.settings.Blaze;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.Presentation;
+import com.intellij.openapi.project.Project;
+
+/** Wraps an action and makes it invisible for blaze-based projects. */
+public class BlazeActionRemover extends AnAction {
+
+  public static void hideAction(String actionId) {
+    AnAction oldAction = ActionManager.getInstance().getAction(actionId);
+    if (oldAction != null) {
+      replaceAction(actionId, new BlazeActionRemover(oldAction));
+    }
+  }
+
+  private static void replaceAction(String actionId, AnAction newAction) {
+    ActionManager actionManager = ActionManager.getInstance();
+    AnAction oldAction = actionManager.getAction(actionId);
+    if (oldAction != null) {
+      newAction.getTemplatePresentation().setIcon(oldAction.getTemplatePresentation().getIcon());
+      actionManager.unregisterAction(actionId);
+    }
+    actionManager.registerAction(actionId, newAction);
+  }
+
+  private final AnAction delegate;
+
+  private BlazeActionRemover(AnAction delegate) {
+    super(
+        delegate.getTemplatePresentation().getTextWithMnemonic(),
+        delegate.getTemplatePresentation().getDescription(),
+        delegate.getTemplatePresentation().getIcon());
+    this.delegate = delegate;
+  }
+
+  @Override
+  public void actionPerformed(AnActionEvent e) {
+    delegate.actionPerformed(e);
+  }
+
+  @Override
+  public void update(AnActionEvent e) {
+    Presentation presentation = e.getPresentation();
+    Project project = e.getProject();
+    if (project != null && Blaze.isBlazeProject(project)) {
+      presentation.setEnabledAndVisible(false);
+      return;
+    }
+    delegate.update(e);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/plugin/BlazeBinaryFileType.java b/base/src/com/google/idea/blaze/base/plugin/BlazeBinaryFileType.java
new file mode 100644
index 0000000..114117d
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/plugin/BlazeBinaryFileType.java
@@ -0,0 +1,30 @@
+/*
+ * 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.plugin;
+
+import com.intellij.openapi.fileTypes.UserBinaryFileType;
+
+/** Marker type to mark something as binary. */
+public class BlazeBinaryFileType extends UserBinaryFileType {
+  public static final BlazeBinaryFileType INSTANCE;
+
+  static {
+    INSTANCE = new BlazeBinaryFileType();
+    INSTANCE.setName("Binary File");
+    INSTANCE.setDescription(
+        "The blaze plugin has guessed this file type as binary for performance.");
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/plugin/BlazeFileTypeFactory.java b/base/src/com/google/idea/blaze/base/plugin/BlazeFileTypeFactory.java
new file mode 100644
index 0000000..0254c89
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/plugin/BlazeFileTypeFactory.java
@@ -0,0 +1,29 @@
+/*
+ * 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.plugin;
+
+import com.intellij.ide.highlighter.ArchiveFileType;
+import com.intellij.openapi.fileTypes.FileTypeConsumer;
+import com.intellij.openapi.fileTypes.FileTypeFactory;
+import org.jetbrains.annotations.NotNull;
+
+/** @author chuckj */
+public class BlazeFileTypeFactory extends FileTypeFactory {
+  @Override
+  public void createFileTypes(@NotNull final FileTypeConsumer consumer) {
+    consumer.consume(ArchiveFileType.INSTANCE, "srcjar");
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/plugin/BlazePluginId.java b/base/src/com/google/idea/blaze/base/plugin/BlazePluginId.java
new file mode 100644
index 0000000..64fbb90
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/plugin/BlazePluginId.java
@@ -0,0 +1,29 @@
+/*
+ * 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.plugin;
+
+import com.intellij.openapi.components.ServiceManager;
+
+/** Supplies the ID of the sync plugin. */
+public interface BlazePluginId {
+
+  static BlazePluginId getInstance() {
+    return ServiceManager.getService(BlazePluginId.class);
+  }
+
+  /** @return the plugin ID (same as in the plugin.xml). */
+  String getPluginId();
+}
diff --git a/base/src/com/google/idea/blaze/base/plugin/BlazeSpecificInitializer.java b/base/src/com/google/idea/blaze/base/plugin/BlazeSpecificInitializer.java
new file mode 100644
index 0000000..d399f97
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/plugin/BlazeSpecificInitializer.java
@@ -0,0 +1,43 @@
+/*
+ * 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.plugin;
+
+import com.intellij.openapi.actionSystem.IdeActions;
+import com.intellij.openapi.components.ApplicationComponent;
+
+/** Runs on startup. */
+public class BlazeSpecificInitializer extends ApplicationComponent.Adapter {
+
+  @Override
+  public void initComponent() {
+    hideMakeActions();
+  }
+
+  // The original actions will be visible only on plain IDEA projects.
+  private static void hideMakeActions() {
+    // 'Build' > 'Make Project' action
+    BlazeActionRemover.hideAction("CompileDirty");
+
+    // 'Build' > 'Make Modules' action
+    BlazeActionRemover.hideAction(IdeActions.ACTION_MAKE_MODULE);
+
+    // 'Build' > 'Rebuild' action
+    BlazeActionRemover.hideAction(IdeActions.ACTION_COMPILE_PROJECT);
+
+    // 'Build' > 'Compile Modules' action
+    BlazeActionRemover.hideAction(IdeActions.ACTION_COMPILE);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/plugin/Version.java b/base/src/com/google/idea/blaze/base/plugin/Version.java
new file mode 100644
index 0000000..dc86694
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/plugin/Version.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.base.plugin;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.intellij.ide.plugins.IdeaPluginDescriptor;
+import com.intellij.ide.plugins.PluginManager;
+import com.intellij.openapi.extensions.PluginId;
+
+/** Blaze sync plugin ID and version information. */
+public class Version {
+
+  /** The plugin ID and version from plugin.xml */
+  public static class PluginInfo {
+    public final String id;
+    public final String version;
+
+    @VisibleForTesting
+    public static final PluginInfo UNKNOWN = new PluginInfo("UNKNOWN_PLUGIN", "UNKNOWN_VERSION");
+
+    public PluginInfo(String id, String version) {
+      this.id = id;
+      this.version = version;
+    }
+  }
+
+  public static PluginInfo getSyncPluginInfo() {
+    BlazePluginId idService = BlazePluginId.getInstance();
+    if (idService != null) {
+      PluginId pluginId = PluginId.getId(idService.getPluginId());
+      IdeaPluginDescriptor pluginInfo = PluginManager.getPlugin(pluginId);
+      if (pluginInfo != null) {
+        return new PluginInfo(pluginId.getIdString(), pluginInfo.getVersion());
+      }
+    }
+    return PluginInfo.UNKNOWN;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/plugin/dependency/PluginDependencyHelper.java b/base/src/com/google/idea/blaze/base/plugin/dependency/PluginDependencyHelper.java
new file mode 100644
index 0000000..4ab0bed
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/plugin/dependency/PluginDependencyHelper.java
@@ -0,0 +1,86 @@
+/*
+ * 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.plugin.dependency;
+
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.plugin.BlazePluginId;
+import com.intellij.externalDependencies.DependencyOnPlugin;
+import com.intellij.externalDependencies.ExternalDependenciesManager;
+import com.intellij.externalDependencies.ProjectExternalDependency;
+import com.intellij.openapi.project.Project;
+import java.util.Iterator;
+import java.util.List;
+
+/** Helper class to add plugin dependencies to the project */
+public class PluginDependencyHelper {
+
+  public static void addDependencyOnSyncPlugin(Project blazeProject) {
+    BlazePluginId idService = BlazePluginId.getInstance();
+    if (idService != null) {
+      addDependency(
+          blazeProject, new DependencyOnPlugin(idService.getPluginId(), null, null, null));
+    }
+  }
+
+  /**
+   * Removes a project depedency on a given plugin, if one exists. Doesn't trigger any update
+   * checking. This is to handle migration of the IntelliJ-with-Bazel plugin to a different plugin
+   * ID. This is introduced in v1.9, remove in v2.2+
+   */
+  @Deprecated
+  public static void removeDependencyOnOldPlugin(Project project, String pluginId) {
+    ExternalDependenciesManager manager = ExternalDependenciesManager.getInstance(project);
+    List<ProjectExternalDependency> deps = Lists.newArrayList(manager.getAllDependencies());
+    Iterator<ProjectExternalDependency> iter = deps.iterator();
+    while (iter.hasNext()) {
+      ProjectExternalDependency dep = iter.next();
+      if (!(dep instanceof DependencyOnPlugin)) {
+        continue;
+      }
+      DependencyOnPlugin pluginDep = (DependencyOnPlugin) dep;
+      if (pluginDep.getPluginId().equals(pluginId)) {
+        iter.remove();
+      }
+    }
+    manager.setAllDependencies(deps);
+  }
+
+  /**
+   * Adds dependency, or replaces existing dependency of same type. Doesn't trigger any update
+   * checking
+   */
+  private static void addDependency(Project project, DependencyOnPlugin newDep) {
+
+    ExternalDependenciesManager manager = ExternalDependenciesManager.getInstance(project);
+    List<ProjectExternalDependency> deps = Lists.newArrayList(manager.getAllDependencies());
+    boolean added = false;
+    for (int i = 0; i < deps.size(); i++) {
+      ProjectExternalDependency dep = deps.get(i);
+      if (!(dep instanceof DependencyOnPlugin)) {
+        continue;
+      }
+      DependencyOnPlugin pluginDep = (DependencyOnPlugin) dep;
+      if (pluginDep.getPluginId().equals(newDep.getPluginId())) {
+        added = true;
+        deps.set(i, newDep);
+      }
+    }
+    if (!added) {
+      deps.add(newDep);
+    }
+    manager.setAllDependencies(deps);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/plugin/dependency/ProjectDependencyMigration.java b/base/src/com/google/idea/blaze/base/plugin/dependency/ProjectDependencyMigration.java
new file mode 100644
index 0000000..6f3be8b
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/plugin/dependency/ProjectDependencyMigration.java
@@ -0,0 +1,43 @@
+/*
+ * 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.plugin.dependency;
+
+import com.google.idea.blaze.base.settings.Blaze;
+import com.intellij.openapi.components.ApplicationComponent;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.ProjectManager;
+import com.intellij.openapi.project.ProjectManagerAdapter;
+
+/**
+ * Temporary migration code. Listens for blaze projects opening and closing, and adds required
+ * plugin dependencies
+ */
+public class ProjectDependencyMigration extends ApplicationComponent.Adapter {
+
+  @Override
+  public void initComponent() {
+    ProjectManager projectManager = ProjectManager.getInstance();
+    projectManager.addProjectManagerListener(
+        new ProjectManagerAdapter() {
+          @Override
+          public void projectOpened(Project project) {
+            if (Blaze.isBlazeProject(project)) {
+              PluginDependencyHelper.addDependencyOnSyncPlugin(project);
+            }
+          }
+        });
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/prefetch/FetchExecutor.java b/base/src/com/google/idea/blaze/base/prefetch/FetchExecutor.java
new file mode 100644
index 0000000..c89cc4f
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/prefetch/FetchExecutor.java
@@ -0,0 +1,29 @@
+/*
+ * 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.prefetch;
+
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.intellij.util.concurrency.BoundedTaskExecutor;
+import java.util.concurrent.Executors;
+
+/** Shared executors for any prefetch/copy operations. */
+public class FetchExecutor {
+  private static final int THREAD_COUNT = 32;
+  public static final ListeningExecutorService EXECUTOR =
+      MoreExecutors.listeningDecorator(
+          new BoundedTaskExecutor(Executors.newFixedThreadPool(THREAD_COUNT), THREAD_COUNT));
+}
diff --git a/base/src/com/google/idea/blaze/base/prefetch/PrefetchFileSource.java b/base/src/com/google/idea/blaze/base/prefetch/PrefetchFileSource.java
new file mode 100644
index 0000000..dcdc666
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/prefetch/PrefetchFileSource.java
@@ -0,0 +1,35 @@
+/*
+ * 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.prefetch;
+
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.intellij.openapi.extensions.ExtensionPointName;
+import com.intellij.openapi.project.Project;
+import java.io.File;
+import java.util.Collection;
+import java.util.Set;
+
+/** Provides a source of files to prefetch */
+public interface PrefetchFileSource {
+  ExtensionPointName<PrefetchFileSource> EP_NAME =
+      ExtensionPointName.create("com.google.idea.blaze.PrefetchFileSource");
+  /** Adds any files or directories that we would be interested in prefetching. */
+  void addFilesToPrefetch(
+      Project project, BlazeProjectData blazeProjectData, Collection<File> files);
+
+  /** Returns any source file extensions that are a good candidate for the {@link Prefetcher}. */
+  Set<String> prefetchSrcFileExtensions();
+}
diff --git a/base/src/com/google/idea/blaze/base/prefetch/PrefetchService.java b/base/src/com/google/idea/blaze/base/prefetch/PrefetchService.java
new file mode 100644
index 0000000..fc5f5e2
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/prefetch/PrefetchService.java
@@ -0,0 +1,35 @@
+/*
+ * 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.prefetch;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.project.Project;
+import java.io.File;
+import java.util.Collection;
+
+/** Interface to request prefetching of files */
+public interface PrefetchService {
+  static PrefetchService getInstance() {
+    return ServiceManager.getService(PrefetchService.class);
+  }
+
+  /** Instructs all prefetchers to prefetch these files. */
+  ListenableFuture<?> prefetchFiles(Project project, Collection<File> files);
+
+  ListenableFuture<?> prefetchProjectFiles(Project project, BlazeProjectData blazeProjectData);
+}
diff --git a/base/src/com/google/idea/blaze/base/prefetch/PrefetchServiceImpl.java b/base/src/com/google/idea/blaze/base/prefetch/PrefetchServiceImpl.java
new file mode 100644
index 0000000..8046db0
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/prefetch/PrefetchServiceImpl.java
@@ -0,0 +1,75 @@
+/*
+ * 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.prefetch;
+
+import com.google.common.collect.Lists;
+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.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.projectview.ProjectViewManager;
+import com.google.idea.blaze.base.projectview.ProjectViewSet;
+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.intellij.openapi.project.Project;
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+/** Implementation for prefetcher. */
+public class PrefetchServiceImpl implements PrefetchService {
+
+  @Override
+  public ListenableFuture<?> prefetchFiles(Project project, Collection<File> files) {
+    List<ListenableFuture<?>> futures = Lists.newArrayList();
+    for (Prefetcher prefetcher : Prefetcher.EP_NAME.getExtensions()) {
+      futures.add(prefetcher.prefetchFiles(project, files, FetchExecutor.EXECUTOR));
+    }
+    return Futures.allAsList(futures);
+  }
+
+  @Override
+  public ListenableFuture<?> prefetchProjectFiles(
+      Project project, BlazeProjectData blazeProjectData) {
+    ProjectViewSet projectViewSet = ProjectViewManager.getInstance(project).getProjectViewSet();
+    if (projectViewSet == null) {
+      return Futures.immediateFuture(null);
+    }
+    BlazeImportSettings importSettings =
+        BlazeImportSettingsManager.getInstance(project).getImportSettings();
+    if (importSettings == null) {
+      return Futures.immediateFuture(null);
+    }
+    WorkspaceRoot workspaceRoot = WorkspaceRoot.fromImportSettings(importSettings);
+    ImportRoots importRoots =
+        ImportRoots.builder(workspaceRoot, importSettings.getBuildSystem())
+            .add(projectViewSet)
+            .build();
+
+    Set<File> files = Sets.newHashSet();
+    for (WorkspacePath workspacePath : importRoots.rootDirectories()) {
+      files.add(workspaceRoot.fileForPath(workspacePath));
+    }
+    for (PrefetchFileSource fileSource : PrefetchFileSource.EP_NAME.getExtensions()) {
+      fileSource.addFilesToPrefetch(project, blazeProjectData, files);
+    }
+    return prefetchFiles(project, files);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/prefetch/Prefetcher.java b/base/src/com/google/idea/blaze/base/prefetch/Prefetcher.java
new file mode 100644
index 0000000..64503e0
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/prefetch/Prefetcher.java
@@ -0,0 +1,37 @@
+/*
+ * 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.prefetch;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.intellij.openapi.extensions.ExtensionPointName;
+import com.intellij.openapi.project.Project;
+import java.io.File;
+import java.util.Collection;
+
+/** Prefetches files when a project is opened or roots change. */
+public interface Prefetcher {
+  ExtensionPointName<Prefetcher> EP_NAME =
+      ExtensionPointName.create("com.google.idea.blaze.Prefetcher");
+
+  /**
+   * Prefetches the given list of files.
+   *
+   * <p>It is the responsibility of the prefetcher to filter out any files it isn't interested in.
+   */
+  ListenableFuture<?> prefetchFiles(
+      Project project, Collection<File> file, ListeningExecutorService executor);
+}
diff --git a/base/src/com/google/idea/blaze/base/projectview/ProjectView.java b/base/src/com/google/idea/blaze/base/projectview/ProjectView.java
new file mode 100644
index 0000000..8dfc6f3
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/projectview/ProjectView.java
@@ -0,0 +1,135 @@
+/*
+ * 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.projectview;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.projectview.section.Section;
+import com.google.idea.blaze.base.projectview.section.SectionBuilder;
+import com.google.idea.blaze.base.projectview.section.SectionKey;
+import java.io.Serializable;
+import java.util.List;
+import javax.annotation.Nullable;
+
+/** Represents instructions for what should be included in a project. */
+public final class ProjectView implements Serializable {
+  private static final long serialVersionUID = 3L;
+
+  private final ImmutableList<Section<?>> sections;
+
+  public ProjectView(ImmutableList<Section<?>> sections) {
+    this.sections = sections;
+  }
+
+  @SuppressWarnings("unchecked")
+  public <T, SectionType extends Section<T>> ImmutableList<SectionType> getSectionsOfType(
+      SectionKey<T, SectionType> key) {
+    ImmutableList.Builder<SectionType> result = ImmutableList.builder();
+    for (Section<?> section : sections) {
+      if (section.isSectionType(key)) {
+        result.add((SectionType) section);
+      }
+    }
+    return result.build();
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static Builder builder(ProjectView projectView) {
+    return new Builder(projectView);
+  }
+
+  public ImmutableList<Section<?>> getSections() {
+    return sections;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    ProjectView that = (ProjectView) o;
+    return Objects.equal(sections, that.sections);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(sections);
+  }
+
+  /** Builder class. */
+  public static class Builder {
+    private final List<Section<?>> sections = Lists.newArrayList();
+
+    Builder() {}
+
+    Builder(ProjectView projectView) {
+      sections.addAll(projectView.sections);
+    }
+
+    /** Gets the last section of the type in the builder. Useful to add on to sections. */
+    @SuppressWarnings("unchecked")
+    @Nullable
+    public <T, SectionType extends Section<T>> SectionType getLast(SectionKey<T, SectionType> key) {
+      for (Section<?> section : sections) {
+        if (section.isSectionType(key)) {
+          return (SectionType) section;
+        }
+      }
+      return null;
+    }
+
+    public <T, SectionType extends Section<T>> Builder add(SectionBuilder<T, SectionType> builder) {
+      return add(builder.build());
+    }
+
+    public <T, SectionType extends Section<T>> Builder add(SectionType section) {
+      sections.add(section);
+      return this;
+    }
+
+    /** Replaces a section if it already exists. If it doesn't, just add the section. */
+    public <T, SectionType extends Section<T>> Builder replace(
+        @Nullable Section<T> section, SectionBuilder<T, SectionType> builder) {
+      return replace(section, builder.build());
+    }
+
+    /** Replaces a section if it already exists. If it doesn't, just add the section. */
+    public <T> Builder replace(@Nullable Section<T> toReplace, Section<T> replaceWith) {
+      if (toReplace == null) {
+        return add(replaceWith);
+      }
+
+      int i = sections.indexOf(toReplace);
+      if (i == -1) {
+        throw new IllegalArgumentException("Section not in this builder.");
+      }
+      sections.remove(i);
+      sections.add(i, replaceWith);
+      return this;
+    }
+
+    public ProjectView build() {
+      return new ProjectView(ImmutableList.copyOf(sections));
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/projectview/ProjectViewEdit.java b/base/src/com/google/idea/blaze/base/projectview/ProjectViewEdit.java
new file mode 100644
index 0000000..7087681
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/projectview/ProjectViewEdit.java
@@ -0,0 +1,120 @@
+/*
+ * 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.projectview;
+
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.projectview.parser.ProjectViewParser;
+import com.google.idea.blaze.base.scope.Scope;
+import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
+import com.google.idea.blaze.base.util.SaveUtil;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.Messages;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import javax.annotation.Nullable;
+
+/** Represents a modification to one or more project view files. */
+public class ProjectViewEdit {
+
+  private static final Logger LOG = Logger.getInstance(ProjectViewEdit.class);
+  private final Project project;
+  private final List<Modification> modifications;
+
+  ProjectViewEdit(Project project, List<Modification> modifications) {
+    this.project = project;
+    this.modifications = modifications;
+  }
+
+  private static class Modification {
+    ProjectView oldProjectView;
+    ProjectView newProjectView;
+    File projectViewFile;
+  }
+
+  /** Creates a new edit that modifies the local project view only. */
+  @Nullable
+  public static ProjectViewEdit editLocalProjectView(Project project, ProjectViewEditor editor) {
+    List<Modification> modifications = Lists.newArrayList();
+    BlazeProjectData projectData =
+        BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
+    if (projectData == null) {
+      return null;
+    }
+    ProjectViewSet oldProjectViewSet =
+        Scope.root(
+            (context) -> {
+              SaveUtil.saveAllFiles();
+              return ProjectViewManager.getInstance(project)
+                  .reloadProjectView(context, projectData.workspacePathResolver);
+            });
+    if (oldProjectViewSet == null) {
+      return null;
+    }
+
+    ProjectViewSet.ProjectViewFile projectViewFile = oldProjectViewSet.getTopLevelProjectViewFile();
+    if (projectViewFile == null) {
+      return null;
+    }
+
+    ProjectView.Builder builder = ProjectView.builder(projectViewFile.projectView);
+    if (editor.editProjectView(builder)) {
+      Modification modification = new Modification();
+      modification.newProjectView = builder.build();
+      modification.oldProjectView = projectViewFile.projectView;
+      modification.projectViewFile = projectViewFile.projectViewFile;
+      modifications.add(modification);
+    }
+    return new ProjectViewEdit(project, modifications);
+  }
+
+  public void apply() {
+    apply(true);
+  }
+
+  public void undo() {
+    apply(false);
+  }
+
+  private void apply(boolean isApply) {
+    SaveUtil.saveAllFiles();
+    for (Modification modification : modifications) {
+      ProjectView projectView = isApply ? modification.newProjectView : modification.oldProjectView;
+      String projectViewText = ProjectViewParser.projectViewToString(projectView);
+      try {
+        ProjectViewStorageManager.getInstance()
+            .writeProjectView(projectViewText, modification.projectViewFile);
+      } catch (IOException e) {
+        LOG.error(e);
+        Messages.showErrorDialog(
+            project,
+            "Could not write updated project view. Is the file write protected?",
+            "Edit Failed");
+      }
+    }
+  }
+
+  public boolean hasModifications() {
+    return !modifications.isEmpty();
+  }
+
+  /** Interface for an edit to the project view */
+  public interface ProjectViewEditor {
+    boolean editProjectView(ProjectView.Builder builder);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/projectview/ProjectViewManager.java b/base/src/com/google/idea/blaze/base/projectview/ProjectViewManager.java
new file mode 100644
index 0000000..bbf77d3
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/projectview/ProjectViewManager.java
@@ -0,0 +1,43 @@
+/*
+ * 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.projectview;
+
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.project.Project;
+import javax.annotation.Nullable;
+
+/** Class that manages access to a project's {@link ProjectView}. */
+public abstract class ProjectViewManager {
+
+  public static ProjectViewManager getInstance(Project project) {
+    return ServiceManager.getService(project, ProjectViewManager.class);
+  }
+
+  /** Returns the current project view collection. If there is an error, returns null. */
+  @Nullable
+  public abstract ProjectViewSet getProjectViewSet();
+
+  /**
+   * Reloads the project view, replacing the current one only if there are no errors.
+   *
+   * @return Success.
+   */
+  @Nullable
+  public abstract ProjectViewSet reloadProjectView(
+      BlazeContext context, WorkspacePathResolver workspacePathResolver);
+}
diff --git a/base/src/com/google/idea/blaze/base/projectview/ProjectViewManagerImpl.java b/base/src/com/google/idea/blaze/base/projectview/ProjectViewManagerImpl.java
new file mode 100644
index 0000000..1e48489
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/projectview/ProjectViewManagerImpl.java
@@ -0,0 +1,103 @@
+/*
+ * 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.projectview;
+
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.projectview.parser.ProjectViewParser;
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.settings.BlazeImportSettings;
+import com.google.idea.blaze.base.settings.BlazeImportSettingsManager;
+import com.google.idea.blaze.base.sync.data.BlazeDataStorage;
+import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver;
+import com.google.idea.blaze.base.util.SerializationUtil;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/** Project view manager implementation. */
+/** Stores mutable per-project user settings. */
+final class ProjectViewManagerImpl extends ProjectViewManager {
+
+  private static final Logger LOG = Logger.getInstance(ProjectViewManagerImpl.class);
+  private static final String CACHE_FILE_NAME = "project.view.dat";
+
+  private final Project project;
+  @Nullable private ProjectViewSet projectViewSet;
+  private boolean projectViewSetLoaded = false;
+
+  public ProjectViewManagerImpl(@NotNull Project project) {
+    this.project = project;
+  }
+
+  @Nullable
+  @Override
+  public ProjectViewSet getProjectViewSet() {
+    if (projectViewSet == null && !projectViewSetLoaded) {
+      ProjectViewSet loadedProjectViewSet = null;
+      try {
+        BlazeImportSettings importSettings =
+            BlazeImportSettingsManager.getInstance(project).getImportSettings();
+        if (importSettings == null) {
+          return null;
+        }
+        File file = getCacheFile(project, importSettings);
+
+        List<ClassLoader> classLoaders = Lists.newArrayList();
+        classLoaders.add(getClass().getClassLoader());
+        classLoaders.add(Thread.currentThread().getContextClassLoader());
+        loadedProjectViewSet = (ProjectViewSet) SerializationUtil.loadFromDisk(file, classLoaders);
+      } catch (IOException e) {
+        LOG.info(e);
+      }
+      this.projectViewSet = loadedProjectViewSet;
+      this.projectViewSetLoaded = true;
+    }
+    return projectViewSet;
+  }
+
+  @Override
+  public ProjectViewSet reloadProjectView(
+      BlazeContext context, WorkspacePathResolver workspacePathResolver) {
+    BlazeImportSettings importSettings =
+        BlazeImportSettingsManager.getInstance(project).getImportSettings();
+    assert importSettings != null;
+    assert importSettings.getProjectViewFile() != null;
+    File projectViewFile = new File(importSettings.getProjectViewFile());
+    ProjectViewParser parser = new ProjectViewParser(context, workspacePathResolver);
+    parser.parseProjectView(projectViewFile);
+
+    boolean success = !context.hasErrors();
+    if (success) {
+      ProjectViewSet projectViewSet = parser.getResult();
+      File file = getCacheFile(project, importSettings);
+      try {
+        SerializationUtil.saveToDisk(file, projectViewSet);
+      } catch (IOException e) {
+        LOG.error(e);
+      }
+      this.projectViewSet = projectViewSet;
+    }
+    return success ? projectViewSet : null;
+  }
+
+  private static File getCacheFile(Project project, BlazeImportSettings importSettings) {
+    return new File(BlazeDataStorage.getProjectCacheDir(project, importSettings), CACHE_FILE_NAME);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/projectview/ProjectViewSet.java b/base/src/com/google/idea/blaze/base/projectview/ProjectViewSet.java
new file mode 100644
index 0000000..1f3ed1f
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/projectview/ProjectViewSet.java
@@ -0,0 +1,132 @@
+/*
+ * 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.projectview;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+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.Section;
+import com.google.idea.blaze.base.projectview.section.SectionKey;
+import java.io.File;
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.List;
+import javax.annotation.Nullable;
+
+/** A collection of project views and their file names. */
+public final class ProjectViewSet implements Serializable {
+  private static final long serialVersionUID = 2L;
+
+  private final ImmutableList<ProjectViewFile> projectViewFiles;
+
+  public ProjectViewSet(ImmutableList<ProjectViewFile> projectViewFiles) {
+    this.projectViewFiles = projectViewFiles;
+  }
+
+  /** Returns all values from all list sections in the project views, in order */
+  public <T> List<T> listItems(SectionKey<T, ListSection<T>> key) {
+    List<T> result = Lists.newArrayList();
+    for (ListSection<T> section : getSections(key)) {
+      result.addAll(section.items());
+    }
+    return result;
+  }
+
+  /** Returns all values from all scalar sections in the project views, in order */
+  public <T> List<T> listScalarItems(SectionKey<T, ScalarSection<T>> key) {
+    List<T> result = Lists.newArrayList();
+    for (ScalarSection<T> section : getSections(key)) {
+      result.add(section.getValue());
+    }
+    return result;
+  }
+
+  /** Gets the last value from any scalar sections */
+  @Nullable
+  public <T> T getScalarValue(SectionKey<T, ScalarSection<T>> key) {
+    return getScalarValue(key, null);
+  }
+
+  /** Gets the last value from any scalar sections */
+  public <T> T getScalarValue(SectionKey<T, ScalarSection<T>> key, T defaultValue) {
+    Collection<ScalarSection<T>> sections = getSections(key);
+    if (sections.isEmpty()) {
+      return defaultValue;
+    } else {
+      return Iterables.getLast(sections).getValue();
+    }
+  }
+
+  public <T, SectionType extends Section<T>> Collection<SectionType> getSections(
+      SectionKey<T, SectionType> key) {
+    List<SectionType> result = Lists.newArrayList();
+    for (ProjectViewFile projectViewFile : projectViewFiles) {
+      ProjectView projectView = projectViewFile.projectView;
+      result.addAll(projectView.getSectionsOfType(key));
+    }
+    return result;
+  }
+
+  public Collection<ProjectViewFile> getProjectViewFiles() {
+    return projectViewFiles;
+  }
+
+  @Nullable
+  public ProjectViewFile getTopLevelProjectViewFile() {
+    return !projectViewFiles.isEmpty() ? projectViewFiles.get(projectViewFiles.size() - 1) : null;
+  }
+
+  /** A project view/file pair */
+  public static class ProjectViewFile implements Serializable {
+    private static final long serialVersionUID = 1L;
+    public final ProjectView projectView;
+    @Nullable public final File projectViewFile;
+
+    public ProjectViewFile(ProjectView projectView, @Nullable File projectViewFile) {
+      this.projectView = projectView;
+      this.projectViewFile = projectViewFile;
+    }
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  /** Builder for a project view */
+  public static class Builder {
+    ImmutableList.Builder<ProjectViewFile> projectViewFiles = ImmutableList.builder();
+
+    public Builder add(ProjectView projectView) {
+      return add(null, projectView);
+    }
+
+    public Builder add(@Nullable File projectViewFile, ProjectView projectView) {
+      projectViewFiles.add(new ProjectViewFile(projectView, projectViewFile));
+      return this;
+    }
+
+    public Builder addAll(Collection<ProjectViewFile> projectViewFiles) {
+      this.projectViewFiles.addAll(projectViewFiles);
+      return this;
+    }
+
+    public ProjectViewSet build() {
+      return new ProjectViewSet(projectViewFiles.build());
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/projectview/ProjectViewStorageManager.java b/base/src/com/google/idea/blaze/base/projectview/ProjectViewStorageManager.java
new file mode 100644
index 0000000..4288c1a
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/projectview/ProjectViewStorageManager.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.projectview;
+
+import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
+import com.intellij.openapi.components.ServiceManager;
+import java.io.File;
+import java.io.IOException;
+import javax.annotation.Nullable;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Manages project view storage.
+ *
+ * <p>For the most part, use ProjectViewManager instead. This is a lower-level API intended for use
+ * by ProjectViewManager itself, and during the import process before a project exists.
+ */
+public abstract class ProjectViewStorageManager {
+
+  private static final String BLAZE_EXTENSION = "blazeproject";
+  private static final String BAZEL_EXTENSION = "bazelproject";
+  private static final String LEGACY_EXTENSION = "asproject";
+
+  public static final ImmutableList<String> VALID_EXTENSIONS =
+      ImmutableList.of(BLAZE_EXTENSION, BAZEL_EXTENSION, LEGACY_EXTENSION);
+
+  public static boolean isProjectViewFile(@NotNull File file) {
+    return isProjectViewFile(file.getName());
+  }
+
+  public static boolean isProjectViewFile(String fileName) {
+    for (String ext : VALID_EXTENSIONS) {
+      if (fileName.endsWith("." + ext)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  public static String getProjectViewFileName(BuildSystem buildSystem) {
+    switch (buildSystem) {
+      case Blaze:
+        return "." + BLAZE_EXTENSION;
+      case Bazel:
+        return "." + BAZEL_EXTENSION;
+      default:
+        throw new IllegalArgumentException("Unrecognized build system type: " + buildSystem);
+    }
+  }
+
+  public static File getLocalProjectViewFileName(
+      BuildSystem buildSystem, File projectDataDirectory) {
+    return new File(projectDataDirectory, getProjectViewFileName(buildSystem));
+  }
+
+  public static ProjectViewStorageManager getInstance() {
+    return ServiceManager.getService(ProjectViewStorageManager.class);
+  }
+
+  @Nullable
+  public abstract String loadProjectView(File projectViewFile) throws IOException;
+
+  public abstract void writeProjectView(String projectViewText, File projectViewFile)
+      throws IOException;
+}
diff --git a/base/src/com/google/idea/blaze/base/projectview/ProjectViewStorageManagerImpl.java b/base/src/com/google/idea/blaze/base/projectview/ProjectViewStorageManagerImpl.java
new file mode 100644
index 0000000..de784d7
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/projectview/ProjectViewStorageManagerImpl.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.projectview;
+
+import com.google.common.base.Charsets;
+import com.google.common.collect.ImmutableList;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/** Project view storage implementation. */
+final class ProjectViewStorageManagerImpl extends ProjectViewStorageManager {
+  private static final Logger LOG = Logger.getInstance(ProjectViewManagerImpl.class);
+
+  @Nullable
+  @Override
+  public String loadProjectView(@NotNull File projectViewFile) throws IOException {
+    FileInputStream fis = new FileInputStream(projectViewFile);
+    byte[] data = new byte[(int) projectViewFile.length()];
+    fis.read(data);
+    fis.close();
+    return new String(data, Charsets.UTF_8);
+  }
+
+  @Override
+  public void writeProjectView(@NotNull String projectViewText, @NotNull File projectViewFile)
+      throws IOException {
+    FileWriter fileWriter = new FileWriter(projectViewFile);
+    try {
+      fileWriter.write(projectViewText);
+    } finally {
+      fileWriter.close();
+    }
+
+    LocalFileSystem.getInstance().refreshIoFiles(ImmutableList.of(projectViewFile));
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/projectview/ProjectViewVerifier.java b/base/src/com/google/idea/blaze/base/projectview/ProjectViewVerifier.java
new file mode 100644
index 0000000..4d7d56a
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/projectview/ProjectViewVerifier.java
@@ -0,0 +1,147 @@
+/*
+ * 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.projectview;
+
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.io.WorkspaceScanner;
+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;
+import com.google.idea.blaze.base.projectview.section.sections.DirectoryEntry;
+import com.google.idea.blaze.base.projectview.section.sections.DirectorySection;
+import com.google.idea.blaze.base.projectview.section.sections.ExcludedSourceSection;
+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.projectview.WorkspaceLanguageSettings;
+import com.intellij.openapi.util.io.FileUtil;
+import java.util.List;
+
+/** Verifies project views. */
+public class ProjectViewVerifier {
+
+  private static class MissingDirectoryIssueData extends IssueOutput.IssueData {
+    public final WorkspacePath workspacePath;
+
+    public MissingDirectoryIssueData(WorkspacePath workspacePath) {
+      this.workspacePath = workspacePath;
+    }
+  }
+
+  /** Verifies the project view. Any errors are output to the context as issues. */
+  public static boolean verifyProjectView(
+      BlazeContext context,
+      WorkspaceRoot workspaceRoot,
+      ProjectViewSet projectViewSet,
+      WorkspaceLanguageSettings workspaceLanguageSettings) {
+    if (!verifyIncludedPackagesExistOnDisk(context, workspaceRoot, projectViewSet)) {
+      return false;
+    }
+    if (!verifyIncludedPackagesAreNotExcluded(context, projectViewSet)) {
+      return false;
+    }
+    for (BlazeSyncPlugin syncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) {
+      if (!syncPlugin.validateProjectView(context, projectViewSet, workspaceLanguageSettings)) {
+        return false;
+      }
+    }
+    if (!projectViewSet.listItems(ExcludedSourceSection.KEY).isEmpty()) {
+      IssueOutput.warn("excluded_sources is deprecated and has no effect.")
+          .inFile(projectViewSet.getTopLevelProjectViewFile().projectViewFile)
+          .submit(context);
+    }
+    return true;
+  }
+
+  private static boolean verifyIncludedPackagesAreNotExcluded(
+      BlazeContext context, ProjectViewSet projectViewSet) {
+    boolean ok = true;
+
+    List<WorkspacePath> includedDirectories = getIncludedDirectories(projectViewSet);
+
+    for (WorkspacePath includedDirectory : includedDirectories) {
+      for (ProjectViewSet.ProjectViewFile projectViewFile : projectViewSet.getProjectViewFiles()) {
+        List<DirectoryEntry> directoryEntries = Lists.newArrayList();
+        for (ListSection<DirectoryEntry> section :
+            projectViewFile.projectView.getSectionsOfType(DirectorySection.KEY)) {
+          directoryEntries.addAll(section.items());
+        }
+
+        for (DirectoryEntry entry : directoryEntries) {
+          if (entry.included) {
+            continue;
+          }
+
+          WorkspacePath excludedDirectory = entry.directory;
+          if (FileUtil.isAncestor(
+              excludedDirectory.relativePath(), includedDirectory.relativePath(), false)) {
+            IssueOutput.error(
+                    String.format(
+                        "%s is included, but that contradicts %s which was excluded",
+                        includedDirectory.toString(), excludedDirectory.toString()))
+                .inFile(projectViewFile.projectViewFile)
+                .submit(context);
+            ok = false;
+          }
+        }
+      }
+    }
+    return ok;
+  }
+
+  private static List<WorkspacePath> getIncludedDirectories(ProjectViewSet projectViewSet) {
+    List<WorkspacePath> includedDirectories = Lists.newArrayList();
+    for (DirectoryEntry entry : projectViewSet.listItems(DirectorySection.KEY)) {
+      if (entry.included) {
+        includedDirectories.add(entry.directory);
+      }
+    }
+    return includedDirectories;
+  }
+
+  private static boolean verifyIncludedPackagesExistOnDisk(
+      BlazeContext context, WorkspaceRoot workspaceRoot, ProjectViewSet projectViewSet) {
+    boolean ok = true;
+
+    WorkspaceScanner workspaceScanner = WorkspaceScanner.getInstance();
+
+    for (ProjectViewSet.ProjectViewFile projectViewFile : projectViewSet.getProjectViewFiles()) {
+      List<DirectoryEntry> directoryEntries = Lists.newArrayList();
+      for (ListSection<DirectoryEntry> section :
+          projectViewFile.projectView.getSectionsOfType(DirectorySection.KEY)) {
+        directoryEntries.addAll(section.items());
+      }
+      for (DirectoryEntry entry : directoryEntries) {
+        if (!entry.included) {
+          continue;
+        }
+        WorkspacePath workspacePath = entry.directory;
+        if (!workspaceScanner.exists(workspaceRoot, workspacePath)) {
+          IssueOutput.error(
+                  String.format(
+                      "Directory '%s' specified in import roots not found "
+                          + "under workspace root '%s'",
+                      workspacePath, workspaceRoot))
+              .inFile(projectViewFile.projectViewFile)
+              .withData(new MissingDirectoryIssueData(workspacePath))
+              .submit(context);
+          ok = false;
+        }
+      }
+    }
+    return ok;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/projectview/parser/ParseContext.java b/base/src/com/google/idea/blaze/base/projectview/parser/ParseContext.java
new file mode 100644
index 0000000..a7b626a
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/projectview/parser/ParseContext.java
@@ -0,0 +1,133 @@
+/*
+ * 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.projectview.parser;
+
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.scope.output.IssueOutput;
+import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver;
+import com.google.idea.blaze.base.ui.BlazeValidationError;
+import java.io.File;
+import java.util.List;
+import org.jetbrains.annotations.Nullable;
+
+/** Context for the project view parser. */
+public class ParseContext {
+  private final BlazeContext context;
+  private final WorkspacePathResolver workspacePathResolver;
+  @Nullable private final File file;
+  private final List<String> lines;
+
+  @Nullable private Line currentLine;
+  @Nullable private String currentRawLine;
+  private int currentLineIndex;
+  private int savedPosition = -1;
+
+  /** A line that is being parsed */
+  public static class Line {
+    public final String text;
+    public final int indent;
+
+    public Line(String text, int indent) {
+      this.text = text;
+      this.indent = indent;
+    }
+  }
+
+  public ParseContext(
+      BlazeContext context,
+      WorkspacePathResolver workspacePathResolver,
+      @Nullable File file,
+      String text) {
+    this.context = context;
+    this.workspacePathResolver = workspacePathResolver;
+    this.file = file;
+    this.lines = Lists.newArrayList(text.split("\n"));
+    this.currentLine = null;
+    this.currentLineIndex = -1;
+    consume();
+  }
+
+  public Line current() {
+    assert currentLine != null;
+    return currentLine;
+  }
+
+  public String currentRawLine() {
+    assert currentRawLine != null;
+    return currentRawLine;
+  }
+
+  public void consume() {
+    while (++currentLineIndex < lines.size()) {
+      update();
+      break;
+    }
+  }
+
+  private void update() {
+    String line = lines.get(currentLineIndex);
+    int indent = 0;
+    while (indent < line.length() && line.charAt(indent) == ' ') {
+      ++indent;
+    }
+    currentLine = new Line(line.trim(), indent);
+    currentRawLine = line;
+  }
+
+  public boolean atEnd() {
+    return currentLineIndex >= lines.size();
+  }
+
+  public BlazeContext getContext() {
+    return context;
+  }
+
+  public WorkspacePathResolver getWorkspacePathResolver() {
+    return workspacePathResolver;
+  }
+
+  public void savePosition() {
+    savedPosition = currentLineIndex;
+  }
+
+  public void clearSavedPosition() {
+    savedPosition = -1;
+  }
+
+  public void resetToSavedPosition() {
+    if (savedPosition != -1) {
+      currentLineIndex = savedPosition;
+      savedPosition = -1;
+      update();
+    }
+  }
+
+  @Nullable
+  public File getProjectViewFile() {
+    return file;
+  }
+
+  public void addErrors(List<BlazeValidationError> errors) {
+    for (BlazeValidationError error : errors) {
+      addError(error.getError());
+    }
+  }
+
+  public void addError(String error) {
+    IssueOutput.error(error).inFile(file).onLine(currentLineIndex).submit(context);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/projectview/parser/ProjectViewParser.java b/base/src/com/google/idea/blaze/base/projectview/parser/ProjectViewParser.java
new file mode 100644
index 0000000..2f4707f
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/projectview/parser/ProjectViewParser.java
@@ -0,0 +1,145 @@
+/*
+ * 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.projectview.parser;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
+import com.google.idea.blaze.base.projectview.ProjectView;
+import com.google.idea.blaze.base.projectview.ProjectViewSet;
+import com.google.idea.blaze.base.projectview.ProjectViewStorageManager;
+import com.google.idea.blaze.base.projectview.section.Section;
+import com.google.idea.blaze.base.projectview.section.SectionParser;
+import com.google.idea.blaze.base.projectview.section.sections.Sections;
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.scope.output.IssueOutput;
+import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Set;
+
+/** Parses and writes project views. */
+public class ProjectViewParser {
+
+  private final BlazeContext context;
+  private final WorkspacePathResolver workspacePathResolver;
+  private final boolean recursive;
+
+  Set<File> encounteredProjectViewFiles = Sets.newHashSet();
+  ImmutableList.Builder<ProjectViewSet.ProjectViewFile> projectViewFiles = ImmutableList.builder();
+
+  public ProjectViewParser(BlazeContext context, WorkspacePathResolver workspacePathResolver) {
+    this.context = context;
+    this.workspacePathResolver = workspacePathResolver;
+    this.recursive = true;
+  }
+
+  public void parseProjectView(File projectViewFile) {
+    if (!encounteredProjectViewFiles.add(projectViewFile)) {
+      return;
+    }
+    String projectViewText = null;
+    try {
+      projectViewText = ProjectViewStorageManager.getInstance().loadProjectView(projectViewFile);
+    } catch (IOException e) {
+      // Error handled below
+    }
+    if (projectViewText == null) {
+      IssueOutput.error(
+              String.format("Could not load project view file: '%s'", projectViewFile.getPath()))
+          .submit(context);
+      return;
+    }
+    parseProjectView(
+        new ParseContext(context, workspacePathResolver, projectViewFile, projectViewText));
+  }
+
+  public void parseProjectView(String text) {
+    parseProjectView(new ParseContext(context, workspacePathResolver, null, text));
+  }
+
+  private void parseProjectView(ParseContext parseContext) {
+    ImmutableList.Builder<Section<?>> sections = ImmutableList.builder();
+
+    List<SectionParser> sectionParsers = Sections.getParsers();
+    while (!parseContext.atEnd()) {
+      Section section = null;
+      for (SectionParser sectionParser : sectionParsers) {
+        section = sectionParser.parse(this, parseContext);
+        if (section != null) {
+          sections.add(section);
+          break;
+        }
+      }
+      if (section == null) {
+        if (parseContext.current().indent != 0) {
+          parseContext.addError(
+              String.format("Invalid indentation on line: '%s'", parseContext.current().text));
+          skipSection(parseContext);
+        } else {
+          parseContext.addError(
+              String.format("Could not parse: '%s'", parseContext.current().text));
+          parseContext.consume();
+
+          // Skip past the entire section
+          skipSection(parseContext);
+        }
+      }
+    }
+
+    ProjectView projectView = new ProjectView(sections.build());
+    projectViewFiles.add(
+        new ProjectViewSet.ProjectViewFile(projectView, parseContext.getProjectViewFile()));
+  }
+
+  /** Skips all lines until the next unindented, non-empty line. */
+  private static void skipSection(ParseContext parseContext) {
+    while (!parseContext.atEnd() && parseContext.current().indent != 0) {
+      parseContext.consume();
+    }
+  }
+
+  public boolean isRecursive() {
+    return recursive;
+  }
+
+  public ProjectViewSet getResult() {
+    return new ProjectViewSet(projectViewFiles.build());
+  }
+
+  public static String projectViewToString(ProjectView projectView) {
+    StringBuilder sb = new StringBuilder();
+
+    List<SectionParser> sectionParsers = Sections.getParsers();
+    for (Section<?> section : projectView.getSections()) {
+      SectionParser sectionParser =
+          sectionParsers
+              .stream()
+              .filter(parser -> section.isSectionType(parser.getSectionKey()))
+              .findFirst()
+              .orElse(null);
+      if (sectionParser != null) {
+        sectionParser.print(sb, section);
+      }
+    }
+
+    // Because we split lines we'll always have an extra newline at the end
+    if (sb.length() > 0) {
+      sb.deleteCharAt(sb.length() - 1);
+    }
+    return sb.toString();
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/projectview/section/Glob.java b/base/src/com/google/idea/blaze/base/projectview/section/Glob.java
new file mode 100644
index 0000000..7b42abe
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/projectview/section/Glob.java
@@ -0,0 +1,92 @@
+/*
+ * 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.projectview.section;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.Lists;
+import com.intellij.openapi.fileTypes.FileNameMatcher;
+import java.io.Serializable;
+import java.util.Collection;
+import org.jetbrains.jps.model.fileTypes.FileNameMatcherFactory;
+
+/** Glob matcher. */
+public class Glob implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  private String pattern;
+  private transient FileNameMatcher matcher;
+
+  public Glob(String pattern) {
+    this.pattern = pattern;
+  }
+
+  /** A set of globs */
+  public static class GlobSet implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private final Collection<Glob> globs = Lists.newArrayList();
+
+    public GlobSet(Collection<Glob> globs) {
+      this.globs.addAll(globs);
+    }
+
+    public boolean isEmpty() {
+      return globs.isEmpty();
+    }
+
+    public void add(Glob glob) {
+      globs.add(glob);
+    }
+
+    public boolean matches(String string) {
+      for (Glob glob : globs) {
+        if (glob.matches(string)) {
+          return true;
+        }
+      }
+      return false;
+    }
+  }
+
+  public boolean matches(String string) {
+    if (matcher == null) {
+      matcher = FileNameMatcherFactory.getInstance().createMatcher(pattern);
+    }
+    return matcher.accept(string);
+  }
+
+  @Override
+  public String toString() {
+    return pattern;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    Glob glob = (Glob) o;
+    return Objects.equal(pattern, glob.pattern);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(pattern);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/projectview/section/GlobSectionParser.java b/base/src/com/google/idea/blaze/base/projectview/section/GlobSectionParser.java
new file mode 100644
index 0000000..01eb2d2
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/projectview/section/GlobSectionParser.java
@@ -0,0 +1,52 @@
+/*
+ * 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.projectview.section;
+
+import com.google.idea.blaze.base.projectview.parser.ParseContext;
+import com.google.idea.blaze.base.projectview.parser.ProjectViewParser;
+import java.util.regex.PatternSyntaxException;
+import javax.annotation.Nullable;
+
+/** Parses glob sections. */
+public class GlobSectionParser extends ListSectionParser<Glob> {
+
+  public GlobSectionParser(SectionKey<Glob, ListSection<Glob>> key) {
+    super(key);
+  }
+
+  @Nullable
+  @Override
+  protected final Glob parseItem(ProjectViewParser parser, ParseContext parseContext) {
+    String text = parseContext.current().text;
+    try {
+      Glob glob = new Glob(text);
+      return glob;
+    } catch (PatternSyntaxException e) {
+      parseContext.addError(e.getMessage());
+      return null;
+    }
+  }
+
+  @Override
+  protected final void printItem(Glob item, StringBuilder sb) {
+    sb.append(item.toString());
+  }
+
+  @Override
+  public ItemType getItemType() {
+    return ItemType.FileSystemItem;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/projectview/section/LabelSectionParser.java b/base/src/com/google/idea/blaze/base/projectview/section/LabelSectionParser.java
new file mode 100644
index 0000000..bc1ed4a
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/projectview/section/LabelSectionParser.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.projectview.section;
+
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.model.primitives.Label;
+import com.google.idea.blaze.base.projectview.parser.ParseContext;
+import com.google.idea.blaze.base.projectview.parser.ProjectViewParser;
+import com.google.idea.blaze.base.ui.BlazeValidationError;
+import java.util.List;
+import javax.annotation.Nullable;
+
+/** Section of labels */
+public final class LabelSectionParser extends ListSectionParser<Label> {
+  public LabelSectionParser(SectionKey<Label, ListSection<Label>> key) {
+    super(key);
+  }
+
+  @Nullable
+  @Override
+  protected Label parseItem(ProjectViewParser parser, ParseContext parseContext) {
+    String text = parseContext.current().text;
+    List<BlazeValidationError> errors = Lists.newArrayList();
+    if (!Label.validate(text, errors)) {
+      parseContext.addErrors(errors);
+      return null;
+    }
+    return new Label(text);
+  }
+
+  @Override
+  protected void printItem(Label item, StringBuilder sb) {
+    sb.append(item.toString());
+  }
+
+  @Override
+  public ItemType getItemType() {
+    return ItemType.Label;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/projectview/section/ListSection.java b/base/src/com/google/idea/blaze/base/projectview/section/ListSection.java
new file mode 100644
index 0000000..40ec7da
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/projectview/section/ListSection.java
@@ -0,0 +1,109 @@
+/*
+ * 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.projectview.section;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.base.projectview.section.sections.ItemOrTextBlock;
+import com.google.idea.blaze.base.projectview.section.sections.TextBlock;
+import java.util.Collection;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
+
+/**
+ * List value. Eg.
+ *
+ * <p>my_attribute: value0 value1 value2 ...
+ */
+public final class ListSection<T> extends Section<T> {
+  private static final long serialVersionUID = 2L;
+
+  private final ImmutableList<ItemOrTextBlock<T>> itemsOrComments;
+
+  ListSection(
+      SectionKey<T, ? extends ListSection<T>> sectionKey, ImmutableList<ItemOrTextBlock<T>> items) {
+    super(sectionKey);
+    this.itemsOrComments = items;
+  }
+
+  public Collection<T> items() {
+    return itemsOrComments
+        .stream()
+        .map(item -> item.item)
+        .filter(item -> item != null)
+        .collect(Collectors.toList());
+  }
+
+  public ImmutableList<ItemOrTextBlock<T>> itemsOrComments() {
+    return itemsOrComments;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    if (!super.equals(o)) {
+      return false;
+    }
+    ListSection<?> that = (ListSection<?>) o;
+    return Objects.equal(itemsOrComments, that.itemsOrComments);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(super.hashCode(), itemsOrComments);
+  }
+
+  public static <T> Builder<T> builder(SectionKey<T, ListSection<T>> sectionKey) {
+    return new Builder<>(sectionKey, null);
+  }
+
+  public static <T> Builder<T> update(
+      SectionKey<T, ListSection<T>> sectionKey, @Nullable ListSection<T> section) {
+    return new Builder<>(sectionKey, section);
+  }
+
+  /** Builder for list sections */
+  public static class Builder<T> extends SectionBuilder<T, ListSection<T>> {
+    private final ImmutableList.Builder<ItemOrTextBlock<T>> items = ImmutableList.builder();
+
+    public Builder(SectionKey<T, ListSection<T>> sectionKey, @Nullable ListSection<T> section) {
+      super(sectionKey);
+      if (section != null) {
+        items.addAll(section.itemsOrComments);
+      }
+    }
+
+    public final Builder<T> add(T item) {
+      items.add(new ItemOrTextBlock<>(item));
+      return this;
+    }
+
+    public final Builder<T> add(TextBlock textBlock) {
+      items.add(new ItemOrTextBlock<T>(textBlock));
+      return this;
+    }
+
+    @Override
+    public final ListSection<T> build() {
+      return new ListSection<>(getSectionKey(), items.build());
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/projectview/section/ListSectionParser.java b/base/src/com/google/idea/blaze/base/projectview/section/ListSectionParser.java
new file mode 100644
index 0000000..a95b230
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/projectview/section/ListSectionParser.java
@@ -0,0 +1,128 @@
+/*
+ * 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.projectview.section;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.projectview.parser.ParseContext;
+import com.google.idea.blaze.base.projectview.parser.ProjectViewParser;
+import com.google.idea.blaze.base.projectview.section.sections.ItemOrTextBlock;
+import com.google.idea.blaze.base.projectview.section.sections.TextBlock;
+import com.google.idea.blaze.base.projectview.section.sections.TextBlockSection;
+import java.util.List;
+import javax.annotation.Nullable;
+
+/** List section parser base class. */
+public abstract class ListSectionParser<T> extends SectionParser {
+  private final SectionKey<T, ListSection<T>> key;
+
+  protected ListSectionParser(SectionKey<T, ListSection<T>> key) {
+    this.key = key;
+  }
+
+  @Override
+  public SectionKey<T, ListSection<T>> getSectionKey() {
+    return key;
+  }
+
+  @Nullable
+  @Override
+  public final ListSection<T> parse(ProjectViewParser parser, ParseContext parseContext) {
+    if (parseContext.atEnd()) {
+      return null;
+    }
+
+    String name = getName();
+    if (!parseContext.current().text.equals(name + ':')) {
+      return null;
+    }
+    parseContext.consume();
+
+    ImmutableList.Builder<ItemOrTextBlock<T>> builder = ImmutableList.builder();
+
+    boolean correctIndentationRun = true;
+    List<ItemOrTextBlock<T>> savedTextBlocks = Lists.newArrayList();
+    while (!parseContext.atEnd()) {
+      boolean isIndented = parseContext.current().indent == SectionParser.INDENT;
+      if (!isIndented && correctIndentationRun) {
+        parseContext.savePosition();
+      }
+      correctIndentationRun = isIndented;
+
+      ItemOrTextBlock<T> itemOrTextBlock = null;
+      TextBlock textBlock = TextBlockSection.parseTextBlock(parseContext);
+      if (textBlock != null) {
+        itemOrTextBlock = new ItemOrTextBlock<>(textBlock);
+      } else if (isIndented) {
+        T item = parseItem(parser, parseContext);
+        if (item != null) {
+          parseContext.consume();
+          itemOrTextBlock = new ItemOrTextBlock<>(item);
+        }
+      }
+
+      if (itemOrTextBlock == null) {
+        break;
+      }
+
+      if (isIndented) {
+        builder.addAll(savedTextBlocks);
+        builder.add(itemOrTextBlock);
+        savedTextBlocks.clear();
+        parseContext.clearSavedPosition();
+      } else {
+        savedTextBlocks.add(new ItemOrTextBlock<>(textBlock));
+      }
+    }
+    parseContext.resetToSavedPosition();
+
+    ImmutableList<ItemOrTextBlock<T>> items = builder.build();
+    if (items.isEmpty()) {
+      parseContext.addError(String.format("Empty section: '%s'", name));
+    }
+
+    return new ListSection<>(key, items);
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public final void print(StringBuilder sb, Section<?> section) {
+    ListSection<T> listSection = (ListSection<T>) section;
+
+    // Omit empty sections completely
+    if (listSection.itemsOrComments().isEmpty()) {
+      return;
+    }
+
+    sb.append(getName()).append(':').append('\n');
+    for (ItemOrTextBlock<T> item : listSection.itemsOrComments()) {
+      if (item.item != null) {
+        for (int i = 0; i < SectionParser.INDENT; ++i) {
+          sb.append(' ');
+        }
+        printItem(item.item, sb);
+        sb.append('\n');
+      } else if (item.textBlock != null) {
+        item.textBlock.print(sb);
+      }
+    }
+  }
+
+  @Nullable
+  protected abstract T parseItem(ProjectViewParser parser, ParseContext parseContext);
+
+  protected abstract void printItem(T item, StringBuilder sb);
+}
diff --git a/base/src/com/google/idea/blaze/base/projectview/section/ScalarSection.java b/base/src/com/google/idea/blaze/base/projectview/section/ScalarSection.java
new file mode 100644
index 0000000..0b5b903
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/projectview/section/ScalarSection.java
@@ -0,0 +1,77 @@
+/*
+ * 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.projectview.section;
+
+import com.google.common.base.Objects;
+
+/** Scalar value. */
+public final class ScalarSection<T> extends Section<T> {
+  private static final long serialVersionUID = 1L;
+
+  private final T value;
+
+  public ScalarSection(SectionKey<T, ScalarSection<T>> sectionKey, T value) {
+    super(sectionKey);
+    this.value = value;
+  }
+
+  public T getValue() {
+    return value;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    if (!super.equals(o)) {
+      return false;
+    }
+    ScalarSection<?> that = (ScalarSection<?>) o;
+    return Objects.equal(value, that.value);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(super.hashCode(), value);
+  }
+
+  public static <T> Builder<T> builder(SectionKey<T, ScalarSection<T>> sectionKey) {
+    return new Builder<>(sectionKey);
+  }
+
+  /** Builder for scalar sections */
+  public static class Builder<T> extends SectionBuilder<T, ScalarSection<T>> {
+    private T value;
+
+    public Builder(SectionKey<T, ScalarSection<T>> sectionKey) {
+      super(sectionKey);
+    }
+
+    public Builder<T> set(T value) {
+      this.value = value;
+      return this;
+    }
+
+    @Override
+    public ScalarSection<T> build() {
+      return new ScalarSection<>(getSectionKey(), value);
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/projectview/section/ScalarSectionParser.java b/base/src/com/google/idea/blaze/base/projectview/section/ScalarSectionParser.java
new file mode 100644
index 0000000..dbe2e6a
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/projectview/section/ScalarSectionParser.java
@@ -0,0 +1,77 @@
+/*
+ * 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.projectview.section;
+
+import com.google.idea.blaze.base.projectview.parser.ParseContext;
+import com.google.idea.blaze.base.projectview.parser.ProjectViewParser;
+import javax.annotation.Nullable;
+
+/** Parses scalar values */
+public abstract class ScalarSectionParser<T> extends SectionParser {
+
+  private final SectionKey<T, ScalarSection<T>> key;
+  private final char divider;
+
+  protected ScalarSectionParser(SectionKey<T, ScalarSection<T>> key, char divider) {
+    this.key = key;
+    this.divider = divider;
+  }
+
+  @Override
+  public SectionKey<T, ScalarSection<T>> getSectionKey() {
+    return key;
+  }
+
+  @Nullable
+  @Override
+  public final ScalarSection<T> parse(ProjectViewParser parser, ParseContext parseContext) {
+    if (parseContext.atEnd()) {
+      return null;
+    }
+
+    String name = getName();
+    ParseContext.Line line = parseContext.current();
+
+    if (!line.text.startsWith(name + divider)) {
+      return null;
+    }
+    String rest = line.text.substring(name.length() + 1).trim();
+    parseContext.consume();
+    T item = parseItem(parser, parseContext, rest);
+    return item != null ? new ScalarSection<>(key, item) : null;
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public final void print(StringBuilder sb, Section<?> section) {
+    sb.append(getName()).append(divider);
+    if (divider != ' ') {
+      sb.append(' ');
+    }
+    printItem(sb, ((ScalarSection<T>) section).getValue());
+    sb.append('\n');
+  }
+
+  /** Used by psi-parser for validation. */
+  public char getDivider() {
+    return divider;
+  }
+
+  @Nullable
+  protected abstract T parseItem(ProjectViewParser parser, ParseContext parseContext, String rest);
+
+  protected abstract void printItem(StringBuilder sb, T value);
+}
diff --git a/base/src/com/google/idea/blaze/base/projectview/section/Section.java b/base/src/com/google/idea/blaze/base/projectview/section/Section.java
new file mode 100644
index 0000000..f4f2740
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/projectview/section/Section.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.projectview.section;
+
+import com.google.common.base.Objects;
+import java.io.Serializable;
+
+/**
+ * A section is a part of an project view file. For instance:
+ *
+ * <p>directories java/com/a java/com/b
+ *
+ * <p>Is a directory section with two items.
+ */
+public abstract class Section<T> implements Serializable {
+  private static final long serialVersionUID = 2L;
+  private final String sectionName;
+
+  protected Section(SectionKey<T, ?> key) {
+    this.sectionName = key.getName();
+  }
+
+  public boolean isSectionType(SectionKey<?, ?> key) {
+    return this.sectionName.equals(key.getName());
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    Section<?> section = (Section<?>) o;
+    return Objects.equal(sectionName, section.sectionName);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(sectionName);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/projectview/section/SectionBuilder.java b/base/src/com/google/idea/blaze/base/projectview/section/SectionBuilder.java
new file mode 100644
index 0000000..444ea03
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/projectview/section/SectionBuilder.java
@@ -0,0 +1,31 @@
+/*
+ * 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.projectview.section;
+
+/** Builder base class. */
+public abstract class SectionBuilder<T, SectionType extends Section<T>> {
+  private final SectionKey<T, SectionType> sectionKey;
+
+  protected SectionBuilder(SectionKey<T, SectionType> sectionKey) {
+    this.sectionKey = sectionKey;
+  }
+
+  public abstract SectionType build();
+
+  public final SectionKey<T, SectionType> getSectionKey() {
+    return sectionKey;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/projectview/section/SectionKey.java b/base/src/com/google/idea/blaze/base/projectview/section/SectionKey.java
new file mode 100644
index 0000000..e7910dc
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/projectview/section/SectionKey.java
@@ -0,0 +1,54 @@
+/*
+ * 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.projectview.section;
+
+import com.google.common.base.Objects;
+import java.io.Serializable;
+
+/** Key to a section of type T. */
+public final class SectionKey<T, SectionType extends Section<T>> implements Serializable {
+  private static final long serialVersionUID = 1L;
+  private final String name;
+
+  public SectionKey(String name) {
+    this.name = name;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public static <T, SectionType extends Section<T>> SectionKey<T, SectionType> of(String name) {
+    return new SectionKey<>(name);
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    SectionKey<?, ?> that = (SectionKey<?, ?>) o;
+    return Objects.equal(name, that.name);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(name);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/projectview/section/SectionParser.java b/base/src/com/google/idea/blaze/base/projectview/section/SectionParser.java
new file mode 100644
index 0000000..6d1164b
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/projectview/section/SectionParser.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.base.projectview.section;
+
+import com.google.idea.blaze.base.projectview.parser.ParseContext;
+import com.google.idea.blaze.base.projectview.parser.ProjectViewParser;
+import javax.annotation.Nullable;
+
+/** Parses a section. */
+public abstract class SectionParser {
+
+  public static final int INDENT = 2;
+
+  /** The type of item(s) in this section */
+  public enum ItemType {
+    FileSystemItem, // files, directories, globs
+    Label, // a blaze label
+    Other, // anything else
+  }
+
+  public String getName() {
+    return getSectionKey().getName();
+  }
+
+  public abstract SectionKey<?, ?> getSectionKey();
+
+  @Nullable
+  public abstract Section<?> parse(ProjectViewParser parser, ParseContext parseContext);
+
+  public abstract void print(StringBuilder sb, Section<?> section);
+
+  public boolean isDeprecated() {
+    return false;
+  }
+
+  /** The type of item(s) in this section. */
+  public abstract ItemType getItemType();
+}
diff --git a/base/src/com/google/idea/blaze/base/projectview/section/sections/AdditionalLanguagesSection.java b/base/src/com/google/idea/blaze/base/projectview/section/sections/AdditionalLanguagesSection.java
new file mode 100644
index 0000000..d9dc894
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/projectview/section/sections/AdditionalLanguagesSection.java
@@ -0,0 +1,60 @@
+/*
+ * 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.projectview.section.sections;
+
+import com.google.idea.blaze.base.model.primitives.LanguageClass;
+import com.google.idea.blaze.base.projectview.parser.ParseContext;
+import com.google.idea.blaze.base.projectview.parser.ProjectViewParser;
+import com.google.idea.blaze.base.projectview.section.ListSection;
+import com.google.idea.blaze.base.projectview.section.ListSectionParser;
+import com.google.idea.blaze.base.projectview.section.SectionKey;
+import com.google.idea.blaze.base.projectview.section.SectionParser;
+import javax.annotation.Nullable;
+
+/** Allows users to set the rule classes they want to be imported */
+public class AdditionalLanguagesSection {
+  public static final SectionKey<LanguageClass, ListSection<LanguageClass>> KEY =
+      SectionKey.of("additional_languages");
+  public static final SectionParser PARSER = new AdditionalLanguagesSectionParser();
+
+  private static class AdditionalLanguagesSectionParser extends ListSectionParser<LanguageClass> {
+    public AdditionalLanguagesSectionParser() {
+      super(KEY);
+    }
+
+    @Nullable
+    @Override
+    protected LanguageClass parseItem(ProjectViewParser parser, ParseContext parseContext) {
+      String text = parseContext.current().text;
+      LanguageClass language = LanguageClass.fromString(text);
+      if (language == null) {
+        parseContext.addError("Invalid language: " + text);
+        return null;
+      }
+      return language;
+    }
+
+    @Override
+    protected void printItem(LanguageClass item, StringBuilder sb) {
+      sb.append(item.getName());
+    }
+
+    @Override
+    public ItemType getItemType() {
+      return ItemType.Other;
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/projectview/section/sections/BuildFlagsSection.java b/base/src/com/google/idea/blaze/base/projectview/section/sections/BuildFlagsSection.java
new file mode 100644
index 0000000..588d289
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/projectview/section/sections/BuildFlagsSection.java
@@ -0,0 +1,52 @@
+/*
+ * 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.projectview.section.sections;
+
+import com.google.idea.blaze.base.projectview.parser.ParseContext;
+import com.google.idea.blaze.base.projectview.parser.ProjectViewParser;
+import com.google.idea.blaze.base.projectview.section.ListSection;
+import com.google.idea.blaze.base.projectview.section.ListSectionParser;
+import com.google.idea.blaze.base.projectview.section.SectionKey;
+import com.google.idea.blaze.base.projectview.section.SectionParser;
+import javax.annotation.Nullable;
+
+/** Section for blaze_flags */
+public class BuildFlagsSection {
+  public static final SectionKey<String, ListSection<String>> KEY = SectionKey.of("build_flags");
+  public static final SectionParser PARSER = new BuildFlagsSectionParser();
+
+  static class BuildFlagsSectionParser extends ListSectionParser<String> {
+    protected BuildFlagsSectionParser() {
+      super(KEY);
+    }
+
+    @Nullable
+    @Override
+    protected String parseItem(ProjectViewParser parser, ParseContext parseContext) {
+      return parseContext.current().text;
+    }
+
+    @Override
+    protected void printItem(String item, StringBuilder sb) {
+      sb.append(item);
+    }
+
+    @Override
+    public ItemType getItemType() {
+      return ItemType.Other;
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/projectview/section/sections/DirectoryEntry.java b/base/src/com/google/idea/blaze/base/projectview/section/sections/DirectoryEntry.java
new file mode 100644
index 0000000..0744eea
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/projectview/section/sections/DirectoryEntry.java
@@ -0,0 +1,69 @@
+/*
+ * 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.projectview.section.sections;
+
+import com.google.common.base.Objects;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
+import java.io.Serializable;
+
+/** An entry in the directory section. */
+public class DirectoryEntry implements Serializable {
+  private static final long serialVersionUID = 1L;
+  public final WorkspacePath directory;
+  public final boolean included;
+
+  public DirectoryEntry(WorkspacePath directory, boolean included) {
+    this.directory = directory;
+    this.included = included;
+  }
+
+  public static DirectoryEntry include(WorkspacePath directory) {
+    return new DirectoryEntry(directory, true);
+  }
+
+  public static DirectoryEntry exclude(WorkspacePath directory) {
+    return new DirectoryEntry(directory, false);
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    DirectoryEntry that = (DirectoryEntry) o;
+    return Objects.equal(included, that.included) && Objects.equal(directory, that.directory);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(directory, included);
+  }
+
+  @Override
+  public String toString() {
+    return (included ? "" : "-") + directoryString();
+  }
+
+  private String directoryString() {
+    if (directory.isWorkspaceRoot()) {
+      return ".";
+    }
+    return directory.relativePath();
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/projectview/section/sections/DirectorySection.java b/base/src/com/google/idea/blaze/base/projectview/section/sections/DirectorySection.java
new file mode 100644
index 0000000..0163e24
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/projectview/section/sections/DirectorySection.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.projectview.section.sections;
+
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
+import com.google.idea.blaze.base.projectview.parser.ParseContext;
+import com.google.idea.blaze.base.projectview.parser.ProjectViewParser;
+import com.google.idea.blaze.base.projectview.section.ListSection;
+import com.google.idea.blaze.base.projectview.section.ListSectionParser;
+import com.google.idea.blaze.base.projectview.section.SectionKey;
+import com.google.idea.blaze.base.projectview.section.SectionParser;
+import com.google.idea.blaze.base.ui.BlazeValidationError;
+import com.intellij.util.PathUtil;
+import java.util.List;
+import javax.annotation.Nullable;
+import org.jetbrains.annotations.NotNull;
+
+/** "directories" section. */
+public class DirectorySection {
+  public static final SectionKey<DirectoryEntry, ListSection<DirectoryEntry>> KEY =
+      SectionKey.of("directories");
+  public static final SectionParser PARSER = new DirectorySectionParser();
+
+  private static class DirectorySectionParser extends ListSectionParser<DirectoryEntry> {
+    public DirectorySectionParser() {
+      super(KEY);
+    }
+
+    @Nullable
+    @Override
+    protected DirectoryEntry parseItem(ProjectViewParser parser, ParseContext parseContext) {
+      String text = parseContext.current().text;
+      boolean excluded = text.startsWith("-");
+      text = excluded ? text.substring(1) : text;
+
+      text = PathUtil.getCanonicalPath(text);
+
+      List<BlazeValidationError> errors = Lists.newArrayList();
+      if (!WorkspacePath.validate(text, errors)) {
+        parseContext.addErrors(errors);
+        return null;
+      }
+      return new DirectoryEntry(new WorkspacePath(text), !excluded);
+    }
+
+    @Override
+    protected void printItem(@NotNull DirectoryEntry item, @NotNull StringBuilder sb) {
+      sb.append(item.toString());
+    }
+
+    @Override
+    public ItemType getItemType() {
+      return ItemType.FileSystemItem;
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/projectview/section/sections/ExcludeTargetSection.java b/base/src/com/google/idea/blaze/base/projectview/section/sections/ExcludeTargetSection.java
new file mode 100644
index 0000000..9a30791
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/projectview/section/sections/ExcludeTargetSection.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.projectview.section.sections;
+
+import com.google.idea.blaze.base.model.primitives.Label;
+import com.google.idea.blaze.base.projectview.section.LabelSectionParser;
+import com.google.idea.blaze.base.projectview.section.ListSection;
+import com.google.idea.blaze.base.projectview.section.SectionKey;
+import com.google.idea.blaze.base.projectview.section.SectionParser;
+
+/** Excludes a target. */
+public class ExcludeTargetSection {
+  public static final SectionKey<Label, ListSection<Label>> KEY = SectionKey.of("exclude_target");
+  public static final SectionParser PARSER = new LabelSectionParser(KEY);
+}
diff --git a/base/src/com/google/idea/blaze/base/projectview/section/sections/ExcludedSourceSection.java b/base/src/com/google/idea/blaze/base/projectview/section/sections/ExcludedSourceSection.java
new file mode 100644
index 0000000..de6081f
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/projectview/section/sections/ExcludedSourceSection.java
@@ -0,0 +1,35 @@
+/*
+ * 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.projectview.section.sections;
+
+import com.google.idea.blaze.base.projectview.section.Glob;
+import com.google.idea.blaze.base.projectview.section.GlobSectionParser;
+import com.google.idea.blaze.base.projectview.section.ListSection;
+import com.google.idea.blaze.base.projectview.section.SectionKey;
+import com.google.idea.blaze.base.projectview.section.SectionParser;
+
+/** Section for excluding source files. */
+@Deprecated
+public class ExcludedSourceSection {
+  public static final SectionKey<Glob, ListSection<Glob>> KEY = SectionKey.of("excluded_sources");
+  public static final SectionParser PARSER =
+      new GlobSectionParser(KEY) {
+        @Override
+        public boolean isDeprecated() {
+          return true;
+        }
+      };
+}
diff --git a/base/src/com/google/idea/blaze/base/projectview/section/sections/ImportSection.java b/base/src/com/google/idea/blaze/base/projectview/section/sections/ImportSection.java
new file mode 100644
index 0000000..e377b09
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/projectview/section/sections/ImportSection.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.projectview.section.sections;
+
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
+import com.google.idea.blaze.base.projectview.parser.ParseContext;
+import com.google.idea.blaze.base.projectview.parser.ProjectViewParser;
+import com.google.idea.blaze.base.projectview.section.ScalarSection;
+import com.google.idea.blaze.base.projectview.section.ScalarSectionParser;
+import com.google.idea.blaze.base.projectview.section.SectionKey;
+import com.google.idea.blaze.base.projectview.section.SectionParser;
+import com.google.idea.blaze.base.ui.BlazeValidationError;
+import java.io.File;
+import java.util.List;
+import org.jetbrains.annotations.Nullable;
+
+/** "import" section. */
+public class ImportSection {
+  public static final SectionKey<WorkspacePath, ScalarSection<WorkspacePath>> KEY =
+      SectionKey.of("import");
+  public static final SectionParser PARSER = new ImportSectionParser();
+
+  private static class ImportSectionParser extends ScalarSectionParser<WorkspacePath> {
+    public ImportSectionParser() {
+      super(KEY, ' ');
+    }
+
+    @Nullable
+    @Override
+    protected WorkspacePath parseItem(
+        ProjectViewParser parser, ParseContext parseContext, String text) {
+      List<BlazeValidationError> errors = Lists.newArrayList();
+      if (!WorkspacePath.validate(text, errors)) {
+        parseContext.addErrors(errors);
+        return null;
+      }
+
+      WorkspacePath workspacePath = new WorkspacePath(text);
+      if (parser.isRecursive()) {
+        File projectViewFile = parseContext.getWorkspacePathResolver().resolveToFile(workspacePath);
+        if (projectViewFile != null) {
+          parser.parseProjectView(projectViewFile);
+        } else {
+          parseContext.addError("Could not resolve import: " + workspacePath);
+        }
+      }
+      return workspacePath;
+    }
+
+    @Override
+    protected void printItem(StringBuilder sb, WorkspacePath section) {
+      sb.append(section.toString());
+    }
+
+    @Override
+    public ItemType getItemType() {
+      return ItemType.FileSystemItem;
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/projectview/section/sections/ImportTargetOutputSection.java b/base/src/com/google/idea/blaze/base/projectview/section/sections/ImportTargetOutputSection.java
new file mode 100644
index 0000000..95d343b
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/projectview/section/sections/ImportTargetOutputSection.java
@@ -0,0 +1,29 @@
+/*
+ * 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.projectview.section.sections;
+
+import com.google.idea.blaze.base.model.primitives.Label;
+import com.google.idea.blaze.base.projectview.section.LabelSectionParser;
+import com.google.idea.blaze.base.projectview.section.ListSection;
+import com.google.idea.blaze.base.projectview.section.SectionKey;
+import com.google.idea.blaze.base.projectview.section.SectionParser;
+
+/** Forces target output import of mentioned targets. */
+public class ImportTargetOutputSection {
+  public static final SectionKey<Label, ListSection<Label>> KEY =
+      SectionKey.of("import_target_output");
+  public static final SectionParser PARSER = new LabelSectionParser(KEY);
+}
diff --git a/base/src/com/google/idea/blaze/base/projectview/section/sections/ItemOrTextBlock.java b/base/src/com/google/idea/blaze/base/projectview/section/sections/ItemOrTextBlock.java
new file mode 100644
index 0000000..1f75a44
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/projectview/section/sections/ItemOrTextBlock.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.base.projectview.section.sections;
+
+import com.google.common.base.Objects;
+import java.io.Serializable;
+import javax.annotation.Nullable;
+
+/** Union of an item or comment block */
+public class ItemOrTextBlock<T> implements Serializable {
+  private static final long serialVersionUID = 2L;
+  @Nullable public final T item;
+  @Nullable public final TextBlock textBlock;
+
+  public ItemOrTextBlock(T item) {
+    this(item, null);
+  }
+
+  public ItemOrTextBlock(TextBlock comment) {
+    this(null, comment);
+  }
+
+  private ItemOrTextBlock(@Nullable T item, @Nullable TextBlock comment) {
+    this.item = item;
+    this.textBlock = comment;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    ItemOrTextBlock<?> that = (ItemOrTextBlock<?>) o;
+    return Objects.equal(item, that.item) && Objects.equal(textBlock, that.textBlock);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(item, textBlock);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/projectview/section/sections/MetricsProjectSection.java b/base/src/com/google/idea/blaze/base/projectview/section/sections/MetricsProjectSection.java
new file mode 100644
index 0000000..3a0d6c8
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/projectview/section/sections/MetricsProjectSection.java
@@ -0,0 +1,54 @@
+/*
+ * 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.projectview.section.sections;
+
+import com.google.idea.blaze.base.projectview.parser.ParseContext;
+import com.google.idea.blaze.base.projectview.parser.ProjectViewParser;
+import com.google.idea.blaze.base.projectview.section.ScalarSection;
+import com.google.idea.blaze.base.projectview.section.ScalarSectionParser;
+import com.google.idea.blaze.base.projectview.section.SectionKey;
+import com.google.idea.blaze.base.projectview.section.SectionParser;
+import com.intellij.openapi.util.text.StringUtil;
+import javax.annotation.Nullable;
+
+/** Sets the metrics project to allow monitoring of individual projects */
+public class MetricsProjectSection {
+  public static final SectionKey<String, ScalarSection<String>> KEY =
+      SectionKey.of("metrics_project");
+  public static final SectionParser PARSER = new MetricsProjectSectionParser();
+
+  private static class MetricsProjectSectionParser extends ScalarSectionParser<String> {
+    public MetricsProjectSectionParser() {
+      super(KEY, ':');
+    }
+
+    @Nullable
+    @Override
+    protected String parseItem(ProjectViewParser parser, ParseContext parseContext, String rest) {
+      return StringUtil.unquoteString(rest);
+    }
+
+    @Override
+    protected void printItem(StringBuilder sb, String value) {
+      sb.append(value);
+    }
+
+    @Override
+    public ItemType getItemType() {
+      return ItemType.Other;
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/projectview/section/sections/Sections.java b/base/src/com/google/idea/blaze/base/projectview/section/sections/Sections.java
new file mode 100644
index 0000000..93b2fd9
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/projectview/section/sections/Sections.java
@@ -0,0 +1,52 @@
+/*
+ * 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.projectview.section.sections;
+
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.projectview.section.SectionParser;
+import com.google.idea.blaze.base.sync.BlazeSyncPlugin;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/** List of available sections. */
+public class Sections {
+  private static final List<SectionParser> PARSERS =
+      Lists.newArrayList(
+          TextBlockSection.PARSER,
+          ImportSection.PARSER,
+          DirectorySection.PARSER,
+          TargetSection.PARSER,
+          WorkspaceTypeSection.PARSER,
+          AdditionalLanguagesSection.PARSER,
+          TestSourceSection.PARSER,
+          BuildFlagsSection.PARSER,
+          ImportTargetOutputSection.PARSER,
+          ExcludeTargetSection.PARSER,
+          ExcludedSourceSection.PARSER,
+          MetricsProjectSection.PARSER);
+
+  public static List<SectionParser> getParsers() {
+    List<SectionParser> parsers = Lists.newArrayList(PARSERS);
+    for (BlazeSyncPlugin syncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) {
+      parsers.addAll(syncPlugin.getSections());
+    }
+    return parsers;
+  }
+
+  public static List<SectionParser> getUndeprecatedParsers() {
+    return getParsers().stream().filter(p -> !p.isDeprecated()).collect(Collectors.toList());
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/projectview/section/sections/TargetSection.java b/base/src/com/google/idea/blaze/base/projectview/section/sections/TargetSection.java
new file mode 100644
index 0000000..5bd3361
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/projectview/section/sections/TargetSection.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.projectview.section.sections;
+
+import com.google.idea.blaze.base.model.primitives.TargetExpression;
+import com.google.idea.blaze.base.projectview.parser.ParseContext;
+import com.google.idea.blaze.base.projectview.parser.ProjectViewParser;
+import com.google.idea.blaze.base.projectview.section.ListSection;
+import com.google.idea.blaze.base.projectview.section.ListSectionParser;
+import com.google.idea.blaze.base.projectview.section.SectionKey;
+import com.google.idea.blaze.base.projectview.section.SectionParser;
+
+/** "targets" section. */
+public class TargetSection {
+  public static final SectionKey<TargetExpression, ListSection<TargetExpression>> KEY =
+      SectionKey.of("targets");
+  public static final SectionParser PARSER = new TargetSectionParser();
+
+  private static class TargetSectionParser extends ListSectionParser<TargetExpression> {
+    public TargetSectionParser() {
+      super(KEY);
+    }
+
+    @Override
+    protected TargetExpression parseItem(ProjectViewParser parser, ParseContext parseContext) {
+      String text = parseContext.current().text;
+      return TargetExpression.fromString(text);
+    }
+
+    @Override
+    protected void printItem(TargetExpression item, StringBuilder sb) {
+      sb.append(item.toString());
+    }
+
+    @Override
+    public ItemType getItemType() {
+      return ItemType.Label;
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/projectview/section/sections/TestSourceSection.java b/base/src/com/google/idea/blaze/base/projectview/section/sections/TestSourceSection.java
new file mode 100644
index 0000000..44da7b0
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/projectview/section/sections/TestSourceSection.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.projectview.section.sections;
+
+import com.google.idea.blaze.base.projectview.section.Glob;
+import com.google.idea.blaze.base.projectview.section.GlobSectionParser;
+import com.google.idea.blaze.base.projectview.section.ListSection;
+import com.google.idea.blaze.base.projectview.section.SectionKey;
+import com.google.idea.blaze.base.projectview.section.SectionParser;
+
+/** Section for configuring test sources. */
+public class TestSourceSection {
+  public static final SectionKey<Glob, ListSection<Glob>> KEY = SectionKey.of("test_sources");
+  public static final SectionParser PARSER = new GlobSectionParser(KEY);
+}
diff --git a/base/src/com/google/idea/blaze/base/projectview/section/sections/TextBlock.java b/base/src/com/google/idea/blaze/base/projectview/section/sections/TextBlock.java
new file mode 100644
index 0000000..351b0c4
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/projectview/section/sections/TextBlock.java
@@ -0,0 +1,80 @@
+/*
+ * 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.projectview.section.sections;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
+import java.io.Serializable;
+
+/**
+ * A block of text, like comments or whitespace.
+ *
+ * <p>A text block will be entirely of one type (comment/whitespace), and should all have the same
+ * indentation.
+ */
+public class TextBlock implements Serializable {
+  private static final long serialVersionUID = 1L;
+  private final ImmutableList<String> lines;
+
+  public TextBlock(ImmutableList<String> lines) {
+    this.lines = lines;
+  }
+
+  /** Returns raw lines, including any indentation and surrounding whitespace */
+  public ImmutableList<String> lines() {
+    return lines;
+  }
+
+  public static TextBlock of(String... lines) {
+    return new TextBlock(ImmutableList.<String>builder().add(lines).build());
+  }
+
+  /** A text block that is a single newline */
+  public static TextBlock newLine() {
+    return new TextBlock(ImmutableList.of(""));
+  }
+
+  public void print(StringBuilder sb) {
+    for (String line : lines) {
+      sb.append(line);
+      sb.append('\n');
+    }
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    TextBlock textBlock = (TextBlock) o;
+    return Objects.equal(lines, textBlock.lines);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(lines);
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder();
+    print(sb);
+    return sb.toString();
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/projectview/section/sections/TextBlockSection.java b/base/src/com/google/idea/blaze/base/projectview/section/sections/TextBlockSection.java
new file mode 100644
index 0000000..b12b781
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/projectview/section/sections/TextBlockSection.java
@@ -0,0 +1,141 @@
+/*
+ * 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.projectview.section.sections;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.base.projectview.parser.ParseContext;
+import com.google.idea.blaze.base.projectview.parser.ProjectViewParser;
+import com.google.idea.blaze.base.projectview.section.Section;
+import com.google.idea.blaze.base.projectview.section.SectionKey;
+import com.google.idea.blaze.base.projectview.section.SectionParser;
+import java.util.regex.Pattern;
+import javax.annotation.Nullable;
+
+/** Section with a text block. */
+public final class TextBlockSection extends Section<TextBlock> {
+  public static final SectionKey<TextBlock, TextBlockSection> KEY = SectionKey.of("textblock");
+  public static final TextBlockSectionParser PARSER = new TextBlockSectionParser();
+
+  private final TextBlock textBlock;
+
+  public TextBlockSection(TextBlock textBlock) {
+    super(KEY);
+    this.textBlock = textBlock;
+  }
+
+  public TextBlock getTextBlock() {
+    return textBlock;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    if (!super.equals(o)) {
+      return false;
+    }
+    TextBlockSection that = (TextBlockSection) o;
+    return Objects.equal(textBlock, that.textBlock);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(super.hashCode(), textBlock);
+  }
+
+  public static TextBlockSection of(TextBlock textBlock) {
+    return new TextBlockSection(textBlock);
+  }
+
+  /** Parses a single text block with equal indentation. */
+  public static TextBlock parseTextBlock(ParseContext parseContext) {
+    return TextBlockSectionParser.parseTextBlock(parseContext);
+  }
+
+  /** Text block section. */
+  private static final class TextBlockSectionParser extends SectionParser {
+    private static final Pattern COMMENT_REGEX = Pattern.compile("^\\s*#.*$");
+    private static final Pattern WHITESPACE_REGEX = Pattern.compile("^\\s*$");
+    private static final Pattern[] REGEXES = {COMMENT_REGEX, WHITESPACE_REGEX};
+
+    @Override
+    public SectionKey<TextBlock, TextBlockSection> getSectionKey() {
+      return KEY;
+    }
+
+    @Nullable
+    @Override
+    public Section<?> parse(ProjectViewParser parser, ParseContext parseContext) {
+      TextBlock textBlock = parseTextBlock(parseContext);
+      if (textBlock == null) {
+        return null;
+      }
+      return new TextBlockSection(textBlock);
+    }
+
+    @Nullable
+    private static TextBlock parseTextBlock(ParseContext parseContext) {
+      for (Pattern regex : REGEXES) {
+        TextBlock textBlock = parseTextBlock(parseContext, regex);
+        if (textBlock != null) {
+          return textBlock;
+        }
+      }
+      return null;
+    }
+
+    @Nullable
+    private static TextBlock parseTextBlock(ParseContext parseContext, Pattern regex) {
+      ImmutableList.Builder<String> lines = null;
+      int indent = -1;
+      while (!parseContext.atEnd()) {
+        if (indent >= 0 && parseContext.current().indent != indent) {
+          break;
+        }
+        if (!regex.matcher(parseContext.currentRawLine()).matches()) {
+          break;
+        }
+        // First line we match?
+        if (lines == null) {
+          lines = ImmutableList.builder();
+          indent = parseContext.current().indent;
+        }
+        lines.add(parseContext.currentRawLine());
+        parseContext.consume();
+      }
+      if (lines == null) {
+        return null;
+      }
+      return new TextBlock(lines.build());
+    }
+
+    @Override
+    public void print(StringBuilder sb, Section<?> section) {
+      TextBlockSection textBlockSection = (TextBlockSection) section;
+      textBlockSection.getTextBlock().print(sb);
+    }
+
+    @Override
+    public ItemType getItemType() {
+      return ItemType.Other;
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/projectview/section/sections/WorkspaceTypeSection.java b/base/src/com/google/idea/blaze/base/projectview/section/sections/WorkspaceTypeSection.java
new file mode 100644
index 0000000..b59266e
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/projectview/section/sections/WorkspaceTypeSection.java
@@ -0,0 +1,64 @@
+/*
+ * 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.projectview.section.sections;
+
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.model.primitives.WorkspaceType;
+import com.google.idea.blaze.base.projectview.parser.ParseContext;
+import com.google.idea.blaze.base.projectview.parser.ProjectViewParser;
+import com.google.idea.blaze.base.projectview.section.ScalarSection;
+import com.google.idea.blaze.base.projectview.section.ScalarSectionParser;
+import com.google.idea.blaze.base.projectview.section.SectionKey;
+import com.google.idea.blaze.base.projectview.section.SectionParser;
+import com.google.idea.blaze.base.ui.BlazeValidationError;
+import java.util.List;
+import javax.annotation.Nullable;
+
+/** The type of your workspace. */
+public class WorkspaceTypeSection {
+  public static final SectionKey<WorkspaceType, ScalarSection<WorkspaceType>> KEY =
+      SectionKey.of("workspace_type");
+  public static final SectionParser PARSER = new WorkspaceTypeSectionParser();
+
+  private static class WorkspaceTypeSectionParser extends ScalarSectionParser<WorkspaceType> {
+    public WorkspaceTypeSectionParser() {
+      super(KEY, ':');
+    }
+
+    @Override
+    @Nullable
+    protected WorkspaceType parseItem(
+        ProjectViewParser parser, ParseContext parseContext, String text) {
+      List<BlazeValidationError> errors = Lists.newArrayList();
+      WorkspaceType workspaceType = WorkspaceType.fromString(text);
+      if (workspaceType == null) {
+        parseContext.addError("Invalid workspace type: " + text);
+      }
+      parseContext.addErrors(errors);
+      return workspaceType;
+    }
+
+    @Override
+    protected void printItem(StringBuilder sb, WorkspaceType item) {
+      sb.append(item.toString());
+    }
+
+    @Override
+    public ItemType getItemType() {
+      return ItemType.Other;
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/rulemaps/ReverseDependencyMap.java b/base/src/com/google/idea/blaze/base/rulemaps/ReverseDependencyMap.java
new file mode 100644
index 0000000..0a55a8e
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/rulemaps/ReverseDependencyMap.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.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.model.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<Label, Label> createRdepsMap(RuleMap ruleMap) {
+    ImmutableMultimap.Builder<Label, Label> builder = ImmutableMultimap.builder();
+    for (RuleIdeInfo rule : ruleMap.rules()) {
+      Label label = rule.label;
+      for (Label dep : Iterables.concat(rule.dependencies, rule.runtimeDeps)) {
+        if (ruleMap.contains(dep)) {
+          builder.put(dep, label);
+        }
+      }
+    }
+    return builder.build();
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/rulemaps/SourceToRuleMap.java b/base/src/com/google/idea/blaze/base/rulemaps/SourceToRuleMap.java
new file mode 100644
index 0000000..6fcf01e
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/rulemaps/SourceToRuleMap.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.base.rulemaps;
+
+import com.google.common.collect.ImmutableCollection;
+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 {
+
+  static SourceToRuleMap getInstance(Project project) {
+    return ServiceManager.getService(project, SourceToRuleMap.class);
+  }
+
+  ImmutableCollection<Label> getTargetsForSourceFile(File file);
+}
diff --git a/base/src/com/google/idea/blaze/base/rulemaps/SourceToRuleMapImpl.java b/base/src/com/google/idea/blaze/base/rulemaps/SourceToRuleMapImpl.java
new file mode 100644
index 0000000..56fd32f
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/rulemaps/SourceToRuleMapImpl.java
@@ -0,0 +1,93 @@
+/*
+ * 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.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.model.BlazeProjectData;
+import com.google.idea.blaze.base.model.primitives.Label;
+import com.google.idea.blaze.base.projectview.ProjectViewSet;
+import com.google.idea.blaze.base.settings.BlazeImportSettings;
+import com.google.idea.blaze.base.sync.SyncListener;
+import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.project.Project;
+import java.io.File;
+import javax.annotation.Nullable;
+
+/** Maps source files to their respective targets */
+public class SourceToRuleMapImpl implements SourceToRuleMap {
+  private final Project project;
+  private ImmutableMultimap<File, Label> sourceToTargetMap;
+
+  public static SourceToRuleMapImpl getImpl(Project project) {
+    return (SourceToRuleMapImpl) ServiceManager.getService(project, SourceToRuleMap.class);
+  }
+
+  public SourceToRuleMapImpl(Project project) {
+    this.project = project;
+  }
+
+  @Override
+  public ImmutableCollection<Label> getTargetsForSourceFile(File file) {
+    ImmutableMultimap<File, Label> sourceToTargetMap = getSourceToTargetMap();
+    return sourceToTargetMap != null ? sourceToTargetMap.get(file) : ImmutableList.of();
+  }
+
+  @Nullable
+  private synchronized ImmutableMultimap<File, Label> getSourceToTargetMap() {
+    if (this.sourceToTargetMap == null) {
+      this.sourceToTargetMap = initSourceToTargetMap();
+    }
+    return this.sourceToTargetMap;
+  }
+
+  private synchronized void clearSourceToTargetMap() {
+    this.sourceToTargetMap = null;
+  }
+
+  @Nullable
+  private ImmutableMultimap<File, Label> initSourceToTargetMap() {
+    BlazeProjectData blazeProjectData =
+        BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
+    if (blazeProjectData == null) {
+      return null;
+    }
+    ImmutableMultimap.Builder<File, Label> sourceToTargetMap = ImmutableMultimap.builder();
+    for (RuleIdeInfo rule : blazeProjectData.ruleMap.rules()) {
+      Label label = rule.label;
+      for (ArtifactLocation sourceArtifact : rule.sources) {
+        sourceToTargetMap.put(sourceArtifact.getFile(), label);
+      }
+    }
+    return sourceToTargetMap.build();
+  }
+
+  static class ClearSourceToTargetMap extends SyncListener.Adapter {
+    @Override
+    public void onSyncComplete(
+        Project project,
+        BlazeImportSettings importSettings,
+        ProjectViewSet projectViewSet,
+        BlazeProjectData blazeProjectData,
+        SyncResult syncResult) {
+      getImpl(project).clearSourceToTargetMap();
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/run/BlazeBeforeRunTaskProvider.java b/base/src/com/google/idea/blaze/base/run/BlazeBeforeRunTaskProvider.java
new file mode 100644
index 0000000..aa9d617
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/run/BlazeBeforeRunTaskProvider.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.base.run;
+
+import com.google.idea.blaze.base.settings.Blaze;
+import com.intellij.execution.BeforeRunTask;
+import com.intellij.execution.BeforeRunTaskProvider;
+import com.intellij.execution.configurations.RunConfiguration;
+import com.intellij.execution.runners.ExecutionEnvironment;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.util.Key;
+import icons.BlazeIcons;
+import javax.annotation.Nullable;
+import javax.swing.Icon;
+
+/**
+ * Provides a before run task provider that immediately transfers control to {@link
+ * com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationHandler}
+ */
+public final class BlazeBeforeRunTaskProvider
+    extends BeforeRunTaskProvider<BlazeBeforeRunTaskProvider.Task> {
+
+  public static final Key<Task> ID = Key.create("Blaze.BeforeRunTask");
+
+  static class Task extends BeforeRunTask<Task> {
+    private Task() {
+      super(ID);
+      setEnabled(true);
+    }
+  }
+
+  @Override
+  public Key<Task> getId() {
+    return ID;
+  }
+
+  @Nullable
+  @Override
+  public Icon getIcon() {
+    return BlazeIcons.Blaze;
+  }
+
+  @Nullable
+  @Override
+  public Icon getTaskIcon(Task task) {
+    return BlazeIcons.Blaze;
+  }
+
+  @Override
+  public String getName() {
+    return Blaze.guessBuildSystemName() + " before-run task";
+  }
+
+  @Override
+  public String getDescription(Task task) {
+    return Blaze.guessBuildSystemName() + " before-run task";
+  }
+
+  @Override
+  public boolean isConfigurable() {
+    return false;
+  }
+
+  @Nullable
+  @Override
+  public Task createTask(RunConfiguration config) {
+    if (config instanceof BlazeCommandRunConfiguration) {
+      return new Task();
+    }
+    return null;
+  }
+
+  @Override
+  public boolean configureTask(RunConfiguration runConfiguration, Task task) {
+    return false;
+  }
+
+  @Override
+  public boolean canExecuteTask(RunConfiguration configuration, Task task) {
+    return configuration instanceof BlazeCommandRunConfiguration;
+  }
+
+  @Override
+  public boolean executeTask(
+      final DataContext dataContext,
+      final RunConfiguration configuration,
+      final ExecutionEnvironment env,
+      Task task) {
+    if (!canExecuteTask(configuration, task)) {
+      return false;
+    }
+    BlazeCommandRunConfiguration config = (BlazeCommandRunConfiguration) configuration;
+    return config.getHandler().executeBeforeRunTask(env);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/run/BlazeCommandRunConfiguration.java b/base/src/com/google/idea/blaze/base/run/BlazeCommandRunConfiguration.java
new file mode 100644
index 0000000..4d53ff5
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/run/BlazeCommandRunConfiguration.java
@@ -0,0 +1,421 @@
+/*
+ * 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.common.base.Strings;
+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.google.idea.blaze.base.model.primitives.TargetExpression;
+import com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationHandler;
+import com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationHandlerEditor;
+import com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationHandlerProvider;
+import com.google.idea.blaze.base.run.confighandler.BlazeUnknownRunConfigurationHandler;
+import com.google.idea.blaze.base.run.rulefinder.RuleFinder;
+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;
+import com.intellij.execution.ExecutionException;
+import com.intellij.execution.Executor;
+import com.intellij.execution.RunManager;
+import com.intellij.execution.RunnerIconProvider;
+import com.intellij.execution.configurations.ConfigurationFactory;
+import com.intellij.execution.configurations.LocatableConfigurationBase;
+import com.intellij.execution.configurations.RunConfiguration;
+import com.intellij.execution.configurations.RunProfileState;
+import com.intellij.execution.configurations.RuntimeConfigurationError;
+import com.intellij.execution.configurations.RuntimeConfigurationException;
+import com.intellij.execution.runners.ExecutionEnvironment;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.options.SettingsEditor;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.ui.components.JBLabel;
+import com.intellij.ui.components.JBTextField;
+import com.intellij.util.ui.UIUtil;
+import javax.annotation.Nullable;
+import javax.swing.Box;
+import javax.swing.Icon;
+import javax.swing.JComponent;
+import org.jdom.Element;
+import org.jetbrains.annotations.NotNull;
+
+/** 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";
+
+  // Null for configurations created since restart.
+  @Nullable private Element externalElementBackup;
+  // Null when there is no target.
+  @Nullable private TargetExpression target;
+  // Null if the target is null, not a Label, or not a known rule.
+  @Nullable private Kind targetKind;
+  private BlazeCommandRunConfigurationHandler handler;
+  // Null if the handler is BlazeUnknownRunConfigurationHandler.
+  @Nullable private BlazeCommandRunConfigurationHandlerProvider handlerProvider;
+
+  public BlazeCommandRunConfiguration(Project project, ConfigurationFactory factory, String name) {
+    super(project, factory, name);
+    handler = new BlazeUnknownRunConfigurationHandler(this);
+  }
+
+  /** @return The configuration's {@link BlazeCommandRunConfigurationHandler}. */
+  @NotNull
+  public BlazeCommandRunConfigurationHandler getHandler() {
+    return handler;
+  }
+
+  /**
+   * Gets the configuration's {@link BlazeCommandRunConfigurationHandler} if it is an instance of
+   * the given class; otherwise returns null.
+   */
+  @Nullable
+  public <T extends BlazeCommandRunConfigurationHandler> T getHandlerIfType(Class<T> type) {
+    if (type.isInstance(handler)) {
+      return type.cast(handler);
+    } else {
+      return null;
+    }
+  }
+
+  @Override
+  @Nullable
+  public TargetExpression getTarget() {
+    return target;
+  }
+
+  public void setTarget(@Nullable TargetExpression target) {
+    this.target = target;
+    RuleIdeInfo rule = getRuleForTarget();
+    targetKind = rule != null ? rule.kind : null;
+
+    BlazeCommandRunConfigurationHandlerProvider handlerProvider =
+        BlazeCommandRunConfigurationHandlerProvider.findHandlerProvider(targetKind);
+    setHandlerIfDifferentProvider(handlerProvider);
+  }
+
+  private void setHandlerIfDifferentProvider(
+      BlazeCommandRunConfigurationHandlerProvider newProvider) {
+    // Only change the handler if the provider has changed.
+    if (handlerProvider != newProvider) {
+      handlerProvider = newProvider;
+      handler = newProvider.createHandler(this);
+    }
+  }
+
+  /**
+   * Returns the single blaze target corresponding to the configuration's target expression, if one
+   * exists. Returns null if the target expression points to multiple blaze targets, or wasn't
+   * included in the latest sync.
+   */
+  @Nullable
+  public RuleIdeInfo getRuleForTarget() {
+    if (target instanceof Label) {
+      return RuleFinder.getInstance().ruleForTarget(getProject(), (Label) target);
+    }
+    return null;
+  }
+
+  /**
+   * @return The {@link Kind} name, if the target is a known rule. Otherwise, "target pattern" if it
+   *     is a general {@link TargetExpression}, "unknown rule" if it is a {@link Label} without a
+   *     known rule, and "unknown target" if there is no target.
+   */
+  public String getTargetKindName() {
+    RuleIdeInfo rule = getRuleForTarget();
+    if (rule != null) {
+      return rule.kind.toString();
+    } else if (target instanceof Label) {
+      return "unknown rule";
+    } else if (target != null) {
+      return "target pattern";
+    } else {
+      return "unknown target";
+    }
+  }
+
+  // TODO This method can be private after BlazeCommandRunConfigurationUpdater is removed.
+  void loadExternalElementBackup() {
+    if (externalElementBackup != null) {
+      try {
+        handler.readExternal(externalElementBackup);
+      } catch (InvalidDataException e) {
+        // This is what IntelliJ does when getting this exception while loading a configuration.
+        LOG.error(e);
+      }
+    }
+  }
+
+  @Override
+  public void checkConfiguration() throws RuntimeConfigurationException {
+    // Our handler check and its quick fix are not valid when we don't have BlazeProjectData.
+    if (BlazeProjectDataManager.getInstance(getProject()).getBlazeProjectData() == null) {
+      throw new RuntimeConfigurationError(
+          "Configuration cannot be used or modified while project is syncing.");
+    }
+    if (isConfigurationInvalidated()) {
+      throw new RuntimeConfigurationError(
+          "A property of the target unexpectedly changed. The configuration must be updated. "
+              + "Some configuration settings may be lost.",
+          () -> {
+            BlazeCommandRunConfigurationHandler oldHandler = handler;
+            setTarget(target);
+            if (handler != oldHandler) {
+              loadExternalElementBackup();
+            }
+          });
+    }
+    if (target == null) {
+      throw new RuntimeConfigurationError(
+          String.format(
+              "You must specify a %s target expression.", Blaze.buildSystemName(getProject())));
+    }
+    if (!target.toString().startsWith("//")) {
+      throw new RuntimeConfigurationError(
+          "You must specify the full target expression, starting with //");
+    }
+    handler.checkConfiguration();
+  }
+
+  private boolean isConfigurationInvalidated() {
+    boolean configurationInvalidated = handler instanceof BlazeUnknownRunConfigurationHandler;
+    if (!configurationInvalidated) {
+      RuleIdeInfo rule = getRuleForTarget();
+      Kind expectedKind = rule != null ? rule.kind : null;
+      configurationInvalidated = targetKind != expectedKind;
+    }
+    if (!configurationInvalidated) {
+      configurationInvalidated =
+          handlerProvider
+              != BlazeCommandRunConfigurationHandlerProvider.findHandlerProvider(targetKind);
+    }
+    return configurationInvalidated;
+  }
+
+  @Override
+  public void readExternal(Element element) throws InvalidDataException {
+    super.readExternal(element);
+    externalElementBackup = element.clone();
+    // Target is persisted as a tag to permit multiple targets in the future.
+    Element targetElement = element.getChild(TARGET_TAG);
+    if (targetElement != null && !Strings.isNullOrEmpty(targetElement.getTextTrim())) {
+      target = TargetExpression.fromString(targetElement.getTextTrim());
+      targetKind = Kind.fromString(targetElement.getAttributeValue(KIND_ATTR));
+    } else {
+      // Legacy: Added in 1.9 to support reading target as an attribute so
+      // BlazeAndroid(Binary/Test)RunConfiguration elements can be read.
+      // TODO remove in 2.1 once BlazeAndroidBinaryRunConfigurationType and
+      // BlazeAndroidTestRunConfigurationType have been removed.
+      String targetString =
+          element.getAttributeValue(
+              TARGET_TAG); // The attribute ID happens to be identical to the tag ID.
+      if (targetString != null) {
+        target = TargetExpression.fromString(targetString);
+        // Once the above is removed, 'target = null;' should be
+        // the only thing in the outer else clause.
+      } else {
+        target = null;
+      }
+    }
+    // Because BlazeProjectData is not available when configurations are loading,
+    // we can't call setTarget and have it find the appropriate handler provider.
+    // So instead, we use the stored provider ID.
+    String providerId = element.getAttributeValue(HANDLER_ATTR);
+    BlazeCommandRunConfigurationHandlerProvider handlerProvider =
+        BlazeCommandRunConfigurationHandlerProvider.getHandlerProvider(providerId);
+    if (handlerProvider != null) {
+      setHandlerIfDifferentProvider(handlerProvider);
+    }
+    handler.readExternal(element);
+  }
+
+  @Override
+  @SuppressWarnings("ThrowsUncheckedException")
+  public void writeExternal(Element element) throws WriteExternalException {
+    super.writeExternal(element);
+    // We can't write externalElementBackup contents; doing so would cause the configuration
+    // xml to retain duplicate elements and grow across reopenings.
+    // We also can't use the approach in BlazeUnknownRunConfigurationHandler;
+    // this can revive intentionally deleted attributes/elements such as user flags.
+    if (target != null) {
+      // Target is persisted as a tag to permit multiple targets in the future.
+      Element targetElement = new Element(TARGET_TAG);
+      targetElement.setText(target.toString());
+      if (targetKind != null) {
+        targetElement.setAttribute(KIND_ATTR, targetKind.toString());
+      }
+      element.addContent(targetElement);
+    }
+    if (handlerProvider != null) {
+      element.setAttribute(HANDLER_ATTR, handlerProvider.getId());
+    }
+    handler.writeExternal(element);
+  }
+
+  @Override
+  public BlazeCommandRunConfiguration clone() {
+    final BlazeCommandRunConfiguration configuration = (BlazeCommandRunConfiguration) super.clone();
+    if (externalElementBackup != null) {
+      configuration.externalElementBackup = externalElementBackup.clone();
+    }
+    configuration.target = target;
+    configuration.targetKind = targetKind;
+    configuration.handler = handler.cloneFor(configuration);
+    configuration.handlerProvider = handlerProvider;
+    return configuration;
+  }
+
+  @Override
+  @Nullable
+  public RunProfileState getState(
+      @NotNull Executor executor, @NotNull ExecutionEnvironment environment)
+      throws ExecutionException {
+    return handler.getState(executor, environment);
+  }
+
+  @Override
+  @Nullable
+  public String suggestedName() {
+    return handler.suggestedName();
+  }
+
+  @Override
+  public boolean isGeneratedName() {
+    return handler.isGeneratedName(super.isGeneratedName());
+  }
+
+  @Override
+  @Nullable
+  public Icon getExecutorIcon(@NotNull RunConfiguration configuration, @NotNull Executor executor) {
+    return handler.getExecutorIcon(configuration, executor);
+  }
+
+  @Override
+  @NotNull
+  public SettingsEditor<? extends BlazeCommandRunConfiguration> getConfigurationEditor() {
+    return new BlazeCommandRunConfigurationSettingsEditor(this);
+  }
+
+  static class BlazeCommandRunConfigurationSettingsEditor
+      extends SettingsEditor<BlazeCommandRunConfiguration> {
+    @Nullable private BlazeCommandRunConfigurationHandlerProvider handlerProvider;
+    private BlazeCommandRunConfigurationHandlerEditor handlerEditor;
+    @Nullable private JComponent handlerComponent;
+
+    private final Box editor;
+    private final JBLabel targetExpressionLabel;
+    private final JBTextField targetField = new JBTextField(1);
+
+    private boolean isEditable;
+
+    public BlazeCommandRunConfigurationSettingsEditor(BlazeCommandRunConfiguration config) {
+      targetExpressionLabel = new JBLabel(UIUtil.ComponentStyle.LARGE);
+      editor = UiUtil.createBox(targetExpressionLabel, targetField);
+      targetField.getEmptyText().setText("Full target expression starting with //");
+      updateTargetExpressionLabel(config);
+      updateHandlerEditor(config);
+      setEditable(isConfigurationEditable(config));
+    }
+
+    private static boolean isConfigurationEditable(BlazeCommandRunConfiguration config) {
+      RunConfiguration template =
+          RunManager.getInstance(config.getProject())
+              .getConfigurationTemplate(config.getFactory())
+              .getConfiguration();
+      if (config == template) {
+        return true; // The default template is always editable.
+      }
+      return BlazeProjectDataManager.getInstance(config.getProject()).getBlazeProjectData() != null
+          && !config.isConfigurationInvalidated();
+    }
+
+    private void setEditable(boolean editable) {
+      isEditable = editable;
+      targetField.setEnabled(isEditable);
+      if (handlerComponent != null) {
+        handlerComponent.setVisible(isEditable);
+      }
+    }
+
+    private void updateTargetExpressionLabel(BlazeCommandRunConfiguration config) {
+      targetExpressionLabel.setText(
+          String.format(
+              "Target expression (%s handled by %s):",
+              config.getTargetKindName(), config.handler.getHandlerName()));
+    }
+
+    private void updateHandlerEditor(BlazeCommandRunConfiguration config) {
+      handlerProvider = config.handlerProvider;
+      handlerEditor = config.handler.getHandlerEditor();
+
+      if (handlerComponent != null) {
+        editor.remove(handlerComponent);
+      }
+      handlerComponent = handlerEditor.createEditor();
+      if (handlerComponent != null) {
+        editor.add(handlerComponent);
+      }
+    }
+
+    @Override
+    @NotNull
+    protected JComponent createEditor() {
+      return editor;
+    }
+
+    @Override
+    protected void resetEditorFrom(BlazeCommandRunConfiguration config) {
+      updateTargetExpressionLabel(config);
+      if (config.handlerProvider != handlerProvider) {
+        updateHandlerEditor(config);
+      }
+      setEditable(isConfigurationEditable(config));
+      targetField.setText(config.target == null ? null : config.target.toString());
+      handlerEditor.resetEditorFrom(config.handler);
+    }
+
+    @Override
+    protected void applyEditorTo(BlazeCommandRunConfiguration config) {
+      if (!isEditable) {
+        return;
+      }
+      applyTarget(config);
+      updateTargetExpressionLabel(config);
+      if (config.handlerProvider != handlerProvider) {
+        updateHandlerEditor(config);
+        handlerEditor.resetEditorFrom(config.handler);
+      } else {
+        handlerEditor.applyEditorTo(config.handler);
+      }
+    }
+
+    private void applyTarget(BlazeCommandRunConfiguration config) {
+      String targetString = targetField.getText();
+      BlazeCommandRunConfigurationHandler oldHandler = config.handler;
+      config.setTarget(
+          Strings.isNullOrEmpty(targetString) ? null : TargetExpression.fromString(targetString));
+      if (config.handler != oldHandler) {
+        config.loadExternalElementBackup();
+      }
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/run/BlazeCommandRunConfigurationType.java b/base/src/com/google/idea/blaze/base/run/BlazeCommandRunConfigurationType.java
new file mode 100644
index 0000000..83ac98d
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/run/BlazeCommandRunConfigurationType.java
@@ -0,0 +1,103 @@
+/*
+ * 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.settings.Blaze;
+import com.intellij.execution.BeforeRunTask;
+import com.intellij.execution.configurations.ConfigurationFactory;
+import com.intellij.execution.configurations.ConfigurationType;
+import com.intellij.execution.configurations.ConfigurationTypeUtil;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Key;
+import icons.BlazeIcons;
+import javax.swing.Icon;
+import org.jetbrains.annotations.NotNull;
+
+/** A type for run configurations that execute Blaze commands. */
+public class BlazeCommandRunConfigurationType implements ConfigurationType {
+  @NotNull
+  private final BlazeCommandRunConfigurationFactory factory =
+      new BlazeCommandRunConfigurationFactory(this);
+
+  /** A run configuration factory */
+  public static class BlazeCommandRunConfigurationFactory extends ConfigurationFactory {
+    protected BlazeCommandRunConfigurationFactory(@NotNull ConfigurationType type) {
+      super(type);
+    }
+
+    @Override
+    public boolean isApplicable(@NotNull Project project) {
+      return Blaze.isBlazeProject(project);
+    }
+
+    @Override
+    public BlazeCommandRunConfiguration createTemplateConfiguration(Project project) {
+      return new BlazeCommandRunConfiguration(project, this, "Unnamed");
+    }
+
+    @Override
+    public void configureBeforeRunTaskDefaults(
+        Key<? extends BeforeRunTask> providerID, BeforeRunTask task) {
+      task.setEnabled(providerID.equals(BlazeBeforeRunTaskProvider.ID));
+    }
+
+    @Override
+    public boolean isConfigurationSingletonByDefault() {
+      return true;
+    }
+  }
+
+  @NotNull
+  public static BlazeCommandRunConfigurationType getInstance() {
+    return ConfigurationTypeUtil.findConfigurationType(BlazeCommandRunConfigurationType.class);
+  }
+
+  @NotNull
+  @Override
+  public String getDisplayName() {
+    return Blaze.defaultBuildSystemName() + " Command";
+  }
+
+  @NotNull
+  @Override
+  public String getConfigurationTypeDescription() {
+    return String.format(
+        "Configuration for launching arbitrary %s commands.", Blaze.guessBuildSystemName());
+  }
+
+  @NotNull
+  @Override
+  public Icon getIcon() {
+    return BlazeIcons.Blaze;
+  }
+
+  @NotNull
+  @Override
+  public String getId() {
+    return "BlazeCommandRunConfigurationType";
+  }
+
+  @NotNull
+  @Override
+  public BlazeCommandRunConfigurationFactory[] getConfigurationFactories() {
+    return new BlazeCommandRunConfigurationFactory[] {factory};
+  }
+
+  @NotNull
+  public BlazeCommandRunConfigurationFactory getFactory() {
+    return factory;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/run/BlazeCommandRunConfigurationUpdater.java b/base/src/com/google/idea/blaze/base/run/BlazeCommandRunConfigurationUpdater.java
new file mode 100644
index 0000000..c04c988
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/run/BlazeCommandRunConfigurationUpdater.java
@@ -0,0 +1,59 @@
+/*
+ * 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.model.BlazeProjectData;
+import com.google.idea.blaze.base.model.primitives.Label;
+import com.google.idea.blaze.base.projectview.ProjectViewSet;
+import com.google.idea.blaze.base.run.confighandler.BlazeUnknownRunConfigurationHandler;
+import com.google.idea.blaze.base.settings.BlazeImportSettings;
+import com.google.idea.blaze.base.sync.SyncListener;
+import com.intellij.execution.RunManager;
+import com.intellij.execution.configurations.RunConfiguration;
+import com.intellij.openapi.project.Project;
+
+/**
+ * Added in 1.9 to facilitate updating existing configurations to include the new handler-id and
+ * kind attributes. To be removed in 2.1.
+ */
+public class BlazeCommandRunConfigurationUpdater extends SyncListener.Adapter {
+  @Override
+  public void onSyncComplete(
+      Project project,
+      BlazeImportSettings importSettings,
+      ProjectViewSet projectViewSet,
+      BlazeProjectData blazeProjectData,
+      SyncResult syncResult) {
+    final RunManager runManager = RunManager.getInstance(project);
+    for (RunConfiguration configuration : runManager.getAllConfigurationsList()) {
+      if (configuration instanceof BlazeCommandRunConfiguration) {
+        BlazeCommandRunConfiguration blazeConfig = (BlazeCommandRunConfiguration) configuration;
+        // Only update configurations with unknown handlers, as this will
+        // reset any changes made to the handler data since the project loaded.
+        if (blazeConfig.getHandler() instanceof BlazeUnknownRunConfigurationHandler
+            // Also skip unresolved Label targets; these cannot safely be defaulted
+            // to the generic handler and should continue to display an error instead.
+            // If the Blaze cache is invalidated, all Labels can be unresolved;
+            // blindly updating them would result in loss of handler settings.
+            && !(blazeConfig.getTarget() instanceof Label
+                && blazeConfig.getRuleForTarget() == null)) {
+          blazeConfig.setTarget(blazeConfig.getTarget());
+          blazeConfig.loadExternalElementBackup();
+        }
+      }
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/run/BlazeConfigurationNameBuilder.java b/base/src/com/google/idea/blaze/base/run/BlazeConfigurationNameBuilder.java
new file mode 100644
index 0000000..4b7c9d0
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/run/BlazeConfigurationNameBuilder.java
@@ -0,0 +1,118 @@
+/*
+ * 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.model.primitives.Label;
+import com.google.idea.blaze.base.model.primitives.TargetExpression;
+import com.google.idea.blaze.base.settings.Blaze;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.text.StringUtil;
+import java.util.Arrays;
+
+/**
+ * Utility class for creating Blaze configuration names of the form "{build system name} {command
+ * name} {target string}", where each component is optional.
+ */
+public class BlazeConfigurationNameBuilder {
+  private String buildSystemName;
+  private String commandName;
+  private String targetString;
+
+  public BlazeConfigurationNameBuilder() {}
+
+  /**
+   * Use the passed {@code configuration} to initialize the build system name, command name, and
+   * target string. If the configuration's command name is null, this will default to "command". If
+   * the configuration's target is null, target string will also be null.
+   */
+  public BlazeConfigurationNameBuilder(BlazeCommandRunConfiguration configuration) {
+    setBuildSystemName(configuration.getProject());
+
+    String commandName = configuration.getHandler().getCommandName();
+    setCommandName((commandName == null) ? "command" : commandName);
+
+    TargetExpression targetExpression = configuration.getTarget();
+    if (targetExpression != null) {
+      setTargetString(targetExpression);
+    }
+  }
+
+  /**
+   * Sets the build system name to the name of the build system used by {@code project}, e.g.
+   * "Blaze" or "Bazel".
+   */
+  public BlazeConfigurationNameBuilder setBuildSystemName(Project project) {
+    buildSystemName = Blaze.buildSystemName(project);
+    return this;
+  }
+
+  /** Sets the command name to {@code commandName}. */
+  public BlazeConfigurationNameBuilder setCommandName(String commandName) {
+    this.commandName = commandName;
+    return this;
+  }
+
+  /** Sets the target string to {@code targetString}. */
+  public BlazeConfigurationNameBuilder setTargetString(String targetString) {
+    this.targetString = targetString;
+    return this;
+  }
+
+  /**
+   * Sets the target string to a string of the form "{package}:{target}", where 'target' is {@code
+   * label}'s target, and the 'package' is the containing package. For example, the {@link Label}
+   * "//javatests/com/google/foo/bar/baz:FooTest" will set the target string to "baz:FooTest".
+   */
+  public BlazeConfigurationNameBuilder setTargetString(Label label) {
+    this.targetString =
+        String.format("%s:%s", getImmediatePackage(label), label.ruleName().toString());
+    return this;
+  }
+
+  /**
+   * If {@code targetExpression} is a {@link Label}, this is equivalent to {@link
+   * #setTargetString(Label)}. Otherwise, the target string is set to the string value of {@code
+   * targetExpression}.
+   */
+  public BlazeConfigurationNameBuilder setTargetString(TargetExpression targetExpression) {
+    if (targetExpression instanceof Label) {
+      return setTargetString((Label) targetExpression);
+    }
+    return setTargetString(targetExpression.toString());
+  }
+
+  /**
+   * Get the portion of a label between the colon and the preceding slash. Example:
+   * "//javatests/com/google/foo/bar/baz:FooTest" -> "baz".
+   */
+  private static String getImmediatePackage(Label label) {
+    String labelString = label.toString();
+    int colonIndex = labelString.lastIndexOf(':');
+    assert colonIndex >= 0;
+    int slashIndex = labelString.lastIndexOf('/', colonIndex);
+    assert slashIndex >= 0;
+    return labelString.substring(slashIndex + 1, colonIndex);
+  }
+
+  /**
+   * Builds a name of the form "{build system name} {command name} {target string}". Any null
+   * components are omitted, and there is always one space inserted between each included component.
+   */
+  public String build() {
+    // Use this instead of String.join to omit null terms.
+    return StringUtil.join(Arrays.asList(buildSystemName, commandName, targetString), " ");
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/run/BlazeRuleConfigurationFactory.java b/base/src/com/google/idea/blaze/base/run/BlazeRuleConfigurationFactory.java
new file mode 100644
index 0000000..f430596
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/run/BlazeRuleConfigurationFactory.java
@@ -0,0 +1,60 @@
+/*
+ * 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.sync.projectview.WorkspaceLanguageSettings;
+import com.intellij.execution.RunManager;
+import com.intellij.execution.RunnerAndConfigurationSettings;
+import com.intellij.execution.configurations.ConfigurationFactory;
+import com.intellij.execution.configurations.RunConfiguration;
+import com.intellij.openapi.extensions.ExtensionPointName;
+import com.intellij.openapi.project.Project;
+
+/** A factory creating run configurations based on Blaze rules. */
+public abstract class BlazeRuleConfigurationFactory {
+  public static final ExtensionPointName<BlazeRuleConfigurationFactory> EP_NAME =
+      ExtensionPointName.create("com.google.idea.blaze.RuleConfigurationFactory");
+
+  /** Returns whether this factory can handle a rule. */
+  public abstract boolean handlesRule(
+      WorkspaceLanguageSettings workspaceLanguageSettings, RuleIdeInfo rule);
+
+  /**
+   * Returns whether this factory can initialize a configuration. <br>
+   * The default implementation simply checks that the configuration has the same {@link
+   * com.intellij.execution.configurations.ConfigurationType} as the type of {@link
+   * #getConfigurationFactory()}.
+   */
+  public boolean handlesConfiguration(RunConfiguration configuration) {
+    return getConfigurationFactory().getType().equals(configuration.getType());
+  }
+
+  /** Constructs and initializes {@link RunnerAndConfigurationSettings} for the given rule. */
+  public RunnerAndConfigurationSettings createForRule(
+      Project project, RunManager runManager, RuleIdeInfo rule) {
+    ConfigurationFactory factory = getConfigurationFactory();
+    RunConfiguration configuration = factory.createTemplateConfiguration(project, runManager);
+    setupConfiguration(configuration, rule);
+    return runManager.createConfiguration(configuration, factory);
+  }
+
+  /** The factory used to create configurations. */
+  protected abstract ConfigurationFactory getConfigurationFactory();
+
+  /** Initialize the configuration for the given rule. */
+  public abstract void setupConfiguration(RunConfiguration configuration, RuleIdeInfo rule);
+}
diff --git a/base/src/com/google/idea/blaze/base/run/BlazeRunConfiguration.java b/base/src/com/google/idea/blaze/base/run/BlazeRunConfiguration.java
new file mode 100644
index 0000000..f3409af
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/run/BlazeRunConfiguration.java
@@ -0,0 +1,25 @@
+/*
+ * 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.model.primitives.TargetExpression;
+import org.jetbrains.annotations.Nullable;
+
+/** Marker interface for all run configurations */
+public interface BlazeRunConfiguration {
+  @Nullable
+  TargetExpression getTarget();
+}
diff --git a/base/src/com/google/idea/blaze/base/run/BlazeRunConfigurationSyncListener.java b/base/src/com/google/idea/blaze/base/run/BlazeRunConfigurationSyncListener.java
new file mode 100755
index 0000000..ea4aae6
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/run/BlazeRunConfigurationSyncListener.java
@@ -0,0 +1,113 @@
+/*
+ * 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.common.collect.Sets;
+import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+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.projectview.section.sections.TargetSection;
+import com.google.idea.blaze.base.settings.BlazeImportSettings;
+import com.google.idea.blaze.base.sync.SyncListener;
+import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
+import com.intellij.execution.RunManager;
+import com.intellij.execution.RunnerAndConfigurationSettings;
+import com.intellij.execution.configurations.RunConfiguration;
+import com.intellij.openapi.project.Project;
+import com.intellij.util.ui.UIUtil;
+import java.util.List;
+import java.util.Set;
+
+/** Creates run configurations for project view targets, where appropriate. */
+public class BlazeRunConfigurationSyncListener extends SyncListener.Adapter {
+
+  @Override
+  public void onSyncComplete(
+      Project project,
+      BlazeImportSettings importSettings,
+      ProjectViewSet projectViewSet,
+      BlazeProjectData blazeProjectData,
+      SyncResult syncResult) {
+
+    UIUtil.invokeAndWaitIfNeeded(
+        (Runnable)
+            () -> {
+              Set<Label> labelsWithConfigs = labelsWithConfigs(project);
+              Set<TargetExpression> targetExpressions =
+                  Sets.newHashSet(projectViewSet.listItems(TargetSection.KEY));
+              for (RuleIdeInfo rule : blazeProjectData.ruleMap.rules()) {
+                maybeAddRunConfiguration(
+                    project,
+                    blazeProjectData.workspaceLanguageSettings,
+                    targetExpressions,
+                    labelsWithConfigs,
+                    rule);
+              }
+            });
+  }
+
+  /** Collects a set of all the Blaze labels that have an associated run configuration. */
+  private static Set<Label> labelsWithConfigs(Project project) {
+    List<RunConfiguration> configurations =
+        RunManager.getInstance(project).getAllConfigurationsList();
+    Set<Label> labelsWithConfigs = Sets.newHashSet();
+    for (RunConfiguration configuration : configurations) {
+      if (configuration instanceof BlazeRunConfiguration) {
+        BlazeRunConfiguration blazeRunConfiguration = (BlazeRunConfiguration) configuration;
+        TargetExpression target = blazeRunConfiguration.getTarget();
+        if (target instanceof Label) {
+          labelsWithConfigs.add((Label) target);
+        }
+      }
+    }
+    return labelsWithConfigs;
+  }
+
+  /**
+   * Adds a run configuration for an android_binary target if there is not already a configuration
+   * for that target.
+   */
+  private static void maybeAddRunConfiguration(
+      Project project,
+      WorkspaceLanguageSettings workspaceLanguageSettings,
+      Set<TargetExpression> importTargets,
+      Set<Label> labelsWithConfigs,
+      RuleIdeInfo rule) {
+    Label label = rule.label;
+    // We only auto-generate configurations for rules listed in the project view.
+    if (!importTargets.contains(label) || labelsWithConfigs.contains(label)) {
+      return;
+    }
+    labelsWithConfigs.add(label);
+    final RunManager runManager = RunManager.getInstance(project);
+
+    for (BlazeRuleConfigurationFactory configurationFactory :
+        BlazeRuleConfigurationFactory.EP_NAME.getExtensions()) {
+      if (configurationFactory.handlesRule(workspaceLanguageSettings, rule)) {
+        final RunnerAndConfigurationSettings settings =
+            configurationFactory.createForRule(project, runManager, rule);
+        runManager.addConfiguration(settings, false /* isShared */);
+        if (runManager.getSelectedConfiguration() == null) {
+          // TODO(joshgiles): Better strategy for picking initially selected config.
+          runManager.setSelectedConfiguration(settings);
+        }
+        break;
+      }
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/run/RuleNameHeuristic.java b/base/src/com/google/idea/blaze/base/run/RuleNameHeuristic.java
new file mode 100644
index 0000000..8f92d75
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/run/RuleNameHeuristic.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.base.run;
+
+import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+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 {
+
+  @Override
+  public boolean matchesSource(RuleIdeInfo rule, File sourceFile, @Nullable TestSize testSize) {
+    String sourceName = FileUtil.getNameWithoutExtension(sourceFile);
+    return sourceName.equals(rule.label.ruleName().toString());
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/run/TestRuleFinder.java b/base/src/com/google/idea/blaze/base/run/TestRuleFinder.java
new file mode 100644
index 0000000..4049462
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/run/TestRuleFinder.java
@@ -0,0 +1,37 @@
+/*
+ * 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.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 {
+
+  static TestRuleFinder getInstance(Project project) {
+    return ServiceManager.getService(project, TestRuleFinder.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);
+
+}
diff --git a/base/src/com/google/idea/blaze/base/run/TestRuleHeuristic.java b/base/src/com/google/idea/blaze/base/run/TestRuleHeuristic.java
new file mode 100644
index 0000000..67e6813
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/run/TestRuleHeuristic.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.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
new file mode 100644
index 0000000..1e1b56d
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/run/TestSizeHeuristic.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.run;
+
+import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+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 {
+
+  @Override
+  public boolean matchesSource(RuleIdeInfo rule, 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;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/run/confighandler/BlazeCommandGenericRunConfigurationHandler.java b/base/src/com/google/idea/blaze/base/run/confighandler/BlazeCommandGenericRunConfigurationHandler.java
new file mode 100644
index 0000000..97a9aaa
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/run/confighandler/BlazeCommandGenericRunConfigurationHandler.java
@@ -0,0 +1,424 @@
+/*
+ * 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.confighandler;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.base.async.process.LineProcessingOutputStream;
+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.issueparser.IssueOutputLineProcessor;
+import com.google.idea.blaze.base.metrics.Action;
+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.run.BlazeCommandRunConfiguration;
+import com.google.idea.blaze.base.run.BlazeConfigurationNameBuilder;
+import com.google.idea.blaze.base.run.processhandler.LineProcessingProcessAdapter;
+import com.google.idea.blaze.base.run.processhandler.ScopedBlazeProcessHandler;
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.scope.scopes.IdeaLogScope;
+import com.google.idea.blaze.base.scope.scopes.IssuesScope;
+import com.google.idea.blaze.base.scope.scopes.LoggedTimingScope;
+import com.google.idea.blaze.base.settings.Blaze;
+import com.google.idea.blaze.base.settings.BlazeImportSettings;
+import com.google.idea.blaze.base.settings.BlazeImportSettingsManager;
+import com.google.idea.blaze.base.ui.UiUtil;
+import com.intellij.execution.ExecutionException;
+import com.intellij.execution.Executor;
+import com.intellij.execution.configurations.CommandLineState;
+import com.intellij.execution.configurations.RunConfiguration;
+import com.intellij.execution.configurations.RunProfile;
+import com.intellij.execution.configurations.RunProfileState;
+import com.intellij.execution.configurations.RuntimeConfigurationError;
+import com.intellij.execution.configurations.RuntimeConfigurationException;
+import com.intellij.execution.process.ProcessHandler;
+import com.intellij.execution.process.ProcessListener;
+import com.intellij.execution.runners.ExecutionEnvironment;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.ComboBox;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.ui.components.JBTextField;
+import com.intellij.util.execution.ParametersListUtil;
+import java.io.File;
+import java.util.List;
+import javax.annotation.Nullable;
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.Icon;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.ScrollPaneConstants;
+import org.jdom.Element;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Generic handler for {@link BlazeCommandRunConfiguration}s, used as a fallback in the case where
+ * no other handlers are more relevant.
+ */
+public class BlazeCommandGenericRunConfigurationHandler
+    implements BlazeCommandRunConfigurationHandler {
+  private static final String COMMAND_ATTR = "blaze-command";
+  private static final String USER_BLAZE_FLAG_TAG = "blaze-user-flag";
+  private static final String USER_EXE_FLAG_TAG = "blaze-user-exe-flag";
+  private static final String BLAZE_BINARY_TAG = "blaze-binary";
+
+  /** The configuration this handler is for. */
+  protected final BlazeCommandRunConfiguration configuration;
+
+  @Nullable private BlazeCommandName command;
+  @Nullable private String blazeBinary;
+  private ImmutableList<String> blazeFlags = ImmutableList.of();
+  private ImmutableList<String> exeFlags = ImmutableList.of();
+
+  public BlazeCommandGenericRunConfigurationHandler(BlazeCommandRunConfiguration configuration) {
+    this.configuration = configuration;
+  }
+
+  protected BlazeCommandGenericRunConfigurationHandler(
+      BlazeCommandGenericRunConfigurationHandler other,
+      BlazeCommandRunConfiguration configuration) {
+    this(configuration);
+    command = other.command;
+    blazeFlags = other.blazeFlags;
+    exeFlags = other.exeFlags;
+    blazeBinary = other.blazeBinary;
+  }
+
+  @Nullable
+  public BlazeCommandName getCommand() {
+    return command;
+  }
+
+  /** @return The list of blaze flags that the user specified manually. */
+  public List<String> getBlazeFlags() {
+    return blazeFlags;
+  }
+
+  /** @return The list of executable flags the user specified manually. */
+  public List<String> getExeFlags() {
+    return exeFlags;
+  }
+
+  /**
+   * @return The list of all flags to be used on the Blaze command line for blaze. Subclasses should
+   *     override this method to add flags for higher-level settings (e.g. "run locally").
+   */
+  public List<String> getAllBlazeFlags() {
+    return getBlazeFlags();
+  }
+
+  @Nullable
+  public String getBlazeBinary() {
+    return blazeBinary;
+  }
+
+  /**
+   * @return The list of all flags to be used for the executable on the Blaze command line.
+   *     Subclasses should override this method to add flags if desired.
+   */
+  public List<String> getAllExeFlags() {
+    return getExeFlags();
+  }
+
+  public void setCommand(@Nullable BlazeCommandName command) {
+    this.command = command;
+  }
+
+  public final void setBlazeFlags(List<String> flags) {
+    this.blazeFlags = ImmutableList.copyOf(flags);
+  }
+
+  public final void setExeFlags(List<String> flags) {
+    this.exeFlags = ImmutableList.copyOf(flags);
+  }
+
+  public void setBlazeBinary(@Nullable String blazeBinary) {
+    this.blazeBinary = blazeBinary;
+  }
+
+  /** Searches through all blaze flags for the first one beginning with '--test_filter' */
+  @Nullable
+  public String getTestFilterFlag() {
+    for (String flag : getAllBlazeFlags()) {
+      if (flag.startsWith(BlazeFlags.TEST_FILTER)) {
+        return flag;
+      }
+    }
+    return null;
+  }
+
+  @Override
+  public void checkConfiguration() throws RuntimeConfigurationException {
+    if (command == null) {
+      throw new RuntimeConfigurationError("You must specify a command.");
+    }
+    if (blazeBinary != null && !(new File(blazeBinary).exists())) {
+      throw new RuntimeConfigurationError(
+          Blaze.buildSystemName(configuration.getProject()) + " binary does not exist");
+    }
+  }
+
+  @Override
+  public void readExternal(Element element) throws InvalidDataException {
+    String commandString = element.getAttributeValue(COMMAND_ATTR);
+    command =
+        Strings.isNullOrEmpty(commandString) ? null : BlazeCommandName.fromString(commandString);
+    blazeFlags = loadUserFlags(element, USER_BLAZE_FLAG_TAG);
+    exeFlags = loadUserFlags(element, USER_EXE_FLAG_TAG);
+    blazeBinary = element.getAttributeValue(BLAZE_BINARY_TAG);
+  }
+
+  private static ImmutableList<String> loadUserFlags(Element root, String tag) {
+    ImmutableList.Builder<String> flagsBuilder = ImmutableList.builder();
+    for (Element e : root.getChildren(tag)) {
+      String flag = e.getTextTrim();
+      if (flag != null && !flag.isEmpty()) {
+        flagsBuilder.add(flag);
+      }
+    }
+    return flagsBuilder.build();
+  }
+
+  @Override
+  public void writeExternal(Element element) {
+    if (command != null) {
+      element.setAttribute(COMMAND_ATTR, command.toString());
+    }
+    saveUserFlags(element, blazeFlags, USER_BLAZE_FLAG_TAG);
+    saveUserFlags(element, exeFlags, USER_EXE_FLAG_TAG);
+    if (!Strings.isNullOrEmpty(blazeBinary)) {
+      element.setAttribute(BLAZE_BINARY_TAG, blazeBinary);
+    }
+  }
+
+  private static void saveUserFlags(Element root, List<String> flags, String tag) {
+    for (String flag : flags) {
+      Element child = new Element(tag);
+      child.setText(flag);
+      root.addContent(child);
+    }
+  }
+
+  @Override
+  public BlazeCommandGenericRunConfigurationHandler cloneFor(
+      BlazeCommandRunConfiguration configuration) {
+    return new BlazeCommandGenericRunConfigurationHandler(this, configuration);
+  }
+
+  @Override
+  public RunProfileState getState(Executor executor, ExecutionEnvironment environment) {
+    return new BlazeCommandGenericRunConfigurationHandler.BlazeCommandRunProfileState(environment);
+  }
+
+  @Override
+  public boolean executeBeforeRunTask(ExecutionEnvironment environment) {
+    // Don't execute any tasks.
+    return true;
+  }
+
+  @Override
+  @Nullable
+  public String suggestedName() {
+    if (configuration.getTarget() == null) {
+      return null;
+    }
+    return new BlazeConfigurationNameBuilder(configuration).build();
+  }
+
+  @Override
+  @Nullable
+  public String getCommandName() {
+    return command != null ? command.toString() : null;
+  }
+
+  @Override
+  public boolean isGeneratedName(boolean hasGeneratedFlag) {
+    return hasGeneratedFlag;
+  }
+
+  @Override
+  public String getHandlerName() {
+    return "Generic Handler";
+  }
+
+  @Override
+  @Nullable
+  public Icon getExecutorIcon(RunConfiguration configuration, Executor executor) {
+    return null;
+  }
+
+  @Override
+  public BlazeCommandRunConfigurationHandlerEditor getHandlerEditor() {
+    return new BlazeCommandGenericRunConfigurationHandler
+        .BlazeCommandGenericRunConfigurationHandlerEditor(this);
+  }
+
+  /** {@link RunProfileState} for generic blaze commands. */
+  private static class BlazeCommandRunProfileState extends CommandLineState {
+    private final BlazeCommandRunConfiguration configuration;
+    private final BlazeCommandGenericRunConfigurationHandler handler;
+
+    BlazeCommandRunProfileState(ExecutionEnvironment environment) {
+      super(environment);
+      RunProfile runProfile = environment.getRunProfile();
+      configuration = (BlazeCommandRunConfiguration) runProfile;
+      handler = (BlazeCommandGenericRunConfigurationHandler) configuration.getHandler();
+    }
+
+    @Override
+    @NotNull
+    protected ProcessHandler startProcess() throws ExecutionException {
+      Project project = configuration.getProject();
+      BlazeImportSettings importSettings =
+          BlazeImportSettingsManager.getInstance(project).getImportSettings();
+      assert importSettings != null;
+
+      ProjectViewSet projectViewSet = ProjectViewManager.getInstance(project).getProjectViewSet();
+      assert projectViewSet != null;
+
+      BlazeCommand blazeCommand =
+          BlazeCommand.builder(Blaze.getBuildSystem(project), handler.getCommand())
+              .setBlazeBinary(handler.getBlazeBinary())
+              .addTargets(configuration.getTarget())
+              .addBlazeFlags(BlazeFlags.buildFlags(project, projectViewSet))
+              .addBlazeFlags(handler.getAllBlazeFlags())
+              .addExeFlags(handler.getAllExeFlags())
+              .build();
+
+      WorkspaceRoot workspaceRoot = WorkspaceRoot.fromImportSettings(importSettings);
+      return new ScopedBlazeProcessHandler(
+          project,
+          blazeCommand,
+          workspaceRoot,
+          new ScopedBlazeProcessHandler.ScopedProcessHandlerDelegate() {
+            @Override
+            public void onBlazeContextStart(BlazeContext context) {
+              context
+                  .push(new LoggedTimingScope(project, Action.BLAZE_COMMAND_USAGE))
+                  .push(new IssuesScope(project))
+                  .push(new IdeaLogScope());
+            }
+
+            @Override
+            public ImmutableList<ProcessListener> createProcessListeners(BlazeContext context) {
+              LineProcessingOutputStream outputStream =
+                  LineProcessingOutputStream.of(
+                      new IssueOutputLineProcessor(project, context, workspaceRoot));
+              return ImmutableList.of(new LineProcessingProcessAdapter(outputStream));
+            }
+          });
+    }
+  }
+
+  /** {@link BlazeCommandRunConfigurationHandlerEditor} for generic blaze commands. */
+  static class BlazeCommandGenericRunConfigurationHandlerEditor
+      implements BlazeCommandRunConfigurationHandlerEditor {
+    private final String buildSystemName;
+
+    private final ComboBox commandCombo;
+    private final JTextArea blazeFlagsField = new JTextArea(5, 1);
+    private final JTextArea exeFlagsField = new JTextArea(5, 1);
+    private final JBTextField blazeBinaryField = new JBTextField(1);
+
+    public BlazeCommandGenericRunConfigurationHandlerEditor(
+        BlazeCommandGenericRunConfigurationHandler handler) {
+      buildSystemName = Blaze.buildSystemName(handler.configuration.getProject());
+      commandCombo =
+          new ComboBox(new DefaultComboBoxModel(BlazeCommandName.knownCommands().toArray()));
+      // Allow the user to manually specify an unlisted command.
+      commandCombo.setEditable(true);
+      blazeBinaryField.getEmptyText().setText("(Use global)");
+    }
+
+    private static String makeArgString(List<String> arguments) {
+      StringBuilder flagString = new StringBuilder();
+      for (String flag : arguments) {
+        if (flagString.length() > 0) {
+          flagString.append('\n');
+        }
+        if (flag.isEmpty() || flag.contains(" ") || flag.contains("|")) {
+          flagString.append('"');
+          flagString.append(flag);
+          flagString.append('"');
+        } else {
+          flagString.append(flag);
+        }
+      }
+      return flagString.toString();
+    }
+
+    @Override
+    public void resetEditorFrom(BlazeCommandRunConfigurationHandler h) {
+      BlazeCommandGenericRunConfigurationHandler handler =
+          (BlazeCommandGenericRunConfigurationHandler) h;
+
+      commandCombo.setSelectedItem(handler.command);
+
+      // Normally we could just use ParametersListUtils.join, but that will only space-delimit args
+      blazeFlagsField.setText(makeArgString(handler.getBlazeFlags()));
+      exeFlagsField.setText(makeArgString(handler.getExeFlags()));
+
+      blazeBinaryField.setText(Strings.nullToEmpty(handler.blazeBinary));
+    }
+
+    @Override
+    public void applyEditorTo(BlazeCommandRunConfigurationHandler h) {
+      BlazeCommandGenericRunConfigurationHandler handler =
+          (BlazeCommandGenericRunConfigurationHandler) h;
+      Object selectedCommand = commandCombo.getSelectedItem();
+      if (selectedCommand instanceof BlazeCommandName) {
+        handler.command = (BlazeCommandName) selectedCommand;
+      } else {
+        handler.command =
+            Strings.isNullOrEmpty((String) selectedCommand)
+                ? null
+                : BlazeCommandName.fromString(selectedCommand.toString());
+      }
+      handler.blazeFlags =
+          ImmutableList.copyOf(
+              ParametersListUtil.parse(Strings.nullToEmpty(blazeFlagsField.getText())));
+      handler.exeFlags =
+          ImmutableList.copyOf(
+              ParametersListUtil.parse(Strings.nullToEmpty(exeFlagsField.getText())));
+
+      String blazeBinary = blazeBinaryField.getText();
+      handler.blazeBinary = Strings.emptyToNull(blazeBinary);
+    }
+
+    @Override
+    @NotNull
+    public JComponent createEditor() {
+      return UiUtil.createBox(
+          new JLabel(buildSystemName + " command:"),
+          commandCombo,
+          new JLabel(buildSystemName + " flags:"),
+          new JScrollPane(
+              blazeFlagsField,
+              JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
+              ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED),
+          new JLabel("Executable flags:"),
+          new JScrollPane(
+              exeFlagsField,
+              JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
+              ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED),
+          new JLabel(buildSystemName + " binary:"),
+          blazeBinaryField);
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/run/confighandler/BlazeCommandGenericRunConfigurationHandlerProvider.java b/base/src/com/google/idea/blaze/base/run/confighandler/BlazeCommandGenericRunConfigurationHandlerProvider.java
new file mode 100644
index 0000000..f993c45
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/run/confighandler/BlazeCommandGenericRunConfigurationHandlerProvider.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.run.confighandler;
+
+import com.google.idea.blaze.base.model.primitives.Kind;
+import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
+
+/**
+ * Generic handler provider for {@link BlazeCommandRunConfiguration}s, used as a fallback in the
+ * case where no other handler providers are more relevant.
+ */
+public class BlazeCommandGenericRunConfigurationHandlerProvider
+    implements BlazeCommandRunConfigurationHandlerProvider {
+
+  public static BlazeCommandGenericRunConfigurationHandlerProvider getInstance() {
+    return BlazeCommandRunConfigurationHandlerProvider.EP_NAME.findExtension(
+        BlazeCommandGenericRunConfigurationHandlerProvider.class);
+  }
+
+  @Override
+  public boolean canHandleKind(Kind kind) {
+    return true;
+  }
+
+  @Override
+  public BlazeCommandRunConfigurationHandler createHandler(BlazeCommandRunConfiguration config) {
+    return new BlazeCommandGenericRunConfigurationHandler(config);
+  }
+
+  @Override
+  public String getId() {
+    return "BlazeCommandGenericRunConfigurationHandlerProvider";
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/run/confighandler/BlazeCommandRunConfigurationHandler.java b/base/src/com/google/idea/blaze/base/run/confighandler/BlazeCommandRunConfigurationHandler.java
new file mode 100644
index 0000000..7d88a7e
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/run/confighandler/BlazeCommandRunConfigurationHandler.java
@@ -0,0 +1,111 @@
+/*
+ * 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.confighandler;
+
+import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
+import com.intellij.execution.ExecutionException;
+import com.intellij.execution.Executor;
+import com.intellij.execution.configurations.RunConfiguration;
+import com.intellij.execution.configurations.RunProfileState;
+import com.intellij.execution.configurations.RuntimeConfigurationException;
+import com.intellij.execution.runners.ExecutionEnvironment;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.WriteExternalException;
+import javax.annotation.Nullable;
+import javax.swing.Icon;
+import org.jdom.Element;
+
+/**
+ * Supports the run configuration flow for {@link BlazeCommandRunConfiguration}s.
+ *
+ * <p>Provides rule-specific configuration state, editor, name, RunProfileState, and
+ * before-run-tasks.
+ */
+public interface BlazeCommandRunConfigurationHandler {
+
+  /**
+   * Checks whether the handler settings are valid.
+   *
+   * @throws RuntimeConfigurationException for configuration errors the user should be warned about.
+   */
+  void checkConfiguration() throws RuntimeConfigurationException;
+
+  /** Loads this handler's state from the external data. */
+  void readExternal(Element element) throws InvalidDataException;
+
+  /** Writes this handler's state to the element. */
+  @SuppressWarnings("ThrowsUncheckedException")
+  void writeExternal(Element element) throws WriteExternalException;
+
+  /**
+   * Creates a clone of this handler for the specified configuration.
+   *
+   * @return A new BlazeCommandRunConfigurationHandler with the same state as this one, except its
+   *     configuration is the specified {@code configuration}.
+   */
+  BlazeCommandRunConfigurationHandler cloneFor(BlazeCommandRunConfiguration configuration);
+
+  /** @return the RunProfileState corresponding to the given environment. */
+  RunProfileState getState(Executor executor, ExecutionEnvironment environment)
+      throws ExecutionException;
+
+  /**
+   * Executes any required before run tasks.
+   *
+   * @return true if no task exists or the task was successfully completed. Otherwise returns false
+   *     if the task either failed or was cancelled.
+   */
+  boolean executeBeforeRunTask(ExecutionEnvironment environment);
+
+  /**
+   * @return The default name of the run configuration based on its settings and this handler's
+   *     state.
+   */
+  @Nullable
+  String suggestedName();
+
+  /**
+   * Allows overriding the default behavior of {@link
+   * com.intellij.execution.configurations.LocatableConfiguration#isGeneratedName()}. Return {@code
+   * hasGeneratedFlag} to keep the default behavior.
+   *
+   * @param hasGeneratedFlag Whether the configuration reports its name is generated.
+   * @return Whether the run configuration's name should be treated as generated (allowing
+   *     regenerating it when settings change).
+   */
+  boolean isGeneratedName(boolean hasGeneratedFlag);
+
+  /**
+   * @return The name of the Blaze command associated with this handler. May be null if no command
+   *     is appropriate.
+   */
+  @Nullable
+  String getCommandName();
+
+  /** @return The name of this handler. Shown in the UI. */
+  String getHandlerName();
+
+  /**
+   * Allows overriding the default behavior of {@link
+   * com.intellij.execution.RunnerIconProvider#getExecutorIcon(RunConfiguration, Executor)}. Return
+   * null to keep the default behavior.
+   */
+  @Nullable
+  Icon getExecutorIcon(RunConfiguration configuration, Executor executor);
+
+  /** @return A {@link BlazeCommandRunConfigurationHandlerEditor} for this handler. */
+  BlazeCommandRunConfigurationHandlerEditor getHandlerEditor();
+}
diff --git a/base/src/com/google/idea/blaze/base/run/confighandler/BlazeCommandRunConfigurationHandlerEditor.java b/base/src/com/google/idea/blaze/base/run/confighandler/BlazeCommandRunConfigurationHandlerEditor.java
new file mode 100644
index 0000000..0c9bce7
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/run/confighandler/BlazeCommandRunConfigurationHandlerEditor.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.base.run.confighandler;
+
+import javax.swing.JComponent;
+import org.jetbrains.annotations.Nullable;
+
+/** Provides support for editing {@link BlazeCommandRunConfigurationHandler}s. */
+public interface BlazeCommandRunConfigurationHandlerEditor {
+  /** Reset the editor based on the state of the given handler. */
+  void resetEditorFrom(BlazeCommandRunConfigurationHandler handler);
+
+  /** Update the given handler's state based on the editor. */
+  void applyEditorTo(BlazeCommandRunConfigurationHandler handler);
+
+  /** @return A component to display for the editor. */
+  @Nullable
+  JComponent createEditor();
+}
diff --git a/base/src/com/google/idea/blaze/base/run/confighandler/BlazeCommandRunConfigurationHandlerProvider.java b/base/src/com/google/idea/blaze/base/run/confighandler/BlazeCommandRunConfigurationHandlerProvider.java
new file mode 100644
index 0000000..af67362
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/run/confighandler/BlazeCommandRunConfigurationHandlerProvider.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.run.confighandler;
+
+import com.google.idea.blaze.base.model.primitives.Kind;
+import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
+import com.intellij.openapi.extensions.ExtensionPointName;
+import javax.annotation.Nullable;
+
+/**
+ * Provides a {@link BlazeCommandRunConfigurationHandler} corresponding to a given {@link
+ * BlazeCommandRunConfiguration}.
+ */
+public interface BlazeCommandRunConfigurationHandlerProvider {
+
+  ExtensionPointName<BlazeCommandRunConfigurationHandlerProvider> EP_NAME =
+      ExtensionPointName.create(
+          "com.google.idea.blaze.BlazeCommandRunConfigurationHandlerProvider");
+
+  /**
+   * Find a BlazeCommandRunConfigurationHandlerProvider applicable to the given kind. If no provider
+   * is more relevant, {@link BlazeCommandGenericRunConfigurationHandlerProvider} is returned.
+   */
+  static BlazeCommandRunConfigurationHandlerProvider findHandlerProvider(Kind kind) {
+    for (BlazeCommandRunConfigurationHandlerProvider handlerProvider : EP_NAME.getExtensions()) {
+      if (handlerProvider.canHandleKind(kind)) {
+        return handlerProvider;
+      }
+    }
+    // BlazeCommandGenericRunConfigurationHandlerProvider can handle any kind,
+    // and will be returned by this point.
+    assert false;
+    return null;
+  }
+
+  /** Get the BlazeCommandRunConfigurationHandlerProvider with the given ID, if one exists. */
+  @Nullable
+  static BlazeCommandRunConfigurationHandlerProvider getHandlerProvider(@Nullable String id) {
+    for (BlazeCommandRunConfigurationHandlerProvider handlerProvider : EP_NAME.getExtensions()) {
+      if (handlerProvider.getId().equals(id)) {
+        return handlerProvider;
+      }
+    }
+    return null;
+  }
+
+  /** Whether this extension is applicable to the kind. */
+  boolean canHandleKind(Kind kind);
+
+  /** Returns the corresponding {@link BlazeCommandRunConfigurationHandler}. */
+  BlazeCommandRunConfigurationHandler createHandler(BlazeCommandRunConfiguration config);
+
+  /**
+   * Returns the unique ID of this {@link BlazeCommandRunConfigurationHandlerProvider}. The ID is
+   * used to store configuration settings and must not change between plugin versions.
+   */
+  String getId();
+}
diff --git a/base/src/com/google/idea/blaze/base/run/confighandler/BlazeUnknownRunConfigurationHandler.java b/base/src/com/google/idea/blaze/base/run/confighandler/BlazeUnknownRunConfigurationHandler.java
new file mode 100644
index 0000000..e968860
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/run/confighandler/BlazeUnknownRunConfigurationHandler.java
@@ -0,0 +1,158 @@
+/*
+ * 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.confighandler;
+
+import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
+import com.google.idea.blaze.base.settings.Blaze;
+import com.intellij.execution.ExecutionException;
+import com.intellij.execution.Executor;
+import com.intellij.execution.configurations.RunConfiguration;
+import com.intellij.execution.configurations.RunProfileState;
+import com.intellij.execution.configurations.RuntimeConfigurationException;
+import com.intellij.execution.runners.ExecutionEnvironment;
+import com.intellij.openapi.util.InvalidDataException;
+import java.util.HashSet;
+import java.util.Set;
+import javax.annotation.Nullable;
+import javax.swing.Icon;
+import javax.swing.JComponent;
+import org.jdom.Attribute;
+import org.jdom.Element;
+
+/**
+ * Fallback handler for {@link BlazeCommandRunConfiguration}s with uninitialized targets or unknown
+ * handler providers.
+ *
+ * <p>Cannot be run and provides no editor. Writes all attributes and elements it initially read,
+ * except those with names matching existing content written by the configuration itself.
+ */
+public class BlazeUnknownRunConfigurationHandler implements BlazeCommandRunConfigurationHandler {
+
+  private final BlazeCommandRunConfiguration configuration;
+
+  @Nullable private Element externalElementBackup;
+
+  public BlazeUnknownRunConfigurationHandler(BlazeCommandRunConfiguration configuration) {
+    this.configuration = configuration;
+  }
+
+  @Override
+  public void checkConfiguration() throws RuntimeConfigurationException {
+    // No need to throw anything here; BlazeCommandRunConfiguration's
+    // check will already detect any config with this handler as invalid
+    // because its provider is null and all targets are handled by some provider.
+    assert false;
+  }
+
+  @Override
+  public void readExternal(Element element) throws InvalidDataException {
+    externalElementBackup = element.clone();
+  }
+
+  @Override
+  public void writeExternal(Element element) {
+    // Write back attributes and elements from externalElementBackup,
+    // but take care not to write any which exist in the passed element.
+    // Such attributes and elements belong to the configuration, not the handler!
+    if (externalElementBackup != null) {
+      Set<String> configurationAttributeNames = new HashSet<>();
+      for (Attribute attribute : element.getAttributes()) {
+        configurationAttributeNames.add(attribute.getName());
+      }
+      Set<String> configurationElementNames = new HashSet<>();
+      for (Element child : element.getChildren()) {
+        configurationElementNames.add(child.getName());
+      }
+
+      for (Attribute attribute : externalElementBackup.getAttributes()) {
+        if (!configurationAttributeNames.contains(attribute.getName())) {
+          element.setAttribute(attribute.clone());
+        }
+      }
+      for (Element child : externalElementBackup.getChildren()) {
+        if (!configurationElementNames.contains(child.getName())) {
+          element.addContent(child.clone());
+        }
+      }
+    }
+  }
+
+  @Override
+  public BlazeUnknownRunConfigurationHandler cloneFor(BlazeCommandRunConfiguration configuration) {
+    return new BlazeUnknownRunConfigurationHandler(configuration);
+  }
+
+  @Override
+  public RunProfileState getState(Executor executor, ExecutionEnvironment environment)
+      throws ExecutionException {
+    return null;
+  }
+
+  @Override
+  public boolean executeBeforeRunTask(ExecutionEnvironment environment) {
+    return true;
+  }
+
+  @Nullable
+  @Override
+  public String suggestedName() {
+    return String.format(
+        "Unknown %s Configuration", Blaze.buildSystemName(configuration.getProject()));
+  }
+
+  @Override
+  public boolean isGeneratedName(boolean hasGeneratedFlag) {
+    return hasGeneratedFlag;
+  }
+
+  @Nullable
+  @Override
+  public String getCommandName() {
+    return null;
+  }
+
+  @Override
+  public String getHandlerName() {
+    return "no handler";
+  }
+
+  @Override
+  @Nullable
+  public Icon getExecutorIcon(RunConfiguration configuration, Executor executor) {
+    return null;
+  }
+
+  @Override
+  public BlazeCommandRunConfigurationHandlerEditor getHandlerEditor() {
+    return new BlazeCommandRunConfigurationHandlerEditor() {
+      @Override
+      public void resetEditorFrom(BlazeCommandRunConfigurationHandler handler) {}
+
+      @Override
+      public void applyEditorTo(BlazeCommandRunConfigurationHandler handler) {}
+
+      @Override
+      @Nullable
+      public JComponent createEditor() {
+        // Note: currently this will never be displayed,
+        // as the handler editor is not shown for invalidated configurations.
+        //return new JBLabel("Configuration could not be loaded "
+        //    + "because its handler could not be found.");
+        return null;
+      }
+    };
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/run/processhandler/LineProcessingProcessAdapter.java b/base/src/com/google/idea/blaze/base/run/processhandler/LineProcessingProcessAdapter.java
new file mode 100644
index 0000000..891f19f
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/run/processhandler/LineProcessingProcessAdapter.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.base.run.processhandler;
+
+import com.google.common.base.Charsets;
+import com.google.idea.blaze.base.async.process.LineProcessingOutputStream;
+import com.intellij.execution.process.ProcessAdapter;
+import com.intellij.execution.process.ProcessEvent;
+import com.intellij.openapi.util.Key;
+import java.io.IOException;
+
+/** Writes output from a process to a stream */
+public final class LineProcessingProcessAdapter extends ProcessAdapter {
+  private final LineProcessingOutputStream myOutputStream;
+
+  public LineProcessingProcessAdapter(LineProcessingOutputStream outputStream) {
+    myOutputStream = outputStream;
+  }
+
+  @Override
+  public void onTextAvailable(ProcessEvent event, Key outputType) {
+    String text = event.getText();
+    if (text != null) {
+      try {
+        myOutputStream.write(text.getBytes(Charsets.UTF_8));
+      } catch (IOException e) {
+        // Ignore -- cannot happen
+      }
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/run/processhandler/ScopedBlazeProcessHandler.java b/base/src/com/google/idea/blaze/base/run/processhandler/ScopedBlazeProcessHandler.java
new file mode 100644
index 0000000..7cd3050
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/run/processhandler/ScopedBlazeProcessHandler.java
@@ -0,0 +1,123 @@
+/*
+ * 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.processhandler;
+
+import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.base.command.BlazeCommand;
+import com.google.idea.blaze.base.filecache.FileCaches;
+import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.intellij.execution.ExecutionException;
+import com.intellij.execution.configurations.GeneralCommandLine;
+import com.intellij.execution.process.KillableColoredProcessHandler;
+import com.intellij.execution.process.ProcessAdapter;
+import com.intellij.execution.process.ProcessEvent;
+import com.intellij.execution.process.ProcessListener;
+import com.intellij.execution.process.ProcessOutputTypes;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Key;
+
+/**
+ * Scoped process handler.
+ *
+ * <p>A context is created during construction and is ended when the process is terminated.
+ */
+public final class ScopedBlazeProcessHandler extends KillableColoredProcessHandler {
+  /**
+   * Methods to give the caller of {@link ScopedBlazeProcessHandler} hooks after the context is
+   * created.
+   */
+  public interface ScopedProcessHandlerDelegate {
+    /**
+     * This method is called when the process starts. Any context setup (like pushing scopes on the
+     * context) should be done here.
+     */
+    void onBlazeContextStart(BlazeContext context);
+
+    /** Get a list of process listeners to add to the process. */
+    ImmutableList<ProcessListener> createProcessListeners(BlazeContext context);
+  }
+
+  private final ScopedProcessHandlerDelegate scopedProcessHandlerDelegate;
+  private final BlazeContext context;
+
+  /**
+   * Construct a process handler and a context to be used for the life of the process.
+   *
+   * @param blazeCommand the blaze command to run
+   * @param workspaceRoot workspace root
+   * @param scopedProcessHandlerDelegate delegate methods that will be run with the process's
+   *     context.
+   * @throws ExecutionException
+   */
+  public ScopedBlazeProcessHandler(
+      Project project,
+      BlazeCommand blazeCommand,
+      WorkspaceRoot workspaceRoot,
+      ScopedProcessHandlerDelegate scopedProcessHandlerDelegate)
+      throws ExecutionException {
+    super(
+        new GeneralCommandLine(blazeCommand.toList())
+            .withWorkDirectory(workspaceRoot.directory().getPath()));
+
+    this.scopedProcessHandlerDelegate = scopedProcessHandlerDelegate;
+    this.context = new BlazeContext();
+    // The context is released in the ScopedProcessHandlerListener.
+    this.context.hold();
+
+    for (ProcessListener processListener :
+        scopedProcessHandlerDelegate.createProcessListeners(context)) {
+      addProcessListener(processListener);
+    }
+    addProcessListener(new ScopedProcessHandlerListener(project));
+  }
+
+  @Override
+  public void coloredTextAvailable(String text, Key attributes) {
+    // Change blaze's stderr output to normal color, otherwise
+    // test output looks red
+    if (attributes == ProcessOutputTypes.STDERR) {
+      attributes = ProcessOutputTypes.STDOUT;
+    }
+
+    super.coloredTextAvailable(text, attributes);
+  }
+
+  /**
+   * Handle the {@link BlazeContext} held in a {@link ScopedBlazeProcessHandler}. This class will
+   * take care of calling methods when the process starts and freeing the context when the process
+   * terminates.
+   */
+  private class ScopedProcessHandlerListener extends ProcessAdapter {
+
+    private final Project project;
+
+    ScopedProcessHandlerListener(Project project) {
+      this.project = project;
+    }
+
+    @Override
+    public void startNotified(ProcessEvent event) {
+      scopedProcessHandlerDelegate.onBlazeContextStart(context);
+    }
+
+    @Override
+    public void processWillTerminate(ProcessEvent event, boolean willBeDestroyed) {
+      FileCaches.refresh(project);
+      context.release();
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/run/producers/AllInPackageBlazeConfigurationProducer.java b/base/src/com/google/idea/blaze/base/run/producers/AllInPackageBlazeConfigurationProducer.java
new file mode 100644
index 0000000..e3746f3
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/run/producers/AllInPackageBlazeConfigurationProducer.java
@@ -0,0 +1,118 @@
+/*
+ * 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.producers;
+
+import com.google.idea.blaze.base.command.BlazeCommandName;
+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.run.BlazeCommandRunConfiguration;
+import com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType;
+import com.google.idea.blaze.base.run.confighandler.BlazeCommandGenericRunConfigurationHandler;
+import com.intellij.execution.actions.ConfigurationContext;
+import com.intellij.openapi.util.Ref;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiElement;
+import java.util.Objects;
+import javax.annotation.Nullable;
+
+/** Runs tests in all packages below selected directory */
+public class AllInPackageBlazeConfigurationProducer
+    extends BlazeRunConfigurationProducer<BlazeCommandRunConfiguration> {
+
+  public AllInPackageBlazeConfigurationProducer() {
+    super(BlazeCommandRunConfigurationType.getInstance());
+  }
+
+  @Override
+  protected boolean doSetupConfigFromContext(
+      BlazeCommandRunConfiguration configuration,
+      ConfigurationContext context,
+      Ref<PsiElement> sourceElement) {
+
+    PsiDirectory dir = getTestDirectory(context);
+    if (dir == null) {
+      return false;
+    }
+    WorkspaceRoot root = WorkspaceRoot.fromProject(context.getModule().getProject());
+    WorkspacePath packagePath = getWorkspaceRelativeDirectoryPath(root, dir);
+    if (packagePath == null) {
+      return false;
+    }
+    sourceElement.set(dir);
+
+    configuration.setTarget(TargetExpression.allFromPackageRecursive(packagePath));
+    BlazeCommandGenericRunConfigurationHandler handler =
+        configuration.getHandlerIfType(BlazeCommandGenericRunConfigurationHandler.class);
+    if (handler == null) {
+      return false;
+    }
+    handler.setCommand(BlazeCommandName.TEST);
+    configuration.setGeneratedName();
+    return true;
+  }
+
+  @Override
+  protected boolean doIsConfigFromContext(
+      BlazeCommandRunConfiguration configuration, ConfigurationContext context) {
+
+    PsiDirectory dir = getTestDirectory(context);
+    if (dir == null) {
+      return false;
+    }
+    WorkspaceRoot root = WorkspaceRoot.fromProject(context.getModule().getProject());
+    WorkspacePath packagePath = getWorkspaceRelativeDirectoryPath(root, dir);
+    if (packagePath == null) {
+      return false;
+    }
+    BlazeCommandGenericRunConfigurationHandler handler =
+        configuration.getHandlerIfType(BlazeCommandGenericRunConfigurationHandler.class);
+    if (handler == null) {
+      return false;
+    }
+    return Objects.equals(handler.getCommand(), BlazeCommandName.TEST)
+        && Objects.equals(
+            configuration.getTarget(), TargetExpression.allFromPackageRecursive(packagePath));
+  }
+
+  @Nullable
+  private static PsiDirectory getTestDirectory(ConfigurationContext context) {
+    WorkspaceRoot root = WorkspaceRoot.fromProject(context.getModule().getProject());
+    PsiElement location = context.getPsiLocation();
+    if (location instanceof PsiDirectory) {
+      PsiDirectory dir = (PsiDirectory) location;
+      if (isInWorkspace(root, dir)) {
+        return dir;
+      }
+    }
+    return null;
+  }
+
+  @Nullable
+  private static WorkspacePath getWorkspaceRelativeDirectoryPath(
+      WorkspaceRoot root, PsiDirectory dir) {
+    VirtualFile file = dir.getVirtualFile();
+    if (isInWorkspace(root, dir)) {
+      return root.workspacePathFor(file);
+    }
+    return null;
+  }
+
+  private static boolean isInWorkspace(WorkspaceRoot root, PsiDirectory dir) {
+    return root.isInWorkspace(dir.getVirtualFile());
+  }
+}
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
new file mode 100644
index 0000000..a28b644
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/run/producers/BlazeBuildFileRunConfigurationProducer.java
@@ -0,0 +1,185 @@
+/*
+ * 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.producers;
+
+import com.google.idea.blaze.base.command.BlazeCommandName;
+import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+import com.google.idea.blaze.base.lang.buildfile.psi.FuncallExpression;
+import com.google.idea.blaze.base.model.BlazeProjectData;
+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.BlazeRuleConfigurationFactory;
+import com.google.idea.blaze.base.run.confighandler.BlazeCommandGenericRunConfigurationHandler;
+import com.google.idea.blaze.base.run.rulefinder.RuleFinder;
+import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
+import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
+import com.intellij.execution.actions.ConfigurationContext;
+import com.intellij.openapi.util.Ref;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.util.PsiTreeUtil;
+import java.util.Objects;
+import javax.annotation.Nullable;
+
+/** Creates run configurations from a BUILD file targets. */
+public class BlazeBuildFileRunConfigurationProducer
+    extends BlazeRunConfigurationProducer<BlazeCommandRunConfiguration> {
+
+  private static class BuildTarget {
+    private final FuncallExpression rule;
+    private final String ruleType;
+    private final Label label;
+    @Nullable private final RuleIdeInfo ruleIdeInfo;
+
+    public BuildTarget(
+        FuncallExpression rule, String ruleType, Label label, @Nullable RuleIdeInfo ruleIdeInfo) {
+      this.rule = rule;
+      this.ruleType = ruleType;
+      this.label = label;
+      this.ruleIdeInfo = ruleIdeInfo;
+    }
+  }
+
+  public BlazeBuildFileRunConfigurationProducer() {
+    super(BlazeCommandRunConfigurationType.getInstance());
+  }
+
+  @Override
+  protected boolean doSetupConfigFromContext(
+      BlazeCommandRunConfiguration configuration,
+      ConfigurationContext context,
+      Ref<PsiElement> sourceElement) {
+    BlazeProjectData blazeProjectData =
+        BlazeProjectDataManager.getInstance(configuration.getProject()).getBlazeProjectData();
+    if (blazeProjectData == null) {
+      return false;
+    }
+    WorkspaceLanguageSettings workspaceLanguageSettings =
+        blazeProjectData.workspaceLanguageSettings;
+    BuildTarget target = getBuildTarget(context);
+    if (target == null) {
+      return false;
+    }
+    sourceElement.set(target.rule);
+    setupConfiguration(configuration, workspaceLanguageSettings, target);
+    return true;
+  }
+
+  @Override
+  protected boolean doIsConfigFromContext(
+      BlazeCommandRunConfiguration configuration, ConfigurationContext context) {
+    BuildTarget target = getBuildTarget(context);
+    if (target == null) {
+      return false;
+    }
+    if (!Objects.equals(configuration.getTarget(), target.label)) {
+      return false;
+    }
+
+    // We don't know any details about how the various factories set up configurations from here.
+    // Simply returning true at this point would be overly broad
+    // (all configs with a matching target would be identified).
+    // A complete equality check, meanwhile, would be too restrictive
+    // (things like config name and user flags shouldn't count)
+    // - not to mention we lack the equals() implementations needed to perform such a check!
+
+    // So we compromise: if the target, suggested name, and command name match,
+    // we consider it close enough. The suggested name is checked because it tends
+    // to cover what the handler considers important,
+    // and ignores changes the user may have made to the name.
+    BlazeProjectData blazeProjectData =
+        BlazeProjectDataManager.getInstance(configuration.getProject()).getBlazeProjectData();
+    if (blazeProjectData == null) {
+      return false;
+    }
+    WorkspaceLanguageSettings workspaceLanguageSettings =
+        blazeProjectData.workspaceLanguageSettings;
+    BlazeCommandRunConfiguration generatedConfiguration =
+        new BlazeCommandRunConfiguration(
+            configuration.getProject(), configuration.getFactory(), configuration.getName());
+    setupConfiguration(generatedConfiguration, workspaceLanguageSettings, target);
+
+    // 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)) {
+      BlazeCommandGenericRunConfigurationHandler handler =
+          configuration.getHandlerIfType(BlazeCommandGenericRunConfigurationHandler.class);
+      if (handler != null && handler.getTestFilterFlag() != null) {
+        return false;
+      }
+    }
+    // End-TODO
+
+    return Objects.equals(configuration.suggestedName(), generatedConfiguration.suggestedName())
+        && Objects.equals(
+            configuration.getHandler().getCommandName(),
+            generatedConfiguration.getHandler().getCommandName());
+  }
+
+  @Nullable
+  private static BuildTarget getBuildTarget(ConfigurationContext context) {
+    FuncallExpression rule =
+        PsiTreeUtil.getNonStrictParentOfType(context.getPsiLocation(), FuncallExpression.class);
+    if (rule == null) {
+      return null;
+    }
+    String ruleType = rule.getFunctionName();
+    Label label = rule.resolveBuildLabel();
+    if (ruleType == null || label == null) {
+      return null;
+    }
+    RuleIdeInfo ruleIdeInfo = RuleFinder.getInstance().ruleForTarget(context.getProject(), label);
+    return new BuildTarget(rule, ruleType, label, ruleIdeInfo);
+  }
+
+  private static void setupConfiguration(
+      BlazeCommandRunConfiguration configuration,
+      WorkspaceLanguageSettings workspaceLanguageSettings,
+      BuildTarget target) {
+    // First see if a BlazeRuleConfigurationFactory can give us a specialized setup.
+    if (target.ruleIdeInfo != null) {
+      for (BlazeRuleConfigurationFactory configurationFactory :
+          BlazeRuleConfigurationFactory.EP_NAME.getExtensions()) {
+        if (configurationFactory.handlesRule(workspaceLanguageSettings, target.ruleIdeInfo)
+            && configurationFactory.handlesConfiguration(configuration)) {
+          configurationFactory.setupConfiguration(configuration, target.ruleIdeInfo);
+          return;
+        }
+      }
+    }
+
+    // If no factory exists, directly set up the configuration.
+    configuration.setTarget(target.label);
+    // Try to make it a 'blaze build' command, if applicable.
+    BlazeCommandGenericRunConfigurationHandler handler =
+        configuration.getHandlerIfType(BlazeCommandGenericRunConfigurationHandler.class);
+    if (handler != null) {
+      // TODO move the old test rule functionality to a BlazeRuleConfigurationFactory
+      handler.setCommand(
+          isTestRule(target.ruleType) ? BlazeCommandName.TEST : BlazeCommandName.BUILD);
+    }
+    configuration.setGeneratedName();
+  }
+
+  // TODO this functionality should be moved to a BlazeRuleConfigurationFactory
+  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/producers/BlazeRunConfigurationProducer.java b/base/src/com/google/idea/blaze/base/run/producers/BlazeRunConfigurationProducer.java
new file mode 100644
index 0000000..c4b50fc
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/run/producers/BlazeRunConfigurationProducer.java
@@ -0,0 +1,91 @@
+/*
+ * 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.producers;
+
+import com.google.idea.blaze.base.settings.Blaze;
+import com.intellij.execution.actions.ConfigurationContext;
+import com.intellij.execution.actions.ConfigurationFromContext;
+import com.intellij.execution.actions.RunConfigurationProducer;
+import com.intellij.execution.configurations.ConfigurationType;
+import com.intellij.execution.configurations.RunConfiguration;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.util.NullUtils;
+import com.intellij.openapi.util.Ref;
+import com.intellij.psi.PsiElement;
+
+/** Base class for Blaze run configuration producers. */
+public abstract class BlazeRunConfigurationProducer<T extends RunConfiguration>
+    extends RunConfigurationProducer<T> {
+
+  protected BlazeRunConfigurationProducer(ConfigurationType configurationType) {
+    super(configurationType);
+  }
+
+  @Override
+  public boolean isPreferredConfiguration(
+      ConfigurationFromContext self, ConfigurationFromContext other) {
+    return Blaze.isBlazeProject(self.getConfiguration().getProject());
+  }
+
+  @Override
+  public boolean shouldReplace(ConfigurationFromContext self, ConfigurationFromContext other) {
+    return Blaze.isBlazeProject(self.getConfiguration().getProject())
+        && !other.isProducedBy(BlazeRunConfigurationProducer.class);
+  }
+
+  @Override
+  protected final boolean setupConfigurationFromContext(
+      T configuration, ConfigurationContext context, Ref<PsiElement> sourceElement) {
+    if (NullUtils.hasNull(configuration, context, sourceElement)) {
+      return false;
+    }
+    if (!validContext(context)) {
+      return false;
+    }
+    return doSetupConfigFromContext(configuration, context, sourceElement);
+  }
+
+  protected abstract boolean doSetupConfigFromContext(
+      T configuration, ConfigurationContext context, Ref<PsiElement> sourceElement);
+
+  @Override
+  public final boolean isConfigurationFromContext(T configuration, ConfigurationContext context) {
+    if (NullUtils.hasNull(configuration, context)) {
+      return false;
+    }
+    if (!validContext(context)) {
+      return false;
+    }
+    return doIsConfigFromContext(configuration, context);
+  }
+
+  protected abstract boolean doIsConfigFromContext(T configuration, ConfigurationContext context);
+
+  private static boolean validContext(ConfigurationContext context) {
+    Module module = context.getModule();
+    if (module == null) {
+      return false;
+    }
+    if (!isBlazeContext(context)) {
+      return false;
+    }
+    return true;
+  }
+
+  private static boolean isBlazeContext(ConfigurationContext context) {
+    return Blaze.isBlazeProject(context.getProject());
+  }
+}
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
new file mode 100644
index 0000000..1b1b858
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/run/rulefinder/RuleFinder.java
@@ -0,0 +1,72 @@
+/*
+ * 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, input -> input.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, input -> input.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/rulefinder/RuleFinderImpl.java b/base/src/com/google/idea/blaze/base/run/rulefinder/RuleFinderImpl.java
new file mode 100644
index 0000000..bc547d7
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/run/rulefinder/RuleFinderImpl.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.base.run.rulefinder;
+
+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.model.BlazeProjectData;
+import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
+import com.intellij.openapi.project.Project;
+import java.util.List;
+import org.jetbrains.annotations.NotNull;
+
+/** Implementation of RuleFinder. */
+class RuleFinderImpl extends RuleFinder {
+  @Override
+  public List<RuleIdeInfo> findRules(
+      @NotNull Project project, @NotNull Predicate<RuleIdeInfo> 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);
+      }
+    }
+    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/TestRuleFinderImpl.java
new file mode 100644
index 0000000..258f059
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/run/testmap/TestRuleFinderImpl.java
@@ -0,0 +1,180 @@
+/*
+ * 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.testmap;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+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.model.BlazeProjectData;
+import com.google.idea.blaze.base.model.RuleMap;
+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.settings.BlazeImportSettings;
+import com.google.idea.blaze.base.sync.SyncListener;
+import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
+import com.intellij.openapi.project.Project;
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+import java.util.Queue;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
+
+/**
+ * Used to locate tests from source files for things like right-clicks.
+ *
+ * <p>It's essentially a map from source file -> reachable test rules.
+ */
+public class TestRuleFinderImpl implements TestRuleFinder {
+
+  private final Project project;
+  @Nullable private TestMap testMap;
+
+  static class TestMap {
+    private final Project project;
+    private final Multimap<File, Label> rootsMap;
+    private final RuleMap ruleMap;
+
+    TestMap(Project project, RuleMap ruleMap) {
+      this.project = project;
+      this.rootsMap = createRootsMap(ruleMap.rules());
+      this.ruleMap = ruleMap;
+    }
+
+    private Collection<RuleIdeInfo> testTargetsForSourceFile(File sourceFile) {
+      BlazeProjectData blazeProjectData =
+          BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
+      if (blazeProjectData != null) {
+        return testRulesForSourceFile(blazeProjectData.reverseDependencies, sourceFile);
+      }
+      return ImmutableList.of();
+    }
+
+    @VisibleForTesting
+    Collection<Label> testTargetsForSourceFile(
+        ImmutableMultimap<Label, Label> rdepsMap, File sourceFile) {
+      return testRulesForSourceFile(rdepsMap, sourceFile)
+          .stream()
+          .map((rule) -> rule.label)
+          .collect(Collectors.toList());
+    }
+
+    Collection<RuleIdeInfo> testRulesForSourceFile(
+        ImmutableMultimap<Label, Label> rdepsMap, File sourceFile) {
+      List<RuleIdeInfo> result = Lists.newArrayList();
+      Collection<Label> roots = rootsMap.get(sourceFile);
+
+      Queue<Label> todo = Queues.newArrayDeque();
+      for (Label label : roots) {
+        todo.add(label);
+      }
+      Set<Label> seen = Sets.newHashSet();
+      while (!todo.isEmpty()) {
+        Label label = todo.remove();
+        if (!seen.add(label)) {
+          continue;
+        }
+
+        RuleIdeInfo rule = ruleMap.get(label);
+        if (isTestRule(rule)) {
+          result.add(rule);
+        }
+        for (Label rdep : rdepsMap.get(label)) {
+          todo.add(rdep);
+        }
+      }
+      return result;
+    }
+
+    static Multimap<File, Label> createRootsMap(Collection<RuleIdeInfo> rules) {
+      Multimap<File, Label> result = ArrayListMultimap.create();
+      for (RuleIdeInfo ruleIdeInfo : rules) {
+        for (ArtifactLocation source : ruleIdeInfo.sources) {
+          result.put(source.getFile(), ruleIdeInfo.label);
+        }
+      }
+      return result;
+    }
+
+    private static boolean isTestRule(@Nullable RuleIdeInfo rule) {
+      return rule != null
+          && rule.kind != null
+          && rule.kind.isOneOf(
+              Kind.ANDROID_ROBOLECTRIC_TEST,
+              Kind.ANDROID_TEST,
+              Kind.JAVA_TEST,
+              Kind.GWT_TEST,
+              Kind.CC_TEST);
+    }
+  }
+
+  public TestRuleFinderImpl(Project project) {
+    this.project = project;
+  }
+
+  @Override
+  public Collection<RuleIdeInfo> testTargetsForSourceFile(File sourceFile) {
+    TestMap testMap = getTestMap();
+    if (testMap == null) {
+      return ImmutableList.of();
+    }
+    return testMap.testTargetsForSourceFile(sourceFile);
+  }
+
+  private synchronized TestMap getTestMap() {
+    if (testMap == null) {
+      testMap = initTestMap();
+    }
+    return testMap;
+  }
+
+  @Nullable
+  private TestMap initTestMap() {
+    BlazeProjectData blazeProjectData =
+        BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
+    if (blazeProjectData == null) {
+      return null;
+    }
+    return new TestMap(project, blazeProjectData.ruleMap);
+  }
+
+  private synchronized void clearMapData() {
+    this.testMap = null;
+  }
+
+  static class ClearTestMap extends SyncListener.Adapter {
+    @Override
+    public void onSyncComplete(
+        Project project,
+        BlazeImportSettings importSettings,
+        ProjectViewSet projectViewSet,
+        BlazeProjectData blazeProjectData,
+        SyncResult syncResult) {
+      TestRuleFinder testRuleFinder = TestRuleFinder.getInstance(project);
+      ((TestRuleFinderImpl) testRuleFinder).clearMapData();
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/scope/BlazeContext.java b/base/src/com/google/idea/blaze/base/scope/BlazeContext.java
new file mode 100644
index 0000000..82568ae
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/scope/BlazeContext.java
@@ -0,0 +1,250 @@
+/*
+ * 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.scope;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Lists;
+import java.util.List;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/** Scoped operation context. */
+public class BlazeContext {
+  @Nullable private BlazeContext parentContext;
+
+  @NotNull private final List<BlazeScope> scopes = Lists.newArrayList();
+
+  @NotNull
+  private final ArrayListMultimap<Class<? extends Output>, OutputSink<?>> outputSinks =
+      ArrayListMultimap.create();
+
+  boolean isEnding;
+
+  boolean isCancelled;
+
+  private int holdCount;
+
+  private boolean hasErrors;
+
+  private boolean propagatesErrors = true;
+
+  public BlazeContext() {
+    this(null);
+  }
+
+  public BlazeContext(@Nullable BlazeContext parentContext) {
+    this.parentContext = parentContext;
+  }
+
+  public BlazeContext push(@NotNull BlazeScope scope) {
+    scopes.add(scope);
+    scope.onScopeBegin(this);
+    return this;
+  }
+
+  /** Ends the context scope. */
+  public void endScope() {
+    if (isEnding || holdCount > 0) {
+      return;
+    }
+    isEnding = true;
+    for (int i = scopes.size() - 1; i >= 0; i--) {
+      scopes.get(i).onScopeEnd(this);
+    }
+
+    if (parentContext != null && hasErrors && propagatesErrors) {
+      parentContext.setHasError();
+    }
+  }
+
+  /**
+   * Requests cancellation of the operation.
+   *
+   * <p>
+   *
+   * <p>Each context holder must handle cancellation individually.
+   */
+  public void setCancelled() {
+    if (isEnding || isCancelled) {
+      return;
+    }
+
+    isCancelled = true;
+
+    if (parentContext != null) {
+      parentContext.setCancelled();
+    }
+  }
+
+  public void hold() {
+    ++holdCount;
+  }
+
+  public void release() {
+    if (--holdCount == 0) {
+      endScope();
+    }
+  }
+
+  public boolean isEnding() {
+    return isEnding;
+  }
+
+  public boolean isCancelled() {
+    return isCancelled;
+  }
+
+  @Nullable
+  public <T extends BlazeScope> T getScope(@NotNull Class<T> scopeClass) {
+    return getScope(scopeClass, scopes.size());
+  }
+
+  @Nullable
+  private <T extends BlazeScope> T getScope(@NotNull Class<T> scopeClass, int endIndex) {
+    for (int i = endIndex - 1; i >= 0; i--) {
+      if (scopes.get(i).getClass() == scopeClass) {
+        return scopeClass.cast(scopes.get(i));
+      }
+    }
+    if (parentContext != null) {
+      return parentContext.getScope(scopeClass);
+    }
+    return null;
+  }
+
+  @Nullable
+  public <T extends BlazeScope> T getParentScope(@NotNull T scope) {
+    int index = scopes.indexOf(scope);
+    if (index == -1) {
+      throw new IllegalArgumentException("Scope does not belong to this context.");
+    }
+    @SuppressWarnings("unchecked")
+    Class<T> scopeClass = (Class<T>) scope.getClass();
+    return getScope(scopeClass, index);
+  }
+
+  /**
+   * Find all instances of {@param scopeClass} that are on the stack starting with this context.
+   * That includes this context and all parent contexts recursively.
+   *
+   * @param scopeClass type of scopes to locate
+   * @return The ordered list of all scopes of type {@param scopeClass}, ordered from {@param
+   *     startingScope} to the root.
+   */
+  @NotNull
+  public <T extends BlazeScope> List<T> getScopes(@NotNull Class<T> scopeClass) {
+    List<T> scopesCollector = Lists.newArrayList();
+    getScopes(scopesCollector, scopeClass, scopes.size());
+    return scopesCollector;
+  }
+
+  /**
+   * Find all instances of {@param scopeClass} that are above {@param startingScope} on the stack.
+   * That includes this context and all parent contexts recursively. {@param startingScope} must be
+   * in the this {@link BlazeContext}.
+   *
+   * @param scopeClass type of scopes to locate
+   * @param startingScope scope to start our search from
+   * @return If {@param startingScope} is in this context, the ordered list of all scopes of type
+   *     {@param scopeClass}, ordered from {@param startingScope} to the root. Otherwise, an empty
+   *     list.
+   */
+  @NotNull
+  public <T extends BlazeScope> List<T> getScopes(
+      @NotNull Class<T> scopeClass, @NotNull BlazeScope startingScope) {
+    List<T> scopesCollector = Lists.newArrayList();
+    int index = scopes.indexOf(startingScope);
+    if (index == -1) {
+      return scopesCollector;
+    }
+
+    // index + 1 so we include startingScope
+    getScopes(scopesCollector, scopeClass, index + 1);
+    return scopesCollector;
+  }
+
+  /** Add matching scopes to {@param scopesCollector}. Search from {@param maxIndex} - 1 to 0. */
+  @VisibleForTesting
+  <T extends BlazeScope> void getScopes(
+      @NotNull List<T> scopesCollector, @NotNull Class<T> scopeClass, int maxIndex) {
+    for (int i = maxIndex - 1; i >= 0; --i) {
+      BlazeScope scope = scopes.get(i);
+      if (scope.getClass() == scopeClass) {
+        scopesCollector.add((T) scope);
+      }
+    }
+    if (parentContext != null) {
+      parentContext.getScopes(scopesCollector, scopeClass, parentContext.scopes.size());
+    }
+  }
+
+  public <T extends Output> BlazeContext addOutputSink(
+      @NotNull Class<T> outputClass, @NotNull OutputSink<T> outputSink) {
+    outputSinks.put(outputClass, outputSink);
+    return this;
+  }
+
+  /** Produces output by sending it to any registered sinks. */
+  @SuppressWarnings("unchecked")
+  public synchronized <T extends Output> void output(@NotNull T output) {
+    Class<? extends Output> outputClass = output.getClass();
+    List<OutputSink<?>> outputSinks = this.outputSinks.get(outputClass);
+
+    boolean continuePropagation = true;
+    for (int i = outputSinks.size() - 1; i >= 0; --i) {
+      OutputSink<?> outputSink = outputSinks.get(i);
+      OutputSink.Propagation propagation = ((OutputSink<T>) outputSink).onOutput(output);
+      continuePropagation = propagation == OutputSink.Propagation.Continue;
+      if (!continuePropagation) {
+        break;
+      }
+    }
+    if (continuePropagation && parentContext != null) {
+      parentContext.output(output);
+    }
+  }
+
+  /**
+   * Sets the error state.
+   *
+   * <p>
+   *
+   * <p>The error state will be propagated to any parents.
+   */
+  public void setHasError() {
+    this.hasErrors = true;
+  }
+
+  /** Returns true if there were errors */
+  public boolean hasErrors() {
+    return hasErrors;
+  }
+
+  public boolean isRoot() {
+    return parentContext == null;
+  }
+
+  /** Returns true if no errors and isn't cancelled. */
+  public boolean shouldContinue() {
+    return !hasErrors() && !isCancelled();
+  }
+
+  /** Sets whether errors are propagated to the parent context. */
+  public void setPropagatesErrors(boolean propagatesErrors) {
+    this.propagatesErrors = propagatesErrors;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/scope/BlazeScope.java b/base/src/com/google/idea/blaze/base/scope/BlazeScope.java
new file mode 100644
index 0000000..3c60543
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/scope/BlazeScope.java
@@ -0,0 +1,33 @@
+/*
+ * 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.scope;
+
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * A scoped facet of a scoped operation.
+ *
+ * <p>
+ *
+ * <p>Attaches to a blaze context and starts and ends with it.
+ */
+public interface BlazeScope {
+  /** Called when the scope is added to the context. */
+  void onScopeBegin(@NotNull BlazeContext context);
+
+  /** Called when the context scope is ending. */
+  void onScopeEnd(@NotNull BlazeContext context);
+}
diff --git a/base/src/com/google/idea/blaze/base/scope/Output.java b/base/src/com/google/idea/blaze/base/scope/Output.java
new file mode 100644
index 0000000..86af670
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/scope/Output.java
@@ -0,0 +1,19 @@
+/*
+ * 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.scope;
+
+/** A base interface for contextual output operations. */
+public interface Output {}
diff --git a/base/src/com/google/idea/blaze/base/scope/OutputSink.java b/base/src/com/google/idea/blaze/base/scope/OutputSink.java
new file mode 100644
index 0000000..8d8a51e
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/scope/OutputSink.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.base.scope;
+
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * An output sink registered with a context.
+ *
+ * <p>
+ *
+ * <p>Register these via a ScopeExtension.
+ */
+public interface OutputSink<T extends Output> {
+  /** Whether to continue propagation */
+  enum Propagation {
+    Continue,
+    Stop
+  }
+
+  /**
+   * Called when an Output of the correct type goes through the scope.
+   *
+   * @return Whether to continue propagation of this input.
+   */
+  Propagation onOutput(@NotNull T output);
+}
diff --git a/base/src/com/google/idea/blaze/base/scope/Result.java b/base/src/com/google/idea/blaze/base/scope/Result.java
new file mode 100644
index 0000000..383f67c
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/scope/Result.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.base.scope;
+
+/** Helper class to be used when you want to return a result or error in a scoped function. */
+public class Result<T> {
+  public final T result;
+  public final Throwable error;
+
+  public Result(T result) {
+    this.result = result;
+    this.error = null;
+  }
+
+  public Result(Throwable error) {
+    this.result = null;
+    this.error = error;
+  }
+
+  public static <T> Result<T> of(T result) {
+    return new Result<T>(result);
+  }
+
+  public static <T> Result<T> error(Throwable t) {
+    return new Result<T>(t);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/scope/Scope.java b/base/src/com/google/idea/blaze/base/scope/Scope.java
new file mode 100644
index 0000000..bb0c98d
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/scope/Scope.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.scope;
+
+import com.intellij.openapi.diagnostic.Logger;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/** Helper methods to run scoped functions and operations in a scoped context. */
+public final class Scope {
+  private static final Logger LOG = Logger.getInstance(Scope.class);
+
+  /** Runs a scoped function in a new root scope. */
+  public static <T> T root(@NotNull ScopedFunction<T> scopedFunction) {
+    return push(null, scopedFunction);
+  }
+
+  /** Runs a scoped function in a new nested scope. */
+  public static <T> T push(
+      @Nullable BlazeContext parentContext, @NotNull ScopedFunction<T> scopedFunction) {
+    BlazeContext context = new BlazeContext(parentContext);
+    try {
+      return scopedFunction.execute(context);
+    } catch (RuntimeException e) {
+      context.setHasError();
+      LOG.error(e);
+      throw e;
+    } finally {
+      context.endScope();
+    }
+  }
+
+  /** Runs a scoped operation in a new root scope. */
+  public static void root(@NotNull ScopedOperation scopedOperation) {
+    push(null, scopedOperation);
+  }
+
+  /** Runs a scoped operation in a new nested scope. */
+  public static void push(
+      @Nullable BlazeContext parentContext, @NotNull ScopedOperation scopedOperation) {
+    BlazeContext context = new BlazeContext(parentContext);
+    try {
+      scopedOperation.execute(context);
+    } catch (RuntimeException e) {
+      context.setHasError();
+      LOG.error(e);
+      throw e;
+    } finally {
+      context.endScope();
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/scope/ScopedFunction.java b/base/src/com/google/idea/blaze/base/scope/ScopedFunction.java
new file mode 100644
index 0000000..47c6365
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/scope/ScopedFunction.java
@@ -0,0 +1,23 @@
+/*
+ * 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.scope;
+
+import org.jetbrains.annotations.NotNull;
+
+/** A scoped operation that can return a result to its caller. */
+public interface ScopedFunction<T> {
+  T execute(@NotNull BlazeContext context);
+}
diff --git a/base/src/com/google/idea/blaze/base/scope/ScopedOperation.java b/base/src/com/google/idea/blaze/base/scope/ScopedOperation.java
new file mode 100644
index 0000000..1a704aa
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/scope/ScopedOperation.java
@@ -0,0 +1,23 @@
+/*
+ * 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.scope;
+
+import org.jetbrains.annotations.NotNull;
+
+/** A scoped operation. */
+public interface ScopedOperation {
+  void execute(@NotNull BlazeContext context);
+}
diff --git a/base/src/com/google/idea/blaze/base/scope/ScopedTask.java b/base/src/com/google/idea/blaze/base/scope/ScopedTask.java
new file mode 100644
index 0000000..725bf2f
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/scope/ScopedTask.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.scope;
+
+import com.google.idea.blaze.base.scope.scopes.ProgressIndicatorScope;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.progress.Progressive;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/** Wrapper between an IntelliJ Task and a BlazeContext */
+public abstract class ScopedTask implements Progressive {
+  @Nullable final BlazeContext parentContext;
+
+  public ScopedTask() {
+    this(null /* parentContext */);
+  }
+
+  public ScopedTask(@Nullable BlazeContext parentContext) {
+    this.parentContext = parentContext;
+  }
+
+  @Override
+  public void run(@NotNull final ProgressIndicator indicator) {
+    Scope.push(
+        parentContext,
+        new ScopedOperation() {
+          @Override
+          public void execute(@NotNull BlazeContext context) {
+            context.push(new ProgressIndicatorScope(indicator));
+            ScopedTask.this.execute(context);
+          }
+        });
+  }
+
+  protected abstract void execute(@NotNull BlazeContext context);
+}
diff --git a/base/src/com/google/idea/blaze/base/scope/output/IssueOutput.java b/base/src/com/google/idea/blaze/base/scope/output/IssueOutput.java
new file mode 100644
index 0000000..e3bc15a
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/scope/output/IssueOutput.java
@@ -0,0 +1,176 @@
+/*
+ * 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.scope.output;
+
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.scope.Output;
+import com.intellij.pom.Navigatable;
+import java.io.File;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/** An issue in a blaze operation. */
+public class IssueOutput implements Output {
+
+  public static final int NO_LINE = -1;
+  public static final int NO_COLUMN = -1;
+
+  @Nullable private final File file;
+  private final int line;
+  private final int column;
+  @NotNull private final Category category;
+  @NotNull private final String message;
+  @Nullable Navigatable navigatable;
+  @Nullable IssueData issueData;
+
+  /** Base class for issue data */
+  public static class IssueData {}
+
+  /** Issue category */
+  public enum Category {
+    ERROR,
+    WARNING,
+    STATISTICS,
+    INFORMATION
+  }
+
+  @NotNull
+  public static Builder issue(@NotNull Category category, @NotNull String message) {
+    return new Builder(category, message);
+  }
+
+  @NotNull
+  public static Builder error(@NotNull String message) {
+    return new Builder(Category.ERROR, message);
+  }
+
+  @NotNull
+  public static Builder warn(@NotNull String message) {
+    return new Builder(Category.WARNING, message);
+  }
+
+  /** Builder for an issue */
+  public static class Builder {
+    @NotNull private final Category category;
+    @NotNull private final String message;
+    @Nullable private File file;
+    private int line = NO_LINE;
+    private int column = NO_COLUMN;
+    @Nullable Navigatable navigatable;
+    @Nullable IssueData issueData;
+
+    public Builder(@NotNull Category category, @NotNull String message) {
+      this.category = category;
+      this.message = message;
+    }
+
+    @NotNull
+    public Builder inFile(@Nullable File file) {
+      this.file = file;
+      return this;
+    }
+
+    @NotNull
+    public Builder onLine(int line) {
+      this.line = line;
+      return this;
+    }
+
+    @NotNull
+    public Builder inColumn(int column) {
+      this.column = column;
+      return this;
+    }
+
+    @NotNull
+    public Builder withData(@Nullable IssueData issueData) {
+      this.issueData = issueData;
+      return this;
+    }
+
+    @NotNull
+    public Builder navigatable(@Nullable Navigatable navigatable) {
+      this.navigatable = navigatable;
+      return this;
+    }
+
+    public IssueOutput build() {
+      return new IssueOutput(file, line, column, navigatable, category, message, issueData);
+    }
+
+    public void submit(@NotNull BlazeContext context) {
+      context.output(build());
+      if (category == Category.ERROR) {
+        context.setHasError();
+      }
+    }
+  }
+
+  private IssueOutput(
+      @Nullable File file,
+      int line,
+      int column,
+      @Nullable Navigatable navigatable,
+      @NotNull Category category,
+      @NotNull String message,
+      @Nullable IssueData issueData) {
+    this.file = file;
+    this.line = line;
+    this.column = column;
+    this.navigatable = navigatable;
+    this.category = category;
+    this.message = message;
+    this.issueData = issueData;
+  }
+
+  @Nullable
+  public File getFile() {
+    return file;
+  }
+
+  public int getLine() {
+    return line;
+  }
+
+  public int getColumn() {
+    return column;
+  }
+
+  @Nullable
+  public Navigatable getNavigatable() {
+    return navigatable;
+  }
+
+  @NotNull
+  public Category getCategory() {
+    return category;
+  }
+
+  @NotNull
+  public String getMessage() {
+    return message;
+  }
+
+  @Override
+  public String toString() {
+    return message;
+  }
+
+  @Nullable
+  public IssueData getIssueData() {
+    return issueData;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/scope/output/PerformanceWarning.java b/base/src/com/google/idea/blaze/base/scope/output/PerformanceWarning.java
new file mode 100644
index 0000000..006875d
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/scope/output/PerformanceWarning.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.scope.output;
+
+import com.google.idea.blaze.base.scope.Output;
+
+/** Output that is collected when running in performance collection mode. */
+public class PerformanceWarning implements Output {
+  public final String text;
+
+  public PerformanceWarning(String text) {
+    this.text = text;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/scope/output/PrintOutput.java b/base/src/com/google/idea/blaze/base/scope/output/PrintOutput.java
new file mode 100644
index 0000000..7e9e8af
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/scope/output/PrintOutput.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.scope.output;
+
+import com.google.idea.blaze.base.scope.Output;
+import com.intellij.execution.process.ProcessOutputTypes;
+import com.intellij.openapi.util.Key;
+import org.jetbrains.annotations.NotNull;
+
+/** Output that can be printed to a log. */
+public class PrintOutput implements Output {
+
+  @NotNull private final String text;
+
+  @NotNull private final OutputType outputType;
+
+  /** The output type */
+  public enum OutputType {
+    NORMAL,
+    LOGGED,
+    ERROR;
+
+    public static OutputType fromProcessOutputKey(Key outputKey) {
+      return outputKey == ProcessOutputTypes.STDERR ? ERROR : NORMAL;
+    }
+  }
+
+  public PrintOutput(@NotNull String text, @NotNull OutputType outputType) {
+    this.text = text;
+    this.outputType = outputType;
+  }
+
+  public PrintOutput(@NotNull String text) {
+    this(text, OutputType.NORMAL);
+  }
+
+  @NotNull
+  public String getText() {
+    return text;
+  }
+
+  @NotNull
+  public OutputType getOutputType() {
+    return outputType;
+  }
+
+  public static PrintOutput output(String text) {
+    return new PrintOutput(text);
+  }
+
+  public static PrintOutput log(String text) {
+    return new PrintOutput(text, OutputType.LOGGED);
+  }
+
+  public static PrintOutput error(String text) {
+    return new PrintOutput(text, OutputType.ERROR);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/scope/output/StatusOutput.java b/base/src/com/google/idea/blaze/base/scope/output/StatusOutput.java
new file mode 100644
index 0000000..ea3bc3d
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/scope/output/StatusOutput.java
@@ -0,0 +1,33 @@
+/*
+ * 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.scope.output;
+
+import com.google.idea.blaze.base.scope.Output;
+import org.jetbrains.annotations.NotNull;
+
+/** Status message output. */
+public class StatusOutput implements Output {
+  @NotNull String status;
+
+  public StatusOutput(@NotNull String status) {
+    this.status = status;
+  }
+
+  @NotNull
+  public String getStatus() {
+    return status;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/scope/scopes/BlazeConsoleScope.java b/base/src/com/google/idea/blaze/base/scope/scopes/BlazeConsoleScope.java
new file mode 100644
index 0000000..19205cf
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/scope/scopes/BlazeConsoleScope.java
@@ -0,0 +1,127 @@
+/*
+ * 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.scope.scopes;
+
+import com.google.idea.blaze.base.console.BlazeConsoleService;
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.scope.BlazeScope;
+import com.google.idea.blaze.base.scope.OutputSink;
+import com.google.idea.blaze.base.scope.output.PrintOutput;
+import com.google.idea.blaze.base.scope.output.PrintOutput.OutputType;
+import com.google.idea.blaze.base.scope.output.StatusOutput;
+import com.intellij.execution.ui.ConsoleViewContentType;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.project.Project;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/** Moves print output to the blaze console. */
+public class BlazeConsoleScope implements BlazeScope {
+
+  /** Builder for blaze console scope */
+  public static class Builder {
+    private Project project;
+    private ProgressIndicator progressIndicator;
+    private boolean suppressConsole = false;
+
+    public Builder(@NotNull Project project) {
+      this(project, null);
+    }
+
+    public Builder(@NotNull Project project, ProgressIndicator progressIndicator) {
+      this.project = project;
+      this.progressIndicator = progressIndicator;
+    }
+
+    public Builder setSuppressConsole(boolean suppressConsole) {
+      this.suppressConsole = suppressConsole;
+      return this;
+    }
+
+    public BlazeConsoleScope build() {
+      return new BlazeConsoleScope(project, progressIndicator, suppressConsole);
+    }
+  }
+
+  @NotNull private final Project project;
+
+  @NotNull private final BlazeConsoleService blazeConsoleService;
+
+  @Nullable private final ProgressIndicator progressIndicator;
+
+  private final boolean showDialogOnChange;
+  private boolean activated;
+
+  private OutputSink<PrintOutput> printSink =
+      (output) -> {
+        @NotNull String text = output.getText();
+        @NotNull
+        ConsoleViewContentType contentType =
+            output.getOutputType() == OutputType.ERROR
+                ? ConsoleViewContentType.ERROR_OUTPUT
+                : ConsoleViewContentType.NORMAL_OUTPUT;
+        print(text, contentType);
+        return OutputSink.Propagation.Continue;
+      };
+
+  private OutputSink<StatusOutput> statusSink =
+      (output) -> {
+        @NotNull String text = output.getStatus();
+        @NotNull ConsoleViewContentType contentType = ConsoleViewContentType.NORMAL_OUTPUT;
+        print(text, contentType);
+        return OutputSink.Propagation.Continue;
+      };
+
+  private BlazeConsoleScope(
+      @NotNull Project project,
+      @Nullable ProgressIndicator progressIndicator,
+      boolean suppressConsole) {
+    this.project = project;
+    this.blazeConsoleService = BlazeConsoleService.getInstance(project);
+    this.progressIndicator = progressIndicator;
+    this.showDialogOnChange = !suppressConsole;
+  }
+
+  private void print(String text, ConsoleViewContentType contentType) {
+    blazeConsoleService.print(text + "\n", contentType);
+
+    if (showDialogOnChange && !activated) {
+      activated = true;
+      ApplicationManager.getApplication()
+          .invokeLater(() -> blazeConsoleService.activateConsoleWindow());
+    }
+  }
+
+  @Override
+  public void onScopeBegin(@NotNull final BlazeContext context) {
+    context.addOutputSink(PrintOutput.class, printSink);
+    context.addOutputSink(StatusOutput.class, statusSink);
+    blazeConsoleService.clear();
+    blazeConsoleService.setStopHandler(
+        () -> {
+          if (progressIndicator != null) {
+            progressIndicator.cancel();
+          }
+          context.setCancelled();
+        });
+  }
+
+  @Override
+  public void onScopeEnd(@NotNull BlazeContext context) {
+    blazeConsoleService.setStopHandler(null);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/scope/scopes/IdeaLogScope.java b/base/src/com/google/idea/blaze/base/scope/scopes/IdeaLogScope.java
new file mode 100644
index 0000000..4a1130c
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/scope/scopes/IdeaLogScope.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.scope.scopes;
+
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.scope.BlazeScope;
+import com.google.idea.blaze.base.scope.OutputSink;
+import com.google.idea.blaze.base.scope.output.IssueOutput;
+import com.google.idea.blaze.base.scope.output.PrintOutput;
+import com.google.idea.blaze.base.scope.output.StatusOutput;
+import com.intellij.openapi.diagnostic.Logger;
+import org.jetbrains.annotations.NotNull;
+
+/** Scope that captures relevant output to the IntelliJ log file. */
+public class IdeaLogScope implements BlazeScope {
+
+  private static final Logger LOG = Logger.getInstance(IdeaLogScope.class);
+
+  private static final OutputSink<IssueOutput> issueSink =
+      (output) -> {
+        LOG.warn(output.toString());
+        return OutputSink.Propagation.Continue;
+      };
+
+  private static final OutputSink<PrintOutput> printSink =
+      (output) -> {
+        switch (output.getOutputType()) {
+          case NORMAL:
+            break;
+          case LOGGED:
+            LOG.info(output.getText());
+            break;
+          case ERROR:
+            LOG.warn(output.getText());
+            break;
+        }
+        return OutputSink.Propagation.Continue;
+      };
+
+  private static final OutputSink<StatusOutput> statusSink =
+      (output) -> {
+        LOG.info(output.getStatus());
+        return OutputSink.Propagation.Continue;
+      };
+
+  @Override
+  public void onScopeBegin(@NotNull BlazeContext context) {
+    context.addOutputSink(IssueOutput.class, issueSink);
+    context.addOutputSink(PrintOutput.class, printSink);
+    context.addOutputSink(StatusOutput.class, statusSink);
+  }
+
+  @Override
+  public void onScopeEnd(@NotNull BlazeContext context) {}
+}
diff --git a/base/src/com/google/idea/blaze/base/scope/scopes/IssuesScope.java b/base/src/com/google/idea/blaze/base/scope/scopes/IssuesScope.java
new file mode 100644
index 0000000..fc02ee0
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/scope/scopes/IssuesScope.java
@@ -0,0 +1,82 @@
+/*
+ * 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.scope.scopes;
+
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.scope.BlazeScope;
+import com.google.idea.blaze.base.scope.OutputSink;
+import com.google.idea.blaze.base.scope.output.IssueOutput;
+import com.google.idea.blaze.base.ui.BlazeProblemsView;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.wm.ToolWindow;
+import com.intellij.openapi.wm.ToolWindowManager;
+import java.util.UUID;
+import org.jetbrains.annotations.NotNull;
+
+/** Shows the compiler output. */
+public class IssuesScope implements BlazeScope, OutputSink<IssueOutput> {
+
+  private final Project project;
+  private final UUID sessionId;
+  private int issuesCount;
+
+  public IssuesScope(@NotNull Project project) {
+    this.project = project;
+    this.sessionId = UUID.randomUUID();
+  }
+
+  @Override
+  public void onScopeBegin(@NotNull BlazeContext context) {
+    context.addOutputSink(IssueOutput.class, this);
+    BlazeProblemsView blazeProblemsView = BlazeProblemsView.getInstance(project);
+    if (blazeProblemsView != null) {
+      blazeProblemsView.clearOldMessages(sessionId);
+    }
+  }
+
+  @Override
+  public void onScopeEnd(@NotNull BlazeContext context) {
+    if (issuesCount > 0) {
+      ApplicationManager.getApplication()
+          .invokeLater(
+              new Runnable() {
+                @Override
+                public void run() {
+                  focusProblemsView();
+                }
+              });
+    }
+  }
+
+  private void focusProblemsView() {
+    ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(project);
+    ToolWindow toolWindow = toolWindowManager.getToolWindow("Problems");
+    if (toolWindow != null) {
+      toolWindow.activate(null, false, false);
+    }
+  }
+
+  @Override
+  public Propagation onOutput(@NotNull IssueOutput output) {
+    BlazeProblemsView blazeProblemsView = BlazeProblemsView.getInstance(project);
+    if (blazeProblemsView != null) {
+      blazeProblemsView.addMessage(output, sessionId);
+    }
+    ++issuesCount;
+    return Propagation.Continue;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/scope/scopes/LoggedTimingScope.java b/base/src/com/google/idea/blaze/base/scope/scopes/LoggedTimingScope.java
new file mode 100644
index 0000000..3b34acd
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/scope/scopes/LoggedTimingScope.java
@@ -0,0 +1,54 @@
+/*
+ * 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.scope.scopes;
+
+import com.google.common.base.Stopwatch;
+import com.google.idea.blaze.base.metrics.Action;
+import com.google.idea.blaze.base.metrics.LoggingService;
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.scope.BlazeScope;
+import com.intellij.openapi.project.Project;
+import java.util.concurrent.TimeUnit;
+
+/** Timing scope where the results are sent to a logging service */
+public class LoggedTimingScope implements BlazeScope {
+  // It is not guaranteed that the threading model will be sane during the entirety of this scope,
+  // so we use wall clock time and not ThreadMXBean where we could get user/system time.
+
+  Project project;
+  private final Action action;
+  private Stopwatch timer;
+
+  /** @param action The action we will be reporting a time for to the logging service */
+  public LoggedTimingScope(Project project, Action action) {
+    this.project = project;
+    this.action = action;
+    this.timer = Stopwatch.createUnstarted();
+  }
+
+  @Override
+  public void onScopeBegin(BlazeContext context) {
+    timer.start();
+  }
+
+  @Override
+  public void onScopeEnd(BlazeContext context) {
+    if (!context.isCancelled()) {
+      long totalMS = timer.elapsed(TimeUnit.MILLISECONDS);
+      LoggingService.reportEvent(project, action, totalMS);
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/scope/scopes/NotificationScope.java b/base/src/com/google/idea/blaze/base/scope/scopes/NotificationScope.java
new file mode 100644
index 0000000..5557e4e
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/scope/scopes/NotificationScope.java
@@ -0,0 +1,84 @@
+/*
+ * 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.scope.scopes;
+
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.scope.BlazeScope;
+import com.google.idea.blaze.base.scope.output.PrintOutput;
+import com.google.idea.blaze.base.scope.output.StatusOutput;
+import com.intellij.openapi.project.Project;
+import com.intellij.ui.SystemNotifications;
+import org.jetbrains.annotations.NotNull;
+
+/** Notifies the user with a system notification when the scope ends. */
+public class NotificationScope implements BlazeScope {
+
+  private static final long NOTIFICATION_THRESHOLD_MS = 0;
+
+  @NotNull private Project project;
+
+  @NotNull private final String notificationName;
+
+  @NotNull private final String notificationTitle;
+
+  @NotNull private final String notificationText;
+
+  @NotNull private final String notificationErrorText;
+
+  private long startTime;
+
+  public NotificationScope(
+      @NotNull Project project,
+      @NotNull String notificationName,
+      @NotNull String notificationTitle,
+      @NotNull String notificationText,
+      @NotNull String notificationErrorText) {
+    this.project = project;
+    this.notificationName = notificationName;
+    this.notificationTitle = notificationTitle;
+    this.notificationText = notificationText;
+    this.notificationErrorText = notificationErrorText;
+  }
+
+  @Override
+  public void onScopeBegin(@NotNull BlazeContext context) {
+    startTime = System.currentTimeMillis();
+  }
+
+  @Override
+  public void onScopeEnd(@NotNull BlazeContext context) {
+    if (project.isDisposed()) {
+      return;
+    }
+    if (context.isCancelled()) {
+      context.output(new StatusOutput(notificationName + " cancelled"));
+      return;
+    }
+    long duration = System.currentTimeMillis() - startTime;
+    if (duration < NOTIFICATION_THRESHOLD_MS) {
+      return;
+    }
+
+    String notificationText =
+        !context.hasErrors() ? this.notificationText : this.notificationErrorText;
+
+    SystemNotifications.getInstance().notify(notificationName, notificationTitle, notificationText);
+
+    if (context.hasErrors()) {
+      context.output(PrintOutput.error(notificationName + " failed"));
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/scope/scopes/PerformanceWarningScope.java b/base/src/com/google/idea/blaze/base/scope/scopes/PerformanceWarningScope.java
new file mode 100644
index 0000000..38a2dc0
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/scope/scopes/PerformanceWarningScope.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.scope.scopes;
+
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.scope.BlazeScope;
+import com.google.idea.blaze.base.scope.OutputSink;
+import com.google.idea.blaze.base.scope.output.PerformanceWarning;
+import com.google.idea.blaze.base.scope.output.PrintOutput;
+import java.util.List;
+
+/** Shows performance warnings. */
+public class PerformanceWarningScope implements BlazeScope, OutputSink<PerformanceWarning> {
+
+  private final List<PerformanceWarning> outputs = Lists.newArrayList();
+
+  @Override
+  public void onScopeBegin(BlazeContext context) {
+    context.addOutputSink(PerformanceWarning.class, this);
+  }
+
+  @Override
+  public void onScopeEnd(BlazeContext context) {
+    if (outputs.isEmpty()) {
+      return;
+    }
+    context.output(new PrintOutput("\n===== PERFORMANCE WARNINGS =====\n"));
+    context.output(new PrintOutput("Your IDE isn't as fast as it could be."));
+    context.output(
+        new PrintOutput("You can turn these off via Blaze > Show Performance Warnings."));
+    context.output(new PrintOutput(""));
+    for (PerformanceWarning output : outputs) {
+      context.output(new PrintOutput(output.text));
+    }
+  }
+
+  @Override
+  public Propagation onOutput(PerformanceWarning output) {
+    outputs.add(output);
+    return Propagation.Continue;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/scope/scopes/ProgressIndicatorScope.java b/base/src/com/google/idea/blaze/base/scope/scopes/ProgressIndicatorScope.java
new file mode 100644
index 0000000..faa3a7b
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/scope/scopes/ProgressIndicatorScope.java
@@ -0,0 +1,69 @@
+/*
+ * 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.scope.scopes;
+
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.scope.BlazeScope;
+import com.google.idea.blaze.base.scope.OutputSink;
+import com.google.idea.blaze.base.scope.output.StatusOutput;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.progress.util.AbstractProgressIndicatorExBase;
+import com.intellij.openapi.wm.ex.ProgressIndicatorEx;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Progress indicator scope.
+ *
+ * <p>
+ *
+ * <p>Channels status outputs to the progress indicator text.
+ *
+ * <p>Cancels the scope if the user presses cancel on the progress indicator.
+ */
+public class ProgressIndicatorScope extends AbstractProgressIndicatorExBase
+    implements BlazeScope, OutputSink<StatusOutput> {
+
+  private final ProgressIndicator progressIndicator;
+  private BlazeContext context;
+
+  public ProgressIndicatorScope(@NotNull ProgressIndicator progressIndicator) {
+    this.progressIndicator = progressIndicator;
+
+    if (progressIndicator instanceof ProgressIndicatorEx) {
+      ((ProgressIndicatorEx) progressIndicator).addStateDelegate(this);
+    }
+  }
+
+  @Override
+  public void onScopeBegin(@NotNull BlazeContext context) {
+    this.context = context;
+    context.addOutputSink(StatusOutput.class, this);
+  }
+
+  @Override
+  public void onScopeEnd(@NotNull BlazeContext context) {}
+
+  @Override
+  public void cancel() {
+    context.setCancelled();
+  }
+
+  @Override
+  public Propagation onOutput(@NotNull StatusOutput output) {
+    progressIndicator.setText(output.getStatus());
+    return Propagation.Continue;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/scope/scopes/ProjectCloseScope.java b/base/src/com/google/idea/blaze/base/scope/scopes/ProjectCloseScope.java
new file mode 100644
index 0000000..da1bc16
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/scope/scopes/ProjectCloseScope.java
@@ -0,0 +1,87 @@
+/*
+ * 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.scope.scopes;
+
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.scope.BlazeScope;
+import com.google.idea.blaze.base.settings.Blaze;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.ProjectManager;
+import com.intellij.openapi.project.ProjectManagerListener;
+import com.intellij.openapi.ui.Messages;
+import org.jetbrains.annotations.NotNull;
+
+/** Prevents the user from closing the project while the scope is open. */
+public class ProjectCloseScope implements ProjectManagerListener, BlazeScope {
+
+  @NotNull private final Project project;
+
+  private boolean isApplicationExitingOrProjectClosing;
+
+  public ProjectCloseScope(@NotNull Project project) {
+    this.project = project;
+  }
+
+  @Override
+  public void onScopeBegin(@NotNull BlazeContext context) {
+    ProjectManager projectManager = ProjectManager.getInstance();
+    projectManager.addProjectManagerListener(project, this);
+  }
+
+  @Override
+  public void onScopeEnd(@NotNull BlazeContext context) {
+    ProjectManager projectManager = ProjectManager.getInstance();
+    projectManager.removeProjectManagerListener(project, this);
+  }
+
+  @Override
+  public void projectOpened(Project project) {}
+
+  @Override
+  public boolean canCloseProject(Project project) {
+    if (!project.equals(this.project)) {
+      return true;
+    }
+    if (shouldPromptUser()) {
+      askUserToWait();
+      return false;
+    }
+    return false;
+  }
+
+  @Override
+  public void projectClosed(Project project) {}
+
+  @Override
+  public void projectClosing(Project project) {
+    if (project.equals(this.project)) {
+      isApplicationExitingOrProjectClosing = true;
+    }
+  }
+
+  private boolean shouldPromptUser() {
+    return !isApplicationExitingOrProjectClosing;
+  }
+
+  private void askUserToWait() {
+    String buildSystem = Blaze.buildSystemName(project);
+    Messages.showMessageDialog(
+        project,
+        String.format("Please wait until %s command execution finishes", buildSystem),
+        buildSystem + " Running",
+        null);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/scope/scopes/TimingScope.java b/base/src/com/google/idea/blaze/base/scope/scopes/TimingScope.java
new file mode 100644
index 0000000..d3dcad0
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/scope/scopes/TimingScope.java
@@ -0,0 +1,112 @@
+/*
+ * 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.scope.scopes;
+
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.scope.BlazeScope;
+import com.google.idea.blaze.base.scope.output.PrintOutput;
+import java.util.List;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/** Prints timing information as output. */
+public class TimingScope implements BlazeScope {
+
+  @NotNull private final String name;
+
+  private long startTime;
+
+  private double duration;
+
+  @Nullable private TimingScope parentScope;
+
+  @NotNull private List<TimingScope> children = Lists.newArrayList();
+
+  public TimingScope(@NotNull String name) {
+    this.name = name;
+  }
+
+  @Override
+  public void onScopeBegin(@NotNull BlazeContext context) {
+    startTime = System.currentTimeMillis();
+    parentScope = context.getParentScope(this);
+
+    if (parentScope != null) {
+      parentScope.children.add(this);
+    }
+  }
+
+  @Override
+  public void onScopeEnd(@NotNull BlazeContext context) {
+    if (context.isCancelled()) {
+      return;
+    }
+
+    long elapsedTime = System.currentTimeMillis() - startTime;
+    duration = (double) elapsedTime / 1000.0;
+
+    if (parentScope == null) {
+      outputReport(context);
+    }
+  }
+
+  private void outputReport(@NotNull BlazeContext context) {
+    context.output(PrintOutput.log("\n==== TIMING REPORT ====\n"));
+    outputReport(context, this, 0);
+  }
+
+  private static void outputReport(
+      @NotNull BlazeContext context, @NotNull TimingScope timingScope, int depth) {
+    String selfString = "";
+
+    // Self time trivially 100% if no children
+    if (timingScope.children.size() > 0) {
+      // Calculate self time as <my duration> - <sum child duration>
+      double selfTime = timingScope.duration;
+      for (TimingScope child : timingScope.children) {
+        selfTime -= child.duration;
+      }
+
+      selfString = selfTime > 0.1 ? String.format(" (%s)", durationStr(selfTime)) : "";
+    }
+
+    context.output(
+        PrintOutput.log(
+            String.format(
+                "%s%s: %s%s",
+                getIndentation(depth),
+                timingScope.name,
+                durationStr(timingScope.duration),
+                selfString)));
+
+    for (TimingScope child : timingScope.children) {
+      outputReport(context, child, depth + 1);
+    }
+  }
+
+  private static String durationStr(double time) {
+    return time >= 1.0 ? String.format("%.1fs", time) : String.format("%dms", (int) (time * 1000));
+  }
+
+  private static String getIndentation(int depth) {
+    StringBuilder sb = new StringBuilder();
+    for (int i = 0; i < depth; ++i) {
+      sb.append("    ");
+    }
+    return sb.toString();
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/settings/Blaze.java b/base/src/com/google/idea/blaze/base/settings/Blaze.java
new file mode 100644
index 0000000..d9e5537
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/settings/Blaze.java
@@ -0,0 +1,124 @@
+/*
+ * 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.settings;
+
+import com.google.idea.blaze.base.bazel.BuildSystemProvider;
+import com.intellij.ide.DataManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.ProjectManager;
+import javax.annotation.Nullable;
+import javax.swing.SwingUtilities;
+
+/** Blaze project utilities. */
+public class Blaze {
+
+  /** Build system enum */
+  public enum BuildSystem {
+    Blaze,
+    Bazel;
+
+    /** The build system name, capitalized. */
+    public String getName() {
+      return name();
+    }
+
+    /** The build system name, capitalized. */
+    public String getLowerCaseName() {
+      return name().toLowerCase();
+    }
+  }
+
+  private Blaze() {}
+
+  public static boolean isBlazeProjectOpen() {
+    for (Project project : ProjectManager.getInstance().getOpenProjects()) {
+      if (isBlazeProject(project)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /** Returns whether this project was imported from blaze. */
+  public static boolean isBlazeProject(Project project) {
+    return BlazeImportSettingsManager.getInstance(project).getImportSettings() != null;
+  }
+
+  /**
+   * Returns the build system associated with this project, or falls back to the default blaze build
+   * system if the project is null or not a blaze project.
+   */
+  public static BuildSystem getBuildSystem(@Nullable Project project) {
+    BlazeImportSettings importSettings =
+        project == null
+            ? null
+            : BlazeImportSettingsManager.getInstance(project).getImportSettings();
+    if (importSettings == null) {
+      return BuildSystemProvider.defaultBuildSystem().buildSystem();
+    }
+    return importSettings.getBuildSystem();
+  }
+
+  /**
+   * Returns the build system provider associated with this project, or falls back to the default
+   * blaze build system if the project is null or not a blaze project.
+   */
+  public static BuildSystemProvider getBuildSystemProvider(@Nullable Project project) {
+    return BuildSystemProvider.getBuildSystemProvider(getBuildSystem(project));
+  }
+
+  /**
+   * The name of the build system associated with the given project, or falls back to the default
+   * blaze build system if the project is null or not a blaze project.
+   */
+  public static String buildSystemName(@Nullable Project project) {
+    return getBuildSystem(project).getName();
+  }
+
+  /** The default build system */
+  public static BuildSystem defaultBuildSystem() {
+    return BuildSystemProvider.defaultBuildSystem().buildSystem();
+  }
+
+  /**
+   * The name of the application-wide build system default. This should only be used in situations
+   * where it doesn't make sense to use the build system associated with the current project (e.g.
+   * the import project action).
+   */
+  public static String defaultBuildSystemName() {
+    return BuildSystemProvider.defaultBuildSystem().buildSystem().getName();
+  }
+
+  /**
+   * Tries to guess the current project, and uses that to determine the build system name.<br>
+   * Should only be used in situations where the current project is not accessible.
+   */
+  public static String guessBuildSystemName() {
+    Project project = guessCurrentProject();
+    return buildSystemName(project);
+  }
+
+  private static Project guessCurrentProject() {
+    Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
+    if (openProjects.length == 1) {
+      return openProjects[0];
+    }
+    if (SwingUtilities.isEventDispatchThread()) {
+      return (Project) DataManager.getInstance().getDataContext().getData("project");
+    }
+    return null;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/settings/BlazeImportSettings.java b/base/src/com/google/idea/blaze/base/settings/BlazeImportSettings.java
new file mode 100644
index 0000000..7f0ea60
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/settings/BlazeImportSettings.java
@@ -0,0 +1,134 @@
+/*
+ * 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.settings;
+
+import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
+import com.intellij.util.xmlb.annotations.Tag;
+import javax.annotation.Nullable;
+
+// The tag here is for legacy migration support.
+// No longer needed and can be removed with {@link BlazeImportSettingsManagerLegacy}
+/** Project settings that are set at import time. */
+@Tag("BlazeProjectSettings")
+public final class BlazeImportSettings {
+
+  private String workspaceRoot = "";
+
+  private String projectName = "";
+
+  private String projectDataDirectory = "";
+
+  private String locationHash = "";
+
+  private String projectViewFile;
+
+  private BuildSystem buildSystem =
+      BuildSystem.Blaze; // default for backwards compatibility with existing projects.
+
+  // Used by bean serialization
+  @SuppressWarnings("unused")
+  BlazeImportSettings() {}
+
+  public BlazeImportSettings(
+      String workspaceRoot,
+      String projectName,
+      String projectDataDirectory,
+      String locationHash,
+      String projectViewFile,
+      BuildSystem buildSystem) {
+    this.workspaceRoot = workspaceRoot;
+    this.projectName = projectName;
+    this.projectDataDirectory = projectDataDirectory;
+    this.locationHash = locationHash;
+    this.projectViewFile = projectViewFile;
+    this.buildSystem = buildSystem;
+  }
+
+  @SuppressWarnings("unused")
+  public String getWorkspaceRoot() {
+    return workspaceRoot;
+  }
+
+  @SuppressWarnings("unused")
+  public String getProjectName() {
+    return projectName;
+  }
+
+  @SuppressWarnings("unused")
+  public String getProjectDataDirectory() {
+    return projectDataDirectory;
+  }
+
+  /** Hash used to give the project a unique directory in the system directory. */
+  @SuppressWarnings("unused")
+  public String getLocationHash() {
+    return locationHash;
+  }
+
+  /** The user's local project view file */
+  @SuppressWarnings("unused")
+  public String getProjectViewFile() {
+    return projectViewFile;
+  }
+
+  /** The build system used for the project. */
+  @SuppressWarnings("unused")
+  public BuildSystem getBuildSystem() {
+    return buildSystem;
+  }
+
+  // Used by bean serialization
+  @SuppressWarnings("unused")
+  public void setWorkspaceRoot(String workspaceRoot) {
+    this.workspaceRoot = workspaceRoot;
+  }
+
+  // Used by bean serialization
+  @SuppressWarnings("unused")
+  public void setProjectName(String projectName) {
+    this.projectName = projectName;
+  }
+
+  // Used by bean serialization
+  @SuppressWarnings("unused")
+  public void setProjectDataDirectory(String projectDataDirectory) {
+    this.projectDataDirectory = projectDataDirectory;
+  }
+
+  // Used by bean serialization
+  @SuppressWarnings("unused")
+  public void setLocationHash(String locationHash) {
+    this.locationHash = locationHash;
+  }
+
+  // Used by bean serialization
+  @SuppressWarnings("unused")
+  public void setProjectViewFile(@Nullable String projectViewFile) {
+    this.projectViewFile = projectViewFile;
+  }
+
+  // Used by bean serialization -- legacy import support
+  @SuppressWarnings("unused")
+  public void setAsProjectFile(@Nullable String projectViewFile) {
+    this.projectViewFile = projectViewFile;
+  }
+
+  // Used by bean serialization
+  @SuppressWarnings("unused")
+  public void setBuildSystem(BuildSystem buildSystem) {
+    this.buildSystem = buildSystem;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/settings/BlazeImportSettingsManager.java b/base/src/com/google/idea/blaze/base/settings/BlazeImportSettingsManager.java
new file mode 100644
index 0000000..a45b08e
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/settings/BlazeImportSettingsManager.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.settings;
+
+import com.intellij.openapi.components.PersistentStateComponent;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.components.State;
+import com.intellij.openapi.components.Storage;
+import com.intellij.openapi.components.StoragePathMacros;
+import com.intellij.openapi.project.Project;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/** Manages storage for the project's {@link BlazeImportSettings}. */
+@State(name = "BlazeImportSettings", storages = @Storage(file = StoragePathMacros.WORKSPACE_FILE))
+public class BlazeImportSettingsManager implements PersistentStateComponent<BlazeImportSettings> {
+
+  @Nullable private BlazeImportSettings importSettings;
+
+  private Project project;
+
+  public BlazeImportSettingsManager(@NotNull Project project) {
+    this.project = project;
+  }
+
+  public static BlazeImportSettingsManager getInstance(Project project) {
+    return ServiceManager.getService(project, BlazeImportSettingsManager.class);
+  }
+
+  @SuppressWarnings("unchecked")
+  @Nullable
+  @Override
+  public BlazeImportSettings getState() {
+    return importSettings;
+  }
+
+  @Override
+  public void loadState(BlazeImportSettings importSettings) {
+    this.importSettings = importSettings;
+  }
+
+  @Nullable
+  public BlazeImportSettings getImportSettings() {
+    if (importSettings == null) {
+      importSettings =
+          BlazeImportSettingsManagerLegacy.getInstance(project).migrateImportSettings();
+    }
+
+    return importSettings;
+  }
+
+  public void setImportSettings(@NotNull BlazeImportSettings importSettings) {
+    this.importSettings = importSettings;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/settings/BlazeImportSettingsManagerLegacy.java b/base/src/com/google/idea/blaze/base/settings/BlazeImportSettingsManagerLegacy.java
new file mode 100644
index 0000000..fec7d1d
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/settings/BlazeImportSettingsManagerLegacy.java
@@ -0,0 +1,115 @@
+/*
+ * 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.settings;
+
+import com.google.common.collect.Lists;
+import com.intellij.openapi.components.PersistentStateComponent;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.components.State;
+import com.intellij.openapi.components.Storage;
+import com.intellij.openapi.components.StoragePathMacros;
+import com.intellij.openapi.components.StorageScheme;
+import com.intellij.openapi.project.Project;
+import com.intellij.util.xmlb.annotations.AbstractCollection;
+import java.util.Collection;
+import java.util.List;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Legacy storage for BlazeImportSettings. Introduced in 1.8, can be removed around ~2.2. Removal of
+ * this class will cause old projects to stop loading.
+ */
+@State(
+  name = "BlazeSettings",
+  storages = {
+    @Storage(file = StoragePathMacros.PROJECT_FILE),
+    @Storage(
+      file = StoragePathMacros.PROJECT_CONFIG_DIR + "/blaze.xml",
+      scheme = StorageScheme.DIRECTORY_BASED
+    )
+  }
+)
+public class BlazeImportSettingsManagerLegacy
+    implements PersistentStateComponent<BlazeImportSettingsManagerLegacy.State> {
+
+  @Nullable private BlazeImportSettings importSettings;
+
+  @NotNull private Project project;
+
+  public BlazeImportSettingsManagerLegacy(@NotNull Project project) {
+    this.project = project;
+  }
+
+  @NotNull
+  public static BlazeImportSettingsManagerLegacy getInstance(@NotNull Project project) {
+    return ServiceManager.getService(project, BlazeImportSettingsManagerLegacy.class);
+  }
+
+  @SuppressWarnings("unchecked")
+  @Nullable
+  @Override
+  public State getState() {
+    if (importSettings == null) {
+      return null;
+    }
+    State state = new State();
+    List<BlazeImportSettings> value = Lists.newArrayList();
+    value.add(importSettings);
+    state.setLinkedExternalProjectsSettings(value);
+    return state;
+  }
+
+  @Override
+  public void loadState(State state) {
+    if (state == null) {
+      importSettings = null;
+      return;
+    }
+
+    Collection<BlazeImportSettings> settings = state.getLinkedExternalProjectsSettings();
+    if (settings != null && !settings.isEmpty()) {
+      importSettings = settings.iterator().next();
+    } else {
+      importSettings = null;
+    }
+  }
+
+  @Nullable
+  BlazeImportSettings migrateImportSettings() {
+    BlazeImportSettings importSettings = this.importSettings;
+    this.importSettings = null;
+    return importSettings;
+  }
+
+  /** State class for the Blaze settings. */
+  static class State {
+
+    private List<BlazeImportSettings> importSettings = Lists.newArrayList();
+
+    @AbstractCollection(
+      surroundWithTag = false,
+      elementTypes = {BlazeImportSettings.class}
+    )
+    public List<BlazeImportSettings> getLinkedExternalProjectsSettings() {
+      return importSettings;
+    }
+
+    public void setLinkedExternalProjectsSettings(List<BlazeImportSettings> settings) {
+      importSettings = settings;
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/settings/BlazeUserSettings.java b/base/src/com/google/idea/blaze/base/settings/BlazeUserSettings.java
new file mode 100644
index 0000000..1e74ef4
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/settings/BlazeUserSettings.java
@@ -0,0 +1,171 @@
+/*
+ * 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.settings;
+
+import com.google.idea.blaze.base.sync.status.BlazeSyncStatus;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.components.PersistentStateComponent;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.components.State;
+import com.intellij.openapi.components.Storage;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.ProjectManager;
+import com.intellij.util.xmlb.XmlSerializerUtil;
+import javax.annotation.Nullable;
+
+/** Stores blaze view settings. */
+@State(
+  name = "BlazeUserSettings",
+  storages = {
+    @Storage("blaze.user.settings.xml"),
+    @Storage(value = "blaze.view.xml", deprecated = true)
+  }
+)
+public class BlazeUserSettings implements PersistentStateComponent<BlazeUserSettings> {
+
+  public boolean suppressConsoleForRunAction = false;
+  private boolean resyncAutomatically = false;
+  private boolean syncStatusPopupShown = false;
+  private boolean expandSyncToWorkingSet = true;
+  private boolean showPerformanceWarnings = false;
+  private boolean attachSourcesByDefault = false;
+  private boolean attachSourcesOnDemand = false;
+  private boolean collapseProjectView = true;
+  private String blazeBinaryPath = "/usr/bin/blaze";
+  @Nullable private String bazelBinaryPath;
+
+  public static BlazeUserSettings getInstance() {
+    return ServiceManager.getService(BlazeUserSettings.class);
+  }
+
+  @Override
+  public BlazeUserSettings getState() {
+    return this;
+  }
+
+  @Override
+  public void loadState(BlazeUserSettings state) {
+    XmlSerializerUtil.copyBean(state, this);
+  }
+
+  /**
+   * Also kicks off an incremental sync if we're now syncing automatically, and the project is
+   * currently dirty.
+   */
+  public void setResyncAutomatically(boolean resyncAutomatically) {
+    if (this.resyncAutomatically == resyncAutomatically) {
+      return;
+    }
+    this.resyncAutomatically = resyncAutomatically;
+    ProjectManager projectManager =
+        ApplicationManager.getApplication().getComponent(ProjectManager.class);
+    Project[] openProjects = projectManager.getOpenProjects();
+    for (Project project : openProjects) {
+      if (Blaze.isBlazeProject(project)) {
+        BlazeSyncStatus.getInstance(project).queueAutomaticSyncIfDirty();
+      }
+    }
+  }
+
+  public boolean getResyncAutomatically() {
+    return resyncAutomatically;
+  }
+
+  public boolean getSuppressConsoleForRunAction() {
+    return suppressConsoleForRunAction;
+  }
+
+  public void setSuppressConsoleForRunAction(boolean suppressConsoleForRunAction) {
+    this.suppressConsoleForRunAction = suppressConsoleForRunAction;
+  }
+
+  public boolean getSyncStatusPopupShown() {
+    return syncStatusPopupShown;
+  }
+
+  public void setSyncStatusPopupShown(boolean syncStatusPopupShown) {
+    this.syncStatusPopupShown = syncStatusPopupShown;
+  }
+
+  public boolean getExpandSyncToWorkingSet() {
+    return expandSyncToWorkingSet;
+  }
+
+  public void setExpandSyncToWorkingSet(boolean expandSyncToWorkingSet) {
+    this.expandSyncToWorkingSet = expandSyncToWorkingSet;
+  }
+
+  public boolean getShowPerformanceWarnings() {
+    return showPerformanceWarnings;
+  }
+
+  public void setShowPerformanceWarnings(boolean showPerformanceWarnings) {
+    this.showPerformanceWarnings = showPerformanceWarnings;
+  }
+
+  public String getBlazeBinaryPath() {
+    return blazeBinaryPath;
+  }
+
+  public void setBlazeBinaryPath(String blazeBinaryPath) {
+    this.blazeBinaryPath = blazeBinaryPath;
+  }
+
+  @Nullable
+  public String getBazelBinaryPath() {
+    return bazelBinaryPath;
+  }
+
+  public void setBazelBinaryPath(String bazelBinaryPath) {
+    this.bazelBinaryPath = bazelBinaryPath;
+  }
+
+  public boolean getCollapseProjectView() {
+    return collapseProjectView;
+  }
+
+  public void setCollapseProjectView(boolean collapseProjectView) {
+    this.collapseProjectView = collapseProjectView;
+  }
+
+  // Deprecated -- use BlazeJavaUserSettings
+  @Deprecated
+  @SuppressWarnings("unused") // Used by bean serialization
+  public boolean getAttachSourcesByDefault() {
+    return attachSourcesByDefault;
+  }
+
+  // Deprecated -- use BlazeJavaUserSettings
+  @Deprecated
+  @SuppressWarnings("unused") // Used by bean serialization
+  public void setAttachSourcesByDefault(boolean attachSourcesByDefault) {
+    this.attachSourcesByDefault = attachSourcesByDefault;
+  }
+
+  // Deprecated -- use BlazeJavaUserSettings
+  @Deprecated
+  @SuppressWarnings("unused") // Used by bean serialization
+  public boolean getAttachSourcesOnDemand() {
+    return attachSourcesOnDemand;
+  }
+
+  // Deprecated -- use BlazeJavaUserSettings
+  @Deprecated
+  @SuppressWarnings("unused") // Used by bean serialization
+  public void setAttachSourcesOnDemand(boolean attachSourcesOnDemand) {
+    this.attachSourcesOnDemand = attachSourcesOnDemand;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/settings/IsBlazeProjectCondition.java b/base/src/com/google/idea/blaze/base/settings/IsBlazeProjectCondition.java
new file mode 100644
index 0000000..23da190
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/settings/IsBlazeProjectCondition.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.settings;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Condition;
+
+/** Condition for enabling features (e.g. Blaze and Crow consoles) only in Blaze projects. */
+public class IsBlazeProjectCondition implements Condition<Project> {
+
+  @Override
+  public boolean value(Project project) {
+    return project != null && Blaze.isBlazeProject(project);
+  }
+}
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
new file mode 100644
index 0000000..d541dc9
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/settings/ui/BlazeUserSettingsConfigurable.java
@@ -0,0 +1,287 @@
+/*
+ * 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.settings.ui;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+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;
+import com.google.idea.blaze.base.ui.FileSelectorWithStoredHistory;
+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;
+import java.awt.Insets;
+import java.util.Collection;
+import javax.annotation.Nullable;
+import javax.swing.JCheckBox;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.SwingConstants;
+
+/** Blaze console view settings */
+public class BlazeUserSettingsConfigurable extends BaseConfigurable
+    implements SearchableConfigurable {
+
+  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 Collection<BlazeUserSettingsContributor> settingsContributors;
+
+  private JPanel myMainPanel;
+  private JCheckBox suppressConsoleForRunAction;
+  private JCheckBox resyncAutomatically;
+  private JCheckBox collapseProjectView;
+  private FileSelectorWithStoredHistory blazeBinaryPathField;
+  private FileSelectorWithStoredHistory bazelBinaryPathField;
+
+  public BlazeUserSettingsConfigurable(Project project) {
+    this.buildSystem = Blaze.getBuildSystem(project);
+    this.settingsContributors = Lists.newArrayList();
+    for (BlazeUserSettingsContributor.Provider provider :
+        BlazeUserSettingsContributor.Provider.EP_NAME.getExtensions()) {
+      settingsContributors.add(provider.getContributor());
+    }
+
+    setupUI();
+  }
+
+  @Override
+  public String getDisplayName() {
+    return buildSystem.getName() + " View Settings";
+  }
+
+  @Nullable
+  @Override
+  public String getHelpTopic() {
+    return null;
+  }
+
+  @Override
+  public void apply() throws ConfigurationException {
+    BlazeUserSettings settings = BlazeUserSettings.getInstance();
+    settings.setSuppressConsoleForRunAction(suppressConsoleForRunAction.isSelected());
+    settings.setResyncAutomatically(resyncAutomatically.isSelected());
+    settings.setCollapseProjectView(collapseProjectView.isSelected());
+    settings.setBlazeBinaryPath(Strings.nullToEmpty(blazeBinaryPathField.getText()));
+    settings.setBazelBinaryPath(Strings.nullToEmpty(bazelBinaryPathField.getText()));
+
+    for (BlazeUserSettingsContributor settingsContributor : settingsContributors) {
+      settingsContributor.apply();
+    }
+  }
+
+  @Override
+  public void reset() {
+    BlazeUserSettings settings = BlazeUserSettings.getInstance();
+    suppressConsoleForRunAction.setSelected(settings.getSuppressConsoleForRunAction());
+    resyncAutomatically.setSelected(settings.getResyncAutomatically());
+    collapseProjectView.setSelected(settings.getCollapseProjectView());
+    blazeBinaryPathField.setTextWithHistory(settings.getBlazeBinaryPath());
+    bazelBinaryPathField.setTextWithHistory(settings.getBazelBinaryPath());
+
+    for (BlazeUserSettingsContributor settingsContributor : settingsContributors) {
+      settingsContributor.reset();
+    }
+  }
+
+  @Nullable
+  @Override
+  public JComponent createComponent() {
+    return myMainPanel;
+  }
+
+  @Override
+  public boolean isModified() {
+    BlazeUserSettings settings = BlazeUserSettings.getInstance();
+    boolean isModified =
+        !Objects.equal(
+                suppressConsoleForRunAction.isSelected(), settings.getSuppressConsoleForRunAction())
+            || !Objects.equal(resyncAutomatically.isSelected(), settings.getResyncAutomatically())
+            || !Objects.equal(collapseProjectView.isSelected(), settings.getCollapseProjectView())
+            || !Objects.equal(blazeBinaryPathField.getText(), settings.getBlazeBinaryPath())
+            || !Objects.equal(bazelBinaryPathField.getText(), settings.getBazelBinaryPath());
+
+    for (BlazeUserSettingsContributor settingsContributor : settingsContributors) {
+      isModified |= settingsContributor.isModified();
+    }
+    return isModified;
+  }
+
+  @Override
+  public void disposeUIResources() {}
+
+  @Override
+  public String getId() {
+    return "blaze.view.settings";
+  }
+
+  @Nullable
+  @Override
+  public Runnable enableSearch(String option) {
+    return null;
+  }
+
+  /** Initially generated by IntelliJ from a .form file. */
+  private void setupUI() {
+    int contributorRowCount = 0;
+    for (BlazeUserSettingsContributor contributor : settingsContributors) {
+      contributorRowCount += contributor.getRowCount();
+    }
+
+    final int totalRowSize = 5 + 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));
+    suppressConsoleForRunAction.setVerticalAlignment(SwingConstants.CENTER);
+    myMainPanel.add(
+        suppressConsoleForRunAction,
+        new GridConstraints(
+            rowi++,
+            0,
+            1,
+            2,
+            GridConstraints.ANCHOR_NORTHWEST,
+            GridConstraints.FILL_NONE,
+            GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
+    resyncAutomatically = new JCheckBox();
+    resyncAutomatically.setSelected(false);
+    resyncAutomatically.setText("Automatically re-sync project when BUILD files change");
+    myMainPanel.add(
+        resyncAutomatically,
+        new GridConstraints(
+            rowi++,
+            0,
+            1,
+            2,
+            GridConstraints.ANCHOR_NORTHWEST,
+            GridConstraints.FILL_NONE,
+            GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
+    collapseProjectView = new JCheckBox();
+    collapseProjectView.setSelected(false);
+    collapseProjectView.setText("Collapse project view directory roots");
+    myMainPanel.add(
+        collapseProjectView,
+        new GridConstraints(
+            rowi++,
+            0,
+            1,
+            2,
+            GridConstraints.ANCHOR_NORTHWEST,
+            GridConstraints.FILL_NONE,
+            GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
+    for (BlazeUserSettingsContributor contributor : settingsContributors) {
+      rowi = contributor.addComponents(myMainPanel, rowi);
+    }
+
+    blazeBinaryPathField =
+        FileSelectorWithStoredHistory.create(
+            BLAZE_BINARY_PATH_KEY, "Specify the blaze binary path");
+    bazelBinaryPathField =
+        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");
+    }
+    pathLabel.setLabelFor(pathPanel);
+    myMainPanel.add(
+        pathLabel,
+        new GridConstraints(
+            rowi,
+            0,
+            1,
+            1,
+            GridConstraints.ANCHOR_CENTER,
+            GridConstraints.FILL_NONE,
+            GridConstraints.SIZEPOLICY_FIXED,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            null,
+            0,
+            false));
+    myMainPanel.add(
+        pathPanel,
+        new GridConstraints(
+            rowi,
+            1,
+            1,
+            1,
+            GridConstraints.ANCHOR_CENTER,
+            GridConstraints.FILL_HORIZONTAL,
+            GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
+            GridConstraints.SIZEPOLICY_FIXED,
+            null,
+            null,
+            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/BlazeUserSettingsContributor.java b/base/src/com/google/idea/blaze/base/settings/ui/BlazeUserSettingsContributor.java
new file mode 100644
index 0000000..7ebe8b7
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/settings/ui/BlazeUserSettingsContributor.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.base.settings.ui;
+
+import com.intellij.openapi.extensions.ExtensionPointName;
+import javax.swing.JPanel;
+
+/** Allows other modules to contribute user settings. */
+public interface BlazeUserSettingsContributor {
+  /** Apply UI to settings. */
+  void apply();
+
+  /** Reset UI from settings. */
+  void reset();
+
+  /** @return Whether any settings in the UI is modified. */
+  boolean isModified();
+
+  /** Return the number of components you intend to add. */
+  int getRowCount();
+
+  /**
+   * Return all components. They will be added to the user settings page.
+   *
+   * @param panel The panel to add the components to
+   * @param rowi The row index to start adding components to.
+   * @return The next free row index
+   */
+  int addComponents(JPanel panel, int rowi);
+
+  /** A provider of user settings. Bind one of these to provide settings. */
+  interface Provider {
+    ExtensionPointName<Provider> EP_NAME =
+        ExtensionPointName.create("com.google.idea.blaze.BlazeUserSettingsContributor");
+
+    BlazeUserSettingsContributor getContributor();
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/settings/ui/EditProjectViewAction.java b/base/src/com/google/idea/blaze/base/settings/ui/EditProjectViewAction.java
new file mode 100644
index 0000000..b2e86be
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/settings/ui/EditProjectViewAction.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.settings.ui;
+
+import com.google.idea.blaze.base.actions.BlazeAction;
+import com.google.idea.blaze.base.projectview.ProjectViewManager;
+import com.google.idea.blaze.base.projectview.ProjectViewSet;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.fileEditor.FileEditorManager;
+import com.intellij.openapi.fileEditor.OpenFileDescriptor;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import java.io.File;
+
+/** Opens all the user's project views. */
+public class EditProjectViewAction extends BlazeAction {
+
+  @Override
+  public void actionPerformed(AnActionEvent e) {
+    Project project = e.getProject();
+    if (project == null) {
+      return;
+    }
+    ProjectViewSet projectViewSet = ProjectViewManager.getInstance(project).getProjectViewSet();
+    if (projectViewSet == null) {
+      return;
+    }
+    for (ProjectViewSet.ProjectViewFile projectViewFile : projectViewSet.getProjectViewFiles()) {
+      File file = projectViewFile.projectViewFile;
+      if (file != null) {
+        VirtualFile virtualFile = VfsUtil.findFileByIoFile(file, true);
+        if (virtualFile != null) {
+          OpenFileDescriptor descriptor = new OpenFileDescriptor(project, virtualFile);
+          FileEditorManager.getInstance(project).openTextEditor(descriptor, true);
+        }
+      }
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/settings/ui/JPanelProvidingProject.java b/base/src/com/google/idea/blaze/base/settings/ui/JPanelProvidingProject.java
new file mode 100644
index 0000000..8e763f1
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/settings/ui/JPanelProvidingProject.java
@@ -0,0 +1,45 @@
+/*
+ * 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.settings.ui;
+
+import com.intellij.openapi.actionSystem.CommonDataKeys;
+import com.intellij.openapi.actionSystem.DataProvider;
+import com.intellij.openapi.project.Project;
+import java.awt.LayoutManager;
+import javax.annotation.Nullable;
+import javax.swing.JPanel;
+
+/**
+ * A normal JPanel which implements DataProvider, providing the specified IntelliJ Project.
+ *
+ * <p>This is used by IntelliJ's action system to determine the relevant project associated with a
+ * UI component.
+ */
+public class JPanelProvidingProject extends JPanel implements DataProvider {
+
+  private final Project project;
+
+  public JPanelProvidingProject(Project project, LayoutManager layoutManager) {
+    super(layoutManager);
+    this.project = project;
+  }
+
+  @Nullable
+  @Override
+  public Object getData(String dataId) {
+    return CommonDataKeys.PROJECT.is(dataId) ? project : null;
+  }
+}
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
new file mode 100644
index 0000000..85df043
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/settings/ui/ProjectViewUi.java
@@ -0,0 +1,258 @@
+/*
+ * 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.settings.ui;
+
+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;
+import com.google.idea.blaze.base.scope.Scope;
+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.WorkspacePathResolverProvider;
+import com.google.idea.blaze.base.ui.UiUtil;
+import com.intellij.ide.DataManager;
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.application.Result;
+import com.intellij.openapi.application.WriteAction;
+import com.intellij.openapi.command.undo.UndoUtil;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.EditorFactory;
+import com.intellij.openapi.editor.EditorSettings;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.ex.EditorEx;
+import com.intellij.openapi.editor.impl.DocumentImpl;
+import com.intellij.openapi.editor.impl.EditorFactoryImpl;
+import com.intellij.openapi.fileEditor.impl.FileDocumentManagerImpl;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.ProjectManager;
+import com.intellij.openapi.project.impl.ProjectImpl;
+import com.intellij.openapi.util.Disposer;
+import com.intellij.psi.PsiManager;
+import com.intellij.psi.impl.PsiManagerEx;
+import com.intellij.psi.impl.file.impl.FileManager;
+import com.intellij.testFramework.LightVirtualFile;
+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;
+import javax.swing.JPanel;
+import javax.swing.UIManager;
+import org.jetbrains.annotations.NotNull;
+import org.picocontainer.MutablePicoContainer;
+
+/** UI for changing the ProjectView. */
+public class ProjectViewUi {
+
+  private static final String USE_SHARED_PROJECT_VIEW = "Use shared project view file";
+
+  private final Disposable parentDisposable;
+  private EditorEx projectViewEditor;
+  private JCheckBox useShared;
+
+  private WorkspaceRoot workspaceRoot;
+  private boolean useSharedProjectView;
+  private boolean allowEditShared;
+  private String sharedProjectViewText;
+  private boolean settingsInitialized;
+
+  public ProjectViewUi(Disposable parentDisposable) {
+    this.parentDisposable = parentDisposable;
+  }
+
+  /**
+   * To support the custom language features, we need a ProjectImpl, and it's not desirable to
+   * create one from scratch.<br>
+   *
+   * @return the current, non-default project, if one exists, else the default project.
+   */
+  public static Project getProject() {
+    Project project = (Project) DataManager.getInstance().getDataContext().getData("project");
+    if (project != null && project instanceof ProjectImpl) {
+      return project;
+    }
+    return ProjectManager.getInstance().getDefaultProject();
+  }
+
+  public static Dimension getMinimumSize() {
+    return new Dimension(1000, 550);
+  }
+
+  private static EditorEx createEditor(String tooltip) {
+    Project project = getProject();
+    LightVirtualFile virtualFile =
+        new LightVirtualFile("mockProjectViewFile", ProjectViewLanguage.INSTANCE, "");
+    final Document document =
+        ((EditorFactoryImpl) EditorFactory.getInstance()).createDocument(true);
+    ((DocumentImpl) document).setAcceptSlashR(true);
+    FileDocumentManagerImpl.registerDocument(document, virtualFile);
+
+    FileManager fileManager = ((PsiManagerEx) PsiManager.getInstance(project)).getFileManager();
+    fileManager.setViewProvider(virtualFile, fileManager.createFileViewProvider(virtualFile, true));
+
+    if (project.isDefault()) {
+      // Undo-redo doesn't work with the default project.
+      // Explicitly turn it off to avoid error dialogs.
+      UndoUtil.disableUndoFor(document);
+    }
+
+    EditorEx editor =
+        (EditorEx)
+            EditorFactory.getInstance()
+                .createEditor(document, project, ProjectViewFileType.INSTANCE, false);
+    final EditorSettings settings = editor.getSettings();
+    settings.setLineNumbersShown(false);
+    settings.setLineMarkerAreaShown(false);
+    settings.setFoldingOutlineShown(false);
+    settings.setRightMarginShown(false);
+    settings.setAdditionalPageAtBottom(false);
+    editor.getComponent().setMinimumSize(getMinimumSize());
+    editor.getComponent().setPreferredSize(getMinimumSize());
+    editor.getComponent().setToolTipText(tooltip);
+    editor.getComponent().setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, null);
+    editor.getComponent().setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, null);
+    return editor;
+  }
+
+  public void fillUi(JPanel canvas, int indentLevel) {
+    String tooltip =
+        "Enter a project view descriptor file."
+            + (Blaze.defaultBuildSystem() == BuildSystem.Blaze
+                ? " See 'go/intellij/docs/project-views.md' for more information."
+                : "");
+
+    projectViewEditor = createEditor(tooltip);
+    projectViewEditor
+        .getColorsScheme()
+        .setColor(EditorColors.READONLY_BACKGROUND_COLOR, UIManager.getColor("Label.background"));
+    Disposer.register(
+        parentDisposable, () -> EditorFactory.getInstance().releaseEditor(projectViewEditor));
+
+    JBLabel labelsLabel = new JBLabel("Project View");
+    labelsLabel.setToolTipText(tooltip);
+    canvas.add(labelsLabel, UiUtil.getFillLineConstraints(indentLevel));
+
+    canvas.add(projectViewEditor.getComponent(), UiUtil.getFillLineConstraints(indentLevel));
+
+    useShared = new JCheckBox(USE_SHARED_PROJECT_VIEW);
+    useShared.addActionListener(
+        e -> {
+          useSharedProjectView = useShared.isSelected();
+          if (useSharedProjectView) {
+            setProjectViewText(sharedProjectViewText);
+          }
+          updateTextAreasEnabled();
+        });
+    canvas.add(useShared, UiUtil.getFillLineConstraints(indentLevel));
+  }
+
+  public void init(
+      WorkspaceRoot workspaceRoot,
+      String projectViewText,
+      @Nullable String sharedProjectViewText,
+      @Nullable File sharedProjectViewFile,
+      boolean useSharedProjectView,
+      boolean allowEditShared) {
+    this.workspaceRoot = workspaceRoot;
+    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());
+    }
+
+    useShared.setSelected(useSharedProjectView);
+
+    if (sharedProjectViewText == null) {
+      useShared.setEnabled(false);
+    }
+
+    setDummyWorkspacePathResolverProvider(workspaceRoot);
+    setProjectViewText(projectViewText);
+    settingsInitialized = true;
+  }
+
+  private void setDummyWorkspacePathResolverProvider(WorkspaceRoot workspaceRoot) {
+    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));
+    if (!settingsInitialized) {
+      Disposer.register(
+          parentDisposable,
+          () -> {
+            container.unregisterComponent(key.getName());
+            if (oldProvider != null) {
+              container.registerComponentInstance(key.getName(), oldProvider);
+            }
+          });
+    }
+  }
+
+  private void setProjectViewText(String projectViewText) {
+    new WriteAction() {
+      @Override
+      protected void run(@NotNull Result result) throws Throwable {
+        projectViewEditor.getDocument().setReadOnly(false);
+        projectViewEditor.getDocument().setText(projectViewText);
+      }
+    }.execute();
+    updateTextAreasEnabled();
+  }
+
+  private void updateTextAreasEnabled() {
+    boolean editEnabled = allowEditShared || !useSharedProjectView;
+    projectViewEditor.setViewer(!editEnabled);
+    projectViewEditor.getDocument().setReadOnly(!editEnabled);
+    projectViewEditor.reinitSettings();
+  }
+
+  public ProjectViewSet parseProjectView(final List<IssueOutput> issues) {
+    final String projectViewText = projectViewEditor.getDocument().getText();
+    final OutputSink<IssueOutput> issueCollector =
+        output -> {
+          issues.add(output);
+          return OutputSink.Propagation.Continue;
+        };
+    return Scope.root(
+        context -> {
+          context.addOutputSink(IssueOutput.class, issueCollector);
+          ProjectViewParser projectViewParser =
+              new ProjectViewParser(context, new WorkspacePathResolverImpl(workspaceRoot));
+          projectViewParser.parseProjectView(projectViewText);
+          return projectViewParser.getResult();
+        });
+  }
+
+  public boolean getUseSharedProjectView() {
+    return this.useSharedProjectView;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/sync/BlazeSyncManager.java b/base/src/com/google/idea/blaze/base/sync/BlazeSyncManager.java
new file mode 100644
index 0000000..db3cba2
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/BlazeSyncManager.java
@@ -0,0 +1,62 @@
+/*
+ * 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;
+
+import com.google.idea.blaze.base.async.executor.BlazeExecutor;
+import com.google.idea.blaze.base.settings.Blaze;
+import com.google.idea.blaze.base.settings.BlazeImportSettings;
+import com.google.idea.blaze.base.settings.BlazeImportSettingsManager;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.startup.StartupManager;
+import org.jetbrains.annotations.NotNull;
+
+/** Manages syncing and its listeners. */
+public class BlazeSyncManager {
+
+  @NotNull private final Project project;
+
+  public BlazeSyncManager(@NotNull Project project) {
+    this.project = project;
+  }
+
+  public static BlazeSyncManager getInstance(@NotNull Project project) {
+    return ServiceManager.getService(project, BlazeSyncManager.class);
+  }
+
+  /** Requests a project sync with Blaze. */
+  public void requestProjectSync(@NotNull final BlazeSyncParams syncParams) {
+    StartupManager.getInstance(project)
+        .runWhenProjectIsInitialized(
+            new Runnable() {
+              @Override
+              public void run() {
+                final BlazeImportSettings importSettings =
+                    BlazeImportSettingsManager.getInstance(project).getImportSettings();
+                if (importSettings == null) {
+                  throw new IllegalStateException(
+                      String.format(
+                          "Attempt to sync non-%s project.", Blaze.buildSystemName(project)));
+                }
+
+                final BlazeSyncTask syncTask =
+                    new BlazeSyncTask(project, importSettings, syncParams);
+
+                BlazeExecutor.submitTask(project, syncTask);
+              }
+            });
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/sync/BlazeSyncParams.java b/base/src/com/google/idea/blaze/base/sync/BlazeSyncParams.java
new file mode 100644
index 0000000..0f904fc
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/BlazeSyncParams.java
@@ -0,0 +1,105 @@
+/*
+ * 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;
+
+import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.base.model.primitives.TargetExpression;
+import java.util.Collection;
+import javax.annotation.concurrent.Immutable;
+
+/** Parameters that control the sync. */
+@Immutable
+public final class BlazeSyncParams {
+
+  /** The kind of sync. */
+  public enum SyncMode {
+    RESTORE_EPHEMERAL_STATE,
+    INCREMENTAL,
+    FULL
+  }
+
+  /** Builder for sync params */
+  public static final class Builder {
+    private String title;
+    private SyncMode syncMode;
+    private boolean backgroundSync;
+    private boolean addProjectViewTargets;
+    private boolean addWorkingSet;
+    private ImmutableList.Builder<TargetExpression> targetExpressions = ImmutableList.builder();
+
+    public Builder(String title, SyncMode syncMode) {
+      this.title = title;
+      this.syncMode = syncMode;
+    }
+
+    public Builder setBackgroundSync(boolean backgroundSync) {
+      this.backgroundSync = backgroundSync;
+      return this;
+    }
+
+    public Builder addProjectViewTargets(boolean addProjectViewTargets) {
+      this.addProjectViewTargets = addProjectViewTargets;
+      return this;
+    }
+
+    public Builder addTargetExpression(TargetExpression targetExpression) {
+      this.targetExpressions.add(targetExpression);
+      return this;
+    }
+
+    public Builder addTargetExpressions(Collection<TargetExpression> targets) {
+      this.targetExpressions.addAll(targets);
+      return this;
+    }
+
+    public Builder addWorkingSet(boolean addWorkingSet) {
+      this.addWorkingSet = addWorkingSet;
+      return this;
+    }
+
+    public BlazeSyncParams build() {
+      return new BlazeSyncParams(
+          title,
+          syncMode,
+          backgroundSync,
+          addProjectViewTargets,
+          addWorkingSet,
+          targetExpressions.build());
+    }
+  }
+
+  public final String title;
+  public final SyncMode syncMode;
+  public final boolean backgroundSync;
+  public final boolean addProjectViewTargets;
+  public final boolean addWorkingSet;
+  public final ImmutableList<TargetExpression> targetExpressions;
+
+  private BlazeSyncParams(
+      String title,
+      SyncMode syncMode,
+      boolean backgroundSync,
+      boolean addProjectViewTargets,
+      boolean addWorkingSet,
+      ImmutableList<TargetExpression> targetExpressions) {
+    this.title = title;
+    this.syncMode = syncMode;
+    this.backgroundSync = backgroundSync;
+    this.addProjectViewTargets = addProjectViewTargets;
+    this.addWorkingSet = addWorkingSet;
+    this.targetExpressions = targetExpressions;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/sync/BlazeSyncPlugin.java b/base/src/com/google/idea/blaze/base/sync/BlazeSyncPlugin.java
new file mode 100644
index 0000000..985ff17
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/BlazeSyncPlugin.java
@@ -0,0 +1,242 @@
+/*
+ * 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;
+
+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.RuleMap;
+import com.google.idea.blaze.base.model.SyncState;
+import com.google.idea.blaze.base.model.primitives.LanguageClass;
+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.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.projectview.WorkspaceLanguageSettings;
+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.intellij.openapi.extensions.ExtensionPointName;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleType;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ContentEntry;
+import com.intellij.openapi.roots.ModifiableRootModel;
+import java.util.Collection;
+import java.util.Set;
+import javax.annotation.Nullable;
+
+/** Can plug into the blaze sync system. */
+public interface BlazeSyncPlugin {
+  ExtensionPointName<BlazeSyncPlugin> EP_NAME =
+      ExtensionPointName.create("com.google.idea.blaze.SyncPlugin");
+
+  /**
+   * May be used by the plugin to create/edit modules.
+   *
+   * <p>Using this ensures that the blaze plugin is aware of the modules, won't garbage collect
+   * them, and that all module modifications happen in a single transaction.
+   */
+  interface ModuleEditor {
+    /** Creates a new module and registers it with the module editor. */
+    Module createModule(String moduleName, ModuleType moduleType);
+
+    /**
+     * Edits a module. It will be committed when commit is called.
+     *
+     * <p>The module will be returned in a cleared state. You should not call this method multiple
+     * times.
+     */
+    ModifiableRootModel editModule(Module module);
+
+    /**
+     * Registers a module. This prevents garbage collection of the module upon commit.
+     *
+     * @return True if the module exists and was registered.
+     */
+    boolean registerModule(String moduleName);
+
+    /** Finds a module by name. This doesn't register the module. */
+    @Nullable
+    Module findModule(String moduleName);
+
+    /** Commits the module editor without garbage collection. */
+    void commit();
+  }
+
+  /** @return The default workspace type recommended by this plugin. */
+  @Nullable
+  WorkspaceType getDefaultWorkspaceType();
+
+  /** @return The module type for the workspace given the workspace type. */
+  @Nullable
+  ModuleType getWorkspaceModuleType(WorkspaceType workspaceType);
+
+  /** @return The set of supported languages under this workspace type. */
+  Set<LanguageClass> getSupportedLanguagesInWorkspace(WorkspaceType workspaceType);
+
+  /** Given the rule map, update the sync state for this plugin. Should not have side effects. */
+  void updateSyncState(
+      Project project,
+      BlazeContext context,
+      WorkspaceRoot workspaceRoot,
+      ProjectViewSet projectViewSet,
+      WorkspaceLanguageSettings workspaceLanguageSettings,
+      BlazeRoots blazeRoots,
+      @Nullable WorkingSet workingSet,
+      WorkspacePathResolver workspacePathResolver,
+      RuleMap ruleMap,
+      SyncState.Builder syncStateBuilder,
+      @Nullable SyncState previousSyncState);
+
+  /** Updates the sdk. */
+  void updateSdk(
+      Project project,
+      BlazeContext context,
+      ProjectViewSet projectViewSet,
+      BlazeProjectData blazeProjectData);
+
+  /**
+   * Modify the project content entries. There will be one content entry per project directory from
+   * the project view set.
+   */
+  void updateContentEntries(
+      Project project,
+      BlazeContext context,
+      WorkspaceRoot workspaceRoot,
+      ProjectViewSet projectViewSet,
+      BlazeProjectData blazeProjectData,
+      Collection<ContentEntry> contentEntries);
+
+  /** Modifies the IDE project structure in accordance with the sync data. */
+  void updateProjectStructure(
+      Project project,
+      BlazeContext context,
+      WorkspaceRoot workspaceRoot,
+      ProjectViewSet projectViewSet,
+      BlazeProjectData blazeProjectData,
+      @Nullable BlazeProjectData oldBlazeProjectData,
+      ModuleEditor moduleEditor,
+      Module workspaceModule,
+      ModifiableRootModel workspaceModifiableModel);
+
+  /** Validates the project. */
+  boolean validate(Project project, BlazeContext context, BlazeProjectData blazeProjectData);
+
+  /**
+   * Validates the project view.
+   *
+   * @return True for success, false for fatal error.
+   */
+  boolean validateProjectView(
+      BlazeContext context,
+      ProjectViewSet projectViewSet,
+      WorkspaceLanguageSettings workspaceLanguageSettings);
+
+  /** Returns any custom sections that this plugin supports. */
+  Collection<SectionParser> getSections();
+
+  /** Returns whether this plugin requires resolving ide artifacts to function. */
+  boolean requiresResolveIdeArtifacts();
+
+  /** Convenience adapter to help stubbing out methods. */
+  class Adapter implements BlazeSyncPlugin {
+
+    @Nullable
+    @Override
+    public WorkspaceType getDefaultWorkspaceType() {
+      return null;
+    }
+
+    @Nullable
+    @Override
+    public ModuleType getWorkspaceModuleType(WorkspaceType workspaceType) {
+      return null;
+    }
+
+    @Override
+    public Set<LanguageClass> getSupportedLanguagesInWorkspace(WorkspaceType workspaceType) {
+      return ImmutableSet.of();
+    }
+
+    @Override
+    public void updateSyncState(
+        Project project,
+        BlazeContext context,
+        WorkspaceRoot workspaceRoot,
+        ProjectViewSet projectViewSet,
+        WorkspaceLanguageSettings workspaceLanguageSettings,
+        BlazeRoots blazeRoots,
+        @Nullable WorkingSet workingSet,
+        WorkspacePathResolver workspacePathResolver,
+        RuleMap ruleMap,
+        SyncState.Builder syncStateBuilder,
+        @Nullable SyncState previousSyncState) {}
+
+    @Override
+    public void updateSdk(
+        Project project,
+        BlazeContext context,
+        ProjectViewSet projectViewSet,
+        BlazeProjectData blazeProjectData) {}
+
+    @Override
+    public void updateContentEntries(
+        Project project,
+        BlazeContext context,
+        WorkspaceRoot workspaceRoot,
+        ProjectViewSet projectViewSet,
+        BlazeProjectData blazeProjectData,
+        Collection<ContentEntry> contentEntries) {}
+
+    @Override
+    public void updateProjectStructure(
+        Project project,
+        BlazeContext context,
+        WorkspaceRoot workspaceRoot,
+        ProjectViewSet projectViewSet,
+        BlazeProjectData blazeProjectData,
+        @Nullable BlazeProjectData oldBlazeProjectData,
+        ModuleEditor moduleEditor,
+        Module workspaceModule,
+        ModifiableRootModel workspaceModifiableModel) {}
+
+    @Override
+    public boolean validate(
+        Project project, BlazeContext context, BlazeProjectData blazeProjectData) {
+      return true;
+    }
+
+    @Override
+    public boolean validateProjectView(
+        BlazeContext context,
+        ProjectViewSet projectViewSet,
+        WorkspaceLanguageSettings workspaceLanguageSettings) {
+      return true;
+    }
+
+    @Override
+    public Collection<SectionParser> getSections() {
+      return ImmutableList.of();
+    }
+
+    @Override
+    public boolean requiresResolveIdeArtifacts() {
+      return false;
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/sync/BlazeSyncStartupActivity.java b/base/src/com/google/idea/blaze/base/sync/BlazeSyncStartupActivity.java
new file mode 100644
index 0000000..5b6a8ee
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/BlazeSyncStartupActivity.java
@@ -0,0 +1,52 @@
+/*
+ * 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;
+
+import com.google.idea.blaze.base.settings.BlazeImportSettings;
+import com.google.idea.blaze.base.settings.BlazeImportSettingsManager;
+import com.google.idea.blaze.base.settings.BlazeUserSettings;
+import com.google.idea.blaze.base.sync.BlazeSyncParams.SyncMode;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.startup.StartupActivity;
+import org.jetbrains.annotations.NotNull;
+
+/** Syncs the project upon startup. */
+public class BlazeSyncStartupActivity implements StartupActivity {
+
+  @Override
+  public void runActivity(@NotNull final Project project) {
+    BlazeImportSettings importSettings =
+        BlazeImportSettingsManager.getInstance(project).getImportSettings();
+
+    if (importSettings != null) {
+      BlazeSyncManager.getInstance(project).requestProjectSync(getSyncParams());
+    }
+  }
+
+  private static BlazeSyncParams getSyncParams() {
+    if (BlazeUserSettings.getInstance().getResyncAutomatically()) {
+      return new BlazeSyncParams.Builder("Sync", SyncMode.INCREMENTAL)
+          .addProjectViewTargets(true)
+          .addWorkingSet(BlazeUserSettings.getInstance().getExpandSyncToWorkingSet())
+          .build();
+    }
+    return new BlazeSyncParams.Builder(
+            "Sync Project", BlazeSyncParams.SyncMode.RESTORE_EPHEMERAL_STATE)
+        .addProjectViewTargets(true)
+        .addWorkingSet(BlazeUserSettings.getInstance().getExpandSyncToWorkingSet())
+        .build();
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/sync/BlazeSyncTask.java b/base/src/com/google/idea/blaze/base/sync/BlazeSyncTask.java
new file mode 100755
index 0000000..a49b8bf
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/BlazeSyncTask.java
@@ -0,0 +1,723 @@
+/*
+ * 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;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+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.experiments.ExperimentScope;
+import com.google.idea.blaze.base.filecache.FileCaches;
+import com.google.idea.blaze.base.metrics.Action;
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.model.RuleMap;
+import com.google.idea.blaze.base.model.SyncState;
+import com.google.idea.blaze.base.model.primitives.Label;
+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.prefetch.PrefetchService;
+import com.google.idea.blaze.base.projectview.ProjectViewManager;
+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;
+import com.google.idea.blaze.base.scope.output.PrintOutput;
+import com.google.idea.blaze.base.scope.output.StatusOutput;
+import com.google.idea.blaze.base.scope.scopes.BlazeConsoleScope;
+import com.google.idea.blaze.base.scope.scopes.IdeaLogScope;
+import com.google.idea.blaze.base.scope.scopes.IssuesScope;
+import com.google.idea.blaze.base.scope.scopes.LoggedTimingScope;
+import com.google.idea.blaze.base.scope.scopes.NotificationScope;
+import com.google.idea.blaze.base.scope.scopes.PerformanceWarningScope;
+import com.google.idea.blaze.base.scope.scopes.ProgressIndicatorScope;
+import com.google.idea.blaze.base.scope.scopes.TimingScope;
+import com.google.idea.blaze.base.settings.Blaze;
+import com.google.idea.blaze.base.settings.BlazeImportSettings;
+import com.google.idea.blaze.base.settings.BlazeUserSettings;
+import com.google.idea.blaze.base.sync.BlazeSyncParams.SyncMode;
+import com.google.idea.blaze.base.sync.BlazeSyncPlugin.ModuleEditor;
+import com.google.idea.blaze.base.sync.SyncListener.SyncResult;
+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.BlazeDataStorage;
+import com.google.idea.blaze.base.sync.data.BlazeProjectDataManagerImpl;
+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;
+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.ArtifactLocationDecoder;
+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.google.idea.blaze.base.util.SaveUtil;
+import com.google.idea.blaze.base.vcs.BlazeVcsHandler;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleType;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.progress.Progressive;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ContentEntry;
+import com.intellij.openapi.roots.ModifiableRootModel;
+import com.intellij.openapi.roots.ex.ProjectRootManagerEx;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.StandardFileSystems;
+import com.intellij.openapi.vfs.VirtualFileManager;
+import java.io.File;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
+
+/** Syncs the project with blaze. */
+final class BlazeSyncTask implements Progressive {
+
+  private static final Logger LOG = Logger.getInstance(BlazeSyncTask.class);
+
+  private final Project project;
+  private final BlazeImportSettings importSettings;
+  private final WorkspaceRoot workspaceRoot;
+  private final BlazeSyncParams syncParams;
+  private final boolean showPerformanceWarnings;
+  private long syncStartTime;
+
+  BlazeSyncTask(
+      Project project, BlazeImportSettings importSettings, final BlazeSyncParams syncParams) {
+    this.project = project;
+    this.importSettings = importSettings;
+    this.workspaceRoot = WorkspaceRoot.fromImportSettings(importSettings);
+    this.syncParams = syncParams;
+    this.showPerformanceWarnings = BlazeUserSettings.getInstance().getShowPerformanceWarnings();
+  }
+
+  @Override
+  public void run(final ProgressIndicator indicator) {
+    Scope.root(
+        (BlazeContext context) -> {
+          context.push(new ExperimentScope());
+          if (showPerformanceWarnings) {
+            context.push(new PerformanceWarningScope());
+          }
+          context
+              .push(new ProgressIndicatorScope(indicator))
+              .push(new TimingScope("Sync"))
+              .push(new LoggedTimingScope(project, Action.SYNC_TOTAL_TIME));
+
+          if (!syncParams.backgroundSync) {
+            context
+                .push(new BlazeConsoleScope.Builder(project, indicator).build())
+                .push(new IssuesScope(project))
+                .push(new IdeaLogScope())
+                .push(
+                    new NotificationScope(
+                        project, "Sync", "Sync project", "Sync successful", "Sync failed"));
+          }
+
+          context.output(new StatusOutput("Syncing project..."));
+          syncProject(context);
+        });
+  }
+
+  /** Returns true if sync successfully completed */
+  @VisibleForTesting
+  boolean syncProject(BlazeContext context) {
+    SyncResult syncResult = SyncResult.FAILURE;
+    try {
+      SaveUtil.saveAllFiles();
+      onSyncStart(project);
+      syncResult = doSyncProject(context);
+    } catch (AssertionError | Exception e) {
+      LOG.error(e);
+      IssueOutput.error("Internal error: " + e.getMessage()).submit(context);
+    } finally {
+      afterSync(project, syncResult);
+    }
+    return syncResult == SyncResult.SUCCESS || syncResult == SyncResult.PARTIAL_SUCCESS;
+  }
+
+  /** @return true if sync successfully completed */
+  private SyncResult doSyncProject(final BlazeContext context) {
+    this.syncStartTime = System.currentTimeMillis();
+
+    if (importSettings.getProjectViewFile() == null) {
+      IssueOutput.error(
+              "This project looks like it's been opened from an old version of ASwB. "
+                  + "That is unfortunately not supported. Please reimport your project.")
+          .submit(context);
+      return SyncResult.FAILURE;
+    }
+
+    @Nullable BlazeProjectData oldBlazeProjectData = null;
+    if (syncParams.syncMode != SyncMode.FULL) {
+      oldBlazeProjectData =
+          BlazeProjectDataManagerImpl.getImpl(project).loadProjectRoot(context, importSettings);
+    }
+
+    BlazeVcsHandler vcsHandler = null;
+    for (BlazeVcsHandler candidate : BlazeVcsHandler.EP_NAME.getExtensions()) {
+      if (candidate.handlesProject(Blaze.getBuildSystem(project), workspaceRoot)) {
+        vcsHandler = candidate;
+        break;
+      }
+    }
+    if (vcsHandler == null) {
+      IssueOutput.error("Could not find a VCS handler").submit(context);
+      return SyncResult.FAILURE;
+    }
+
+    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")
+            .withProgressMessage(
+                String.format("Running %s info...", Blaze.buildSystemName(project)))
+            .onError(String.format("Could not get %s roots", Blaze.buildSystemName(project)))
+            .run()
+            .result();
+    if (blazeRoots == null) {
+      return SyncResult.FAILURE;
+    }
+
+    WorkspacePathResolverAndProjectView workspacePathResolverAndProjectView =
+        computeWorkspacePathResolverAndProjectView(context, blazeRoots, vcsHandler, executor);
+    if (workspacePathResolverAndProjectView == null) {
+      return SyncResult.FAILURE;
+    }
+    WorkspacePathResolver workspacePathResolver =
+        workspacePathResolverAndProjectView.workspacePathResolver;
+    ProjectViewSet projectViewSet = workspacePathResolverAndProjectView.projectViewSet;
+
+    WorkspaceLanguageSettings workspaceLanguageSettings =
+        LanguageSupport.createWorkspaceLanguageSettings(context, projectViewSet);
+    if (workspaceLanguageSettings == null) {
+      return SyncResult.FAILURE;
+    }
+
+    if (!ProjectViewVerifier.verifyProjectView(
+        context, workspaceRoot, projectViewSet, workspaceLanguageSettings)) {
+      return SyncResult.FAILURE;
+    }
+
+    final BlazeProjectData newBlazeProjectData;
+
+    WorkingSet workingSet =
+        FutureUtil.waitForFuture(context, workingSetFuture)
+            .timed("WorkingSet")
+            .withProgressMessage("Computing VCS working set...")
+            .onError("Could not compute working set")
+            .run()
+            .result();
+    if (context.isCancelled()) {
+      return SyncResult.CANCELLED;
+    }
+    if (context.hasErrors()) {
+      return SyncResult.FAILURE;
+    }
+
+    if (workingSet != null) {
+      printWorkingSet(context, workingSet);
+    }
+
+    BuildResult ideInfoResult = BuildResult.SUCCESS;
+    BuildResult ideResolveResult = BuildResult.SUCCESS;
+    if (syncParams.syncMode != SyncMode.RESTORE_EPHEMERAL_STATE || oldBlazeProjectData == null) {
+      SyncState.Builder syncStateBuilder = new SyncState.Builder();
+      SyncState previousSyncState =
+          oldBlazeProjectData != null ? oldBlazeProjectData.syncState : null;
+
+      boolean syncPluginRequiresBuild = false;
+      for (BlazeSyncPlugin syncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) {
+        syncPluginRequiresBuild |= syncPlugin.requiresResolveIdeArtifacts();
+      }
+
+      List<TargetExpression> targets = Lists.newArrayList();
+      if (syncParams.addProjectViewTargets || oldBlazeProjectData == null) {
+        targets.addAll(projectViewSet.listItems(TargetSection.KEY));
+      }
+      if (syncParams.addWorkingSet && workingSet != null) {
+        targets.addAll(getWorkingSetTargets(workingSet));
+      }
+      targets.addAll(syncParams.targetExpressions);
+
+      boolean mergeWithOldState = !syncParams.addProjectViewTargets;
+      BlazeIdeInterface.IdeResult ideQueryResult =
+          getIdeQueryResult(
+              project,
+              context,
+              projectViewSet,
+              targets,
+              workspaceLanguageSettings,
+              new ArtifactLocationDecoder(blazeRoots, workspacePathResolver),
+              syncStateBuilder,
+              previousSyncState,
+              mergeWithOldState);
+      if (context.isCancelled()) {
+        return SyncResult.CANCELLED;
+      }
+      if (ideQueryResult.ruleMap == null || ideQueryResult.buildResult == BuildResult.FATAL_ERROR) {
+        context.setHasError();
+        return SyncResult.FAILURE;
+      }
+
+      RuleMap ruleMap = ideQueryResult.ruleMap;
+      ideInfoResult = ideQueryResult.buildResult;
+
+      ListenableFuture<ImmutableMultimap<Label, Label>> reverseDependenciesFuture =
+          BlazeExecutor.getInstance().submit(() -> ReverseDependencyMap.createRdepsMap(ruleMap));
+
+      boolean doResolve = syncPluginRequiresBuild || oldBlazeProjectData == null;
+      if (doResolve) {
+        ideResolveResult =
+            resolveIdeArtifacts(project, context, workspaceRoot, projectViewSet, targets);
+        if (ideResolveResult == BuildResult.FATAL_ERROR) {
+          context.setHasError();
+          return SyncResult.FAILURE;
+        }
+      }
+      if (context.isCancelled()) {
+        return SyncResult.CANCELLED;
+      }
+
+      Scope.push(
+          context,
+          (childContext) -> {
+            childContext.push(new TimingScope("UpdateSyncState"));
+            for (BlazeSyncPlugin syncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) {
+              syncPlugin.updateSyncState(
+                  project,
+                  childContext,
+                  workspaceRoot,
+                  projectViewSet,
+                  workspaceLanguageSettings,
+                  blazeRoots,
+                  workingSet,
+                  workspacePathResolver,
+                  ruleMap,
+                  syncStateBuilder,
+                  previousSyncState);
+            }
+          });
+
+      ImmutableMultimap<Label, Label> reverseDependencies =
+          FutureUtil.waitForFuture(context, reverseDependenciesFuture)
+              .timed("ReverseDependencies")
+              .onError("Failed to compute reverse dependency map")
+              .run()
+              .result();
+      if (reverseDependencies == null) {
+        return SyncResult.FAILURE;
+      }
+
+      newBlazeProjectData =
+          new BlazeProjectData(
+              syncStartTime,
+              ruleMap,
+              blazeRoots,
+              workingSet,
+              workspacePathResolver,
+              workspaceLanguageSettings,
+              syncStateBuilder.build(),
+              reverseDependencies,
+              vcsHandler.getVcsName());
+    } else {
+      // Restore project based on old blaze project data
+      newBlazeProjectData = oldBlazeProjectData;
+    }
+
+    FileCaches.onSync(project, context, projectViewSet, newBlazeProjectData, syncParams.syncMode);
+    ListenableFuture<?> prefetch =
+        PrefetchService.getInstance().prefetchProjectFiles(project, newBlazeProjectData);
+    FutureUtil.waitForFuture(context, prefetch)
+        .withProgressMessage("Prefetching files...")
+        .timed("PrefetchFiles")
+        .onError("Prefetch failed")
+        .run();
+
+    boolean success =
+        updateProject(project, context, projectViewSet, oldBlazeProjectData, newBlazeProjectData);
+    if (!success) {
+      return SyncResult.FAILURE;
+    }
+
+    SyncResult syncResult = SyncResult.SUCCESS;
+
+    if (ideInfoResult == BuildResult.BUILD_ERROR || ideResolveResult == BuildResult.BUILD_ERROR) {
+      final String errorType =
+          ideInfoResult == BuildResult.BUILD_ERROR ? "BUILD file errors" : "compilation errors";
+
+      String message =
+          String.format(
+              "Sync was successful, but there were %s. "
+                  + "The project may not be fully updated or resolve until fixed. "
+                  + "If the errors are from your working set, please uncheck "
+                  + "'Blaze > Expand Sync to Working Set' and try again.",
+              errorType);
+      context.output(PrintOutput.error(message));
+      IssueOutput.warn(message).submit(context);
+      syncResult = SyncResult.PARTIAL_SUCCESS;
+    }
+
+    onSyncComplete(project, context, projectViewSet, newBlazeProjectData, syncResult);
+    return syncResult;
+  }
+
+  static class WorkspacePathResolverAndProjectView {
+    final WorkspacePathResolver workspacePathResolver;
+    final ProjectViewSet projectViewSet;
+
+    public WorkspacePathResolverAndProjectView(
+        WorkspacePathResolver workspacePathResolver, ProjectViewSet projectViewSet) {
+      this.workspacePathResolver = workspacePathResolver;
+      this.projectViewSet = projectViewSet;
+    }
+  }
+
+  private WorkspacePathResolverAndProjectView computeWorkspacePathResolverAndProjectView(
+      BlazeContext context,
+      BlazeRoots blazeRoots,
+      BlazeVcsHandler vcsHandler,
+      ListeningExecutorService executor) {
+    context.output(new StatusOutput("Updating VCS..."));
+
+    for (int i = 0; i < 3; ++i) {
+      WorkspacePathResolver vcsWorkspacePathResolver = null;
+      BlazeVcsHandler.BlazeVcsSyncHandler vcsSyncHandler =
+          vcsHandler.createSyncHandler(project, workspaceRoot);
+      if (vcsSyncHandler != null) {
+        boolean ok =
+            Scope.push(
+                context,
+                (childContext) -> {
+                  childContext.push(new TimingScope("UpdateVcs"));
+                  return vcsSyncHandler.update(context, blazeRoots, executor);
+                });
+        if (!ok) {
+          return null;
+        }
+        vcsWorkspacePathResolver = vcsSyncHandler.getWorkspacePathResolver();
+      }
+
+      WorkspacePathResolver workspacePathResolver =
+          vcsWorkspacePathResolver != null
+              ? vcsWorkspacePathResolver
+              : new WorkspacePathResolverImpl(workspaceRoot, blazeRoots);
+
+      ProjectViewSet projectViewSet =
+          ProjectViewManager.getInstance(project).reloadProjectView(context, workspacePathResolver);
+      if (projectViewSet == null) {
+        return null;
+      }
+
+      if (vcsSyncHandler != null) {
+        BlazeVcsHandler.BlazeVcsSyncHandler.ValidationResult validationResult =
+            vcsSyncHandler.validateProjectView(context, projectViewSet);
+        switch (validationResult) {
+          case OK:
+            // Fall-through and return
+            break;
+          case Error:
+            return null;
+          case RestartSync:
+            continue;
+          default:
+            // Cannot happen
+            return null;
+        }
+      }
+
+      return new WorkspacePathResolverAndProjectView(workspacePathResolver, projectViewSet);
+    }
+    return null;
+  }
+
+  private void printWorkingSet(BlazeContext context, WorkingSet workingSet) {
+    List<String> messages = Lists.newArrayList();
+    messages.addAll(
+        workingSet
+            .addedFiles
+            .stream()
+            .map(file -> file.relativePath() + " (added)")
+            .collect(Collectors.toList()));
+    messages.addAll(
+        workingSet
+            .modifiedFiles
+            .stream()
+            .map(file -> file.relativePath() + " (modified)")
+            .collect(Collectors.toList()));
+    Collections.sort(messages);
+
+    if (messages.isEmpty()) {
+      context.output(PrintOutput.log("Your working set is empty"));
+      return;
+    }
+    int maxFiles = 20;
+    for (String message : Iterables.limit(messages, maxFiles)) {
+      context.output(PrintOutput.log("  " + message));
+    }
+    if (messages.size() > maxFiles) {
+      context.output(PrintOutput.log(String.format("  (and %d more)", messages.size() - maxFiles)));
+    }
+  }
+
+  private Collection<? extends TargetExpression> getWorkingSetTargets(WorkingSet workingSet) {
+    List<TargetExpression> result = Lists.newArrayList();
+    for (WorkspacePath workspacePath :
+        Iterables.concat(workingSet.addedFiles, workingSet.modifiedFiles)) {
+      File buildFile = workspaceRoot.fileForPath(workspacePath);
+      if (buildFile.getName().equals("BUILD")) {
+        result.add(
+            TargetExpression.allFromPackageNonRecursive(
+                workspaceRoot.workspacePathFor(buildFile.getParentFile())));
+      }
+    }
+    return result;
+  }
+
+  private BlazeIdeInterface.IdeResult getIdeQueryResult(
+      Project project,
+      BlazeContext parentContext,
+      ProjectViewSet projectViewSet,
+      List<TargetExpression> targets,
+      WorkspaceLanguageSettings workspaceLanguageSettings,
+      ArtifactLocationDecoder artifactLocationDecoder,
+      SyncState.Builder syncStateBuilder,
+      @Nullable SyncState previousSyncState,
+      boolean mergeWithOldState) {
+
+    return Scope.push(
+        parentContext,
+        context -> {
+          context.push(new TimingScope("IdeQuery"));
+          context.output(new StatusOutput("Building IDE info files..."));
+          context.setPropagatesErrors(false);
+
+          BlazeIdeInterface blazeIdeInterface = BlazeIdeInterface.getInstance();
+          return blazeIdeInterface.updateRuleMap(
+              project,
+              context,
+              workspaceRoot,
+              projectViewSet,
+              targets,
+              workspaceLanguageSettings,
+              artifactLocationDecoder,
+              syncStateBuilder,
+              previousSyncState,
+              mergeWithOldState);
+        });
+  }
+
+  private static BuildResult resolveIdeArtifacts(
+      Project project,
+      BlazeContext parentContext,
+      WorkspaceRoot workspaceRoot,
+      ProjectViewSet projectViewSet,
+      List<TargetExpression> targetExpressions) {
+    return Scope.push(
+        parentContext,
+        context -> {
+          context
+              .push(new LoggedTimingScope(project, Action.BLAZE_BUILD_DURING_SYNC))
+              .push(new TimingScope(Blaze.buildSystemName(project) + "Build"));
+          context.output(new StatusOutput("Building IDE resolve files..."));
+
+          // We don't want IDE resolve errors to fail the whole sync
+          context.setPropagatesErrors(false);
+
+          if (targetExpressions.isEmpty()) {
+            return BuildResult.SUCCESS;
+          }
+          BlazeIdeInterface blazeIdeInterface = BlazeIdeInterface.getInstance();
+          return blazeIdeInterface.resolveIdeArtifacts(
+              project, context, workspaceRoot, projectViewSet, targetExpressions);
+        });
+  }
+
+  private boolean updateProject(
+      Project project,
+      BlazeContext parentContext,
+      ProjectViewSet projectViewSet,
+      @Nullable BlazeProjectData oldBlazeProjectData,
+      BlazeProjectData newBlazeProjectData) {
+    return Scope.push(
+        parentContext,
+        context -> {
+          context
+              .push(new LoggedTimingScope(project, Action.SYNC_IMPORT_DATA_TIME))
+              .push(new TimingScope("UpdateProjectStructure"));
+          context.output(new StatusOutput("Committing project structure..."));
+
+          try {
+            AsyncUtil.executeProjectChangeAction(
+                () ->
+                    ProjectRootManagerEx.getInstanceEx(this.project)
+                        .mergeRootsChangesDuring(
+                            () -> {
+                              updateSdk(context, projectViewSet, newBlazeProjectData);
+                              updateProjectStructure(
+                                  context,
+                                  importSettings,
+                                  projectViewSet,
+                                  newBlazeProjectData,
+                                  oldBlazeProjectData);
+                            }));
+          } catch (Throwable t) {
+            IssueOutput.error("Internal error. Error: " + t).submit(context);
+            LOG.error(t);
+            return false;
+          }
+
+          BlazeProjectDataManagerImpl.getImpl(this.project)
+              .saveProject(importSettings, newBlazeProjectData);
+          return true;
+        });
+  }
+
+  private void updateSdk(
+      BlazeContext context, ProjectViewSet projectViewSet, BlazeProjectData newBlazeProjectData) {
+    for (BlazeSyncPlugin syncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) {
+      syncPlugin.updateSdk(project, context, projectViewSet, newBlazeProjectData);
+    }
+  }
+
+  private void updateProjectStructure(
+      BlazeContext context,
+      BlazeImportSettings importSettings,
+      ProjectViewSet projectViewSet,
+      BlazeProjectData newBlazeProjectData,
+      @Nullable BlazeProjectData oldBlazeProjectData) {
+
+    ModuleEditorImpl moduleEditor =
+        ModuleEditorProvider.getInstance().getModuleEditor(project, importSettings);
+
+    ModuleType workspaceModuleType = null;
+    for (BlazeSyncPlugin syncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) {
+      workspaceModuleType =
+          syncPlugin.getWorkspaceModuleType(
+              newBlazeProjectData.workspaceLanguageSettings.getWorkspaceType());
+      if (workspaceModuleType != null) {
+        break;
+      }
+    }
+    if (workspaceModuleType == null) {
+      workspaceModuleType = ModuleType.EMPTY;
+      IssueOutput.warn("Could not set module type for workspace module.").submit(context);
+    }
+
+    Module workspaceModule = moduleEditor.createModule(".workspace", workspaceModuleType);
+    ModifiableRootModel workspaceModifiableModel = moduleEditor.editModule(workspaceModule);
+
+    ContentEntryEditor.createContentEntries(
+        project,
+        context,
+        workspaceRoot,
+        projectViewSet,
+        newBlazeProjectData,
+        workspaceModifiableModel);
+
+    for (BlazeSyncPlugin blazeSyncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) {
+      blazeSyncPlugin.updateProjectStructure(
+          project,
+          context,
+          workspaceRoot,
+          projectViewSet,
+          newBlazeProjectData,
+          oldBlazeProjectData,
+          moduleEditor,
+          workspaceModule,
+          workspaceModifiableModel);
+    }
+
+    createProjectDataDirectoryModule(
+        moduleEditor, new File(importSettings.getProjectDataDirectory()), workspaceModuleType);
+
+    moduleEditor.commitWithGc(context);
+  }
+
+  /**
+   * Creates a module that includes the user's data directory.
+   *
+   * <p>This is useful to be able to edit the project view without IntelliJ complaining it's outside
+   * the project.
+   */
+  private void createProjectDataDirectoryModule(
+      ModuleEditor moduleEditor, File projectDataDirectory, ModuleType moduleType) {
+    Module module = moduleEditor.createModule(".project-data-dir", moduleType);
+    ModifiableRootModel modifiableModel = moduleEditor.editModule(module);
+    ContentEntry rootContentEntry =
+        modifiableModel.addContentEntry(pathToUrl(projectDataDirectory));
+    rootContentEntry.addExcludeFolder(pathToUrl(new File(projectDataDirectory, ".idea")));
+    rootContentEntry.addExcludeFolder(
+        pathToUrl(BlazeDataStorage.getProjectDataDir(importSettings)));
+  }
+
+  private static String pathToUrl(File path) {
+    String filePath = FileUtil.toSystemIndependentName(path.getPath());
+    return VirtualFileManager.constructUrl(StandardFileSystems.FILE_PROTOCOL, filePath);
+  }
+
+  private static void onSyncStart(Project project) {
+    final SyncListener[] syncListeners = SyncListener.EP_NAME.getExtensions();
+    for (SyncListener syncListener : syncListeners) {
+      syncListener.onSyncStart(project);
+    }
+  }
+
+  private static void afterSync(Project project, SyncResult syncResult) {
+    final SyncListener[] syncListeners = SyncListener.EP_NAME.getExtensions();
+    for (SyncListener syncListener : syncListeners) {
+      syncListener.afterSync(project, syncResult);
+    }
+  }
+
+  private void onSyncComplete(
+      Project project,
+      BlazeContext context,
+      ProjectViewSet projectViewSet,
+      BlazeProjectData blazeProjectData,
+      SyncResult syncResult) {
+    validate(project, context, blazeProjectData);
+
+    final SyncListener[] syncListeners = SyncListener.EP_NAME.getExtensions();
+    for (SyncListener syncListener : syncListeners) {
+      syncListener.onSyncComplete(
+          project, importSettings, projectViewSet, blazeProjectData, syncResult);
+    }
+  }
+
+  private static void validate(
+      Project project, BlazeContext context, BlazeProjectData blazeProjectData) {
+    for (BlazeSyncPlugin syncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) {
+      syncPlugin.validate(project, context, blazeProjectData);
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/sync/SyncListener.java b/base/src/com/google/idea/blaze/base/sync/SyncListener.java
new file mode 100644
index 0000000..6445b14
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/SyncListener.java
@@ -0,0 +1,72 @@
+/*
+ * 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;
+
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.projectview.ProjectViewSet;
+import com.google.idea.blaze.base.settings.BlazeImportSettings;
+import com.intellij.openapi.extensions.ExtensionPointName;
+import com.intellij.openapi.project.Project;
+
+/** Extension interface for listening to syncs. */
+public interface SyncListener {
+  ExtensionPointName<SyncListener> EP_NAME =
+      ExtensionPointName.create("com.google.idea.blaze.SyncListener");
+
+  /** Result of the sync operation */
+  enum SyncResult {
+    /** Full success */
+    SUCCESS,
+    /** The user has errors in their BUILD files or compilation errors */
+    PARTIAL_SUCCESS,
+    /** The user cancelled */
+    CANCELLED,
+    /** Failure -- sync could not complete */
+    FAILURE,
+  }
+
+  /** Called after open documents have been saved, prior to starting the blaze sync. */
+  void onSyncStart(Project project);
+
+  /** Called on successful (or partially successful) completion of a sync */
+  void onSyncComplete(
+      Project project,
+      BlazeImportSettings importSettings,
+      ProjectViewSet projectViewSet,
+      BlazeProjectData blazeProjectData,
+      SyncResult syncResult);
+
+  /** Guaranteed to be called once per sync, regardless of whether it successfully completed */
+  void afterSync(Project project, SyncResult syncResult);
+
+  /** Convenience adapter class. */
+  abstract class Adapter implements SyncListener {
+
+    @Override
+    public void onSyncStart(Project project) {}
+
+    @Override
+    public void onSyncComplete(
+        Project project,
+        BlazeImportSettings importSettings,
+        ProjectViewSet projectViewSet,
+        BlazeProjectData blazeProjectData,
+        SyncResult syncResult) {}
+
+    @Override
+    public void afterSync(Project project, SyncResult syncResult) {}
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/sync/actions/ExpandSyncToWorkingSetAction.java b/base/src/com/google/idea/blaze/base/sync/actions/ExpandSyncToWorkingSetAction.java
new file mode 100644
index 0000000..0509cca
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/actions/ExpandSyncToWorkingSetAction.java
@@ -0,0 +1,33 @@
+/*
+ * 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.actions;
+
+import com.google.idea.blaze.base.actions.BlazeToggleAction;
+import com.google.idea.blaze.base.settings.BlazeUserSettings;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+
+/** Manages a tick box of whether to expand the sync targets to the working set. */
+public class ExpandSyncToWorkingSetAction extends BlazeToggleAction {
+  @Override
+  public boolean isSelected(AnActionEvent e) {
+    return BlazeUserSettings.getInstance().getExpandSyncToWorkingSet();
+  }
+
+  @Override
+  public void setSelected(AnActionEvent e, boolean state) {
+    BlazeUserSettings.getInstance().setExpandSyncToWorkingSet(state);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/sync/actions/FullSyncProjectAction.java b/base/src/com/google/idea/blaze/base/sync/actions/FullSyncProjectAction.java
new file mode 100644
index 0000000..2c1f625
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/actions/FullSyncProjectAction.java
@@ -0,0 +1,52 @@
+/*
+ * 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.actions;
+
+import com.google.idea.blaze.base.actions.BlazeAction;
+import com.google.idea.blaze.base.settings.BlazeUserSettings;
+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.actionSystem.Presentation;
+import com.intellij.openapi.project.Project;
+
+/** Re-imports (syncs) an Android-Blaze project, without showing the "Import Project" wizard. */
+public class FullSyncProjectAction extends BlazeAction {
+
+  public FullSyncProjectAction() {
+    super("Non-Incrementally Sync Project with BUILD Files");
+  }
+
+  @Override
+  public void actionPerformed(final AnActionEvent e) {
+    Project project = e.getProject();
+    if (project != null) {
+      Presentation presentation = e.getPresentation();
+      presentation.setEnabled(false);
+      try {
+        BlazeSyncParams syncParams =
+            new BlazeSyncParams.Builder("Full Sync", SyncMode.FULL)
+                .addProjectViewTargets(true)
+                .addWorkingSet(BlazeUserSettings.getInstance().getExpandSyncToWorkingSet())
+                .build();
+        BlazeSyncManager.getInstance(project).requestProjectSync(syncParams);
+      } finally {
+        presentation.setEnabled(true);
+      }
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/sync/actions/IncrementalSyncProjectAction.java b/base/src/com/google/idea/blaze/base/sync/actions/IncrementalSyncProjectAction.java
new file mode 100644
index 0000000..134acfb
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/actions/IncrementalSyncProjectAction.java
@@ -0,0 +1,114 @@
+/*
+ * 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.actions;
+
+import com.google.idea.blaze.base.actions.BlazeAction;
+import com.google.idea.blaze.base.settings.Blaze;
+import com.google.idea.blaze.base.settings.BlazeUserSettings;
+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.google.idea.blaze.base.sync.status.BlazeSyncStatus;
+import com.google.idea.blaze.base.sync.status.BlazeSyncStatusImpl;
+import com.intellij.notification.Notification;
+import com.intellij.notification.NotificationDisplayType;
+import com.intellij.notification.NotificationGroup;
+import com.intellij.notification.NotificationType;
+import com.intellij.notification.Notifications;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.Presentation;
+import com.intellij.openapi.project.Project;
+import icons.BlazeIcons;
+import javax.swing.Icon;
+
+/** Re-imports (syncs) an Android-Blaze project, without showing the "Import Project" wizard. */
+public class IncrementalSyncProjectAction extends BlazeAction {
+
+  public IncrementalSyncProjectAction() {
+    super("Sync Project with BUILD Files");
+  }
+
+  @Override
+  public void actionPerformed(final AnActionEvent e) {
+    Project project = e.getProject();
+    if (project != null) {
+      BlazeSyncManager.getInstance(project)
+          .requestProjectSync(
+              new BlazeSyncParams.Builder("Sync", SyncMode.INCREMENTAL)
+                  .addProjectViewTargets(true)
+                  .addWorkingSet(BlazeUserSettings.getInstance().getExpandSyncToWorkingSet())
+                  .build());
+      updateIcon(e);
+    }
+  }
+
+  @Override
+  protected void doUpdate(AnActionEvent e) {
+    super.doUpdate(e);
+    updateIcon(e);
+  }
+
+  private static void updateIcon(AnActionEvent e) {
+    Project project = e.getProject();
+    Presentation presentation = e.getPresentation();
+    if (project == null) {
+      presentation.setIcon(BlazeIcons.Blaze);
+      presentation.setEnabled(true);
+      return;
+    }
+    BlazeSyncStatusImpl statusHelper = BlazeSyncStatusImpl.getImpl(project);
+    BlazeSyncStatus.SyncStatus status = statusHelper.getStatus();
+    presentation.setIcon(getIcon(status));
+    presentation.setEnabled(!statusHelper.syncInProgress.get());
+
+    if (status == BlazeSyncStatus.SyncStatus.DIRTY
+        && !BlazeUserSettings.getInstance().getSyncStatusPopupShown()) {
+      BlazeUserSettings.getInstance().setSyncStatusPopupShown(true);
+      showPopupNotification(project);
+    }
+  }
+
+  private static Icon getIcon(BlazeSyncStatus.SyncStatus status) {
+    switch (status) {
+      case FAILED:
+        return BlazeIcons.BlazeFailed;
+      case DIRTY:
+        return BlazeIcons.BlazeDirty;
+      case CLEAN:
+        return BlazeIcons.BlazeClean;
+      default:
+        return BlazeIcons.Blaze;
+    }
+  }
+
+  private static final NotificationGroup NOTIFICATION_GROUP =
+      new NotificationGroup("Changes since last blaze sync", NotificationDisplayType.BALLOON, true);
+
+  private static void showPopupNotification(Project project) {
+    String msg =
+        "Some relevant files (e.g. BUILD files, .blazeproject file) have changed "
+            + "since the last sync. Please press the 'Sync' button in the toolbar to "
+            + "re-sync your IntelliJ project.";
+    Notification notification =
+        new Notification(
+            NOTIFICATION_GROUP.getDisplayId(),
+            String.format("Changes since last %s sync", Blaze.buildSystemName(project)),
+            msg,
+            NotificationType.INFORMATION);
+    notification.setImportant(true);
+    Notifications.Bus.notify(notification, 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
new file mode 100644
index 0000000..2d6512b
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/actions/PartialSyncAction.java
@@ -0,0 +1,113 @@
+/*
+ * 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.actions;
+
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.actions.BlazeAction;
+import com.google.idea.blaze.base.model.primitives.TargetExpression;
+import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
+import com.google.idea.blaze.base.rulemaps.SourceToRuleMap;
+import com.google.idea.blaze.base.settings.Blaze;
+import com.google.idea.blaze.base.sync.BlazeSyncManager;
+import com.google.idea.blaze.base.sync.BlazeSyncParams;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.CommonDataKeys;
+import com.intellij.openapi.actionSystem.Presentation;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import java.io.File;
+import java.util.List;
+import javax.annotation.Nullable;
+
+/** Allows a partial sync of the project depending on what's been selected. */
+public class PartialSyncAction extends BlazeAction {
+  @Override
+  public void actionPerformed(AnActionEvent e) {
+    Project project = e.getProject();
+    if (project != null) {
+      List<TargetExpression> targetExpressions = Lists.newArrayList();
+      getTargets(e, targetExpressions);
+
+      BlazeSyncParams syncParams =
+          new BlazeSyncParams.Builder("Partial Sync", BlazeSyncParams.SyncMode.INCREMENTAL)
+              .addTargetExpressions(targetExpressions)
+              .build();
+
+      BlazeSyncManager.getInstance(project).requestProjectSync(syncParams);
+    }
+  }
+
+  @Override
+  protected void doUpdate(AnActionEvent e) {
+    super.doUpdate(e);
+    List<TargetExpression> targets = Lists.newArrayList();
+    String objectName = getTargets(e, targets);
+
+    boolean enabled = objectName != null && !targets.isEmpty();
+    Presentation presentation = e.getPresentation();
+    presentation.setEnabled(enabled);
+
+    if (enabled) {
+      presentation.setText(
+          String.format("Partially Sync %s with %s", objectName, buildSystemName(e.getProject())));
+    } else {
+      presentation.setText(String.format("Partial %s Sync", buildSystemName(e.getProject())));
+    }
+  }
+
+  private static String buildSystemName(@Nullable Project project) {
+    return Blaze.buildSystemName(project);
+  }
+
+  @Nullable
+  private String getTargets(AnActionEvent e, List<TargetExpression> targets) {
+    Project project = e.getProject();
+    VirtualFile virtualFile = e.getData(CommonDataKeys.VIRTUAL_FILE);
+    if (project == null || virtualFile == null || !virtualFile.isInLocalFileSystem()) {
+      return null;
+    }
+
+    String objectName = null;
+    WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProject(project);
+
+    if (virtualFile.isDirectory()) {
+      if (workspaceRoot.isInWorkspace(virtualFile)) {
+        targets.add(
+            TargetExpression.allFromPackageRecursive(workspaceRoot.workspacePathFor(virtualFile)));
+      }
+      objectName = "Package";
+    } else {
+      targets.addAll(
+          SourceToRuleMap.getInstance(project)
+              .getTargetsForSourceFile(new File(virtualFile.getPath())));
+
+      // If empty, try to build parent package
+      if (targets.isEmpty()) {
+        VirtualFile parent = virtualFile.getParent();
+        if (parent.isDirectory()) {
+          if (workspaceRoot.isInWorkspace(parent)) {
+            targets.add(
+                TargetExpression.allFromPackageNonRecursive(
+                    workspaceRoot.workspacePathFor(parent)));
+          }
+        }
+      }
+      objectName = "File";
+    }
+
+    return objectName;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/sync/actions/ShowPerformanceWarningsToggleAction.java b/base/src/com/google/idea/blaze/base/sync/actions/ShowPerformanceWarningsToggleAction.java
new file mode 100644
index 0000000..d7c6e96
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/actions/ShowPerformanceWarningsToggleAction.java
@@ -0,0 +1,33 @@
+/*
+ * 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.actions;
+
+import com.google.idea.blaze.base.actions.BlazeToggleAction;
+import com.google.idea.blaze.base.settings.BlazeUserSettings;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+
+/** Manages a tick box of whether to show performance warnings. */
+public class ShowPerformanceWarningsToggleAction extends BlazeToggleAction {
+  @Override
+  public boolean isSelected(AnActionEvent e) {
+    return BlazeUserSettings.getInstance().getShowPerformanceWarnings();
+  }
+
+  @Override
+  public void setSelected(AnActionEvent e, boolean state) {
+    BlazeUserSettings.getInstance().setShowPerformanceWarnings(state);
+  }
+}
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
new file mode 100644
index 0000000..530f819
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/actions/SyncWorkingSetAction.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.base.sync.actions;
+
+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.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.project.Project;
+
+/** Allows a partial sync of the project depending on what's been selected. */
+public class SyncWorkingSetAction extends BlazeAction {
+  @Override
+  public void actionPerformed(AnActionEvent e) {
+    Project project = e.getProject();
+    if (project != null) {
+      BlazeSyncParams syncParams =
+          new BlazeSyncParams.Builder("Sync Working Set", BlazeSyncParams.SyncMode.INCREMENTAL)
+              .addWorkingSet(true)
+              .build();
+
+      BlazeSyncManager.getInstance(project).requestProjectSync(syncParams);
+    }
+  }
+}
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
new file mode 100644
index 0000000..7755abd
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/aspects/AspectStrategy.java
@@ -0,0 +1,115 @@
+/*
+ * 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
new file mode 100644
index 0000000..d46ac4a
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/aspects/BlazeIdeInterface.java
@@ -0,0 +1,94 @@
+/*
+ * 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.model.RuleMap;
+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;
+import com.google.idea.blaze.base.projectview.ProjectViewSet;
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
+import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.project.Project;
+import java.util.List;
+import javax.annotation.Nullable;
+
+/** Indirection between ide_build_info and aspect style IDE info. */
+public interface BlazeIdeInterface {
+
+  static BlazeIdeInterface getInstance() {
+    return ServiceManager.getService(BlazeIdeInterface.class);
+  }
+
+  /** The result of a blaze operation */
+  enum BuildResult {
+    SUCCESS, // Success
+    BUILD_ERROR, // Return code 1, a build error
+    FATAL_ERROR; // Some other failure
+
+    public static BuildResult fromExitCode(int exitCode) {
+      if (exitCode == 0) {
+        return SUCCESS;
+      } else if (exitCode == 1) {
+        return BUILD_ERROR;
+      }
+      return FATAL_ERROR;
+    }
+  }
+
+  /** The result of the ide operation */
+  class IdeResult {
+    @Nullable public final RuleMap ruleMap;
+    public final BuildResult buildResult;
+
+    public IdeResult(@Nullable RuleMap ruleMap, BuildResult buildResult) {
+      this.ruleMap = ruleMap;
+      this.buildResult = buildResult;
+    }
+  }
+
+  /**
+   * Queries blaze to update the rule map for the given targets.
+   *
+   * @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(
+      Project project,
+      BlazeContext context,
+      WorkspaceRoot workspaceRoot,
+      ProjectViewSet projectViewSet,
+      List<TargetExpression> targets,
+      WorkspaceLanguageSettings workspaceLanguageSettings,
+      ArtifactLocationDecoder artifactLocationDecoder,
+      SyncState.Builder syncStateBuilder,
+      @Nullable SyncState previousSyncState,
+      boolean mergeWithOldState);
+
+  /**
+   * Attempts to resolve the requested ide artifacts.
+   *
+   * <p>Amounts to a build of the ide-resolve output group.
+   */
+  BuildResult resolveIdeArtifacts(
+      Project project,
+      BlazeContext context,
+      WorkspaceRoot workspaceRoot,
+      ProjectViewSet projectViewSet,
+      List<TargetExpression> targets);
+}
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
new file mode 100644
index 0000000..1b4be0e
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/aspects/BlazeIdeInterfaceAspectsImpl.java
@@ -0,0 +1,409 @@
+/*
+ * 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.common.base.Objects;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.idea.blaze.base.async.FutureUtil;
+import com.google.idea.blaze.base.async.executor.BlazeExecutor;
+import com.google.idea.blaze.base.async.process.ExternalTask;
+import com.google.idea.blaze.base.async.process.LineProcessingOutputStream;
+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.command.ExperimentalShowArtifactsLineProcessor;
+import com.google.idea.blaze.base.filecache.FileDiffer;
+import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+import com.google.idea.blaze.base.issueparser.IssueOutputLineProcessor;
+import com.google.idea.blaze.base.metrics.Action;
+import com.google.idea.blaze.base.model.RuleMap;
+import com.google.idea.blaze.base.model.SyncState;
+import com.google.idea.blaze.base.model.primitives.Label;
+import com.google.idea.blaze.base.model.primitives.TargetExpression;
+import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
+import com.google.idea.blaze.base.prefetch.PrefetchService;
+import com.google.idea.blaze.base.projectview.ProjectViewSet;
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.scope.Result;
+import com.google.idea.blaze.base.scope.Scope;
+import com.google.idea.blaze.base.scope.ScopedFunction;
+import com.google.idea.blaze.base.scope.output.PerformanceWarning;
+import com.google.idea.blaze.base.scope.output.PrintOutput;
+import com.google.idea.blaze.base.scope.scopes.LoggedTimingScope;
+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.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.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import java.io.File;
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicLong;
+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 = 13L;
+    RuleMap ruleMap;
+    ImmutableMap<File, Long> fileState = null;
+    Map<File, Label> fileToLabel = Maps.newHashMap();
+    WorkspaceLanguageSettings workspaceLanguageSettings;
+    String aspectStrategyName;
+  }
+
+  @Override
+  public IdeResult updateRuleMap(
+      Project project,
+      BlazeContext context,
+      WorkspaceRoot workspaceRoot,
+      ProjectViewSet projectViewSet,
+      List<TargetExpression> targets,
+      WorkspaceLanguageSettings workspaceLanguageSettings,
+      ArtifactLocationDecoder artifactLocationDecoder,
+      SyncState.Builder syncStateBuilder,
+      @Nullable SyncState previousSyncState,
+      boolean mergeWithOldState) {
+    State prevState = previousSyncState != null ? previousSyncState.get(State.class) : null;
+
+    // If the language filter has changed, redo everything from scratch
+    if (prevState != null
+        && !prevState.workspaceLanguageSettings.equals(workspaceLanguageSettings)) {
+      prevState = null;
+    }
+
+    // If the aspect strategy has changed, redo everything from scratch
+    final AspectStrategy aspectStrategy = getAspectStrategy(project);
+    if (prevState != null
+        && !Objects.equal(prevState.aspectStrategyName, aspectStrategy.getName())) {
+      prevState = null;
+    }
+
+    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);
+    }
+    // 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.
+    if (ideInfoResult.buildResult == BuildResult.BUILD_ERROR) {
+      mergeWithOldState = true;
+    }
+
+    List<File> fileList = ideInfoResult.files;
+    List<File> updatedFiles = Lists.newArrayList();
+    List<File> removedFiles = Lists.newArrayList();
+    ImmutableMap<File, Long> fileState =
+        FileDiffer.updateFiles(
+            prevState != null ? prevState.fileState : null, fileList, updatedFiles, removedFiles);
+    if (fileState == null) {
+      return new IdeResult(prevState != null ? prevState.ruleMap : null, BuildResult.FATAL_ERROR);
+    }
+
+    context.output(
+        PrintOutput.log(
+            String.format(
+                "Total rules: %d, new/changed: %d, removed: %d",
+                fileList.size(), updatedFiles.size(), removedFiles.size())));
+
+    ListenableFuture<?> prefetchFuture =
+        PrefetchService.getInstance().prefetchFiles(project, updatedFiles);
+    if (!FutureUtil.waitForFuture(context, prefetchFuture)
+        .timed("FetchAspectOutput")
+        .withProgressMessage("Reading IDE info result...")
+        .run()
+        .success()) {
+      return new IdeResult(prevState != null ? prevState.ruleMap : null, BuildResult.FATAL_ERROR);
+    }
+
+    State state =
+        updateState(
+            context,
+            prevState,
+            fileState,
+            workspaceLanguageSettings,
+            artifactLocationDecoder,
+            aspectStrategy,
+            updatedFiles,
+            removedFiles,
+            mergeWithOldState);
+    if (state == null) {
+      return new IdeResult(prevState != null ? prevState.ruleMap : null, BuildResult.FATAL_ERROR);
+    }
+    syncStateBuilder.put(State.class, state);
+    return new IdeResult(state.ruleMap, ideInfoResult.buildResult);
+  }
+
+  private static class IdeInfoResult {
+    private final List<File> files;
+    private final BuildResult buildResult;
+
+    IdeInfoResult(List<File> files, BuildResult buildResult) {
+      this.files = files;
+      this.buildResult = buildResult;
+    }
+  }
+
+  private static IdeInfoResult getIdeInfo(
+      Project project,
+      BlazeContext parentContext,
+      WorkspaceRoot workspaceRoot,
+      ProjectViewSet projectViewSet,
+      List<TargetExpression> targets,
+      AspectStrategy aspectStrategy) {
+    return Scope.push(
+        parentContext,
+        context -> {
+          context.push(
+              new TimingScope(String.format("Execute%sCommand", Blaze.buildSystemName(project))));
+
+          List<File> result = Lists.newArrayList();
+
+          BuildSystem buildSystem = Blaze.getBuildSystem(project);
+          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.EXPERIMENTAL_SHOW_ARTIFACTS)
+              .addBlazeFlags(BlazeFlags.buildFlags(project, projectViewSet));
+
+          aspectStrategy.modifyIdeInfoCommand(blazeCommandBuilder);
+
+          int retVal =
+              ExternalTask.builder(workspaceRoot)
+                  .addBlazeCommand(blazeCommandBuilder.build())
+                  .context(context)
+                  .stderr(
+                      LineProcessingOutputStream.of(
+                          new ExperimentalShowArtifactsLineProcessor(
+                              result, aspectStrategy.getAspectOutputFileExtension()),
+                          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 final File file;
+    private final RuleIdeInfo ruleIdeInfo;
+
+    RuleIdeInfoPair(File file, RuleIdeInfo ruleIdeInfo) {
+      this.file = file;
+      this.ruleIdeInfo = ruleIdeInfo;
+    }
+  }
+
+  @Nullable
+  static State updateState(
+      BlazeContext parentContext,
+      @Nullable State prevState,
+      ImmutableMap<File, Long> fileState,
+      WorkspaceLanguageSettings workspaceLanguageSettings,
+      ArtifactLocationDecoder artifactLocationDecoder,
+      AspectStrategy aspectStrategy,
+      List<File> newFiles,
+      List<File> removedFiles,
+      boolean mergeWithOldState) {
+    Result<State> result =
+        Scope.push(
+            parentContext,
+            (ScopedFunction<Result<State>>)
+                context -> {
+                  context.push(new TimingScope("UpdateRuleMap"));
+
+                  // If we're not removing we have to merge the old state
+                  // into the new one or we'll miss file removes next time
+                  ImmutableMap<File, Long> nextFileState = fileState;
+                  if (mergeWithOldState && prevState != null) {
+                    ImmutableMap.Builder<File, Long> fileStateBuilder =
+                        ImmutableMap.<File, Long>builder().putAll(fileState);
+                    for (Map.Entry<File, Long> entry : prevState.fileState.entrySet()) {
+                      if (!fileState.containsKey(entry.getKey())) {
+                        fileStateBuilder.put(entry);
+                      }
+                    }
+                    nextFileState = fileStateBuilder.build();
+                  }
+
+                  State state = new State();
+                  state.fileState = nextFileState;
+                  state.workspaceLanguageSettings = workspaceLanguageSettings;
+                  state.aspectStrategyName = aspectStrategy.getName();
+
+                  Map<Label, RuleIdeInfo> ruleMap = Maps.newHashMap();
+                  Map<Label, RuleIdeInfo> updatedRules = Maps.newHashMap();
+                  if (prevState != null) {
+                    ruleMap.putAll(prevState.ruleMap.map());
+                    state.fileToLabel.putAll(prevState.fileToLabel);
+                  }
+
+                  // Update removed unless we're merging with the old state
+                  if (!mergeWithOldState) {
+                    for (File removedFile : removedFiles) {
+                      Label label = state.fileToLabel.remove(removedFile);
+                      if (label != null) {
+                        ruleMap.remove(label);
+                      }
+                    }
+                  }
+
+                  AtomicLong totalSizeLoaded = new AtomicLong(0);
+
+                  ListeningExecutorService executor = BlazeExecutor.getInstance().getExecutor();
+
+                  // Read protos from any new files
+                  List<ListenableFuture<RuleIdeInfoPair>> 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,
+                                      artifactLocationDecoder,
+                                      ruleProto);
+                              return new RuleIdeInfoPair(file, ruleIdeInfo);
+                            }));
+                  }
+
+                  // Update state with result from proto files
+                  int duplicateRuleLabels = 0;
+                  try {
+                    for (RuleIdeInfoPair ruleIdeInfoOrSdkInfo : Futures.allAsList(futures).get()) {
+                      if (ruleIdeInfoOrSdkInfo.ruleIdeInfo != null) {
+                        File file = ruleIdeInfoOrSdkInfo.file;
+                        Label label = ruleIdeInfoOrSdkInfo.ruleIdeInfo.label;
+                        RuleIdeInfo previousRule =
+                            updatedRules.putIfAbsent(label, ruleIdeInfoOrSdkInfo.ruleIdeInfo);
+                        if (previousRule == null) {
+                          state.fileToLabel.put(file, label);
+                        } else {
+                          duplicateRuleLabels++;
+                        }
+                      }
+                    }
+                  } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                    return Result.error(null);
+                  } catch (ExecutionException e) {
+                    return Result.error(e);
+                  }
+                  ruleMap.putAll(updatedRules);
+
+                  context.output(
+                      PrintOutput.log(
+                          String.format(
+                              "Loaded %d aspect files, total size %dkB",
+                              newFiles.size(), totalSizeLoaded.get() / 1024)));
+                  if (duplicateRuleLabels > 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()))));
+                  }
+
+                  state.ruleMap = new RuleMap(ImmutableMap.copyOf(ruleMap));
+                  return Result.of(state);
+                });
+
+    if (result.error != null) {
+      LOG.error(result.error);
+      return null;
+    }
+    return result.result;
+  }
+
+  @Override
+  public BuildResult resolveIdeArtifacts(
+      Project project,
+      BlazeContext context,
+      WorkspaceRoot workspaceRoot,
+      ProjectViewSet projectViewSet,
+      List<TargetExpression> targets) {
+    AspectStrategy aspectStrategy = getAspectStrategy(project);
+
+    BlazeCommand.Builder blazeCommandBuilder =
+        BlazeCommand.builder(Blaze.getBuildSystem(project), BlazeCommandName.BUILD)
+            .addTargets(targets)
+            .addBlazeFlags()
+            .addBlazeFlags(BlazeFlags.KEEP_GOING)
+            .addBlazeFlags(BlazeFlags.buildFlags(project, projectViewSet));
+
+    aspectStrategy.modifyIdeResolveCommand(blazeCommandBuilder);
+
+    BlazeCommand blazeCommand = blazeCommandBuilder.build();
+
+    int retVal =
+        ExternalTask.builder(workspaceRoot)
+            .addBlazeCommand(blazeCommand)
+            .context(context)
+            .stderr(
+                LineProcessingOutputStream.of(
+                    new IssueOutputLineProcessor(project, context, workspaceRoot)))
+            .build()
+            .run(new LoggedTimingScope(project, Action.BLAZE_BUILD));
+
+    return BuildResult.fromExitCode(retVal);
+  }
+
+  private AspectStrategy getAspectStrategy(Project project) {
+    BuildSystem buildSystem = Blaze.getBuildSystem(project);
+    if (buildSystem == BuildSystem.Bazel) {
+      return AspectStrategy.NATIVE_ASPECT;
+    }
+    return USE_SKYLARK_ASPECT.getValue()
+        ? AspectStrategy.SKYLARK_ASPECT
+        : AspectStrategy.NATIVE_ASPECT;
+  }
+}
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
new file mode 100644
index 0000000..4f1e622
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/aspects/IdeInfoFromProtobuf.java
@@ -0,0 +1,351 @@
+/*
+ * 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.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.ArtifactLocation;
+import com.google.idea.blaze.base.ideinfo.CRuleIdeInfo;
+import com.google.idea.blaze.base.ideinfo.CToolchainIdeInfo;
+import com.google.idea.blaze.base.ideinfo.JavaRuleIdeInfo;
+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.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.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
+import com.google.repackaged.devtools.build.lib.ideinfo.androidstudio.AndroidStudioIdeInfo;
+import com.google.repackaged.protobuf.ProtocolStringList;
+import java.util.Collection;
+import java.util.List;
+import javax.annotation.Nullable;
+
+/** Conversion functions from new aspect-style Bazel IDE info to ASWB internal classes. */
+public class IdeInfoFromProtobuf {
+
+  @Nullable
+  public static RuleIdeInfo makeRuleIdeInfo(
+      WorkspaceLanguageSettings workspaceLanguageSettings,
+      ArtifactLocationDecoder decoder,
+      AndroidStudioIdeInfo.RuleIdeInfo message) {
+    Kind kind = getKind(message);
+    if (kind == null) {
+      return null;
+    }
+    if (!workspaceLanguageSettings.isLanguageActive(kind.getLanguageClass())) {
+      return null;
+    }
+
+    Label label = new Label(message.getLabel());
+    ArtifactLocation buildFile = getBuildFile(decoder, message);
+
+    Collection<Label> dependencies = makeLabelListFromProtobuf(message.getDependenciesList());
+    Collection<Label> runtimeDeps = makeLabelListFromProtobuf(message.getRuntimeDepsList());
+    Collection<String> tags = ImmutableList.copyOf(message.getTagsList());
+
+    Collection<ArtifactLocation> sources = Lists.newArrayList();
+    CRuleIdeInfo cRuleIdeInfo = null;
+    if (message.hasCRuleIdeInfo()) {
+      cRuleIdeInfo = makeCRuleIdeInfo(decoder, message.getCRuleIdeInfo());
+      sources.addAll(cRuleIdeInfo.sources);
+    }
+    CToolchainIdeInfo cToolchainIdeInfo = null;
+    if (message.hasCToolchainIdeInfo()) {
+      cToolchainIdeInfo = makeCToolchainIdeInfo(message.getCToolchainIdeInfo());
+    }
+    JavaRuleIdeInfo javaRuleIdeInfo = null;
+    if (message.hasJavaRuleIdeInfo()) {
+      javaRuleIdeInfo = makeJavaRuleIdeInfo(decoder, message.getJavaRuleIdeInfo());
+      Collection<ArtifactLocation> javaSources =
+          makeArtifactLocationList(decoder, message.getJavaRuleIdeInfo().getSourcesList());
+      sources.addAll(javaSources);
+    }
+    AndroidRuleIdeInfo androidRuleIdeInfo = null;
+    if (message.hasAndroidRuleIdeInfo()) {
+      androidRuleIdeInfo = makeAndroidRuleIdeInfo(decoder, message.getAndroidRuleIdeInfo());
+    }
+    TestIdeInfo testIdeInfo = null;
+    if (message.hasTestInfo()) {
+      testIdeInfo = makeTestIdeInfo(message.getTestInfo());
+    }
+    ProtoLibraryLegacyInfo protoLibraryLegacyInfo = null;
+    if (message.hasProtoLibraryLegacyJavaIdeInfo()) {
+      protoLibraryLegacyInfo =
+          makeProtoLibraryLegacyInfo(decoder, message.getProtoLibraryLegacyJavaIdeInfo());
+    }
+    JavaToolchainIdeInfo javaToolchainIdeInfo = null;
+    if (message.hasJavaToolchainIdeInfo()) {
+      javaToolchainIdeInfo = makeJavaToolchainIdeInfo(message.getJavaToolchainIdeInfo());
+    }
+
+    return new RuleIdeInfo(
+        label,
+        kind,
+        buildFile,
+        dependencies,
+        runtimeDeps,
+        tags,
+        sources,
+        cRuleIdeInfo,
+        cToolchainIdeInfo,
+        javaRuleIdeInfo,
+        androidRuleIdeInfo,
+        testIdeInfo,
+        protoLibraryLegacyInfo,
+        javaToolchainIdeInfo);
+  }
+
+  @Nullable
+  private static ArtifactLocation getBuildFile(
+      ArtifactLocationDecoder decoder, AndroidStudioIdeInfo.RuleIdeInfo message) {
+    if (message.hasBuildFileArtifactLocation()) {
+      return makeArtifactLocation(decoder, message.getBuildFileArtifactLocation());
+    }
+    return null;
+  }
+
+  private static CRuleIdeInfo makeCRuleIdeInfo(
+      ArtifactLocationDecoder decoder, AndroidStudioIdeInfo.CRuleIdeInfo cRuleIdeInfo) {
+    List<ArtifactLocation> sources =
+        makeArtifactLocationList(decoder, cRuleIdeInfo.getSourceList());
+    List<ExecutionRootPath> transitiveIncludeDirectories =
+        makeExecutionRootPathList(cRuleIdeInfo.getTransitiveIncludeDirectoryList());
+    List<ExecutionRootPath> transitiveQuoteIncludeDirectories =
+        makeExecutionRootPathList(cRuleIdeInfo.getTransitiveQuoteIncludeDirectoryList());
+    List<ExecutionRootPath> transitiveSystemIncludeDirectories =
+        makeExecutionRootPathList(cRuleIdeInfo.getTransitiveSystemIncludeDirectoryList());
+
+    CRuleIdeInfo.Builder builder =
+        CRuleIdeInfo.builder()
+            .addSources(sources)
+            .addTransitiveIncludeDirectories(transitiveIncludeDirectories)
+            .addTransitiveQuoteIncludeDirectories(transitiveQuoteIncludeDirectories)
+            .addTransitiveDefines(cRuleIdeInfo.getTransitiveDefineList())
+            .addTransitiveSystemIncludeDirectories(transitiveSystemIncludeDirectories);
+
+    return builder.build();
+  }
+
+  private static List<ExecutionRootPath> makeExecutionRootPathList(Iterable<String> relativePaths) {
+    List<ExecutionRootPath> workspacePaths = Lists.newArrayList();
+    for (String relativePath : relativePaths) {
+      workspacePaths.add(new ExecutionRootPath(relativePath));
+    }
+    return workspacePaths;
+  }
+
+  private static CToolchainIdeInfo makeCToolchainIdeInfo(
+      AndroidStudioIdeInfo.CToolchainIdeInfo cToolchainIdeInfo) {
+    Collection<ExecutionRootPath> builtInIncludeDirectories =
+        makeExecutionRootPathList(cToolchainIdeInfo.getBuiltInIncludeDirectoryList());
+    ExecutionRootPath cppExecutable = new ExecutionRootPath(cToolchainIdeInfo.getCppExecutable());
+    ExecutionRootPath preprocessorExecutable =
+        new ExecutionRootPath(cToolchainIdeInfo.getPreprocessorExecutable());
+
+    UnfilteredCompilerOptions unfilteredCompilerOptions =
+        new UnfilteredCompilerOptions(cToolchainIdeInfo.getUnfilteredCompilerOptionList());
+
+    CToolchainIdeInfo.Builder builder =
+        CToolchainIdeInfo.builder()
+            .addBaseCompilerOptions(cToolchainIdeInfo.getBaseCompilerOptionList())
+            .addCCompilerOptions(cToolchainIdeInfo.getCOptionList())
+            .addCppCompilerOptions(cToolchainIdeInfo.getCppOptionList())
+            .addLinkOptions(cToolchainIdeInfo.getLinkOptionList())
+            .addBuiltInIncludeDirectories(builtInIncludeDirectories)
+            .setCppExecutable(cppExecutable)
+            .setPreprocessorExecutable(preprocessorExecutable)
+            .setTargetName(cToolchainIdeInfo.getTargetName())
+            .addUnfilteredCompilerOptions(unfilteredCompilerOptions.getToolchainFlags())
+            .addUnfilteredToolchainSystemIncludes(
+                unfilteredCompilerOptions.getToolchainSysIncludes());
+
+    return builder.build();
+  }
+
+  private static JavaRuleIdeInfo makeJavaRuleIdeInfo(
+      ArtifactLocationDecoder decoder, AndroidStudioIdeInfo.JavaRuleIdeInfo javaRuleIdeInfo) {
+    return new JavaRuleIdeInfo(
+        makeLibraryArtifactList(decoder, javaRuleIdeInfo.getJarsList()),
+        makeLibraryArtifactList(decoder, javaRuleIdeInfo.getGeneratedJarsList()),
+        javaRuleIdeInfo.hasFilteredGenJar()
+            ? makeLibraryArtifact(decoder, javaRuleIdeInfo.getFilteredGenJar())
+            : null,
+        javaRuleIdeInfo.hasPackageManifest()
+            ? makeArtifactLocation(decoder, javaRuleIdeInfo.getPackageManifest())
+            : null,
+        javaRuleIdeInfo.hasJdeps()
+            ? makeArtifactLocation(decoder, javaRuleIdeInfo.getJdeps())
+            : null);
+  }
+
+  private static AndroidRuleIdeInfo makeAndroidRuleIdeInfo(
+      ArtifactLocationDecoder decoder, AndroidStudioIdeInfo.AndroidRuleIdeInfo androidRuleIdeInfo) {
+    return new AndroidRuleIdeInfo(
+        makeArtifactLocationList(decoder, androidRuleIdeInfo.getResourcesList()),
+        androidRuleIdeInfo.getJavaPackage(),
+        androidRuleIdeInfo.getGenerateResourceClass(),
+        androidRuleIdeInfo.hasManifest()
+            ? makeArtifactLocation(decoder, androidRuleIdeInfo.getManifest())
+            : null,
+        androidRuleIdeInfo.hasIdlJar()
+            ? makeLibraryArtifact(decoder, androidRuleIdeInfo.getIdlJar())
+            : null,
+        androidRuleIdeInfo.hasResourceJar()
+            ? makeLibraryArtifact(decoder, androidRuleIdeInfo.getResourceJar())
+            : null,
+        androidRuleIdeInfo.getHasIdlSources(),
+        !Strings.isNullOrEmpty(androidRuleIdeInfo.getLegacyResources())
+            ? new Label(androidRuleIdeInfo.getLegacyResources())
+            : null);
+  }
+
+  private static TestIdeInfo makeTestIdeInfo(AndroidStudioIdeInfo.TestInfo testInfo) {
+    String size = testInfo.getSize();
+    TestIdeInfo.TestSize testSize = TestIdeInfo.DEFAULT_RULE_TEST_SIZE;
+    if (!Strings.isNullOrEmpty(size)) {
+      switch (size) {
+        case "small":
+          testSize = TestIdeInfo.TestSize.SMALL;
+          break;
+        case "medium":
+          testSize = TestIdeInfo.TestSize.MEDIUM;
+          break;
+        case "large":
+          testSize = TestIdeInfo.TestSize.LARGE;
+          break;
+        case "enormous":
+          testSize = TestIdeInfo.TestSize.ENORMOUS;
+          break;
+        default:
+          break;
+      }
+    }
+    return new TestIdeInfo(testSize);
+  }
+
+  private static ProtoLibraryLegacyInfo makeProtoLibraryLegacyInfo(
+      ArtifactLocationDecoder decoder,
+      AndroidStudioIdeInfo.ProtoLibraryLegacyJavaIdeInfo protoLibraryLegacyJavaIdeInfo) {
+    final ProtoLibraryLegacyInfo.ApiFlavor apiFlavor;
+    if (protoLibraryLegacyJavaIdeInfo.getApiVersion() == 1) {
+      apiFlavor = ProtoLibraryLegacyInfo.ApiFlavor.VERSION_1;
+    } else {
+      switch (protoLibraryLegacyJavaIdeInfo.getApiFlavor()) {
+        case MUTABLE:
+          apiFlavor = ProtoLibraryLegacyInfo.ApiFlavor.MUTABLE;
+          break;
+        case IMMUTABLE:
+          apiFlavor = ProtoLibraryLegacyInfo.ApiFlavor.IMMUTABLE;
+          break;
+        case BOTH:
+          apiFlavor = ProtoLibraryLegacyInfo.ApiFlavor.BOTH;
+          break;
+        default:
+          apiFlavor = ProtoLibraryLegacyInfo.ApiFlavor.NONE;
+          break;
+      }
+    }
+    return new ProtoLibraryLegacyInfo(
+        apiFlavor,
+        makeLibraryArtifactList(decoder, protoLibraryLegacyJavaIdeInfo.getJars1List()),
+        makeLibraryArtifactList(decoder, protoLibraryLegacyJavaIdeInfo.getJarsMutableList()),
+        makeLibraryArtifactList(decoder, protoLibraryLegacyJavaIdeInfo.getJarsImmutableList()));
+  }
+
+  private static JavaToolchainIdeInfo makeJavaToolchainIdeInfo(
+      AndroidStudioIdeInfo.JavaToolchainIdeInfo javaToolchainIdeInfo) {
+    return new JavaToolchainIdeInfo(
+        javaToolchainIdeInfo.getSourceVersion(), javaToolchainIdeInfo.getTargetVersion());
+  }
+
+  private static Collection<LibraryArtifact> makeLibraryArtifactList(
+      ArtifactLocationDecoder decoder, List<AndroidStudioIdeInfo.LibraryArtifact> jarsList) {
+    ImmutableList.Builder<LibraryArtifact> builder = ImmutableList.builder();
+    for (AndroidStudioIdeInfo.LibraryArtifact libraryArtifact : jarsList) {
+      LibraryArtifact lib = makeLibraryArtifact(decoder, libraryArtifact);
+      if (lib != null) {
+        builder.add(lib);
+      }
+    }
+    return builder.build();
+  }
+
+  @Nullable
+  private static LibraryArtifact makeLibraryArtifact(
+      ArtifactLocationDecoder decoder, AndroidStudioIdeInfo.LibraryArtifact libraryArtifact) {
+    ArtifactLocation classJar =
+        libraryArtifact.hasJar() ? makeArtifactLocation(decoder, libraryArtifact.getJar()) : null;
+    ArtifactLocation iJar =
+        libraryArtifact.hasInterfaceJar()
+            ? makeArtifactLocation(decoder, libraryArtifact.getInterfaceJar())
+            : null;
+    ArtifactLocation sourceJar =
+        libraryArtifact.hasSourceJar()
+            ? makeArtifactLocation(decoder, libraryArtifact.getSourceJar())
+            : null;
+    if (iJar == null && classJar == null) {
+      // Failed to find ArtifactLocation file --
+      // presumably because it was removed from file system since blaze build
+      return null;
+    }
+    return new LibraryArtifact(iJar, classJar, sourceJar);
+  }
+
+  private static List<ArtifactLocation> makeArtifactLocationList(
+      ArtifactLocationDecoder decoder, List<AndroidStudioIdeInfo.ArtifactLocation> sourcesList) {
+    ImmutableList.Builder<ArtifactLocation> builder = ImmutableList.builder();
+    for (AndroidStudioIdeInfo.ArtifactLocation pbArtifactLocation : sourcesList) {
+      ArtifactLocation loc = makeArtifactLocation(decoder, pbArtifactLocation);
+      if (loc != null) {
+        builder.add(loc);
+      }
+    }
+    return builder.build();
+  }
+
+  @Nullable
+  private static ArtifactLocation makeArtifactLocation(
+      ArtifactLocationDecoder decoder, AndroidStudioIdeInfo.ArtifactLocation pbArtifactLocation) {
+    if (pbArtifactLocation == null) {
+      return null;
+    }
+    return decoder.decode(pbArtifactLocation);
+  }
+
+  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();
+    if (!Strings.isNullOrEmpty(kindString)) {
+      return Kind.fromString(kindString);
+    }
+    return null;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/sync/aspects/UnfilteredCompilerOptions.java b/base/src/com/google/idea/blaze/base/sync/aspects/UnfilteredCompilerOptions.java
new file mode 100644
index 0000000..8509647
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/aspects/UnfilteredCompilerOptions.java
@@ -0,0 +1,84 @@
+/*
+ * 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.common.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.model.primitives.ExecutionRootPath;
+import java.util.List;
+
+/**
+ * unfilteredCompilerOptions is a grab bag of options passed to the compiler. Do minimal parsing to
+ * extract what we need.
+ */
+final class UnfilteredCompilerOptions {
+  private enum NextOption {
+    ISYSTEM,
+    FLAG
+  }
+
+  private final List<ExecutionRootPath> toolchainSysIncludes;
+  private final List<String> toolchainFlags;
+
+  public UnfilteredCompilerOptions(Iterable<String> unfilteredOptions) {
+    List<String> toolchainSystemIncludePaths = Lists.newArrayList();
+    toolchainFlags = Lists.newArrayList();
+    splitUnfilteredCompilerOptions(unfilteredOptions, toolchainSystemIncludePaths, toolchainFlags);
+
+    toolchainSysIncludes = Lists.newArrayList();
+    for (String systemInclude : toolchainSystemIncludePaths) {
+      toolchainSysIncludes.add(new ExecutionRootPath(systemInclude));
+    }
+  }
+
+  public List<String> getToolchainFlags() {
+    return toolchainFlags;
+  }
+
+  public List<ExecutionRootPath> getToolchainSysIncludes() {
+    return toolchainSysIncludes;
+  }
+
+  @VisibleForTesting
+  static void splitUnfilteredCompilerOptions(
+      Iterable<String> unfilteredOptions,
+      List<String> toolchainSysIncludes,
+      List<String> toolchainFlags) {
+    NextOption nextOption = NextOption.FLAG;
+    for (String unfilteredOption : unfilteredOptions) {
+      // We are looking for either the flag pair
+      // "-isystem /path/to/dir" or the flag "-isystem/path/to/dir"
+      //
+      // blaze emits isystem flags in both formats.
+      // The latter isn't ideal but apparently it is accepted by GCC and will be emitted by
+      // blaze under certain circumstances.
+      if (nextOption == NextOption.ISYSTEM) {
+        toolchainSysIncludes.add(unfilteredOption);
+        nextOption = NextOption.FLAG;
+      } else {
+        if (unfilteredOption.equals("-isystem")) {
+          nextOption = NextOption.ISYSTEM;
+        } else if (unfilteredOption.startsWith("-isystem")) {
+          String iSystemIncludePath = unfilteredOption.substring("-isystem".length());
+          toolchainSysIncludes.add(iSystemIncludePath);
+        } else {
+          toolchainFlags.add(unfilteredOption);
+          nextOption = NextOption.FLAG;
+        }
+      }
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/sync/data/BlazeDataStorage.java b/base/src/com/google/idea/blaze/base/sync/data/BlazeDataStorage.java
new file mode 100644
index 0000000..2a9fca8
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/data/BlazeDataStorage.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.base.sync.data;
+
+import com.google.common.base.Strings;
+import com.google.idea.blaze.base.settings.BlazeImportSettings;
+import com.intellij.openapi.application.PathManager;
+import com.intellij.openapi.project.Project;
+import java.io.File;
+
+/** Defines where we store our blaze project data. */
+public class BlazeDataStorage {
+  private static final String DATA_SUBDIRECTORY = ".blaze";
+
+  public static File getProjectDataDir(BlazeImportSettings importSettings) {
+    return new File(importSettings.getProjectDataDirectory(), DATA_SUBDIRECTORY);
+  }
+
+  public static File getProjectCacheDir(Project project, BlazeImportSettings importSettings) {
+    String locationHash = importSettings.getLocationHash();
+
+    // Legacy support: The location hash used to be just the project hash
+    if (Strings.isNullOrEmpty(locationHash)) {
+      locationHash = project.getLocationHash();
+    }
+
+    return new File(getProjectConfigurationDir(), locationHash);
+  }
+
+  private static File getProjectConfigurationDir() {
+    return new File(PathManager.getSystemPath(), "blaze/projects").getAbsoluteFile();
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/sync/data/BlazeProjectDataManager.java b/base/src/com/google/idea/blaze/base/sync/data/BlazeProjectDataManager.java
new file mode 100644
index 0000000..f92cf85
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/data/BlazeProjectDataManager.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.data;
+
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.sync.BlazeSyncPlugin;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.project.Project;
+import javax.annotation.Nullable;
+
+/** Stores a cache of blaze project data. */
+public interface BlazeProjectDataManager {
+  static BlazeProjectDataManager getInstance(Project project) {
+    return ServiceManager.getService(project, BlazeProjectDataManager.class);
+  }
+
+  @Nullable
+  BlazeProjectData getBlazeProjectData();
+
+  BlazeSyncPlugin.ModuleEditor editModules();
+}
diff --git a/base/src/com/google/idea/blaze/base/sync/data/BlazeProjectDataManagerImpl.java b/base/src/com/google/idea/blaze/base/sync/data/BlazeProjectDataManagerImpl.java
new file mode 100644
index 0000000..821109d
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/data/BlazeProjectDataManagerImpl.java
@@ -0,0 +1,133 @@
+/*
+ * 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.data;
+
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.async.executor.BlazeExecutor;
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.scope.output.StatusOutput;
+import com.google.idea.blaze.base.settings.BlazeImportSettings;
+import com.google.idea.blaze.base.settings.BlazeImportSettingsManager;
+import com.google.idea.blaze.base.sync.BlazeSyncPlugin;
+import com.google.idea.blaze.base.sync.projectstructure.ModuleEditorProvider;
+import com.google.idea.blaze.base.util.SerializationUtil;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.project.Project;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import javax.annotation.Nullable;
+
+/** Stores a cache of blaze project data and issues any side effects when that data is updated. */
+public class BlazeProjectDataManagerImpl implements BlazeProjectDataManager {
+
+  private static final Logger LOG = Logger.getInstance(BlazeProjectDataManagerImpl.class.getName());
+
+  private final Project project;
+
+  @Nullable private volatile BlazeProjectData blazeProjectData;
+
+  private final Object saveLock = new Object();
+
+  public static BlazeProjectDataManagerImpl getImpl(Project project) {
+    return (BlazeProjectDataManagerImpl) BlazeProjectDataManager.getInstance(project);
+  }
+
+  public BlazeProjectDataManagerImpl(Project project) {
+    this.project = project;
+  }
+
+  @Nullable
+  public BlazeProjectData loadProjectRoot(
+      BlazeContext context, BlazeImportSettings importSettings) {
+    BlazeProjectData projectData = blazeProjectData;
+    if (projectData != null) {
+      return projectData;
+    }
+    synchronized (this) {
+      projectData = blazeProjectData;
+      return projectData != null ? projectData : loadProject(context, importSettings);
+    }
+  }
+
+  @Override
+  @Nullable
+  public BlazeProjectData getBlazeProjectData() {
+    return blazeProjectData;
+  }
+
+  @Override
+  public BlazeSyncPlugin.ModuleEditor editModules() {
+    return ModuleEditorProvider.getInstance()
+        .getModuleEditor(
+            project, BlazeImportSettingsManager.getInstance(project).getImportSettings());
+  }
+
+  @Nullable
+  private synchronized BlazeProjectData loadProject(
+      BlazeContext context, BlazeImportSettings importSettings) {
+    BlazeProjectData blazeProjectData = null;
+    try {
+      File file = getCacheFile(project, importSettings);
+
+      List<ClassLoader> classLoaders = Lists.newArrayList();
+      for (BlazeSyncPlugin syncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) {
+        classLoaders.add(syncPlugin.getClass().getClassLoader());
+      }
+      classLoaders.add(getClass().getClassLoader());
+      classLoaders.add(Thread.currentThread().getContextClassLoader());
+
+      blazeProjectData = (BlazeProjectData) SerializationUtil.loadFromDisk(file, classLoaders);
+    } catch (IOException e) {
+      String buildSystemName = importSettings.getBuildSystem().getLowerCaseName();
+      context.output(
+          new StatusOutput(
+              String.format("Stale %s project cache, sync will be needed", buildSystemName)));
+      LOG.info(e);
+    }
+
+    this.blazeProjectData = blazeProjectData;
+    return blazeProjectData;
+  }
+
+  public void saveProject(
+      final BlazeImportSettings importSettings, final BlazeProjectData blazeProjectData) {
+    this.blazeProjectData = blazeProjectData;
+
+    // Can only run one save operation per project at a time
+    synchronized (saveLock) {
+      BlazeExecutor.submitTask(
+          project,
+          "Saving sync data...",
+          (ProgressIndicator indicator) -> {
+            try {
+              File file = getCacheFile(project, importSettings);
+              SerializationUtil.saveToDisk(file, blazeProjectData);
+            } catch (IOException e) {
+              LOG.error(
+                  "Could not save cache data file to disk. Please resync project. Error: "
+                      + e.getMessage());
+            }
+          });
+    }
+  }
+
+  private static File getCacheFile(Project project, BlazeImportSettings importSettings) {
+    return new File(BlazeDataStorage.getProjectCacheDir(project, importSettings), "cache.dat");
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/sync/projectstructure/ContentEntryEditor.java b/base/src/com/google/idea/blaze/base/sync/projectstructure/ContentEntryEditor.java
new file mode 100644
index 0000000..e3e4357
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/projectstructure/ContentEntryEditor.java
@@ -0,0 +1,128 @@
+/*
+ * 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.projectstructure;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+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.projectview.ProjectViewSet;
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.settings.Blaze;
+import com.google.idea.blaze.base.sync.BlazeSyncPlugin;
+import com.google.idea.blaze.base.sync.projectview.ImportRoots;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ContentEntry;
+import com.intellij.openapi.roots.ModifiableRootModel;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.io.FileUtilRt;
+import com.intellij.openapi.vfs.VfsUtilCore;
+import com.intellij.util.io.URLUtil;
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+
+/** Modifies content entries based on project data. */
+public class ContentEntryEditor {
+
+  public static void createContentEntries(
+      Project project,
+      BlazeContext context,
+      WorkspaceRoot workspaceRoot,
+      ProjectViewSet projectViewSet,
+      BlazeProjectData blazeProjectData,
+      ModifiableRootModel modifiableRootModel) {
+    ImportRoots importRoots =
+        ImportRoots.builder(workspaceRoot, Blaze.getBuildSystem(project))
+            .add(projectViewSet)
+            .build();
+    Collection<WorkspacePath> rootDirectories = importRoots.rootDirectories();
+    Collection<WorkspacePath> excludeDirectories = importRoots.excludeDirectories();
+    Multimap<WorkspacePath, WorkspacePath> excludesByRootDirectory =
+        sortExcludesByRootDirectory(rootDirectories, excludeDirectories);
+
+    List<ContentEntry> contentEntries = Lists.newArrayList();
+    for (WorkspacePath rootDirectory : rootDirectories) {
+      File root = workspaceRoot.fileForPath(rootDirectory);
+      ContentEntry contentEntry = modifiableRootModel.addContentEntry(pathToUrl(root.getPath()));
+      contentEntries.add(contentEntry);
+
+      for (WorkspacePath exclude : excludesByRootDirectory.get(rootDirectory)) {
+        File excludeFolder = workspaceRoot.fileForPath(exclude);
+        contentEntry.addExcludeFolder(pathToIdeaUrl(excludeFolder));
+      }
+    }
+
+    for (BlazeSyncPlugin syncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) {
+      syncPlugin.updateContentEntries(
+          project, context, workspaceRoot, projectViewSet, blazeProjectData, contentEntries);
+    }
+  }
+
+  private static Multimap<WorkspacePath, WorkspacePath> sortExcludesByRootDirectory(
+      Collection<WorkspacePath> rootDirectories, Collection<WorkspacePath> excludedDirectories) {
+
+    Multimap<WorkspacePath, WorkspacePath> result = ArrayListMultimap.create();
+    for (WorkspacePath exclude : excludedDirectories) {
+      WorkspacePath foundWorkspacePath =
+          rootDirectories
+              .stream()
+              .filter(rootDirectory -> isUnderRootDirectory(rootDirectory, exclude.relativePath()))
+              .findFirst()
+              .orElse(null);
+      if (foundWorkspacePath != null) {
+        result.put(foundWorkspacePath, exclude);
+      }
+    }
+    return result;
+  }
+
+  private static boolean isUnderRootDirectory(WorkspacePath rootDirectory, String relativePath) {
+    if (rootDirectory.isWorkspaceRoot()) {
+      return true;
+    }
+    String rootDirectoryString = rootDirectory.toString();
+    return relativePath.startsWith(rootDirectoryString)
+        && (relativePath.length() == rootDirectoryString.length()
+            || (relativePath.charAt(rootDirectoryString.length()) == '/'));
+  }
+
+  @NotNull
+  private static String pathToUrl(@NotNull String filePath) {
+    filePath = FileUtil.toSystemIndependentName(filePath);
+    if (filePath.endsWith(".srcjar") || filePath.endsWith(".jar")) {
+      return URLUtil.JAR_PROTOCOL + URLUtil.SCHEME_SEPARATOR + filePath + URLUtil.JAR_SEPARATOR;
+    } else if (filePath.contains("src.jar!")) {
+      return URLUtil.JAR_PROTOCOL + URLUtil.SCHEME_SEPARATOR + filePath;
+    } else {
+      return VfsUtilCore.pathToUrl(filePath);
+    }
+  }
+
+  @NotNull
+  private static String pathToIdeaUrl(@NotNull File path) {
+    return pathToUrl(toSystemIndependentName(path.getPath()));
+  }
+
+  @NotNull
+  private static String toSystemIndependentName(@NonNls @NotNull String aFileName) {
+    return FileUtilRt.toSystemIndependentName(aFileName);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/sync/projectstructure/ModuleEditorImpl.java b/base/src/com/google/idea/blaze/base/sync/projectstructure/ModuleEditorImpl.java
new file mode 100644
index 0000000..2f05f68
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/projectstructure/ModuleEditorImpl.java
@@ -0,0 +1,178 @@
+/*
+ * 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.projectstructure;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.idea.blaze.base.io.FileAttributeProvider;
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.scope.output.PrintOutput;
+import com.google.idea.blaze.base.settings.BlazeImportSettings;
+import com.google.idea.blaze.base.sync.BlazeSyncPlugin;
+import com.google.idea.blaze.base.sync.data.BlazeDataStorage;
+import com.intellij.ide.highlighter.ModuleFileType;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.module.ModifiableModuleModel;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleManager;
+import com.intellij.openapi.module.ModuleType;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.CompilerModuleExtension;
+import com.intellij.openapi.roots.ModifiableRootModel;
+import com.intellij.openapi.roots.ModuleRootManager;
+import com.intellij.openapi.roots.impl.ModifiableModelCommitter;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import javax.annotation.Nullable;
+
+/** Module editor implementation. */
+public class ModuleEditorImpl implements BlazeSyncPlugin.ModuleEditor {
+  private static final Logger LOG = Logger.getInstance(ModuleEditorImpl.class.getName());
+  private static final String EXTERNAL_SYSTEM_ID_KEY = "external.system.id";
+  private static final String EXTERNAL_SYSTEM_ID_VALUE = "Blaze";
+
+  private final Project project;
+  private final ModifiableModuleModel moduleModel;
+  private final File imlDirectory;
+  private final Set<String> moduleNames = Sets.newHashSet();
+  @VisibleForTesting public Collection<ModifiableRootModel> modifiableModels = Lists.newArrayList();
+
+  public ModuleEditorImpl(Project project, BlazeImportSettings importSettings) {
+    this.project = project;
+    this.moduleModel = ModuleManager.getInstance(project).getModifiableModel();
+
+    this.imlDirectory = getImlDirectory(importSettings);
+    if (!FileAttributeProvider.getInstance().exists(imlDirectory)) {
+      if (!imlDirectory.mkdirs()) {
+        LOG.error("Could not make directory: " + imlDirectory.getPath());
+      }
+    }
+  }
+
+  @Override
+  public boolean registerModule(String moduleName) {
+    boolean hasModule = moduleModel.findModuleByName(moduleName) != null;
+    if (hasModule) {
+      moduleNames.add(moduleName);
+    }
+    return hasModule;
+  }
+
+  @Override
+  public Module createModule(String moduleName, ModuleType moduleType) {
+    Module module = moduleModel.findModuleByName(moduleName);
+    if (module == null) {
+      File imlFile = new File(imlDirectory, moduleName + ModuleFileType.DOT_DEFAULT_EXTENSION);
+      removeImlFile(imlFile);
+      module = moduleModel.newModule(imlFile.getPath(), moduleType.getId());
+      module.setOption(EXTERNAL_SYSTEM_ID_KEY, EXTERNAL_SYSTEM_ID_VALUE);
+    }
+    module.setOption(Module.ELEMENT_TYPE, moduleType.getId());
+    moduleNames.add(moduleName);
+    return module;
+  }
+
+  @Override
+  public ModifiableRootModel editModule(Module module) {
+    ModifiableRootModel modifiableModel =
+        ModuleRootManager.getInstance(module).getModifiableModel();
+    modifiableModels.add(modifiableModel);
+
+    modifiableModel.clear();
+    modifiableModel.inheritSdk();
+    CompilerModuleExtension compilerSettings =
+        modifiableModel.getModuleExtension(CompilerModuleExtension.class);
+    if (compilerSettings != null) {
+      compilerSettings.inheritCompilerOutputPath(false);
+    }
+
+    return modifiableModel;
+  }
+
+  @Override
+  @Nullable
+  public Module findModule(String moduleName) {
+    return moduleModel.findModuleByName(moduleName);
+  }
+
+  public void commitWithGc(BlazeContext context) {
+    List<Module> orphanModules = Lists.newArrayList();
+    for (Module module : ModuleManager.getInstance(project).getModules()) {
+      if (!moduleNames.contains(module.getName())) {
+        orphanModules.add(module);
+      }
+    }
+    if (orphanModules.size() > 0) {
+      context.output(
+          PrintOutput.log(String.format("Removing %d dead modules", orphanModules.size())));
+      for (Module module : orphanModules) {
+        if (module.isDisposed()) {
+          continue;
+        }
+        moduleModel.disposeModule(module);
+        File imlFile = new File(module.getModuleFilePath());
+        removeImlFile(imlFile);
+      }
+    }
+
+    context.output(
+        PrintOutput.log(String.format("Workspace has %s modules", modifiableModels.size())));
+
+    commit();
+  }
+
+  @Override
+  public void commit() {
+    ModifiableModelCommitter.multiCommit(modifiableModels, moduleModel);
+  }
+
+  private File getImlDirectory(BlazeImportSettings importSettings) {
+    return new File(BlazeDataStorage.getProjectDataDir(importSettings), "modules");
+  }
+
+  // Delete using the virtual file to ensure that IntelliJ properly updates its index.
+  // Otherwise, it is possible for IntelliJ to read the
+  // old IML file from its index and behave unpredictably
+  // (like failing to save the new IML files to disk).
+  private static void removeImlFile(final File imlFile) {
+    final VirtualFile imlVirtualFile = VfsUtil.findFileByIoFile(imlFile, true);
+    if (imlVirtualFile != null && imlVirtualFile.exists()) {
+      ApplicationManager.getApplication()
+          .runWriteAction(
+              new Runnable() {
+                @Override
+                public void run() {
+                  try {
+                    imlVirtualFile.delete(this);
+                  } catch (IOException e) {
+                    LOG.warn(
+                        String.format(
+                            "Could not delete file: %s, will try to continue anyway.",
+                            imlVirtualFile.getPath()),
+                        e);
+                  }
+                }
+              });
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/sync/projectstructure/ModuleEditorProvider.java b/base/src/com/google/idea/blaze/base/sync/projectstructure/ModuleEditorProvider.java
new file mode 100644
index 0000000..6280c37
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/projectstructure/ModuleEditorProvider.java
@@ -0,0 +1,33 @@
+/*
+ * 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.projectstructure;
+
+import com.google.idea.blaze.base.settings.BlazeImportSettings;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.project.Project;
+
+/**
+ * Provides a ModuleEditor. This indirection is required to avoid committing modules during
+ * integration tests of the sync process, as this is not allowed by LightPlatformTestCase.
+ */
+public interface ModuleEditorProvider {
+
+  static ModuleEditorProvider getInstance() {
+    return ServiceManager.getService(ModuleEditorProvider.class);
+  }
+
+  ModuleEditorImpl getModuleEditor(Project project, BlazeImportSettings importSettings);
+}
diff --git a/base/src/com/google/idea/blaze/base/sync/projectstructure/ModuleEditorProviderImpl.java b/base/src/com/google/idea/blaze/base/sync/projectstructure/ModuleEditorProviderImpl.java
new file mode 100644
index 0000000..8cfa34b
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/projectstructure/ModuleEditorProviderImpl.java
@@ -0,0 +1,31 @@
+/*
+ * 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.projectstructure;
+
+import com.google.idea.blaze.base.settings.BlazeImportSettings;
+import com.intellij.openapi.project.Project;
+
+/**
+ * Provides a ModuleEditor. This indirection is required to avoid committing modules during
+ * integration tests of the sync process, as this is not allowed by LightPlatformTestCase.
+ */
+public class ModuleEditorProviderImpl implements ModuleEditorProvider {
+
+  @Override
+  public ModuleEditorImpl getModuleEditor(Project project, BlazeImportSettings importSettings) {
+    return new ModuleEditorImpl(project, importSettings);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/sync/projectview/ImportRoots.java b/base/src/com/google/idea/blaze/base/sync/projectview/ImportRoots.java
new file mode 100644
index 0000000..5a73ef4
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/projectview/ImportRoots.java
@@ -0,0 +1,171 @@
+/*
+ * 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.projectview;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.idea.blaze.base.bazel.BuildSystemProvider;
+import com.google.idea.blaze.base.model.primitives.Label;
+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.section.sections.DirectoryEntry;
+import com.google.idea.blaze.base.projectview.section.sections.DirectorySection;
+import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
+import com.intellij.openapi.util.io.FileUtil;
+import java.util.Collection;
+import java.util.Set;
+
+/** The roots to import. Derived from project view. */
+public final class ImportRoots {
+  /** Builder for import roots */
+  public static class Builder {
+    private final ImmutableCollection.Builder<WorkspacePath> rootDirectoriesBuilder =
+        ImmutableList.builder();
+    private final ImmutableSet.Builder<WorkspacePath> excludeDirectoriesBuilder =
+        ImmutableSet.builder();
+
+    private final WorkspaceRoot workspaceRoot;
+    private final BuildSystem buildSystem;
+
+    private Builder(WorkspaceRoot workspaceRoot, BuildSystem buildSystem) {
+      this.workspaceRoot = workspaceRoot;
+      this.buildSystem = buildSystem;
+    }
+
+    public Builder add(ProjectViewSet projectViewSet) {
+      for (DirectoryEntry entry : projectViewSet.listItems(DirectorySection.KEY)) {
+        add(entry);
+      }
+      return this;
+    }
+
+    @VisibleForTesting
+    public Builder add(DirectoryEntry entry) {
+      if (entry.included) {
+        rootDirectoriesBuilder.add(entry.directory);
+      } else {
+        excludeDirectoriesBuilder.add(entry.directory);
+      }
+      return this;
+    }
+
+    public ImportRoots build() {
+      ImmutableCollection<WorkspacePath> rootDirectories = rootDirectoriesBuilder.build();
+
+      // Remove any duplicates and any overlapping directories
+      ImmutableSet.Builder<WorkspacePath> minimalRootDirectories = ImmutableSet.builder();
+      for (WorkspacePath directory : rootDirectories) {
+        boolean ok = true;
+        for (WorkspacePath otherDirectory : rootDirectories) {
+          if (directory == otherDirectory) {
+            continue;
+          }
+          ok = ok && !isAncestor(otherDirectory.relativePath(), directory.relativePath());
+        }
+        if (ok) {
+          minimalRootDirectories.add(directory);
+        }
+      }
+
+      // for bazel projects, if we're including the workspace root,
+      // we force-exclude the bazel artifact directories
+      // (e.g. bazel-bin, bazel-genfiles).
+      if (buildSystem == BuildSystem.Bazel && hasWorkspaceRoot(rootDirectories)) {
+        excludeBuildSystemArtifacts();
+      }
+      return new ImportRoots(minimalRootDirectories.build(), excludeDirectoriesBuilder.build());
+    }
+
+    private void excludeBuildSystemArtifacts() {
+      for (String dir :
+          BuildSystemProvider.getBuildSystemProvider(buildSystem)
+              .buildArtifactDirectories(workspaceRoot)) {
+        excludeDirectoriesBuilder.add(new WorkspacePath(dir));
+      }
+    }
+
+    private static boolean hasWorkspaceRoot(ImmutableCollection<WorkspacePath> rootDirectories) {
+      return rootDirectories.stream().anyMatch(WorkspacePath::isWorkspaceRoot);
+    }
+  }
+
+  private final ImmutableCollection<WorkspacePath> rootDirectories;
+  private final ImmutableSet<WorkspacePath> excludeDirectories;
+
+  public static Builder builder(WorkspaceRoot workspaceRoot, BuildSystem buildSystem) {
+    return new Builder(workspaceRoot, buildSystem);
+  }
+
+  private ImportRoots(
+      ImmutableCollection<WorkspacePath> rootDirectories,
+      ImmutableSet<WorkspacePath> excludeDirectories) {
+    this.rootDirectories = rootDirectories;
+    this.excludeDirectories = excludeDirectories;
+  }
+
+  public Collection<WorkspacePath> rootDirectories() {
+    return rootDirectories;
+  }
+
+  public Set<WorkspacePath> excludeDirectories() {
+    return excludeDirectories;
+  }
+
+  /** Returns true if this rule should be imported as source. */
+  public boolean importAsSource(Label label) {
+    return containsLabel(label);
+  }
+
+  private boolean containsLabel(Label label) {
+    boolean included = false;
+    boolean excluded = false;
+    for (WorkspacePath workspacePath : rootDirectories()) {
+      included = included || matchesLabel(workspacePath, label);
+    }
+    for (WorkspacePath workspacePath : excludeDirectories()) {
+      excluded = excluded || matchesLabel(workspacePath, label);
+    }
+    return included && !excluded;
+  }
+
+  private static boolean matchesLabel(WorkspacePath workspacePath, Label label) {
+    if (workspacePath.isWorkspaceRoot()) {
+      return true;
+    }
+    String moduleLabelStr = label.toString();
+    int packagePrefixLength = "//".length();
+    int nextCharIndex = workspacePath.relativePath().length() + packagePrefixLength;
+    if (moduleLabelStr.startsWith(workspacePath.relativePath(), packagePrefixLength)
+        && moduleLabelStr.length() >= nextCharIndex) {
+      char c = moduleLabelStr.charAt(nextCharIndex);
+      return c == '/' || c == ':';
+    }
+    return false;
+  }
+
+  /** Returns true if 'path' is a strict child of 'ancestorPath'. */
+  private static boolean isAncestor(String ancestorPath, String path) {
+    // FileUtil.isAncestor has a bug in its handling of equal,
+    // empty paths (it ignores the 'strict' flag in this case).
+    if (ancestorPath.equals(path)) {
+      return false;
+    }
+    return FileUtil.isAncestor(ancestorPath, path, true);
+  }
+}
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
new file mode 100644
index 0000000..f0e43ab
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/projectview/LanguageSupport.java
@@ -0,0 +1,78 @@
+/*
+ * 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.projectview;
+
+import com.google.common.collect.Sets;
+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.ProjectViewSet;
+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.output.IssueOutput;
+import com.google.idea.blaze.base.sync.BlazeSyncPlugin;
+import com.intellij.openapi.diagnostic.Logger;
+import java.util.Set;
+
+/** Reads the user's language preferences from the project view. */
+public class LanguageSupport {
+
+  private static final Logger LOG = Logger.getInstance(LanguageSupport.class);
+
+  public static WorkspaceLanguageSettings createWorkspaceLanguageSettings(
+      BlazeContext context, ProjectViewSet projectViewSet) {
+    WorkspaceType workspaceType = projectViewSet.getScalarValue(WorkspaceTypeSection.KEY);
+    if (workspaceType == null) {
+      for (BlazeSyncPlugin syncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) {
+        WorkspaceType pluginWorkspaceType = syncPlugin.getDefaultWorkspaceType();
+        if (pluginWorkspaceType != null) {
+          if (workspaceType == null || workspaceType.ordinal() < pluginWorkspaceType.ordinal()) {
+            workspaceType = pluginWorkspaceType;
+          }
+        }
+      }
+    }
+
+    if (workspaceType == null) {
+      LOG.error("Could not find workspace type."); // Should never happen
+      return null;
+    }
+
+    Set<LanguageClass> activeLanguages = Sets.newHashSet();
+    for (LanguageClass languageClass : workspaceType.getLanguages()) {
+      activeLanguages.add(languageClass);
+    }
+    activeLanguages.addAll(projectViewSet.listItems(AdditionalLanguagesSection.KEY));
+
+    Set<LanguageClass> supportedLanguages = Sets.newHashSet();
+    for (BlazeSyncPlugin syncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) {
+      supportedLanguages.addAll(syncPlugin.getSupportedLanguagesInWorkspace(workspaceType));
+    }
+
+    for (LanguageClass languageClass : activeLanguages) {
+      if (!supportedLanguages.contains(languageClass)) {
+        IssueOutput.error(
+                String.format(
+                    "Language '%s' is not supported for this plugin with workspace type: '%s'",
+                    languageClass.getName(), workspaceType.getName()))
+            .submit(context);
+        return null;
+      }
+    }
+
+    return new WorkspaceLanguageSettings(workspaceType, activeLanguages);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/sync/projectview/ProjectViewRuleImportFilter.java b/base/src/com/google/idea/blaze/base/sync/projectview/ProjectViewRuleImportFilter.java
new file mode 100644
index 0000000..5cd8b16
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/projectview/ProjectViewRuleImportFilter.java
@@ -0,0 +1,62 @@
+/*
+ * 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.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.model.primitives.Label;
+import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
+import com.google.idea.blaze.base.projectview.ProjectViewSet;
+import com.google.idea.blaze.base.projectview.section.sections.ExcludeTargetSection;
+import com.google.idea.blaze.base.projectview.section.sections.ImportTargetOutputSection;
+import com.google.idea.blaze.base.settings.Blaze;
+import com.intellij.openapi.project.Project;
+import java.util.Set;
+
+/** Filters rules into source/library depending on the project view. */
+public class ProjectViewRuleImportFilter {
+  private final ImportRoots importRoots;
+  private final Set<Label> importTargetOutputs;
+  private final Set<Label> excludedTargets;
+
+  public ProjectViewRuleImportFilter(
+      Project project, WorkspaceRoot workspaceRoot, ProjectViewSet projectViewSet) {
+    this.importRoots =
+        ImportRoots.builder(workspaceRoot, Blaze.getBuildSystem(project))
+            .add(projectViewSet)
+            .build();
+    this.importTargetOutputs =
+        Sets.newHashSet(projectViewSet.listItems(ImportTargetOutputSection.KEY));
+    this.excludedTargets = Sets.newHashSet(projectViewSet.listItems(ExcludeTargetSection.KEY));
+  }
+
+  public boolean isSourceRule(RuleIdeInfo rule) {
+    return importRoots.importAsSource(rule.label) && !importTargetOutput(rule);
+  }
+
+  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);
+  }
+
+  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);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/sync/projectview/SourceTestConfig.java b/base/src/com/google/idea/blaze/base/sync/projectview/SourceTestConfig.java
new file mode 100644
index 0000000..018193e
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/projectview/SourceTestConfig.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.projectview;
+
+import com.google.idea.blaze.base.projectview.ProjectViewSet;
+import com.google.idea.blaze.base.projectview.section.Glob;
+import com.google.idea.blaze.base.projectview.section.sections.TestSourceSection;
+
+/** Affects the way sources are imported. */
+public class SourceTestConfig {
+  private final Glob.GlobSet testSources;
+
+  public SourceTestConfig(ProjectViewSet projectViewSet) {
+    this.testSources = new Glob.GlobSet(projectViewSet.listItems(TestSourceSection.KEY));
+  }
+
+  /** Returns true if this artifact is a test artifact. */
+  public boolean isTestSource(String relativePath) {
+    return testSources.matches(relativePath);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/sync/projectview/WorkspaceLanguageSettings.java b/base/src/com/google/idea/blaze/base/sync/projectview/WorkspaceLanguageSettings.java
new file mode 100644
index 0000000..4ebde4d
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/projectview/WorkspaceLanguageSettings.java
@@ -0,0 +1,90 @@
+/*
+ * 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.projectview;
+
+import com.google.common.base.Objects;
+import com.google.idea.blaze.base.model.primitives.LanguageClass;
+import com.google.idea.blaze.base.model.primitives.WorkspaceType;
+import java.io.Serializable;
+import java.util.Set;
+import javax.annotation.concurrent.Immutable;
+
+/** Contains the user's language preferences from the project view. */
+@Immutable
+public class WorkspaceLanguageSettings implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  private final WorkspaceType workspaceType;
+  private final Set<LanguageClass> activeLanguages;
+
+  public WorkspaceLanguageSettings(
+      WorkspaceType workspaceType, Set<LanguageClass> activeLanguages) {
+    this.workspaceType = workspaceType;
+    this.activeLanguages = activeLanguages;
+  }
+
+  public WorkspaceType getWorkspaceType() {
+    return workspaceType;
+  }
+
+  public boolean isWorkspaceType(WorkspaceType workspaceType) {
+    return this.workspaceType == workspaceType;
+  }
+
+  public boolean isWorkspaceType(WorkspaceType... workspaceTypes) {
+    for (WorkspaceType workspaceType : workspaceTypes) {
+      if (this.workspaceType == workspaceType) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  public boolean isLanguageActive(LanguageClass languageClass) {
+    return activeLanguages.contains(languageClass);
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    WorkspaceLanguageSettings that = (WorkspaceLanguageSettings) o;
+    return workspaceType == that.workspaceType
+        && Objects.equal(activeLanguages, that.activeLanguages);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(workspaceType, activeLanguages);
+  }
+
+  @Override
+  public String toString() {
+    return "WorkspaceLanguageSettings {"
+        + "\n"
+        + "  workspaceType: "
+        + workspaceType
+        + "\n"
+        + "  activeLanguages: "
+        + activeLanguages
+        + "\n"
+        + '}';
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/sync/sdk/DefaultSdkProvider.java b/base/src/com/google/idea/blaze/base/sync/sdk/DefaultSdkProvider.java
new file mode 100644
index 0000000..8777975
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/sdk/DefaultSdkProvider.java
@@ -0,0 +1,30 @@
+/*
+ * 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.sdk;
+
+import com.google.idea.blaze.base.model.primitives.LanguageClass;
+import com.intellij.openapi.extensions.ExtensionPointName;
+import java.io.File;
+import javax.annotation.Nullable;
+
+/** May download or otherwise provide default sdk locations for languages. */
+public interface DefaultSdkProvider {
+  ExtensionPointName<DefaultSdkProvider> EP_NAME =
+      ExtensionPointName.create("com.google.idea.blaze.DefaultSdkProvider");
+
+  @Nullable
+  File provideSdkForLanguage(LanguageClass language);
+}
diff --git a/base/src/com/google/idea/blaze/base/sync/status/BlazeSyncStatus.java b/base/src/com/google/idea/blaze/base/sync/status/BlazeSyncStatus.java
new file mode 100644
index 0000000..aa98c7e
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/status/BlazeSyncStatus.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.base.sync.status;
+
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.project.Project;
+
+/** Interface to tell blaze it might need to resync. */
+public interface BlazeSyncStatus {
+
+  /** The current sync status */
+  enum SyncStatus {
+    FAILED,
+    DIRTY,
+    CLEAN,
+  }
+
+  SyncStatus getStatus();
+
+  static BlazeSyncStatus getInstance(Project project) {
+    return ServiceManager.getService(project, BlazeSyncStatus.class);
+  }
+
+  void setDirty();
+
+  void queueAutomaticSyncIfDirty();
+}
diff --git a/base/src/com/google/idea/blaze/base/sync/status/BlazeSyncStatusImpl.java b/base/src/com/google/idea/blaze/base/sync/status/BlazeSyncStatusImpl.java
new file mode 100644
index 0000000..a6b062b
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/status/BlazeSyncStatusImpl.java
@@ -0,0 +1,208 @@
+/*
+ * 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.status;
+
+import com.google.idea.blaze.base.projectview.ProjectViewStorageManager;
+import com.google.idea.blaze.base.settings.BlazeUserSettings;
+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.google.idea.blaze.base.sync.SyncListener.SyncResult;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.fileEditor.FileEditorManager;
+import com.intellij.openapi.fileEditor.FileEditorManagerAdapter;
+import com.intellij.openapi.fileEditor.FileEditorManagerEvent;
+import com.intellij.openapi.fileEditor.FileEditorManagerListener;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileAdapter;
+import com.intellij.openapi.vfs.VirtualFileEvent;
+import com.intellij.openapi.vfs.VirtualFileManager;
+import com.intellij.openapi.vfs.VirtualFileMoveEvent;
+import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
+import java.util.Collection;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Per-project listener for changes to BUILD files, and other changes requiring an incremental sync.
+ */
+public class BlazeSyncStatusImpl implements BlazeSyncStatus {
+
+  public static BlazeSyncStatusImpl getImpl(@NotNull Project project) {
+    return (BlazeSyncStatusImpl) BlazeSyncStatus.getInstance(project);
+  }
+
+  private static Logger log = Logger.getInstance(BlazeSyncStatusImpl.class);
+
+  private final Project project;
+
+  public final AtomicBoolean syncInProgress = new AtomicBoolean(false);
+  private final AtomicBoolean syncPending = new AtomicBoolean(false);
+
+  /** has a BUILD file changed since the last sync started */
+  private volatile boolean dirty = false;
+
+  private volatile boolean failedSync = false;
+
+  public BlazeSyncStatusImpl(Project project) {
+    this.project = project;
+    // listen for changes to the VFS
+    VirtualFileManager.getInstance().addVirtualFileListener(new FileListener(), project);
+
+    // trigger VFS updates whenever navigating away from an unsaved BUILD file
+    project
+        .getMessageBus()
+        .connect()
+        .subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileFocusListener());
+  }
+
+  private static boolean automaticSyncEnabled() {
+    return BlazeUserSettings.getInstance().getResyncAutomatically();
+  }
+
+  @Override
+  public SyncStatus getStatus() {
+    if (failedSync) {
+      return SyncStatus.FAILED;
+    }
+    return dirty ? SyncStatus.DIRTY : SyncStatus.CLEAN;
+  }
+
+  public void syncStarted() {
+    syncPending.set(false);
+    syncInProgress.set(true);
+  }
+
+  public void syncEnded(SyncResult syncResult) {
+    syncInProgress.set(false);
+    failedSync = syncResult == SyncResult.FAILURE;
+    if (syncResult == SyncResult.SUCCESS && !syncPending.get()) {
+      dirty = false;
+    } else if (syncResult == SyncResult.PARTIAL_SUCCESS || syncResult == SyncResult.CANCELLED) {
+      dirty = true;
+    }
+  }
+
+  @Override
+  public void setDirty() {
+    dirty = true;
+    queueIncrementalSync();
+  }
+
+  @Override
+  public void queueAutomaticSyncIfDirty() {
+    if (dirty) {
+      queueIncrementalSync();
+    }
+  }
+
+  private void queueIncrementalSync() {
+    if (automaticSyncEnabled() && syncPending.compareAndSet(false, true)) {
+      log.info("Automatic sync started");
+      BlazeSyncManager.getInstance(project)
+          .requestProjectSync(
+              new BlazeSyncParams.Builder("Sync", SyncMode.INCREMENTAL)
+                  .setBackgroundSync(true)
+                  .addProjectViewTargets(true)
+                  .addWorkingSet(BlazeUserSettings.getInstance().getExpandSyncToWorkingSet())
+                  .build());
+    }
+  }
+
+  /**
+   * Listens for changes to files which impact the sync process (BUILD files and project view files)
+   */
+  private class FileListener extends VirtualFileAdapter {
+    @Override
+    public void fileCreated(@NotNull VirtualFileEvent event) {
+      processEvent(event);
+    }
+
+    @Override
+    public void fileDeleted(@NotNull VirtualFileEvent event) {
+      processEvent(event);
+      // we (sometimes) only get one event when a directory is deleted, so check the children too.
+      checkChildren(event.getFile());
+    }
+
+    @Override
+    public void fileMoved(@NotNull VirtualFileMoveEvent event) {
+      processEvent(event);
+    }
+
+    @Override
+    public void contentsChanged(@NotNull VirtualFileEvent event) {
+      processEvent(event);
+    }
+
+    private void processEvent(@NotNull VirtualFileEvent event) {
+      if (isSyncSensitiveFile(event.getFile())) {
+        setDirty();
+      }
+    }
+
+    private void checkChildren(VirtualFile file) {
+      if (!(file instanceof NewVirtualFile)) {
+        return;
+      }
+      Collection<VirtualFile> children = ((NewVirtualFile) file).getCachedChildren();
+      for (VirtualFile child : children) {
+        if (isSyncSensitiveFile(child)) {
+          setDirty();
+          return;
+        }
+      }
+    }
+  }
+
+  /**
+   * Listens for changes to files which impact the sync process (BUILD files and project view files)
+   */
+  private static class FileFocusListener extends FileEditorManagerAdapter {
+    @Override
+    public void fileClosed(@NotNull FileEditorManager source, @NotNull VirtualFile file) {
+      processEvent(file);
+    }
+
+    @Override
+    public void selectionChanged(@NotNull FileEditorManagerEvent event) {
+      processEvent(event.getOldFile());
+    }
+
+    private void processEvent(@Nullable VirtualFile file) {
+      if (isSyncSensitiveFile(file)) {
+        FileDocumentManager manager = FileDocumentManager.getInstance();
+        Document doc = manager.getCachedDocument(file);
+        if (doc != null) {
+          manager.saveDocument(doc);
+        }
+      }
+    }
+  }
+
+  private static boolean isSyncSensitiveFile(@Nullable VirtualFile file) {
+    return file != null
+        && (isBuildFile(file) || ProjectViewStorageManager.isProjectViewFile(file.getPath()));
+  }
+
+  private static boolean isBuildFile(VirtualFile file) {
+    return file.getName().equals("BUILD");
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/sync/status/BlazeSyncStatusListener.java b/base/src/com/google/idea/blaze/base/sync/status/BlazeSyncStatusListener.java
new file mode 100644
index 0000000..05ca578
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/status/BlazeSyncStatusListener.java
@@ -0,0 +1,36 @@
+/*
+ * 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.status;
+
+import com.google.idea.blaze.base.sync.SyncListener;
+import com.intellij.openapi.project.Project;
+
+/**
+ * Application-wide listener for blaze syncs. Notifies per-project status listener when they start
+ * and finish.
+ */
+public class BlazeSyncStatusListener extends SyncListener.Adapter {
+
+  @Override
+  public void onSyncStart(Project project) {
+    BlazeSyncStatusImpl.getImpl(project).syncStarted();
+  }
+
+  @Override
+  public void afterSync(Project project, SyncResult syncResult) {
+    BlazeSyncStatusImpl.getImpl(project).syncEnded(syncResult);
+  }
+}
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
new file mode 100644
index 0000000..13fce58
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/workspace/ArtifactLocationDecoder.java
@@ -0,0 +1,87 @@
+/*
+ * 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.workspace;
+
+import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
+import com.google.repackaged.devtools.build.lib.ideinfo.androidstudio.AndroidStudioIdeInfo;
+import com.google.repackaged.devtools.build.lib.ideinfo.androidstudio.PackageManifestOuterClass;
+import java.io.File;
+import javax.annotation.Nullable;
+
+/** Decodes android_studio_ide_info.proto ArtifactLocation file paths */
+public class ArtifactLocationDecoder {
+
+  private final BlazeRoots blazeRoots;
+  private final WorkspacePathResolver pathResolver;
+
+  public ArtifactLocationDecoder(BlazeRoots blazeRoots, WorkspacePathResolver pathResolver) {
+    this.blazeRoots = blazeRoots;
+    this.pathResolver = pathResolver;
+  }
+
+  /**
+   * Decodes the ArtifactLocation proto, locates the absolute artifact file path. Returns null if
+   * the file can't be found (presumably because it was removed since the blaze build)
+   */
+  @Nullable
+  public ArtifactLocation decode(AndroidStudioIdeInfo.ArtifactLocation loc) {
+    return decode(
+        loc.getRootExecutionPathFragment(),
+        loc.getRelativePath(),
+        loc.getIsSource());
+  }
+
+  /**
+   * Decodes the ArtifactLocation proto, locates the absolute artifact file path. Returns null if
+   * the file can't be found (presumably because it was removed since the blaze build)
+   */
+  @Nullable
+  public ArtifactLocation decode(PackageManifestOuterClass.ArtifactLocation loc) {
+    return decode(
+        loc.getRootExecutionPathFragment(),
+        loc.getRelativePath(),
+        loc.getIsSource());
+  }
+
+  @Nullable
+  private ArtifactLocation decode(
+      String rootExecutionPathFragment, String relativePath, boolean isSource) {
+    File root;
+    if (isSource) {
+      root = pathResolver.findPackageRoot(relativePath);
+    } else {
+      root = new File(blazeRoots.executionRoot, rootExecutionPathFragment);
+    }
+    if (root == null) {
+      return null;
+    }
+    return ArtifactLocation.builder()
+        .setRootPath(root.toString())
+        .setRootExecutionPathFragment(rootExecutionPathFragment)
+        .setRelativePath(relativePath)
+        .setIsSource(isSource)
+        .build();
+  }
+
+  @Deprecated
+  private String deriveRootExecutionPathFragmentFromRoot(String rootPath) {
+    String execRoot = blazeRoots.executionRoot.toString();
+    if (rootPath.startsWith(execRoot)) {
+      return rootPath.substring(execRoot.length());
+    }
+    return "";
+  }
+}
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
new file mode 100644
index 0000000..092fb48
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/workspace/BlazeRoots.java
@@ -0,0 +1,128 @@
+/*
+ * 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.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)));
+          }
+        });
+  }
+
+  private static String getOrThrow(
+      BuildSystem buildSystem, ImmutableMap<String, String> map, String key) {
+    String value = map.get(key);
+    if (value == null) {
+      throw new RuntimeException(
+          String.format("Could not locate %s in %s info", key, buildSystem.getLowerCaseName()));
+    }
+    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);
+    FileAttributeProvider fileAttributeProvider = FileAttributeProvider.getInstance();
+    for (String path : paths) {
+      File packagePath = new File(path.replace("%workspace%", workspaceRoot));
+      if (fileAttributeProvider.exists(packagePath)) {
+        packagePaths.add(packagePath);
+      }
+    }
+    return packagePaths;
+  }
+
+  public final File executionRoot;
+  public final List<File> packagePaths;
+  public final ExecutionRootPath blazeBinExecutionRootPath;
+  public final ExecutionRootPath blazeGenfilesExecutionRootPath;
+
+  @VisibleForTesting
+  public BlazeRoots(
+      File executionRoot,
+      List<File> packagePaths,
+      ExecutionRootPath blazeBinExecutionRootPath,
+      ExecutionRootPath blazeGenfilesExecutionRootPath) {
+    this.executionRoot = executionRoot;
+    this.packagePaths = packagePaths;
+    this.blazeBinExecutionRootPath = blazeBinExecutionRootPath;
+    this.blazeGenfilesExecutionRootPath = blazeGenfilesExecutionRootPath;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/sync/workspace/WorkingSet.java b/base/src/com/google/idea/blaze/base/sync/workspace/WorkingSet.java
new file mode 100644
index 0000000..614f5d3
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/workspace/WorkingSet.java
@@ -0,0 +1,42 @@
+/*
+ * 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.workspace;
+
+import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
+import java.io.Serializable;
+
+/** Computes the working set of files of directories from source control. */
+public class WorkingSet implements Serializable {
+  private static final long serialVersionUID = 2L;
+
+  public final ImmutableList<WorkspacePath> addedFiles;
+  public final ImmutableList<WorkspacePath> modifiedFiles;
+  public final ImmutableList<WorkspacePath> deletedFiles;
+
+  public WorkingSet(
+      ImmutableList<WorkspacePath> addedFiles,
+      ImmutableList<WorkspacePath> modifiedFiles,
+      ImmutableList<WorkspacePath> deletedFiles) {
+    this.addedFiles = addedFiles;
+    this.modifiedFiles = modifiedFiles;
+    this.deletedFiles = deletedFiles;
+  }
+
+  public boolean isEmpty() {
+    return addedFiles.isEmpty() && modifiedFiles.isEmpty() && deletedFiles.isEmpty();
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/sync/workspace/WorkspacePathResolver.java b/base/src/com/google/idea/blaze/base/sync/workspace/WorkspacePathResolver.java
new file mode 100644
index 0000000..d01975d
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/workspace/WorkspacePathResolver.java
@@ -0,0 +1,59 @@
+/*
+ * 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.workspace;
+
+import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.base.model.primitives.ExecutionRootPath;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
+import java.io.File;
+import java.io.Serializable;
+import javax.annotation.Nullable;
+
+/**
+ * Converts workspace-relative paths to absolute files with a minimum of file system calls
+ * (typically none).
+ */
+public interface WorkspacePathResolver extends Serializable {
+  /** Resolves a workspace path to an absolute file. */
+  @Nullable
+  default File resolveToFile(WorkspacePath workspacepath) {
+    return resolveToFile(workspacepath.relativePath());
+  }
+
+  /** Resolves a workspace relative path to an absolute file. */
+  @Nullable
+  default File resolveToFile(String workspaceRelativePath) {
+    File packageRoot = findPackageRoot(workspaceRelativePath);
+    return packageRoot != null ? new File(packageRoot, workspaceRelativePath) : null;
+  }
+
+  /**
+   * This method should be used for directories. Returns all workspace files corresponding to the
+   * given execution-root-relative path.
+   */
+  ImmutableList<File> resolveToIncludeDirectories(ExecutionRootPath executionRootPath);
+
+  /** Finds the package root directory that a workspace relative path is in. */
+  @Nullable
+  File findPackageRoot(String relativePath);
+
+  /**
+   * Given a resolved, absolute file, returns the corresponding {@link WorkspacePath}. Returns null
+   * if the file is not in the workspace.
+   */
+  @Nullable
+  WorkspacePath getWorkspacePath(File absoluteFile);
+}
diff --git a/base/src/com/google/idea/blaze/base/sync/workspace/WorkspacePathResolverImpl.java b/base/src/com/google/idea/blaze/base/sync/workspace/WorkspacePathResolverImpl.java
new file mode 100644
index 0000000..5721b91
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/workspace/WorkspacePathResolverImpl.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.sync.workspace;
+
+import com.google.common.collect.ImmutableList;
+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.WorkspacePath;
+import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
+import java.io.File;
+import java.util.List;
+import javax.annotation.Nullable;
+
+/** Uses the package path locations to resolve a workspace path. */
+public class WorkspacePathResolverImpl implements WorkspacePathResolver {
+  private static final long serialVersionUID = 2L;
+
+  private final WorkspaceRoot workspaceRoot;
+  private final List<File> packagePaths;
+
+  public WorkspacePathResolverImpl(WorkspaceRoot workspaceRoot, BlazeRoots blazeRoots) {
+    this(workspaceRoot, blazeRoots.packagePaths);
+  }
+
+  public WorkspacePathResolverImpl(WorkspaceRoot workspaceRoot) {
+    this(workspaceRoot, ImmutableList.of(workspaceRoot.directory()));
+  }
+
+  public WorkspacePathResolverImpl(WorkspaceRoot workspaceRoot, List<File> packagePaths) {
+    this.workspaceRoot = workspaceRoot;
+    this.packagePaths = packagePaths;
+  }
+
+  @Override
+  public ImmutableList<File> resolveToIncludeDirectories(ExecutionRootPath executionRootPath) {
+    File trackedLocation = executionRootPath.getFileRootedAt(workspaceRoot.directory());
+    return ImmutableList.of(trackedLocation);
+  }
+
+  @Override
+  @Nullable
+  public File findPackageRoot(String relativePath) {
+    if (packagePaths.size() == 1) {
+      return packagePaths.get(0);
+    }
+    // fall back to manually checking each one
+    FileAttributeProvider existenceChecker = FileAttributeProvider.getInstance();
+    for (File pkg : packagePaths) {
+      if (existenceChecker.exists(new File(pkg, relativePath))) {
+        return pkg;
+      }
+    }
+    return null;
+  }
+
+  @Nullable
+  @Override
+  public WorkspacePath getWorkspacePath(File absoluteFile) {
+    return workspaceRoot.workspacePathForSafe(absoluteFile);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/sync/workspace/WorkspacePathResolverProvider.java b/base/src/com/google/idea/blaze/base/sync/workspace/WorkspacePathResolverProvider.java
new file mode 100644
index 0000000..62f419d
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/workspace/WorkspacePathResolverProvider.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.workspace;
+
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.project.Project;
+import javax.annotation.Nullable;
+
+/** Provides a WorkspacePathResolver. */
+public interface WorkspacePathResolverProvider {
+
+  static WorkspacePathResolverProvider getInstance(Project project) {
+    return ServiceManager.getService(project, WorkspacePathResolverProvider.class);
+  }
+
+  /**
+   * Returns a WorkspacePathResolver for this project, or null if it's not a blaze/bazel project.
+   */
+  @Nullable
+  WorkspacePathResolver getPathResolver();
+}
diff --git a/base/src/com/google/idea/blaze/base/sync/workspace/WorkspacePathResolverProviderImpl.java b/base/src/com/google/idea/blaze/base/sync/workspace/WorkspacePathResolverProviderImpl.java
new file mode 100644
index 0000000..ff8941a
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/workspace/WorkspacePathResolverProviderImpl.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.sync.workspace;
+
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
+import com.intellij.openapi.project.Project;
+import javax.annotation.Nullable;
+
+/** Provides a WorkspacePathResolver. */
+public class WorkspacePathResolverProviderImpl implements WorkspacePathResolverProvider {
+
+  private final Project project;
+
+  public WorkspacePathResolverProviderImpl(Project project) {
+    this.project = project;
+  }
+
+  @Nullable
+  @Override
+  public WorkspacePathResolver getPathResolver() {
+    BlazeProjectData projectData =
+        BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
+    return projectData != null ? projectData.workspacePathResolver : null;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/treeview/BlazePsiDirectoryNode.java b/base/src/com/google/idea/blaze/base/treeview/BlazePsiDirectoryNode.java
new file mode 100644
index 0000000..755bb58
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/treeview/BlazePsiDirectoryNode.java
@@ -0,0 +1,58 @@
+/*
+ * 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.treeview;
+
+import com.intellij.ide.projectView.PresentationData;
+import com.intellij.ide.projectView.ViewSettings;
+import com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.ui.SimpleTextAttributes;
+import org.jetbrains.annotations.NotNull;
+
+/** A PsiDirectoryNode that doesn't render module names or source roots. */
+public class BlazePsiDirectoryNode extends PsiDirectoryNode {
+  public BlazePsiDirectoryNode(@NotNull PsiDirectoryNode original) {
+    this(original.getProject(), original.getValue(), original.getSettings());
+  }
+
+  public BlazePsiDirectoryNode(Project project, PsiDirectory directory, ViewSettings settings) {
+    super(project, directory, settings);
+  }
+
+  @Override
+  protected boolean shouldShowModuleName() {
+    return false;
+  }
+
+  @Override
+  protected boolean shouldShowSourcesRoot() {
+    return false;
+  }
+
+  @Override
+  protected void updateImpl(PresentationData data) {
+    super.updateImpl(data);
+    PsiDirectory psiDirectory = getValue();
+    assert psiDirectory != null;
+    String text = psiDirectory.getName();
+
+    data.setPresentableText(text);
+    data.clearText();
+    data.addText(text, SimpleTextAttributes.REGULAR_ATTRIBUTES);
+    data.setLocationString("");
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/treeview/BlazePsiDirectoryRootNode.java b/base/src/com/google/idea/blaze/base/treeview/BlazePsiDirectoryRootNode.java
new file mode 100644
index 0000000..4c061c3
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/treeview/BlazePsiDirectoryRootNode.java
@@ -0,0 +1,54 @@
+/*
+ * 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.treeview;
+
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
+import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
+import com.intellij.ide.projectView.PresentationData;
+import com.intellij.ide.projectView.ViewSettings;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.ui.SimpleTextAttributes;
+
+/**
+ * A PsiDirectoryNode that represents a directory root, rendering the whole directory name from the
+ * workspace root.
+ */
+public class BlazePsiDirectoryRootNode extends BlazePsiDirectoryNode {
+  public BlazePsiDirectoryRootNode(Project project, PsiDirectory directory, ViewSettings settings) {
+    super(project, directory, settings);
+  }
+
+  @Override
+  protected void updateImpl(PresentationData data) {
+    super.updateImpl(data);
+    WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProject(getProject());
+    PsiDirectory psiDirectory = getValue();
+    assert psiDirectory != null;
+    WorkspacePath workspacePath = workspaceRoot.workspacePathFor(psiDirectory.getVirtualFile());
+    String text = workspacePath.relativePath();
+
+    for (BlazePsiDirectoryRootNodeNameModifier modifier :
+        BlazePsiDirectoryRootNodeNameModifier.EP_NAME.getExtensions()) {
+      text = modifier.modifyRootNodeName(text);
+    }
+
+    data.setPresentableText(text);
+    data.clearText();
+    data.addText(text, SimpleTextAttributes.REGULAR_ATTRIBUTES);
+    data.setLocationString("");
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/treeview/BlazePsiDirectoryRootNodeNameModifier.java b/base/src/com/google/idea/blaze/base/treeview/BlazePsiDirectoryRootNodeNameModifier.java
new file mode 100644
index 0000000..e15b85f
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/treeview/BlazePsiDirectoryRootNodeNameModifier.java
@@ -0,0 +1,26 @@
+/*
+ * 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.treeview;
+
+import com.intellij.openapi.extensions.ExtensionPointName;
+
+/** Extension point to allow modifying the root node names. */
+public interface BlazePsiDirectoryRootNodeNameModifier {
+  ExtensionPointName<BlazePsiDirectoryRootNodeNameModifier> EP_NAME =
+      ExtensionPointName.create("com.google.idea.blaze.BlazePsiDirectoryRootNodeNameModifier");
+
+  String modifyRootNodeName(String name);
+}
diff --git a/base/src/com/google/idea/blaze/base/treeview/BlazeTreeStructureProvider.java b/base/src/com/google/idea/blaze/base/treeview/BlazeTreeStructureProvider.java
new file mode 100644
index 0000000..764e3b0
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/treeview/BlazeTreeStructureProvider.java
@@ -0,0 +1,154 @@
+/*
+ * 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.treeview;
+
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
+import com.google.idea.blaze.base.settings.Blaze;
+import com.google.idea.blaze.base.settings.BlazeImportSettings;
+import com.google.idea.blaze.base.settings.BlazeImportSettingsManager;
+import com.intellij.ide.projectView.ProjectViewSettings;
+import com.intellij.ide.projectView.TreeStructureProvider;
+import com.intellij.ide.projectView.ViewSettings;
+import com.intellij.ide.projectView.impl.nodes.ExternalLibrariesNode;
+import com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode;
+import com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode;
+import com.intellij.ide.util.treeView.AbstractTreeNode;
+import com.intellij.openapi.project.DumbAware;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiManager;
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Modifies the project view:
+ *
+ * <p>- Replaces the root with a single workspace root - Removes rendering of module names and
+ * source roots
+ */
+public class BlazeTreeStructureProvider implements TreeStructureProvider, DumbAware {
+  @NotNull
+  @Override
+  public Collection<AbstractTreeNode> modify(
+      @NotNull AbstractTreeNode parent,
+      @NotNull Collection<AbstractTreeNode> children,
+      ViewSettings settings) {
+    Project project = parent.getProject();
+    if (project == null || !Blaze.isBlazeProject(project)) {
+      return children;
+    }
+
+    if (parent instanceof ProjectViewProjectNode) {
+      WorkspaceRootNode rootNode = createRootNode(project, settings);
+      if (rootNode == null) {
+        return children;
+      }
+
+      Collection<AbstractTreeNode> result = Lists.newArrayList();
+      result.add(rootNode);
+      for (AbstractTreeNode treeNode : children) {
+        if (treeNode instanceof ExternalLibrariesNode) {
+          result.add(treeNode);
+        }
+      }
+      return result;
+    } else {
+      List<AbstractTreeNode> result = Lists.newArrayList();
+      for (AbstractTreeNode treeNode : children) {
+        if (treeNode.getClass().equals(PsiDirectoryNode.class)) {
+          result.add(new BlazePsiDirectoryNode((PsiDirectoryNode) treeNode));
+        } else {
+          result.add(treeNode);
+        }
+      }
+      return result;
+    }
+  }
+
+  @Nullable
+  private WorkspaceRootNode createRootNode(
+      @NotNull Project project, @NotNull ViewSettings settings) {
+    BlazeImportSettings importSettings =
+        BlazeImportSettingsManager.getInstance(project).getImportSettings();
+    if (importSettings != null) {
+      WorkspaceRoot workspaceRoot = WorkspaceRoot.fromImportSettings(importSettings);
+      File fdir = workspaceRoot.directory();
+      VirtualFile vdir = LocalFileSystem.getInstance().findFileByIoFile(fdir);
+      if (vdir != null) {
+        final PsiManager psiManager = PsiManager.getInstance(project);
+        PsiDirectory directory = psiManager.findDirectory(vdir);
+        return new WorkspaceRootNode(project, workspaceRoot, directory, wrapViewSettings(settings));
+      }
+    }
+    return null;
+  }
+
+  @Nullable
+  @Override
+  public Object getData(Collection<AbstractTreeNode> selected, String dataName) {
+    return null;
+  }
+
+  private ViewSettings wrapViewSettings(@NotNull final ViewSettings original) {
+    return new ProjectViewSettings() {
+      @Override
+      public boolean isShowMembers() {
+        return original.isShowMembers();
+      }
+
+      @Override
+      public boolean isStructureView() {
+        return original.isStructureView();
+      }
+
+      @Override
+      public boolean isShowModules() {
+        return original.isShowModules();
+      }
+
+      @Override
+      public boolean isFlattenPackages() {
+        return false;
+      }
+
+      @Override
+      public boolean isAbbreviatePackageNames() {
+        return original.isAbbreviatePackageNames();
+      }
+
+      @Override
+      public boolean isHideEmptyMiddlePackages() {
+        return false;
+      }
+
+      @Override
+      public boolean isShowLibraryContents() {
+        return original.isShowLibraryContents();
+      }
+
+      @Override
+      public boolean isShowExcludedFiles() {
+        return true;
+      }
+    };
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/treeview/WorkspaceRootNode.java b/base/src/com/google/idea/blaze/base/treeview/WorkspaceRootNode.java
new file mode 100644
index 0000000..d0c504a
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/treeview/WorkspaceRootNode.java
@@ -0,0 +1,117 @@
+/*
+ * 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.treeview;
+
+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.ProjectViewManager;
+import com.google.idea.blaze.base.projectview.ProjectViewSet;
+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;
+import com.intellij.ide.util.treeView.AbstractTreeNode;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiManager;
+import com.intellij.ui.SimpleTextAttributes;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Workspace root node.
+ *
+ * <p>
+ *
+ * <p>Customizes rendering of the workspace root node to cut out the full absolute path of the
+ * 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(
+      Project project, WorkspaceRoot workspaceRoot, PsiDirectory value, ViewSettings viewSettings) {
+    super(project, value, viewSettings);
+    this.workspaceRoot = workspaceRoot;
+  }
+
+  @Override
+  public Collection<AbstractTreeNode> getChildrenImpl() {
+    if (!COLLAPSE_PROJECT_VIEW.getValue()) {
+      return super.getChildrenImpl();
+    }
+    if (!BlazeUserSettings.getInstance().getCollapseProjectView()) {
+      return super.getChildrenImpl();
+    }
+    Project project = getProject();
+    if (project == null) {
+      return super.getChildrenImpl();
+    }
+    List<AbstractTreeNode> children = Lists.newArrayList();
+    ProjectViewSet projectViewSet = ProjectViewManager.getInstance(project).getProjectViewSet();
+    if (projectViewSet == null) {
+      return super.getChildrenImpl();
+    }
+
+    WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProject(project);
+    ImportRoots importRoots =
+        ImportRoots.builder(workspaceRoot, Blaze.getBuildSystem(project))
+            .add(projectViewSet)
+            .build();
+    if (importRoots.rootDirectories().stream().anyMatch(WorkspacePath::isWorkspaceRoot)) {
+      return super.getChildrenImpl();
+    }
+    for (WorkspacePath workspacePath : importRoots.rootDirectories()) {
+      VirtualFile virtualFile =
+          VfsUtil.findFileByIoFile(workspaceRoot.fileForPath(workspacePath), false);
+      if (virtualFile == null) {
+        continue;
+      }
+      PsiDirectory psiDirectory = PsiManager.getInstance(project).findDirectory(virtualFile);
+      if (psiDirectory == null) {
+        continue;
+      }
+      children.add(new BlazePsiDirectoryRootNode(project, psiDirectory, getSettings()));
+    }
+    if (children.isEmpty()) {
+      return super.getChildrenImpl();
+    }
+    return children;
+  }
+
+  @Override
+  protected void updateImpl(PresentationData data) {
+    super.updateImpl(data);
+    PsiDirectory psiDirectory = getValue();
+    assert psiDirectory != null;
+    String text = psiDirectory.getName();
+
+    data.setPresentableText(text);
+    data.clearText();
+    data.addText(text, SimpleTextAttributes.REGULAR_ATTRIBUTES);
+    data.setLocationString("");
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/ui/BlazeProblemsView.java b/base/src/com/google/idea/blaze/base/ui/BlazeProblemsView.java
new file mode 100644
index 0000000..53aefa6
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/ui/BlazeProblemsView.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.ui;
+
+import com.google.idea.blaze.base.scope.output.IssueOutput;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.project.Project;
+import java.util.UUID;
+import javax.annotation.Nullable;
+
+/** An interface to the IntelliJ problems view */
+public interface BlazeProblemsView {
+  @Nullable
+  static BlazeProblemsView getInstance(Project project) {
+    return ServiceManager.getService(project, BlazeProblemsView.class);
+  }
+
+  void clearOldMessages(UUID sessionId);
+
+  void addMessage(IssueOutput issue, UUID sessionId);
+}
diff --git a/base/src/com/google/idea/blaze/base/ui/BlazeValidationError.java b/base/src/com/google/idea/blaze/base/ui/BlazeValidationError.java
new file mode 100644
index 0000000..452ae82
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/ui/BlazeValidationError.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.ui;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.Messages;
+import java.util.Collection;
+import javax.annotation.concurrent.Immutable;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/** An error occuring during a blaze validation */
+@Immutable
+public final class BlazeValidationError {
+
+  @NotNull private final String error;
+
+  public BlazeValidationError(@NotNull String validationFailure) {
+    this.error = validationFailure;
+  }
+
+  @NotNull
+  public String getError() {
+    return error;
+  }
+
+  public static void collect(
+      @Nullable Collection<BlazeValidationError> errors, @NotNull BlazeValidationError error) {
+    if (errors != null) {
+      errors.add(error);
+    }
+  }
+
+  public static void throwError(@NotNull Collection<BlazeValidationError> errors)
+      throws IllegalArgumentException {
+    BlazeValidationError error = !errors.isEmpty() ? errors.iterator().next() : null;
+    String errorMessage = error != null ? error.getError() : "Unknown validation error";
+    throw new IllegalArgumentException(errorMessage);
+  }
+
+  /**
+   * Shows an error dialog.
+   *
+   * @return true if there are no errors
+   */
+  public static boolean verify(
+      @NotNull Project project,
+      @NotNull String title,
+      @NotNull Collection<BlazeValidationError> errors) {
+    if (!errors.isEmpty()) {
+      BlazeValidationError error = errors.iterator().next();
+      Messages.showErrorDialog(project, error.getError(), title);
+      return false;
+    }
+    return true;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/ui/BlazeValidationResult.java b/base/src/com/google/idea/blaze/base/ui/BlazeValidationResult.java
new file mode 100644
index 0000000..4200003
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/ui/BlazeValidationResult.java
@@ -0,0 +1,43 @@
+/*
+ * 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.ui;
+
+import org.jetbrains.annotations.Nullable;
+
+/** Pair of (success, validation error) */
+public class BlazeValidationResult {
+  public final boolean success;
+  @Nullable public final BlazeValidationError error;
+
+  private static final BlazeValidationResult SUCCESS = new BlazeValidationResult(true, null);
+
+  private BlazeValidationResult(boolean success, @Nullable BlazeValidationError error) {
+    this.success = success;
+    this.error = error;
+  }
+
+  public static BlazeValidationResult success() {
+    return SUCCESS;
+  }
+
+  public static BlazeValidationResult failure(BlazeValidationError error) {
+    return new BlazeValidationResult(false, error);
+  }
+
+  public static BlazeValidationResult failure(String error) {
+    return failure(new BlazeValidationError(error));
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/ui/ComboWrapper.java b/base/src/com/google/idea/blaze/base/ui/ComboWrapper.java
new file mode 100644
index 0000000..331cde0
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/ui/ComboWrapper.java
@@ -0,0 +1,64 @@
+/*
+ * 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.ui;
+
+import com.intellij.openapi.ui.ComboBox;
+import com.intellij.ui.ListCellRendererWrapper;
+import java.awt.event.ActionListener;
+import java.util.Collection;
+import javax.swing.DefaultComboBoxModel;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * A simple wrapper for IDEA's {@link ComboBox} class adding type safety for the methods we commonly
+ * use.
+ */
+public final class ComboWrapper<T> {
+  @NotNull private final ComboBox combo;
+
+  public static <T> ComboWrapper<T> create() {
+    return new ComboWrapper<T>();
+  }
+
+  private ComboWrapper() {
+    combo = new ComboBox();
+  }
+
+  public void setItems(@NotNull Collection<T> values) {
+    combo.setModel(new DefaultComboBoxModel(values.toArray()));
+  }
+
+  public void setSelectedItem(T value) {
+    combo.setSelectedItem(value);
+  }
+
+  public T getSelectedItem() {
+    return (T) combo.getSelectedItem();
+  }
+
+  public void addActionListener(@NotNull ActionListener listener) {
+    combo.addActionListener(listener);
+  }
+
+  public void setRenderer(ListCellRendererWrapper<T> renderer) {
+    combo.setRenderer(renderer);
+  }
+
+  @NotNull
+  public ComboBox getCombo() {
+    return combo;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/ui/FileSelectorWithStoredHistory.java b/base/src/com/google/idea/blaze/base/ui/FileSelectorWithStoredHistory.java
new file mode 100644
index 0000000..8636726
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/ui/FileSelectorWithStoredHistory.java
@@ -0,0 +1,66 @@
+/*
+ * 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.ui;
+
+import com.intellij.ide.util.BrowseFilesListener;
+import com.intellij.openapi.ui.ComponentWithBrowseButton;
+import com.intellij.openapi.ui.TextComponentAccessor;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.ui.TextFieldWithStoredHistory;
+import javax.annotation.Nullable;
+
+/** A file selector panel with text field, browse button and stored history. */
+public class FileSelectorWithStoredHistory
+    extends ComponentWithBrowseButton<TextFieldWithStoredHistory> {
+
+  public static FileSelectorWithStoredHistory create(String historyKey, String title) {
+    TextFieldWithStoredHistory textField = new TextFieldWithStoredHistory(historyKey);
+    return new FileSelectorWithStoredHistory(textField, title);
+  }
+
+  private FileSelectorWithStoredHistory(TextFieldWithStoredHistory textField, String title) {
+    super(textField, null);
+
+    addBrowseFolderListener(
+        title,
+        "",
+        null,
+        BrowseFilesListener.SINGLE_FILE_DESCRIPTOR,
+        TextComponentAccessor.TEXT_FIELD_WITH_STORED_HISTORY_WHOLE_TEXT);
+  }
+
+  /** Set the text without altering the history. */
+  public void setText(@Nullable String text) {
+    if (text == null) {
+      getChildComponent().reset();
+    } else {
+      getChildComponent().setText(text);
+    }
+  }
+
+  public void setTextWithHistory(@Nullable String text) {
+    setText(text);
+    if (text != null) {
+      getChildComponent().addCurrentTextToHistory();
+    }
+  }
+
+  @Nullable
+  public String getText() {
+    String text = getChildComponent().getText();
+    return StringUtil.nullize(text);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/ui/IntegerTextField.java b/base/src/com/google/idea/blaze/base/ui/IntegerTextField.java
new file mode 100644
index 0000000..8a6e108
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/ui/IntegerTextField.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.base.ui;
+
+import java.text.FieldPosition;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.text.ParsePosition;
+import javax.swing.JFormattedTextField;
+import javax.swing.text.NumberFormatter;
+
+/** Naive extension of JTextField, accepting integers or null. */
+public class IntegerTextField extends JFormattedTextField {
+
+  private static final NumberFormatter integerFormatter =
+      new NumberFormatter(new NullableNumberFormat(NumberFormat.getIntegerInstance()));
+
+  static {
+    integerFormatter.setValueClass(Integer.class);
+  }
+
+  private static class NullableNumberFormat extends NumberFormat {
+
+    private final NumberFormat base;
+
+    private NullableNumberFormat(NumberFormat base) {
+      this.base = base;
+    }
+
+    @Override
+    public Object parseObject(String source) throws ParseException {
+      if (source == null || source.trim().isEmpty()) {
+        return null;
+      }
+      return super.parseObject(source);
+    }
+
+    @Override
+    public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos) {
+      return base.format(number, toAppendTo, pos);
+    }
+
+    @Override
+    public StringBuffer format(long number, StringBuffer toAppendTo, FieldPosition pos) {
+      return base.format(number, toAppendTo, pos);
+    }
+
+    @Override
+    public Number parse(String source, ParsePosition parsePosition) {
+      return base.parse(source, parsePosition);
+    }
+  }
+
+  private int minValue = Integer.MIN_VALUE;
+  private int maxValue = Integer.MAX_VALUE;
+
+  public IntegerTextField() {
+    super(integerFormatter);
+  }
+
+  @Override
+  public void setValue(Object value) {
+    if (value == null) {
+      super.setValue(value);
+      return;
+    }
+    Integer integer;
+    try {
+      integer = Integer.parseInt(getFormatter().valueToString(value));
+
+    } catch (ParseException | NumberFormatException e) {
+      return; // retain existing value if invalid
+    }
+    super.setValue(integer < minValue ? minValue : integer > maxValue ? maxValue : integer);
+  }
+
+  public IntegerTextField setMinValue(int minValue) {
+    this.minValue = minValue;
+    return this;
+  }
+
+  public IntegerTextField setMaxValue(int maxValue) {
+    this.maxValue = maxValue;
+    return this;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/ui/UiUtil.java b/base/src/com/google/idea/blaze/base/ui/UiUtil.java
new file mode 100644
index 0000000..9f28ccb
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/ui/UiUtil.java
@@ -0,0 +1,102 @@
+/*
+ * 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.ui;
+
+import com.google.common.collect.Lists;
+import com.intellij.util.ui.GridBag;
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.Insets;
+import javax.swing.Box;
+import javax.swing.JComponent;
+import org.jetbrains.annotations.NotNull;
+
+/** A collection of UI utility methods. */
+public final class UiUtil {
+
+  public static final int INSETS = 7;
+
+  private UiUtil() {}
+
+  public static Box createBox(@NotNull Component... components) {
+    return createBox(Lists.newArrayList(components));
+  }
+
+  /** Puts all the given components in order in a box, aligned left. */
+  public static Box createBox(@NotNull Iterable<Component> components) {
+    Box box = Box.createVerticalBox();
+    box.setAlignmentX(0);
+    for (Component component : components) {
+      if (component instanceof JComponent) {
+        ((JComponent) component).setAlignmentX(0);
+      }
+      box.add(component);
+    }
+    return box;
+  }
+
+  /** Puts all the given components in order in a horizontal box. */
+  public static Box createHorizontalBox(int gap, @NotNull Component... components) {
+    return createHorizontalBox(gap, Lists.newArrayList(components));
+  }
+
+  public static Box createHorizontalBox(int gap, @NotNull Iterable<Component> components) {
+    Box box = Box.createHorizontalBox();
+    for (Component component : components) {
+      box.add(component);
+      box.add(Box.createRigidArea(new Dimension(gap, 0)));
+    }
+    return box;
+  }
+
+  @NotNull
+  public static GridBag getLabelConstraints(int indentLevel) {
+    Insets insets = new Insets(INSETS, INSETS + INSETS * indentLevel, 0, INSETS);
+    return new GridBag().anchor(GridBagConstraints.WEST).weightx(0).insets(insets);
+  }
+
+  @NotNull
+  public static GridBag getFillLineConstraints(int indentLevel) {
+    Insets insets = new Insets(INSETS, INSETS + INSETS * indentLevel, 0, INSETS);
+    return new GridBag()
+        .weightx(1)
+        .coverLine()
+        .fillCellHorizontally()
+        .anchor(GridBagConstraints.WEST)
+        .insets(insets);
+  }
+
+  public static void fillBottom(@NotNull JComponent component) {
+    component.add(
+        Box.createVerticalGlue(), new GridBag().weightx(1).weighty(1).fillCell().coverLine());
+  }
+
+  public static void setEnabledRecursive(Component component, boolean enabled) {
+    component.setEnabled(enabled);
+    if (component instanceof Container) {
+      for (Component child : ((Container) component).getComponents()) {
+        setEnabledRecursive(child, enabled);
+      }
+    }
+  }
+
+  public static void setPreferredWidth(JComponent component, int width) {
+    int height = component.getPreferredSize().height;
+    component.setPreferredSize(new Dimension(width, height));
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/util/BlazeHelperBinaryUtil.java b/base/src/com/google/idea/blaze/base/util/BlazeHelperBinaryUtil.java
new file mode 100644
index 0000000..d89004e
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/util/BlazeHelperBinaryUtil.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.util;
+
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.util.io.URLUtil;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
+import java.util.HashMap;
+import java.util.Map;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/** Extracts binaries from the resource section of the jar for execution */
+public final class BlazeHelperBinaryUtil {
+
+  private static final Logger LOG = Logger.getInstance(BlazeHelperBinaryUtil.class);
+
+  private static final File tempDirectory = com.google.common.io.Files.createTempDir();
+  private static final Map<String, File> cachedFiles = new HashMap<>();
+
+  @Nullable
+  public static synchronized File getBlazeHelperBinary(@NotNull String binaryName) {
+    File file = cachedFiles.get(binaryName);
+    if (file != null) {
+      return file;
+    }
+    file = new File(tempDirectory, binaryName);
+    File directory = file.getParentFile();
+
+    if (!directory.mkdirs()) {
+      LOG.error("Could not create temporary dir: " + directory);
+      return null;
+    }
+
+    URL url = BlazeHelperBinaryUtil.class.getResource(binaryName);
+    if (url == null) {
+      LOG.error(String.format("Blaze binary '%s' was not found", binaryName));
+      return null;
+    }
+    try (InputStream inputStream = URLUtil.openResourceStream(url)) {
+      Files.copy(inputStream, file.toPath(), StandardCopyOption.REPLACE_EXISTING);
+      file.setExecutable(true);
+      cachedFiles.put(binaryName, file);
+      return file;
+    } catch (IOException e) {
+      LOG.error(String.format("Error loading blaze binary '%s'", binaryName));
+      return null;
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/util/PackagePrefixCalculator.java b/base/src/com/google/idea/blaze/base/util/PackagePrefixCalculator.java
new file mode 100644
index 0000000..e923236
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/util/PackagePrefixCalculator.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.util;
+
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
+import org.jetbrains.annotations.NotNull;
+
+/** Calculates package prefix from workspace paths. */
+public final class PackagePrefixCalculator {
+
+  public static String packagePrefixOf(@NotNull WorkspacePath workspacePath) {
+    int skipIndex = 0;
+
+    skipIndex = skipIndex == 0 ? skip(workspacePath, "java/") : skipIndex;
+    skipIndex = skipIndex == 0 ? skip(workspacePath, "javatests/") : skipIndex;
+
+    return workspacePath.relativePath().substring(skipIndex).replace('/', '.');
+  }
+
+  private static int skip(@NotNull WorkspacePath workspacePath, @NotNull String skipString) {
+    if (workspacePath.relativePath().startsWith(skipString)) {
+      return skipString.length();
+    }
+    return 0;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/util/SaveUtil.java b/base/src/com/google/idea/blaze/base/util/SaveUtil.java
new file mode 100644
index 0000000..6454a7a
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/util/SaveUtil.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.base.util;
+
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.util.ui.UIUtil;
+
+/** Utility for saving all files. */
+public class SaveUtil {
+  public static void saveAllFiles() {
+    UIUtil.invokeAndWaitIfNeeded(
+        new Runnable() {
+          @Override
+          public void run() {
+            FileDocumentManager.getInstance().saveAllDocuments();
+          }
+        });
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/util/SerializationUtil.java b/base/src/com/google/idea/blaze/base/util/SerializationUtil.java
new file mode 100644
index 0000000..51f46d4
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/util/SerializationUtil.java
@@ -0,0 +1,100 @@
+/*
+ * 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.util;
+
+import com.google.common.io.Closeables;
+import com.intellij.CommonBundle;
+import com.intellij.openapi.diagnostic.Logger;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamClass;
+import java.io.Serializable;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/** Utils for serialization. */
+public class SerializationUtil {
+  private static final Logger LOG = Logger.getInstance(SerializationUtil.class.getName());
+
+  public static void saveToDisk(@NotNull File file, @NotNull Serializable serializable)
+      throws IOException {
+    ensureExists(file.getParentFile());
+    FileOutputStream fos = null;
+    try {
+      fos = new FileOutputStream(file);
+      ObjectOutputStream oos = new ObjectOutputStream(fos);
+      try {
+        oos.writeObject(serializable);
+      } finally {
+        Closeables.close(oos, false);
+      }
+    } finally {
+      Closeables.close(fos, false);
+    }
+  }
+
+  @Nullable
+  public static Object loadFromDisk(
+      @NotNull File file, @NotNull final Iterable<ClassLoader> classLoaders) throws IOException {
+    try {
+      FileInputStream fin = null;
+      try {
+        if (!file.exists()) {
+          return null;
+        }
+        fin = new FileInputStream(file);
+        ObjectInputStream ois =
+            new ObjectInputStream(fin) {
+              @Override
+              protected Class<?> resolveClass(ObjectStreamClass desc)
+                  throws IOException, ClassNotFoundException {
+                String name = desc.getName();
+                for (ClassLoader loader : classLoaders) {
+                  try {
+                    return Class.forName(name, false, loader);
+                  } catch (ClassNotFoundException e) {
+                    // Ignore - will throw eventually in super
+                  }
+                }
+                return super.resolveClass(desc);
+              }
+            };
+        try {
+          return (Object) ois.readObject();
+        } finally {
+          Closeables.close(ois, false);
+        }
+      } finally {
+        Closeables.close(fin, false);
+      }
+    } catch (ClassNotFoundException e) {
+      throw new IOException(e);
+    } catch (ClassCastException e) {
+      throw new IOException(e);
+    }
+  }
+
+  private static void ensureExists(@NotNull File dir) throws IOException {
+    if (!dir.exists() && !dir.mkdirs()) {
+      throw new IOException(
+          CommonBundle.message("exception.directory.can.not.create", dir.getPath()));
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/vcs/BlazeVcsHandler.java b/base/src/com/google/idea/blaze/base/vcs/BlazeVcsHandler.java
new file mode 100644
index 0000000..16e6909
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/vcs/BlazeVcsHandler.java
@@ -0,0 +1,75 @@
+/*
+ * 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.vcs;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+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.settings.Blaze.BuildSystem;
+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.intellij.openapi.extensions.ExtensionPointName;
+import com.intellij.openapi.project.Project;
+import javax.annotation.Nullable;
+
+/** Provides a diff against the version control system. */
+public interface BlazeVcsHandler {
+  ExtensionPointName<BlazeVcsHandler> EP_NAME =
+      ExtensionPointName.create("com.google.idea.blaze.VcsHandler");
+
+  /** Returns the name of this VCS, eg. "git" or "hg" */
+  String getVcsName();
+
+  /** Returns whether this vcs handler can manage this project */
+  boolean handlesProject(BuildSystem buildSystem, WorkspaceRoot workspaceRoot);
+
+  /** Returns the working set of modified files compared to some "upstream". */
+  ListenableFuture<WorkingSet> getWorkingSet(
+      Project project,
+      BlazeContext context,
+      WorkspaceRoot workspaceRoot,
+      ListeningExecutorService executor);
+
+  /** Optionally creates a sync handler to perform vcs-specific computation during sync. */
+  @Nullable
+  BlazeVcsSyncHandler createSyncHandler(Project project, WorkspaceRoot workspaceRoot);
+
+  /** Sync handler that performs VCS specific computation. */
+  interface BlazeVcsSyncHandler {
+    enum ValidationResult {
+      OK,
+      Error,
+      RestartSync, // The sync process needs restarting
+    }
+
+    /**
+     * Updates the vcs state of the project.
+     *
+     * @return True for OK, false to abort the sync process.
+     */
+    boolean update(BlazeContext context, BlazeRoots blazeRoots, ListeningExecutorService executor);
+
+    /** Returns a custom workspace path resolver for this vcs. */
+    @Nullable
+    WorkspacePathResolver getWorkspacePathResolver();
+
+    /** Validates the project view. Can cause sync to fail or restart. */
+    ValidationResult validateProjectView(BlazeContext context, ProjectViewSet projectViewSet);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/vcs/FallbackBlazeVcsHandler.java b/base/src/com/google/idea/blaze/base/vcs/FallbackBlazeVcsHandler.java
new file mode 100644
index 0000000..a975203
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/vcs/FallbackBlazeVcsHandler.java
@@ -0,0 +1,58 @@
+/*
+ * 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.vcs;
+
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+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.BuildSystem;
+import com.google.idea.blaze.base.sync.workspace.WorkingSet;
+import com.intellij.openapi.project.Project;
+import javax.annotation.Nullable;
+
+/**
+ * Used for bazel projects, when no other vcs handler can be found. Fallback to returning a null
+ * working set.
+ */
+public class FallbackBlazeVcsHandler implements BlazeVcsHandler {
+
+  @Override
+  public String getVcsName() {
+    return "Generic VCS Handler";
+  }
+
+  @Override
+  public boolean handlesProject(BuildSystem buildSystem, WorkspaceRoot workspaceRoot) {
+    return buildSystem == BuildSystem.Bazel;
+  }
+
+  @Override
+  public ListenableFuture<WorkingSet> getWorkingSet(
+      Project project,
+      BlazeContext context,
+      WorkspaceRoot workspaceRoot,
+      ListeningExecutorService executor) {
+    return Futures.immediateFuture(null);
+  }
+
+  @Nullable
+  @Override
+  public BlazeVcsSyncHandler createSyncHandler(Project project, WorkspaceRoot workspaceRoot) {
+    return null;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/vcs/VcsWorkspacePathResolver.java b/base/src/com/google/idea/blaze/base/vcs/VcsWorkspacePathResolver.java
new file mode 100644
index 0000000..2b5516c
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/vcs/VcsWorkspacePathResolver.java
@@ -0,0 +1,26 @@
+/*
+ * 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.vcs;
+
+import java.io.File;
+import javax.annotation.Nullable;
+
+/** Created by tomlu on 5/13/16. */
+public interface VcsWorkspacePathResolver {
+
+  @Nullable
+  File findPackageRoot(String relativePath);
+}
diff --git a/base/src/com/google/idea/blaze/base/vcs/git/GitBlazeVcsHandler.java b/base/src/com/google/idea/blaze/base/vcs/git/GitBlazeVcsHandler.java
new file mode 100644
index 0000000..e1ffb78
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/vcs/git/GitBlazeVcsHandler.java
@@ -0,0 +1,111 @@
+/*
+ * 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.vcs.git;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.idea.blaze.base.async.process.ExternalTask;
+import com.google.idea.blaze.base.io.FileAttributeProvider;
+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.BuildSystem;
+import com.google.idea.blaze.base.sync.workspace.WorkingSet;
+import com.google.idea.blaze.base.vcs.BlazeVcsHandler;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.text.StringUtil;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import javax.annotation.Nullable;
+
+/** Vcs diff provider for git */
+public class GitBlazeVcsHandler implements BlazeVcsHandler {
+
+  private static final Logger LOG = Logger.getInstance(GitBlazeVcsHandler.class);
+
+  @Override
+  public String getVcsName() {
+    return "git";
+  }
+
+  @Override
+  public boolean handlesProject(BuildSystem buildSystem, WorkspaceRoot workspaceRoot) {
+    return buildSystem == BuildSystem.Bazel
+        && isGitRepository(workspaceRoot)
+        && tracksRemote(workspaceRoot);
+  }
+
+  @Override
+  public ListenableFuture<WorkingSet> getWorkingSet(
+      Project project,
+      BlazeContext context,
+      WorkspaceRoot workspaceRoot,
+      ListeningExecutorService executor) {
+    return executor.submit(
+        () -> {
+          String upstreamSha = getUpstreamSha(workspaceRoot, false);
+          if (upstreamSha == null) {
+            return null;
+          }
+          return GitWorkingSetProvider.calculateWorkingSet(workspaceRoot, upstreamSha);
+        });
+  }
+
+  @Nullable
+  @Override
+  public BlazeVcsSyncHandler createSyncHandler(Project project, WorkspaceRoot workspaceRoot) {
+    return null;
+  }
+
+  private static boolean isGitRepository(WorkspaceRoot workspaceRoot) {
+    // TODO: What if the git repo root is a parent directory of the workspace root?
+    // Just call 'git rev-parse --is-inside-work-tree' or similar instead?
+    File gitDir = new File(workspaceRoot.directory(), ".git");
+    return FileAttributeProvider.getInstance().isDirectory(gitDir);
+  }
+
+  /**
+   * If we're not on a git branch which tracks a remote, we have no way of determining a WorkingSet.
+   */
+  private static boolean tracksRemote(WorkspaceRoot workspaceRoot) {
+    return getUpstreamSha(workspaceRoot, true) != null;
+  }
+
+  /**
+   * Returns the git commit SHA corresponding to the most recent commit in the current branch which
+   * matches a commit in the currently-tracked remote branch.
+   */
+  @Nullable
+  public static String getUpstreamSha(WorkspaceRoot workspaceRoot, boolean suppressErrors) {
+    ByteArrayOutputStream stdout = new ByteArrayOutputStream();
+    ByteArrayOutputStream stderr = new ByteArrayOutputStream();
+
+    int retVal =
+        ExternalTask.builder(workspaceRoot)
+            .args("git", "rev-parse", "@{u}")
+            .stdout(stdout)
+            .stderr(stderr)
+            .build()
+            .run();
+    if (retVal != 0) {
+      if (!suppressErrors) {
+        LOG.error(stderr);
+      }
+      return null;
+    }
+    return StringUtil.trimEnd(stdout.toString(), "\n");
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/vcs/git/GitStatusLineProcessor.java b/base/src/com/google/idea/blaze/base/vcs/git/GitStatusLineProcessor.java
new file mode 100644
index 0000000..8c4952b
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/vcs/git/GitStatusLineProcessor.java
@@ -0,0 +1,78 @@
+/*
+ * 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.vcs.git;
+
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.async.process.LineProcessingOutputStream;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
+import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
+import com.intellij.openapi.util.text.StringUtil;
+import java.io.File;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.annotation.Nullable;
+
+/** Finds file modifications from git status output */
+public class GitStatusLineProcessor implements LineProcessingOutputStream.LineProcessor {
+
+  private static final Pattern REGEX = Pattern.compile("^(A|M|D)\\s*(.*?)$");
+
+  private final WorkspaceRoot workspaceRoot;
+  private final String gitRoot;
+
+  public final List<WorkspacePath> addedFiles = Lists.newArrayList();
+  public final List<WorkspacePath> modifiedFiles = Lists.newArrayList();
+  public final List<WorkspacePath> deletedFiles = Lists.newArrayList();
+
+  public GitStatusLineProcessor(WorkspaceRoot workspaceRoot, String gitRoot) {
+    this.workspaceRoot = workspaceRoot;
+    this.gitRoot = gitRoot;
+  }
+
+  @Override
+  public boolean processLine(String line) {
+    Matcher matcher = REGEX.matcher(line);
+    if (matcher.find()) {
+      String type = matcher.group(1);
+      String file = matcher.group(2);
+      file = StringUtil.trimEnd(file, '/');
+
+      WorkspacePath workspacePath = getWorkspacePath(file);
+      if (workspacePath == null) {
+        return true;
+      }
+      switch (type) {
+        case "A":
+          addedFiles.add(workspacePath);
+          break;
+        case "M":
+          modifiedFiles.add(workspacePath);
+          break;
+        case "D":
+          deletedFiles.add(workspacePath);
+          break;
+      }
+    }
+    return true;
+  }
+
+  @Nullable
+  private WorkspacePath getWorkspacePath(String gitPath) {
+    File absoluteFile = new File(gitRoot, gitPath);
+    return workspaceRoot.workspacePathForSafe(absoluteFile);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/vcs/git/GitWorkingSetProvider.java b/base/src/com/google/idea/blaze/base/vcs/git/GitWorkingSetProvider.java
new file mode 100644
index 0000000..cfef8c0
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/vcs/git/GitWorkingSetProvider.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.base.vcs.git;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.base.async.process.ExternalTask;
+import com.google.idea.blaze.base.async.process.LineProcessingOutputStream;
+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;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.util.text.StringUtil;
+import java.io.ByteArrayOutputStream;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
+
+/** Vcs diff provider for git. */
+public class GitWorkingSetProvider {
+
+  private static final Logger LOG = Logger.getInstance(GitWorkingSetProvider.class);
+
+  /**
+   * Finds all changes between HEAD and the git commit specified by the provided SHA.<br>
+   * Returns null if an error occurred.
+   */
+  @Nullable
+  public static WorkingSet calculateWorkingSet(WorkspaceRoot workspaceRoot, String upstreamSha) {
+
+    String gitRoot = getConsoleOutput(workspaceRoot, "git", "rev-parse", "--show-toplevel");
+    if (gitRoot == null) {
+      return null;
+    }
+    GitStatusLineProcessor processor = new GitStatusLineProcessor(workspaceRoot, gitRoot);
+    ByteArrayOutputStream stderr = new ByteArrayOutputStream();
+
+    // Do a git diff to find all modified files we know about
+    int retVal =
+        ExternalTask.builder(workspaceRoot)
+            .args("git", "diff", "--name-status", "--no-renames", upstreamSha)
+            .stdout(LineProcessingOutputStream.of(processor))
+            .stderr(stderr)
+            .build()
+            .run();
+    if (retVal != 0) {
+      LOG.error(stderr);
+      return null;
+    }
+
+    // Finally list all untracked files, as they're not caught by the git diff step above
+    String untrackedFilesOutput =
+        getConsoleOutput(workspaceRoot, "git", "ls-files", "--others", "--exclude-standard");
+    if (untrackedFilesOutput == null) {
+      return null;
+    }
+
+    List<WorkspacePath> untrackedFiles =
+        Arrays.asList(untrackedFilesOutput.split("\n"))
+            .stream()
+            .filter(s -> !Strings.isNullOrEmpty(s))
+            .filter(WorkspacePath::validate)
+            .map(WorkspacePath::new)
+            .collect(Collectors.toList());
+
+    return new WorkingSet(
+        ImmutableList.<WorkspacePath>builder()
+            .addAll(processor.addedFiles)
+            .addAll(untrackedFiles)
+            .build(),
+        ImmutableList.copyOf(processor.modifiedFiles),
+        ImmutableList.copyOf(processor.deletedFiles));
+  }
+
+  /** @return the console output, in string form, or null if there was a non-zero exit code. */
+  @Nullable
+  private static String getConsoleOutput(WorkspaceRoot workspaceRoot, String... commands) {
+    ByteArrayOutputStream stdout = new ByteArrayOutputStream();
+    ByteArrayOutputStream stderr = new ByteArrayOutputStream();
+
+    int retVal =
+        ExternalTask.builder(workspaceRoot)
+            .args(commands)
+            .stdout(stdout)
+            .stderr(stderr)
+            .build()
+            .run();
+    if (retVal != 0) {
+      LOG.error(stderr);
+      return null;
+    }
+    return StringUtil.trimEnd(stdout.toString(), "\n");
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/wizard2/BazelWizardOptionProvider.java b/base/src/com/google/idea/blaze/base/wizard2/BazelWizardOptionProvider.java
new file mode 100644
index 0000000..9e62e78
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/wizard2/BazelWizardOptionProvider.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.wizard2;
+
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+
+/** Provides bazel options for the wizard. */
+public class BazelWizardOptionProvider implements BlazeWizardOptionProvider {
+
+  @Override
+  public Collection<BlazeSelectWorkspaceOption> getSelectWorkspaceOptions(
+      BlazeNewProjectBuilder builder) {
+    return ImmutableList.of(new UseExistingBazelWorkspaceOption(builder));
+  }
+
+  @Override
+  public Collection<BlazeSelectProjectViewOption> getSelectProjectViewOptions(
+      BlazeNewProjectBuilder builder) {
+    return ImmutableList.of(
+        new CreateFromScratchProjectViewOption(),
+        new ImportFromWorkspaceProjectViewOption(builder),
+        new GenerateFromBuildFileSelectProjectViewOption(builder),
+        new CopyExternalProjectViewOption(builder));
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/wizard2/BlazeNewProjectBuilder.java b/base/src/com/google/idea/blaze/base/wizard2/BlazeNewProjectBuilder.java
new file mode 100644
index 0000000..ad36704
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/wizard2/BlazeNewProjectBuilder.java
@@ -0,0 +1,202 @@
+/*
+ * 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.wizard2;
+
+import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
+import com.google.idea.blaze.base.plugin.dependency.PluginDependencyHelper;
+import com.google.idea.blaze.base.projectview.ProjectView;
+import com.google.idea.blaze.base.projectview.ProjectViewSet;
+import com.google.idea.blaze.base.projectview.ProjectViewStorageManager;
+import com.google.idea.blaze.base.projectview.parser.ProjectViewParser;
+import com.google.idea.blaze.base.settings.Blaze;
+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;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.text.StringUtil;
+import java.io.File;
+import java.io.IOException;
+import java.util.UUID;
+
+/** Contains the state to build a new project throughout the new project wizard process. */
+public final class BlazeNewProjectBuilder {
+  private static final Logger LOG = Logger.getInstance(BlazeNewProjectBuilder.class);
+
+  // Stored in user settings as the last imported workspace
+  private static final String LAST_IMPORTED_BLAZE_WORKSPACE =
+      "blaze-wizard.last-imported-workspace";
+  private static final String LAST_IMPORTED_BAZEL_WORKSPACE =
+      "blaze-wizard.last-imported-bazel-workspace";
+
+  public static String lastImportedWorkspaceKey(BuildSystem buildSystem) {
+    switch (buildSystem) {
+      case Blaze:
+        return LAST_IMPORTED_BLAZE_WORKSPACE;
+      case Bazel:
+        return LAST_IMPORTED_BAZEL_WORKSPACE;
+      default:
+        throw new RuntimeException("Unrecognized build system type: " + buildSystem);
+    }
+  }
+
+  private final BlazeWizardUserSettings userSettings;
+  private BlazeSelectWorkspaceOption workspaceOption;
+  private BlazeSelectProjectViewOption projectViewOption;
+  private File projectViewFile;
+  private ProjectView projectView;
+  private ProjectViewSet projectViewSet;
+  private String projectName;
+  private String projectDataDirectory;
+  private WorkspaceRoot workspaceRoot;
+  private BuildSystem buildSystem;
+
+  public BlazeNewProjectBuilder() {
+    this.userSettings = BlazeWizardUserSettingsStorage.getInstance().copyUserSettings();
+  }
+
+  public BlazeWizardUserSettings getUserSettings() {
+    return userSettings;
+  }
+
+  public BlazeSelectWorkspaceOption getWorkspaceOption() {
+    return workspaceOption;
+  }
+
+  public BlazeSelectProjectViewOption getProjectViewOption() {
+    return projectViewOption;
+  }
+
+  public String getProjectName() {
+    return projectName;
+  }
+
+  public ProjectView getProjectView() {
+    return projectView;
+  }
+
+  public ProjectViewSet getProjectViewSet() {
+    return projectViewSet;
+  }
+
+  public String getProjectDataDirectory() {
+    return projectDataDirectory;
+  }
+
+  public BuildSystem getBuildSystem() {
+    return buildSystem;
+  }
+
+  public String getBuildSystemName() {
+    if (buildSystem != null) {
+      return buildSystem.getName();
+    }
+    return Blaze.defaultBuildSystemName();
+  }
+
+  public BlazeNewProjectBuilder setWorkspaceOption(BlazeSelectWorkspaceOption workspaceOption) {
+    this.workspaceOption = workspaceOption;
+    this.buildSystem = workspaceOption.getBuildSystemForWorkspace();
+    return this;
+  }
+
+  public BlazeNewProjectBuilder setProjectViewOption(
+      BlazeSelectProjectViewOption projectViewOption) {
+    this.projectViewOption = projectViewOption;
+    return this;
+  }
+
+  public BlazeNewProjectBuilder setProjectView(ProjectView projectView) {
+    this.projectView = projectView;
+    return this;
+  }
+
+  public BlazeNewProjectBuilder setProjectViewFile(File projectViewFile) {
+    this.projectViewFile = projectViewFile;
+    return this;
+  }
+
+  public BlazeNewProjectBuilder setProjectViewSet(ProjectViewSet projectViewSet) {
+    this.projectViewSet = projectViewSet;
+    return this;
+  }
+
+  public BlazeNewProjectBuilder setProjectName(String projectName) {
+    this.projectName = projectName;
+    return this;
+  }
+
+  public BlazeNewProjectBuilder setProjectDataDirectory(String projectDataDirectory) {
+    this.projectDataDirectory = projectDataDirectory;
+    return this;
+  }
+
+  /** Commits the project. May report errors. */
+  public void commit() throws BlazeProjectCommitException {
+    this.workspaceRoot = workspaceOption.getWorkspaceRoot();
+
+    workspaceOption.commit();
+    projectViewOption.commit();
+
+    String workspaceKey = lastImportedWorkspaceKey(workspaceOption.getBuildSystemForWorkspace());
+    userSettings.put(workspaceKey, workspaceRoot.toString());
+
+    if (!StringUtil.isEmpty(projectDataDirectory)) {
+      File projectDataDir = new File(projectDataDirectory);
+      if (!projectDataDir.exists()) {
+        if (!projectDataDir.mkdirs()) {
+          throw new BlazeProjectCommitException(
+              "Unable to create the project directory: " + projectDataDirectory);
+        }
+      }
+    }
+
+    try {
+      LOG.assertTrue(projectViewFile != null);
+      ProjectViewStorageManager.getInstance()
+          .writeProjectView(ProjectViewParser.projectViewToString(projectView), projectViewFile);
+    } catch (IOException e) {
+      throw new BlazeProjectCommitException("Could not create project view file", e);
+    }
+  }
+
+  /**
+   * Commits the project data. This method mustn't fail, because the project has already been
+   * created.
+   */
+  public void commitToProject(Project project) {
+    BlazeWizardUserSettingsStorage.getInstance().commit(userSettings);
+
+    BlazeImportSettings importSettings =
+        new BlazeImportSettings(
+            workspaceRoot.directory().getPath(),
+            projectName,
+            projectDataDirectory,
+            createLocationHash(projectName),
+            projectViewFile.getPath(),
+            buildSystem);
+
+    BlazeImportSettingsManager.getInstance(project).setImportSettings(importSettings);
+    PluginDependencyHelper.addDependencyOnSyncPlugin(project);
+    // Initial sync of the project happens in BlazeSyncStartupActivity
+  }
+
+  private static String createLocationHash(String projectName) {
+    String uuid = UUID.randomUUID().toString();
+    uuid = uuid.substring(0, Math.min(uuid.length(), 8));
+    return projectName.replaceAll("[^a-zA-Z0-9]", "") + "-" + uuid;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/wizard2/BlazeProjectCommitException.java b/base/src/com/google/idea/blaze/base/wizard2/BlazeProjectCommitException.java
new file mode 100644
index 0000000..a7011e8
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/wizard2/BlazeProjectCommitException.java
@@ -0,0 +1,31 @@
+/*
+ * 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.wizard2;
+
+/** Throws during the commit stage of the new project wizard. */
+public class BlazeProjectCommitException extends Exception {
+  public BlazeProjectCommitException(String message) {
+    super(message);
+  }
+
+  public BlazeProjectCommitException(String message, Throwable cause) {
+    super(message, cause);
+  }
+
+  public BlazeProjectCommitException(Throwable cause) {
+    super(cause);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/wizard2/BlazeSelectProjectViewOption.java b/base/src/com/google/idea/blaze/base/wizard2/BlazeSelectProjectViewOption.java
new file mode 100644
index 0000000..348d657
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/wizard2/BlazeSelectProjectViewOption.java
@@ -0,0 +1,30 @@
+/*
+ * 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.wizard2;
+
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
+import javax.annotation.Nullable;
+
+/** Provides an option on the "Select .blazeproject" screen */
+public interface BlazeSelectProjectViewOption extends BlazeWizardOption {
+  @Nullable
+  WorkspacePath getSharedProjectView();
+
+  @Nullable
+  String getInitialProjectViewText();
+
+  void commit();
+}
diff --git a/base/src/com/google/idea/blaze/base/wizard2/BlazeSelectWorkspaceOption.java b/base/src/com/google/idea/blaze/base/wizard2/BlazeSelectWorkspaceOption.java
new file mode 100644
index 0000000..1c8654d
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/wizard2/BlazeSelectWorkspaceOption.java
@@ -0,0 +1,35 @@
+/*
+ * 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.wizard2;
+
+import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
+import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
+
+/** 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 the name of the workspace. Used to generate default project names. */
+  String getWorkspaceName();
+
+  BuildSystem getBuildSystemForWorkspace();
+
+  void commit() throws BlazeProjectCommitException;
+}
diff --git a/base/src/com/google/idea/blaze/base/wizard2/BlazeWizardOption.java b/base/src/com/google/idea/blaze/base/wizard2/BlazeWizardOption.java
new file mode 100644
index 0000000..775824f
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/wizard2/BlazeWizardOption.java
@@ -0,0 +1,48 @@
+/*
+ * 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.wizard2;
+
+import com.google.idea.blaze.base.ui.BlazeValidationResult;
+import com.google.idea.blaze.base.ui.UiUtil;
+import javax.annotation.Nullable;
+import javax.swing.JComponent;
+
+/** Base class for the workspace and project view options. */
+public interface BlazeWizardOption {
+  int VERTICAL_LAYOUT_GAP = 10;
+  int HORIZONTAL_LAYOUT_GAP = 10;
+  int PREFERRED_COMPONENT_WIDTH = 700;
+
+  /** @return A stable option name, used to remember which option was selected. */
+  String getOptionName();
+
+  /** @return the option text, eg "Create workspace from scratch" */
+  String getOptionText();
+
+  /** @return a ui component to be added below the corresponding radio button */
+  @Nullable
+  JComponent getUiComponent();
+
+  BlazeValidationResult validate();
+
+  default void optionSelected() {
+    UiUtil.setEnabledRecursive(getUiComponent(), true);
+  }
+
+  default void optionDeselected() {
+    UiUtil.setEnabledRecursive(getUiComponent(), false);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/wizard2/BlazeWizardOptionProvider.java b/base/src/com/google/idea/blaze/base/wizard2/BlazeWizardOptionProvider.java
new file mode 100644
index 0000000..20a7e4d
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/wizard2/BlazeWizardOptionProvider.java
@@ -0,0 +1,30 @@
+/*
+ * 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.wizard2;
+
+import com.intellij.openapi.extensions.ExtensionPointName;
+import java.util.Collection;
+
+/** Provides options during the import process. */
+public interface BlazeWizardOptionProvider {
+  ExtensionPointName<BlazeWizardOptionProvider> EP_NAME =
+      ExtensionPointName.create("com.google.idea.blaze.BlazeWizardOptionProvider");
+
+  Collection<BlazeSelectWorkspaceOption> getSelectWorkspaceOptions(BlazeNewProjectBuilder builder);
+
+  Collection<BlazeSelectProjectViewOption> getSelectProjectViewOptions(
+      BlazeNewProjectBuilder builder);
+}
diff --git a/base/src/com/google/idea/blaze/base/wizard2/BlazeWizardUserSettings.java b/base/src/com/google/idea/blaze/base/wizard2/BlazeWizardUserSettings.java
new file mode 100644
index 0000000..7d3ba41
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/wizard2/BlazeWizardUserSettings.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.wizard2;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.Maps;
+import com.intellij.util.xmlb.annotations.MapAnnotation;
+import com.intellij.util.xmlb.annotations.Tag;
+import java.util.Map;
+
+/**
+ * A bundle of settings that are stored between invocations of the wizard.
+ *
+ * <p>It's the user's responsibility to appropriately namespace the keys.
+ */
+public class BlazeWizardUserSettings {
+  Map<String, String> values = Maps.newHashMap();
+
+  public BlazeWizardUserSettings() {}
+
+  public BlazeWizardUserSettings(BlazeWizardUserSettings state) {
+    values.putAll(state.getValues());
+  }
+
+  public String get(String key, String defaultValue) {
+    return values.getOrDefault(key, defaultValue);
+  }
+
+  public void put(String key, String value) {
+    values.put(key, value);
+  }
+
+  @SuppressWarnings("unused")
+  @Tag("settings")
+  @MapAnnotation(surroundWithTag = false)
+  public Map<String, String> getValues() {
+    return values;
+  }
+
+  @SuppressWarnings("unused")
+  public void setValues(Map<String, String> values) {
+    this.values = values;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    BlazeWizardUserSettings that = (BlazeWizardUserSettings) o;
+    return Objects.equal(values, that.values);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(values);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/wizard2/BlazeWizardUserSettingsStorage.java b/base/src/com/google/idea/blaze/base/wizard2/BlazeWizardUserSettingsStorage.java
new file mode 100644
index 0000000..2184d14
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/wizard2/BlazeWizardUserSettingsStorage.java
@@ -0,0 +1,52 @@
+/*
+ * 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.wizard2;
+
+import com.intellij.openapi.components.PersistentStateComponent;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.components.State;
+import com.intellij.openapi.components.Storage;
+import org.jetbrains.annotations.Nullable;
+
+/** Stores wizard user settings between runs. */
+@State(name = "BlazeWizardUserSettings", storages = @Storage("blaze.wizard.settings.xml"))
+public class BlazeWizardUserSettingsStorage
+    implements PersistentStateComponent<BlazeWizardUserSettings> {
+  private BlazeWizardUserSettings state = new BlazeWizardUserSettings();
+
+  static BlazeWizardUserSettingsStorage getInstance() {
+    return ServiceManager.getService(BlazeWizardUserSettingsStorage.class);
+  }
+
+  @Nullable
+  @Override
+  public BlazeWizardUserSettings getState() {
+    return state;
+  }
+
+  @Override
+  public void loadState(BlazeWizardUserSettings state) {
+    this.state = state;
+  }
+
+  BlazeWizardUserSettings copyUserSettings() {
+    return new BlazeWizardUserSettings(state);
+  }
+
+  void commit(BlazeWizardUserSettings userSettings) {
+    this.state = userSettings;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/wizard2/CopyExternalProjectViewOption.java b/base/src/com/google/idea/blaze/base/wizard2/CopyExternalProjectViewOption.java
new file mode 100644
index 0000000..437f736
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/wizard2/CopyExternalProjectViewOption.java
@@ -0,0 +1,152 @@
+/*
+ * 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.wizard2;
+
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
+import com.google.idea.blaze.base.projectview.ProjectViewStorageManager;
+import com.google.idea.blaze.base.ui.BlazeValidationResult;
+import com.google.idea.blaze.base.ui.UiUtil;
+import com.intellij.openapi.fileChooser.FileChooserDescriptor;
+import com.intellij.openapi.fileChooser.FileChooserDialog;
+import com.intellij.openapi.fileChooser.FileChooserFactory;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import java.awt.Dimension;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import javax.annotation.Nullable;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JTextField;
+
+class CopyExternalProjectViewOption implements BlazeSelectProjectViewOption {
+  private static final String LAST_WORKSPACE_PATH = "copy-external.last-project-view-path";
+
+  final BlazeWizardUserSettings userSettings;
+  final JComponent component;
+  final JTextField projectViewPathField;
+
+  CopyExternalProjectViewOption(BlazeNewProjectBuilder builder) {
+    this.userSettings = builder.getUserSettings();
+
+    String defaultWorkspacePath = userSettings.get(LAST_WORKSPACE_PATH, "");
+    this.projectViewPathField = new JTextField(defaultWorkspacePath);
+
+    JButton button = new JButton("...");
+    button.addActionListener(action -> chooseWorkspacePath());
+    int buttonSize = projectViewPathField.getPreferredSize().height;
+    button.setPreferredSize(new Dimension(buttonSize, buttonSize));
+
+    JComponent box =
+        UiUtil.createHorizontalBox(
+            HORIZONTAL_LAYOUT_GAP, new JLabel("Project view:"), projectViewPathField, button);
+    UiUtil.setPreferredWidth(box, PREFERRED_COMPONENT_WIDTH);
+    this.component = box;
+  }
+
+  @Override
+  public String getOptionName() {
+    return "copy-external";
+  }
+
+  @Override
+  public String getOptionText() {
+    return "Copy external";
+  }
+
+  @Override
+  public JComponent getUiComponent() {
+    return component;
+  }
+
+  @Override
+  public BlazeValidationResult validate() {
+    if (getProjectViewPath().isEmpty()) {
+      return BlazeValidationResult.failure("Path to project view file cannot be empty.");
+    }
+    File file = new File(getProjectViewPath());
+    if (!file.exists()) {
+      return BlazeValidationResult.failure("Project view file does not exist.");
+    }
+    return BlazeValidationResult.success();
+  }
+
+  @Nullable
+  @Override
+  public WorkspacePath getSharedProjectView() {
+    return null;
+  }
+
+  @Nullable
+  @Override
+  public String getInitialProjectViewText() {
+    try {
+      byte[] bytes = Files.readAllBytes(Paths.get(getProjectViewPath()));
+      return new String(bytes, StandardCharsets.UTF_8);
+    } catch (IOException e) {
+      return null;
+    }
+  }
+
+  @Override
+  public void commit() {
+    userSettings.put(LAST_WORKSPACE_PATH, getProjectViewPath());
+  }
+
+  private String getProjectViewPath() {
+    return projectViewPathField.getText().trim();
+  }
+
+  private void chooseWorkspacePath() {
+    FileChooserDescriptor descriptor =
+        new FileChooserDescriptor(true, false, false, false, false, false)
+            .withShowHiddenFiles(true) // Show root project view file
+            .withHideIgnored(false)
+            .withTitle("Select Project View File")
+            .withDescription("Select a project view file to import.")
+            .withFileFilter(
+                virtualFile ->
+                    ProjectViewStorageManager.isProjectViewFile(new File(virtualFile.getPath())));
+    FileChooserDialog chooser =
+        FileChooserFactory.getInstance().createFileChooser(descriptor, null, null);
+
+    File startingLocation = null;
+    String projectViewPath = getProjectViewPath();
+    if (!projectViewPath.isEmpty()) {
+      File fileLocation = new File(projectViewPath);
+      if (fileLocation.exists()) {
+        startingLocation = fileLocation;
+      }
+    }
+    final VirtualFile[] files;
+    if (startingLocation != null) {
+      VirtualFile toSelect =
+          LocalFileSystem.getInstance().refreshAndFindFileByPath(startingLocation.getPath());
+      files = chooser.choose(null, toSelect);
+    } else {
+      files = chooser.choose(null);
+    }
+    if (files.length == 0) {
+      return;
+    }
+    VirtualFile file = files[0];
+    projectViewPathField.setText(file.getPath());
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/wizard2/CreateFromScratchProjectViewOption.java b/base/src/com/google/idea/blaze/base/wizard2/CreateFromScratchProjectViewOption.java
new file mode 100644
index 0000000..d886175
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/wizard2/CreateFromScratchProjectViewOption.java
@@ -0,0 +1,58 @@
+/*
+ * 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.wizard2;
+
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
+import com.google.idea.blaze.base.ui.BlazeValidationResult;
+import javax.annotation.Nullable;
+import javax.swing.JComponent;
+
+class CreateFromScratchProjectViewOption implements BlazeSelectProjectViewOption {
+  @Override
+  public String getOptionName() {
+    return "create-from-scratch";
+  }
+
+  @Override
+  public String getOptionText() {
+    return "Create from scratch";
+  }
+
+  @Override
+  public JComponent getUiComponent() {
+    return null;
+  }
+
+  @Nullable
+  @Override
+  public WorkspacePath getSharedProjectView() {
+    return null;
+  }
+
+  @Nullable
+  @Override
+  public String getInitialProjectViewText() {
+    return "";
+  }
+
+  @Override
+  public void commit() {}
+
+  @Override
+  public BlazeValidationResult validate() {
+    return BlazeValidationResult.success();
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/wizard2/GenerateFromBuildFileSelectProjectViewOption.java b/base/src/com/google/idea/blaze/base/wizard2/GenerateFromBuildFileSelectProjectViewOption.java
new file mode 100644
index 0000000..9826c2a
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/wizard2/GenerateFromBuildFileSelectProjectViewOption.java
@@ -0,0 +1,208 @@
+/*
+ * 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.wizard2;
+
+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;
+import com.google.idea.blaze.base.projectview.section.sections.DirectoryEntry;
+import com.google.idea.blaze.base.projectview.section.sections.DirectorySection;
+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.ui.BlazeValidationResult;
+import com.google.idea.blaze.base.ui.UiUtil;
+import com.intellij.openapi.fileChooser.FileChooserDescriptor;
+import com.intellij.openapi.fileChooser.FileChooserDialog;
+import com.intellij.openapi.fileChooser.FileChooserFactory;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import java.awt.Dimension;
+import java.io.File;
+import javax.annotation.Nullable;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JTextField;
+
+class GenerateFromBuildFileSelectProjectViewOption implements BlazeSelectProjectViewOption {
+  private static final String LAST_WORKSPACE_PATH = "generate-from-build-file.last-workspace-path";
+  private final BlazeNewProjectBuilder builder;
+  private final BlazeWizardUserSettings userSettings;
+  private final JTextField buildFilePathField;
+  private final JComponent component;
+
+  public GenerateFromBuildFileSelectProjectViewOption(BlazeNewProjectBuilder builder) {
+    this.builder = builder;
+    this.userSettings = builder.getUserSettings();
+
+    String defaultWorkspacePath = userSettings.get(LAST_WORKSPACE_PATH, "");
+    this.buildFilePathField = new JTextField(defaultWorkspacePath);
+
+    JButton button = new JButton("...");
+    button.addActionListener(action -> chooseWorkspacePath());
+    int buttonSize = buildFilePathField.getPreferredSize().height;
+    button.setPreferredSize(new Dimension(buttonSize, buttonSize));
+
+    JComponent box =
+        UiUtil.createHorizontalBox(
+            HORIZONTAL_LAYOUT_GAP, new JLabel("BUILD file:"), buildFilePathField, button);
+    UiUtil.setPreferredWidth(box, PREFERRED_COMPONENT_WIDTH);
+    this.component = box;
+  }
+
+  @Override
+  public String getOptionName() {
+    return "generate-from-build-file";
+  }
+
+  @Override
+  public String getOptionText() {
+    return "Generate from BUILD file";
+  }
+
+  @Override
+  public JComponent getUiComponent() {
+    return component;
+  }
+
+  @Override
+  public BlazeValidationResult validate() {
+    if (getBuildFilePath().isEmpty()) {
+      return BlazeValidationResult.failure("BUILD file field cannot be empty.");
+    }
+    WorkspaceRoot temporaryWorkspaceRoot = builder.getWorkspaceOption().getTemporaryWorkspaceRoot();
+    File file = temporaryWorkspaceRoot.fileForPath(new WorkspacePath(getBuildFilePath()));
+    if (!file.exists()) {
+      return BlazeValidationResult.failure("BUILD file does not exist.");
+    }
+
+    return BlazeValidationResult.success();
+  }
+
+  @Nullable
+  @Override
+  public WorkspacePath getSharedProjectView() {
+    return null;
+  }
+
+  @Nullable
+  @Override
+  public String getInitialProjectViewText() {
+    WorkspaceRoot temporaryWorkspaceRoot = builder.getWorkspaceOption().getTemporaryWorkspaceRoot();
+    WorkspacePath workspacePath = new WorkspacePath(getBuildFilePath());
+    return guessProjectViewFromLocation(
+        temporaryWorkspaceRoot,
+        temporaryWorkspaceRoot.workspacePathFor(
+            temporaryWorkspaceRoot.fileForPath(workspacePath).getParentFile()));
+  }
+
+  @Override
+  public void commit() {
+    userSettings.put(LAST_WORKSPACE_PATH, getBuildFilePath());
+  }
+
+  private static String guessProjectViewFromLocation(
+      WorkspaceRoot workspaceRoot, WorkspacePath workspacePath) {
+
+    WorkspacePath mainModuleWorkspaceRelativePath = workspacePath;
+    WorkspacePath testModuleWorkspaceRelativePath =
+        guessTestRelativePath(workspaceRoot, mainModuleWorkspaceRelativePath);
+
+    ListSection.Builder<DirectoryEntry> directorySectionBuilder =
+        ListSection.builder(DirectorySection.KEY);
+    directorySectionBuilder.add(DirectoryEntry.include(mainModuleWorkspaceRelativePath));
+    if (testModuleWorkspaceRelativePath != null) {
+      directorySectionBuilder.add(DirectoryEntry.include(testModuleWorkspaceRelativePath));
+    }
+
+    ListSection.Builder<TargetExpression> targetSectionBuilder =
+        ListSection.builder(TargetSection.KEY);
+    targetSectionBuilder.add(
+        TargetExpression.allFromPackageRecursive(mainModuleWorkspaceRelativePath));
+    if (testModuleWorkspaceRelativePath != null) {
+      targetSectionBuilder.add(
+          TargetExpression.allFromPackageRecursive(testModuleWorkspaceRelativePath));
+    }
+
+    return ProjectViewParser.projectViewToString(
+        ProjectView.builder()
+            .add(directorySectionBuilder)
+            .add(TextBlockSection.of(TextBlock.newLine()))
+            .add(targetSectionBuilder)
+            .build());
+  }
+
+  @Nullable
+  private static WorkspacePath guessTestRelativePath(
+      WorkspaceRoot workspaceRoot, WorkspacePath projectWorkspacePath) {
+    String projectRelativePath = projectWorkspacePath.relativePath();
+    String testBuildFileRelativePath = null;
+    if (projectRelativePath.startsWith("java/")) {
+      testBuildFileRelativePath = projectRelativePath.replaceFirst("java/", "javatests/");
+    } else if (projectRelativePath.contains("/java/")) {
+      testBuildFileRelativePath = projectRelativePath.replaceFirst("/java/", "/javatests/");
+    }
+    if (testBuildFileRelativePath != null) {
+      File testBuildFile = workspaceRoot.fileForPath(new WorkspacePath(testBuildFileRelativePath));
+      if (testBuildFile.exists()) {
+        return new WorkspacePath(testBuildFileRelativePath);
+      }
+    }
+    return null;
+  }
+
+  private String getBuildFilePath() {
+    return buildFilePathField.getText().trim();
+  }
+
+  private void chooseWorkspacePath() {
+    FileChooserDescriptor descriptor =
+        new FileChooserDescriptor(true, false, false, false, false, false)
+            .withShowHiddenFiles(true) // Show root project view file
+            .withHideIgnored(false)
+            .withTitle("Select BUILD File")
+            .withDescription("Select a BUILD file to synthesize a project view from.")
+            .withFileFilter(virtualFile -> virtualFile.getName().equals("BUILD"));
+    FileChooserDialog chooser =
+        FileChooserFactory.getInstance().createFileChooser(descriptor, null, null);
+
+    WorkspaceRoot temporaryWorkspaceRoot = builder.getWorkspaceOption().getTemporaryWorkspaceRoot();
+
+    File startingLocation = temporaryWorkspaceRoot.directory();
+    String buildFilePath = getBuildFilePath();
+    if (!buildFilePath.isEmpty()) {
+      File fileLocation = temporaryWorkspaceRoot.fileForPath(new WorkspacePath(buildFilePath));
+      if (fileLocation.exists()) {
+        startingLocation = fileLocation;
+      }
+    }
+    VirtualFile toSelect =
+        LocalFileSystem.getInstance().refreshAndFindFileByPath(startingLocation.getPath());
+    VirtualFile[] files = chooser.choose(null, toSelect);
+    if (files.length == 0) {
+      return;
+    }
+    VirtualFile file = files[0];
+    String newWorkspacePath =
+        FileUtil.getRelativePath(temporaryWorkspaceRoot.directory(), 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
new file mode 100644
index 0000000..1162d05
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/wizard2/ImportFromWorkspaceProjectViewOption.java
@@ -0,0 +1,164 @@
+/*
+ * 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.wizard2;
+
+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.ui.BlazeValidationResult;
+import com.google.idea.blaze.base.ui.UiUtil;
+import com.intellij.openapi.fileChooser.FileChooserDescriptor;
+import com.intellij.openapi.fileChooser.FileChooserDialog;
+import com.intellij.openapi.fileChooser.FileChooserFactory;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import java.awt.Dimension;
+import java.io.File;
+import javax.annotation.Nullable;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JTextField;
+
+class ImportFromWorkspaceProjectViewOption implements BlazeSelectProjectViewOption {
+  private static final String LAST_WORKSPACE_PATH = "import-from-workspace.last-workspace-path";
+
+  final BlazeNewProjectBuilder builder;
+  final BlazeWizardUserSettings userSettings;
+  final JComponent component;
+  final JTextField projectViewPathField;
+
+  ImportFromWorkspaceProjectViewOption(BlazeNewProjectBuilder builder) {
+    this.builder = builder;
+    this.userSettings = builder.getUserSettings();
+
+    String defaultWorkspacePath = userSettings.get(LAST_WORKSPACE_PATH, "");
+    this.projectViewPathField = new JTextField(defaultWorkspacePath);
+
+    JButton button = new JButton("...");
+    button.addActionListener(action -> chooseWorkspacePath());
+    int buttonSize = projectViewPathField.getPreferredSize().height;
+    button.setPreferredSize(new Dimension(buttonSize, buttonSize));
+
+    JComponent box =
+        UiUtil.createHorizontalBox(
+            HORIZONTAL_LAYOUT_GAP, new JLabel("Project view:"), projectViewPathField, button);
+    UiUtil.setPreferredWidth(box, PREFERRED_COMPONENT_WIDTH);
+    this.component = box;
+  }
+
+  @Override
+  public String getOptionName() {
+    return "import-from-workspace";
+  }
+
+  @Override
+  public String getOptionText() {
+    return "Import from workspace";
+  }
+
+  @Override
+  public JComponent getUiComponent() {
+    return component;
+  }
+
+  @Override
+  public BlazeValidationResult validate() {
+    if (getProjectViewPath().isEmpty()) {
+      return BlazeValidationResult.failure("Workspace path to project view file cannot be empty.");
+    }
+    WorkspaceRoot temporaryWorkspaceRoot = builder.getWorkspaceOption().getTemporaryWorkspaceRoot();
+    File file = temporaryWorkspaceRoot.fileForPath(getSharedProjectView());
+    if (!file.exists()) {
+      return BlazeValidationResult.failure("Project view file does not exist.");
+    }
+
+    return BlazeValidationResult.success();
+  }
+
+  @Nullable
+  @Override
+  public WorkspacePath getSharedProjectView() {
+    return new WorkspacePath(getProjectViewPath());
+  }
+
+  @Nullable
+  @Override
+  public String getInitialProjectViewText() {
+    return null;
+  }
+
+  @Override
+  public void commit() {
+    userSettings.put(LAST_WORKSPACE_PATH, getProjectViewPath());
+  }
+
+  private String getProjectViewPath() {
+    return projectViewPathField.getText().trim();
+  }
+
+  private void chooseWorkspacePath() {
+    FileChooserDescriptor descriptor =
+        new FileChooserDescriptor(true, false, false, false, false, false)
+            .withShowHiddenFiles(true) // Show root project view file
+            .withHideIgnored(false)
+            .withTitle("Select Project View File")
+            .withDescription("Select a project view file to import.")
+            .withFileFilter(
+                virtualFile ->
+                    ProjectViewStorageManager.isProjectViewFile(new File(virtualFile.getPath())));
+    FileChooserDialog chooser =
+        FileChooserFactory.getInstance().createFileChooser(descriptor, null, null);
+
+    WorkspaceRoot temporaryWorkspaceRoot = builder.getWorkspaceOption().getTemporaryWorkspaceRoot();
+
+    File startingLocation = temporaryWorkspaceRoot.directory();
+    String projectViewPath = getProjectViewPath();
+    if (!projectViewPath.isEmpty()) {
+      // Avoid exception -- workspace paths cannot end with trailing slash
+      projectViewPath = StringUtil.trimEnd(projectViewPath, '/');
+
+      File fileLocation = temporaryWorkspaceRoot.fileForPath(new WorkspacePath(projectViewPath));
+      if (fileLocation.exists()) {
+        startingLocation = fileLocation;
+      }
+    }
+    VirtualFile toSelect =
+        LocalFileSystem.getInstance().refreshAndFindFileByPath(startingLocation.getPath());
+    VirtualFile[] files = chooser.choose(null, toSelect);
+    if (files.length == 0) {
+      return;
+    }
+    VirtualFile file = files[0];
+
+    if (!FileUtil.isAncestor(temporaryWorkspaceRoot.directory().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()),
+          "Cannot Use Project View File");
+      return;
+    }
+
+    String newWorkspacePath =
+        FileUtil.getRelativePath(temporaryWorkspaceRoot.directory(), new File(file.getPath()));
+    projectViewPathField.setText(newWorkspacePath);
+  }
+}
diff --git a/blaze-base/src/com/google/idea/blaze/base/wizard2/README b/base/src/com/google/idea/blaze/base/wizard2/README
similarity index 100%
rename from blaze-base/src/com/google/idea/blaze/base/wizard2/README
rename to base/src/com/google/idea/blaze/base/wizard2/README
diff --git a/base/src/com/google/idea/blaze/base/wizard2/UseExistingBazelWorkspaceOption.java b/base/src/com/google/idea/blaze/base/wizard2/UseExistingBazelWorkspaceOption.java
new file mode 100644
index 0000000..c810f64
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/wizard2/UseExistingBazelWorkspaceOption.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.wizard2;
+
+import com.google.idea.blaze.base.bazel.BuildSystemProvider;
+import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
+import com.google.idea.blaze.base.ui.BlazeValidationResult;
+import icons.BlazeIcons;
+import java.io.File;
+import javax.swing.Icon;
+
+class UseExistingBazelWorkspaceOption extends UseExistingWorkspaceOption {
+
+  UseExistingBazelWorkspaceOption(BlazeNewProjectBuilder builder) {
+    super(builder, BuildSystem.Bazel);
+  }
+
+  @Override
+  protected boolean isWorkspaceRoot(File file) {
+    return BuildSystemProvider.getWorkspaceRootProvider(BuildSystem.Bazel).isWorkspaceRoot(file);
+  }
+
+  @Override
+  protected BlazeValidationResult validateWorkspaceRoot(File workspaceRoot) {
+    if (!isWorkspaceRoot(workspaceRoot)) {
+      return BlazeValidationResult.failure(
+          "Invalid workspace root: choose a bazel workspace directory "
+              + "(containing a WORKSPACE file)");
+    }
+    return BlazeValidationResult.success();
+  }
+
+  @Override
+  public String getOptionName() {
+    return "use-existing-bazel-workspace";
+  }
+
+  @Override
+  public String getOptionText() {
+    return "Use existing bazel workspace";
+  }
+
+  @Override
+  protected String getWorkspaceName(File workspaceRoot) {
+    return workspaceRoot.getName();
+  }
+
+  @Override
+  protected String fileChooserDescription() {
+    return "Select the directory of the workspace you want to use.";
+  }
+
+  @Override
+  protected Icon getBuildSystemIcon() {
+    return BlazeIcons.BazelLeaf;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/wizard2/UseExistingWorkspaceOption.java b/base/src/com/google/idea/blaze/base/wizard2/UseExistingWorkspaceOption.java
new file mode 100644
index 0000000..9d18f4e
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/wizard2/UseExistingWorkspaceOption.java
@@ -0,0 +1,209 @@
+/*
+ * 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.wizard2;
+
+import com.google.common.base.Joiner;
+import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
+import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
+import com.google.idea.blaze.base.ui.BlazeValidationResult;
+import com.google.idea.blaze.base.ui.UiUtil;
+import com.google.idea.blaze.base.vcs.BlazeVcsHandler;
+import com.intellij.icons.AllIcons;
+import com.intellij.openapi.fileChooser.FileChooserDescriptor;
+import com.intellij.openapi.fileChooser.FileChooserDialog;
+import com.intellij.openapi.fileChooser.FileChooserFactory;
+import com.intellij.openapi.util.IconLoader;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import java.awt.Dimension;
+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;
+import javax.swing.JLabel;
+import javax.swing.JTextField;
+
+/** Option to use an existing workspace */
+public abstract class UseExistingWorkspaceOption implements BlazeSelectWorkspaceOption {
+
+  private final BlazeWizardUserSettings userSettings;
+  private final JComponent component;
+  private final JTextField directoryField;
+  private final BuildSystem buildSystem;
+
+  protected UseExistingWorkspaceOption(BlazeNewProjectBuilder builder, BuildSystem buildSystem) {
+    this.userSettings = builder.getUserSettings();
+    this.buildSystem = buildSystem;
+
+    String defaultDirectory =
+        userSettings.get(BlazeNewProjectBuilder.lastImportedWorkspaceKey(buildSystem), "");
+
+    this.directoryField = new JTextField(defaultDirectory);
+
+    JButton button = new JButton("...");
+    button.addActionListener(action -> chooseDirectory());
+    int buttonSize = directoryField.getPreferredSize().height;
+    button.setPreferredSize(new Dimension(buttonSize, buttonSize));
+
+    JComponent box =
+        UiUtil.createHorizontalBox(
+            HORIZONTAL_LAYOUT_GAP,
+            getIconComponent(),
+            new JLabel("Workspace:"),
+            directoryField,
+            button);
+    UiUtil.setPreferredWidth(box, PREFERRED_COMPONENT_WIDTH);
+    this.component = box;
+  }
+
+  protected abstract boolean isWorkspaceRoot(File file);
+
+  protected abstract BlazeValidationResult validateWorkspaceRoot(File workspaceRoot);
+
+  private boolean isWorkspaceRoot(VirtualFile file) {
+    return isWorkspaceRoot(new File(file.getPath()));
+  }
+
+  protected abstract String fileChooserDescription();
+
+  protected abstract Icon getBuildSystemIcon();
+
+  protected abstract String getWorkspaceName(File workspaceRoot);
+
+  @Override
+  public BuildSystem getBuildSystemForWorkspace() {
+    return buildSystem;
+  }
+
+  @Override
+  public JComponent getUiComponent() {
+    return component;
+  }
+
+  @Override
+  public void commit() throws BlazeProjectCommitException {}
+
+  @Override
+  public WorkspaceRoot getWorkspaceRoot() {
+    return new WorkspaceRoot(new File(getDirectory()));
+  }
+
+  @Nullable
+  @Override
+  public WorkspaceRoot getTemporaryWorkspaceRoot() {
+    return getWorkspaceRoot();
+  }
+
+  @Override
+  public String getWorkspaceName() {
+    File workspaceRoot = new File(getDirectory());
+    return getWorkspaceName(workspaceRoot);
+  }
+
+  @Override
+  public BlazeValidationResult validate() {
+    if (getDirectory().isEmpty()) {
+      return BlazeValidationResult.failure("Please select a workspace");
+    }
+
+    File workspaceRootFile = new File(getDirectory());
+    if (!workspaceRootFile.exists()) {
+      return BlazeValidationResult.failure("Workspace does not exist");
+    }
+
+    WorkspaceRoot workspaceRoot = new WorkspaceRoot(workspaceRootFile);
+    boolean hasVcsHandler =
+        Arrays.stream(BlazeVcsHandler.EP_NAME.getExtensions())
+            .anyMatch(vcsHandler -> vcsHandler.handlesProject(buildSystem, workspaceRoot));
+    if (!hasVcsHandler) {
+      StringBuilder sb = new StringBuilder();
+      sb.append("Workspace is not of a supported VCS type. ");
+      sb.append("VCS types considered were: ");
+      Joiner.on(", ")
+          .appendTo(
+              sb,
+              Arrays.stream(BlazeVcsHandler.EP_NAME.getExtensions())
+                  .map(BlazeVcsHandler::getVcsName)
+                  .collect(Collectors.toList()));
+      return BlazeValidationResult.failure(sb.toString());
+    }
+
+    return validateWorkspaceRoot(workspaceRootFile);
+  }
+
+  private String getDirectory() {
+    return directoryField.getText().trim();
+  }
+
+  private void chooseDirectory() {
+    FileChooserDescriptor descriptor =
+        new FileChooserDescriptor(false, true, false, false, false, false) {
+          @Override
+          public boolean isFileSelectable(VirtualFile file) {
+            // Default implementation doesn't filter directories,
+            // we want to make sure only workspace roots are selectable
+            return super.isFileSelectable(file) && isWorkspaceRoot(file);
+          }
+
+          @Override
+          public Icon getIcon(VirtualFile file) {
+            if (buildSystem == BuildSystem.Bazel) {
+              // isWorkspaceRoot requires file system calls -- it's too expensive
+              return super.getIcon(file);
+            }
+            if (isWorkspaceRoot(file)) {
+              return AllIcons.Nodes.SourceFolder;
+            }
+            return super.getIcon(file);
+          }
+        }.withHideIgnored(false)
+            .withTitle("Select Workspace Root")
+            .withDescription(fileChooserDescription())
+            .withFileFilter(this::isWorkspaceRoot);
+    FileChooserDialog chooser =
+        FileChooserFactory.getInstance().createFileChooser(descriptor, null, null);
+
+    final VirtualFile[] files;
+    File existingLocation = new File(getDirectory());
+    if (existingLocation.exists()) {
+      VirtualFile toSelect =
+          LocalFileSystem.getInstance().refreshAndFindFileByPath(existingLocation.getPath());
+      files = chooser.choose(null, toSelect);
+    } else {
+      files = chooser.choose(null);
+    }
+    if (files.length == 0) {
+      return;
+    }
+    VirtualFile file = files[0];
+    directoryField.setText(file.getPath());
+  }
+
+  private JComponent getIconComponent() {
+    JLabel iconPanel =
+        new JLabel(IconLoader.getIconSnapshot(getBuildSystemIcon())) {
+          @Override
+          public boolean isEnabled() {
+            return true;
+          }
+        };
+    UiUtil.setPreferredWidth(iconPanel, 16);
+    return iconPanel;
+  }
+}
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
new file mode 100644
index 0000000..de7e373
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/wizard2/ui/BlazeEditProjectViewControl.java
@@ -0,0 +1,341 @@
+/*
+ * 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.wizard2.ui;
+
+import com.google.common.collect.Lists;
+import com.google.common.hash.HashCode;
+import com.google.common.hash.Hashing;
+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.ProjectViewSet;
+import com.google.idea.blaze.base.projectview.ProjectViewStorageManager;
+import com.google.idea.blaze.base.projectview.section.ScalarSection;
+import com.google.idea.blaze.base.projectview.section.sections.ImportSection;
+import com.google.idea.blaze.base.scope.output.IssueOutput;
+import com.google.idea.blaze.base.settings.ui.JPanelProvidingProject;
+import com.google.idea.blaze.base.settings.ui.ProjectViewUi;
+import com.google.idea.blaze.base.ui.BlazeValidationError;
+import com.google.idea.blaze.base.ui.BlazeValidationResult;
+import com.google.idea.blaze.base.ui.UiUtil;
+import com.google.idea.blaze.base.wizard2.BlazeNewProjectBuilder;
+import com.google.idea.blaze.base.wizard2.BlazeSelectProjectViewOption;
+import com.google.idea.blaze.base.wizard2.BlazeSelectWorkspaceOption;
+import com.intellij.ide.RecentProjectsManager;
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.application.ApplicationNamesInfo;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.fileChooser.FileChooserDescriptor;
+import com.intellij.openapi.ui.TextComponentAccessor;
+import com.intellij.openapi.ui.TextFieldWithBrowseButton;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.ui.components.JBLabel;
+import com.intellij.util.SystemProperties;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.GridBagLayout;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.stream.Collectors;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import org.jetbrains.annotations.Nullable;
+
+/** The UI control to collect project settings when importing a Blaze project. */
+public final class BlazeEditProjectViewControl {
+
+  private static final FileChooserDescriptor PROJECT_FOLDER_DESCRIPTOR =
+      new FileChooserDescriptor(false, true, false, false, false, false);
+  private static final Logger LOG = Logger.getInstance(BlazeEditProjectViewControl.class);
+
+  private final JPanel component;
+  private final String buildSystemName;
+  private final ProjectViewUi projectViewUi;
+
+  private TextFieldWithBrowseButton projectDataDirField;
+  private JTextField projectNameField;
+  private HashCode paramsHash;
+  private WorkspaceRoot workspaceRoot;
+
+  public BlazeEditProjectViewControl(BlazeNewProjectBuilder builder, Disposable parentDisposable) {
+    this.projectViewUi = new ProjectViewUi(parentDisposable);
+    JPanel component = new JPanelProvidingProject(ProjectViewUi.getProject(), new GridBagLayout());
+    fillUi(component, 0);
+    update(builder);
+    UiUtil.fillBottom(component);
+    this.component = component;
+    this.buildSystemName = builder.getBuildSystemName();
+  }
+
+  public Component getUiComponent() {
+    return component;
+  }
+
+  private void fillUi(JPanel canvas, int indentLevel) {
+    JLabel projectDataDirLabel = new JBLabel("Project data directory:");
+
+    Dimension minSize = ProjectViewUi.getMinimumSize();
+    // Add 120 pixels so we have room for our extra fields
+    minSize.setSize(minSize.width, minSize.height + 120);
+    canvas.setMinimumSize(minSize);
+    canvas.setPreferredSize(minSize);
+
+    projectDataDirField = new TextFieldWithBrowseButton();
+    projectDataDirField.addBrowseFolderListener(
+        "",
+        buildSystemName + " project data directory",
+        null,
+        PROJECT_FOLDER_DESCRIPTOR,
+        TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT,
+        false);
+    final String dataDirToolTipText =
+        "Directory in which to store the project's metadata. "
+            + "Choose a directory outside of your workspace.";
+    projectDataDirField.setToolTipText(dataDirToolTipText);
+    projectDataDirLabel.setToolTipText(dataDirToolTipText);
+
+    canvas.add(projectDataDirLabel, UiUtil.getLabelConstraints(indentLevel));
+    canvas.add(projectDataDirField, UiUtil.getFillLineConstraints(0));
+
+    JLabel projectNameLabel = new JLabel("Project name:");
+    projectNameField = new JTextField();
+    final String projectNameToolTipText = "Project display name.";
+    projectNameField.setToolTipText(projectNameToolTipText);
+    projectNameLabel.setToolTipText(projectNameToolTipText);
+    canvas.add(projectNameLabel, UiUtil.getLabelConstraints(indentLevel));
+    canvas.add(projectNameField, UiUtil.getFillLineConstraints(0));
+
+    projectViewUi.fillUi(canvas, indentLevel);
+  }
+
+  public void update(BlazeNewProjectBuilder builder) {
+    BlazeSelectWorkspaceOption workspaceOption = builder.getWorkspaceOption();
+    BlazeSelectProjectViewOption projectViewOption = builder.getProjectViewOption();
+    String workspaceName = workspaceOption.getWorkspaceName();
+    WorkspaceRoot workspaceRoot = workspaceOption.getWorkspaceRoot();
+    WorkspaceRoot temporaryWorkspaceRoot = workspaceOption.getTemporaryWorkspaceRoot();
+    WorkspacePath workspacePath = projectViewOption.getSharedProjectView();
+    String initialProjectViewText = projectViewOption.getInitialProjectViewText();
+
+    HashCode hashCode =
+        Hashing.md5()
+            .newHasher()
+            .putUnencodedChars(workspaceName)
+            .putUnencodedChars(workspaceRoot.toString())
+            .putUnencodedChars(temporaryWorkspaceRoot.toString())
+            .putUnencodedChars(workspacePath != null ? workspacePath.toString() : "")
+            .putUnencodedChars(initialProjectViewText != null ? initialProjectViewText : "")
+            .hash();
+
+    // If any params have changed, reinit the control
+    if (!hashCode.equals(paramsHash)) {
+      this.paramsHash = hashCode;
+      init(
+          workspaceName,
+          workspaceRoot,
+          temporaryWorkspaceRoot,
+          workspacePath,
+          initialProjectViewText);
+    }
+  }
+
+  private void init(
+      String workspaceName,
+      WorkspaceRoot workspaceRoot,
+      WorkspaceRoot temporaryWorkspaceRoot,
+      @Nullable WorkspacePath sharedProjectView,
+      @Nullable String initialProjectViewText) {
+    this.workspaceRoot = workspaceRoot;
+    projectNameField.setText(workspaceName);
+    String defaultDataDir = getDefaultProjectDataDirectory(workspaceName);
+    projectDataDirField.setText(defaultDataDir);
+
+    String projectViewText = "";
+    File sharedProjectViewFile = null;
+
+    if (sharedProjectView != null) {
+      sharedProjectViewFile = temporaryWorkspaceRoot.fileForPath(sharedProjectView);
+
+      try {
+        projectViewText =
+            ProjectViewStorageManager.getInstance().loadProjectView(sharedProjectViewFile);
+        if (projectViewText == null) {
+          LOG.error("Could not load project view: " + sharedProjectViewFile);
+          projectViewText = "";
+        }
+      } catch (IOException e) {
+        LOG.error(e);
+      }
+    } else {
+      projectViewText = initialProjectViewText;
+      LOG.assertTrue(projectViewText != null);
+    }
+
+    projectViewUi.init(
+        temporaryWorkspaceRoot,
+        projectViewText,
+        sharedProjectViewFile != null ? projectViewText : null,
+        sharedProjectViewFile,
+        sharedProjectViewFile != null,
+        false /* allowEditShared - not allowed during import */);
+  }
+
+  private static String getDefaultProjectDataDirectory(String projectName) {
+    File defaultDataDirectory = new File(getDefaultProjectsDirectory());
+    File desiredLocation = new File(defaultDataDirectory, projectName);
+    return newUniquePath(desiredLocation);
+  }
+
+  private static String getDefaultProjectsDirectory() {
+    final String lastProjectLocation =
+        RecentProjectsManager.getInstance().getLastProjectCreationLocation();
+    if (lastProjectLocation != null) {
+      return lastProjectLocation.replace('/', File.separatorChar);
+    }
+    final String userHome = SystemProperties.getUserHome();
+    String productName = ApplicationNamesInfo.getInstance().getLowercaseProductName();
+    return userHome.replace('/', File.separatorChar)
+        + File.separator
+        + productName.replace(" ", "")
+        + "Projects";
+  }
+
+  /** Returns a unique file path by appending numbers until a non-collision is found. */
+  private static String newUniquePath(File location) {
+    if (!location.exists()) {
+      return location.getAbsolutePath();
+    }
+
+    String name = location.getName();
+    File directory = location.getParentFile();
+    int tries = 0;
+    while (true) {
+      String candidateName = String.format("%s-%02d", name, tries);
+      File candidateFile = new File(directory, candidateName);
+      if (!candidateFile.exists()) {
+        return candidateFile.getAbsolutePath();
+      }
+      tries++;
+    }
+  }
+
+  public BlazeValidationResult validate() {
+    // Validate project settings fields
+    String projectName = projectNameField.getText().trim();
+    if (StringUtil.isEmpty(projectName)) {
+      return BlazeValidationResult.failure(
+          new BlazeValidationError("Project name is not specified"));
+    }
+    String projectDataDirPath = projectDataDirField.getText().trim();
+    if (StringUtil.isEmpty(projectDataDirPath)) {
+      return BlazeValidationResult.failure(
+          new BlazeValidationError("Project data directory is not specified"));
+    }
+    File projectDataDir = new File(projectDataDirPath);
+    if (!projectDataDir.isAbsolute()) {
+      return BlazeValidationResult.failure(
+          new BlazeValidationError("Project data directory is not valid"));
+    }
+    File workspaceRootDirectory = workspaceRoot.directory();
+    if (FileUtil.isAncestor(projectDataDir, workspaceRootDirectory, false)) {
+      return BlazeValidationResult.failure(
+          new BlazeValidationError(
+              "Project data directory must not contain the workspace. "
+                  + "Please choose a directory outside your workspace."));
+    }
+    if (FileUtil.isAncestor(workspaceRootDirectory, projectDataDir, false)) {
+      return BlazeValidationResult.failure(
+          new BlazeValidationError(
+              "Project data directory cannot be inside your workspace. "
+                  + "Please choose a directory outside your workspace."));
+    }
+
+    List<IssueOutput> issues = Lists.newArrayList();
+
+    projectViewUi.parseProjectView(issues);
+    BlazeValidationError projectViewParseError = validationErrorFromIssueList(issues);
+    if (projectViewParseError != null) {
+      return BlazeValidationResult.failure(projectViewParseError);
+    }
+
+    return BlazeValidationResult.success();
+  }
+
+  @Nullable
+  private static BlazeValidationError validationErrorFromIssueList(List<IssueOutput> issues) {
+    List<IssueOutput> errors =
+        issues
+            .stream()
+            .filter(issue -> issue.getCategory() == IssueOutput.Category.ERROR)
+            .collect(Collectors.toList());
+
+    if (!errors.isEmpty()) {
+      StringBuilder errorMessage = new StringBuilder();
+      errorMessage.append("The following issues were found:\n\n");
+      for (IssueOutput issue : errors) {
+        errorMessage.append(issue.getMessage());
+        errorMessage.append('\n');
+      }
+      return new BlazeValidationError(errorMessage.toString());
+    }
+    return null;
+  }
+
+  public void updateBuilder(BlazeNewProjectBuilder builder) {
+    String projectName = projectNameField.getText().trim();
+    String projectDataDirectory = projectDataDirField.getText().trim();
+    File localProjectViewFile =
+        ProjectViewStorageManager.getLocalProjectViewFileName(
+            builder.getBuildSystem(), new File(projectDataDirectory));
+
+    BlazeSelectProjectViewOption selectProjectViewOption = builder.getProjectViewOption();
+    boolean useSharedProjectView = projectViewUi.getUseSharedProjectView();
+
+    // If we're using a shared project view, synthesize a local one that imports the shared one
+    ProjectViewSet parseResult = projectViewUi.parseProjectView(Lists.newArrayList());
+
+    final ProjectView projectView;
+    final ProjectViewSet projectViewSet;
+    if (useSharedProjectView && selectProjectViewOption.getSharedProjectView() != null) {
+      projectView =
+          ProjectView.builder()
+              .add(
+                  ScalarSection.builder(ImportSection.KEY)
+                      .set(selectProjectViewOption.getSharedProjectView()))
+              .build();
+      projectViewSet =
+          ProjectViewSet.builder()
+              .addAll(parseResult.getProjectViewFiles())
+              .add(localProjectViewFile, projectView)
+              .build();
+    } else {
+      ProjectViewSet.ProjectViewFile projectViewFile = parseResult.getTopLevelProjectViewFile();
+      assert projectViewFile != null;
+      projectView = projectViewFile.projectView;
+      projectViewSet = parseResult;
+    }
+
+    builder
+        .setProjectView(projectView)
+        .setProjectViewFile(localProjectViewFile)
+        .setProjectViewSet(projectViewSet)
+        .setProjectName(projectName)
+        .setProjectDataDirectory(projectDataDirectory);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/wizard2/ui/BlazeSelectOptionControl.java b/base/src/com/google/idea/blaze/base/wizard2/ui/BlazeSelectOptionControl.java
new file mode 100644
index 0000000..a79644d
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/wizard2/ui/BlazeSelectOptionControl.java
@@ -0,0 +1,165 @@
+/*
+ * 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.wizard2.ui;
+
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.settings.ui.ProjectViewUi;
+import com.google.idea.blaze.base.ui.BlazeValidationResult;
+import com.google.idea.blaze.base.wizard2.BlazeNewProjectBuilder;
+import com.google.idea.blaze.base.wizard2.BlazeWizardOption;
+import com.google.idea.blaze.base.wizard2.BlazeWizardUserSettings;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.ui.components.panels.HorizontalLayout;
+import com.intellij.ui.components.panels.VerticalLayout;
+import java.awt.Dimension;
+import java.util.Collection;
+import javax.swing.ButtonGroup;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JSeparator;
+import javax.swing.border.EmptyBorder;
+
+/** UI for selecting a client during the import process. */
+public abstract class BlazeSelectOptionControl<T extends BlazeWizardOption> {
+  private static final Logger LOG = Logger.getInstance(BlazeSelectOptionControl.class);
+
+  private final BlazeWizardUserSettings userSettings;
+  private final JPanel canvas;
+  private final JLabel titleLabel;
+  private final Collection<OptionUiEntry<T>> optionUiEntryList;
+
+  static class OptionUiEntry<T> {
+    final T option;
+    final JRadioButton radioButton;
+
+    OptionUiEntry(T option, JRadioButton radioButton) {
+      this.option = option;
+      this.radioButton = radioButton;
+    }
+  }
+
+  BlazeSelectOptionControl(BlazeNewProjectBuilder builder, Collection<T> options) {
+    if (options == null) {
+      LOG.error("No options on select screen '" + getTitle() + "'");
+    }
+
+    this.userSettings = builder.getUserSettings();
+
+    JPanel canvas = new JPanel(new VerticalLayout(4));
+
+    Dimension minSize = ProjectViewUi.getMinimumSize();
+    canvas.setPreferredSize(minSize);
+
+    titleLabel = new JLabel(getTitle());
+    canvas.add(titleLabel);
+    canvas.add(new JSeparator());
+
+    JPanel content = new JPanel(new VerticalLayout(12));
+    content.setBorder(new EmptyBorder(20, 100, 0, 0));
+    canvas.add(content);
+
+    ButtonGroup buttonGroup = new ButtonGroup();
+    Collection<OptionUiEntry<T>> optionUiEntryList = Lists.newArrayList();
+    for (T option : options) {
+      JPanel vertical = new JPanel(new VerticalLayout(10));
+      JRadioButton radioButton = new JRadioButton();
+      radioButton.setText(option.getOptionText());
+      vertical.add(radioButton);
+
+      JComponent optionComponent = option.getUiComponent();
+      if (optionComponent != null) {
+        JPanel horizontal = new JPanel(new HorizontalLayout(0));
+        horizontal.setBorder(new EmptyBorder(0, 25, 0, 0));
+        horizontal.add(optionComponent);
+        vertical.add(horizontal);
+
+        option.optionDeselected();
+        radioButton.addItemListener(
+            itemEvent -> {
+              if (radioButton.isSelected()) {
+                option.optionSelected();
+              } else {
+                option.optionDeselected();
+              }
+            });
+      }
+
+      content.add(vertical);
+      buttonGroup.add(radioButton);
+      optionUiEntryList.add(new OptionUiEntry<>(option, radioButton));
+    }
+
+    OptionUiEntry selected = null;
+    String previouslyChosenOption = userSettings.get(getOptionKey(), null);
+    if (previouslyChosenOption != null) {
+      for (OptionUiEntry<T> entry : optionUiEntryList) {
+        if (entry.option.getOptionName().equals(previouslyChosenOption)) {
+          selected = entry;
+          break;
+        }
+      }
+    }
+    if (selected == null) {
+      selected = Iterables.getFirst(optionUiEntryList, null);
+    }
+    if (selected != null) {
+      selected.radioButton.setSelected(true);
+    }
+
+    this.canvas = canvas;
+    this.optionUiEntryList = optionUiEntryList;
+  }
+
+  public BlazeValidationResult validate() {
+    T option = getSelectedOption();
+    if (option == null) {
+      return BlazeValidationResult.failure("No option selected.");
+    }
+    return option.validate();
+  }
+
+  public JComponent getUiComponent() {
+    return canvas;
+  }
+
+  public T getSelectedOption() {
+    for (OptionUiEntry<T> entry : optionUiEntryList) {
+      if (entry.radioButton.isSelected()) {
+        return entry.option;
+      }
+    }
+    return null;
+  }
+
+  public void commit() {
+    T selectedOption = getSelectedOption();
+    if (selectedOption != null) {
+      userSettings.put(getOptionKey(), selectedOption.getOptionName());
+    }
+  }
+
+  /** Call when the title changes. */
+  protected void setTitle(String newTitle) {
+    titleLabel.setText(newTitle);
+  }
+
+  abstract String getTitle();
+
+  abstract String getOptionKey();
+}
diff --git a/base/src/com/google/idea/blaze/base/wizard2/ui/BlazeSelectProjectViewControl.java b/base/src/com/google/idea/blaze/base/wizard2/ui/BlazeSelectProjectViewControl.java
new file mode 100644
index 0000000..a2b6467
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/wizard2/ui/BlazeSelectProjectViewControl.java
@@ -0,0 +1,78 @@
+/*
+ * 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.wizard2.ui;
+
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.projectview.ProjectViewStorageManager;
+import com.google.idea.blaze.base.ui.BlazeValidationResult;
+import com.google.idea.blaze.base.wizard2.BlazeNewProjectBuilder;
+import com.google.idea.blaze.base.wizard2.BlazeSelectProjectViewOption;
+import com.google.idea.blaze.base.wizard2.BlazeWizardOptionProvider;
+import java.util.Collection;
+import javax.swing.JComponent;
+
+/** UI for selecting the project view during the import process. */
+public class BlazeSelectProjectViewControl {
+
+  private BlazeSelectOptionControl<BlazeSelectProjectViewOption> selectOptionControl;
+
+  public BlazeSelectProjectViewControl(BlazeNewProjectBuilder builder) {
+    Collection<BlazeSelectProjectViewOption> options = Lists.newArrayList();
+    for (BlazeWizardOptionProvider optionProvider :
+        BlazeWizardOptionProvider.EP_NAME.getExtensions()) {
+      options.addAll(optionProvider.getSelectProjectViewOptions(builder));
+    }
+
+    this.selectOptionControl =
+        new BlazeSelectOptionControl<BlazeSelectProjectViewOption>(builder, options) {
+          @Override
+          String getTitle() {
+            return BlazeSelectProjectViewControl.getTitle(builder);
+          }
+
+          @Override
+          String getOptionKey() {
+            return "select-project-view.selected-option";
+          }
+        };
+  }
+
+  public JComponent getUiComponent() {
+    return selectOptionControl.getUiComponent();
+  }
+
+  public BlazeValidationResult validate() {
+    return selectOptionControl.validate();
+  }
+
+  public void update(BlazeNewProjectBuilder builder) {
+    selectOptionControl.setTitle(getTitle(builder));
+  }
+
+  public void updateBuilder(BlazeNewProjectBuilder builder) {
+    builder.setProjectViewOption(selectOptionControl.getSelectedOption());
+  }
+
+  public void commit() {
+    selectOptionControl.commit();
+  }
+
+  private static String getTitle(BlazeNewProjectBuilder builder) {
+    String projectViewString =
+        ProjectViewStorageManager.getProjectViewFileName(builder.getBuildSystem());
+    return String.format("Select project view (%s file)", projectViewString);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/wizard2/ui/BlazeSelectWorkspaceControl.java b/base/src/com/google/idea/blaze/base/wizard2/ui/BlazeSelectWorkspaceControl.java
new file mode 100644
index 0000000..f0c6b45
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/wizard2/ui/BlazeSelectWorkspaceControl.java
@@ -0,0 +1,66 @@
+/*
+ * 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.wizard2.ui;
+
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.ui.BlazeValidationResult;
+import com.google.idea.blaze.base.wizard2.BlazeNewProjectBuilder;
+import com.google.idea.blaze.base.wizard2.BlazeSelectWorkspaceOption;
+import com.google.idea.blaze.base.wizard2.BlazeWizardOptionProvider;
+import java.util.Collection;
+import javax.swing.JComponent;
+
+/** UI for selecting a client during the import process. */
+public class BlazeSelectWorkspaceControl {
+  BlazeSelectOptionControl<BlazeSelectWorkspaceOption> selectOptionControl;
+
+  public BlazeSelectWorkspaceControl(BlazeNewProjectBuilder builder) {
+    Collection<BlazeSelectWorkspaceOption> options = Lists.newArrayList();
+    for (BlazeWizardOptionProvider optionProvider :
+        BlazeWizardOptionProvider.EP_NAME.getExtensions()) {
+      options.addAll(optionProvider.getSelectWorkspaceOptions(builder));
+    }
+
+    this.selectOptionControl =
+        new BlazeSelectOptionControl<BlazeSelectWorkspaceOption>(builder, options) {
+          @Override
+          String getTitle() {
+            return "Select workspace";
+          }
+
+          @Override
+          String getOptionKey() {
+            return "select-workspace.selected-option";
+          }
+        };
+  }
+
+  public JComponent getUiComponent() {
+    return selectOptionControl.getUiComponent();
+  }
+
+  public BlazeValidationResult validate() {
+    return selectOptionControl.validate();
+  }
+
+  public void updateBuilder(BlazeNewProjectBuilder builder) {
+    builder.setWorkspaceOption(selectOptionControl.getSelectedOption());
+  }
+
+  public void commit() {
+    selectOptionControl.commit();
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/wizard2/ui/SelectBazelBinaryControl.java b/base/src/com/google/idea/blaze/base/wizard2/ui/SelectBazelBinaryControl.java
new file mode 100644
index 0000000..e582c8d
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/wizard2/ui/SelectBazelBinaryControl.java
@@ -0,0 +1,115 @@
+/*
+ * 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.wizard2.ui;
+
+import com.google.common.base.Strings;
+import com.google.idea.blaze.base.async.process.ExternalTask;
+import com.google.idea.blaze.base.io.FileAttributeProvider;
+import com.google.idea.blaze.base.settings.BlazeUserSettings;
+import com.google.idea.blaze.base.settings.ui.BlazeUserSettingsConfigurable;
+import com.google.idea.blaze.base.ui.BlazeValidationResult;
+import com.google.idea.blaze.base.ui.FileSelectorWithStoredHistory;
+import com.google.idea.blaze.base.wizard2.BlazeNewProjectBuilder;
+import com.intellij.ui.components.panels.VerticalLayout;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import javax.annotation.Nullable;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSeparator;
+import javax.swing.border.EmptyBorder;
+
+/** UI for selecting the build system binary during the import process. */
+public class SelectBazelBinaryControl {
+
+  public final BlazeNewProjectBuilder builder;
+
+  private boolean uiInitialized = false;
+  private JPanel component;
+  private FileSelectorWithStoredHistory bazelBinaryPath;
+
+  public SelectBazelBinaryControl(BlazeNewProjectBuilder builder) {
+    this.builder = builder;
+  }
+
+  public JComponent getUiComponent() {
+    if (!uiInitialized) {
+      initUi();
+      uiInitialized = true;
+    }
+    return component;
+  }
+
+  private void initUi() {
+    bazelBinaryPath =
+        FileSelectorWithStoredHistory.create(
+            BlazeUserSettingsConfigurable.BAZEL_BINARY_PATH_KEY, "Specify the bazel binary path");
+    bazelBinaryPath.setText(getInitialBinaryPath());
+
+    component = new JPanel(new VerticalLayout(4));
+    component.add(new JLabel("Select a bazel binary"));
+    component.add(new JSeparator());
+
+    JPanel content = new JPanel(new VerticalLayout(12));
+    content.setBorder(new EmptyBorder(50, 100, 0, 100));
+    component.add(content);
+
+    content.add(new JLabel("Specify a bazel binary to be used for all bazel projects"));
+    content.add(bazelBinaryPath);
+  }
+
+  public BlazeValidationResult validate() {
+    String binaryPath = getBazelPath();
+    if (Strings.isNullOrEmpty(binaryPath)) {
+      return BlazeValidationResult.failure("Select a bazel binary");
+    }
+    if (!FileAttributeProvider.getInstance().isFile(new File(binaryPath))) {
+      return BlazeValidationResult.failure("Invalid bazel binary: file does not exist");
+    }
+    return BlazeValidationResult.success();
+  }
+
+  public void commit() {
+    if (!Strings.isNullOrEmpty(getBazelPath())) {
+      BlazeUserSettings.getInstance().setBazelBinaryPath(getBazelPath());
+    }
+  }
+
+  private String getBazelPath() {
+    String text = bazelBinaryPath.getText();
+    return text != null ? text.trim() : "";
+  }
+
+  private static String getInitialBinaryPath() {
+    String existingPath = BlazeUserSettings.getInstance().getBazelBinaryPath();
+    if (existingPath != null) {
+      return existingPath;
+    }
+    return guessBinaryPath();
+  }
+
+  /** Try to guess an initial binary path */
+  @Nullable
+  private static String guessBinaryPath() {
+    ByteArrayOutputStream stdout = new ByteArrayOutputStream();
+    int retVal = ExternalTask.builder().args("which", "bazel").stdout(stdout).build().run();
+    if (retVal != 0) {
+      return null;
+    }
+    return stdout.toString().trim();
+  }
+}
diff --git a/base/src/icons/BlazeIcons.java b/base/src/icons/BlazeIcons.java
new file mode 100644
index 0000000..2e3fb4e
--- /dev/null
+++ b/base/src/icons/BlazeIcons.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 icons;
+
+import com.intellij.openapi.util.IconLoader;
+import javax.swing.Icon;
+
+/** Class to manage icons used by the Blaze plugin. */
+public class BlazeIcons {
+
+  private static final String BASE = "/";
+
+  public static final Icon Blaze = load("base/resources/icons/blaze.png"); // 16x16
+  public static final Icon BlazeSlow = load("base/resources/icons/blaze_slow.png"); // 16x16
+  public static final Icon BlazeDirty = load("base/resources/icons/blaze_dirty.png"); // 16x16
+  public static final Icon BlazeClean = load("base/resources/icons/blaze_clean.png"); // 16x16
+  public static final Icon BlazeFailed = load("base/resources/icons/blaze_failed.png"); // 16x16
+  // This is just the Blaze icon scaled down to the size IJ wants for tool windows.
+  public static final Icon BlazeToolWindow =
+      load("base/resources/icons/blazeToolWindow.png"); // 13x13
+
+  public static final Icon BazelLeaf = load("base/resources/icons/bazel_leaf.png"); // 16x16
+
+  // Build file support icons
+  public static final Icon BuildFile = load("base/resources/icons/build_file.png"); // 16x16
+  public static final Icon BuildRule = load("base/resources/icons/build_rule.png"); // 16x16
+
+  private static Icon load(String path) {
+    return IconLoader.getIcon(BASE + path, BlazeIcons.class);
+  }
+}
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
new file mode 100644
index 0000000..db6e935
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/ArgumentCompletionContributorTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.completion;
+
+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.intellij.codeInsight.lookup.LookupElement;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.testFramework.fixtures.CompletionAutoPopupTester;
+
+/** Tests for code completion of funcall arguments. */
+public class ArgumentCompletionContributorTest extends BuildFileIntegrationTestCase {
+
+  private CompletionAutoPopupTester completionTester;
+
+  public void doSetup() {
+    super.doSetup();
+    completionTester = new CompletionAutoPopupTester(testFixture);
+  }
+
+  @Override
+  protected boolean runInDispatchThread() {
+    return false;
+  }
+
+  @Override
+  protected void invokeTestRunnable(Runnable runnable) throws Exception {
+    completionTester.runWithAutoPopupEnabled(runnable);
+  }
+
+  public void testIncompleteFuncall() {
+    BuildFile file =
+        createBuildFile(
+            "BUILD", "def function(name, deps, srcs):", "  # empty function", "function(d");
+
+    Editor editor = openFileInEditor(file.getVirtualFile());
+    setCaretPosition(editor, 2, "function(n".length());
+
+    LookupElement[] completionItems = testFixture.completeBasic();
+    assertThat(completionItems).isNull();
+
+    assertFileContents(
+        file, "def function(name, deps, srcs):", "  # empty function", "function(deps");
+  }
+
+  public void testExistingKeywordArg() {
+    BuildFile file =
+        createBuildFile(
+            "BUILD",
+            "def function(name, deps, srcs):",
+            "  # empty function",
+            "function(name = \"lib\")");
+
+    Editor editor = openFileInEditor(file.getVirtualFile());
+    setCaretPosition(editor, 2, "function(".length());
+
+    String[] completionItems = getCompletionItemsAsStrings();
+    assertThat(completionItems).hasLength(4);
+    assertThat(completionItems).asList().containsAllOf("name", "deps", "srcs", "function");
+  }
+
+  public void testNoArgumentCompletionInComment() {
+    BuildFile file =
+        createBuildFile(
+            "BUILD", "def function(name, deps, srcs):", "  # empty function", "function(#");
+
+    Editor editor = openFileInEditor(file.getVirtualFile());
+    setCaretPosition(editor, 2, "function(#".length());
+
+    completionTester.typeWithPauses("n");
+    assertNull(testFixture.getLookup());
+  }
+}
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
new file mode 100644
index 0000000..6fe0d21
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/BuiltInFunctionAttributeCompletionContributorTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.completion;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
+import com.google.idea.blaze.base.lang.buildfile.language.semantics.AttributeDefinition;
+import com.google.idea.blaze.base.lang.buildfile.language.semantics.BuildLanguageSpec;
+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.repackaged.devtools.build.lib.query2.proto.proto2api.Build;
+import com.intellij.codeInsight.lookup.LookupElement;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.project.Project;
+import javax.annotation.Nullable;
+
+/** Tests for BuiltInFunctionAttributeCompletionContributor. */
+public class BuiltInFunctionAttributeCompletionContributorTest
+    extends BuildFileIntegrationTestCase {
+
+  private MockBuildLanguageSpecProvider specProvider;
+
+  @Override
+  protected void doSetup() {
+    super.doSetup();
+    specProvider = new MockBuildLanguageSpecProvider();
+    registerApplicationService(BuildLanguageSpecProvider.class, specProvider);
+  }
+
+  public void testSimpleCompletion() {
+    setRuleAndAttributes("sh_binary", "name", "deps", "srcs", "data");
+
+    BuildFile file = createBuildFile("BUILD", "sh_binary(");
+
+    Editor editor = openFileInEditor(file.getVirtualFile());
+    setCaretPosition(editor, 0, "sh_binary(".length());
+
+    String[] completionItems = getCompletionItemsAsStrings();
+    assertThat(completionItems).asList().containsAllOf("name", "deps", "srcs", "data");
+  }
+
+  public void testSimpleSingleCompletion() {
+    setRuleAndAttributes("sh_binary", "name", "deps", "srcs", "data");
+
+    BuildFile file = createBuildFile("BUILD", "sh_binary(", "    n");
+
+    Editor editor = openFileInEditor(file.getVirtualFile());
+    setCaretPosition(editor, 1, "    n".length());
+
+    String[] completionItems = getCompletionItemsAsStrings();
+    assertThat(completionItems).isNull();
+    assertFileContents(file, "sh_binary(", "    name");
+  }
+
+  public void testNoCompletionInUnknownRule() {
+    setRuleAndAttributes("sh_binary", "name", "deps", "srcs", "data");
+
+    BuildFile file = createBuildFile("BUILD", "java_binary(");
+
+    Editor editor = openFileInEditor(file.getVirtualFile());
+    setCaretPosition(editor, 0, "java_binary(".length());
+
+    LookupElement[] completionItems = testFixture.completeBasic();
+    assertThat(completionItems).isEmpty();
+  }
+
+  public void testNoCompletionInComment() {
+    setRuleAndAttributes("sh_binary", "name", "deps", "srcs", "data");
+
+    BuildFile file = createBuildFile("BUILD", "sh_binary(#");
+
+    Editor editor = openFileInEditor(file.getVirtualFile());
+    setCaretPosition(editor, 0, "sh_binary(#".length());
+    assertEmpty(getCompletionItemsAsStrings());
+  }
+
+  public void testCompletionInSkylarkExtension() {
+    setRuleAndAttributes("sh_binary", "name", "deps", "srcs", "data");
+
+    BuildFile file = createBuildFile("skylark.bzl", "native.sh_binary(");
+
+    Editor editor = openFileInEditor(file.getVirtualFile());
+    setCaretPosition(editor, 0, "native.sh_binary(".length());
+
+    String[] completionItems = getCompletionItemsAsStrings();
+    assertThat(completionItems).asList().containsAllOf("name", "deps", "srcs", "data");
+  }
+
+  private void setRuleAndAttributes(String ruleName, String... attributes) {
+    ImmutableMap.Builder<String, AttributeDefinition> map = ImmutableMap.builder();
+    for (String attr : attributes) {
+      map.put(
+          attr,
+          new AttributeDefinition(attr, Build.Attribute.Discriminator.UNKNOWN, false, null, null));
+    }
+    RuleDefinition rule = new RuleDefinition(ruleName, map.build(), null);
+    specProvider.setRules(ImmutableMap.of(ruleName, rule));
+  }
+
+  private static class MockBuildLanguageSpecProvider implements BuildLanguageSpecProvider {
+
+    BuildLanguageSpec languageSpec;
+
+    void setRules(ImmutableMap<String, RuleDefinition> rules) {
+      languageSpec = new BuildLanguageSpec(rules);
+    }
+
+    @Nullable
+    @Override
+    public BuildLanguageSpec getLanguageSpec(Project project) {
+      return languageSpec;
+    }
+  }
+}
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
new file mode 100644
index 0000000..f448b7a
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/BuiltInFunctionCompletionContributorTest.java
@@ -0,0 +1,137 @@
+/*
+ * 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.completion;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
+import com.google.idea.blaze.base.lang.buildfile.language.semantics.BuildLanguageSpec;
+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.intellij.codeInsight.lookup.LookupElement;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.project.Project;
+import javax.annotation.Nullable;
+
+/** Tests BuiltInFunctionCompletionContributor */
+public class BuiltInFunctionCompletionContributorTest extends BuildFileIntegrationTestCase {
+
+  private MockBuildLanguageSpecProvider specProvider;
+
+  @Override
+  protected void doSetup() {
+    super.doSetup();
+    specProvider = new MockBuildLanguageSpecProvider();
+    registerApplicationService(BuildLanguageSpecProvider.class, specProvider);
+  }
+
+  public void testSimpleTopLevelCompletion() {
+    setRules("java_library", "android_binary");
+
+    BuildFile file = createBuildFile("BUILD", "");
+
+    Editor editor = openFileInEditor(file.getVirtualFile());
+    setCaretPosition(editor, 0, 0);
+
+    LookupElement[] completionItems = testFixture.completeBasic();
+    assertThat(completionItems).hasLength(2);
+    assertThat(completionItems[0].getLookupString()).isEqualTo("android_binary");
+    assertThat(completionItems[1].getLookupString()).isEqualTo("java_library");
+
+    assertFileContents(file, "");
+  }
+
+  public void testUniqueTopLevelCompletion() {
+    setRules("java_library", "android_binary");
+
+    BuildFile file = createBuildFile("BUILD", "ja");
+
+    Editor editor = openFileInEditor(file.getVirtualFile());
+    setCaretPosition(editor, 0, 2);
+
+    LookupElement[] completionItems = testFixture.completeBasic();
+    assertThat(completionItems).isNull();
+
+    assertFileContents(file, "java_library()");
+    assertCaretPosition(editor, 0, "java_library(".length());
+  }
+
+  public void testSkylarkNativeCompletion() {
+    setRules("java_library", "android_binary");
+
+    BuildFile file = createBuildFile("build_defs.bzl", "def function():", "  native.j");
+
+    Editor editor = openFileInEditor(file.getVirtualFile());
+    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());
+  }
+
+  public void testNoCompletionInsideRule() {
+    setRules("java_library", "android_binary");
+
+    String[] contents = {"java_library(", "    name = \"lib\"", ""};
+
+    BuildFile file = createBuildFile("BUILD", contents);
+
+    Editor editor = openFileInEditor(file.getVirtualFile());
+    setCaretPosition(editor, 2, 0);
+
+    LookupElement[] completionItems = testFixture.completeBasic();
+    assertThat(completionItems).isEmpty();
+    assertFileContents(file, contents);
+  }
+
+  public void testNoCompletionInComment() {
+    setRules("java_library", "android_binary");
+
+    BuildFile file = createBuildFile("BUILD", "#java");
+
+    Editor editor = openFileInEditor(file.getVirtualFile());
+    setCaretPosition(editor, 0, "#java".length());
+
+    assertEmpty(getCompletionItemsAsStrings());
+  }
+
+  private void setRules(String... ruleNames) {
+    ImmutableMap.Builder<String, RuleDefinition> rules = ImmutableMap.builder();
+    for (String name : ruleNames) {
+      rules.put(name, new RuleDefinition(name, ImmutableMap.of(), null));
+    }
+    specProvider.setRules(rules.build());
+  }
+
+  private static class MockBuildLanguageSpecProvider implements BuildLanguageSpecProvider {
+
+    BuildLanguageSpec languageSpec;
+
+    void setRules(ImmutableMap<String, RuleDefinition> rules) {
+      languageSpec = new BuildLanguageSpec(rules);
+    }
+
+    @Nullable
+    @Override
+    public BuildLanguageSpec getLanguageSpec(Project project) {
+      return languageSpec;
+    }
+  }
+}
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
new file mode 100644
index 0000000..5838f8f
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/FilePathCompletionTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.completion;
+
+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.intellij.openapi.editor.Editor;
+import com.intellij.openapi.vfs.VirtualFile;
+
+/** Tests file path code completion in BUILD file labels. */
+public class FilePathCompletionTest extends BuildFileIntegrationTestCase {
+
+  public void testUniqueDirectoryCompleted() {
+    BuildFile file = createBuildFile("java/BUILD", "'//'");
+
+    Editor editor = openFileInEditor(file);
+    setCaretPosition(editor, 0, "'//".length());
+
+    assertThat(completeIfUnique()).isTrue();
+    assertFileContents(file, "'//java'");
+    // check caret remains inside closing quote
+    assertCaretPosition(editor, 0, "'//java".length());
+  }
+
+  public void testUniqueMultiSegmentDirectoryCompleted() {
+    BuildFile file = createBuildFile("java/com/google/BUILD", "'//'");
+
+    Editor editor = openFileInEditor(file);
+    setCaretPosition(editor, 0, "'//".length());
+
+    assertThat(completeIfUnique()).isTrue();
+    assertFileContents(file, "'//java/com/google'");
+  }
+
+  // expected to be a typical workflow -- complete a segment,
+  // get the possibilities, then start typing
+  // next segment and complete again
+  public void testMultiStageCompletion() {
+    createDirectory("foo");
+    createDirectory("bar");
+    createDirectory("other");
+    createDirectory("other/foo");
+    createDirectory("other/bar");
+
+    BuildFile file = createBuildFile("BUILD", "'//'");
+
+    Editor editor = openFileInEditor(file);
+    setCaretPosition(editor, 0, "'//".length());
+
+    String[] completionItems = getCompletionItemsAsStrings();
+    assertThat(completionItems).hasLength(3);
+
+    performTypingAction(editor, 'o');
+    assertThat(completeIfUnique()).isTrue();
+    assertFileContents(file, "'//other'");
+    assertCaretPosition(editor, 0, "'//other".length());
+
+    performTypingAction(editor, '/');
+    performTypingAction(editor, 'f');
+    assertThat(completeIfUnique()).isTrue();
+    assertFileContents(file, "'//other/foo'");
+    assertCaretPosition(editor, 0, "'//other/foo".length());
+  }
+
+  public void testCompletionSuggestionString() {
+    createDirectory("foo");
+    createDirectory("bar");
+    createDirectory("other");
+    createDirectory("ostrich/foo");
+    createDirectory("ostrich/fooz");
+
+    VirtualFile file = createAndSetCaret("BUILD", "'//o<caret>'");
+
+    String[] completionItems = getCompletionItemsAsSuggestionStrings();
+    assertThat(completionItems).asList().containsExactly("other", "ostrich");
+
+    performTypingAction(testFixture.getEditor(), 's');
+
+    assertThat(completeIfUnique()).isTrue();
+    assertFileContents(file, "'//ostrich'");
+
+    completionItems = getCompletionItemsAsSuggestionStrings();
+    assertThat(completionItems).asList().containsExactly("/foo", "/fooz");
+
+    performTypingAction(testFixture.getEditor(), '/');
+
+    completionItems = getCompletionItemsAsSuggestionStrings();
+    assertThat(completionItems).asList().containsExactly("foo", "fooz");
+
+    performTypingAction(testFixture.getEditor(), 'f');
+
+    completionItems = getCompletionItemsAsSuggestionStrings();
+    assertThat(completionItems).asList().containsExactly("foo", "fooz");
+  }
+
+  private VirtualFile createAndSetCaret(String filePath, String... fileContents) {
+    VirtualFile file = createFile(filePath, 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
new file mode 100644
index 0000000..aab1659
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/LocalSymbolCompletionTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.completion;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.base.Joiner;
+import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
+import com.intellij.psi.PsiFile;
+
+/** Tests code completion works with general symbols in scope. */
+public class LocalSymbolCompletionTest extends BuildFileIntegrationTestCase {
+
+  private PsiFile setInput(String... fileContents) {
+    return testFixture.configureByText("BUILD", Joiner.on("\n").join(fileContents));
+  }
+
+  private void assertResult(String... resultingFileContents) {
+    String s = testFixture.getFile().getText();
+    testFixture.checkResult(Joiner.on("\n").join(resultingFileContents));
+  }
+
+  public void testLocalVariable() {
+    setInput("var = [a, b]", "def function(name, deps, srcs):", "  v<caret>");
+
+    completeIfUnique();
+
+    assertResult("var = [a, b]", "def function(name, deps, srcs):", "  var<caret>");
+  }
+
+  public void testLocalFunction() {
+    setInput("def fnName():return True", "def function(name, deps, srcs):", "  fnN<caret>");
+
+    completeIfUnique();
+
+    assertResult("def fnName():return True", "def function(name, deps, srcs):", "  fnName<caret>");
+  }
+
+  public void testNoCompletionAfterDot() {
+    setInput("var = [a, b]", "def function(name, deps, srcs):", "  ext.v<caret>");
+
+    String[] completionItems = getCompletionItemsAsStrings();
+    assertThat(completionItems).isEmpty();
+  }
+
+  public void testFunctionParam() {
+    setInput("def test(var):", "  v<caret>");
+
+    completeIfUnique();
+
+    assertResult("def test(var):", "  var<caret>");
+  }
+
+  // b/28912523: when symbol is present in multiple assignment statements, should only be
+  // included once in the code-completion dialog
+  public void testSymbolAssignedMultipleTimes() {
+    setInput("var = 1", "var = 2", "var = 3", "<caret>");
+
+    completeIfUnique();
+
+    assertResult("var = 1", "var = 2", "var = 3", "var<caret>");
+  }
+
+  public void testSymbolDefinedOutsideScope() {
+    setInput("<caret>", "var = 1");
+
+    assertThat(getCompletionItemsAsStrings()).isEmpty();
+  }
+
+  public void testSymbolDefinedOutsideScope2() {
+    setInput("def fn():", "  var = 1", "v<caret>");
+
+    assertThat(testFixture.completeBasic()).isEmpty();
+  }
+
+  public void testSymbolDefinedOutsideScope3() {
+    setInput("for var in (1, 2, 3): print var", "v<caret>");
+
+    assertThat(testFixture.completeBasic()).isEmpty();
+  }
+}
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
new file mode 100644
index 0000000..afc25d0
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/ParameterCompletionContributorTest.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.base.lang.buildfile.completion;
+
+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.intellij.codeInsight.lookup.LookupElement;
+import com.intellij.openapi.editor.Editor;
+
+/** Tests ParameterCompletionContributor. */
+public class ParameterCompletionContributorTest extends BuildFileIntegrationTestCase {
+
+  public void testArgsCompletion() {
+    BuildFile file = createBuildFile("BUILD", "def function(arg1, *");
+
+    Editor editor = openFileInEditor(file.getVirtualFile());
+    setCaretPosition(editor, 0, "def function(arg1, *".length());
+
+    LookupElement[] completionItems = testFixture.completeBasic();
+    assertThat(completionItems).isNull();
+
+    assertFileContents(file, "def function(arg1, *args");
+  }
+
+  public void testKwargsCompletion() {
+    BuildFile file = createBuildFile("BUILD", "def function(arg1, **");
+
+    Editor editor = openFileInEditor(file.getVirtualFile());
+    setCaretPosition(editor, 0, "def function(arg1, **".length());
+
+    LookupElement[] completionItems = testFixture.completeBasic();
+    assertThat(completionItems).isNull();
+
+    assertFileContents(file, "def function(arg1, **kwargs");
+  }
+}
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
new file mode 100644
index 0000000..6a289f7
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/RuleTargetCompletionTest.java
@@ -0,0 +1,146 @@
+/*
+ * 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.completion;
+
+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.intellij.codeInsight.lookup.LookupElement;
+import com.intellij.openapi.editor.Editor;
+
+/** Tests code completion of rule target labels. */
+public class RuleTargetCompletionTest extends BuildFileIntegrationTestCase {
+
+  public void testLocalTarget() {
+    BuildFile file =
+        createBuildFile(
+            "java/com/google/BUILD",
+            "java_library(name = 'lib')",
+            "java_library(",
+            "    name = 'test',",
+            "    deps = [':']");
+
+    Editor editor = openFileInEditor(file);
+    setCaretPosition(editor, 3, "    deps = [':".length());
+
+    LookupElement[] completionItems = testFixture.completeBasic();
+    assertThat(completionItems).hasLength(1);
+    assertThat(completionItems[0].toString()).isEqualTo("':lib'");
+  }
+
+  public void testIgnoreContainingTarget() {
+    BuildFile file =
+        createBuildFile(
+            "java/com/google/BUILD", "java_library(", "    name = 'lib',", "    deps = [':']");
+
+    Editor editor = openFileInEditor(file);
+    setCaretPosition(editor, 2, "    deps = [':".length());
+
+    LookupElement[] completionItems = testFixture.completeBasic();
+    assertThat(completionItems).isEmpty();
+  }
+
+  public void testNotCodeCompletionInNameField() {
+    BuildFile file =
+        createBuildFile(
+            "java/com/google/BUILD",
+            "java_library(name = 'lib')",
+            "java_library(",
+            "    name = 'l'",
+            ")");
+
+    Editor editor = openFileInEditor(file);
+    setCaretPosition(editor, 2, "    name = 'l".length());
+
+    String[] completionItems = getCompletionItemsAsStrings();
+    assertThat(completionItems).isEmpty();
+  }
+
+  public void testNonLocalTarget() {
+    BuildFile foo = createBuildFile("java/com/google/foo/BUILD", "java_library(name = 'foo_lib')");
+
+    BuildFile bar =
+        createBuildFile(
+            "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());
+
+    String[] completionItems = getCompletionItemsAsStrings();
+    assertThat(completionItems).asList().containsExactly("'//java/com/google/foo:foo_lib'");
+  }
+
+  public void testNonLocalRulesNotCompletedWithoutColon() {
+    BuildFile foo = createBuildFile("java/com/google/foo/BUILD", "java_library(name = 'foo_lib')");
+
+    BuildFile bar =
+        createBuildFile(
+            "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());
+
+    String[] completionItems = getCompletionItemsAsStrings();
+    assertThat(completionItems).isEmpty();
+  }
+
+  public void testPackageLocalRulesCompletedWithoutColon() {
+    BuildFile file =
+        createBuildFile(
+            "java/com/google/BUILD",
+            "java_library(name = 'lib')",
+            "java_library(",
+            "    name = 'test',",
+            "    deps = ['']");
+
+    Editor editor = openFileInEditor(file);
+    setCaretPosition(editor, 3, "    deps = ['".length());
+
+    assertThat(completeIfUnique()).isTrue();
+    assertFileContents(
+        file,
+        "java_library(name = 'lib')",
+        "java_library(",
+        "    name = 'test',",
+        "    deps = ['lib']");
+  }
+
+  public void testLocalPathIgnoredForNonLocalLabels() {
+    BuildFile rootPackage = createBuildFile("java/BUILD", "java_library(name = 'root_rule')");
+
+    BuildFile otherPackage =
+        createBuildFile(
+            "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());
+
+    String[] completionItems = 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
new file mode 100644
index 0000000..9038c9b
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/SkylarkExtensionCompletionTest.java
@@ -0,0 +1,142 @@
+/*
+ * 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.completion;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
+import com.intellij.openapi.vfs.VirtualFile;
+
+/** Tests auto-complete of skylark bzl files in 'load' statements. */
+public class SkylarkExtensionCompletionTest extends BuildFileIntegrationTestCase {
+
+  private VirtualFile createAndSetCaret(String filePath, String... fileContents) {
+    VirtualFile file = createFile(filePath, fileContents);
+    testFixture.configureFromExistingVirtualFile(file);
+    return file;
+  }
+
+  public void testSimpleCase() {
+    createFile("skylark.bzl");
+    VirtualFile file = createAndSetCaret("BUILD", "load(':<caret>'");
+
+    assertThat(completeIfUnique()).isTrue();
+    assertFileContents(file, "load(':skylark.bzl'");
+  }
+
+  public void testSelfNotInResults() {
+    createFile("BUILD");
+    VirtualFile file = createAndSetCaret("self.bzl", "load(':<caret>'");
+
+    assertThat(testFixture.completeBasic()).isEmpty();
+  }
+
+  public void testSelfNotInResults2() {
+    createFile("skylark.bzl");
+    createFile("BUILD");
+    VirtualFile file = createAndSetCaret("self.bzl", "load(':<caret>'");
+
+    assertThat(completeIfUnique()).isTrue();
+    assertFileContents(file, "load(':skylark.bzl'");
+  }
+
+  public void testNoRulesInResults() {
+    createFile("java/com/google/foo/skylark.bzl");
+    createFile("java/com/google/foo/BUILD", "java_library(name = 'foo')");
+    VirtualFile file =
+        createAndSetCaret("java/com/google/bar/BUILD", "load('//java/com/google/foo:<caret>'");
+
+    assertThat(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>'");
+
+    assertThat(completeIfUnique()).isTrue();
+    assertFileContents(file, "'//java/com/google/foo:foo'");
+  }
+
+  public void testNonSkylarkFilesNotInResults() {
+    createFile("java/com/google/foo/text.txt");
+
+    VirtualFile file =
+        createAndSetCaret("java/com/google/bar/BUILD", "load('//java/com/google/foo:<caret>'");
+
+    assertThat(testFixture.completeBasic()).isEmpty();
+  }
+
+  public void testLabelStartsWithColon() {
+    createFile("java/com/google/skylark.bzl");
+    VirtualFile file = createAndSetCaret("java/com/google/BUILD", "load(':<caret>'");
+
+    assertThat(completeIfUnique()).isTrue();
+    assertFileContents(file, "load(':skylark.bzl'");
+  }
+
+  public void testLabelStartsWithSlashes() {
+    createFile("java/com/google/skylark.bzl");
+    VirtualFile file =
+        createAndSetCaret("java/com/google/BUILD", "load('//java/com/google:<caret>'");
+
+    assertThat(completeIfUnique()).isTrue();
+    assertFileContents(file, "load('//java/com/google:skylark.bzl'");
+  }
+
+  public void testLabelStartsWithSlashesWithoutColon() {
+    createFile("java/com/google/skylark.bzl");
+    VirtualFile file =
+        createAndSetCaret("java/com/google/BUILD", "load('//java/com/google<caret>'");
+
+    assertThat(completeIfUnique()).isTrue();
+    assertFileContents(file, "load('//java/com/google:skylark.bzl'");
+  }
+
+  public void testDirectoryCompletionInLoadStatement() {
+    createFile("java/com/google/skylark.bzl");
+    VirtualFile file = createAndSetCaret("java/com/google/BUILD", "load('//<caret>'");
+
+    assertThat(completeIfUnique()).isTrue();
+    assertFileContents(file, "load('//java/com/google'");
+
+    assertThat(completeIfUnique()).isTrue();
+    assertFileContents(file, "load('//java/com/google:skylark.bzl'");
+  }
+
+  public void testMultipleFiles() {
+    createFile("java/com/google/skylark.bzl");
+    createFile("java/com/google/other.bzl");
+    VirtualFile file =
+        createAndSetCaret("java/com/google/BUILD", "load('//java/com/google:<caret>'");
+
+    String[] strings = getCompletionItemsAsStrings();
+    assertThat(strings).hasLength(2);
+    assertThat(strings)
+        .asList()
+        .containsExactly("'//java/com/google:other.bzl'", "'//java/com/google:skylark.bzl'");
+  }
+
+  // relative paths in skylark extensions which lie in subdirectories
+  // are relative to the parent blaze package directory
+  public void testRelativePathInSubdirectory() {
+    createFile("java/com/google/BUILD");
+    createFile("java/com/google/nonPackageSubdirectory/skylark.bzl", "def function(): return");
+    VirtualFile file =
+        createAndSetCaret("java/com/google/nonPackageSubdirectory/other.bzl", "load(':n<caret>'");
+
+    assertThat(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
new file mode 100644
index 0000000..4c00b51
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/SkylarkExtensionSymbolCompletionTest.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.buildfile.completion;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
+import com.intellij.openapi.vfs.VirtualFile;
+
+/** Tests auto-complete of symbols loaded from skylark bzl files. */
+public class SkylarkExtensionSymbolCompletionTest extends BuildFileIntegrationTestCase {
+
+  private VirtualFile createAndSetCaret(String filePath, String... fileContents) {
+    VirtualFile file = createFile(filePath, fileContents);
+    testFixture.configureFromExistingVirtualFile(file);
+    return file;
+  }
+
+  public void testGlobalVariable() {
+    createFile("skylark.bzl", "VAR = []");
+    VirtualFile file = createAndSetCaret("BUILD", "load(':skylark.bzl', '<caret>')");
+
+    assertThat(completeIfUnique()).isTrue();
+    assertFileContents(file, "load(':skylark.bzl', 'VAR')");
+  }
+
+  public void testFunctionStatement() {
+    createFile("skylark.bzl", "def fn(param):stmt");
+    VirtualFile file = createAndSetCaret("BUILD", "load(':skylark.bzl', '<caret>')");
+
+    assertThat(completeIfUnique()).isTrue();
+    assertFileContents(file, "load(':skylark.bzl', 'fn')");
+  }
+
+  public void testMultipleOptions() {
+    createFile("skylark.bzl", "def fn(param):stmt", "VAR = []");
+    VirtualFile file = createAndSetCaret("BUILD", "load(':skylark.bzl', '<caret>')");
+
+    String[] options = getCompletionItemsAsStrings();
+    assertThat(options).asList().containsExactly("'fn'", "'VAR'");
+  }
+
+  public void testRulesNotIncluded() {
+    createFile("skylark.bzl", "java_library(name = 'lib')", "native.java_library(name = 'foo'");
+
+    VirtualFile file = createAndSetCaret("BUILD", "load(':skylark.bzl', '<caret>')");
+
+    assertThat(testFixture.completeBasic()).isEmpty();
+  }
+
+  public void testLoadedSymbols() {
+    createFile("other.bzl", "def function()");
+    createFile("skylark.bzl", "load(':other.bzl', 'function')");
+    VirtualFile file = createAndSetCaret("BUILD", "load(':skylark.bzl', '<caret>')");
+
+    assertThat(completeIfUnique()).isTrue();
+    assertFileContents(file, "load(':skylark.bzl', 'function')");
+  }
+
+  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>')");
+
+    assertThat(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
new file mode 100644
index 0000000..ac3b331
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/editor/BuildBraceMatcherTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.editor;
+
+import com.google.common.base.Joiner;
+import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
+import com.intellij.psi.PsiFile;
+
+/** Test brace matching (auto-inserting closing braces when appropriate) */
+public class BuildBraceMatcherTest extends BuildFileIntegrationTestCase {
+
+  private PsiFile setInput(String... fileContents) {
+    return testFixture.configureByText("BUILD", Joiner.on("\n").join(fileContents));
+  }
+
+  public void testClosingParenInserted() {
+    PsiFile file = setInput("java_library<caret>");
+
+    performTypingAction(testFixture.getEditor(), '(');
+
+    assertFileContents(file, "java_library()");
+  }
+
+  public void testClosingBraceInserted() {
+    PsiFile file = setInput("<caret>");
+
+    performTypingAction(testFixture.getEditor(), '{');
+
+    assertFileContents(file, "{}");
+  }
+
+  public void testClosingBracketInserted() {
+    PsiFile file = setInput("<caret>");
+
+    performTypingAction(testFixture.getEditor(), '[');
+
+    assertFileContents(file, "[]");
+  }
+
+  public void testNoClosingBracketInsertedIfLaterDanglingRBracket() {
+    PsiFile file = setInput("java_library(", "    srcs =<caret> 'source.java']", ")");
+
+    performTypingAction(testFixture.getEditor(), '[');
+
+    assertFileContents(file, "java_library(", "    srcs =[ 'source.java']", ")");
+  }
+
+  public void testClosingBracketInsertedIfFollowedByWhitespace() {
+    PsiFile file = setInput("java_library(", "    srcs =<caret> 'source.java'", ")");
+
+    performTypingAction(testFixture.getEditor(), '[');
+
+    assertFileContents(file, "java_library(", "    srcs =[] 'source.java'", ")");
+  }
+
+  public void testNoClosingBraceInsertedWhenFollowedByIdentifier() {
+    PsiFile file = setInput("hello = <caret>test");
+
+    performTypingAction(testFixture.getEditor(), '(');
+
+    assertFileContents(file, "hello = (test");
+
+    file = setInput("hello = <caret>test");
+
+    performTypingAction(testFixture.getEditor(), '[');
+
+    assertFileContents(file, "hello = [test");
+
+    file = setInput("hello = <caret>test");
+
+    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
new file mode 100644
index 0000000..eaccda5
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/editor/BuildIndentOnEnterTest.java
@@ -0,0 +1,275 @@
+/*
+ * 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.editor;
+
+import com.google.common.base.Joiner;
+import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
+import com.intellij.openapi.actionSystem.IdeActions;
+
+/** Tests that indents are inserted correctly when enter is pressed. */
+public class BuildIndentOnEnterTest extends BuildFileIntegrationTestCase {
+
+  private void setInput(String... fileContents) {
+    testFixture.configureByText("BUILD", Joiner.on("\n").join(fileContents));
+  }
+
+  private void pressEnterAndAssertResult(String... resultingFileContents) {
+    pressButton(IdeActions.ACTION_EDITOR_ENTER);
+    String s = testFixture.getFile().getText();
+    testFixture.checkResult(Joiner.on("\n").join(resultingFileContents));
+  }
+
+  public void testSimpleIndent() {
+    setInput("a=1<caret>");
+    pressEnterAndAssertResult("a=1", "<caret>");
+  }
+
+  public void testAlignInListMiddle() {
+    setInput("target = [a,<caret>", "          c]");
+    pressEnterAndAssertResult("target = [a,", "          <caret>", "          c]");
+  }
+
+  public void testNoAlignAfterList() {
+    setInput("target = [", "    arg", "]<caret>");
+    pressEnterAndAssertResult("target = [", "    arg", "]", "<caret>");
+  }
+
+  public void testAlignInDict() {
+    setInput("some_call({'aaa': 'v1',<caret>})");
+    pressEnterAndAssertResult("some_call({'aaa': 'v1',", "           <caret>})");
+  }
+
+  public void testAlignInDictInParams() { // PY-1947
+    setInput("foobar({<caret>})");
+    pressEnterAndAssertResult("foobar({", "    <caret>", "})");
+  }
+
+  public void testAlignInEmptyList() {
+    setInput("target = [<caret>]");
+    pressEnterAndAssertResult("target = [", "    <caret>", "]");
+  }
+
+  public void testAlignInEmptyParens() {
+    setInput("foo(<caret>)");
+    pressEnterAndAssertResult("foo(", "    <caret>", ")");
+  }
+
+  public void testAlignInEmptyDict() {
+    setInput("{<caret>}");
+    pressEnterAndAssertResult("{", "    <caret>", "}");
+  }
+
+  public void testAlignInEmptyTuple() {
+    setInput("(<caret>)");
+    pressEnterAndAssertResult("(", "    <caret>", ")");
+  }
+
+  public void testEnterInNonEmptyArgList() {
+    setInput("func(<caret>params=1)");
+    pressEnterAndAssertResult("func(", "    <caret>params=1)");
+  }
+
+  public void testNoIndentAfterTuple() {
+    setInput("()<caret>");
+    pressEnterAndAssertResult("()", "<caret>");
+  }
+
+  public void testNoIndentAfterList() {
+    setInput("target = [1, 2]<caret>");
+    pressEnterAndAssertResult("target = [1, 2]", "<caret>");
+  }
+
+  public void testNoIndentAfterDict() {
+    setInput("target = {}<caret>");
+    pressEnterAndAssertResult("target = {}", "<caret>");
+  }
+
+  public void testEmptyFuncallStart() {
+    setInput("func(<caret>", ")");
+    pressEnterAndAssertResult("func(", "    <caret>", ")");
+  }
+
+  public void testEmptyFuncallAfterNewlineNoIndent() {
+    setInput("func(", "<caret>)");
+    pressEnterAndAssertResult("func(", "", "<caret>)");
+  }
+
+  public void testEmptyFuncallAfterNewlineWithIndent() {
+    setInput("func(", "    <caret>", ")");
+    pressEnterAndAssertResult("func(", "    ", "    <caret>", ")");
+  }
+
+  public void testFuncallAfterFirstArg() {
+    setInput("func(", "    arg1,<caret>", ")");
+    pressEnterAndAssertResult("func(", "    arg1,", "    <caret>", ")");
+  }
+
+  public void testFuncallFirstArgOnSameLine() {
+    setInput("func(arg1, arg2,<caret>");
+    pressEnterAndAssertResult("func(arg1, arg2,", "     <caret>");
+  }
+
+  public void testFuncallFirstArgOnSameLineWithClosingBrace() {
+    setInput("func(arg1, arg2,<caret>)");
+    pressEnterAndAssertResult("func(arg1, arg2,", "     <caret>)");
+  }
+
+  public void testNonEmptyDict() {
+    setInput("{key1 : value1,<caret>}");
+    pressEnterAndAssertResult("{key1 : value1,", " <caret>}");
+  }
+
+  public void testNonEmptyDictFirstArgIndented() {
+    setInput("{", "    key1 : value1,<caret>" + "}");
+    pressEnterAndAssertResult("{", "    key1 : value1,", "    <caret>" + "}");
+  }
+
+  public void testEmptyDictAlreadyIndented() {
+    setInput("{", "    <caret>" + "}");
+    pressEnterAndAssertResult("{", "    ", "    <caret>" + "}");
+  }
+
+  public void testEmptyParamIndent() {
+    setInput("def fn(<caret>)");
+    pressEnterAndAssertResult("def fn(", "    <caret>", ")");
+  }
+
+  public void testNonEmptyParamIndent() {
+    setInput("def fn(param1,<caret>)");
+    pressEnterAndAssertResult("def fn(param1,", "       <caret>)");
+  }
+
+  public void testFunctionDefAfterColon() {
+    setInput("def fn():<caret>");
+    pressEnterAndAssertResult("def fn():", "  <caret>");
+  }
+
+  // def fn():stmt* (THIS IS CURRENTLY BROKEN -- shouldn't indent but does)
+  public void testFunctionDefSingleStatement() {
+    setInput("def fn():stmt<caret>");
+    pressEnterAndAssertResult("def fn():stmt", "<caret>");
+  }
+
+  public void testFunctionDefAfterFirstSuiteStatement() {
+    setInput("def fn():", "  stmt1<caret>");
+    pressEnterAndAssertResult("def fn():", "  stmt1", "  <caret>");
+  }
+
+  public void testNoIndentAfterSuiteDedentOnEmptyLine() {
+    setInput("def fn():", "  stmt1", "  stmt2", "<caret>");
+    pressEnterAndAssertResult("def fn():", "  stmt1", "  stmt2", "", "<caret>");
+  }
+
+  public void testIndentAfterIf() {
+    setInput("if condition:<caret>");
+    pressEnterAndAssertResult("if condition:", "  <caret>");
+  }
+
+  public void testNoIndentAfterIfPlusStatement() {
+    setInput("if condition:stmt<caret>");
+    pressEnterAndAssertResult("if condition:stmt", "<caret>");
+  }
+
+  public void testIndentAfterElseIf() {
+    setInput("if condition:", "  stmt", "elif:<caret>");
+    pressEnterAndAssertResult("if condition:", "  stmt", "elif:", "  <caret>");
+  }
+
+  public void testNoIndentAfterElseIfPlusStatement() {
+    setInput("if condition:", "  stmt", "elif:stmt<caret>");
+    pressEnterAndAssertResult("if condition:", "  stmt", "elif:stmt", "<caret>");
+  }
+
+  public void testIndentAfterElse() {
+    setInput("if condition:", "  stmt", "else:<caret>");
+    pressEnterAndAssertResult("if condition:", "  stmt", "else:", "  <caret>");
+  }
+
+  public void testNoIndentAfterElsePlusStatement() {
+    setInput("if condition:", "  stmt", "else:stmt<caret>");
+    pressEnterAndAssertResult("if condition:", "  stmt", "else:stmt", "<caret>");
+  }
+
+  public void testIndentAfterForColon() {
+    setInput("for x in list:<caret>");
+    pressEnterAndAssertResult("for x in list:", "  <caret>");
+  }
+
+  public void testNoIndentAfterForPlusStatement() {
+    setInput("for x in list:do_action<caret>");
+    pressEnterAndAssertResult("for x in list:do_action", "<caret>");
+  }
+
+  public void testCommonRuleCase1() {
+    setInput("java_library(", "    name = 'lib'", "    srcs = [<caret>]");
+    pressEnterAndAssertResult(
+        "java_library(", "    name = 'lib'", "    srcs = [", "        <caret>", "    ]");
+  }
+
+  public void testCommonRuleCase2() {
+    setInput(
+        "java_library(", "    name = 'lib'", "    srcs = [", "        'source',<caret>", "    ]");
+    pressEnterAndAssertResult(
+        "java_library(",
+        "    name = 'lib'",
+        "    srcs = [",
+        "        'source',",
+        "        <caret>",
+        "    ]");
+  }
+
+  public void testCommonRuleCase3() {
+    setInput("java_library(", "    name = 'lib'", "    srcs = ['first',<caret>]");
+    pressEnterAndAssertResult(
+        "java_library(", "    name = 'lib'", "    srcs = ['first',", "            <caret>]");
+  }
+
+  public void testDedentAfterReturn() {
+    setInput("def fn():", "  return None<caret>");
+    pressEnterAndAssertResult("def fn():", "  return None", "<caret>");
+  }
+
+  public void testDedentAfterEmptyReturn() {
+    setInput("def fn():", "  return<caret>");
+    pressEnterAndAssertResult("def fn():", "  return", "<caret>");
+  }
+
+  public void testDedentAfterReturnWithTrailingWhitespace() {
+    setInput("def fn():", "  return<caret>   ");
+    pressEnterAndAssertResult("def fn():", "  return", "<caret>");
+  }
+
+  public void testDedentAfterComplexReturn() {
+    setInput("def fn():", "  return a == b<caret>");
+    pressEnterAndAssertResult("def fn():", "  return a == b", "<caret>");
+  }
+
+  public void testDedentAfterPass() {
+    setInput("def fn():", "  pass<caret>");
+    pressEnterAndAssertResult("def fn():", "  pass", "<caret>");
+  }
+
+  public void testDedentAfterPassInLoop() {
+    setInput("def fn():", "  for a in (1,2,3):", "    pass<caret>");
+    pressEnterAndAssertResult("def fn():", "  for a in (1,2,3):", "    pass", "  <caret>");
+  }
+
+  // regression test for b/29564041
+  public void testNoExceptionPressingEnterAtStartOfFile() {
+    setInput("#<caret>");
+    pressEnterAndAssertResult("#", "<caret>");
+  }
+}
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
new file mode 100644
index 0000000..459d942
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/editor/BuildQuoteHandlerTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.editor;
+
+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;
+
+/** Tests for BuildQuoteHandler. */
+public class BuildQuoteHandlerTest extends BuildFileIntegrationTestCase {
+
+  public void testClosingQuoteInserted() {
+    BuildFile file = createBuildFile("BUILD", "");
+
+    performTypingAction(file, '"');
+    assertFileContents(file, "\"\"");
+  }
+
+  public void testClosingSingleQuoteInserted() {
+    BuildFile file = createBuildFile("BUILD", "");
+
+    performTypingAction(file, '\'');
+    assertFileContents(file, "''");
+  }
+
+  public void testClosingTripleQuoteInserted() {
+    BuildFile file = createBuildFile("BUILD", "");
+
+    performTypingAction(file, '"');
+    performTypingAction(file, '"');
+    performTypingAction(file, '"');
+    assertFileContents(file, "\"\"\"\"\"\"");
+  }
+
+  public void testClosingTripleSingleQuoteInserted() {
+    BuildFile file = createBuildFile("BUILD", "");
+
+    performTypingAction(file, '\'');
+    performTypingAction(file, '\'');
+    performTypingAction(file, '\'');
+    assertFileContents(file, "''''''");
+  }
+
+  public void testOnlyCaretMovedWhenCompletingExistingClosingQuotes() {
+    BuildFile file = createBuildFile("BUILD", "'text<caret>'", "laterContents");
+
+    testFixture.configureFromExistingVirtualFile(file.getVirtualFile());
+
+    performTypingAction(file, '\'');
+
+    testFixture.checkResult(Joiner.on("\n").join("'text'<caret>", "laterContents"));
+  }
+
+  public void testOnlyCaretMovedWhenCompletingExistingClosingTripleQuotes() {
+    BuildFile file = createBuildFile("BUILD", "'''text<caret>'''", "laterContents");
+
+    testFixture.configureFromExistingVirtualFile(file.getVirtualFile());
+
+    performTypingAction(file, '\'');
+
+    testFixture.checkResult(Joiner.on("\n").join("'''text'<caret>''", "laterContents"));
+
+    performTypingAction(file, '\'');
+
+    testFixture.checkResult(Joiner.on("\n").join("'''text''<caret>'", "laterContents"));
+
+    performTypingAction(file, '\'');
+
+    testFixture.checkResult(Joiner.on("\n").join("'''text'''<caret>", "laterContents"));
+  }
+
+  public void testAdditionalTripleQuotesNotInsertedWhenClosingQuotes() {
+    BuildFile file = createBuildFile("BUILD", "'''text''<caret>", "laterContents");
+
+    testFixture.configureFromExistingVirtualFile(file.getVirtualFile());
+
+    performTypingAction(file, '\'');
+
+    testFixture.checkResult(Joiner.on("\n").join("'''text'''<caret>", "laterContents"));
+  }
+
+  public void testAdditionalQuoteNotInsertedWhenClosingQuotes() {
+    BuildFile file = createBuildFile("BUILD", "'text<caret>", "laterContents");
+
+    testFixture.configureFromExistingVirtualFile(file.getVirtualFile());
+
+    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
new file mode 100644
index 0000000..554b127
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/editor/EnterInLineCommentTest.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.base.lang.buildfile.editor;
+
+import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
+import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
+import com.intellij.openapi.editor.Editor;
+
+/** Test that comments are continued when creating a newline mid comment. */
+public class EnterInLineCommentTest extends BuildFileIntegrationTestCase {
+
+  public void testInternalNewlineCommented() {
+    BuildFile file = createBuildFile("BUILD", "# first line comment", "# second line comment");
+
+    Editor editor = openFileInEditor(file.getVirtualFile());
+    setCaretPosition(editor, 1, "# second ".length());
+    performTypingAction(editor, '\n');
+    assertFileContents(file, "# first line comment", "# second ", "# line comment");
+    assertCaretPosition(editor, 2, 2);
+  }
+
+  public void testNewlineAtEndOfComment() {
+    BuildFile file = createBuildFile("BUILD", "# first line comment", "# second line comment");
+
+    Editor editor = openFileInEditor(file.getVirtualFile());
+    setCaretPosition(editor, 1, "# second line comment".length());
+    performTypingAction(editor, '\n');
+    assertFileContents(file, "# first line comment", "# second line comment", "");
+    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
new file mode 100644
index 0000000..c7f9152
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/BlazePackageFindUsagesTest.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.findusages;
+
+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.StringLiteral;
+import com.google.idea.blaze.base.lang.buildfile.search.FindUsages;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiReference;
+
+/**
+ * Tests that all references to a blaze package (including in the package components of labels) are
+ * found by the 'Find Usages' action.
+ */
+public class BlazePackageFindUsagesTest extends BuildFileIntegrationTestCase {
+
+  public void testDirectReferenceFound() {
+    BuildFile foo = createBuildFile("java/com/google/foo/BUILD");
+
+    BuildFile bar =
+        createBuildFile(
+            "java/com/google/bar/BUILD",
+            "package_group(name = \"grp\", packages = [\"//java/com/google/foo\"])");
+
+    PsiReference[] references = FindUsages.findAllReferences(foo);
+    assertThat(references).hasLength(1);
+
+    PsiElement ref = references[0].getElement();
+    assertThat(ref).isInstanceOf(StringLiteral.class);
+    assertThat(ref.getContainingFile()).isEqualTo(bar);
+  }
+
+  public void testLabelFragmentReferenceFound() {
+    BuildFile foo = createBuildFile("java/com/google/foo/BUILD", "java_library(name = \"lib\")");
+
+    BuildFile bar =
+        createBuildFile(
+            "java/com/google/bar/BUILD",
+            "java_library(name = \"lib2\", exports = [\"//java/com/google/foo:lib\"])");
+
+    PsiReference[] references = FindUsages.findAllReferences(foo);
+    assertThat(references).hasLength(1);
+
+    PsiElement ref = references[0].getElement();
+    assertThat(ref).isInstanceOf(StringLiteral.class);
+    assertThat(ref.getContainingFile()).isEqualTo(bar);
+  }
+
+  /** If these don't resolve, directory rename refactoring won't update all labels correctly */
+  public void testInternalReferencesResolve() {
+    BuildFile buildFile =
+        createBuildFile(
+            "java/com/google/BUILD",
+            "java_library(name = \"lib\")",
+            "java_library(name = \"other\", deps = [\"//java/com/google:lib\"])");
+
+    PsiReference[] references = FindUsages.findAllReferences(buildFile);
+    assertThat(references).hasLength(1);
+  }
+}
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
new file mode 100644
index 0000000..a02d996
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/ExternalFileUsagesTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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.findusages;
+
+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.Argument;
+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.StringLiteral;
+import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
+import com.google.idea.blaze.base.lang.buildfile.search.FindUsages;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiReference;
+
+/**
+ * Tests that references to external files (e.g. Java classes, text files) are found by the 'Find
+ * Usages' action
+ */
+public class ExternalFileUsagesTest extends BuildFileIntegrationTestCase {
+
+  public void testJavaClassUsagesFound() {
+    PsiFile javaFile =
+        createPsiFile(
+            "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\"])");
+
+    PsiReference[] references = FindUsages.findAllReferences(javaFile);
+    assertThat(references).hasLength(1);
+
+    Argument.Keyword arg =
+        buildFile.findChildByClass(FuncallExpression.class).getKeywordArgument("srcs");
+
+    PsiElement ref = references[0].getElement();
+    assertThat(ref).isInstanceOf(StringLiteral.class);
+    assertThat(PsiUtils.getParentOfType(ref, Argument.Keyword.class)).isEqualTo(arg);
+  }
+
+  public void testTextFileUsagesFound() {
+    PsiFile textFile = createPsiFile("com/google/foo/data.txt");
+
+    BuildFile buildFile =
+        createBuildFile(
+            "com/google/foo/BUILD",
+            "filegroup(name = \"lib\", srcs = [\"data.txt\"])",
+            "filegroup(name = \"lib2\", srcs = [\"//com/google/foo:data.txt\"])");
+
+    PsiReference[] references = FindUsages.findAllReferences(textFile);
+    assertThat(references).hasLength(2);
+  }
+
+  public void testInvalidReferenceDoesntResolve() {
+    BuildFile packageFoo = createBuildFile("com/google/foo/BUILD");
+    PsiFile textFileInFoo = createPsiFile("com/google/foo/data.txt");
+
+    BuildFile packageBar =
+        createBuildFile(
+            "com/google/bar/BUILD", "filegroup(name = \"lib\", srcs = [\":data.txt\"])");
+
+    PsiReference[] references = FindUsages.findAllReferences(textFileInFoo);
+    assertThat(references).isEmpty();
+  }
+
+  public void testSkylarkExtensionUsagesFound() {
+    BuildFile ext = createBuildFile("com/google/foo/ext.bzl", "def fn(): return");
+    createBuildFile(
+        "com/google/foo/BUILD",
+        "load(':ext.bzl', 'fn')",
+        "load('ext.bzl', 'fn')",
+        "load('//com/google/foo:ext.bzl', 'fn')");
+
+    PsiReference[] references = FindUsages.findAllReferences(ext);
+    assertThat(references).hasLength(3);
+  }
+
+  public void testSkylarkExtensionInSubDirectoryUsagesFound() {
+    BuildFile ext = createBuildFile("com/google/foo/subdir/ext.bzl", "def fn(): return");
+    createBuildFile(
+        "com/google/foo/BUILD",
+        "load(':subdir/ext.bzl', 'fn')",
+        "load('subdir/ext.bzl', 'fn')",
+        "load('//com/google/foo:subdir/ext.bzl', 'fn')");
+
+    PsiReference[] references = FindUsages.findAllReferences(ext);
+    assertThat(references).hasLength(3);
+  }
+
+  public void testSkylarkExtensionInSubDirectoryOfDifferentPackage() {
+    BuildFile otherPkg = createBuildFile("com/google/foo/BUILD");
+    BuildFile ext = createBuildFile("com/google/foo/subdir/ext.bzl", "def fn(): return");
+
+    createBuildFile("com/google/bar/BUILD", "load('//com/google/foo:subdir/ext.bzl', 'fn')");
+
+    PsiReference[] references = FindUsages.findAllReferences(ext);
+    assertThat(references).hasLength(1);
+  }
+
+  public void testSkylarkExtensionReferencedFromSubpackage() {
+    BuildFile pkg = createBuildFile("com/google/foo/BUILD");
+    BuildFile ext1 = createBuildFile("com/google/foo/subdir/testing.bzl", "def fn(): return");
+    BuildFile ext2 =
+        createBuildFile("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
new file mode 100644
index 0000000..b16e666
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/FindParameterUsagesTest.java
@@ -0,0 +1,69 @@
+/*
+ * 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.findusages;
+
+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.FunctionStatement;
+import com.google.idea.blaze.base.lang.buildfile.psi.ParameterList;
+import com.google.idea.blaze.base.lang.buildfile.search.FindUsages;
+import com.intellij.psi.PsiReference;
+
+/**
+ * Tests that usages of function parameters (i.e. by named args in funcall expressions) are found
+ */
+public class FindParameterUsagesTest extends BuildFileIntegrationTestCase {
+
+  public void testLocalReferences() {
+    BuildFile buildFile =
+        createBuildFile(
+            "java/com/google/build_defs.bzl",
+            "def function(arg1, arg2)",
+            "function(arg1 = 1, arg2 = \"name\")");
+
+    FunctionStatement fn = buildFile.findChildByClass(FunctionStatement.class);
+    ParameterList params = fn.getParameterList();
+
+    PsiReference[] references = FindUsages.findAllReferences(params.findParameterByName("arg1"));
+    assertThat(references).hasLength(1);
+
+    references = FindUsages.findAllReferences(params.findParameterByName("arg2"));
+    assertThat(references).hasLength(1);
+  }
+
+  public void testNonLocalReferences() {
+    BuildFile foo = createBuildFile("java/com/google/build_defs.bzl", "def function(arg1, arg2)");
+
+    BuildFile bar =
+        createBuildFile(
+            "java/com/google/other/BUILD",
+            "load(\"//java/com/google:build_defs.bzl\", \"function\")",
+            "function(arg1 = 1, arg2 = \"name\", extra = x)");
+
+    FunctionStatement fn = foo.findChildByClass(FunctionStatement.class);
+    ParameterList params = fn.getParameterList();
+
+    PsiReference[] references = FindUsages.findAllReferences(params.findParameterByName("arg1"));
+    assertThat(references).hasLength(1);
+    assertThat(references[0].getElement().getContainingFile()).isEqualTo(bar);
+
+    references = FindUsages.findAllReferences(params.findParameterByName("arg2"));
+    assertThat(references).hasLength(1);
+    assertThat(references[0].getElement().getContainingFile()).isEqualTo(bar);
+  }
+}
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
new file mode 100644
index 0000000..4d69567
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/FindRuleUsagesTest.java
@@ -0,0 +1,131 @@
+/*
+ * 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.findusages;
+
+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.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.ListLiteral;
+import com.google.idea.blaze.base.lang.buildfile.psi.StringLiteral;
+import com.google.idea.blaze.base.lang.buildfile.search.FindUsages;
+import com.intellij.codeInsight.navigation.actions.GotoDeclarationAction;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiReference;
+
+/** Tests that usages of build rules are found */
+public class FindRuleUsagesTest extends BuildFileIntegrationTestCase {
+
+  public void testLocalReferences() {
+    BuildFile buildFile =
+        createBuildFile(
+            "java/com/google/BUILD",
+            "java_library(name = \"target\")",
+            "top_level_ref = \":target\"",
+            "java_library(name = \"other\", deps = [\":target\"]");
+
+    FuncallExpression target = buildFile.findChildByClass(FuncallExpression.class);
+
+    PsiReference[] references = FindUsages.findAllReferences(target);
+    assertThat(references).hasLength(2);
+
+    PsiElement firstRef = references[0].getElement();
+    assertThat(firstRef).isInstanceOf(StringLiteral.class);
+    assertThat(firstRef.getParent()).isInstanceOf(AssignmentStatement.class);
+
+    PsiElement secondRef = references[1].getElement();
+    assertThat(secondRef).isInstanceOf(StringLiteral.class);
+    assertThat(secondRef.getParent()).isInstanceOf(ListLiteral.class);
+  }
+
+  // test full package references, made locally
+  public void testLocalFullReference() {
+    BuildFile buildFile =
+        createBuildFile(
+            "java/com/google/BUILD",
+            "java_library(name = \"target\")",
+            "java_library(name = \"other\", deps = [\"//java/com/google:target\"]");
+
+    FuncallExpression target = buildFile.findChildByClass(FuncallExpression.class);
+
+    PsiReference[] references = FindUsages.findAllReferences(target);
+    assertThat(references).hasLength(1);
+
+    PsiElement ref = references[0].getElement();
+    assertThat(ref).isInstanceOf(StringLiteral.class);
+    assertThat(ref.getParent()).isInstanceOf(ListLiteral.class);
+  }
+
+  public void testNonLocalReferences() {
+    BuildFile targetFile =
+        createBuildFile("java/com/google/foo/BUILD", "java_library(name = \"target\")");
+
+    BuildFile refFile =
+        createBuildFile(
+            "java/com/google/bar/BUILD",
+            "java_library(name = \"ref\", exports = [\"//java/com/google/foo:target\"])");
+
+    FuncallExpression target = targetFile.findChildByClass(FuncallExpression.class);
+
+    PsiReference[] references = FindUsages.findAllReferences(target);
+    assertThat(references).hasLength(1);
+
+    PsiElement ref = references[0].getElement();
+    assertThat(ref).isInstanceOf(StringLiteral.class);
+    assertThat(ref.getContainingFile()).isEqualTo(refFile);
+  }
+
+  public void testFindUsagesWorksFromNameString() {
+    BuildFile targetFile =
+        createBuildFile("java/com/google/foo/BUILD", "java_library(name = \"tar<caret>get\")");
+
+    BuildFile refFile =
+        createBuildFile(
+            "java/com/google/bar/BUILD",
+            "java_library(name = \"ref\", exports = [\"//java/com/google/foo:target\"])");
+
+    testFixture.configureFromExistingVirtualFile(targetFile.getVirtualFile());
+
+    PsiElement targetElement =
+        GotoDeclarationAction.findElementToShowUsagesOf(
+            testFixture.getEditor(), testFixture.getEditor().getCaretModel().getOffset());
+
+    PsiReference[] references = FindUsages.findAllReferences(targetElement);
+    assertThat(references).hasLength(1);
+
+    PsiElement ref = references[0].getElement();
+    assertThat(ref).isInstanceOf(StringLiteral.class);
+    assertThat(ref.getContainingFile()).isEqualTo(refFile);
+  }
+
+  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\")");
+
+    BuildFile refFile =
+        createBuildFile(
+            "java/com/google/bar/BUILD", "java_library(name = \"ref\", exports = [\":target\"])");
+
+    FuncallExpression target = targetFile.findChildByClass(FuncallExpression.class);
+    assertThat(target).isNotNull();
+
+    PsiReference[] references = FindUsages.findAllReferences(target);
+    assertThat(references).hasLength(0);
+  }
+}
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
new file mode 100644
index 0000000..22be9b5
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/FunctionStatementUsagesTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.findusages;
+
+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.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.StringLiteral;
+import com.google.idea.blaze.base.lang.buildfile.search.FindUsages;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiReference;
+
+/** Tests that usages of function declarations are found */
+public class FunctionStatementUsagesTest extends BuildFileIntegrationTestCase {
+
+  public void testLocalReferences() {
+    BuildFile buildFile =
+        createBuildFile(
+            "java/com/google/build_defs.bzl",
+            "def function(name, srcs, deps):",
+            "    # function body",
+            "function(name = \"foo\")");
+
+    FunctionStatement funcDef = buildFile.findChildByClass(FunctionStatement.class);
+
+    PsiReference[] references = FindUsages.findAllReferences(funcDef);
+    assertThat(references).hasLength(1);
+
+    PsiElement ref = references[0].getElement();
+    assertThat(ref).isInstanceOf(FuncallExpression.class);
+  }
+
+  public void testLoadedFunctionReferences() {
+    BuildFile extFile =
+        createBuildFile("java/com/google/build_defs.bzl", "def function(name, deps)");
+
+    BuildFile buildFile =
+        createBuildFile(
+            "java/com/google/BUILD",
+            "load(",
+            "\"//java/com/google:build_defs.bzl\",",
+            "\"function\"",
+            ")");
+
+    FunctionStatement funcDef = extFile.findChildByClass(FunctionStatement.class);
+    LoadStatement load = buildFile.firstChildOfClass(LoadStatement.class);
+
+    PsiReference[] references = FindUsages.findAllReferences(funcDef);
+    assertThat(references).hasLength(1);
+
+    PsiElement ref = references[0].getElement();
+    assertThat(ref).isInstanceOf(StringLiteral.class);
+    assertThat(ref.getParent()).isEqualTo(load);
+  }
+
+  public void testFuncallReference() {
+    BuildFile extFile =
+        createBuildFile("java/com/google/tools/build_defs.bzl", "def function(name, deps)");
+
+    BuildFile buildFile =
+        createBuildFile(
+            "java/com/google/BUILD",
+            "load(",
+            "\"//java/com/google/tools:build_defs.bzl\",",
+            "\"function\"",
+            ")",
+            "function(name = \"name\", deps = []");
+
+    FunctionStatement function = extFile.firstChildOfClass(FunctionStatement.class);
+    FuncallExpression funcall = buildFile.firstChildOfClass(FuncallExpression.class);
+
+    PsiReference[] references = FindUsages.findAllReferences(function);
+    assertThat(references).hasLength(2);
+
+    assertThat(references[1].getElement()).isEqualTo(funcall);
+  }
+}
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
new file mode 100644
index 0000000..c1de37e
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/GlobFindUsagesTest.java
@@ -0,0 +1,169 @@
+/*
+ * 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.findusages;
+
+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.GlobExpression;
+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.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiManager;
+import com.intellij.psi.PsiReference;
+import com.intellij.psi.impl.PsiManagerEx;
+import com.intellij.psi.impl.file.impl.FileManager;
+import com.intellij.testFramework.LightVirtualFile;
+
+/** Tests that file references in globs are included in the 'find usages' results. */
+public class GlobFindUsagesTest extends BuildFileIntegrationTestCase {
+
+  public void testSimpleGlobReferencingSingleFile() {
+    PsiFile ref = createPsiFile("java/com/google/Test.java");
+    BuildFile file = createBuildFile("java/com/google/BUILD", "glob(['**/*.java'])");
+
+    PsiReference[] references = FindUsages.findAllReferences(ref);
+    assertThat(references).hasLength(1);
+    assertThat(references[0].getElement()).isInstanceOf(GlobExpression.class);
+  }
+
+  public void testSimpleGlobReferencingSingleFile2() {
+    PsiFile ref = createPsiFile("java/com/google/Test.java");
+    BuildFile file = createBuildFile("java/com/google/BUILD", "glob(['*.java'])");
+
+    GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
+
+    PsiReference[] references = FindUsages.findAllReferences(ref);
+    assertThat(references).hasLength(1);
+    assertThat(references[0].getElement()).isEqualTo(glob);
+  }
+
+  public void testSimpleGlobReferencingSingleFile3() {
+    PsiFile ref = createPsiFile("java/com/google/Test.java");
+    BuildFile file = createBuildFile("java/com/google/BUILD", "glob(['T*t.java'])");
+
+    GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
+
+    PsiReference[] references = FindUsages.findAllReferences(ref);
+    assertThat(references).hasLength(1);
+    assertThat(references[0].getElement()).isEqualTo(glob);
+  }
+
+  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'])");
+
+    GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
+
+    PsiReference[] references = FindUsages.findAllReferences(ref1);
+    assertThat(references).hasLength(1);
+    assertThat(references[0].getElement()).isEqualTo(glob);
+
+    references = FindUsages.findAllReferences(ref2);
+    assertThat(references).hasLength(1);
+    assertThat(references[0].getElement()).isEqualTo(glob);
+  }
+
+  public void testFindsSubDirectories() {
+    PsiFile ref1 = createPsiFile("java/com/google/test/Test.java");
+    BuildFile file = createBuildFile("java/com/google/BUILD", "glob(['**/*.java'])");
+
+    GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
+
+    PsiReference[] references = FindUsages.findAllReferences(ref1);
+    assertThat(references).hasLength(1);
+    assertThat(references[0].getElement()).isEqualTo(glob);
+  }
+
+  public void testGlobWithExcludes() {
+    PsiFile test = createPsiFile("java/com/google/tests/Test.java");
+    PsiFile foo = createPsiFile("java/com/google/Foo.java");
+    BuildFile file =
+        createBuildFile(
+            "java/com/google/BUILD",
+            "glob(" + "  ['**/*.java']," + "  exclude = ['tests/*.java'])");
+
+    GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
+
+    PsiReference[] references = FindUsages.findAllReferences(foo);
+    assertThat(references).hasLength(1);
+    assertThat(references[0].getElement()).isEqualTo(glob);
+
+    assertThat(FindUsages.findAllReferences(test)).isEmpty();
+  }
+
+  public void testIncludeDirectories() {
+    PsiDirectory dir = createPsiDirectory("java/com/google/tests");
+    PsiFile test = createPsiFile("java/com/google/tests/Test.java");
+    PsiFile foo = createPsiFile("java/com/google/Foo.java");
+    BuildFile file =
+        createBuildFile(
+            "java/com/google/BUILD",
+            "glob(" + "  ['**/*']," + "  exclude = ['BUILD']," + "  exclude_directories = 0)");
+
+    GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
+
+    PsiReference[] references = FindUsages.findAllReferences(dir);
+    assertThat(references).hasLength(1);
+    assertThat(references[0].getElement()).isEqualTo(glob);
+  }
+
+  public void testExcludeDirectories() {
+    PsiDirectory dir = createPsiDirectory("java/com/google/tests");
+    PsiFile test = createPsiFile("java/com/google/tests/Test.java");
+    PsiFile foo = createPsiFile("java/com/google/Foo.java");
+    BuildFile file =
+        createBuildFile(
+            "java/com/google/BUILD", "glob(" + "  ['**/*']," + "  exclude = ['BUILD'])");
+
+    GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
+
+    PsiReference[] references = FindUsages.findAllReferences(dir);
+    assertThat(references).isEmpty();
+  }
+
+  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");
+
+    GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(pkg, GlobExpression.class);
+
+    PsiReference[] references = FindUsages.findAllReferences(subPkg);
+    assertThat(references).isEmpty();
+  }
+
+  // regression test for b/29267289
+  public void testInMemoryFileHandledGracefully() {
+    BuildFile pkg = createBuildFile("java/com/google/BUILD", "glob(['**/*.java'])");
+
+    LightVirtualFile inMemoryFile =
+        new LightVirtualFile("mockProjectViewFile", ProjectViewLanguage.INSTANCE, "");
+
+    FileManager fileManager =
+        ((PsiManagerEx) PsiManager.getInstance(getProject())).getFileManager();
+    fileManager.setViewProvider(
+        inMemoryFile, fileManager.createFileViewProvider(inMemoryFile, true));
+
+    PsiFile psiFile = fileManager.findFile(inMemoryFile);
+
+    PsiReference[] references = FindUsages.findAllReferences(psiFile);
+  }
+}
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
new file mode 100644
index 0000000..50fafb4
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/LocalVariableUsagesTest.java
@@ -0,0 +1,82 @@
+/*
+ * 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.findusages;
+
+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.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.TargetExpression;
+import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
+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.intellij.psi.PsiElement;
+import com.intellij.psi.PsiReference;
+
+/**
+ * Tests that references to local variables are found by the 'Find Usages' action TODO: Support
+ * comprehension suffix, and add test for it
+ */
+public class LocalVariableUsagesTest extends BuildFileIntegrationTestCase {
+
+  public void testLocalReferences() {
+    BuildFile buildFile =
+        createBuildFile(
+            "java/com/google/BUILD",
+            "localVar = 5",
+            "funcall(localVar)",
+            "def function(name):",
+            "    tempVar = localVar");
+
+    TargetExpression target =
+        buildFile.findChildByClass(AssignmentStatement.class).getLeftHandSideExpression();
+
+    PsiReference[] references = FindUsages.findAllReferences(target);
+    assertThat(references).hasLength(2);
+
+    FuncallExpression funcall = buildFile.findChildByClass(FuncallExpression.class);
+    assertThat(funcall).isNotNull();
+
+    PsiElement firstRef = references[0].getElement();
+    assertThat(PsiUtils.getParentOfType(firstRef, FuncallExpression.class)).isEqualTo(funcall);
+
+    FunctionStatement function = buildFile.findChildByClass(FunctionStatement.class);
+    assertThat(function).isNotNull();
+
+    PsiElement secondRef = references[1].getElement();
+    assertThat(secondRef.getParent()).isInstanceOf(AssignmentStatement.class);
+    assertThat(PsiUtils.getParentOfType(secondRef, FunctionStatement.class)).isEqualTo(function);
+  }
+
+  // the case where a symbol is the target of multiple assignment statements
+  public void testMultipleAssignments() {
+    BuildFile buildFile =
+        createBuildFile("java/com/google/BUILD", "var = 5", "var += 1", "var = 0");
+
+    TargetExpression target =
+        buildFile.findChildByClass(AssignmentStatement.class).getLeftHandSideExpression();
+
+    PsiReference[] references = FindUsages.findAllReferences(target);
+    assertThat(references).hasLength(2);
+
+    assertThat(references[0]).isInstanceOf(LocalReference.class);
+    assertThat(references[1]).isInstanceOf(TargetReference.class);
+  }
+}
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
new file mode 100644
index 0000000..8581cbc
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/formatting/BuildFileFoldingBuilderTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.formatting;
+
+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.LoadStatement;
+import com.intellij.lang.folding.FoldingDescriptor;
+import com.intellij.openapi.editor.Editor;
+
+/** Tests for {@link BuildFileFoldingBuilder}. */
+public class BuildFileFoldingBuilderTest extends BuildFileIntegrationTestCase {
+
+  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():");
+
+    getFoldingRegions(file);
+  }
+
+  public void testFuncDefStatementsFolded() {
+    BuildFile file =
+        createBuildFile(
+            "java/com/google/BUILD",
+            "# multi-line comment, not folded",
+            "# second line of comment",
+            "def function(arg1, arg2):",
+            "    stmt1",
+            "    stmt2",
+            "",
+            "variable = 1");
+
+    FoldingDescriptor[] foldingRegions = getFoldingRegions(file);
+    assertThat(foldingRegions).hasLength(1);
+    assertThat(foldingRegions[0].getElement().getPsi())
+        .isEqualTo(file.findFunctionInScope("function"));
+  }
+
+  public void testRulesFolded() {
+    BuildFile file =
+        createBuildFile(
+            "java/com/google/BUILD",
+            "java_library(",
+            "    name = 'lib',",
+            "    srcs = glob(['*.java']),",
+            ")");
+
+    FoldingDescriptor[] foldingRegions = getFoldingRegions(file);
+    assertThat(foldingRegions).hasLength(1);
+    assertThat(foldingRegions[0].getElement().getPsi()).isEqualTo(file.findRule("lib"));
+  }
+
+  public void testLoadStatementFolded() {
+    BuildFile file =
+        createBuildFile(
+            "java/com/google/BUILD",
+            "load(",
+            "   '//java/com/foo/build_defs.bzl',",
+            "   'function1',",
+            "   'function2',",
+            ")");
+
+    FoldingDescriptor[] foldingRegions = getFoldingRegions(file);
+    assertThat(foldingRegions).hasLength(1);
+    assertThat(foldingRegions[0].getElement().getPsi())
+        .isEqualTo(file.findChildByClass(LoadStatement.class));
+  }
+
+  private FoldingDescriptor[] getFoldingRegions(BuildFile file) {
+    Editor editor = openFileInEditor(file.getVirtualFile());
+    return new BuildFileFoldingBuilder().buildFoldRegions(file.getNode(), editor.getDocument());
+  }
+}
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
new file mode 100644
index 0000000..85b4d65
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/language/BuildFileTypeTest.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.lang.buildfile.language;
+
+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.intellij.psi.PsiFile;
+
+/** Tests that BUILD files are recognized as such */
+public class BuildFileTypeTest extends BuildFileIntegrationTestCase {
+
+  public void testSkylarkExtensionRecognized() {
+    PsiFile file = createPsiFile("java/com/google/foo/build_defs.bzl");
+    assertThat(file).isInstanceOf(BuildFile.class);
+  }
+
+  public void testExactNameMatch() {
+    PsiFile file = createPsiFile("java/com/google/foo/BUILD");
+    assertThat(file).isInstanceOf(BuildFile.class);
+  }
+
+  /**
+   * We may want to support these in the future (and in the meantime the user can manually have them
+   * recognized as BUILD files, for syntax highlighting, etc.).<br>
+   * Currently, turned off by default because references won't resolve correctly -- they'll point
+   * back to normal BUILD files.
+   */
+  public void testOtherBuildFilesNotRecognized() {
+    PsiFile file = createPsiFile("java/com/google/foo/BUILD.tools");
+    assertThat(file).isNotInstanceOf(BuildFile.class);
+
+    file = createPsiFile("java/com/google/foo/BUILD.bazel");
+    assertThat(file).isNotInstanceOf(BuildFile.class);
+  }
+}
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/lexer/AbstractLexerTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/lexer/AbstractLexerTest.java
new file mode 100644
index 0000000..d753970
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/lexer/AbstractLexerTest.java
@@ -0,0 +1,261 @@
+/*
+ * 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.lexer;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests of tokenization behavior of {@link BuildLexerBase}. */
+@RunWith(JUnit4.class)
+public abstract class AbstractLexerTest {
+
+  private final BuildLexerBase.LexerMode mode;
+  protected String lastError;
+
+  protected AbstractLexerTest(BuildLexerBase.LexerMode mode) {
+    this.mode = mode;
+  }
+
+  /**
+   * Create a lexer which takes input from the specified string. Resets the error handler
+   * beforehand.
+   */
+  protected BuildLexerBase createLexer(String input) {
+    lastError = null;
+    return new BuildLexerBase(input, 0, mode) {
+      @Override
+      protected void error(String message, int start, int end) {
+        super.error(message, start, end);
+        lastError = message;
+      }
+    };
+  }
+
+  protected Token[] tokens(String input) {
+    Token[] tokens = createLexer(input).getTokens().toArray(new Token[0]);
+    assertNoCharactersMissing(input.length(), tokens);
+    return tokens;
+  }
+
+  /**
+   * Both the syntax highlighter and the parser require every character be accounted for by a
+   * lexical element.
+   */
+  private static void assertNoCharactersMissing(int totalLength, Token[] tokens) {
+    if (tokens.length != 0 && tokens[tokens.length - 1].right != totalLength) {
+      throw new AssertionError(
+          String.format(
+              "Last tokenized character '%s' doesn't match document length '%s'",
+              tokens[tokens.length - 1].right, totalLength));
+    }
+    int start = 0;
+    for (int i = 0; i < tokens.length; i++) {
+      Token token = tokens[i];
+      if (token.left != start) {
+        throw new AssertionError("Gap/inconsistency at: " + start);
+      }
+      start = token.right;
+    }
+  }
+
+  /**
+   * Returns a string containing the names of the tokens and their associated values.
+   * (String-literals are printed without escaping.)
+   */
+  protected String values(Token[] tokens) {
+    StringBuilder buffer = new StringBuilder();
+    for (Token token : tokens) {
+      if (isIgnored(token.kind)) {
+        continue;
+      }
+      if (buffer.length() > 0) {
+        buffer.append(' ');
+      }
+      buffer.append(token.kind.name());
+      if (token.kind != TokenKind.WHITESPACE && token.value != null) {
+        buffer.append('(').append(token.value).append(')');
+      }
+    }
+    return buffer.toString();
+  }
+
+  /** Returns a string containing just the names of the tokens. */
+  protected String names(Token[] tokens) {
+    StringBuilder buf = new StringBuilder();
+    for (Token token : tokens) {
+      if (isIgnored(token.kind)) {
+        continue;
+      }
+      if (buf.length() > 0) {
+        buf.append(' ');
+      }
+      buf.append(token.kind.name());
+    }
+    return buf.toString();
+  }
+
+  private boolean isIgnored(TokenKind kind) {
+    if (mode == BuildLexerBase.LexerMode.Parsing) {
+      return kind == TokenKind.WHITESPACE || kind == TokenKind.COMMENT;
+    }
+    return false;
+  }
+
+  /**
+   * Returns a string containing just the half-open position intervals of each token. e.g. "[3,4)
+   * [4,9)".
+   */
+  protected String positions(Token[] tokens) {
+    StringBuilder buf = new StringBuilder();
+    for (Token token : tokens) {
+      if (isIgnored(token.kind)) {
+        continue;
+      }
+      if (buf.length() > 0) {
+        buf.append(' ');
+      }
+      buf.append('[').append(token.left).append(',').append(token.right).append(')');
+    }
+    return buf.toString();
+  }
+
+  @Test
+  public void testIntegers() throws Exception {
+    // Detection of MINUS immediately following integer constant proves we
+    // don't consume too many chars.
+
+    // decimal
+    assertEquals("INT(12345) MINUS", values(tokens("12345-")));
+
+    // octal
+    assertEquals("INT(5349) MINUS", values(tokens("012345-")));
+
+    // octal (bad)
+    assertEquals("INT(0) MINUS", values(tokens("012349-")));
+    assertEquals("invalid base-8 integer constant: 012349", lastError);
+
+    // hexadecimal (uppercase)
+    assertEquals("INT(1193055) MINUS", values(tokens("0X12345F-")));
+
+    // hexadecimal (lowercase)
+    assertEquals("INT(1193055) MINUS", values(tokens("0x12345f-")));
+
+    // hexadecimal (lowercase) [note: "g" cause termination of token]
+    assertEquals("INT(74565) IDENTIFIER(g) MINUS", values(tokens("0x12345g-")));
+  }
+
+  @Test
+  public void testStringDelimiters() throws Exception {
+    assertEquals("STRING(foo)", values(tokens("\"foo\"")));
+    assertEquals("STRING(foo)", values(tokens("'foo'")));
+  }
+
+  @Test
+  public void testQuotesInStrings() throws Exception {
+    assertEquals("STRING(foo'bar)", values(tokens("'foo\\'bar'")));
+    assertEquals("STRING(foo'bar)", values(tokens("\"foo'bar\"")));
+    assertEquals("STRING(foo\"bar)", values(tokens("'foo\"bar'")));
+    assertEquals("STRING(foo\"bar)", values(tokens("\"foo\\\"bar\"")));
+  }
+
+  @Test
+  public void testStringEscapes() throws Exception {
+    assertEquals("STRING(a\tb\nc\rd)", values(tokens("'a\\tb\\nc\\rd'"))); // \t \r \n
+    assertEquals("STRING(x\\hx)", values(tokens("'x\\hx'"))); // \h is unknown => "\h"
+    assertEquals("STRING(\\$$)", values(tokens("'\\$$'")));
+    assertEquals("STRING(ab)", values(tokens("'a\\\nb'"))); // escape end of line
+    assertEquals("STRING(abcd)", values(tokens("\"ab\\ucd\"")));
+    assertEquals("escape sequence not implemented: \\u", lastError);
+  }
+
+  @Test
+  public void testRawString() throws Exception {
+    assertEquals("STRING(abcd)", values(tokens("r'abcd'")));
+    assertEquals("STRING(abcd)", values(tokens("r\"abcd\"")));
+    assertEquals("STRING(a\\tb\\nc\\rd)", values(tokens("r'a\\tb\\nc\\rd'"))); // r'a\tb\nc\rd'
+    assertEquals("STRING(a\\\")", values(tokens("r\"a\\\"\""))); // r"a\""
+    assertEquals("STRING(a\\\\b)", values(tokens("r'a\\\\b'"))); // r'a\\b'
+    assertEquals("STRING(ab) IDENTIFIER(r)", values(tokens("r'ab'r")));
+
+    // Unterminated raw string
+    values(tokens("r'\\'")); // r'\'
+    assertEquals("unterminated string literal at eof", lastError);
+  }
+
+  @Test
+  public void testTripleRawString() throws Exception {
+    // r'''a\ncd'''
+    assertEquals("STRING(ab\\ncd)", values(tokens("r'''ab\\ncd'''")));
+    // r"""ab
+    // cd"""
+    assertEquals("STRING(ab\ncd)", values(tokens("\"\"\"ab\ncd\"\"\"")));
+
+    // Unterminated raw string
+    values(tokens("r'''\\'''")); // r'''\'''
+    assertEquals("unterminated string literal at eof", lastError);
+  }
+
+  @Test
+  public void testOctalEscapes() throws Exception {
+    // Regression test for a bug.
+    assertEquals(
+        "STRING(\0 \1 \t \u003f I I1 \u00ff \u00ff \u00fe)",
+        values(tokens("'\\0 \\1 \\11 \\77 \\111 \\1111 \\377 \\777 \\776'")));
+    // Test boundaries (non-octal char, EOF).
+    assertEquals("STRING(\1b \1)", values(tokens("'\\1b \\1'")));
+  }
+
+  @Test
+  public void testTripleQuotedStrings() throws Exception {
+    assertEquals("STRING(a\"b'c \n d\"\"e)", values(tokens("\"\"\"a\"b'c \n d\"\"e\"\"\"")));
+    assertEquals("STRING(a\"b'c \n d\"\"e)", values(tokens("'''a\"b'c \n d\"\"e'''")));
+  }
+
+  @Test
+  public void testBadChar() throws Exception {
+    assertEquals("IDENTIFIER(a) ILLEGAL($) IDENTIFIER(b)", values(tokens("a$b")));
+    assertEquals("invalid character: '$'", lastError);
+  }
+
+  @Test
+  public void testContainsErrors() throws Exception {
+    BuildLexerBase lexerSuccess = createLexer("foo");
+    assertFalse(lexerSuccess.containsErrors());
+
+    BuildLexerBase lexerFail = createLexer("f$o");
+    assertTrue(lexerFail.containsErrors());
+
+    String s = "'unterminated";
+    lexerFail = createLexer(s);
+    assertTrue(lexerFail.containsErrors());
+    assertEquals("STRING(unterminated)", values(tokens(s)));
+  }
+
+  @Test
+  public void testUnterminatedEscapedQuotedString() throws Exception {
+    // regression test --
+    assertEquals(
+        "STRING(escaped \n string) NEWLINE IDENTIFIER(next_line)",
+        values(tokens("\"escaped \\n string\nnext_line")));
+
+    assertEquals("STRING(escaped \n string)", values(tokens("'escaped \\n string")));
+  }
+}
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/lexer/BlazeLexerTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/lexer/BlazeLexerTest.java
new file mode 100644
index 0000000..dcb3db1
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/lexer/BlazeLexerTest.java
@@ -0,0 +1,181 @@
+/*
+ * 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.lexer;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests of tokenization behavior of {@link BuildLexerBase} in 'parsing mode' (see {@link
+ * BuildLexerBase.LexerMode})
+ */
+@RunWith(JUnit4.class)
+public class BlazeLexerTest extends AbstractLexerTest {
+
+  public BlazeLexerTest() {
+    super(BuildLexerBase.LexerMode.Parsing);
+  }
+
+  @Test
+  public void testBasics1() throws Exception {
+    assertEquals("IDENTIFIER RPAREN", names(tokens("wiz) ")));
+    assertEquals("IDENTIFIER RPAREN", names(tokens("wiz )")));
+    assertEquals("IDENTIFIER RPAREN", names(tokens(" wiz)")));
+    assertEquals("IDENTIFIER RPAREN", names(tokens(" wiz ) ")));
+    assertEquals("IDENTIFIER RPAREN", names(tokens("wiz\t)")));
+  }
+
+  @Test
+  public void testBasics2() throws Exception {
+    assertEquals("RPAREN", names(tokens(")")));
+    assertEquals("RPAREN", names(tokens(" )")));
+    assertEquals("RPAREN", names(tokens(" ) ")));
+    assertEquals("RPAREN", names(tokens(") ")));
+  }
+
+  @Test
+  public void testBasics3() throws Exception {
+    assertEquals("INT NEWLINE INT", names(tokens("123#456\n789")));
+    assertEquals("INT NEWLINE INT", names(tokens("123 #456\n789")));
+    assertEquals("INT NEWLINE INT", names(tokens("123#456 \n789")));
+    assertEquals("INT NEWLINE INDENT INT", names(tokens("123#456\n 789")));
+    assertEquals("INT NEWLINE INT", names(tokens("123#456\n789 ")));
+  }
+
+  @Test
+  public void testBasics4() throws Exception {
+    assertEquals("", names(tokens("")));
+    assertEquals("", names(tokens("# foo")));
+    assertEquals("INT INT INT INT", names(tokens("1 2 3 4")));
+    assertEquals("INT DOT INT", names(tokens("1.234")));
+    assertEquals(
+        "IDENTIFIER LPAREN IDENTIFIER COMMA IDENTIFIER RPAREN", names(tokens("foo(bar, wiz)")));
+  }
+
+  @Test
+  public void testIntegersAndDot() throws Exception {
+    assertEquals("INT(1) DOT INT(2345)", values(tokens("1.2345")));
+
+    assertEquals("INT(1) DOT INT(2) DOT INT(345)", values(tokens("1.2.345")));
+
+    assertEquals("INT(1) DOT INT(0)", values(tokens("1.23E10")));
+    assertEquals("invalid base-10 integer constant: 23E10", lastError);
+
+    assertEquals("INT(1) DOT INT(0) MINUS INT(10)", values(tokens("1.23E-10")));
+    assertEquals("invalid base-10 integer constant: 23E", lastError);
+
+    assertEquals("DOT INT(123)", values(tokens(". 123")));
+    assertEquals("DOT INT(123)", values(tokens(".123")));
+    assertEquals("DOT IDENTIFIER(abc)", values(tokens(".abc")));
+
+    assertEquals("IDENTIFIER(foo) DOT INT(123)", values(tokens("foo.123")));
+    assertEquals(
+        "IDENTIFIER(foo) DOT IDENTIFIER(bcd)", values(tokens("foo.bcd"))); // 'b' are hex chars
+    assertEquals("IDENTIFIER(foo) DOT IDENTIFIER(xyz)", values(tokens("foo.xyz")));
+  }
+
+  @Test
+  public void testIndentation() throws Exception {
+    assertEquals("INT(1) NEWLINE INT(2) NEWLINE INT(3)", values(tokens("1\n2\n3")));
+    assertEquals(
+        "INT(1) NEWLINE INDENT INT(2) NEWLINE INT(3) NEWLINE DEDENT INT(4)",
+        values(tokens("1\n  2\n  3\n4 ")));
+    assertEquals("INT(1) NEWLINE INDENT INT(2) NEWLINE INT(3)", values(tokens("1\n  2\n  3")));
+    assertEquals(
+        "INT(1) NEWLINE INDENT INT(2) NEWLINE INDENT INT(3)", values(tokens("1\n  2\n    3")));
+    assertEquals(
+        "INT(1) NEWLINE INDENT INT(2) NEWLINE INDENT INT(3) NEWLINE "
+            + "DEDENT INT(4) NEWLINE DEDENT INT(5)",
+        values(tokens("1\n  2\n    3\n  4\n5")));
+
+    assertEquals(
+        "INT(1) NEWLINE INDENT INT(2) NEWLINE INDENT INT(3) NEWLINE "
+            + "DEDENT INT(4) NEWLINE DEDENT INT(5)",
+        values(tokens("1\n  2\n    3\n   4\n5")));
+    assertEquals("indentation error", lastError);
+  }
+
+  @Test
+  public void testIndentationInsideParens() throws Exception {
+    // Indentation is ignored inside parens:
+    assertEquals(
+        "INT(1) LPAREN INT(2) INT(3) INT(4) INT(5)", values(tokens("1 (\n  2\n    3\n  4\n5")));
+    assertEquals(
+        "INT(1) LBRACE INT(2) INT(3) INT(4) INT(5)", values(tokens("1 {\n  2\n    3\n  4\n5")));
+    assertEquals(
+        "INT(1) LBRACKET INT(2) INT(3) INT(4) INT(5)", values(tokens("1 [\n  2\n    3\n  4\n5")));
+    assertEquals(
+        "INT(1) LBRACKET INT(2) RBRACKET NEWLINE INDENT INT(3) "
+            + "NEWLINE INT(4) NEWLINE DEDENT INT(5)",
+        values(tokens("1 [\n  2]\n    3\n    4\n5")));
+  }
+
+  @Test
+  public void testNoIndentationAtEOF() throws Exception {
+    assertEquals("INDENT INT(1)", values(tokens("\n  1")));
+  }
+
+  @Test
+  public void testBlankLineIndentation() throws Exception {
+    // Blank lines and comment lines should not generate any indents
+    // (but note that every input ends with).
+    assertEquals("", names(tokens("\n      #\n")));
+    assertEquals("", names(tokens("      #")));
+    assertEquals("NEWLINE", names(tokens("      #\n")));
+    assertEquals("NEWLINE", names(tokens("      #comment\n")));
+    assertEquals(
+        "DEF IDENTIFIER LPAREN IDENTIFIER RPAREN COLON NEWLINE "
+            + "INDENT RETURN IDENTIFIER NEWLINE DEDENT",
+        names(tokens("def f(x):\n" + "  # comment\n" + "\n" + "  \n" + "  return x\n")));
+  }
+
+  @Test
+  public void testMultipleCommentLines() throws Exception {
+    assertEquals(
+        "NEWLINE "
+            + "DEF IDENTIFIER LPAREN IDENTIFIER RPAREN COLON NEWLINE "
+            + "INDENT RETURN IDENTIFIER NEWLINE DEDENT",
+        names(
+            tokens(
+                "# Copyright\n"
+                    + "#\n"
+                    + "# A comment line\n"
+                    + "# An adjoining line\n"
+                    + "def f(x):\n"
+                    + "  return x\n")));
+  }
+
+  @Test
+  public void testBackslash() throws Exception {
+    // backslash followed by newline marked as whitespace (skipped by parser)
+    assertEquals("IDENTIFIER IDENTIFIER", names(tokens("a\\\nb")));
+    assertEquals("IDENTIFIER ILLEGAL IDENTIFIER", names(tokens("a\\ b")));
+    assertEquals("IDENTIFIER LPAREN INT RPAREN", names(tokens("a(\\\n2)")));
+  }
+
+  @Test
+  public void testTokenPositions() throws Exception {
+    //            foo   (     bar   ,     {      1       :
+    assertEquals(
+        "[0,3) [3,4) [4,7) [7,8) [9,10) [10,11) [11,12)"
+            //      'quux'  }       )
+            + " [13,19) [19,20) [20,21)",
+        positions(tokens("foo(bar, {1: 'quux'})")));
+  }
+}
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/lexer/HighlightingLexerTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/lexer/HighlightingLexerTest.java
new file mode 100644
index 0000000..3b87a83
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/lexer/HighlightingLexerTest.java
@@ -0,0 +1,189 @@
+/*
+ * 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.lexer;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests of tokenization behavior of {@link BuildLexerBase} in 'highlighting mode' (see {@link
+ * BuildLexerBase.LexerMode})
+ */
+@RunWith(JUnit4.class)
+public class HighlightingLexerTest
+    extends com.google.idea.blaze.base.lang.buildfile.lexer.AbstractLexerTest {
+
+  public HighlightingLexerTest() {
+    super(BuildLexerBase.LexerMode.SyntaxHighlighting);
+  }
+
+  @Test
+  public void testBasics1() throws Exception {
+    assertEquals("IDENTIFIER RPAREN WHITESPACE", names(tokens("wiz) ")));
+    assertEquals("IDENTIFIER WHITESPACE RPAREN", names(tokens("wiz )")));
+    assertEquals("WHITESPACE IDENTIFIER RPAREN", names(tokens(" wiz)")));
+    assertEquals("WHITESPACE IDENTIFIER WHITESPACE RPAREN WHITESPACE", names(tokens(" wiz ) ")));
+    assertEquals("IDENTIFIER WHITESPACE RPAREN", names(tokens("wiz\t)")));
+  }
+
+  @Test
+  public void testBasics2() throws Exception {
+    assertEquals("RPAREN", names(tokens(")")));
+    assertEquals("WHITESPACE RPAREN", names(tokens(" )")));
+    assertEquals("WHITESPACE RPAREN WHITESPACE", names(tokens(" ) ")));
+    assertEquals("RPAREN WHITESPACE", names(tokens(") ")));
+  }
+
+  @Test
+  public void testBasics3() throws Exception {
+    assertEquals("INT COMMENT NEWLINE INT", names(tokens("123#456\n789")));
+    assertEquals("INT WHITESPACE COMMENT NEWLINE INT", names(tokens("123 #456\n789")));
+    assertEquals("INT COMMENT NEWLINE INT", names(tokens("123#456 \n789")));
+    assertEquals("INT COMMENT NEWLINE WHITESPACE INT", names(tokens("123#456\n 789")));
+    assertEquals("INT COMMENT NEWLINE INT WHITESPACE", names(tokens("123#456\n789 ")));
+  }
+
+  @Test
+  public void testBasics4() throws Exception {
+    assertEquals("", names(tokens("")));
+    assertEquals("COMMENT", names(tokens("# foo")));
+    assertEquals("INT WHITESPACE INT WHITESPACE INT WHITESPACE INT", names(tokens("1 2 3 4")));
+    assertEquals("INT DOT INT", names(tokens("1.234")));
+    assertEquals(
+        "IDENTIFIER LPAREN IDENTIFIER COMMA WHITESPACE IDENTIFIER RPAREN",
+        names(tokens("foo(bar, wiz)")));
+  }
+
+  @Test
+  public void testIntegersAndDot() throws Exception {
+    assertEquals("INT(1) DOT INT(2345)", values(tokens("1.2345")));
+
+    assertEquals("INT(1) DOT INT(2) DOT INT(345)", values(tokens("1.2.345")));
+
+    assertEquals("INT(1) DOT INT(0)", values(tokens("1.23E10")));
+    assertEquals("invalid base-10 integer constant: 23E10", lastError);
+
+    assertEquals("INT(1) DOT INT(0) MINUS INT(10)", values(tokens("1.23E-10")));
+    assertEquals("invalid base-10 integer constant: 23E", lastError);
+
+    assertEquals("DOT WHITESPACE INT(123)", values(tokens(". 123")));
+    assertEquals("DOT INT(123)", values(tokens(".123")));
+    assertEquals("DOT IDENTIFIER(abc)", values(tokens(".abc")));
+
+    assertEquals("IDENTIFIER(foo) DOT INT(123)", values(tokens("foo.123")));
+    assertEquals(
+        "IDENTIFIER(foo) DOT IDENTIFIER(bcd)", values(tokens("foo.bcd"))); // 'b' are hex chars
+    assertEquals("IDENTIFIER(foo) DOT IDENTIFIER(xyz)", values(tokens("foo.xyz")));
+  }
+
+  @Test
+  public void testNoIndentation() throws Exception {
+    assertEquals("INT(1) NEWLINE INT(2) NEWLINE INT(3)", values(tokens("1\n2\n3")));
+    assertEquals(
+        "INT(1) NEWLINE WHITESPACE INT(2) NEWLINE WHITESPACE INT(3) NEWLINE INT(4) WHITESPACE",
+        values(tokens("1\n  2\n  3\n4 ")));
+    assertEquals(
+        "INT(1) NEWLINE WHITESPACE INT(2) NEWLINE WHITESPACE INT(3)",
+        values(tokens("1\n  2\n  3")));
+    assertEquals(
+        "INT(1) NEWLINE WHITESPACE INT(2) NEWLINE WHITESPACE INT(3)",
+        values(tokens("1\n  2\n    3")));
+    assertEquals(
+        "INT(1) NEWLINE WHITESPACE INT(2) NEWLINE WHITESPACE INT(3) "
+            + "NEWLINE WHITESPACE INT(4) NEWLINE INT(5)",
+        values(tokens("1\n  2\n    3\n  4\n5")));
+
+    assertEquals(
+        "INT(1) NEWLINE WHITESPACE INT(2) NEWLINE WHITESPACE INT(3) "
+            + "NEWLINE WHITESPACE INT(4) NEWLINE INT(5)",
+        values(tokens("1\n  2\n    3\n   4\n5")));
+  }
+
+  @Test
+  public void testIndentationInsideParens() throws Exception {
+    // Indentation is ignored inside parens:
+    assertEquals(
+        "INT(1) WHITESPACE LPAREN NEWLINE WHITESPACE INT(2) NEWLINE "
+            + "WHITESPACE INT(3) NEWLINE WHITESPACE INT(4) NEWLINE INT(5)",
+        values(tokens("1 (\n  2\n    3\n  4\n5")));
+    assertEquals(
+        "INT(1) WHITESPACE LBRACE NEWLINE WHITESPACE INT(2) NEWLINE "
+            + "WHITESPACE INT(3) NEWLINE WHITESPACE INT(4) NEWLINE INT(5)",
+        values(tokens("1 {\n  2\n    3\n  4\n5")));
+    assertEquals(
+        "INT(1) WHITESPACE LBRACKET NEWLINE WHITESPACE INT(2) NEWLINE "
+            + "WHITESPACE INT(3) NEWLINE WHITESPACE INT(4) NEWLINE INT(5)",
+        values(tokens("1 [\n  2\n    3\n  4\n5")));
+    assertEquals(
+        "INT(1) WHITESPACE LBRACKET NEWLINE WHITESPACE INT(2) RBRACKET "
+            + "NEWLINE WHITESPACE INT(3) NEWLINE WHITESPACE INT(4) NEWLINE INT(5)",
+        values(tokens("1 [\n  2]\n    3\n    4\n5")));
+  }
+
+  @Test
+  public void testNoIndentationAtEOF() throws Exception {
+    assertEquals("NEWLINE WHITESPACE INT(1)", values(tokens("\n  1")));
+  }
+
+  @Test
+  public void testBlankLineIndentation() throws Exception {
+    // Blank lines and comment lines should not generate any newlines indents
+    // (but note that every input ends with).
+    assertEquals("NEWLINE WHITESPACE COMMENT NEWLINE", names(tokens("\n      #\n")));
+    assertEquals("WHITESPACE COMMENT", names(tokens("      #")));
+    assertEquals("WHITESPACE COMMENT NEWLINE", names(tokens("      #\n")));
+    assertEquals("WHITESPACE COMMENT NEWLINE", names(tokens("      #comment\n")));
+    assertEquals(
+        "DEF WHITESPACE IDENTIFIER LPAREN IDENTIFIER RPAREN COLON NEWLINE WHITESPACE "
+            + "COMMENT NEWLINE NEWLINE WHITESPACE NEWLINE WHITESPACE "
+            + "RETURN WHITESPACE IDENTIFIER NEWLINE",
+        names(tokens("def f(x):\n" + "  # comment\n" + "\n" + "  \n" + "  return x\n")));
+  }
+
+  @Test
+  public void testMultipleCommentLines() throws Exception {
+    assertEquals(
+        "COMMENT NEWLINE COMMENT NEWLINE COMMENT NEWLINE COMMENT NEWLINE DEF WHITESPACE IDENTIFIER "
+            + "LPAREN IDENTIFIER RPAREN COLON NEWLINE WHITESPACE "
+            + "RETURN WHITESPACE IDENTIFIER NEWLINE",
+        names(
+            tokens(
+                "# Copyright\n"
+                    + "#\n"
+                    + "# A comment line\n"
+                    + "# An adjoining line\n"
+                    + "def f(x):\n"
+                    + "  return x\n")));
+  }
+
+  @Test
+  public void testBackslash() throws Exception {
+    // illegal characters marked as whitespace (skipped by parser)
+    assertEquals("IDENTIFIER WHITESPACE IDENTIFIER", names(tokens("a\\\nb")));
+    assertEquals("IDENTIFIER ILLEGAL WHITESPACE IDENTIFIER", names(tokens("a\\ b")));
+    assertEquals("IDENTIFIER LPAREN WHITESPACE INT RPAREN", names(tokens("a(\\\n2)")));
+  }
+
+  @Test
+  public void testTokenPositions() throws Exception {
+    assertEquals(
+        "[0,3) [3,4) [4,7) [7,8) [8,9) [9,10) [10,11) [11,12) [12,13) [13,19) [19,20) [20,21)",
+        positions(tokens("foo(bar, {1: 'quux'})")));
+  }
+}
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
new file mode 100644
index 0000000..10b7869
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/parser/BuildParserTest.java
@@ -0,0 +1,581 @@
+/*
+ * 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.parser;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
+import com.google.idea.blaze.base.lang.buildfile.psi.BuildElement;
+import com.google.idea.blaze.base.lang.buildfile.psi.LoadStatement;
+import com.intellij.lang.ASTNode;
+import com.intellij.lang.FileASTNode;
+import com.intellij.lang.ParserDefinition;
+import com.intellij.lang.PsiParser;
+import com.intellij.lang.impl.PsiBuilderAdapter;
+import com.intellij.lang.impl.PsiBuilderImpl;
+import com.intellij.lexer.Lexer;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.impl.source.CharTableImpl;
+import com.intellij.psi.impl.source.tree.LeafElement;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/** Test for the BUILD file parser (converting lexical elements into PSI elements) */
+public class BuildParserTest extends BuildFileIntegrationTestCase {
+
+  private final List<String> errors = Lists.newArrayList();
+
+  @Override
+  protected void doTearDown() {
+    errors.clear();
+  }
+
+  public void testAugmentedAssign() throws Exception {
+    assertThat(parse("x += 1")).isEqualTo("aug_assign(reference, int)");
+    assertThat(parse("x -= 1")).isEqualTo("aug_assign(reference, int)");
+    assertThat(parse("x *= 1")).isEqualTo("aug_assign(reference, int)");
+    assertThat(parse("x /= 1")).isEqualTo("aug_assign(reference, int)");
+    assertThat(parse("x %= 1")).isEqualTo("aug_assign(reference, int)");
+    assertNoErrors();
+  }
+
+  public void testAssign() throws Exception {
+    assertThat(parse("a, b = 5\n")).isEqualTo("assignment(list(reference, target), int)");
+    assertNoErrors();
+  }
+
+  public void testAssign2() throws Exception {
+    assertThat(parse("a = b;c = d\n"))
+        .isEqualTo(
+            Joiner.on("").join("assignment(target, reference), ", "assignment(target, reference)"));
+    assertNoErrors();
+  }
+
+  public void testInvalidAssign() throws Exception {
+    parse("1 + (b = c)");
+    assertContainsErrors();
+  }
+
+  public void testTupleAssign() throws Exception {
+    assertThat(parse("list[0] = 5; dict['key'] = value\n"))
+        .isEqualTo(
+            Joiner.on("")
+                .join(
+                    "assignment(function_call(reference, positional(int)), int), ",
+                    "assignment(function_call(reference, positional(string)), reference)"));
+    assertNoErrors();
+  }
+
+  public void testPrimary() throws Exception {
+    assertThat(parse("f(1 + 2)"))
+        .isEqualTo("function_call(reference, arg_list(positional(binary_op(int, int))))");
+    assertNoErrors();
+  }
+
+  public void testSecondary() throws Exception {
+    assertThat(parse("f(1 % 2)"))
+        .isEqualTo("function_call(reference, arg_list(positional(binary_op(int, int))))");
+    assertNoErrors();
+  }
+
+  public void testDoesNotGetStuck() throws Exception {
+    // Make sure the parser does not get stuck when trying
+    // to parse an expression containing a syntax error.
+    parse("f(1, ], 3)");
+    parse("f(1, ), 3)");
+    parse("[ ) for v in 3)");
+    parse("f(1, [x for foo foo foo foo], 3)");
+  }
+
+  public void testInvalidFunctionStatementDoesNotGetStuck() throws Exception {
+    // Make sure the parser does not get stuck when trying
+    // to parse a function statement containing a syntax error.
+    parse("def is ");
+    parse("def fn(");
+    parse("def empty)");
+  }
+
+  public void testSubstring() throws Exception {
+    assertThat(parse("'FOO.CC'[:].lower()[1:]"))
+        .isEqualTo(
+            Joiner.on("")
+                .join(
+                    "function_call(",
+                    "function_call(function_call(string), reference, arg_list), ",
+                    "positional(int))"));
+    assertNoErrors();
+  }
+
+  public void testFuncallExpr() throws Exception {
+    assertThat(parse("foo(1, 2, bar=wiz)"))
+        .isEqualTo(
+            Joiner.on("")
+                .join(
+                    "function_call(reference, arg_list(",
+                    "positional(int), ",
+                    "positional(int), ",
+                    "keyword(reference)))"));
+    assertNoErrors();
+  }
+
+  public void testMethCallExpr() throws Exception {
+    assertThat(parse("foo.foo(1, 2, bar=wiz)"))
+        .isEqualTo(
+            Joiner.on("")
+                .join(
+                    "function_call(reference, reference, ",
+                    "arg_list(positional(int), positional(int), keyword(reference)))"));
+    assertNoErrors();
+  }
+
+  public void testChainedMethCallExpr() throws Exception {
+    assertThat(parse("foo.replace().split(1)"))
+        .isEqualTo(
+            "function_call(function_call(reference, reference, arg_list), "
+                + "reference, arg_list(positional(int)))");
+    assertNoErrors();
+  }
+
+  public void testPropRefExpr() throws Exception {
+    assertThat(parse("foo.foo")).isEqualTo("dot_expr(reference, reference)");
+    assertNoErrors();
+  }
+
+  public void testStringMethExpr() throws Exception {
+    assertThat(parse("'foo'.foo()")).isEqualTo("function_call(string, reference, arg_list)");
+    assertNoErrors();
+  }
+
+  public void testFuncallLocation() throws Exception {
+    assertThat(parse("a(b);c = d\n"))
+        .isEqualTo(
+            Joiner.on("")
+                .join(
+                    "function_call(reference, arg_list(positional(reference))), ",
+                    "assignment(target, reference)"));
+    assertNoErrors();
+  }
+
+  public void testList() throws Exception {
+    assertThat(parse("[0,f(1),2]"))
+        .isEqualTo("list(int, function_call(reference, arg_list(positional(int))), int)");
+    assertNoErrors();
+  }
+
+  public void testDict() throws Exception {
+    assertThat(parse("{1:2,2:f(1),3:4}"))
+        .isEqualTo(
+            Joiner.on("")
+                .join(
+                    "dict(",
+                    "dict_entry(int, int), ",
+                    "dict_entry(int, function_call(reference, arg_list(positional(int)))), ",
+                    "dict_entry(int, int)",
+                    ")"));
+    assertNoErrors();
+  }
+
+  public void testArgumentList() throws Exception {
+    assertThat(parse("f(0,g(1,2),2)"))
+        .isEqualTo(
+            Joiner.on("")
+                .join(
+                    "function_call(reference, arg_list(",
+                    "positional(int), ",
+                    "positional("
+                        + "function_call(reference, arg_list(positional(int), positional(int)))), ",
+                    "positional(int)))"));
+    assertNoErrors();
+  }
+
+  public void testForBreakContinue() throws Exception {
+    String parsed =
+        parse("def foo():", "  for i in [1, 2]:", "    break", "    continue", "    break");
+    assertThat(parsed)
+        .isEqualTo(
+            Joiner.on("")
+                .join(
+                    "function_def(parameter_list, ",
+                    "stmt_list(for(target, list(int, int), ",
+                    "stmt_list(flow, flow, flow))))"));
+    assertNoErrors();
+  }
+
+  public void testEmptyTuple() throws Exception {
+    assertThat(parse("()")).isEqualTo("list");
+    assertNoErrors();
+  }
+
+  public void testTupleTrailingComma() throws Exception {
+    assertThat(parse("(42,)")).isEqualTo("list(int)");
+    assertNoErrors();
+  }
+
+  public void testSingleton() throws Exception {
+    assertThat(parse("(42)")) // not a tuple!
+        .isEqualTo("list(int)");
+    assertNoErrors();
+  }
+
+  public void testDictionaryLiterals() throws Exception {
+    assertThat(parse("{1:42}")).isEqualTo("dict(dict_entry(int, int))");
+    assertNoErrors();
+  }
+
+  public void testDictionaryLiterals1() throws Exception {
+    assertThat(parse("{}")).isEqualTo("dict");
+    assertNoErrors();
+  }
+
+  public void testDictionaryLiterals2() throws Exception {
+    assertThat(parse("{1:42,}")).isEqualTo("dict(dict_entry(int, int))");
+    assertNoErrors();
+  }
+
+  public void testDictionaryLiterals3() throws Exception {
+    assertThat(parse("{1:42,2:43,3:44}"))
+        .isEqualTo(
+            Joiner.on("")
+                .join(
+                    "dict(",
+                    "dict_entry(int, int), ",
+                    "dict_entry(int, int), ",
+                    "dict_entry(int, int))"));
+    assertNoErrors();
+  }
+
+  public void testInvalidListComprehensionSyntax() throws Exception {
+    assertThat(parse("[x for x for y in ['a']]")).isEqualTo("list_comp(reference, reference)");
+    assertContainsErrors();
+  }
+
+  public void testListComprehensionEmptyList() throws Exception {
+    // At the moment, we just parse the components of comprehension suffixes.
+    assertThat(parse("['foo/%s.java' % x for x in []]"))
+        .isEqualTo("list_comp(binary_op(string, reference), target, list)");
+    assertNoErrors();
+  }
+
+  public void testListComprehension() throws Exception {
+    assertThat(parse("['foo/%s.java' % x for x in ['bar', 'wiz', 'quux']]"))
+        .isEqualTo(
+            Joiner.on("")
+                .join(
+                    "list_comp(binary_op(string, reference), ",
+                    "target, ",
+                    "list(string, string, string))"));
+    assertNoErrors();
+  }
+
+  public void testDoesntGetStuck2() throws Exception {
+    parse(
+        "def foo():",
+        "  a = 2 for 4", // parse error
+        "  b = [3, 4]",
+        "",
+        "d = 4 ada", // parse error
+        "",
+        "def bar():",
+        "  a = [3, 4]",
+        "  b = 2 + + 5", // parse error
+        "");
+    assertContainsErrors();
+  }
+
+  public void testDoesntGetStuck3() throws Exception {
+    parse("load(*)");
+    parse("load()");
+    parse("load(,)");
+    parse("load)");
+    parse("load(,");
+    parse("load(,\"string\"");
+    assertContainsErrors();
+  }
+
+  public void testExprAsStatement() throws Exception {
+    String parsed =
+        parse("li = []", "li.append('a.c')", "\"\"\" string comment \"\"\"", "foo(bar)");
+    assertThat(parsed)
+        .isEqualTo(
+            Joiner.on("")
+                .join(
+                    "assignment(target, list), ",
+                    "function_call(reference, reference, arg_list(positional(string))), ",
+                    "string, ",
+                    "function_call(reference, arg_list(positional(reference)))"));
+    assertNoErrors();
+  }
+
+  public void testPrecedence1() {
+    assertThat(parse("'%sx' % 'foo' + 'bar'"))
+        .isEqualTo("binary_op(binary_op(string, string), string)");
+    assertNoErrors();
+  }
+
+  public void testPrecedence2() {
+    assertThat(parse("('%sx' + 'foo') * 'bar'"))
+        .isEqualTo("binary_op(list(binary_op(string, string)), string)");
+    assertNoErrors();
+  }
+
+  public void testPrecedence3() {
+    assertThat(parse("'%sx' % ('foo' + 'bar')"))
+        .isEqualTo("binary_op(string, list(binary_op(string, string)))");
+    assertNoErrors();
+  }
+
+  public void testPrecedence4() throws Exception {
+    assertThat(parse("1 + - (2 - 3)"))
+        .isEqualTo("binary_op(int, positional(list(binary_op(int, int))))");
+    assertNoErrors();
+  }
+
+  public void testPrecedence5() throws Exception {
+    assertThat(parse("2 * x | y + 1"))
+        .isEqualTo("binary_op(binary_op(int, reference), binary_op(reference, int))");
+    assertNoErrors();
+  }
+
+  public void testNotIsIgnored() throws Exception {
+    assertThat(parse("not 'b'")).isEqualTo("string");
+    assertNoErrors();
+  }
+
+  public void testNotIn() throws Exception {
+    assertThat(parse("'a' not in 'b'")).isEqualTo("binary_op(string, string)");
+    assertNoErrors();
+  }
+
+  public void testParseBuildFileWithSingeRule() throws Exception {
+    ASTNode tree =
+        createAST(
+            "genrule(name = 'foo',",
+            "   srcs = ['input.csv'],",
+            "   outs = [ 'result.txt',",
+            "           'result.log'],",
+            "   cmd = 'touch result.txt result.log')");
+    List<BuildElement> stmts = getTopLevelNodesOfType(tree, BuildElement.class);
+    assertThat(stmts).hasSize(1);
+    assertNoErrors();
+  }
+
+  public void testParseBuildFileWithMultipleRules() throws Exception {
+    ASTNode tree =
+        createAST(
+            "genrule(name = 'foo',",
+            "   srcs = ['input.csv'],",
+            "   outs = [ 'result.txt',",
+            "           'result.log'],",
+            "   srcs = ['input.csv'],",
+            "   cmd = 'touch result.txt result.log')",
+            "",
+            "genrule(name = 'bar',",
+            "   outs = [ 'graph.svg'],",
+            "   cmd = 'touch graph.svg')");
+    List<BuildElement> stmts = getTopLevelNodesOfType(tree, BuildElement.class);
+    assertThat(stmts).hasSize(2);
+    assertNoErrors();
+  }
+
+  public void testMissingComma() throws Exception {
+    // missing comma after name='foo'
+    parse("genrule(name = 'foo'", "   srcs = ['in'])");
+    assertContainsError("',' expected");
+  }
+
+  public void testDoubleSemicolon() throws Exception {
+    parse("x = 1; ; x = 2;");
+    assertContainsError("expected an expression");
+  }
+
+  public void testMissingBlock() throws Exception {
+    parse("x = 1;", "def foo(x):", "x = 2;\n");
+    assertContainsError("'indent' expected");
+  }
+
+  public void testFunCallBadSyntax() throws Exception {
+    parse("f(1,\n");
+    assertContainsError("')' expected");
+  }
+
+  public void testFunCallBadSyntax2() throws Exception {
+    parse("f(1, 5, ,)\n");
+    assertContainsError("expected an expression");
+  }
+
+  public void testLoad() throws Exception {
+    ASTNode tree = createAST("load('file', 'foo', '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.getImportedSymbolNames()).isEqualTo(new String[] {"foo", "bar"});
+    assertNoErrors();
+  }
+
+  public void testLoadNoSymbol() throws Exception {
+    parse("load('/foo/bar/file')\n");
+    assertContainsError("'load' statements must include at least one loaded function");
+  }
+
+  public void testFunctionDefinition() throws Exception {
+    ASTNode tree =
+        createAST(
+            "def function(name = 'foo', srcs, outs, *args, **kwargs):",
+            "   native.java_library(",
+            "     name = name,",
+            "     srcs = srcs,",
+            "   )",
+            "   return");
+    List<BuildElement> stmts = getTopLevelNodesOfType(tree, BuildElement.class);
+    assertThat(stmts).hasSize(1);
+    assertNoErrors();
+  }
+
+  public void testFunctionCall() throws Exception {
+    ASTNode tree = createAST("function(name = 'foo', srcs, *args, **kwargs)");
+    List<BuildElement> stmts = getTopLevelNodesOfType(tree, BuildElement.class);
+    assertThat(stmts).hasSize(1);
+    assertThat(treeToString(tree))
+        .isEqualTo(
+            Joiner.on("")
+                .join(
+                    "function_call(reference, arg_list(",
+                    "keyword(string), ",
+                    "positional(reference), ",
+                    "*(reference), ",
+                    "**(reference)))"));
+    assertNoErrors();
+  }
+
+  public void testConditionalStatement() throws Exception {
+    // we don't yet bother specifying which kind of conditionals we hit
+    assertThat(parse("if x : y elif a : b else c"))
+        .isEqualTo(
+            Joiner.on("")
+                .join(
+                    "if(",
+                    "if_part(reference, reference), ",
+                    "else_if_part(reference, reference), ",
+                    "else_part(reference))"));
+  }
+
+  private ASTNode createAST(String... lines) {
+    StringBuilder builder = new StringBuilder();
+    for (String line : lines) {
+      builder.append(line).append("\n");
+    }
+    return createAST(builder.toString());
+  }
+
+  private ASTNode createAST(String text) {
+    ParserDefinition definition = new BuildParserDefinition();
+    PsiParser parser = definition.createParser(getProject());
+    Lexer lexer = definition.createLexer(getProject());
+    PsiBuilderImpl psiBuilder =
+        new PsiBuilderImpl(
+            getProject(), null, definition, lexer, new CharTableImpl(), text, null, null);
+    PsiBuilderAdapter adapter =
+        new PsiBuilderAdapter(psiBuilder) {
+          @Override
+          public void error(String messageText) {
+            super.error(messageText);
+            errors.add(messageText);
+          }
+        };
+    return parser.parse(definition.getFileNodeType(), adapter);
+  }
+
+  private String parse(String... lines) {
+    StringBuilder builder = new StringBuilder();
+    for (String line : lines) {
+      builder.append(line).append("\n");
+    }
+    return parse(builder.toString());
+  }
+
+  private String parse(String text) {
+    ASTNode tree = createAST(text);
+    return treeToString(tree);
+  }
+
+  private String treeToString(ASTNode tree) {
+    StringBuilder builder = new StringBuilder();
+    nodeToString(tree, builder);
+    return builder.toString();
+  }
+
+  private void nodeToString(ASTNode node, StringBuilder builder) {
+    if (node instanceof LeafElement || node.getPsi() == null) {
+      return;
+    }
+    PsiElement[] childPsis = getChildBuildPsis(node);
+    if (node instanceof FileASTNode) {
+      appendChildren(childPsis, builder, false);
+      return;
+    }
+    builder.append(node.getElementType());
+    appendChildren(childPsis, builder, true);
+  }
+
+  private void appendChildren(PsiElement[] childPsis, StringBuilder builder, boolean bracket) {
+    if (childPsis.length == 0) {
+      return;
+    }
+    if (bracket) {
+      builder.append("(");
+    }
+    nodeToString(childPsis[0].getNode(), builder);
+    for (int i = 1; i < childPsis.length; i++) {
+      builder.append(", ");
+      nodeToString(childPsis[i].getNode(), builder);
+    }
+    if (bracket) {
+      builder.append(")");
+    }
+  }
+
+  private static <T> List<T> getTopLevelNodesOfType(ASTNode node, Class<T> clazz) {
+    return (List)
+        Arrays.stream(node.getChildren(null))
+            .map(ASTNode::getPsi)
+            .filter(psiElement -> clazz.isInstance(psiElement))
+            .collect(Collectors.toList());
+  }
+
+  private PsiElement[] getChildBuildPsis(ASTNode node) {
+    return Arrays.stream(node.getChildren(null))
+        .map(ASTNode::getPsi)
+        .filter(psiElement -> psiElement instanceof BuildElement)
+        .toArray(PsiElement[]::new);
+  }
+
+  private void assertNoErrors() {
+    assertThat(errors).isEmpty();
+  }
+
+  private void assertContainsErrors() {
+    assertThat(errors).isNotEmpty();
+  }
+
+  private void assertContainsError(String message) {
+    assertThat(errors).contains(message);
+  }
+}
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
new file mode 100644
index 0000000..a882998
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/refactor/FileCopyTest.java
@@ -0,0 +1,45 @@
+/*
+ * 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.refactor;
+
+import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
+import com.intellij.openapi.command.WriteCommandAction;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.refactoring.copy.CopyHandler;
+
+/** Tests copying files */
+public class FileCopyTest extends BuildFileIntegrationTestCase {
+
+  public void testCopyingJavaFileReferencedByGlob() {
+    createDirectory("java");
+    PsiFile javaFile = createPsiFile("java/Test.java", "package java;", "public class Test {}");
+
+    PsiFile javaFile2 = createPsiFile("java/Test2.java", "package java;", "public class Test2 {}");
+
+    createBuildFile(
+        "java/BUILD", "java_library(", "    name = 'lib',", "    srcs = glob(['**/*.java']),", ")");
+
+    PsiDirectory otherDir = createPsiDirectory("java/other");
+
+    WriteCommandAction.runWriteCommandAction(
+        null,
+        () -> {
+          CopyHandler.doCopy(new PsiElement[] {javaFile, javaFile2}, otherDir);
+        });
+  }
+}
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
new file mode 100644
index 0000000..ca3a792
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/refactor/RenameRefactoringTest.java
@@ -0,0 +1,231 @@
+/*
+ * 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.refactor;
+
+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.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.StringLiteral;
+import com.google.idea.blaze.base.lang.buildfile.psi.TargetExpression;
+import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
+import com.intellij.psi.PsiFile;
+import com.intellij.refactoring.rename.RenameDialog;
+import com.intellij.refactoring.rename.RenamePsiElementProcessor;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/** Tests that BUILD file references are correctly updated when performing rename refactors. */
+public class RenameRefactoringTest extends BuildFileIntegrationTestCase {
+
+  public void testRenameJavaClass() {
+    PsiFile javaFile =
+        createPsiFile(
+            "com/google/foo/JavaClass.java",
+            "package com.google.foo;",
+            "public class JavaClass {}");
+
+    createBuildFile(
+        "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\"])");
+
+    List<StringLiteral> references =
+        findAllReferencingElementsOfType(javaFile, StringLiteral.class);
+
+    Set<String> oldStrings =
+        references.stream().map(StringLiteral::getStringContents).collect(Collectors.toSet());
+
+    assertThat(references).hasSize(3);
+
+    renamePsiElement(javaFile, "NewName.java");
+
+    Set<String> newStrings =
+        references.stream().map(StringLiteral::getStringContents).collect(Collectors.toSet());
+
+    Set<String> expectedNewStrings =
+        oldStrings
+            .stream()
+            .map((s) -> s.replaceAll("JavaClass", "NewName"))
+            .collect(Collectors.toSet());
+
+    assertThat(expectedNewStrings).containsExactlyElementsIn(newStrings);
+  }
+
+  public void testRenameRule() {
+    BuildFile fooPackage =
+        createBuildFile(
+            "com/google/foo/BUILD",
+            "rule_type(name = \"target\")",
+            "java_library(name = \"local_ref\", srcs = [\":target\"])");
+
+    BuildFile barPackage =
+        createBuildFile(
+            "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");
+
+    assertFileContents(
+        fooPackage,
+        "rule_type(name = \"newTargetName\")",
+        "java_library(name = \"local_ref\", srcs = [\":newTargetName\"])");
+
+    assertFileContents(
+        barPackage,
+        "rule_type(name = \"ref\", arg = \"//com/google/foo:newTargetName\")",
+        "top_level_ref = \"//com/google/foo:newTargetName\"");
+  }
+
+  public void testRenameSkylarkExtension() {
+    BuildFile extFile =
+        createBuildFile("java/com/google/tools/build_defs.bzl", "def function(name, deps)");
+
+    BuildFile buildFile =
+        createBuildFile(
+            "java/com/google/BUILD",
+            "load(",
+            "\"//java/com/google:tools/build_defs.bzl\",",
+            "\"function\"",
+            ")",
+            "function(name = \"name\", deps = []");
+
+    renamePsiElement(extFile, "skylark.bzl");
+
+    assertFileContents(
+        buildFile,
+        "load(",
+        "\"//java/com/google:tools/skylark.bzl\",",
+        "\"function\"",
+        ")",
+        "function(name = \"name\", deps = []");
+  }
+
+  public void testRenameLoadedFunction() {
+    BuildFile extFile =
+        createBuildFile("java/com/google/tools/build_defs.bzl", "def function(name, deps)");
+
+    BuildFile buildFile =
+        createBuildFile(
+            "java/com/google/BUILD",
+            "load(",
+            "\"//java/com/google/tools:build_defs.bzl\",",
+            "\"function\"",
+            ")",
+            "function(name = \"name\", deps = []");
+
+    FunctionStatement fn = extFile.findChildByClass(FunctionStatement.class);
+    renamePsiElement(fn, "action");
+
+    assertFileContents(extFile, "def action(name, deps)");
+
+    assertFileContents(
+        buildFile,
+        "load(",
+        "\"//java/com/google/tools:build_defs.bzl\",",
+        "\"action\"",
+        ")",
+        "action(name = \"name\", deps = []");
+  }
+
+  public void testRenameLocalVariable() {
+    BuildFile file = createBuildFile("java/com/google/BUILD", "a = 1", "c = a");
+
+    TargetExpression target = PsiUtils.findFirstChildOfClassRecursive(file, TargetExpression.class);
+    assertThat(target.getText()).isEqualTo("a");
+
+    renamePsiElement(target, "b");
+
+    assertFileContents(file, "b = 1", "c = b");
+  }
+
+  // all references, including path fragments in labels, should be renamed.
+  public void testRenameDirectory() {
+    createBuildFile("java/com/baz/BUILD");
+    createBuildFile("java/com/google/tools/BUILD");
+    BuildFile buildFile =
+        createBuildFile(
+            "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");
+
+    assertFileContents(
+        buildFile,
+        "load(",
+        "\"//java/alt/google/tools:build_defs.bzl\",",
+        "\"function\"",
+        ")",
+        "function(name = \"name\", deps = [\"//java/alt/baz:target\"]");
+  }
+
+  public void testRenameFunctionParameter() {
+    BuildFile extFile =
+        createBuildFile("java/com/google/tools/build_defs.bzl", "def function(name, deps)");
+
+    BuildFile buildFile =
+        createBuildFile(
+            "java/com/google/BUILD",
+            "load(",
+            "\"//java/com/google/tools:build_defs.bzl\",",
+            "\"function\"",
+            ")",
+            "function(name = \"name\", deps = []");
+
+    FunctionStatement fn = extFile.findChildByClass(FunctionStatement.class);
+    Parameter param = fn.getParameterList().findParameterByName("deps");
+    renamePsiElement(param, "exports");
+
+    assertFileContents(extFile, "def function(name, exports)");
+
+    assertFileContents(
+        buildFile,
+        "load(",
+        "\"//java/com/google/tools:build_defs.bzl\",",
+        "\"function\"",
+        ")",
+        "function(name = \"name\", exports = []");
+  }
+
+  public void testRenameSuggestionForBuildFile() {
+    BuildFile buildFile = createBuildFile("java/com/google/BUILD");
+    RenamePsiElementProcessor processor = RenamePsiElementProcessor.forElement(buildFile);
+    RenameDialog dialog = processor.createRenameDialog(getProject(), buildFile, buildFile, null);
+    String[] suggestions = dialog.getSuggestedNames();
+    assertThat(suggestions[0]).isEqualTo("BUILD");
+  }
+
+  public void testRenameSuggestionForSkylarkFile() {
+    BuildFile buildFile = createBuildFile("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");
+  }
+
+}
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
new file mode 100644
index 0000000..0fcd82c
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/GlobReferenceTest.java
@@ -0,0 +1,147 @@
+/*
+ * 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.references;
+
+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.GlobExpression;
+import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.ResolveResult;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/** Tests that glob references are resolved correctly. */
+public class GlobReferenceTest extends BuildFileIntegrationTestCase {
+
+  public void testSimpleGlobReferencingSingleFile() {
+    PsiFile ref = createPsiFile("java/com/google/Test.java");
+    BuildFile file = createBuildFile("java/com/google/BUILD", "glob(['**/*.java'])");
+
+    GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
+    List<PsiElement> references = multiResolve(glob);
+    assertThat(references).hasSize(1);
+    assertThat(references).containsExactly(ref);
+  }
+
+  public void testSimpleGlobReferencingSingleFile2() {
+    PsiFile ref = createPsiFile("java/com/google/Test.java");
+    BuildFile file = createBuildFile("java/com/google/BUILD", "glob(['*.java'])");
+
+    GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
+    List<PsiElement> references = multiResolve(glob);
+    assertThat(references).hasSize(1);
+    assertThat(references).containsExactly(ref);
+  }
+
+  public void testSimpleGlobReferencingSingleFile3() {
+    PsiFile ref = createPsiFile("java/com/google/Test.java");
+    BuildFile file = createBuildFile("java/com/google/BUILD", "glob(['T*t.java'])");
+
+    GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
+    List<PsiElement> references = multiResolve(glob);
+    assertThat(references).hasSize(1);
+    assertThat(references).containsExactly(ref);
+  }
+
+  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'])");
+
+    GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
+    List<PsiElement> references = multiResolve(glob);
+    assertThat(references).hasSize(2);
+    assertThat(references).containsExactly(ref1, ref2);
+  }
+
+  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'])");
+
+    GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
+    List<PsiElement> references = multiResolve(glob);
+    assertThat(references).hasSize(2);
+    assertThat(references).containsExactly(ref1, ref2);
+  }
+
+  public void testGlobWithExcludes() {
+    PsiFile test = createPsiFile("java/com/google/tests/Test.java");
+    PsiFile foo = createPsiFile("java/com/google/Foo.java");
+    BuildFile file =
+        createBuildFile(
+            "java/com/google/BUILD",
+            "glob(" + "  ['**/*.java']," + "  exclude = ['tests/*.java'])");
+
+    GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
+    List<PsiElement> references = multiResolve(glob);
+    assertThat(references).hasSize(1);
+    assertThat(references).containsExactly(foo);
+  }
+
+  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");
+    BuildFile file =
+        createBuildFile(
+            "java/com/google/BUILD",
+            "glob(" + "  ['**/*']," + "  exclude = ['BUILD']," + "  exclude_directories = 0)");
+
+    GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
+    List<PsiElement> references = multiResolve(glob);
+    assertThat(references).hasSize(3);
+    assertThat(references).containsExactly(foo, test, test.getParent());
+  }
+
+  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");
+    BuildFile file =
+        createBuildFile(
+            "java/com/google/BUILD", "glob(" + "  ['**/*']," + "  exclude = ['BUILD'])");
+
+    GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
+    List<PsiElement> references = multiResolve(glob);
+    assertThat(references).hasSize(2);
+    assertThat(references).containsExactly(foo, 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");
+
+    GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(pkg, GlobExpression.class);
+    List<PsiElement> references = multiResolve(glob);
+    assertThat(references).isEmpty();
+  }
+
+  private List<PsiElement> multiResolve(GlobExpression glob) {
+    ResolveResult[] result = glob.getReference().multiResolve(false);
+    return Arrays.stream(result)
+        .map(ResolveResult::getElement)
+        .filter(Objects::nonNull)
+        .collect(Collectors.toList());
+  }
+}
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
new file mode 100644
index 0000000..310375f
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/KeywordReferenceTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.references;
+
+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.ArgumentList;
+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.ParameterList;
+
+/** Tests that keyword references are correctly resolved. */
+public class KeywordReferenceTest extends BuildFileIntegrationTestCase {
+
+  public void testPlainKeywordReference() {
+    BuildFile file =
+        createBuildFile(
+            "java/com/google/build_defs.bzl",
+            "def function(name, deps)",
+            "function(name = \"name\", deps = [])");
+
+    ParameterList params = file.firstChildOfClass(FunctionStatement.class).getParameterList();
+    assertThat(params.getElements()).hasLength(2);
+
+    ArgumentList args = file.firstChildOfClass(FuncallExpression.class).getArgList();
+    assertThat(args.getKeywordArgument("name").getReferencedElement())
+        .isEqualTo(params.findParameterByName("name"));
+
+    assertThat(args.getKeywordArgument("deps").getReferencedElement())
+        .isEqualTo(params.findParameterByName("deps"));
+  }
+
+  public void testKwargsReference() {
+    BuildFile file =
+        createBuildFile(
+            "java/com/google/build_defs.bzl",
+            "def function(name, **kwargs)",
+            "function(name = \"name\", deps = [])");
+
+    ArgumentList args = file.firstChildOfClass(FuncallExpression.class).getArgList();
+    assertThat(args.getKeywordArgument("deps").getReferencedElement())
+        .isInstanceOf(Parameter.StarStar.class);
+  }
+}
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
new file mode 100644
index 0000000..e3f3e7f
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/LabelReferenceTest.java
@@ -0,0 +1,163 @@
+/*
+ * 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.references;
+
+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.Argument;
+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.StringLiteral;
+import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
+import com.google.idea.blaze.base.lang.buildfile.search.FindUsages;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiReference;
+import java.util.List;
+
+/** Tests that string literal references are correctly resolved. */
+public class LabelReferenceTest extends BuildFileIntegrationTestCase {
+
+  public void testExternalFileReference() {
+    BuildFile file =
+        createBuildFile(
+            "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");
+
+    List<StringLiteral> strings =
+        PsiUtils.findAllChildrenOfClassRecursive(file, StringLiteral.class);
+    assertThat(strings).hasSize(2);
+    assertThat(strings.get(0).getReferencedElement()).isEqualTo(txtFile);
+    assertThat(strings.get(1).getReferencedElement()).isEqualTo(xmlFile);
+  }
+
+  public void testLocalRuleReference() {
+    BuildFile file =
+        createBuildFile(
+            "java/com/google/BUILD",
+            "java_library(name = \"lib\")",
+            "java_library(name = \"foo\", deps = [\":lib\"])",
+            "java_library(name = \"bar\", deps = [\"//java/com/google:lib\"])");
+
+    FuncallExpression lib = file.findRule("lib");
+    FuncallExpression foo = file.findRule("foo");
+    FuncallExpression bar = file.findRule("bar");
+
+    assertThat(lib).isNotNull();
+
+    StringLiteral label =
+        PsiUtils.findFirstChildOfClassRecursive(
+            foo.getKeywordArgument("deps"), StringLiteral.class);
+    assertThat(label.getReferencedElement()).isEqualTo(lib);
+
+    label =
+        PsiUtils.findFirstChildOfClassRecursive(
+            bar.getKeywordArgument("deps"), StringLiteral.class);
+    assertThat(label.getReferencedElement()).isEqualTo(lib);
+  }
+
+  public void testTargetInAnotherPackageResolves() {
+    BuildFile targetFile = createBuildFile("java/com/google/foo/BUILD", "rule(name = \"target\")");
+
+    BuildFile referencingFile =
+        createBuildFile(
+            "java/com/google/bar/BUILD",
+            "rule(name = \"other\", dep = \"//java/com/google/foo:target\")");
+
+    FuncallExpression target = targetFile.findRule("target");
+    assertThat(target).isNotNull();
+
+    Argument.Keyword depArgument = referencingFile.findRule("other").getKeywordArgument("dep");
+
+    assertThat(depArgument.getValue().getReferencedElement()).isEqualTo(target);
+  }
+
+  public void testRuleNameDoesntCrossPackageBoundaries() {
+    BuildFile targetFile =
+        createBuildFile("java/com/google/pkg/subpkg/BUILD", "rule(name = \"target\")");
+
+    BuildFile referencingFile =
+        createBuildFile(
+            "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");
+    assertThat(ref.resolve()).isNotNull();
+    assertThat(ref.resolve()).isEqualTo(targetFile.findRule("target"));
+  }
+
+  public void testLabelWithImplicitRuleName() {
+    BuildFile targetFile = createBuildFile("java/com/google/foo/BUILD", "rule(name = \"foo\")");
+
+    BuildFile referencingFile =
+        createBuildFile(
+            "java/com/google/bar/BUILD", "rule(name = \"other\", dep = \"//java/com/google/foo\")");
+
+    FuncallExpression target = targetFile.findRule("foo");
+    assertThat(target).isNotNull();
+
+    Argument.Keyword depArgument = referencingFile.findRule("other").getKeywordArgument("dep");
+
+    assertThat(depArgument.getValue().getReferencedElement()).isEqualTo(target);
+  }
+
+  public void testAbsoluteLabelInSkylarkExtension() {
+    BuildFile targetFile = createBuildFile("java/com/google/foo/BUILD", "rule(name = \"foo\")");
+
+    BuildFile referencingFile =
+        createBuildFile("java/com/google/foo/skylark.bzl", "LIST = ['//java/com/google/foo:foo']");
+
+    FuncallExpression target = targetFile.findRule("foo");
+    assertThat(target).isNotNull();
+
+    StringLiteral label =
+        PsiUtils.findFirstChildOfClassRecursive(referencingFile, StringLiteral.class);
+    assertThat(label.getReferencedElement()).isEqualTo(target);
+  }
+
+  public void testRulePreferredOverFile() {
+    BuildFile targetFile = createBuildFile("java/com/foo/BUILD", "java_library(name = 'lib')");
+
+    createDirectory("java/com/foo/lib");
+
+    BuildFile referencingFile =
+        createBuildFile(
+            "java/com/google/bar/BUILD",
+            "java_library(",
+            "    name = 'bar',",
+            "    src = glob(['**/*.java'])," + "    deps = ['//java/com/foo:lib'],",
+            ")");
+
+    FuncallExpression target = targetFile.findRule("lib");
+    assertThat(target).isNotNull();
+
+    PsiReference[] references = FindUsages.findAllReferences(target);
+    assertThat(references).hasLength(1);
+
+    PsiElement element = references[0].getElement();
+    FuncallExpression rule = PsiUtils.getParentOfType(element, FuncallExpression.class);
+    assertThat(rule.getName()).isEqualTo("bar");
+    assertThat(rule.getContainingFile()).isEqualTo(referencingFile);
+  }
+}
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
new file mode 100644
index 0000000..7152ec1
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/LoadedSkylarkExtensionTest.java
@@ -0,0 +1,144 @@
+/*
+ * 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.references;
+
+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.FuncallExpression;
+import com.google.idea.blaze.base.lang.buildfile.psi.FunctionStatement;
+import com.google.idea.blaze.base.lang.buildfile.psi.LoadStatement;
+
+/** Tests that funcall references and load statement contents are correctly resolved. */
+public class LoadedSkylarkExtensionTest extends BuildFileIntegrationTestCase {
+
+  public void testStandardLoadReference() {
+    BuildFile extFile =
+        createBuildFile("java/com/google/build_defs.bzl", "def function(name, deps)");
+
+    BuildFile buildFile =
+        createBuildFile(
+            "java/com/google/BUILD",
+            "load(",
+            "\"//java/com/google:build_defs.bzl\",",
+            "\"function\"",
+            ")");
+
+    LoadStatement load = buildFile.firstChildOfClass(LoadStatement.class);
+    assertThat(load.getImportPsiElement().getReferencedElement()).isEqualTo(extFile);
+
+    FunctionStatement function = extFile.firstChildOfClass(FunctionStatement.class);
+    assertThat(function).isNotNull();
+
+    assertThat(load.getImportedSymbolElements()).hasLength(1);
+    assertThat(load.getImportedSymbolElements()[0].getReferencedElement()).isEqualTo(function);
+  }
+
+  // TODO: If we want to support this deprecated format,
+  // we should start by relaxing the ":" requirement in Label
+  //public void testDeprecatedImportLabelFormat() {
+  //  BuildFile extFile = createBuildFile(
+  //    "java/com/google/build_defs.bzl",
+  //    "def function(name, deps)");
+  //
+  //  BuildFile buildFile = createBuildFile(
+  //    "java/com/google/tools/BUILD",
+  //    "load(",
+  //    "\"//java/com/google/build_defs.bzl\",",
+  //    "\"function\"",
+  //    ")");
+  //
+  //  LoadStatement load = buildFile.firstChildOfClass(LoadStatement.class);
+  //  assertThat(load.getImportPsiElement().getReferencedElement()).isEqualTo(extFile);
+  //}
+
+  public void testPackageLocalImportLabelFormat() {
+    BuildFile extFile =
+        createBuildFile("java/com/google/tools/build_defs.bzl", "def function(name, deps)");
+
+    BuildFile buildFile =
+        createBuildFile(
+            "java/com/google/tools/BUILD", "load(", "\":build_defs.bzl\",", "\"function\"", ")");
+
+    LoadStatement load = buildFile.firstChildOfClass(LoadStatement.class);
+    assertThat(load.getImportPsiElement().getReferencedElement()).isEqualTo(extFile);
+  }
+
+  public void testMultipleImportedFunctions() {
+    BuildFile extFile =
+        createBuildFile(
+            "java/com/google/build_defs.bzl", "def fn1(name, deps)", "def fn2(name, deps)");
+
+    BuildFile buildFile =
+        createBuildFile(
+            "java/com/google/BUILD",
+            "load(",
+            "\"//java/com/google:build_defs.bzl\",",
+            "\"fn1\"",
+            "\"fn2\"",
+            ")");
+
+    LoadStatement load = buildFile.firstChildOfClass(LoadStatement.class);
+    assertThat(load.getImportPsiElement().getReferencedElement()).isEqualTo(extFile);
+
+    FunctionStatement[] functions = extFile.childrenOfClass(FunctionStatement.class);
+    assertThat(functions).hasLength(2);
+    assertThat(load.getImportedFunctionReferences()).isEqualTo(functions);
+  }
+
+  public void testFuncallReference() {
+    BuildFile extFile =
+        createBuildFile("java/com/google/tools/build_defs.bzl", "def function(name, deps)");
+
+    BuildFile buildFile =
+        createBuildFile(
+            "java/com/google/BUILD",
+            "load(",
+            "\"//java/com/google/tools:build_defs.bzl\",",
+            "\"function\"",
+            ")",
+            "function(name = \"name\", deps = []");
+
+    FunctionStatement function = extFile.firstChildOfClass(FunctionStatement.class);
+    FuncallExpression funcall = buildFile.firstChildOfClass(FuncallExpression.class);
+
+    assertThat(function).isNotNull();
+    assertThat(funcall.getReferencedElement()).isEqualTo(function);
+  }
+
+  // relative paths in skylark extensions which lie in subdirectories
+  // are relative to the parent blaze package directory
+  public void testRelativePathInSubdirectory() {
+    createFile("java/com/google/BUILD");
+    BuildFile referencedFile =
+        createBuildFile(
+            "java/com/google/nonPackageSubdirectory/skylark.bzl", "def function(): return");
+    BuildFile file =
+        createBuildFile(
+            "java/com/google/nonPackageSubdirectory/other.bzl",
+            "load(" + "    ':nonPackageSubdirectory/skylark.bzl',",
+            "    'function',",
+            ")",
+            "function()");
+
+    FunctionStatement function = referencedFile.firstChildOfClass(FunctionStatement.class);
+    FuncallExpression funcall = file.firstChildOfClass(FuncallExpression.class);
+
+    assertThat(function).isNotNull();
+    assertThat(funcall.getReferencedElement()).isEqualTo(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
new file mode 100644
index 0000000..7bff52c
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/LocalReferenceTest.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.references;
+
+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.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.ReferenceExpression;
+import com.google.idea.blaze.base.lang.buildfile.psi.TargetExpression;
+import com.intellij.psi.PsiElement;
+
+/**
+ * Tests that local references (to TargetExpressions within a given file) are correctly resolved.
+ */
+public class LocalReferenceTest extends BuildFileIntegrationTestCase {
+
+  public void testCreatesReference() {
+    BuildFile file = createBuildFile("java/com/google/BUILD", "a = 1", "c = a");
+
+    AssignmentStatement[] stmts = file.childrenOfClass(AssignmentStatement.class);
+    assertThat(stmts).hasLength(2);
+    assertThat(stmts[1].getAssignedValue()).isInstanceOf(ReferenceExpression.class);
+
+    ReferenceExpression ref = (ReferenceExpression) stmts[1].getAssignedValue();
+    assertThat(ref.getReference()).isInstanceOf(LocalReference.class);
+  }
+
+  public void testReferenceResolves() {
+    BuildFile file = createBuildFile("java/com/google/BUILD", "a = 1", "c = a");
+
+    AssignmentStatement[] stmts = file.childrenOfClass(AssignmentStatement.class);
+    ReferenceExpression ref = (ReferenceExpression) stmts[1].getAssignedValue();
+
+    PsiElement referencedElement = ref.getReferencedElement();
+    assertThat(referencedElement).isEqualTo(stmts[0].getLeftHandSideExpression());
+  }
+
+  public void testTargetInOuterScope() {
+    BuildFile file = createBuildFile("java/com/google/BUILD", "a = 1", "function(c = a)");
+
+    TargetExpression target =
+        file.findChildByClass(AssignmentStatement.class).getLeftHandSideExpression();
+    FuncallExpression funcall = file.findChildByClass(FuncallExpression.class);
+    ReferenceExpression ref =
+        funcall.getKeywordArgument("c").firstChildOfClass(ReferenceExpression.class);
+    assertThat(ref.getReferencedElement()).isEqualTo(target);
+  }
+
+  public void testReferenceInsideFuncallExpression() {
+    BuildFile file = createBuildFile("java/com/google/BUILD", "a = 1", "a.function(c)");
+
+    TargetExpression target =
+        file.findChildByClass(AssignmentStatement.class).getLeftHandSideExpression();
+    FuncallExpression funcall = file.findChildByClass(FuncallExpression.class);
+    ReferenceExpression ref = funcall.firstChildOfClass(ReferenceExpression.class);
+    assertThat(ref.getReferencedElement()).isEqualTo(target);
+  }
+}
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
new file mode 100644
index 0000000..47d1afc
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/PackageReferenceTest.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.references;
+
+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.Argument;
+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.StringLiteral;
+import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
+import com.intellij.psi.PsiReference;
+
+/** Tests that package references in string literals are correctly resolved. */
+public class PackageReferenceTest extends BuildFileIntegrationTestCase {
+
+  public void testDirectReferenceResolves() {
+    BuildFile buildFile1 = createBuildFile("java/com/google/tools/BUILD", "# contents");
+
+    BuildFile buildFile2 =
+        createBuildFile(
+            "java/com/google/other/BUILD",
+            "package_group(name = \"grp\", packages = [\"//java/com/google/tools\"])");
+
+    Argument.Keyword packagesArg =
+        buildFile2
+            .firstChildOfClass(FuncallExpression.class)
+            .getArgList()
+            .getKeywordArgument("packages");
+    StringLiteral string =
+        PsiUtils.findFirstChildOfClassRecursive(packagesArg, StringLiteral.class);
+    assertThat(string.getReferencedElement()).isEqualTo(buildFile1);
+  }
+
+  public void testLabelFragmentResolves() {
+    BuildFile buildFile1 =
+        createBuildFile("java/com/google/tools/BUILD", "java_library(name = \"lib\")");
+
+    BuildFile buildFile2 =
+        createBuildFile(
+            "java/com/google/other/BUILD",
+            "java_library(name = \"lib2\", exports = [\"//java/com/google/tools:lib\"])");
+
+    FuncallExpression libTarget = buildFile1.firstChildOfClass(FuncallExpression.class);
+    assertThat(libTarget).isNotNull();
+
+    Argument.Keyword packagesArg =
+        buildFile2
+            .firstChildOfClass(FuncallExpression.class)
+            .getArgList()
+            .getKeywordArgument("exports");
+    StringLiteral string =
+        PsiUtils.findFirstChildOfClassRecursive(packagesArg, StringLiteral.class);
+
+    PsiReference[] references = string.getReferences();
+    assertThat(references).hasLength(2);
+    assertThat(references[0].resolve()).isEqualTo(libTarget);
+    assertThat(references[1].resolve()).isEqualTo(buildFile1);
+  }
+}
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
new file mode 100644
index 0000000..567ad1e
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/search/BlazePackageTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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.search;
+
+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.intellij.psi.PsiFile;
+
+/** Tests for BlazePackage */
+public class BlazePackageTest extends BuildFileIntegrationTestCase {
+
+  public void testFindPackage() {
+    BuildFile packageFile = createBuildFile("java/com/google/BUILD");
+    PsiFile subDirFile = createPsiFile("java/com/google/tools/test.txt");
+    BlazePackage blazePackage = BlazePackage.getContainingPackage(subDirFile);
+    assertThat(blazePackage).isNotNull();
+    assertThat(blazePackage.buildFile).isEqualTo(packageFile);
+  }
+
+  public void testScopeDoesntCrossPackageBoundary() {
+    BuildFile pkg = createBuildFile("java/com/google/BUILD");
+    BuildFile subpkg = createBuildFile("java/com/google/other/BUILD");
+
+    BlazePackage blazePackage = BlazePackage.getContainingPackage(pkg);
+    assertThat(blazePackage.buildFile).isEqualTo(pkg);
+    assertFalse(blazePackage.getSearchScope(false).contains(subpkg.getVirtualFile()));
+  }
+
+  public void testScopeIncludesSubdirectoriesWhichAreNotBlazePackages() {
+    BuildFile pkg = createBuildFile("java/com/google/BUILD");
+    BuildFile subpkg = createBuildFile("java/com/google/foo/bar/BUILD");
+    PsiFile subDirFile = createPsiFile("java/com/google/foo/test.txt");
+
+    BlazePackage blazePackage = BlazePackage.getContainingPackage(subDirFile);
+    assertThat(blazePackage.buildFile).isEqualTo(pkg);
+    assertTrue(blazePackage.getSearchScope(false).contains(subDirFile.getVirtualFile()));
+  }
+
+  public void testScopeLimitedToBlazeFiles() {
+    BuildFile pkg = createBuildFile("java/com/google/BUILD");
+    BuildFile subpkg = createBuildFile("java/com/google/foo/bar/BUILD");
+    PsiFile subDirFile = createPsiFile("java/com/google/foo/test.txt");
+
+    BlazePackage blazePackage = BlazePackage.getContainingPackage(subDirFile);
+    assertThat(blazePackage.buildFile).isEqualTo(pkg);
+    assertFalse(blazePackage.getSearchScope(true).contains(subDirFile.getVirtualFile()));
+  }
+}
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
new file mode 100644
index 0000000..2eb828c
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/search/GlobalWordIndexTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.search;
+
+import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.impl.cache.CacheManager;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.psi.search.UsageSearchContext;
+import java.util.Arrays;
+import org.intellij.lang.annotations.MagicConstant;
+
+/**
+ * Test the WordScanner indexes keywords in the way we expect.<br>
+ * This is vital for navigation, refactoring, highlighting etc.
+ */
+public class GlobalWordIndexTest extends BuildFileIntegrationTestCase {
+
+  public void testWordsInComments() {
+    VirtualFile file = createFile("java/com/google/BUILD", "# words in comments");
+    assertContainsWords(file, UsageSearchContext.IN_COMMENTS, "words", "in", "comments");
+  }
+
+  public void testWordsInStrings() {
+    VirtualFile file =
+        createFile(
+            "java/com/google/BUILD",
+            "name = \"long name with spaces\",",
+            "src = [\"name_without_spaces\"]");
+    assertContainsWords(
+        file,
+        UsageSearchContext.IN_STRINGS,
+        "long",
+        "name",
+        "with",
+        "spaces",
+        "name_without_spaces");
+  }
+
+  public void testWordsInCode() {
+    VirtualFile file =
+        createFile(
+            "java/com/google/BUILD",
+            "java_library(",
+            "name = \"long name with spaces\",",
+            "src = [\"name_without_spaces\"]",
+            ")");
+    assertContainsWords(file, UsageSearchContext.IN_CODE, "java_library", "name", "src");
+  }
+
+  private void assertContainsWords(
+      VirtualFile file,
+      @MagicConstant(flagsFromClass = UsageSearchContext.class) short occurenceMask,
+      String... words) {
+
+    for (String word : words) {
+      VirtualFile[] files =
+          CacheManager.SERVICE
+              .getInstance(getProject())
+              .getVirtualFilesWithWord(
+                  word, occurenceMask, GlobalSearchScope.fileScope(getProject(), file), true);
+      if (!Arrays.asList(files).contains(file)) {
+        fail(String.format("Word '%s' not found in file '%s'", word, file));
+      }
+    }
+  }
+}
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
new file mode 100644
index 0000000..3ba00e5
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/validation/GlobValidationTest.java
@@ -0,0 +1,227 @@
+/*
+ * 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.validation;
+
+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.GlobExpression;
+import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
+import com.intellij.codeInsight.daemon.impl.AnnotationHolderImpl;
+import com.intellij.lang.annotation.Annotation;
+import com.intellij.lang.annotation.AnnotationHolder;
+import com.intellij.lang.annotation.AnnotationSession;
+import com.intellij.psi.PsiFile;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/** Tests glob validation. */
+public class GlobValidationTest extends BuildFileIntegrationTestCase {
+
+  public void testNormalGlob() {
+    BuildFile file = createBuildFile("java/com/google/BUILD", "glob(['**/*.java'])");
+
+    assertNoErrors(file);
+  }
+
+  public void testNamedIncludeArgument() {
+    BuildFile file = createBuildFile("java/com/google/BUILD", "glob(include = ['**/*.java'])");
+
+    assertNoErrors(file);
+  }
+
+  public void testAllArguments() {
+    BuildFile file =
+        createBuildFile(
+            "java/com/google/BUILD",
+            "glob(['**/*.java'], exclude = ['test/*.java'], exclude_directories = 0)");
+
+    assertNoErrors(file);
+  }
+
+  public void testEmptyExcludeList() {
+    BuildFile file = createBuildFile("java/com/google/BUILD", "glob(['**/*.java'], exclude = [])");
+
+    assertNoErrors(file);
+  }
+
+  public void testNoIncludesError() {
+    BuildFile file = createBuildFile("java/com/google/BUILD", "glob(exclude = ['BUILD'])");
+
+    assertHasError(file, "Glob expression must contain at least one included string");
+  }
+
+  public void testSingletonExcludeArgumentError() {
+    BuildFile file =
+        createBuildFile("java/com/google/BUILD", "glob(['**/*.java'], exclude = 'BUILD')");
+
+    assertHasError(file, "Glob parameter 'exclude' must be a list of strings");
+  }
+
+  public void testSingletonIncludeArgumentError() {
+    BuildFile file = createBuildFile("java/com/google/BUILD", "glob(include = '**/*.java')");
+
+    assertHasError(file, "Glob parameter 'include' must be a list of strings");
+  }
+
+  public void testInvalidExcludeDirectoriesValue() {
+    BuildFile file =
+        createBuildFile(
+            "java/com/google/BUILD",
+            "glob(['**/*.java'], exclude = ['test/*.java'], exclude_directories = true)");
+
+    assertHasError(file, "exclude_directories parameter to glob must be 0 or 1");
+  }
+
+  public void testUnrecognizedArgumentError() {
+    BuildFile file =
+        createBuildFile(
+            "java/com/google/BUILD", "glob(['**/*.java'], exclude = ['test/*.java'], extra = 1)");
+
+    assertHasError(file, "Unrecognized glob argument");
+  }
+
+  public void testInvalidListArgumentValue() {
+    BuildFile file = createBuildFile("java/com/google/BUILD", "glob(include = foo)");
+
+    assertHasError(file, "Glob parameter 'include' must be a list of strings");
+  }
+
+  public void testLocalVariableReference() {
+    BuildFile file =
+        createBuildFile("java/com/google/BUILD", "foo = ['*.java']", "glob(include = foo)");
+
+    assertNoErrors(file);
+  }
+
+  public void testLoadedVariableReference() {
+    BuildFile ext = createBuildFile("java/com/foo/vars.bzl", "LIST_VAR = ['*']");
+    BuildFile file =
+        createBuildFile(
+            "java/com/google/BUILD",
+            "load('//java/com/foo:vars.bzl', 'LIST_VAR')",
+            "glob(include = LIST_VAR)");
+
+    assertNoErrors(file);
+  }
+
+  public void testInvalidLoadedVariableReference() {
+    BuildFile ext = createBuildFile("java/com/foo/vars.bzl", "LIST_VAR = ['*']", "def function()");
+    BuildFile file =
+        createBuildFile(
+            "java/com/google/BUILD",
+            "load('//java/com/foo:vars.bzl', 'LIST_VAR', 'function')",
+            "glob(include = function)");
+
+    assertHasError(file, "Glob parameter 'include' must be a list of strings");
+  }
+
+  public void testUnresolvedReferenceExpression() {
+    BuildFile file = createBuildFile("java/com/google/BUILD", "glob(include = ref)");
+
+    assertHasError(file, "Glob parameter 'include' must be a list of strings");
+  }
+
+  public void testPossibleListExpressionFuncallExpression() {
+    BuildFile file = createBuildFile("java/com/google/BUILD", "glob(include = fn.list)");
+
+    assertNoErrors(file);
+  }
+
+  public void testPossibleListExpressionParameter() {
+    BuildFile file =
+        createBuildFile(
+            "java/com/google/BUILD", "def function(param1, param2):", "    glob(include = param1)");
+
+    assertNoErrors(file);
+  }
+
+  public void testNestedGlobs() {
+    // blaze accepts nested globs
+    BuildFile file = createBuildFile("java/com/google/BUILD", "glob(glob(['*.java']))");
+
+    assertNoErrors(file);
+  }
+
+  public void testKnownInvalidResolvedListExpression() {
+    BuildFile file =
+        createBuildFile("java/com/google/BUILD", "bool_literal = True", "glob(bool_literal)");
+
+    assertHasError(file, "Glob parameter 'include' must be a list of strings");
+  }
+
+  public void testKnownInvalidResolvedString() {
+    BuildFile file =
+        createBuildFile("java/com/google/BUILD", "bool_literal = True", "glob([bool_literal])");
+
+    assertHasError(file, "Glob parameter 'include' must be a list of strings");
+  }
+
+  public void testPossibleStringLiteralIfStatement() {
+    BuildFile file =
+        createBuildFile("java/com/google/BUILD", "glob(include = ['*.java', if test : a else b])");
+
+    // we don't know what the IfStatement evaluates to
+    assertNoErrors(file);
+  }
+
+  public void testPossibleStringLiteralParameter() {
+    BuildFile file =
+        createBuildFile(
+            "java/com/google/BUILD",
+            "def function(param1, param2):",
+            "    glob(include = [param1])");
+
+    assertNoErrors(file);
+  }
+
+  private void assertNoErrors(BuildFile file) {
+    assertThat(validateFile(file)).isEmpty();
+  }
+
+  private void assertHasError(BuildFile file, String error) {
+    assertHasError(validateFile(file), error);
+  }
+
+  private void assertHasError(List<Annotation> annotations, String error) {
+    List<String> messages =
+        annotations.stream().map(Annotation::getMessage).collect(Collectors.toList());
+
+    assertThat(messages).contains(error);
+  }
+
+  private List<Annotation> validateFile(BuildFile file) {
+    GlobErrorAnnotator annotator = createAnnotator(file);
+    for (GlobExpression glob :
+        PsiUtils.findAllChildrenOfClassRecursive(file, GlobExpression.class)) {
+      annotator.visitGlobExpression(glob);
+    }
+    return annotationHolder;
+  }
+
+  private GlobErrorAnnotator createAnnotator(PsiFile file) {
+    annotationHolder = new AnnotationHolderImpl(new AnnotationSession(file));
+    return new GlobErrorAnnotator() {
+      @Override
+      protected AnnotationHolder getHolder() {
+        return annotationHolder;
+      }
+    };
+  }
+
+  private AnnotationHolderImpl annotationHolder = null;
+}
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
new file mode 100644
index 0000000..4e629f8
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/projectview/ProjectViewCompletionTest.java
@@ -0,0 +1,185 @@
+/*
+ * 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;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.base.Joiner;
+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.section.SectionParser;
+import com.google.idea.blaze.base.projectview.section.sections.Sections;
+import com.intellij.codeInsight.lookup.Lookup;
+import com.intellij.codeInsight.lookup.LookupElement;
+import com.intellij.psi.PsiFile;
+import java.util.Arrays;
+import java.util.stream.Collectors;
+
+/** Tests auto-complete in project view files */
+public class ProjectViewCompletionTest extends ProjectViewIntegrationTestCase {
+
+  private PsiFile setInput(String... fileContents) {
+    return testFixture.configureByText(".blazeproject", Joiner.on("\n").join(fileContents));
+  }
+
+  private void assertResult(String... resultingFileContents) {
+    String s = testFixture.getFile().getText();
+    testFixture.checkResult(Joiner.on("\n").join(resultingFileContents));
+  }
+
+  public void testSectionTypeKeywords() {
+    setInput("<caret>");
+    String[] keywords = getCompletionItemsAsStrings();
+
+    assertThat(keywords)
+        .asList()
+        .containsAllIn(
+            Sections.getUndeprecatedParsers()
+                .stream()
+                .map(SectionParser::getName)
+                .collect(Collectors.toList()));
+  }
+
+  public void testColonAndNewLineAndIndentInsertedAfterListSection() {
+    setInput("direc<caret>");
+    assertThat(completeIfUnique()).isTrue();
+    assertResult("directories:", "  <caret>");
+  }
+
+  public void testWhitespaceDividerInsertedAfterScalarSection() {
+    setInput("impo<caret>");
+
+    LookupElement[] completionItems = testFixture.completeBasic();
+    assertThat(completionItems[0].getLookupString()).isEqualTo("import");
+
+    testFixture.getLookup().setCurrentItem(completionItems[0]);
+    testFixture.finishLookup(Lookup.NORMAL_SELECT_CHAR);
+
+    assertResult("import <caret>");
+  }
+
+  public void testColonDividerAndSpaceInsertedAfterScalarSection() {
+    setInput("works<caret>");
+    assertThat(completeIfUnique()).isTrue();
+    assertResult("workspace_type: <caret>");
+  }
+
+  public void testNoKeywordCompletionInListItem() {
+    setInput("directories:", "  <caret>");
+
+    String[] completionItems = getCompletionItemsAsStrings();
+    if (completionItems == null) {
+      fail("Spurious completion. New file contents: " + testFixture.getFile().getText());
+    }
+    assertThat(completionItems).isEmpty();
+  }
+
+  public void testNoKeywordCompletionAfterKeyword() {
+    setInput("import <caret>");
+
+    String[] completionItems = getCompletionItemsAsStrings();
+    if (completionItems == null) {
+      fail("Spurious completion. New file contents: " + testFixture.getFile().getText());
+    }
+    assertThat(completionItems).isEmpty();
+  }
+
+  public void testWorkspaceTypeCompletion() {
+    setInput("workspace_type: <caret>");
+
+    String[] types = getCompletionItemsAsStrings();
+
+    assertThat(types)
+        .asList()
+        .containsAllIn(
+            Arrays.stream(WorkspaceType.values())
+                .map(WorkspaceType::getName)
+                .collect(Collectors.toList()));
+  }
+
+  public void testAdditionalLanguagesCompletion() {
+    setInput("additional_languages:", "  <caret>");
+
+    String[] types = getCompletionItemsAsStrings();
+
+    assertThat(types)
+        .asList()
+        .containsAllIn(
+            Arrays.stream(LanguageClass.values())
+                .map(LanguageClass::getName)
+                .collect(Collectors.toList()));
+  }
+
+  public void testUniqueDirectoryCompleted() {
+    setInput("import <caret>");
+
+    createDirectory("java");
+
+    String[] completionItems = getCompletionItemsAsStrings();
+    assertThat(completionItems).isNull();
+    assertResult("import java<caret>");
+  }
+
+  public void testUniqueMultiSegmentDirectoryCompleted() {
+    setInput("import <caret>");
+
+    createDirectory("java/com/google");
+
+    String[] completionItems = getCompletionItemsAsStrings();
+    assertThat(completionItems).isNull();
+    assertResult("import java/com/google<caret>");
+  }
+
+  public void testNonDirectoriesIgnored() {
+    setInput("import <caret>");
+
+    createDirectory("java/com/google");
+    createFile("java/IgnoredFile.java");
+
+    String[] completionItems = getCompletionItemsAsStrings();
+    assertThat(completionItems).isNull();
+    assertResult("import java/com/google<caret>");
+  }
+
+  public void testMultipleDirectoryOptions() {
+    createDirectory("foo");
+    createDirectory("bar");
+    createDirectory("other");
+    createDirectory("ostrich/foo");
+    createDirectory("ostrich/fooz");
+
+    setInput("targets:", "  //o<caret>");
+
+    String[] completionItems = getCompletionItemsAsSuggestionStrings();
+    assertThat(completionItems).asList().containsExactly("other", "ostrich");
+
+    performTypingAction(testFixture.getEditor(), 's');
+
+    completionItems = getCompletionItemsAsStrings();
+    assertThat(completionItems).isNull();
+    assertResult("targets:", "  //ostrich<caret>");
+  }
+
+  public void testRuleCompletion() {
+    createFile("BUILD", "java_library(name = 'lib')");
+
+    setInput("targets:", "  //:<caret>");
+
+    String[] completionItems = 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
new file mode 100644
index 0000000..151d384
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/projectview/ProjectViewIntegrationTestCase.java
@@ -0,0 +1,54 @@
+/*
+ * 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;
+
+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.model.BlazeProjectData;
+import com.google.idea.blaze.base.model.RuleMap;
+import com.google.idea.blaze.base.model.primitives.ExecutionRootPath;
+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.WorkspacePathResolverImpl;
+
+/** Project view file specific integration test base */
+public abstract class ProjectViewIntegrationTestCase extends BlazeIntegrationTestCase {
+
+  @Override
+  protected void doSetup() {
+    mockBlazeProjectDataManager(getMockBlazeProjectData());
+  }
+
+  private BlazeProjectData getMockBlazeProjectData() {
+    BlazeRoots fakeRoots =
+        new BlazeRoots(
+            null,
+            ImmutableList.of(workspaceRoot.directory()),
+            new ExecutionRootPath("out/crosstool/bin"),
+            new ExecutionRootPath("out/crosstool/gen"));
+    return new BlazeProjectData(
+        0,
+        new RuleMap(ImmutableMap.of()),
+        fakeRoots,
+        new WorkingSet(ImmutableList.of(), ImmutableList.of(), ImmutableList.of()),
+        new WorkspacePathResolverImpl(workspaceRoot, fakeRoots),
+        null,
+        null,
+        null,
+        null);
+  }
+}
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
new file mode 100644
index 0000000..68cf418
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/projectview/ProjectViewParserIntegrationTest.java
@@ -0,0 +1,148 @@
+/*
+ * 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;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.base.Joiner;
+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.intellij.psi.PsiElement;
+import com.intellij.psi.PsiErrorElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.impl.source.tree.LeafElement;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/** Tests for the project view file parser */
+public class ProjectViewParserIntegrationTest extends ProjectViewIntegrationTestCase {
+
+  private final List<String> errors = Lists.newArrayList();
+
+  @Override
+  protected void doSetup() {
+    errors.clear();
+    super.doSetup();
+  }
+
+  public void testStandardFile() {
+    assertThat(
+            parse(
+                "directories:",
+                "  java/com/google/work",
+                "  java/com/google/other",
+                "",
+                "targets:",
+                "  //java/com/google/work/...:all",
+                "  //java/com/google/other/...:all"))
+        .isEqualTo(
+            Joiner.on("")
+                .join(
+                    "list_section(list_item, list_item), ", "list_section(list_item, list_item)"));
+    assertNoErrors();
+  }
+
+  public void testIncludeScalarSections() {
+    assertThat(
+            parse(
+                "import java/com/google/work/.blazeproject",
+                "",
+                "workspace_type: intellij_plugin",
+                "",
+                "import_target_output:",
+                "  //java/com/google/work:target",
+                "",
+                "test_sources:",
+                "  java/com/google/common/*"))
+        .isEqualTo(
+            Joiner.on("")
+                .join(
+                    "scalar_section(scalar_item), ",
+                    "scalar_section(scalar_item), ",
+                    "list_section(list_item), ",
+                    "list_section(list_item)"));
+    assertNoErrors();
+  }
+
+  public void testUnrecognizedKeyword() {
+    parse("impart java/com/google/work/.blazeproject", "", "workspace_trype: intellij_plugin");
+
+    assertContainsErrors("Unrecognized keyword: impart", "Unrecognized keyword: workspace_trype");
+  }
+
+  private String parse(String... lines) {
+    PsiFile file = createPsiFile(".blazeproject", lines);
+    collectErrors(file);
+    return treeToString(file);
+  }
+
+  private String treeToString(PsiElement psi) {
+    StringBuilder builder = new StringBuilder();
+    nodeToString(psi, builder);
+    return builder.toString();
+  }
+
+  private void nodeToString(PsiElement psi, StringBuilder builder) {
+    if (psi.getNode() instanceof LeafElement) {
+      return;
+    }
+    PsiElement[] children =
+        Arrays.stream(psi.getChildren())
+            .filter(t -> t instanceof ProjectViewPsiElement)
+            .toArray(PsiElement[]::new);
+    if (psi instanceof ProjectViewPsiElement) {
+      builder.append(psi.getNode().getElementType());
+      appendChildren(children, builder, true);
+    } else {
+      appendChildren(children, builder, false);
+    }
+  }
+
+  private void appendChildren(PsiElement[] childPsis, StringBuilder builder, boolean bracket) {
+    if (childPsis.length == 0) {
+      return;
+    }
+    if (bracket) {
+      builder.append("(");
+    }
+    nodeToString(childPsis[0], builder);
+    for (int i = 1; i < childPsis.length; i++) {
+      builder.append(", ");
+      nodeToString(childPsis[i], builder);
+    }
+    if (bracket) {
+      builder.append(")");
+    }
+  }
+
+  private void assertNoErrors() {
+    assertThat(errors).isEmpty();
+  }
+
+  private void assertContainsErrors(String... errors) {
+    assertThat(this.errors).containsAllIn(Arrays.asList(errors));
+  }
+
+  private void collectErrors(PsiElement psi) {
+    errors.addAll(
+        PsiUtils.findAllChildrenOfClassRecursive(psi, PsiErrorElement.class)
+            .stream()
+            .map(PsiErrorElement::getErrorDescription)
+            .collect(Collectors.toList()));
+  }
+}
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/projectview/lexer/ProjectViewLexerTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/projectview/lexer/ProjectViewLexerTest.java
new file mode 100644
index 0000000..610aef3
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/projectview/lexer/ProjectViewLexerTest.java
@@ -0,0 +1,133 @@
+/*
+ * 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.lexer;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.base.Joiner;
+import com.google.idea.blaze.base.lang.projectview.ProjectViewIntegrationTestCase;
+import com.google.idea.blaze.base.lang.projectview.lexer.ProjectViewLexerBase.Token;
+
+/** Tests for the project view file lexer */
+public class ProjectViewLexerTest extends ProjectViewIntegrationTestCase {
+
+  public void testStandardCase() {
+    String result =
+        tokenize(
+            "directories:",
+            "  java/com/google/work",
+            "  java/com/google/other",
+            "",
+            "targets:",
+            "  //java/com/google/work/...:all",
+            "  //java/com/google/other/...:all");
+
+    assertThat(result)
+        .isEqualTo(
+            Joiner.on(" ")
+                .join(
+                    "list_keyword :",
+                    "indent identifier",
+                    "indent identifier",
+                    "list_keyword :",
+                    "indent identifier : identifier",
+                    "indent identifier : identifier"));
+  }
+
+  public void testIncludeScalarSections() {
+    String result =
+        tokenize(
+            "import java/com/google/work/.blazeproject",
+            "",
+            "workspace_type: intellij_plugin",
+            "",
+            "import_target_output:",
+            "  //java/com/google/work:target",
+            "",
+            "test_sources:",
+            "  java/com/google/common/*");
+
+    assertThat(result)
+        .isEqualTo(
+            Joiner.on(" ")
+                .join(
+                    "scalar_keyword identifier",
+                    "scalar_keyword : identifier",
+                    "list_keyword :",
+                    "indent identifier : identifier",
+                    "list_keyword :",
+                    "indent identifier"));
+  }
+
+  public void testUnrecognizedKeyword() {
+    String result =
+        tokenize(
+            "impart java/com/google/work/.blazeproject", "", "workspace_trype: intellij_plugin");
+
+    assertThat(result)
+        .isEqualTo(Joiner.on(" ").join("identifier identifier", "identifier : identifier"));
+  }
+
+  private static String tokenize(String... lines) {
+    return names(tokens(Joiner.on("\n").join(lines)));
+  }
+
+  private static Token[] tokens(String input) {
+    Token[] tokens = new ProjectViewLexerBase(input).getTokens().toArray(new Token[0]);
+    assertNoCharactersMissing(input.length(), tokens);
+    return tokens;
+  }
+
+  /**
+   * Both the syntax highlighter and the parser require every character be accounted for by a
+   * lexical element.
+   */
+  private static void assertNoCharactersMissing(int totalLength, Token[] tokens) {
+    if (tokens.length != 0 && tokens[tokens.length - 1].right != totalLength) {
+      throw new AssertionError(
+          String.format(
+              "Last tokenized character '%s' doesn't match document length '%s'",
+              tokens[tokens.length - 1].right, totalLength));
+    }
+    int start = 0;
+    for (int i = 0; i < tokens.length; i++) {
+      Token token = tokens[i];
+      if (token.left != start) {
+        throw new AssertionError("Gap/inconsistency at: " + start);
+      }
+      start = token.right;
+    }
+  }
+
+  /** Returns a string containing the names of the tokens. */
+  private static String names(Token[] tokens) {
+    StringBuilder buf = new StringBuilder();
+    for (Token token : tokens) {
+      if (isIgnored(token.type)) {
+        continue;
+      }
+      if (buf.length() > 0) {
+        buf.append(' ');
+      }
+      buf.append(token.type);
+    }
+    return buf.toString();
+  }
+
+  private static boolean isIgnored(ProjectViewTokenType kind) {
+    return kind == ProjectViewTokenType.WHITESPACE || kind == ProjectViewTokenType.NEWLINE;
+  }
+}
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
new file mode 100644
index 0000000..1fe63b7
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/run/BlazeCommandRunConfigurationGenericHandlerIntegrationTest.java
@@ -0,0 +1,244 @@
+/*
+ * 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.command.BlazeCommandName;
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.model.RuleMap;
+import com.google.idea.blaze.base.model.primitives.ExecutionRootPath;
+import com.google.idea.blaze.base.model.primitives.TargetExpression;
+import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration.BlazeCommandRunConfigurationSettingsEditor;
+import com.google.idea.blaze.base.run.confighandler.BlazeCommandGenericRunConfigurationHandler;
+import com.google.idea.blaze.base.run.confighandler.BlazeUnknownRunConfigurationHandler;
+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.WorkspacePathResolverImpl;
+import com.intellij.openapi.options.ConfigurationException;
+import com.intellij.openapi.util.Disposer;
+import org.jdom.Element;
+
+/**
+ * Integration tests for {@link BlazeCommandRunConfiguration} with {@link
+ * BlazeCommandGenericRunConfigurationHandler} and {@link BlazeUnknownRunConfigurationHandler}.
+ */
+public class BlazeCommandRunConfigurationGenericHandlerIntegrationTest
+    extends BlazeIntegrationTestCase {
+  private static final BlazeCommandName COMMAND = BlazeCommandName.fromString("command");
+
+  private BlazeCommandRunConfigurationType type;
+  private BlazeCommandRunConfiguration configuration;
+
+  @Override
+  protected void doSetup() throws Exception {
+    super.doSetup();
+    // Without BlazeProjectData, the configuration editor is always disabled.
+    mockBlazeProjectDataManager(getMockBlazeProjectData());
+    type = BlazeCommandRunConfigurationType.getInstance();
+    configuration = type.getFactory().createTemplateConfiguration(getProject());
+  }
+
+  private BlazeProjectData getMockBlazeProjectData() {
+    BlazeRoots fakeRoots =
+        new BlazeRoots(
+            null,
+            ImmutableList.of(workspaceRoot.directory()),
+            new ExecutionRootPath("out/crosstool/bin"),
+            new ExecutionRootPath("out/crosstool/gen"));
+    return new BlazeProjectData(
+        0,
+        new RuleMap(ImmutableMap.of()),
+        fakeRoots,
+        new WorkingSet(ImmutableList.of(), ImmutableList.of(), ImmutableList.of()),
+        new WorkspacePathResolverImpl(workspaceRoot, fakeRoots),
+        null,
+        null,
+        null,
+        null);
+  }
+
+  public void testNewConfigurationHasUnknownHandler() {
+    assertThat(configuration.getHandler()).isInstanceOf(BlazeUnknownRunConfigurationHandler.class);
+  }
+
+  public void testSetTargetNullMakesGenericHandler() {
+    configuration.setTarget(null);
+    assertThat(configuration.getHandler())
+        .isInstanceOf(BlazeCommandGenericRunConfigurationHandler.class);
+  }
+
+  public void testTargetExpressionMakesGenericHandler() {
+    configuration.setTarget(TargetExpression.fromString("//..."));
+    assertThat(configuration.getHandler())
+        .isInstanceOf(BlazeCommandGenericRunConfigurationHandler.class);
+  }
+
+  public void testReadAndWriteMatches() throws Exception {
+    TargetExpression targetExpression = TargetExpression.fromString("//...");
+    configuration.setTarget(targetExpression);
+
+    BlazeCommandGenericRunConfigurationHandler handler =
+        (BlazeCommandGenericRunConfigurationHandler) configuration.getHandler();
+    handler.setCommand(COMMAND);
+    handler.setBlazeFlags(ImmutableList.of("--flag1", "--flag2"));
+    handler.setExeFlags(ImmutableList.of("--exeFlag1"));
+    handler.setBlazeBinary("/usr/bin/blaze");
+
+    Element element = new Element("test");
+    configuration.writeExternal(element);
+    BlazeCommandRunConfiguration readConfiguration =
+        type.getFactory().createTemplateConfiguration(getProject());
+    readConfiguration.readExternal(element);
+
+    assertThat(readConfiguration.getTarget()).isEqualTo(targetExpression);
+    assertThat(readConfiguration.getHandler())
+        .isInstanceOf(BlazeCommandGenericRunConfigurationHandler.class);
+
+    BlazeCommandGenericRunConfigurationHandler readHandler =
+        (BlazeCommandGenericRunConfigurationHandler) readConfiguration.getHandler();
+    assertThat(readHandler.getCommand()).isEqualTo(COMMAND);
+    assertThat(readHandler.getAllBlazeFlags()).containsExactly("--flag1", "--flag2").inOrder();
+    assertThat(readHandler.getAllExeFlags()).containsExactly("--exeFlag1");
+    assertThat(readHandler.getBlazeBinary()).isEqualTo("/usr/bin/blaze");
+  }
+
+  public void testReadAndWriteHandlesNulls() throws Exception {
+    Element element = new Element("test");
+    configuration.writeExternal(element);
+    BlazeCommandRunConfiguration readConfiguration =
+        type.getFactory().createTemplateConfiguration(getProject());
+    readConfiguration.readExternal(element);
+
+    assertThat(readConfiguration.getTarget()).isEqualTo(configuration.getTarget());
+    assertThat(readConfiguration.getHandler())
+        .isInstanceOf(BlazeUnknownRunConfigurationHandler.class);
+  }
+
+  public void testEditorWithUnknownHandlerDoesNotApplyTo() throws ConfigurationException {
+    assertThat(configuration.getTarget()).isNull();
+    assertThat(configuration.getHandler()).isInstanceOf(BlazeUnknownRunConfigurationHandler.class);
+
+    BlazeCommandRunConfigurationSettingsEditor editor =
+        new BlazeCommandRunConfigurationSettingsEditor(configuration);
+    // Because the configuration's handler is BlazeUnknownRunConfigurationHandler,
+    // resetting the editor to it will leave it in the disabled state.
+    editor.resetFrom(configuration);
+
+    BlazeCommandRunConfiguration readConfiguration =
+        type.getFactory().createTemplateConfiguration(getProject());
+    TargetExpression targetExpression = TargetExpression.fromString("//...");
+    readConfiguration.setTarget(targetExpression);
+
+    BlazeCommandGenericRunConfigurationHandler readHandler =
+        (BlazeCommandGenericRunConfigurationHandler) readConfiguration.getHandler();
+    readHandler.setCommand(COMMAND);
+    readHandler.setBlazeFlags(ImmutableList.of("--flag1", "--flag2"));
+    readHandler.setExeFlags(ImmutableList.of("--exeFlag1"));
+    readHandler.setBlazeBinary("/usr/bin/blaze");
+
+    // The editor is disabled, making applyEditorTo a no-op.
+    editor.applyEditorTo(readConfiguration);
+
+    assertThat(readConfiguration.getTarget()).isEqualTo(targetExpression);
+    assertThat(readConfiguration.getHandler())
+        .isInstanceOf(BlazeCommandGenericRunConfigurationHandler.class);
+
+    readHandler = (BlazeCommandGenericRunConfigurationHandler) readConfiguration.getHandler();
+    assertThat(readHandler.getCommand()).isEqualTo(COMMAND);
+    assertThat(readHandler.getAllBlazeFlags()).containsExactly("--flag1", "--flag2").inOrder();
+    assertThat(readHandler.getAllExeFlags()).containsExactly("--exeFlag1");
+    assertThat(readHandler.getBlazeBinary()).isEqualTo("/usr/bin/blaze");
+
+    Disposer.dispose(editor);
+  }
+
+  public void testEditorApplyToAndResetFromMatches() throws ConfigurationException {
+    BlazeCommandRunConfigurationSettingsEditor editor =
+        new BlazeCommandRunConfigurationSettingsEditor(configuration);
+    TargetExpression targetExpression = TargetExpression.fromString("//...");
+    configuration.setTarget(targetExpression);
+
+    BlazeCommandGenericRunConfigurationHandler handler =
+        (BlazeCommandGenericRunConfigurationHandler) configuration.getHandler();
+    handler.setCommand(COMMAND);
+    handler.setBlazeFlags(ImmutableList.of("--flag1", "--flag2"));
+    handler.setExeFlags(ImmutableList.of("--exeFlag1"));
+    handler.setBlazeBinary("/usr/bin/blaze");
+
+    editor.resetFrom(configuration);
+    BlazeCommandRunConfiguration readConfiguration =
+        type.getFactory().createTemplateConfiguration(getProject());
+    editor.applyEditorTo(readConfiguration);
+
+    assertThat(readConfiguration.getTarget()).isEqualTo(targetExpression);
+    assertThat(readConfiguration.getHandler())
+        .isInstanceOf(BlazeCommandGenericRunConfigurationHandler.class);
+
+    BlazeCommandGenericRunConfigurationHandler readHandler =
+        (BlazeCommandGenericRunConfigurationHandler) readConfiguration.getHandler();
+    assertThat(readHandler.getCommand()).isEqualTo(handler.getCommand());
+    assertThat(readHandler.getAllBlazeFlags()).isEqualTo(handler.getAllBlazeFlags());
+    assertThat(readHandler.getAllExeFlags()).isEqualTo(handler.getAllExeFlags());
+    assertThat(readHandler.getBlazeBinary()).isEqualTo(handler.getBlazeBinary());
+
+    Disposer.dispose(editor);
+  }
+
+  public void testEditorApplyToAndResetFromHandlesNulls() throws ConfigurationException {
+    BlazeCommandRunConfigurationSettingsEditor editor =
+        new BlazeCommandRunConfigurationSettingsEditor(configuration);
+
+    // Call setTarget to initialize a generic handler, or this won't apply anything.
+    configuration.setTarget(null);
+    assertThat(configuration.getTarget()).isNull();
+    assertThat(configuration.getHandler())
+        .isInstanceOf(BlazeCommandGenericRunConfigurationHandler.class);
+    BlazeCommandGenericRunConfigurationHandler handler =
+        (BlazeCommandGenericRunConfigurationHandler) configuration.getHandler();
+
+    editor.resetFrom(configuration);
+
+    BlazeCommandRunConfiguration readConfiguration =
+        type.getFactory().createTemplateConfiguration(getProject());
+    TargetExpression targetExpression = TargetExpression.fromString("//...");
+    readConfiguration.setTarget(targetExpression);
+
+    BlazeCommandGenericRunConfigurationHandler readHandler =
+        (BlazeCommandGenericRunConfigurationHandler) readConfiguration.getHandler();
+    readHandler.setCommand(COMMAND);
+    readHandler.setBlazeFlags(ImmutableList.of("--flag1", "--flag2"));
+    readHandler.setExeFlags(ImmutableList.of("--exeFlag1"));
+    readHandler.setBlazeBinary("/usr/bin/blaze");
+
+    editor.applyEditorTo(readConfiguration);
+
+    assertThat(readConfiguration.getTarget()).isNull();
+    assertThat(configuration.getHandler())
+        .isInstanceOf(BlazeCommandGenericRunConfigurationHandler.class);
+
+    readHandler = (BlazeCommandGenericRunConfigurationHandler) readConfiguration.getHandler();
+    assertThat(readHandler.getCommand()).isEqualTo(handler.getCommand());
+    assertThat(readHandler.getAllBlazeFlags()).isEqualTo(handler.getAllBlazeFlags());
+    assertThat(readHandler.getAllExeFlags()).isEqualTo(handler.getAllExeFlags());
+    assertThat(readHandler.getBlazeBinary()).isEqualTo(handler.getBlazeBinary());
+
+    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
new file mode 100644
index 0000000..a0a044d
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/run/BlazeCommandRunConfigurationSettingsEditorTest.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.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.model.BlazeProjectData;
+import com.google.idea.blaze.base.model.RuleMap;
+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.BlazeRoots;
+import com.google.idea.blaze.base.sync.workspace.WorkingSet;
+import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolverImpl;
+import com.intellij.openapi.options.ConfigurationException;
+import com.intellij.openapi.util.Disposer;
+
+/** Tests for {@link BlazeCommandRunConfiguration.BlazeCommandRunConfigurationSettingsEditor}. */
+public class BlazeCommandRunConfigurationSettingsEditorTest extends BlazeIntegrationTestCase {
+
+  private BlazeCommandRunConfigurationType type;
+  private BlazeCommandRunConfiguration configuration;
+
+  @Override
+  protected void doSetup() throws Exception {
+    super.doSetup();
+    // Without BlazeProjectData, the configuration editor is always disabled.
+    mockBlazeProjectDataManager(getMockBlazeProjectData());
+    type = BlazeCommandRunConfigurationType.getInstance();
+    configuration = type.getFactory().createTemplateConfiguration(getProject());
+  }
+
+  private BlazeProjectData getMockBlazeProjectData() {
+    BlazeRoots fakeRoots =
+        new BlazeRoots(
+            null,
+            ImmutableList.of(workspaceRoot.directory()),
+            new ExecutionRootPath("out/crosstool/bin"),
+            new ExecutionRootPath("out/crosstool/gen"));
+    return new BlazeProjectData(
+        0,
+        new RuleMap(ImmutableMap.of()),
+        fakeRoots,
+        new WorkingSet(ImmutableList.of(), ImmutableList.of(), ImmutableList.of()),
+        new WorkspacePathResolverImpl(workspaceRoot, fakeRoots),
+        null,
+        null,
+        null,
+        null);
+  }
+
+  public void testEditorApplyToAndResetFromMatches() throws ConfigurationException {
+    BlazeCommandRunConfigurationSettingsEditor editor =
+        new BlazeCommandRunConfigurationSettingsEditor(configuration);
+    Label label = new Label("//package:rule");
+    configuration.setTarget(label);
+
+    editor.resetFrom(configuration);
+    BlazeCommandRunConfiguration readConfiguration =
+        type.getFactory().createTemplateConfiguration(getProject());
+    editor.applyEditorTo(readConfiguration);
+
+    assertThat(readConfiguration.getTarget()).isEqualTo(label);
+
+    Disposer.dispose(editor);
+  }
+
+  public void testEditorApplyToAndResetFromHandlesNulls() throws ConfigurationException {
+    BlazeCommandRunConfigurationSettingsEditor editor =
+        new BlazeCommandRunConfigurationSettingsEditor(configuration);
+
+    editor.resetFrom(configuration);
+    BlazeCommandRunConfiguration readConfiguration =
+        type.getFactory().createTemplateConfiguration(getProject());
+    editor.applyEditorTo(readConfiguration);
+
+    assertThat(readConfiguration.getTarget()).isEqualTo(configuration.getTarget());
+
+    Disposer.dispose(editor);
+  }
+}
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/run/TestRuleHeuristicTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/run/TestRuleHeuristicTest.java
new file mode 100644
index 0000000..16e7de0
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/run/TestRuleHeuristicTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.idea.blaze.base.BlazeIntegrationTestCase;
+import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+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;
+import java.io.File;
+import java.util.Collection;
+
+/** Integration tests for {@link TestRuleHeuristic}. */
+public class TestRuleHeuristicTest extends BlazeIntegrationTestCase {
+
+  public void testTestSizeMatched() throws Exception {
+    File source = new File("java/com/foo/FooTest.java");
+    Collection<RuleIdeInfo> rules =
+        ImmutableList.of(
+            RuleIdeInfo.builder()
+                .setLabel("//foo:test1")
+                .setKind("java_test")
+                .setTestInfo(TestIdeInfo.builder().setTestSize(TestSize.MEDIUM))
+                .build(),
+            RuleIdeInfo.builder()
+                .setLabel("//foo:test2")
+                .setKind("java_test")
+                .setTestInfo(TestIdeInfo.builder().setTestSize(TestSize.SMALL))
+                .build());
+    Label match = TestRuleHeuristic.chooseTestTargetForSourceFile(source, rules, TestSize.SMALL);
+    assertThat(match).isEqualTo(new Label("//foo:test2"));
+  }
+
+  public void testRuleNameMatched() throws Exception {
+    File source = new File("java/com/foo/FooTest.java");
+    Collection<RuleIdeInfo> rules =
+        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);
+    assertThat(match).isEqualTo(new Label("//foo:FooTest"));
+  }
+
+  public void testNoMatchFallBackToFirstRule() throws Exception {
+    File source = new File("java/com/foo/FooTest.java");
+    ImmutableList<RuleIdeInfo> rules =
+        ImmutableList.of(
+            RuleIdeInfo.builder()
+                .setLabel("//bar:BarTest")
+                .setKind("java_test")
+                .setTestInfo(TestIdeInfo.builder().setTestSize(TestSize.MEDIUM))
+                .build(),
+            RuleIdeInfo.builder()
+                .setLabel("//foo:OtherTest")
+                .setKind("java_test")
+                .setTestInfo(TestIdeInfo.builder().setTestSize(TestSize.SMALL))
+                .build());
+    Label match = TestRuleHeuristic.chooseTestTargetForSourceFile(source, rules, TestSize.LARGE);
+    assertThat(match).isEqualTo(new Label("//bar:BarTest"));
+  }
+
+  public void testRuleNameCheckedBeforeTestSize() throws Exception {
+    File source = new File("java/com/foo/FooTest.java");
+    ImmutableList<RuleIdeInfo> rules =
+        ImmutableList.of(
+            RuleIdeInfo.builder()
+                .setLabel("//bar:BarTest")
+                .setKind("java_test")
+                .setTestInfo(TestIdeInfo.builder().setTestSize(TestSize.SMALL))
+                .build(),
+            RuleIdeInfo.builder()
+                .setLabel("//foo:FooTest")
+                .setKind("java_test")
+                .setTestInfo(TestIdeInfo.builder().setTestSize(TestSize.MEDIUM))
+                .build());
+    Label match = TestRuleHeuristic.chooseTestTargetForSourceFile(source, rules, TestSize.SMALL);
+    assertThat(match).isEqualTo(new Label("//foo:FooTest"));
+  }
+}
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/sync/ImportRootsTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/sync/ImportRootsTest.java
new file mode 100644
index 0000000..6a2dadf
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/sync/ImportRootsTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.base.BlazeIntegrationTestCase;
+import com.google.idea.blaze.base.bazel.BuildSystemProvider;
+import com.google.idea.blaze.base.model.primitives.Label;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
+import com.google.idea.blaze.base.projectview.section.sections.DirectoryEntry;
+import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
+import com.google.idea.blaze.base.sync.projectview.ImportRoots;
+import java.util.stream.Collectors;
+
+/** Tests for ImportRoots */
+public class ImportRootsTest extends BlazeIntegrationTestCase {
+
+  public void testBazelArtifactDirectoriesExcluded() {
+    ImportRoots importRoots =
+        ImportRoots.builder(workspaceRoot, BuildSystem.Bazel)
+            .add(new DirectoryEntry(new WorkspacePath(""), true))
+            .build();
+
+    ImmutableList<String> artifactDirs =
+        BuildSystemProvider.getBuildSystemProvider(BuildSystem.Bazel)
+            .buildArtifactDirectories(workspaceRoot);
+
+    assertThat(importRoots.rootDirectories()).containsExactly(new WorkspacePath(""));
+    assertThat(
+            importRoots
+                .excludeDirectories()
+                .stream()
+                .map(WorkspacePath::relativePath)
+                .collect(Collectors.toList()))
+        .containsExactlyElementsIn(artifactDirs);
+
+    assertThat(artifactDirs).contains("bazel-" + workspaceRoot.directory().getName());
+  }
+
+  public void testNoAddedExclusionsWithoutWorkspaceRootInclusion() {
+    ImportRoots importRoots =
+        ImportRoots.builder(workspaceRoot, BuildSystem.Bazel)
+            .add(new DirectoryEntry(new WorkspacePath("foo/bar"), true))
+            .build();
+
+    assertThat(importRoots.rootDirectories()).containsExactly(new WorkspacePath("foo/bar"));
+    assertThat(importRoots.excludeDirectories()).isEmpty();
+  }
+
+  public void testNoAddedExclusionsForBlaze() {
+    ImportRoots importRoots =
+        ImportRoots.builder(workspaceRoot, BuildSystem.Blaze)
+            .add(new DirectoryEntry(new WorkspacePath(""), true))
+            .build();
+
+    assertThat(importRoots.rootDirectories()).containsExactly(new WorkspacePath(""));
+    assertThat(importRoots.excludeDirectories()).isEmpty();
+  }
+
+  // if the workspace root is an included directory, all rules should be imported as sources.
+  public void testAllLabelsIncludedUnderWorkspaceRoot() {
+    ImportRoots importRoots =
+        ImportRoots.builder(workspaceRoot, BuildSystem.Blaze)
+            .add(new DirectoryEntry(new WorkspacePath(""), true))
+            .build();
+
+    assertThat(importRoots.importAsSource(new Label("//:target"))).isTrue();
+    assertThat(importRoots.importAsSource(new Label("//foo/bar:target"))).isTrue();
+  }
+
+  public void testNonOverlappingDirectoriesAreNotFilteredOut() {
+    ImportRoots importRoots =
+        ImportRoots.builder(workspaceRoot, BuildSystem.Blaze)
+            .add(new DirectoryEntry(new WorkspacePath("root0/subdir0"), true))
+            .add(new DirectoryEntry(new WorkspacePath("root0/subdir1"), true))
+            .add(new DirectoryEntry(new WorkspacePath("root1"), true))
+            .build();
+    assertThat(importRoots.rootDirectories())
+        .containsExactly(
+            new WorkspacePath("root0/subdir0"),
+            new WorkspacePath("root0/subdir1"),
+            new WorkspacePath("root1"));
+  }
+
+  public void testOverlappingDirectoriesAreFilteredOut() {
+    ImportRoots importRoots =
+        ImportRoots.builder(workspaceRoot, BuildSystem.Blaze)
+            .add(new DirectoryEntry(new WorkspacePath("root"), true))
+            .add(new DirectoryEntry(new WorkspacePath("root"), true))
+            .add(new DirectoryEntry(new WorkspacePath("root/subdir"), true))
+            .build();
+    assertThat(importRoots.rootDirectories()).containsExactly(new WorkspacePath("root"));
+  }
+
+  public void testWorkspaceRootIsOnlyDirectoryLeft() {
+    ImportRoots importRoots =
+        ImportRoots.builder(workspaceRoot, BuildSystem.Blaze)
+            .add(new DirectoryEntry(new WorkspacePath("."), true))
+            .add(new DirectoryEntry(new WorkspacePath("."), true))
+            .add(new DirectoryEntry(new WorkspacePath("root/subdir"), true))
+            .build();
+    assertThat(importRoots.rootDirectories()).containsExactly(new WorkspacePath("."));
+  }
+}
diff --git a/base/tests/unittests/com/google/idea/blaze/base/BlazeIconsTest.java b/base/tests/unittests/com/google/idea/blaze/base/BlazeIconsTest.java
new file mode 100644
index 0000000..bf0a68b
--- /dev/null
+++ b/base/tests/unittests/com/google/idea/blaze/base/BlazeIconsTest.java
@@ -0,0 +1,36 @@
+/*
+ * 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 org.junit.Assert.assertNotNull;
+
+import icons.BlazeIcons;
+import javax.swing.Icon;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for BlazeIcons */
+@RunWith(JUnit4.class)
+public class BlazeIconsTest {
+
+  @Test
+  public void testIcon() {
+    Icon icon = BlazeIcons.Blaze;
+
+    assertNotNull(icon);
+  }
+}
diff --git a/base/tests/unittests/com/google/idea/blaze/base/command/BlazeCommandNameTest.java b/base/tests/unittests/com/google/idea/blaze/base/command/BlazeCommandNameTest.java
new file mode 100644
index 0000000..6d45278
--- /dev/null
+++ b/base/tests/unittests/com/google/idea/blaze/base/command/BlazeCommandNameTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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.command;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Collections2;
+import java.util.Collection;
+import javax.annotation.Nullable;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link BlazeCommandName}. */
+@RunWith(JUnit4.class)
+public class BlazeCommandNameTest {
+  @Test
+  public void emptyNameShouldThrow() {
+    try {
+      BlazeCommandName.fromString("");
+      fail("Empty commands should not be allowed.");
+    } catch (IllegalArgumentException expected) {
+    }
+  }
+
+  @Test
+  public void hardcodedNamesShouldBeKnown() {
+    assertThat(BlazeCommandName.knownCommands()).contains(BlazeCommandName.MOBILE_INSTALL);
+  }
+
+  @Test
+  public void userCommandNamesShouldBecomeKnown() {
+    Collection<String> knownCommandStrings =
+        Collections2.transform(
+            BlazeCommandName.knownCommands(),
+            new Function<BlazeCommandName, String>() {
+              @Nullable
+              @Override
+              public String apply(BlazeCommandName input) {
+                return input.toString();
+              }
+            });
+    assertThat(knownCommandStrings).doesNotContain("user-command");
+    BlazeCommandName userCommand = BlazeCommandName.fromString("user-command");
+    assertThat(BlazeCommandName.knownCommands()).contains(userCommand);
+  }
+}
diff --git a/base/tests/unittests/com/google/idea/blaze/base/command/BlazeCommandTest.java b/base/tests/unittests/com/google/idea/blaze/base/command/BlazeCommandTest.java
new file mode 100644
index 0000000..64b6894
--- /dev/null
+++ b/base/tests/unittests/com/google/idea/blaze/base/command/BlazeCommandTest.java
@@ -0,0 +1,126 @@
+/*
+ * 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.command;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.base.BlazeTestCase;
+import com.google.idea.blaze.base.model.primitives.Label;
+import com.google.idea.blaze.base.model.primitives.TargetExpression;
+import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
+import com.google.idea.blaze.base.settings.BlazeUserSettings;
+import com.google.idea.common.experiments.ExperimentService;
+import com.google.idea.common.experiments.MockExperimentService;
+import java.util.Collections;
+import java.util.List;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link BlazeCommand}. */
+@RunWith(JUnit4.class)
+public class BlazeCommandTest extends BlazeTestCase {
+
+  @Override
+  protected void initTest(
+      @NotNull Container applicationServices, @NotNull Container projectServices) {
+    ExperimentService experimentService = new MockExperimentService();
+    applicationServices.register(ExperimentService.class, experimentService);
+    applicationServices.register(BlazeUserSettings.class, new BlazeUserSettings());
+  }
+
+  @Test
+  public void addedFlagsShouldGoAtStart() {
+    List<String> flagsCommand =
+        BlazeCommand.builder(BuildSystem.Blaze, BlazeCommandName.RUN)
+            .addTargets(new Label("//a:b"))
+            .addBlazeFlags("--flag1", "--flag2")
+            .addExeFlags("--exeFlag1", "--exeFlag2")
+            .build()
+            .toList();
+    // First three strings are always 'blaze run --tool_tag=ijwb:IDEA:ultimate'
+    assertThat(flagsCommand.subList(3, 5)).isEqualTo(ImmutableList.of("--flag1", "--flag2"));
+  }
+
+  @Test
+  public void targetsShouldGoAfterBlazeFlagsAndDoubleHyphen() {
+    List<String> command =
+        BlazeCommand.builder(BuildSystem.Blaze, BlazeCommandName.RUN)
+            .addTargets(new Label("//a:b"), new Label("//c:d"))
+            .addBlazeFlags("--flag1", "--flag2")
+            .addExeFlags("--exeFlag1", "--exeFlag2")
+            .build()
+            .toList();
+    // First six strings should be 'blaze run --tool_tag=ijwb:IDEA:ultimate --flag1 --flag2 --'
+    assertThat(command.indexOf("--")).isEqualTo(5);
+    assertThat(Collections.indexOfSubList(command, ImmutableList.of("//a:b", "//c:d")))
+        .isEqualTo(6);
+  }
+
+  @Test
+  public void exeFlagsShouldGoLast() {
+    List<String> command =
+        BlazeCommand.builder(BuildSystem.Blaze, BlazeCommandName.RUN)
+            .addTargets(new Label("//a:b"), new Label("//c:d"))
+            .addBlazeFlags("--flag1", "--flag2")
+            .addExeFlags("--exeFlag1", "--exeFlag2")
+            .build()
+            .toList();
+    List<String> finalTwoFlags = command.subList(command.size() - 2, command.size());
+    assertThat(finalTwoFlags).containsExactly("--exeFlag1", "--exeFlag2");
+  }
+
+  @Test
+  public void maintainUserOrderingOfTargets() {
+    List<String> command =
+        BlazeCommand.builder(BuildSystem.Blaze, BlazeCommandName.RUN)
+            .addTargets(
+                new Label("//a:b"), TargetExpression.fromString("-//e:f"), new Label("//c:d"))
+            .addBlazeFlags("--flag1", "--flag2")
+            .addExeFlags("--exeFlag1", "--exeFlag2")
+            .build()
+            .toList();
+
+    ImmutableList<Object> expected =
+        ImmutableList.builder()
+            .add("/usr/bin/blaze")
+            .add("run")
+            .add(BlazeFlags.getToolTagFlag())
+            .add("--flag1")
+            .add("--flag2")
+            .add("--")
+            .add("//a:b")
+            .add("-//e:f")
+            .add("//c:d")
+            .add("--exeFlag1")
+            .add("--exeFlag2")
+            .build();
+    assertThat(command).isEqualTo(expected);
+  }
+
+  @Test
+  public void binaryAndCommandShouldComeFirst() {
+    List<String> command =
+        BlazeCommand.builder(BuildSystem.Blaze, BlazeCommandName.BUILD)
+            .addBlazeFlags("--flag")
+            .addExeFlags("--exeFlag")
+            .build()
+            .toList();
+    assertThat(command.subList(0, 2)).isEqualTo(ImmutableList.of("/usr/bin/blaze", "build"));
+  }
+}
diff --git a/base/tests/unittests/com/google/idea/blaze/base/filecache/FileDifferTest.java b/base/tests/unittests/com/google/idea/blaze/base/filecache/FileDifferTest.java
new file mode 100644
index 0000000..ab28819
--- /dev/null
+++ b/base/tests/unittests/com/google/idea/blaze/base/filecache/FileDifferTest.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.filecache;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+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.io.FileAttributeProvider;
+import com.google.idea.common.experiments.ExperimentService;
+import com.google.idea.common.experiments.MockExperimentService;
+import java.io.File;
+import java.util.List;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link FileDiffer} */
+@RunWith(JUnit4.class)
+public class FileDifferTest extends BlazeTestCase {
+  private MockFileAttributeProvider fileModificationProvider;
+
+  private static class MockFileAttributeProvider extends FileAttributeProvider {
+    List<Long> times = Lists.newArrayList();
+    int index;
+
+    public MockFileAttributeProvider add(long time) {
+      times.add(time);
+      return this;
+    }
+
+    @Override
+    public long getFileModifiedTime(@NotNull File file) {
+      return times.get(index++);
+    }
+  }
+
+  @Override
+  protected void initTest(
+      @NotNull Container applicationServices, @NotNull Container projectServices) {
+    super.initTest(applicationServices, projectServices);
+    applicationServices.register(ExperimentService.class, new MockExperimentService());
+    applicationServices.register(BlazeExecutor.class, new MockBlazeExecutor());
+
+    this.fileModificationProvider = new MockFileAttributeProvider();
+    applicationServices.register(FileAttributeProvider.class, fileModificationProvider);
+  }
+
+  @Test
+  public void testDiffWithDiffMethodTimestamp() throws Exception {
+    ImmutableMap<File, Long> oldState =
+        ImmutableMap.<File, Long>builder()
+            .put(new File("file1"), 13L)
+            .put(new File("file2"), 17L)
+            .put(new File("file3"), 21L)
+            .build();
+    List<File> fileList = ImmutableList.of(new File("file1"), new File("file2"));
+    fileModificationProvider.add(13).add(122);
+
+    List<File> newFiles = Lists.newArrayList();
+    List<File> removedFiles = Lists.newArrayList();
+    FileDiffer.updateFiles(oldState, fileList, newFiles, removedFiles);
+
+    assertThat(newFiles).containsExactly(new File("file2"));
+    assertThat(removedFiles).containsExactly(new File("file3"));
+  }
+}
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
new file mode 100644
index 0000000..2e3dea8
--- /dev/null
+++ b/base/tests/unittests/com/google/idea/blaze/base/io/MockWorkspaceScanner.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.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/issueparser/BlazeIssueParserTest.java b/base/tests/unittests/com/google/idea/blaze/base/issueparser/BlazeIssueParserTest.java
new file mode 100644
index 0000000..322754d
--- /dev/null
+++ b/base/tests/unittests/com/google/idea/blaze/base/issueparser/BlazeIssueParserTest.java
@@ -0,0 +1,299 @@
+/*
+ * 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.issueparser;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.google.idea.blaze.base.BlazeTestCase;
+import com.google.idea.blaze.base.model.primitives.TargetExpression;
+import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
+import com.google.idea.blaze.base.projectview.ProjectView;
+import com.google.idea.blaze.base.projectview.ProjectViewManager;
+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.sections.TargetSection;
+import com.google.idea.blaze.base.scope.output.IssueOutput;
+import com.google.idea.common.experiments.ExperimentService;
+import com.google.idea.common.experiments.MockExperimentService;
+import java.io.File;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link BlazeIssueParser}. */
+@RunWith(JUnit4.class)
+public class BlazeIssueParserTest extends BlazeTestCase {
+
+  private ProjectViewManager projectViewManager;
+  private WorkspaceRoot workspaceRoot;
+
+  @Override
+  protected void initTest(
+      @NotNull Container applicationServices, @NotNull Container projectServices) {
+    super.initTest(applicationServices, projectServices);
+
+    applicationServices.register(ExperimentService.class, new MockExperimentService());
+
+    projectViewManager = mock(ProjectViewManager.class);
+    projectServices.register(ProjectViewManager.class, projectViewManager);
+
+    workspaceRoot = new WorkspaceRoot(new File("/root"));
+  }
+
+  @Test
+  public void testParseTargetError() {
+    BlazeIssueParser blazeIssueParser = new BlazeIssueParser(project, workspaceRoot);
+    IssueOutput issue =
+        blazeIssueParser.parseIssue(
+            "ERROR: invalid target format "
+                + "'//javatests/com/google/devtools/aswb/testapps/aswbtestlib/...:alls': "
+                + "invalid package name "
+                + "'javatests/com/google/devtools/aswb/testapps/aswbtestlib/...': "
+                + "package name component contains only '.' characters.");
+    assertNotNull(issue);
+    assertThat(issue.getCategory()).isEqualTo(IssueOutput.Category.ERROR);
+  }
+
+  @Test
+  public void testParseCompileError() {
+    BlazeIssueParser blazeIssueParser = new BlazeIssueParser(project, workspaceRoot);
+    IssueOutput issue =
+        blazeIssueParser.parseIssue(
+            "java/com/google/android/samples/helloroot/math/DivideMath.java:17: error: "
+                + "non-static variable this cannot be referenced from a static context");
+    assertNotNull(issue);
+    assertThat(issue.getFile().getPath())
+        .isEqualTo("/root/java/com/google/android/samples/helloroot/math/DivideMath.java");
+    assertThat(issue.getLine()).isEqualTo(17);
+    assertThat(issue.getMessage())
+        .isEqualTo("non-static variable this cannot be referenced from a static context");
+    assertThat(issue.getCategory()).isEqualTo(IssueOutput.Category.ERROR);
+  }
+
+  @Test
+  public void testParseCompileErrorWithColumn() {
+    BlazeIssueParser blazeIssueParser = new BlazeIssueParser(project, workspaceRoot);
+    IssueOutput issue =
+        blazeIssueParser.parseIssue(
+            "java/com/google/devtools/aswb/pluginrepo/googleplex/PluginsEndpoint.java:33:26: "
+                + "error: '|' is not preceded with whitespace.");
+    assertNotNull(issue);
+    assertThat(issue.getLine()).isEqualTo(33);
+    assertThat(issue.getMessage()).isEqualTo("'|' is not preceded with whitespace.");
+    assertThat(issue.getCategory()).isEqualTo(IssueOutput.Category.ERROR);
+  }
+
+  @Test
+  public void testParseCompileErrorWithAbsolutePath() {
+    BlazeIssueParser blazeIssueParser = new BlazeIssueParser(project, workspaceRoot);
+    IssueOutput issue =
+        blazeIssueParser.parseIssue(
+            "/root/java/com/google/android/samples/helloroot/math/DivideMath.java:17: error: "
+                + "non-static variable this cannot be referenced from a static context");
+    assertNotNull(issue);
+    assertThat(issue.getFile().getPath())
+        .isEqualTo("/root/java/com/google/android/samples/helloroot/math/DivideMath.java");
+  }
+
+  @Test
+  public void testParseCompileErrorWithDepotPath() {
+    BlazeIssueParser blazeIssueParser = new BlazeIssueParser(project, workspaceRoot);
+    IssueOutput issue =
+        blazeIssueParser.parseIssue(
+            "//depot/google3/package_path/DivideMath.java:17: error: "
+                + "non-static variable this cannot be referenced from a static context");
+    assertNotNull(issue);
+    assertThat(issue.getFile().getPath()).isEqualTo("/root/package_path/DivideMath.java");
+  }
+
+  @Test
+  public void testParseBuildError() {
+    BlazeIssueParser blazeIssueParser = new BlazeIssueParser(project, workspaceRoot);
+    IssueOutput issue =
+        blazeIssueParser.parseIssue(
+            "ERROR: /path/to/root/javatests/package_path/BUILD:42:12: "
+                + "Target '//java/package_path:helloroot_visibility' failed");
+    assertNotNull(issue);
+    assertThat(issue.getFile().getPath()).isEqualTo("/path/to/root/javatests/package_path/BUILD");
+    assertThat(issue.getLine()).isEqualTo(42);
+    assertThat(issue.getMessage())
+        .isEqualTo("Target '//java/package_path:helloroot_visibility' failed");
+    assertThat(issue.getCategory()).isEqualTo(IssueOutput.Category.ERROR);
+  }
+
+  @Test
+  public void testParseLinelessBuildError() {
+    BlazeIssueParser blazeIssueParser = new BlazeIssueParser(project, workspaceRoot);
+    IssueOutput issue =
+        blazeIssueParser.parseIssue(
+            "ERROR: /path/to/root/java/package_path/BUILD:char offsets 1222--1229: "
+                + "name 'grubber' is not defined");
+    assertNotNull(issue);
+    assertThat(issue.getFile().getPath()).isEqualTo("/path/to/root/java/package_path/BUILD");
+    assertThat(issue.getMessage()).isEqualTo("name 'grubber' is not defined");
+    assertThat(issue.getCategory()).isEqualTo(IssueOutput.Category.ERROR);
+  }
+
+  @Test
+  public void testLabelProjectViewParser() {
+    ProjectViewSet projectViewSet =
+        ProjectViewSet.builder()
+            .add(
+                new File(".blazeproject"),
+                ProjectView.builder()
+                    .add(
+                        ListSection.builder(TargetSection.KEY)
+                            .add(TargetExpression.fromString("//package/path:hello4")))
+                    .build())
+            .build();
+    when(projectViewManager.getProjectViewSet()).thenReturn(projectViewSet);
+
+    BlazeIssueParser blazeIssueParser = new BlazeIssueParser(project, workspaceRoot);
+    IssueOutput issue =
+        blazeIssueParser.parseIssue(
+            "no such target '//package/path:hello4': "
+                + "target 'hello4' not declared in package 'package/path' "
+                + "defined by /path/to/root/package/path/BUILD");
+    assertNotNull(issue);
+    assertThat(issue.getFile().getPath()).isEqualTo(".blazeproject");
+    assertThat(issue.getCategory()).isEqualTo(IssueOutput.Category.ERROR);
+  }
+
+  @Test
+  public void testPackageProjectViewParser() {
+    ProjectViewSet projectViewSet =
+        ProjectViewSet.builder()
+            .add(
+                new File(".blazeproject"),
+                ProjectView.builder()
+                    .add(
+                        ListSection.builder(TargetSection.KEY)
+                            .add(TargetExpression.fromString("//package/path:hello4")))
+                    .build())
+            .build();
+    when(projectViewManager.getProjectViewSet()).thenReturn(projectViewSet);
+
+    BlazeIssueParser blazeIssueParser = new BlazeIssueParser(project, workspaceRoot);
+    IssueOutput issue =
+        blazeIssueParser.parseIssue(
+            "no such package 'package/path': BUILD file not found on package path");
+    assertNotNull(issue);
+    assertThat(issue.getFile().getPath()).isEqualTo(".blazeproject");
+    assertThat(issue.getCategory()).isEqualTo(IssueOutput.Category.ERROR);
+  }
+
+  @Test
+  public void testDeletedBUILDFileButLeftPackageInLocalTargets() {
+    ProjectViewSet projectViewSet =
+        ProjectViewSet.builder()
+            .add(
+                new File(".blazeproject"),
+                ProjectView.builder()
+                    .add(
+                        ListSection.builder(TargetSection.KEY)
+                            .add(TargetExpression.fromString("//tests/com/google/a/b/c/d/baz:baz")))
+                    .build())
+            .build();
+    when(projectViewManager.getProjectViewSet()).thenReturn(projectViewSet);
+
+    BlazeIssueParser blazeIssueParser = new BlazeIssueParser(project, workspaceRoot);
+    IssueOutput issue =
+        blazeIssueParser.parseIssue(
+            "Error:com.google.a.b.Exception exception in Bar: no targets found beneath "
+                + "'tests/com/google/a/b/c/d/baz' Thrown during call: ...");
+    assertNotNull(issue);
+    assertNotNull(issue.getFile());
+    assertThat(issue.getFile().getPath()).isEqualTo(".blazeproject");
+    assertThat(issue.getCategory()).isEqualTo(IssueOutput.Category.ERROR);
+    assertThat(issue.getMessage())
+        .isEqualTo("no targets found beneath 'tests/com/google/a/b/c/d/baz'");
+  }
+
+  @Test
+  public void testMultilineTraceback() {
+    String[] lines =
+        new String[] {
+          "ERROR: /home/plumpy/whatever:9:12: Traceback (most recent call last):",
+          "\tFile \"/path/to/root/java/com/google/android/samples/helloroot/BUILD\", line 8",
+          "\t\tpackage_group(name = BAD_FUNCTION(\"hellogoogle...\"), ...\"])",
+          "\tFile \"/path/to/root/java/com/google/android/samples/helloroot/BUILD\", line 9, "
+              + "in package_group",
+          "\t\tBAD_FUNCTION",
+          "name 'BAD_FUNCTION' is not defined."
+        };
+
+    BlazeIssueParser blazeIssueParser = new BlazeIssueParser(project, workspaceRoot);
+    for (int i = 0; i < lines.length - 1; ++i) {
+      IssueOutput issue = blazeIssueParser.parseIssue(lines[i]);
+      assertNull(issue);
+    }
+
+    IssueOutput issue = blazeIssueParser.parseIssue(lines[lines.length - 1]);
+    assertNotNull(issue);
+    assertThat(issue.getFile().getPath()).isEqualTo("/home/plumpy/whatever");
+    assertThat(issue.getMessage().split("\n")).hasLength(lines.length);
+    assertThat(issue.getCategory()).isEqualTo(IssueOutput.Category.ERROR);
+  }
+
+  @Test
+  public void testLineAfterTracebackIsAlsoParsed() {
+    String[] lines =
+        new String[] {
+          "ERROR: /home/plumpy/whatever:9:12: Traceback (most recent call last):",
+          "\tFile \"/path/to/root/java/com/google/android/samples/helloroot/BUILD\", line 8",
+          "\t\tpackage_group(name = BAD_FUNCTION(\"hellogoogle...\"), ...\"])",
+          "\tFile \"/path/to/root/java/com/google/android/samples/helloroot/BUILD\", line 9, "
+              + "in package_group",
+          "\t\tBAD_FUNCTION",
+          "name 'BAD_FUNCTION' is not defined."
+        };
+
+    BlazeIssueParser blazeIssueParser = new BlazeIssueParser(project, workspaceRoot);
+    for (int i = 0; i < lines.length; ++i) {
+      blazeIssueParser.parseIssue(lines[i]);
+    }
+
+    IssueOutput issue =
+        blazeIssueParser.parseIssue(
+            "ERROR: /home/plumpy/whatever:char offsets 1222--1229: name 'grubber' is not defined");
+    assertNotNull(issue);
+    assertThat(issue.getFile().getPath()).isEqualTo("/home/plumpy/whatever");
+    assertThat(issue.getMessage()).isEqualTo("name 'grubber' is not defined");
+    assertThat(issue.getCategory()).isEqualTo(IssueOutput.Category.ERROR);
+  }
+
+  @Test
+  public void testMultipleIssues() {
+    BlazeIssueParser blazeIssueParser = new BlazeIssueParser(project, workspaceRoot);
+    IssueOutput issue =
+        blazeIssueParser.parseIssue(
+            "ERROR: /home/plumpy/whatever:char offsets 1222--1229: name 'grubber' is not defined");
+    assertNotNull(issue);
+    issue =
+        blazeIssueParser.parseIssue(
+            "ERROR: /home/plumpy/whatever:char offsets 1222--1229: name 'grubber' is not defined");
+    assertNotNull(issue);
+    issue =
+        blazeIssueParser.parseIssue(
+            "ERROR: /home/plumpy/whatever:char offsets 1222--1229: name 'grubber' is not defined");
+    assertNotNull(issue);
+  }
+}
diff --git a/base/tests/unittests/com/google/idea/blaze/base/lang/buildfile/psi/StripQuotesTest.java b/base/tests/unittests/com/google/idea/blaze/base/lang/buildfile/psi/StripQuotesTest.java
new file mode 100644
index 0000000..f92d5b9
--- /dev/null
+++ b/base/tests/unittests/com/google/idea/blaze/base/lang/buildfile/psi/StripQuotesTest.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.buildfile.psi;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Test strip quotes method in {@link StringLiteral} */
+@RunWith(JUnit4.class)
+public class StripQuotesTest {
+
+  @Test
+  public void testStandardSingleQuotes() {
+    String s = "'normal string'";
+    assertThat(StringLiteral.stripQuotes(s)).isEqualTo("normal string");
+  }
+
+  @Test
+  public void testStandardDoubleQuotes() {
+    String s = "\"normal string\"";
+    assertThat(StringLiteral.stripQuotes(s)).isEqualTo("normal string");
+  }
+
+  @Test
+  public void testOtherQuoteTypeMixedIn() {
+    String s = "\"normal 'string'\"";
+    assertThat(StringLiteral.stripQuotes(s)).isEqualTo("normal 'string'");
+  }
+
+  @Test
+  public void testOtherQuoteTypeMixedIn2() {
+    String s = "'normal \"string\"'";
+    assertThat(StringLiteral.stripQuotes(s)).isEqualTo("normal \"string\"");
+  }
+
+  @Test
+  public void testTrailingQuoteMissing() {
+    String s = "'normal string";
+    assertThat(StringLiteral.stripQuotes(s)).isEqualTo("normal string");
+  }
+
+  @Test
+  public void testStandardTripleSingleQuotes() {
+    String s = "'''normal string'''";
+    assertThat(StringLiteral.stripQuotes(s)).isEqualTo("normal string");
+  }
+
+  @Test
+  public void testStandardTripleDoubleQuotes() {
+    String s = "\"\"\"normal string\"\"\"";
+    assertThat(StringLiteral.stripQuotes(s)).isEqualTo("normal string");
+  }
+
+  @Test
+  public void testTripleQuotesWithMissingTrailingQuotes() {
+    String s = "\"\"\"normal string";
+    assertThat(StringLiteral.stripQuotes(s)).isEqualTo("normal string");
+
+    s = "\"\"\"normal string\"";
+    assertThat(StringLiteral.stripQuotes(s)).isEqualTo("normal string");
+
+    s = "\"\"\"normal string\"\"";
+    assertThat(StringLiteral.stripQuotes(s)).isEqualTo("normal string");
+  }
+}
diff --git a/base/tests/unittests/com/google/idea/blaze/base/metrics/ActionTest.java b/base/tests/unittests/com/google/idea/blaze/base/metrics/ActionTest.java
new file mode 100644
index 0000000..2cbb7ce
--- /dev/null
+++ b/base/tests/unittests/com/google/idea/blaze/base/metrics/ActionTest.java
@@ -0,0 +1,49 @@
+/*
+ * 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.metrics;
+
+import com.google.common.collect.Sets;
+import java.util.HashSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Test for metric actions. */
+@RunWith(JUnit4.class)
+public class ActionTest {
+
+  @Test
+  public void ensureAllActionEnumsHaveUniqueNames() {
+    HashSet<String> names = Sets.newHashSet();
+    for (Action action : Action.values()) {
+      String name = action.getName();
+      Assert.assertTrue(name + " is not unique", names.add(name));
+    }
+  }
+
+  @Test
+  public void ensureAllActionEnumNamesAreAlphanumeric() {
+    Pattern pattern = Pattern.compile("[a-zA-Z0-9]*");
+    for (Action action : Action.values()) {
+      String name = action.getName();
+      Matcher matcher = pattern.matcher(name);
+      Assert.assertTrue(name + " is not valid", matcher.matches());
+    }
+  }
+}
diff --git a/base/tests/unittests/com/google/idea/blaze/base/model/blaze/DeepEqualsTesterTest.java b/base/tests/unittests/com/google/idea/blaze/base/model/blaze/DeepEqualsTesterTest.java
new file mode 100644
index 0000000..c1eea89
--- /dev/null
+++ b/base/tests/unittests/com/google/idea/blaze/base/model/blaze/DeepEqualsTesterTest.java
@@ -0,0 +1,576 @@
+/*
+ * 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.blaze;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.idea.blaze.base.model.blaze.deepequalstester.DeepEqualsTester;
+import com.google.idea.blaze.base.model.blaze.deepequalstester.DeepEqualsTester.TestCorrectnessException;
+import com.google.idea.blaze.base.model.blaze.deepequalstester.DeepEqualsTesterUtil;
+import com.google.idea.blaze.base.model.blaze.deepequalstester.Examples;
+import com.google.idea.blaze.base.model.blaze.deepequalstester.Examples.ExampleNotFoundException;
+import java.io.File;
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests to verify that our equals tester is working correctly */
+@RunWith(JUnit4.class)
+public class DeepEqualsTesterTest {
+
+  // The equals method does not work correctly if T is an array
+  private static class Box<T> implements Serializable {
+
+    public T data;
+
+    public Box(T data) {
+      this.data = data;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      Box<?> box = (Box<?>) o;
+      return Objects.equal(data, box.data);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hashCode(data);
+    }
+  }
+
+  private static class CorrectEqualsAndHash implements Serializable {
+
+    public String name;
+
+    public CorrectEqualsAndHash(String name) {
+      this.name = name;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      CorrectEqualsAndHash foo = (CorrectEqualsAndHash) o;
+      return Objects.equal(name, foo.name);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hashCode(name);
+    }
+  }
+
+  private static class ClassWithCorrectEqualsMember implements Serializable {
+
+    public String myName;
+    public CorrectEqualsAndHash myCorrectEqualsAndHash;
+
+    public ClassWithCorrectEqualsMember(String name, String innerName) {
+      this.myName = name;
+      this.myCorrectEqualsAndHash = new CorrectEqualsAndHash(innerName);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      ClassWithCorrectEqualsMember that = (ClassWithCorrectEqualsMember) o;
+      return Objects.equal(myName, that.myName)
+          && Objects.equal(myCorrectEqualsAndHash, that.myCorrectEqualsAndHash);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hashCode(myName, myCorrectEqualsAndHash);
+    }
+  }
+
+  private static class IncorrectHash implements Serializable {
+
+    public String name;
+    public int num;
+
+    public IncorrectHash(String name, int num) {
+      this.name = name;
+      this.num = num;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      IncorrectHash foo = (IncorrectHash) o;
+      return Objects.equal(name, foo.name) && Objects.equal(num, foo.num);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hashCode(name);
+    }
+  }
+
+  private static class IncorrectEqualsAndHash implements Serializable {
+
+    public String name;
+    public int num;
+
+    public IncorrectEqualsAndHash(String name, int num) {
+      this.name = name;
+      this.num = num;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      IncorrectEqualsAndHash foo = (IncorrectEqualsAndHash) o;
+      return Objects.equal(name, foo.name);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hashCode(name);
+    }
+  }
+
+  private static class ClassWithIncorrectEqualsMember implements Serializable {
+
+    public String myName;
+    public IncorrectEqualsAndHash myIncorrectEqualsAndHash;
+
+    public ClassWithIncorrectEqualsMember(String name, String innerName, int innerNum) {
+      this.myName = name;
+      this.myIncorrectEqualsAndHash = new IncorrectEqualsAndHash(innerName, innerNum);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      ClassWithIncorrectEqualsMember that = (ClassWithIncorrectEqualsMember) o;
+      return Objects.equal(myName, that.myName)
+          && Objects.equal(myIncorrectEqualsAndHash, that.myIncorrectEqualsAndHash);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hashCode(myName, myIncorrectEqualsAndHash);
+    }
+  }
+
+  private static class IncorrectEqualsWithArray implements Serializable {
+
+    public IncorrectEqualsAndHash[] array;
+
+    public IncorrectEqualsWithArray(IncorrectEqualsAndHash[] array) {
+      this.array = array;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      IncorrectEqualsWithArray that = (IncorrectEqualsWithArray) o;
+      return Arrays.equals(array, that.array);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hashCode((Object[]) array);
+    }
+  }
+
+  private static class SubIncorrectEqualsAndHash extends IncorrectEqualsAndHash {
+
+    public Long num;
+
+    public SubIncorrectEqualsAndHash(String name, int iNum, Long num) {
+      super(name, iNum);
+      this.num = num;
+    }
+  }
+
+  private static enum ENUMS {
+    ONE,
+    TWO,
+    THREE
+  }
+
+  private static class DeepClass<T> implements Serializable {
+
+    public ENUMS myEnum;
+    public char myC;
+    public T data;
+    public File f;
+
+    public DeepClass(ENUMS myEnum, char c, T data, File f) {
+      this.myEnum = myEnum;
+      this.myC = c;
+      this.data = data;
+      this.f = f;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      DeepClass<?> deepClass = (DeepClass<?>) o;
+      return Objects.equal(myC, deepClass.myC)
+          && Objects.equal(myEnum, deepClass.myEnum)
+          && Objects.equal(data, deepClass.data)
+          && Objects.equal(f, deepClass.f);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hashCode(myEnum, myC, data, f);
+    }
+  }
+
+  private static class SimpleClassWithSet implements Serializable {
+
+    public Set<File> files;
+
+    public SimpleClassWithSet(Set<File> files) {
+      this.files = files;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      SimpleClassWithSet that = (SimpleClassWithSet) o;
+      return Objects.equal(files, that.files);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hashCode(files);
+    }
+  }
+
+  private static class MapWithIncorrectKey implements Serializable {
+
+    public Map<IncorrectEqualsAndHash, File> files;
+
+    public MapWithIncorrectKey(Map<IncorrectEqualsAndHash, File> files) {
+      this.files = files;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      MapWithIncorrectKey that = (MapWithIncorrectKey) o;
+      return Objects.equal(files, that.files);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hashCode(files);
+    }
+  }
+
+  private static class MapWithIncorrectValue implements Serializable {
+
+    public Map<String, IncorrectEqualsAndHash> map;
+
+    public MapWithIncorrectValue(Map<String, IncorrectEqualsAndHash> map) {
+      this.map = map;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      MapWithIncorrectValue that = (MapWithIncorrectValue) o;
+      return Objects.equal(map, that.map);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hashCode(map);
+    }
+  }
+
+  private static class MapWithCorrectKeyAndValues implements Serializable {
+
+    public Map<String, String> map;
+
+    public MapWithCorrectKeyAndValues(Map<String, String> map) {
+      this.map = map;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      MapWithCorrectKeyAndValues that = (MapWithCorrectKeyAndValues) o;
+      return Objects.equal(map, that.map);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hashCode(map);
+    }
+  }
+
+  private Examples testExamples;
+
+  @Before
+  public void populateExtraExamples() {
+    testExamples = new Examples();
+    testExamples.addExample(
+        CorrectEqualsAndHash.class, new CorrectEqualsAndHash("A"), new CorrectEqualsAndHash("B"));
+    testExamples.addExample(
+        IncorrectEqualsAndHash.class,
+        new IncorrectEqualsAndHash("A", 100),
+        new IncorrectEqualsAndHash("A", 200));
+    testExamples.addExample(
+        IncorrectHash.class, new IncorrectHash("A", 100), new IncorrectHash("A", 200));
+  }
+
+  @Test
+  public void testCorrectEqualsAndHashPassesTester()
+      throws IllegalAccessException, InstantiationException, NoSuchFieldException,
+          ExampleNotFoundException, TestCorrectnessException {
+    CorrectEqualsAndHash myFoo = new CorrectEqualsAndHash("test");
+    DeepEqualsTester.doDeepEqualsAndHashTest(myFoo, testExamples);
+  }
+
+  @Test(expected = AssertionError.class)
+  public void testIncorrectEqualsFailsTester()
+      throws IllegalAccessException, InstantiationException, NoSuchFieldException,
+          ExampleNotFoundException, TestCorrectnessException {
+    IncorrectEqualsAndHash myFoo = new IncorrectEqualsAndHash("test", 4);
+    DeepEqualsTester.doDeepEqualsAndHashTest(myFoo, testExamples);
+  }
+
+  @Test(expected = AssertionError.class)
+  public void testIncorrectHashFailsTester()
+      throws IllegalAccessException, InstantiationException, NoSuchFieldException,
+          ExampleNotFoundException, TestCorrectnessException {
+    IncorrectHash myFoo = new IncorrectHash("test", 4);
+    DeepEqualsTester.doDeepEqualsAndHashTest(myFoo, testExamples);
+  }
+
+  @Test
+  public void testCorrectDeepEqualsAndHashPassesTester()
+      throws IllegalAccessException, InstantiationException, NoSuchFieldException,
+          ExampleNotFoundException, TestCorrectnessException {
+    ClassWithCorrectEqualsMember myFoo = new ClassWithCorrectEqualsMember("test", "inner test");
+    DeepEqualsTester.doDeepEqualsAndHashTest(myFoo, testExamples);
+  }
+
+  @Test(expected = AssertionError.class)
+  public void testDeepIncorrectEqualsFailsTester()
+      throws IllegalAccessException, InstantiationException, NoSuchFieldException,
+          ExampleNotFoundException, TestCorrectnessException {
+    ClassWithIncorrectEqualsMember myFoo =
+        new ClassWithIncorrectEqualsMember("test", "inner test", 4);
+    DeepEqualsTester.doDeepEqualsAndHashTest(myFoo, testExamples);
+  }
+
+  @Test(expected = AssertionError.class)
+  public void testIncorrectEqualsInSuperclassFailsTester()
+      throws IllegalAccessException, InstantiationException, NoSuchFieldException,
+          ExampleNotFoundException, TestCorrectnessException {
+    SubIncorrectEqualsAndHash myFoo = new SubIncorrectEqualsAndHash("test", 4, new Long(39903));
+    DeepEqualsTester.doDeepEqualsAndHashTest(myFoo, testExamples);
+  }
+
+  @Test
+  public void testCorrectEqualsAndHashInArrayPassesTester()
+      throws IllegalAccessException, InstantiationException, NoSuchFieldException,
+          ExampleNotFoundException, TestCorrectnessException {
+    CorrectEqualsAndHash myFoo = new CorrectEqualsAndHash("test");
+    CorrectEqualsAndHash[] array = new CorrectEqualsAndHash[1];
+    array[0] = myFoo;
+    DeepEqualsTester.doDeepEqualsAndHashTest(array, testExamples);
+  }
+
+  @Test(expected = AssertionError.class)
+  public void testIncorrectEqualsInArrayFailsTester()
+      throws IllegalAccessException, InstantiationException, NoSuchFieldException,
+          ExampleNotFoundException, TestCorrectnessException {
+    IncorrectEqualsAndHash myFoo = new IncorrectEqualsAndHash("test", 4);
+    IncorrectEqualsAndHash[] array = new IncorrectEqualsAndHash[1];
+    array[0] = myFoo;
+    IncorrectEqualsWithArray toTest = new IncorrectEqualsWithArray(array);
+    DeepEqualsTester.doDeepEqualsAndHashTest(toTest, testExamples);
+  }
+
+  @Test
+  public void testClassWithSetWithCorrectDeepEqualsAndHashPassesTester()
+      throws IllegalAccessException, InstantiationException, NoSuchFieldException,
+          ExampleNotFoundException, TestCorrectnessException {
+    Set<File> myFiles = Sets.newHashSet();
+    myFiles.add(new File("foo"));
+    myFiles.add(new File("bar"));
+    SimpleClassWithSet myFoo = new SimpleClassWithSet(myFiles);
+    DeepEqualsTester.doDeepEqualsAndHashTest(myFoo, testExamples);
+  }
+
+  @Ignore("causes java reflection return a type variable instead of a concrete type")
+  @Test
+  public void testCorrectEqualsAndHashInSetPassesTester()
+      throws IllegalAccessException, InstantiationException, NoSuchFieldException,
+          ExampleNotFoundException, TestCorrectnessException {
+    CorrectEqualsAndHash myFoo = new CorrectEqualsAndHash("test");
+    HashSet<CorrectEqualsAndHash> set = Sets.newHashSet();
+    set.add(myFoo);
+    DeepEqualsTester.doDeepEqualsAndHashTest(
+        new Box<HashSet<CorrectEqualsAndHash>>(set), testExamples);
+  }
+
+  @Test(expected = AssertionError.class)
+  public void testIncorrectEqualsInSetFailsTester()
+      throws IllegalAccessException, InstantiationException, NoSuchFieldException,
+          ExampleNotFoundException, TestCorrectnessException {
+    IncorrectEqualsAndHash myFoo = new IncorrectEqualsAndHash("test", 4);
+    HashSet<IncorrectEqualsAndHash> set = Sets.newHashSet();
+    set.add(myFoo);
+    DeepEqualsTester.doDeepEqualsAndHashTest(
+        new Box<HashSet<IncorrectEqualsAndHash>>(set), testExamples);
+  }
+
+  @Ignore("causes java reflection return a type variable instead of a concrete type")
+  @Test
+  public void testCorrectDeepEqualsAndHashInSetPassesTester()
+      throws IllegalAccessException, InstantiationException, NoSuchFieldException,
+          ExampleNotFoundException, TestCorrectnessException {
+    CorrectEqualsAndHash myFoo = new CorrectEqualsAndHash("test");
+    DeepClass<CorrectEqualsAndHash> data =
+        new DeepClass<CorrectEqualsAndHash>(ENUMS.THREE, 'z', myFoo, new File("home"));
+    HashSet<DeepClass<CorrectEqualsAndHash>> set = Sets.newHashSet();
+    set.add(data);
+    DeepEqualsTester.doDeepEqualsAndHashTest(
+        new Box<HashSet<DeepClass<CorrectEqualsAndHash>>>(set), testExamples);
+  }
+
+  @Test(expected = AssertionError.class)
+  public void testIncorrectDeepEqualsInSetFailsTester()
+      throws IllegalAccessException, InstantiationException, NoSuchFieldException,
+          ExampleNotFoundException, TestCorrectnessException {
+    IncorrectEqualsAndHash myFoo = new IncorrectEqualsAndHash("test", 4);
+    DeepClass<IncorrectEqualsAndHash> data =
+        new DeepClass<IncorrectEqualsAndHash>(ENUMS.ONE, 'e', myFoo, new File("home"));
+    HashSet<DeepClass<IncorrectEqualsAndHash>> set = Sets.newHashSet();
+    set.add(data);
+    DeepEqualsTester.doDeepEqualsAndHashTest(
+        new Box<HashSet<DeepClass<IncorrectEqualsAndHash>>>(set), testExamples);
+  }
+
+  @Test(expected = AssertionError.class)
+  public void testIncorrectEqualsForMapKeyFailsTester()
+      throws IllegalAccessException, InstantiationException, NoSuchFieldException,
+          ExampleNotFoundException, TestCorrectnessException {
+    IncorrectEqualsAndHash myFoo = new IncorrectEqualsAndHash("test", 4);
+    Map<IncorrectEqualsAndHash, File> map = Maps.newHashMap();
+    map.put(myFoo, new File("file"));
+    MapWithIncorrectKey data = new MapWithIncorrectKey(map);
+    DeepEqualsTester.doDeepEqualsAndHashTest(data, testExamples);
+  }
+
+  @Test(expected = AssertionError.class)
+  public void testIncorrectEqualsForMapValueFailsTester()
+      throws IllegalAccessException, InstantiationException, NoSuchFieldException,
+          ExampleNotFoundException, TestCorrectnessException {
+    IncorrectEqualsAndHash myFoo = new IncorrectEqualsAndHash("test", 4);
+    Map<String, IncorrectEqualsAndHash> map = Maps.newHashMap();
+    map.put("first", myFoo);
+    MapWithIncorrectValue data = new MapWithIncorrectValue(map);
+    DeepEqualsTester.doDeepEqualsAndHashTest(data, testExamples);
+  }
+
+  @Test
+  public void testCorrectEqualsAndHashForMapKeyValuePassesTester()
+      throws IllegalAccessException, InstantiationException, NoSuchFieldException,
+          ExampleNotFoundException, TestCorrectnessException {
+    Map<String, String> map = Maps.newHashMap();
+    map.put("key", "value");
+    MapWithCorrectKeyAndValues data = new MapWithCorrectKeyAndValues(map);
+    DeepEqualsTester.doDeepEqualsAndHashTest(data, testExamples);
+  }
+
+  @Test
+  public void testEqualsAfterCloneReturnsTrue() {
+    CorrectEqualsAndHash myData = new CorrectEqualsAndHash("my data");
+    CorrectEqualsAndHash clone =
+        (CorrectEqualsAndHash) DeepEqualsTesterUtil.cloneWithSerialization(myData);
+    Assert.assertEquals(myData, clone);
+  }
+}
diff --git a/base/tests/unittests/com/google/idea/blaze/base/model/blaze/deepequalstester/DeepEqualsTester.java b/base/tests/unittests/com/google/idea/blaze/base/model/blaze/deepequalstester/DeepEqualsTester.java
new file mode 100644
index 0000000..2e4fd8c
--- /dev/null
+++ b/base/tests/unittests/com/google/idea/blaze/base/model/blaze/deepequalstester/DeepEqualsTester.java
@@ -0,0 +1,145 @@
+/*
+ * 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.blaze.deepequalstester;
+
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.model.blaze.deepequalstester.Examples.ExampleNotFoundException;
+import com.google.idea.blaze.base.model.blaze.deepequalstester.Examples.Pair;
+import com.google.idea.blaze.base.model.blaze.deepequalstester.ReachabilityAnalysis.ReachableClasses;
+import java.io.Serializable;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Assert;
+
+/** Used to test equals implementations */
+public final class DeepEqualsTester {
+
+  /** Thrown when the test fails */
+  public static class TestCorrectnessException extends Exception {
+
+    public TestCorrectnessException(String s) {
+      super(s);
+    }
+  }
+
+  /**
+   * Ensure that the equals method of {@param rootObject} uses all of its fields in its comparison.
+   * Recurse into every field of {@param rootObject} to ensure that they also use all of their
+   * fields in their equals method. Continue recursion until primitives are hit.
+   *
+   * <p>If multiple failures could occur, there is no guarantee that they will always occur in the
+   * same order. Only the first failure is reported.
+   *
+   * @param rootObject an example instantiation of the class we want to test for deep equals sanity.
+   *     The object must be a standard java object (no collections, no arrays, no primitives). If
+   *     you would like to pass these types in, you should put them in a box first.
+   * @param examples examples of objects to use for comparison. This should contain a pair of
+   *     examples for every type that could be reachable from the root object. This value may be
+   *     mutated by this test.
+   */
+  public static <T extends Serializable> void doDeepEqualsAndHashTest(
+      @NotNull T rootObject, Examples examples)
+      throws InstantiationException, IllegalAccessException, NoSuchFieldException,
+          ExampleNotFoundException, TestCorrectnessException {
+    ReachableClasses reachableClasses = new ReachableClasses();
+
+    try {
+      ArrayList<String> initialPath = Lists.newArrayList("root");
+      // Find all of the classes reachable from the root object. This is not sound since it
+      // ignores subtypes (or supertypes) that could be used
+      ReachabilityAnalysis.computeReachableFromObject(
+          rootObject, rootObject.getClass(), initialPath, reachableClasses);
+    } catch (ClassNotFoundException e) {
+      e.printStackTrace();
+    }
+
+    // Add the root object to our list of reachable classes so we can do all the testing in one
+    // loop
+    reachableClasses.addPath(rootObject.getClass(), Lists.newArrayList("root"));
+    // In our situations, we never need a second example of the root object
+    examples.addExample(rootObject.getClass(), rootObject, rootObject);
+    // For each reachable class, do a shallow equals test where we change each value of the
+    // object one at a time and test for equality
+    for (Class<? extends Serializable> clazz : reachableClasses.getClasses()) {
+      Serializable workitem = (Serializable) examples.getExamples(clazz).getFirst();
+      testShallowEquals(workitem, reachableClasses, examples);
+    }
+  }
+
+  private static String getFailureMessage(String method, Field field, List<String> examplePath) {
+    StringBuilder sb = new StringBuilder();
+    sb.append(field.toString())
+        .append(" is not represented in it's parent's ")
+        .append(method)
+        .append(" method\n");
+    for (String path : examplePath) {
+      sb.append("\t").append(path).append("\n");
+    }
+    sb.append("\n");
+    return sb.toString();
+  }
+
+  /**
+   * Mutate each field in the object one at a time and test for equality of the object. Assert a
+   * failure if any mutation doesn't result in the two objects not being equal
+   */
+  private static <T extends Serializable> void testShallowEquals(
+      @NotNull T original, ReachableClasses reachableClasses, Examples examples)
+      throws ExampleNotFoundException, IllegalAccessException, TestCorrectnessException {
+    T clone = (T) DeepEqualsTesterUtil.cloneWithSerialization(original);
+    List<Field> allFields = DeepEqualsTesterUtil.getAllFields(original.getClass());
+    for (Field field : allFields) {
+      if (!Modifier.isStatic(field.getModifiers())) {
+        field.setAccessible(true);
+        Pair<?, ?> examplesPair =
+            examples.getExamples((Class<? extends Serializable>) field.getType());
+        Object newValueForOriginal = examplesPair.getFirst();
+        Object newValueForClone = examplesPair.getSecond();
+        Object oldValueForOriginal = field.get(original);
+        Object oldValueForClone = field.get(clone);
+        // Ensure that the two objects really are equal before we tweak them
+        boolean objectsTheSameBeforeTweak = original.equals(clone);
+        if (!objectsTheSameBeforeTweak) {
+          throw new TestCorrectnessException(
+              "original was not equal to clone before tweaking them");
+        }
+        boolean objectsHashTheSameBeforeTweak = original.hashCode() == clone.hashCode();
+        if (!objectsHashTheSameBeforeTweak) {
+          throw new TestCorrectnessException(
+              "original hash code was not equal to clone hash code before tweaking the objects");
+        }
+        field.set(original, newValueForOriginal);
+        field.set(clone, newValueForClone);
+        boolean equalsWorksAsIntended = !original.equals(clone);
+        boolean hashWorksAsIntended = original.hashCode() != clone.hashCode();
+        // Return to our original state before possibly failing
+        field.set(original, oldValueForOriginal);
+        field.set(clone, oldValueForClone);
+        Assert.assertTrue(
+            getFailureMessage(
+                "equals", field, reachableClasses.getExamplePathTo(original.getClass())),
+            equalsWorksAsIntended);
+        Assert.assertTrue(
+            getFailureMessage(
+                "hash", field, reachableClasses.getExamplePathTo(original.getClass())),
+            hashWorksAsIntended);
+      }
+    }
+  }
+}
diff --git a/base/tests/unittests/com/google/idea/blaze/base/model/blaze/deepequalstester/DeepEqualsTesterUtil.java b/base/tests/unittests/com/google/idea/blaze/base/model/blaze/deepequalstester/DeepEqualsTesterUtil.java
new file mode 100644
index 0000000..2ef9472
--- /dev/null
+++ b/base/tests/unittests/com/google/idea/blaze/base/model/blaze/deepequalstester/DeepEqualsTesterUtil.java
@@ -0,0 +1,100 @@
+/*
+ * 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.blaze.deepequalstester;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.lang.reflect.Field;
+import java.util.List;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/** Utilities for deep equals testing. */
+@VisibleForTesting
+public final class DeepEqualsTesterUtil {
+  public static List<Field> getAllFields(Class<?> clazz) {
+    List<Field> fields = Lists.newArrayList();
+
+    Field[] declaredFields = clazz.getDeclaredFields();
+    for (Field field : declaredFields) {
+      fields.add(field);
+    }
+
+    Class<?> superclass = clazz.getSuperclass();
+    if (superclass != null) {
+      fields.addAll(getAllFields(superclass));
+    }
+    return fields;
+  }
+
+  public static Class getClass(Class declaredClass, Object o) {
+    if (o == null) {
+      return declaredClass;
+    }
+    // The two classes *should* be the same in this case, but the class from o.getClass won't
+    // return true from isPrimitive
+    if (declaredClass.isPrimitive()) {
+      return declaredClass;
+    }
+    return o.getClass();
+  }
+
+  /** Do a deep clone of an object using reflection */
+  public static Object cloneWithSerialization(Object o) {
+    if (o == null) {
+      return null;
+    }
+
+    try {
+      ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+      ObjectOutputStream objOut = new ObjectOutputStream(outputStream);
+      objOut.writeObject(o);
+      ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
+      ObjectInputStream objIn = new ObjectInputStream(inputStream);
+      return objIn.readObject();
+    } catch (Exception e) {
+      return null;
+    }
+  }
+
+  public static boolean isSubclassOf(
+      @Nullable Class<?> possibleSubClass, @NotNull Class<?> possibleSuperClass) {
+    if (possibleSubClass == null) {
+      return false;
+    }
+
+    if (possibleSubClass.equals(possibleSuperClass)) {
+      return true;
+    }
+
+    if (possibleSubClass.equals(Object.class)) {
+      return false;
+    }
+
+    Class<?>[] interfaces = possibleSubClass.getInterfaces();
+    for (Class<?> interfaze : interfaces) {
+      if (interfaze.equals(possibleSuperClass)) {
+        return true;
+      }
+    }
+
+    return isSubclassOf(possibleSubClass.getSuperclass(), possibleSuperClass);
+  }
+}
diff --git a/base/tests/unittests/com/google/idea/blaze/base/model/blaze/deepequalstester/Examples.java b/base/tests/unittests/com/google/idea/blaze/base/model/blaze/deepequalstester/Examples.java
new file mode 100644
index 0000000..b28ce44
--- /dev/null
+++ b/base/tests/unittests/com/google/idea/blaze/base/model/blaze/deepequalstester/Examples.java
@@ -0,0 +1,191 @@
+/*
+ * 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.blaze.deepequalstester;
+
+import static com.google.idea.blaze.base.model.blaze.deepequalstester.DeepEqualsTesterUtil.isSubclassOf;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import java.io.File;
+import java.io.Serializable;
+import java.lang.reflect.Array;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.jetbrains.annotations.NotNull;
+
+/** Examples used in deep equals tests */
+public final class Examples {
+
+  /** pair utility class */
+  public static final class Pair<First, Second> {
+
+    private final First first;
+    private final Second second;
+
+    public static <First, Second> Pair<First, Second> of(First first, Second second) {
+      return new Pair<First, Second>(first, second);
+    }
+
+    public Pair(First first, Second second) {
+      this.first = first;
+      this.second = second;
+    }
+
+    First getFirst() {
+      return first;
+    }
+
+    Second getSecond() {
+      return second;
+    }
+  }
+
+  private static Map<Class<? extends Object>, Pair<? extends Object, ? extends Object>>
+      BASE_EXAMPLES;
+  private static Map<Class<? extends Object>, Pair<? extends Object, ? extends Object>>
+      ARRAY_EXAMPLES;
+
+  private final Map<Class<? extends Object>, Pair<? extends Object, ? extends Object>>
+      customExamples;
+
+  static {
+    BASE_EXAMPLES = Maps.newHashMap();
+    BASE_EXAMPLES.put(String.class, Pair.of("foo", "foobar"));
+    BASE_EXAMPLES.put(File.class, Pair.of(new File("a"), new File("b")));
+    BASE_EXAMPLES.put(Integer.class, Pair.of(1, 2));
+    BASE_EXAMPLES.put(Integer.TYPE, Pair.of(1, 2));
+    BASE_EXAMPLES.put(Long.class, Pair.of(1L, 2L));
+    BASE_EXAMPLES.put(Long.TYPE, Pair.of(1L, 2L));
+    BASE_EXAMPLES.put(Short.class, Pair.of((short) 1, (short) 2));
+    BASE_EXAMPLES.put(Short.TYPE, Pair.of((short) 1, (short) 2));
+    BASE_EXAMPLES.put(Character.class, Pair.of('a', 'b'));
+    BASE_EXAMPLES.put(Character.TYPE, Pair.of('a', 'b'));
+    BASE_EXAMPLES.put(Byte.class, Pair.of((byte) 1, (byte) 2));
+    BASE_EXAMPLES.put(Byte.TYPE, Pair.of((byte) 1, (byte) 2));
+    BASE_EXAMPLES.put(Float.class, Pair.of((float) 1.0, (float) 2.0));
+    BASE_EXAMPLES.put(Float.TYPE, Pair.of((float) 1.0, (float) 2.0));
+    BASE_EXAMPLES.put(Double.class, Pair.of(1.0, 2.0));
+    BASE_EXAMPLES.put(Double.TYPE, Pair.of(1.0, 2.0));
+    BASE_EXAMPLES.put(Boolean.class, Pair.of(true, false));
+    BASE_EXAMPLES.put(Boolean.TYPE, Pair.of(true, false));
+    Map<String, String> mapA = Maps.newHashMap();
+    mapA.put("foo", "bar");
+    Map<String, String> mapB = Maps.newHashMap();
+    mapB.put("tip", "top");
+    BASE_EXAMPLES.put(Map.class, Pair.of(mapA, mapB));
+    Set<String> setA = new ImmutableSet.Builder<String>().add("A").build();
+    Set<String> setB = new ImmutableSet.Builder<String>().add("A").add("B").build();
+    BASE_EXAMPLES.put(Set.class, Pair.of(setA, setB));
+    BASE_EXAMPLES.put(Collection.class, Pair.of(setA, setB));
+    Builder<String> listABuilder = ImmutableList.builder();
+    List<String> listA = listABuilder.add("A").build();
+    Builder<String> listBBuilder = ImmutableList.builder();
+    List<String> listB = listBBuilder.add("A").add("B").build();
+    BASE_EXAMPLES.put(List.class, Pair.of(listA, listB));
+
+    ARRAY_EXAMPLES = Maps.newHashMap();
+    int[] intArrA = {1, 2};
+    int[] intArrB = {3, 4};
+    ARRAY_EXAMPLES.put(Integer.TYPE, Pair.of(intArrA, intArrB));
+    long[] longArrA = {1, 2};
+    long[] longArrB = {3, 4};
+    ARRAY_EXAMPLES.put(Long.TYPE, Pair.of(longArrA, longArrB));
+    short[] shortArrA = {1, 2};
+    short[] shortArrB = {3, 4};
+    ARRAY_EXAMPLES.put(Short.TYPE, Pair.of(shortArrA, shortArrB));
+    char[] charArrA = {'a', 'b'};
+    char[] charArrB = {'c', 'd'};
+    ARRAY_EXAMPLES.put(Character.TYPE, Pair.of(charArrA, charArrB));
+    byte[] byteArrA = {1, 2};
+    byte[] byteArrB = {3, 4};
+    ARRAY_EXAMPLES.put(Byte.TYPE, Pair.of(byteArrA, byteArrB));
+    boolean[] boolArrA = {true, false};
+    boolean[] boolArrB = {false, false};
+    ARRAY_EXAMPLES.put(Boolean.TYPE, Pair.of(boolArrA, boolArrB));
+    float[] floatArrA = {1.0f, 2.0f};
+    float[] floatArrB = {3.0f, 4.0f};
+    ARRAY_EXAMPLES.put(Float.TYPE, Pair.of(floatArrA, floatArrB));
+    double[] doubleArrA = {1.0, 2.0};
+    double[] doubleArrB = {3.0, 4.0};
+    ARRAY_EXAMPLES.put(Double.TYPE, Pair.of(doubleArrA, doubleArrB));
+  }
+
+  /** Thrown when an example could not be found */
+  public static class ExampleNotFoundException extends Exception {
+
+    private final Class<?> clazz;
+
+    public ExampleNotFoundException(Class<?> clazz) {
+      this.clazz = clazz;
+    }
+
+    @Override
+    public String getMessage() {
+      return "Could not find example for: " + clazz.toString();
+    }
+  }
+
+  public Examples() {
+    this.customExamples = Maps.newHashMap();
+  }
+
+  public void addExample(Class<? extends Object> clazz, Object a, Object b) {
+    customExamples.put(clazz, Pair.of(a, b));
+  }
+
+  public <T extends Serializable> Pair<? extends Object, ? extends Object> getExamples(
+      @NotNull Class<T> clazz) throws ExampleNotFoundException {
+    if (customExamples.containsKey(clazz)) {
+      return customExamples.get(clazz);
+    }
+    if (BASE_EXAMPLES.containsKey(clazz)) {
+      return BASE_EXAMPLES.get(clazz);
+    }
+    // Special case subclasses of collections
+    if (isSubclassOf(clazz, Set.class)) {
+      return BASE_EXAMPLES.get(Set.class);
+    }
+    if (isSubclassOf(clazz, List.class)) {
+      return BASE_EXAMPLES.get(List.class);
+    }
+    if (isSubclassOf(clazz, Collection.class)) {
+      return BASE_EXAMPLES.get(Collection.class);
+    }
+
+    // If we have an array of Objects, we have to do a little trickery to create one we can swap
+    // in
+    if (clazz.isArray()) {
+      Class<?> arrayType = clazz.getComponentType();
+      if (!arrayType.isPrimitive()) {
+        Pair<?, ?> examples = getExamples((Class<? extends Serializable>) arrayType);
+        Object arrayA = Array.newInstance(arrayType, 1);
+        Array.set(arrayA, 0, examples.getFirst());
+        Object arrayB = Array.newInstance(arrayType, 1);
+        Array.set(arrayB, 0, examples.getSecond());
+        return Pair.of(arrayA, arrayB);
+      }
+      if (ARRAY_EXAMPLES.containsKey(arrayType)) {
+        return ARRAY_EXAMPLES.get(arrayType);
+      }
+    }
+
+    throw new ExampleNotFoundException(clazz);
+  }
+}
diff --git a/base/tests/unittests/com/google/idea/blaze/base/model/blaze/deepequalstester/ReachabilityAnalysis.java b/base/tests/unittests/com/google/idea/blaze/base/model/blaze/deepequalstester/ReachabilityAnalysis.java
new file mode 100644
index 0000000..3405453
--- /dev/null
+++ b/base/tests/unittests/com/google/idea/blaze/base/model/blaze/deepequalstester/ReachabilityAnalysis.java
@@ -0,0 +1,181 @@
+/*
+ * 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.blaze.deepequalstester;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import java.io.File;
+import java.io.Serializable;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import org.junit.Assert;
+
+final class ReachabilityAnalysis {
+
+  /** Wrapper around a map from class to set of paths that lead to that path from the root object */
+  public static final class ReachableClasses {
+
+    Map<Class<? extends Serializable>, Set<List<String>>> map;
+
+    public ReachableClasses() {
+      map = Maps.newHashMap();
+    }
+
+    boolean alreadyFound(Class<? extends Serializable> clazz) {
+      return map.containsKey(clazz);
+    }
+
+    void addPath(Class<? extends Serializable> clazz, List<String> path) {
+      Set<List<String>> paths;
+      if (map.containsKey(clazz)) {
+        paths = map.get(clazz);
+      } else {
+        paths = Sets.newHashSet();
+        map.put(clazz, paths);
+      }
+      paths.add(path);
+    }
+
+    @Override
+    public String toString() {
+      StringBuilder sb = new StringBuilder();
+
+      Set<? extends Entry<Class<? extends Serializable>, ? extends Set<? extends List<String>>>>
+          entries = map.entrySet();
+      for (Entry<Class<? extends Serializable>, ? extends Set<? extends List<String>>> entry :
+          entries) {
+        sb.append(entry.getKey().toString());
+        sb.append("\n");
+      }
+
+      return sb.toString();
+    }
+
+    public Set<Class<? extends Serializable>> getClasses() {
+      return map.keySet();
+    }
+
+    public List<String> getExamplePathTo(Class<? extends Serializable> aClass) {
+      if (map.containsKey(aClass)) {
+        return map.get(aClass).iterator().next();
+      }
+      return Lists.newArrayList();
+    }
+  }
+
+  /**
+   * Find all of the classes reachable from a root object
+   *
+   * @param root object to start reachability calculation from
+   * @param declaredRootClass declared class of the root object
+   * @param currentPath field access path to get to root
+   * @param reachableClasses output: add classes reachable from root to this object
+   * @throws IllegalAccessException
+   * @throws ClassNotFoundException
+   */
+  public static void computeReachableFromObject(
+      Object root,
+      Class<?> declaredRootClass,
+      List<String> currentPath,
+      ReachableClasses reachableClasses)
+      throws IllegalAccessException, ClassNotFoundException {
+    final Class<?> concreteRootClass = DeepEqualsTesterUtil.getClass(declaredRootClass, root);
+    List<Field> allFields = DeepEqualsTesterUtil.getAllFields(concreteRootClass);
+    for (Field field : allFields) {
+      if (!Modifier.isStatic(field.getModifiers())) {
+        field.setAccessible(true);
+        final Object fieldObject;
+        if (root == null) {
+          fieldObject = null;
+        } else {
+          fieldObject = field.get(root);
+        }
+        List<String> childPath = Lists.newArrayList();
+        childPath.addAll(currentPath);
+        childPath.add(field.toString());
+        addToReachableAndRecurse(
+            fieldObject, field.getType(), field.getGenericType(), childPath, reachableClasses);
+      }
+    }
+  }
+
+  /**
+   * Determine the action we should take based on the type of Object and then take it. In the normal
+   * object case, this results in a recursive call to {@link #computeReachableFromObject(Object,
+   * Class, List, ReachableClasses)}. In the case of Collections, we skip the Collection type and
+   * continue on with the type contained in the collection.
+   */
+  private static void addToReachableAndRecurse(
+      Object object,
+      Class<?> declaredObjectClass,
+      Type genericType,
+      List<String> currentPath,
+      ReachableClasses reachableClasses)
+      throws ClassNotFoundException, IllegalAccessException {
+    Class<? extends Serializable> objectType =
+        DeepEqualsTesterUtil.getClass(declaredObjectClass, object);
+    // TODO(salguarnieri) modify if so all ignored classes are taken care of together
+    if (objectType.isPrimitive()) {
+      // ignore
+    } else if (objectType.isEnum()) {
+      // assume enums do the right thing, ignore
+    } else if (DeepEqualsTesterUtil.isSubclassOf(objectType, String.class)) {
+      // ignore
+    } else if (DeepEqualsTesterUtil.isSubclassOf(objectType, File.class)) {
+      // ignore
+    } else if (DeepEqualsTesterUtil.isSubclassOf(objectType, Collection.class)
+        || DeepEqualsTesterUtil.isSubclassOf(objectType, Map.class)) {
+      if (genericType instanceof ParameterizedType) {
+        ParameterizedType parameterType = (ParameterizedType) genericType;
+        Type[] actualTypeArguments = parameterType.getActualTypeArguments();
+        for (Type typeArgument : actualTypeArguments) {
+          if (typeArgument instanceof Class) {
+            List<String> childPath = Lists.newArrayList();
+            childPath.addAll(currentPath);
+            childPath.add("[[IN COLLECTION]]");
+            // this does not properly handle subtyping
+            addToReachableAndRecurse(null, (Class) typeArgument, null, childPath, reachableClasses);
+          } else {
+            Assert.fail("This case is not handled yet");
+          }
+        }
+      } else {
+        Assert.fail("This case is not handled yet");
+      }
+    } else if (objectType.isArray()) {
+      Class<?> typeInArray = objectType.getComponentType();
+      // This does not properly handle subtyping
+      List<String> childPath = Lists.newArrayList();
+      childPath.addAll(currentPath);
+      childPath.add("[[IN ARRAY]]");
+      addToReachableAndRecurse(null, typeInArray, null, childPath, reachableClasses);
+    } else {
+      boolean doRecursion = !reachableClasses.alreadyFound(objectType);
+      reachableClasses.addPath(objectType, currentPath);
+      if (doRecursion) {
+        computeReachableFromObject(object, declaredObjectClass, currentPath, reachableClasses);
+      }
+    }
+  }
+}
diff --git a/base/tests/unittests/com/google/idea/blaze/base/model/primitives/ExecutionRootPathTest.java b/base/tests/unittests/com/google/idea/blaze/base/model/primitives/ExecutionRootPathTest.java
new file mode 100644
index 0000000..d85de4b
--- /dev/null
+++ b/base/tests/unittests/com/google/idea/blaze/base/model/primitives/ExecutionRootPathTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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 java.io.File;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mockito;
+import org.mockito.stubbing.Answer;
+
+/** Tests execution root path. */
+@RunWith(JUnit4.class)
+public class ExecutionRootPathTest extends BlazeTestCase {
+  @Test
+  public void testSingleLevelPathEndInSlash() {
+    ExecutionRootPath executionRootPath = new ExecutionRootPath("foo");
+    assertThat(executionRootPath.getAbsoluteOrRelativeFile()).isEqualTo(new File("foo/"));
+
+    ExecutionRootPath executionRootPath2 = new ExecutionRootPath("foo/");
+    assertThat(executionRootPath2.getAbsoluteOrRelativeFile()).isEqualTo(new File("foo/"));
+  }
+
+  @Test
+  public void testMultiLevelPathEndInSlash() {
+    ExecutionRootPath executionRootPath = new ExecutionRootPath("foo/bar");
+    assertThat(executionRootPath.getAbsoluteOrRelativeFile()).isEqualTo(new File("foo/bar/"));
+
+    ExecutionRootPath executionRootPath2 = new ExecutionRootPath("foo/bar/");
+    assertThat(executionRootPath2.getAbsoluteOrRelativeFile()).isEqualTo(new File("foo/bar/"));
+  }
+
+  @Test
+  public void testAbsoluteFileDoesNotGetRerooted() {
+    ExecutionRootPath executionRootPath = new ExecutionRootPath("/root/foo/bar");
+    File rootedFile = executionRootPath.getFileRootedAt(new File("/core/dev"));
+    assertThat(rootedFile).isEqualTo(new File("/root/foo/bar"));
+  }
+
+  @Test
+  public void testRelativeFileGetsRerooted() {
+    ExecutionRootPath executionRootPath = new ExecutionRootPath("foo/bar");
+    File rootedFile = executionRootPath.getFileRootedAt(new File("/root"));
+    assertThat(rootedFile).isEqualTo(new File("/root/foo/bar"));
+  }
+
+  @Test
+  public void testCreateRelativePathWithTwoRelativePaths() {
+    ExecutionRootPath relativePathFragment =
+        ExecutionRootPath.createAncestorRelativePath(
+            createMockDirectory("code/lib/fastmath"),
+            createMockDirectory("code/lib/fastmath/lib1"));
+    assertThat(relativePathFragment).isNotNull();
+    assertThat(relativePathFragment.getAbsoluteOrRelativeFile()).isEqualTo(new File("lib1"));
+  }
+
+  @Test
+  public void testCreateRelativePathWithTwoRelativePathsWithNoRelativePath() {
+    ExecutionRootPath relativePathFragment =
+        ExecutionRootPath.createAncestorRelativePath(
+            createMockDirectory("obj/lib/fastmath"), createMockDirectory("code/lib/slowmath"));
+    assertThat(relativePathFragment).isNull();
+  }
+
+  @Test
+  public void testCreateRelativePathWithTwoAbsolutePaths() {
+    ExecutionRootPath relativePathFragment =
+        ExecutionRootPath.createAncestorRelativePath(
+            createMockDirectory("/code/lib/fastmath"),
+            createMockDirectory("/code/lib/fastmath/lib1"));
+    assertThat(relativePathFragment).isNotNull();
+    assertThat(relativePathFragment.getAbsoluteOrRelativeFile()).isEqualTo(new File("lib1"));
+  }
+
+  @Test
+  public void testCreateRelativePathWithTwoAbsolutePathsWithNoRelativePath() {
+    ExecutionRootPath relativePathFragment =
+        ExecutionRootPath.createAncestorRelativePath(
+            createMockDirectory("/obj/lib/fastmath"), createMockDirectory("/code/lib/slowmath"));
+    assertThat(relativePathFragment).isNull();
+  }
+
+  @Test
+  public void testCreateRelativePathWithOneAbsolutePathAndOneRelativePathReturnsNull1() {
+    ExecutionRootPath relativePathFragment =
+        ExecutionRootPath.createAncestorRelativePath(
+            createMockDirectory("/code/lib/fastmath"),
+            createMockDirectory("code/lib/fastmath/lib1"));
+    assertThat(relativePathFragment).isNull();
+  }
+
+  @Test
+  public void testCreateRelativePathWithOneAbsolutePathAndOneRelativePathReturnsNull2() {
+    ExecutionRootPath relativePathFragment =
+        ExecutionRootPath.createAncestorRelativePath(
+            createMockDirectory("code/lib/fastmath"), createMockDirectory("/code/lib/slowmath"));
+    assertThat(relativePathFragment).isNull();
+  }
+
+  private static File createMockDirectory(String path) {
+    File org = new File(path);
+    File spy = Mockito.spy(org);
+    Mockito.when(spy.isDirectory()).then((Answer<Boolean>) invocationOnMock -> true);
+    return spy;
+  }
+}
diff --git a/base/tests/unittests/com/google/idea/blaze/base/model/primitives/LabelTest.java b/base/tests/unittests/com/google/idea/blaze/base/model/primitives/LabelTest.java
new file mode 100644
index 0000000..4ceb066
--- /dev/null
+++ b/base/tests/unittests/com/google/idea/blaze/base/model/primitives/LabelTest.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.model.primitives;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.idea.blaze.base.BlazeTestCase;
+import com.google.idea.common.experiments.ExperimentService;
+import com.google.idea.common.experiments.MockExperimentService;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests label validation */
+@RunWith(JUnit4.class)
+public class LabelTest extends BlazeTestCase {
+
+  @Override
+  protected void initTest(
+      @NotNull Container applicationServices, @NotNull Container projectServices) {
+    super.initTest(applicationServices, projectServices);
+    applicationServices.register(ExperimentService.class, new MockExperimentService());
+  }
+
+  @Test
+  public void testValidatePackage() {
+    // Legal names
+    assertThat(Label.validatePackagePath("foo")).isTrue();
+    assertThat(Label.validatePackagePath("f")).isTrue();
+    assertThat(Label.validatePackagePath("fooBAR")).isTrue();
+    assertThat(Label.validatePackagePath("foo/bar")).isTrue();
+    assertThat(Label.validatePackagePath("f9oo")).isTrue();
+    assertThat(Label.validatePackagePath("f_9oo")).isTrue();
+    assertThat(Label.validatePackagePath("Foo")).isTrue();
+    assertThat(Label.validatePackagePath("9.oo")).isTrue();
+    // This is not advised but is technically legal
+    assertThat(Label.validatePackagePath("")).isTrue();
+
+    // Illegal names
+    assertThat(Label.validatePackagePath("/foo")).isFalse();
+    assertThat(Label.validatePackagePath("foo/")).isFalse();
+    assertThat(Label.validatePackagePath("foo//bar")).isFalse();
+  }
+
+  @Test
+  public void testValidateLabel() {
+    // Valid labels
+    assertThat(Label.validate("//foo:bar")).isTrue();
+    assertThat(Label.validate("//foo/baz:bar")).isTrue();
+    assertThat(Label.validate("//:bar")).isTrue();
+
+    // Invalid labels
+    assertThat(Label.validate("//foo")).isFalse();
+    assertThat(Label.validate("foo")).isFalse();
+    assertThat(Label.validate("foo:bar")).isFalse();
+  }
+}
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
new file mode 100644
index 0000000..1fbb704
--- /dev/null
+++ b/base/tests/unittests/com/google/idea/blaze/base/model/primitives/RuleNameTest.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 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/TargetExpressionTest.java b/base/tests/unittests/com/google/idea/blaze/base/model/primitives/TargetExpressionTest.java
new file mode 100644
index 0000000..880c779
--- /dev/null
+++ b/base/tests/unittests/com/google/idea/blaze/base/model/primitives/TargetExpressionTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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 static org.junit.Assert.fail;
+
+import com.google.idea.blaze.base.BlazeTestCase;
+import com.google.idea.common.experiments.ExperimentService;
+import com.google.idea.common.experiments.MockExperimentService;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link com.google.idea.blaze.base.model.primitives.TargetExpressionFactory}. */
+@RunWith(JUnit4.class)
+public class TargetExpressionTest extends BlazeTestCase {
+  @Override
+  protected void initTest(
+      @NotNull Container applicationServices, @NotNull Container projectServices) {
+    super.initTest(applicationServices, projectServices);
+    applicationServices.register(ExperimentService.class, new MockExperimentService());
+  }
+
+  @Test
+  public void validLabelShouldYieldLabel() {
+    TargetExpression target = TargetExpression.fromString("//package:rule");
+    assertThat(target).isInstanceOf(Label.class);
+  }
+
+  @Test
+  public void globExpressionShouldYieldGeneralTargetExpression() {
+    TargetExpression target = TargetExpression.fromString("//package/...");
+    assertThat(target.getClass()).isSameAs(TargetExpression.class);
+  }
+
+  @Test
+  public void emptyExpressionShouldThrow() {
+    try {
+      TargetExpression.fromString("");
+      fail("Empty expressions should not be allowed.");
+    } catch (IllegalArgumentException expected) {
+    }
+  }
+}
diff --git a/base/tests/unittests/com/google/idea/blaze/base/model/primitives/WorkspacePathTest.java b/base/tests/unittests/com/google/idea/blaze/base/model/primitives/WorkspacePathTest.java
new file mode 100644
index 0000000..67e74e7
--- /dev/null
+++ b/base/tests/unittests/com/google/idea/blaze/base/model/primitives/WorkspacePathTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.common.collect.Lists;
+import com.google.idea.blaze.base.BlazeTestCase;
+import com.google.idea.blaze.base.ui.BlazeValidationError;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for workspace path validation */
+@RunWith(JUnit4.class)
+public class WorkspacePathTest extends BlazeTestCase {
+
+  @Test
+  public void testValidation() {
+    // Valid workspace paths
+    assertThat(WorkspacePath.validate("")).isTrue();
+    assertThat(WorkspacePath.validate("foo")).isTrue();
+    assertThat(WorkspacePath.validate("foo")).isTrue();
+    assertThat(WorkspacePath.validate("foo/bar")).isTrue();
+    assertThat(WorkspacePath.validate("foo/bar/baz")).isTrue();
+
+    // Invalid workspace paths
+    assertThat(WorkspacePath.validate("/foo")).isFalse();
+    assertThat(WorkspacePath.validate("//foo")).isFalse();
+    assertThat(WorkspacePath.validate("/")).isFalse();
+    assertThat(WorkspacePath.validate("foo/")).isFalse();
+    assertThat(WorkspacePath.validate("foo:")).isFalse();
+    assertThat(WorkspacePath.validate(":")).isFalse();
+    assertThat(WorkspacePath.validate("foo:bar")).isFalse();
+
+    List<BlazeValidationError> errors = Lists.newArrayList();
+
+    WorkspacePath.validate("/foo", errors);
+    assertThat(errors.get(0).getError()).isEqualTo("Workspace path may not start with '/': /foo");
+    errors.clear();
+
+    WorkspacePath.validate("/", errors);
+    assertThat(errors.get(0).getError()).isEqualTo("Workspace path may not start with '/': /");
+    errors.clear();
+
+    WorkspacePath.validate("foo/", errors);
+    assertThat(errors.get(0).getError()).isEqualTo("Workspace path may not end with '/': foo/");
+    errors.clear();
+
+    WorkspacePath.validate("foo:bar", errors);
+    assertThat(errors.get(0).getError()).isEqualTo("Workspace path may not contain ':': foo:bar");
+  }
+}
diff --git a/base/tests/unittests/com/google/idea/blaze/base/projectview/ProjectViewSetTest.java b/base/tests/unittests/com/google/idea/blaze/base/projectview/ProjectViewSetTest.java
new file mode 100644
index 0000000..40ea19f
--- /dev/null
+++ b/base/tests/unittests/com/google/idea/blaze/base/projectview/ProjectViewSetTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.projectview;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.base.BlazeTestCase;
+import com.google.idea.blaze.base.TestUtils;
+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.TargetExpression;
+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.Glob;
+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.Section;
+import com.google.idea.blaze.base.projectview.section.SectionParser;
+import com.google.idea.blaze.base.projectview.section.sections.AdditionalLanguagesSection;
+import com.google.idea.blaze.base.projectview.section.sections.BuildFlagsSection;
+import com.google.idea.blaze.base.projectview.section.sections.DirectoryEntry;
+import com.google.idea.blaze.base.projectview.section.sections.DirectorySection;
+import com.google.idea.blaze.base.projectview.section.sections.ExcludeTargetSection;
+import com.google.idea.blaze.base.projectview.section.sections.ExcludedSourceSection;
+import com.google.idea.blaze.base.projectview.section.sections.ImportSection;
+import com.google.idea.blaze.base.projectview.section.sections.ImportTargetOutputSection;
+import com.google.idea.blaze.base.projectview.section.sections.MetricsProjectSection;
+import com.google.idea.blaze.base.projectview.section.sections.Sections;
+import com.google.idea.blaze.base.projectview.section.sections.TargetSection;
+import com.google.idea.blaze.base.projectview.section.sections.TestSourceSection;
+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.projectview.section.sections.WorkspaceTypeSection;
+import com.google.idea.blaze.base.sync.BlazeSyncPlugin;
+import com.google.idea.common.experiments.ExperimentService;
+import com.google.idea.common.experiments.MockExperimentService;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for project view sets */
+@RunWith(JUnit4.class)
+public class ProjectViewSetTest extends BlazeTestCase {
+
+  @Override
+  protected void initTest(
+      @NotNull Container applicationServices, @NotNull Container projectServices) {
+    super.initTest(applicationServices, projectServices);
+    applicationServices.register(ExperimentService.class, new MockExperimentService());
+    registerExtensionPoint(BlazeSyncPlugin.EP_NAME, BlazeSyncPlugin.class);
+  }
+
+  @Test
+  public void testProjectViewSetSerializable() throws Exception {
+    ProjectViewSet projectViewSet =
+        ProjectViewSet.builder()
+            .add(
+                ProjectView.builder()
+                    .add(
+                        ListSection.builder(DirectorySection.KEY)
+                            .add(DirectoryEntry.include(new WorkspacePath("test"))))
+                    .add(
+                        ListSection.builder(TargetSection.KEY)
+                            .add(TargetExpression.fromString("//test:all")))
+                    .add(ScalarSection.builder(ImportSection.KEY).set(new WorkspacePath("test")))
+                    .add(ListSection.builder(TestSourceSection.KEY).add(new Glob("javatests/*")))
+                    .add(ListSection.builder(ExcludedSourceSection.KEY).add(new Glob("*.java")))
+                    .add(ListSection.builder(BuildFlagsSection.KEY).add("--android_sdk=abcd"))
+                    .add(
+                        ListSection.builder(ImportTargetOutputSection.KEY)
+                            .add(new Label("//test:test")))
+                    .add(
+                        ListSection.builder(ExcludeTargetSection.KEY).add(new Label("//test:test")))
+                    .add(ScalarSection.builder(WorkspaceTypeSection.KEY).set(WorkspaceType.JAVA))
+                    .add(
+                        ListSection.builder(AdditionalLanguagesSection.KEY).add(LanguageClass.JAVA))
+                    .add(ScalarSection.builder(MetricsProjectSection.KEY).set("my project"))
+                    .add(TextBlockSection.of(TextBlock.newLine()))
+                    .build())
+            .build();
+
+    // Assert we have all sections
+    assert projectViewSet.getTopLevelProjectViewFile() != null;
+    ProjectView projectView = projectViewSet.getTopLevelProjectViewFile().projectView;
+    for (SectionParser parser : Sections.getParsers()) {
+      ImmutableList<Section<?>> sections = projectView.getSections();
+      assertThat(
+              sections.stream().anyMatch(section -> section.isSectionType(parser.getSectionKey())))
+          .isTrue();
+    }
+
+    TestUtils.assertIsSerializable(projectViewSet);
+  }
+}
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
new file mode 100644
index 0000000..78506ef
--- /dev/null
+++ b/base/tests/unittests/com/google/idea/blaze/base/projectview/ProjectViewVerifierTest.java
@@ -0,0 +1,177 @@
+/*
+ * 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.projectview;
+
+import com.google.common.collect.ImmutableSet;
+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.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.section.ListSection;
+import com.google.idea.blaze.base.projectview.section.sections.DirectoryEntry;
+import com.google.idea.blaze.base.projectview.section.sections.DirectorySection;
+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.WorkspaceLanguageSettings;
+import java.io.File;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for ProjectViewVerifier */
+@RunWith(JUnit4.class)
+public class ProjectViewVerifierTest extends BlazeTestCase {
+
+  private static final String FAKE_ROOT = "/root";
+  private WorkspaceRoot workspaceRoot = new WorkspaceRoot(new File(FAKE_ROOT));
+  private MockWorkspaceScanner workspaceScanner;
+  private ErrorCollector errorCollector = new ErrorCollector();
+  private BlazeContext context;
+  private WorkspaceLanguageSettings workspaceLanguageSettings =
+      new WorkspaceLanguageSettings(WorkspaceType.JAVA, ImmutableSet.of(LanguageClass.JAVA));
+
+  @Override
+  protected void initTest(
+      @NotNull Container applicationServices, @NotNull Container projectServices) {
+    super.initTest(applicationServices, projectServices);
+    registerExtensionPoint(BlazeSyncPlugin.EP_NAME, BlazeSyncPlugin.class);
+
+    workspaceScanner = new MockWorkspaceScanner();
+    applicationServices.register(WorkspaceScanner.class, workspaceScanner);
+    context = new BlazeContext();
+    context.addOutputSink(IssueOutput.class, errorCollector);
+  }
+
+  @Test
+  public void testNoIssues() {
+    ProjectViewSet projectViewSet =
+        ProjectViewSet.builder()
+            .add(
+                ProjectView.builder()
+                    .add(
+                        ListSection.builder(DirectorySection.KEY)
+                            .add(
+                                DirectoryEntry.include(
+                                    new WorkspacePath("java/com/google/android/apps/example")))
+                            .add(
+                                DirectoryEntry.include(
+                                    new WorkspacePath("java/com/google/android/apps/example2"))))
+                    .build())
+            .build();
+    workspaceScanner.addProjectView(workspaceRoot, projectViewSet);
+    ProjectViewVerifier.verifyProjectView(
+        context, workspaceRoot, projectViewSet, workspaceLanguageSettings);
+    errorCollector.assertNoIssues();
+  }
+
+  @Test
+  public void testExcludingExactRootResultsInIssue() {
+    ProjectViewSet projectViewSet =
+        ProjectViewSet.builder()
+            .add(
+                ProjectView.builder()
+                    .add(
+                        ListSection.builder(DirectorySection.KEY)
+                            .add(
+                                DirectoryEntry.include(
+                                    new WorkspacePath("java/com/google/android/apps/example")))
+                            .add(
+                                DirectoryEntry.exclude(
+                                    new WorkspacePath("java/com/google/android/apps/example"))))
+                    .build())
+            .build();
+    workspaceScanner.addProjectView(workspaceRoot, projectViewSet);
+    ProjectViewVerifier.verifyProjectView(
+        context, workspaceRoot, projectViewSet, workspaceLanguageSettings);
+    errorCollector.assertIssues(
+        "java/com/google/android/apps/example is included, "
+            + "but that contradicts java/com/google/android/apps/example which was excluded");
+  }
+
+  @Test
+  public void testExcludingRootViaParentResultsInIssue() {
+    ProjectViewSet projectViewSet =
+        ProjectViewSet.builder()
+            .add(
+                ProjectView.builder()
+                    .add(
+                        ListSection.builder(DirectorySection.KEY)
+                            .add(
+                                DirectoryEntry.include(
+                                    new WorkspacePath("java/com/google/android/apps/example")))
+                            .add(
+                                DirectoryEntry.exclude(
+                                    new WorkspacePath("java/com/google/android/apps"))))
+                    .build())
+            .build();
+    workspaceScanner.addProjectView(workspaceRoot, projectViewSet);
+    ProjectViewVerifier.verifyProjectView(
+        context, workspaceRoot, projectViewSet, workspaceLanguageSettings);
+    errorCollector.assertIssues(
+        "java/com/google/android/apps/example is included, "
+            + "but that contradicts java/com/google/android/apps which was excluded");
+  }
+
+  @Test
+  public void testExcludingSubdirectoryOfRootResultsInNoIssues() {
+    ProjectViewSet projectViewSet =
+        ProjectViewSet.builder()
+            .add(
+                ProjectView.builder()
+                    .add(
+                        ListSection.builder(DirectorySection.KEY)
+                            .add(
+                                DirectoryEntry.include(
+                                    new WorkspacePath("java/com/google/android/apps/example")))
+                            .add(
+                                DirectoryEntry.exclude(
+                                    new WorkspacePath(
+                                        "java/com/google/android/apps/example/subdir"))))
+                    .build())
+            .build();
+    workspaceScanner.addProjectView(workspaceRoot, projectViewSet);
+    ProjectViewVerifier.verifyProjectView(
+        context, workspaceRoot, projectViewSet, workspaceLanguageSettings);
+    errorCollector.assertNoIssues();
+  }
+
+  @Test
+  public void testImportRootMissingResultsInIssue() {
+    ProjectViewSet projectViewSet =
+        ProjectViewSet.builder()
+            .add(
+                ProjectView.builder()
+                    .add(
+                        ListSection.builder(DirectorySection.KEY)
+                            .add(
+                                DirectoryEntry.include(
+                                    new WorkspacePath("java/com/google/android/apps/example"))))
+                    .build())
+            .build();
+    ProjectViewVerifier.verifyProjectView(
+        context, workspaceRoot, projectViewSet, workspaceLanguageSettings);
+    errorCollector.assertIssues(
+        String.format(
+            "Directory '%s' specified in import roots not found under workspace root '%s'",
+            "java/com/google/android/apps/example", "/root"));
+  }
+}
diff --git a/base/tests/unittests/com/google/idea/blaze/base/projectview/parser/ProjectViewParserTest.java b/base/tests/unittests/com/google/idea/blaze/base/projectview/parser/ProjectViewParserTest.java
new file mode 100644
index 0000000..08fea57
--- /dev/null
+++ b/base/tests/unittests/com/google/idea/blaze/base/projectview/parser/ProjectViewParserTest.java
@@ -0,0 +1,443 @@
+/*
+ * 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.projectview.parser;
+
+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.Maps;
+import com.google.idea.blaze.base.BlazeTestCase;
+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.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.ProjectViewStorageManager;
+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.DirectoryEntry;
+import com.google.idea.blaze.base.projectview.section.sections.DirectorySection;
+import com.google.idea.blaze.base.projectview.section.sections.ImportSection;
+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.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.workspace.WorkspacePathResolverImpl;
+import com.google.idea.common.experiments.ExperimentService;
+import com.google.idea.common.experiments.MockExperimentService;
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for the project view parser */
+@RunWith(JUnit4.class)
+public class ProjectViewParserTest extends BlazeTestCase {
+  private ProjectViewParser projectViewParser;
+  private BlazeContext context;
+  private ErrorCollector errorCollector;
+  private WorkspaceRoot workspaceRoot = new WorkspaceRoot(new File("/"));
+  private MockProjectViewStorageManager projectViewStorageManager;
+
+  static class MockProjectViewStorageManager extends ProjectViewStorageManager {
+    Map<String, String> projectViewFiles = Maps.newHashMap();
+
+    @Nullable
+    @Override
+    public String loadProjectView(@NotNull File projectViewFile) throws IOException {
+      return projectViewFiles.get(projectViewFile.getPath());
+    }
+
+    @Override
+    public void writeProjectView(@NotNull String projectViewText, @NotNull File projectViewFile)
+        throws IOException {
+      // no-op
+    }
+
+    void add(String name, String... text) {
+      projectViewFiles.put(name, Joiner.on('\n').join(text));
+    }
+  }
+
+  @Override
+  protected void initTest(
+      @NotNull Container applicationServices, @NotNull Container projectServices) {
+    super.initTest(applicationServices, projectServices);
+    context = new BlazeContext();
+    errorCollector = new ErrorCollector();
+    context.addOutputSink(IssueOutput.class, errorCollector);
+    projectViewParser =
+        new ProjectViewParser(context, new WorkspacePathResolverImpl(workspaceRoot));
+    projectViewStorageManager = new MockProjectViewStorageManager();
+    applicationServices.register(ProjectViewStorageManager.class, projectViewStorageManager);
+    applicationServices.register(ExperimentService.class, new MockExperimentService());
+    registerExtensionPoint(BlazeSyncPlugin.EP_NAME, BlazeSyncPlugin.class);
+  }
+
+  @Test
+  public void testDirectoriesAndTargets() throws Exception {
+    projectViewStorageManager.add(
+        ".blazeproject",
+        "directories:",
+        "  java/com/google",
+        "  java/com/google/android",
+        "  -java/com/google/android/notme",
+        "",
+        "targets:",
+        "  //java/com/google:all",
+        "  //java/com/google/...:all",
+        "  -//java/com/google:thistarget");
+    projectViewParser.parseProjectView(new File(".blazeproject"));
+    errorCollector.assertNoIssues();
+
+    ProjectViewSet projectViewSet = projectViewParser.getResult();
+    ProjectViewSet.ProjectViewFile projectViewFile = projectViewSet.getTopLevelProjectViewFile();
+    assertThat(projectViewFile).isNotNull();
+    assertThat(projectViewFile.projectViewFile).isEqualTo(new File(".blazeproject"));
+    assertThat(projectViewSet.getProjectViewFiles()).containsExactly(projectViewFile);
+
+    ProjectView projectView = projectViewFile.projectView;
+    assertThat(projectView.getSectionsOfType(DirectorySection.KEY).get(0).items())
+        .containsExactly(
+            new DirectoryEntry(new WorkspacePath("java/com/google"), true),
+            new DirectoryEntry(new WorkspacePath("java/com/google/android"), true),
+            new DirectoryEntry(new WorkspacePath("java/com/google/android/notme"), false));
+    assertThat(projectView.getSectionsOfType(TargetSection.KEY).get(0).items())
+        .containsExactly(
+            TargetExpression.fromString("//java/com/google:all"),
+            TargetExpression.fromString("//java/com/google/...:all"),
+            TargetExpression.fromString("-//java/com/google:thistarget"));
+  }
+
+  @Test
+  public void testRootDirectory() throws Exception {
+    projectViewStorageManager.add(
+        ".blazeproject",
+        "directories:",
+        "  .",
+        "  -java/com/google/android/notme",
+        "",
+        "targets:",
+        "  //java/com/google:all");
+    projectViewParser.parseProjectView(new File(".blazeproject"));
+    errorCollector.assertNoIssues();
+
+    ProjectViewSet projectViewSet = projectViewParser.getResult();
+    ProjectViewSet.ProjectViewFile projectViewFile = projectViewSet.getTopLevelProjectViewFile();
+    assertThat(projectViewFile).isNotNull();
+    assertThat(projectViewFile.projectViewFile).isEqualTo(new File(".blazeproject"));
+    assertThat(projectViewSet.getProjectViewFiles()).containsExactly(projectViewFile);
+
+    ProjectView projectView = projectViewFile.projectView;
+    assertThat(projectView.getSectionsOfType(DirectorySection.KEY).get(0).items())
+        .containsExactly(
+            new DirectoryEntry(new WorkspacePath(""), true),
+            new DirectoryEntry(new WorkspacePath("java/com/google/android/notme"), false));
+    assertThat(projectView.getSectionsOfType(TargetSection.KEY).get(0).items())
+        .containsExactly(TargetExpression.fromString("//java/com/google:all"));
+
+    String text = ProjectViewParser.projectViewToString(projectView);
+    assertThat(text)
+        .isEqualTo(
+            Joiner.on('\n')
+                .join(
+                    "directories:",
+                    "  .",
+                    "  -java/com/google/android/notme",
+                    "",
+                    "targets:",
+                    "  //java/com/google:all"));
+  }
+
+  @Test
+  public void testPrint() {
+    ProjectView projectView =
+        ProjectView.builder()
+            .add(
+                ListSection.builder(DirectorySection.KEY)
+                    .add(DirectoryEntry.include(new WorkspacePath("java/com/google/one")))
+                    .add(DirectoryEntry.exclude(new WorkspacePath("java/com/google/two"))))
+            .add(
+                ListSection.builder(TargetSection.KEY)
+                    .add(TargetExpression.fromString("//java/com/google:one"))
+                    .add(TargetExpression.fromString("//java/com/google:two")))
+            .add(
+                ScalarSection.builder(ImportSection.KEY)
+                    .set(new WorkspacePath("some/file.blazeproject")))
+            .build();
+    String text = ProjectViewParser.projectViewToString(projectView);
+    assertThat(text)
+        .isEqualTo(
+            Joiner.on('\n')
+                .join(
+                    "directories:",
+                    "  java/com/google/one",
+                    "  -java/com/google/two",
+                    "targets:",
+                    "  //java/com/google:one",
+                    "  //java/com/google:two",
+                    "import some/file.blazeproject"));
+  }
+
+  @Test
+  public void testImport() {
+    projectViewStorageManager.add("/parent.blazeproject", "directories:", "  parent", "");
+    projectViewStorageManager.add(
+        ".blazeproject", "import parent.blazeproject", "directories:", "  child", "");
+    projectViewParser.parseProjectView(new File(".blazeproject"));
+    errorCollector.assertNoIssues();
+
+    ProjectViewSet projectViewSet = projectViewParser.getResult();
+    assertThat(projectViewSet.getProjectViewFiles()).hasSize(2);
+    Collection<DirectoryEntry> entries = projectViewSet.listItems(DirectorySection.KEY);
+    assertThat(entries)
+        .containsExactly(
+            new DirectoryEntry(new WorkspacePath("parent"), true),
+            new DirectoryEntry(new WorkspacePath("child"), true));
+  }
+
+  @Test
+  public void testMultipleImports() {
+    projectViewStorageManager.add("/grandparent.blazeproject", "directories:", "  grandparent");
+    projectViewStorageManager.add(
+        "/mother.blazeproject", "import grandparent.blazeproject", "directories:", "  mother");
+    projectViewStorageManager.add(
+        "/father.blazeproject", "import grandparent.blazeproject", "directories:", "  father");
+    projectViewStorageManager.add(
+        "/child.blazeproject",
+        "import mother.blazeproject",
+        "directories:",
+        "  child1",
+        "import father.blazeproject",
+        "directories:",
+        "  child2");
+    projectViewParser.parseProjectView(new File("/child.blazeproject"));
+    errorCollector.assertNoIssues();
+
+    ProjectViewSet projectViewSet = projectViewParser.getResult();
+
+    // Ensures we don't parse grandfather twice
+    assertThat(projectViewSet.getProjectViewFiles()).hasSize(4);
+    Collection<DirectoryEntry> entries = projectViewSet.listItems(DirectorySection.KEY);
+
+    // All imports' contributions appear before the children, no matter where they appear
+    assertThat(entries)
+        .containsExactly(
+            new DirectoryEntry(new WorkspacePath("grandparent"), true),
+            new DirectoryEntry(new WorkspacePath("mother"), true),
+            new DirectoryEntry(new WorkspacePath("father"), true),
+            new DirectoryEntry(new WorkspacePath("child1"), true),
+            new DirectoryEntry(new WorkspacePath("child2"), true))
+        .inOrder();
+  }
+
+  @Test
+  public void testMinimumIndentRequired() {
+    projectViewStorageManager.add(
+        ".blazeproject", "directories:", "  java/com/google", "java/com/google2", "");
+    projectViewParser.parseProjectView(new File(".blazeproject"));
+    errorCollector.assertIssues("Could not parse: 'java/com/google2'");
+  }
+
+  @Test
+  public void testIncorrectIndentationResultsInIssue() {
+    projectViewStorageManager.add(
+        ".blazeproject", "directories:", "  java/com/google", " java/com/google2", "");
+    projectViewParser.parseProjectView(new File(".blazeproject"));
+    errorCollector.assertIssues("Invalid indentation on line: 'java/com/google2'");
+  }
+
+  @Test
+  public void testCanParseWithMissingCarriageReturnAtEndOfSection() {
+    projectViewStorageManager.add(".blazeproject", "directories:", "  java/com/google");
+    projectViewParser.parseProjectView(new File(".blazeproject"));
+    ProjectView projectView =
+        projectViewParser.getResult().getTopLevelProjectViewFile().projectView;
+    assertThat(projectView.getSectionsOfType(DirectorySection.KEY).get(0).items())
+        .containsExactly(new DirectoryEntry(new WorkspacePath("java/com/google"), true));
+  }
+
+  @Test
+  public void testImportMissingFileResultsInIssue() {
+    projectViewStorageManager.add(".blazeproject", "import parent.blazeproject");
+    projectViewParser.parseProjectView(new File(".blazeproject"));
+    errorCollector.assertIssues("Could not load project view file: '/parent.blazeproject'");
+  }
+
+  @Test
+  public void testMissingSectionResultsInIssue() {
+    projectViewStorageManager.add(".blazeproject", "nosuchsection:", "  java/com/google");
+    projectViewParser.parseProjectView(new File(".blazeproject"));
+    errorCollector.assertIssues("Could not parse: 'nosuchsection:'");
+  }
+
+  @Test
+  public void testMissingColonResultInIssue() {
+    projectViewStorageManager.add(".blazeproject", "directories", "  java/com/google");
+    projectViewParser.parseProjectView(new File(".blazeproject"));
+    errorCollector.assertIssues("Could not parse: 'directories'");
+  }
+
+  @Test
+  public void testEmptySectionYieldsError() {
+    projectViewStorageManager.add(".blazeproject", "directories:", "");
+    projectViewParser.parseProjectView(new File(".blazeproject"));
+    errorCollector.assertIssues("Empty section: 'directories'");
+  }
+
+  @Test
+  public void testCommentsAreParsed() throws Exception {
+    projectViewStorageManager.add(
+        ".blazeproject",
+        "# comment",
+        "directories:",
+        "  # another comment",
+        "  java/com/google",
+        "  # comment",
+        "  java/com/google/android",
+        "");
+    projectViewParser.parseProjectView(new File(".blazeproject"));
+    errorCollector.assertNoIssues();
+
+    ProjectViewSet projectViewSet = projectViewParser.getResult();
+    ProjectViewSet.ProjectViewFile projectViewFile = projectViewSet.getTopLevelProjectViewFile();
+    ProjectView projectView = projectViewFile.projectView;
+    assertThat(projectView.getSectionsOfType(TextBlockSection.KEY).get(0).getTextBlock())
+        .isEqualTo(new TextBlock(ImmutableList.of("# comment")));
+    assertThat(projectView.getSectionsOfType(DirectorySection.KEY).get(0).items())
+        .containsExactly(
+            new DirectoryEntry(new WorkspacePath("java/com/google"), true),
+            new DirectoryEntry(new WorkspacePath("java/com/google/android"), true));
+  }
+
+  @Test
+  public void testMultipleSections() throws Exception {
+    projectViewStorageManager.add(
+        ".blazeproject",
+        "directories:",
+        "  java/com/google",
+        "directories:",
+        "  java/com/google2",
+        "",
+        "workspace_type: java",
+        "workspace_type: android");
+    projectViewParser.parseProjectView(new File(".blazeproject"));
+    errorCollector.assertNoIssues();
+
+    ProjectViewSet projectViewSet = projectViewParser.getResult();
+    assertThat(projectViewSet.listItems(DirectorySection.KEY))
+        .containsExactly(
+            new DirectoryEntry(new WorkspacePath("java/com/google"), true),
+            new DirectoryEntry(new WorkspacePath("java/com/google2"), true));
+    assertThat(projectViewSet.listScalarItems(WorkspaceTypeSection.KEY))
+        .containsExactly(WorkspaceType.JAVA, WorkspaceType.ANDROID);
+  }
+
+  @Test
+  public void testListParserAcceptsWhitespace() throws Exception {
+    String text =
+        Joiner.on('\n')
+            .join(
+                "directories:",
+                "  dir0",
+                "  ",
+                "",
+                "  dir1",
+                "  ",
+                "  ",
+                "# comment",
+                "  dir2",
+                "",
+                "  # commented out dir",
+                "  ",
+                "# comment",
+                "# comment");
+    projectViewStorageManager.add(".blazeproject", text);
+    projectViewParser.parseProjectView(new File(".blazeproject"));
+    errorCollector.assertNoIssues();
+
+    ProjectViewSet projectViewSet = projectViewParser.getResult();
+    ProjectViewSet.ProjectViewFile projectViewFile = projectViewSet.getTopLevelProjectViewFile();
+    assert projectViewFile != null;
+    ProjectView projectView = projectViewFile.projectView;
+
+    assertThat(projectView)
+        .isEqualTo(
+            ProjectView.builder()
+                .add(
+                    ListSection.builder(DirectorySection.KEY)
+                        .add(DirectoryEntry.include(new WorkspacePath("dir0")))
+                        .add(TextBlock.of("  "))
+                        .add(TextBlock.of(""))
+                        .add(DirectoryEntry.include(new WorkspacePath("dir1")))
+                        .add(TextBlock.of("  ", "  "))
+                        .add(TextBlock.of("# comment"))
+                        .add(DirectoryEntry.include(new WorkspacePath("dir2")))
+                        .add(TextBlock.of(""))
+                        .add(TextBlock.of("  # commented out dir"))
+                        .add(TextBlock.of("  ")))
+                .add(TextBlockSection.of(TextBlock.of("# comment", "# comment")))
+                .build());
+
+    String outputString = ProjectViewParser.projectViewToString(projectView);
+    assertThat(outputString).isEqualTo(text);
+  }
+
+  @Test
+  public void testCommentsAndWhitespacePreserved() throws Exception {
+    String text =
+        Joiner.on('\n')
+            .join(
+                "",
+                "# comment",
+                "  ",
+                "  ",
+                "directories:",
+                "  # another comment",
+                "  java/com/google",
+                "  # comment",
+                "#unindented comment",
+                "  java/com/google/android",
+                "",
+                "  # needlessly indented comment",
+                "",
+                "directories:",
+                "  java/com/google",
+                "  # trailing comment",
+                "directories:",
+                "  java/com/google");
+    projectViewStorageManager.add(".blazeproject", text);
+    projectViewParser.parseProjectView(new File(".blazeproject"));
+    errorCollector.assertNoIssues();
+
+    ProjectViewSet projectViewSet = projectViewParser.getResult();
+    ProjectViewSet.ProjectViewFile projectViewFile = projectViewSet.getTopLevelProjectViewFile();
+    ProjectView projectView = projectViewFile.projectView;
+    String outputString = ProjectViewParser.projectViewToString(projectView);
+    assertThat(outputString).isEqualTo(text);
+  }
+}
diff --git a/base/tests/unittests/com/google/idea/blaze/base/rulemaps/ReverseDependencyMapTest.java b/base/tests/unittests/com/google/idea/blaze/base/rulemaps/ReverseDependencyMapTest.java
new file mode 100644
index 0000000..310b7c3
--- /dev/null
+++ b/base/tests/unittests/com/google/idea/blaze/base/rulemaps/ReverseDependencyMapTest.java
@@ -0,0 +1,175 @@
+/*
+ * 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 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.RuleMapBuilder;
+import com.google.idea.blaze.base.model.RuleMap;
+import com.google.idea.blaze.base.model.primitives.Label;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for the reverse dependency map */
+@RunWith(JUnit4.class)
+public class ReverseDependencyMapTest extends BlazeTestCase {
+  @Override
+  protected void initTest(
+      @NotNull Container applicationServices, @NotNull Container projectServices) {
+    super.initTest(applicationServices, projectServices);
+  }
+
+  @Test
+  public void testSingleDep() {
+    RuleMapBuilder builder = RuleMapBuilder.builder();
+    RuleMap ruleMap =
+        builder
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setBuildFile(sourceRoot("test/BUILD"))
+                    .setLabel("//l:l1")
+                    .setKind("java_library")
+                    .addDependency("//l:l2"))
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setBuildFile(sourceRoot("test/BUILD"))
+                    .setLabel("//l:l2")
+                    .setKind("java_library"))
+            .build();
+
+    ImmutableMultimap<Label, Label> reverseDependencies =
+        ReverseDependencyMap.createRdepsMap(ruleMap);
+    assertThat(reverseDependencies).containsEntry(new Label("//l:l2"), new Label("//l:l1"));
+  }
+
+  @Test
+  public void testLabelDepsOnTwoLabels() {
+    RuleMapBuilder builder = RuleMapBuilder.builder();
+    RuleMap ruleMap =
+        builder
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setBuildFile(sourceRoot("test/BUILD"))
+                    .setLabel("//l:l1")
+                    .setKind("java_library")
+                    .addDependency("//l:l2")
+                    .addDependency("//l:l3"))
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setBuildFile(sourceRoot("test/BUILD"))
+                    .setLabel("//l:l2")
+                    .setKind("java_library"))
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setBuildFile(sourceRoot("test/BUILD"))
+                    .setLabel("//l:l3")
+                    .setKind("java_library"))
+            .build();
+
+    ImmutableMultimap<Label, Label> reverseDependencies =
+        ReverseDependencyMap.createRdepsMap(ruleMap);
+    assertThat(reverseDependencies).containsEntry(new Label("//l:l2"), new Label("//l:l1"));
+    assertThat(reverseDependencies).containsEntry(new Label("//l:l3"), new Label("//l:l1"));
+  }
+
+  @Test
+  public void testTwoLabelsDepOnSameLabel() {
+    RuleMapBuilder builder = RuleMapBuilder.builder();
+    RuleMap ruleMap =
+        builder
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setBuildFile(sourceRoot("test/BUILD"))
+                    .setLabel("//l:l1")
+                    .setKind("java_library")
+                    .addDependency("//l:l3"))
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setBuildFile(sourceRoot("test/BUILD"))
+                    .setLabel("//l:l2")
+                    .addDependency("//l:l3")
+                    .setKind("java_library"))
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setBuildFile(sourceRoot("test/BUILD"))
+                    .setLabel("//l:l3")
+                    .setKind("java_library"))
+            .build();
+
+    ImmutableMultimap<Label, Label> reverseDependencies =
+        ReverseDependencyMap.createRdepsMap(ruleMap);
+    assertThat(reverseDependencies).containsEntry(new Label("//l:l3"), new Label("//l:l1"));
+    assertThat(reverseDependencies).containsEntry(new Label("//l:l3"), new Label("//l:l2"));
+  }
+
+  @Test
+  public void testThreeLevelGraph() {
+    RuleMapBuilder builder = RuleMapBuilder.builder();
+    RuleMap ruleMap =
+        builder
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setBuildFile(sourceRoot("test/BUILD"))
+                    .setLabel("//l:l1")
+                    .setKind("java_library")
+                    .addDependency("//l:l3"))
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setBuildFile(sourceRoot("test/BUILD"))
+                    .setLabel("//l:l2")
+                    .addDependency("//l:l3")
+                    .setKind("java_library"))
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setBuildFile(sourceRoot("test/BUILD"))
+                    .setLabel("//l:l3")
+                    .setKind("java_library"))
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setBuildFile(sourceRoot("test/BUILD"))
+                    .setLabel("//l:l4")
+                    .addDependency("//l:l3")
+                    .setKind("java_library"))
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setBuildFile(sourceRoot("test/BUILD"))
+                    .setLabel("//l:l5")
+                    .addDependency("//l:l4")
+                    .setKind("java_library"))
+            .build();
+
+    ImmutableMultimap<Label, Label> reverseDependencies =
+        ReverseDependencyMap.createRdepsMap(ruleMap);
+    assertThat(reverseDependencies).containsEntry(new Label("//l:l3"), new Label("//l:l1"));
+    assertThat(reverseDependencies).containsEntry(new Label("//l:l3"), new Label("//l:l2"));
+    assertThat(reverseDependencies).containsEntry(new Label("//l:l3"), new Label("//l:l4"));
+    assertThat(reverseDependencies).containsEntry(new Label("//l:l4"), new Label("//l:l5"));
+  }
+
+  private static ArtifactLocation sourceRoot(String relativePath) {
+    return ArtifactLocation.builder()
+        .setRootPath("/")
+        .setRelativePath(relativePath)
+        .setIsSource(true)
+        .build();
+  }
+}
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
new file mode 100644
index 0000000..334557e
--- /dev/null
+++ b/base/tests/unittests/com/google/idea/blaze/base/run/BlazeCommandRunConfigurationTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.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.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.settings.Blaze;
+import com.google.idea.blaze.base.settings.BlazeImportSettings;
+import com.google.idea.blaze.base.settings.BlazeImportSettingsManager;
+import com.google.idea.common.experiments.ExperimentService;
+import com.google.idea.common.experiments.MockExperimentService;
+import com.intellij.ide.ui.UISettings;
+import com.intellij.openapi.extensions.impl.ExtensionPointImpl;
+import com.intellij.openapi.project.Project;
+import java.util.List;
+import org.jdom.Element;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link BlazeCommandRunConfiguration}. */
+@RunWith(JUnit4.class)
+public class BlazeCommandRunConfigurationTest extends BlazeTestCase {
+  private static final BlazeImportSettings DUMMY_IMPORT_SETTINGS =
+      new BlazeImportSettings("", "", "", "", "", Blaze.BuildSystem.Blaze);
+
+  private final BlazeCommandRunConfigurationType type = new BlazeCommandRunConfigurationType();
+  private BlazeCommandRunConfiguration configuration;
+
+  @Override
+  protected void initTest(
+      @NotNull Container applicationServices, @NotNull Container projectServices) {
+    super.initTest(applicationServices, projectServices);
+
+    applicationServices.register(UISettings.class, new UISettings());
+    projectServices.register(
+        BlazeImportSettingsManager.class, new BlazeImportSettingsManager(project));
+    BlazeImportSettingsManager.getInstance(getProject()).setImportSettings(DUMMY_IMPORT_SETTINGS);
+
+    this.configuration = this.type.getFactory().createTemplateConfiguration(project);
+
+    applicationServices.register(ExperimentService.class, new MockExperimentService());
+    applicationServices.register(RuleFinder.class, new MockRuleFinder());
+    ExtensionPointImpl<BlazeCommandRunConfigurationHandlerProvider> handlerProviderEp =
+        registerExtensionPoint(
+            BlazeCommandRunConfigurationHandlerProvider.EP_NAME,
+            BlazeCommandRunConfigurationHandlerProvider.class);
+    handlerProviderEp.registerExtension(new MockBlazeCommandRunConfigurationHandlerProvider());
+  }
+
+  @Test
+  public void readAndWriteShouldMatch() throws Exception {
+    Label label = new Label("//package:rule");
+    configuration.setTarget(label);
+
+    Element element = new Element("test");
+    configuration.writeExternal(element);
+    BlazeCommandRunConfiguration readConfiguration =
+        type.getFactory().createTemplateConfiguration(project);
+    readConfiguration.readExternal(element);
+
+    assertThat(readConfiguration.getTarget()).isEqualTo(label);
+  }
+
+  @Test
+  public void readAndWriteShouldHandleNulls() throws Exception {
+    Element element = new Element("test");
+    configuration.writeExternal(element);
+    BlazeCommandRunConfiguration readConfiguration =
+        type.getFactory().createTemplateConfiguration(project);
+    readConfiguration.readExternal(element);
+
+    assertThat(readConfiguration.getTarget()).isEqualTo(configuration.getTarget());
+  }
+
+  private static class MockRuleFinder extends RuleFinder {
+    @Override
+    public List<RuleIdeInfo> findRules(Project project, Predicate<RuleIdeInfo> 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
new file mode 100644
index 0000000..1cb0276
--- /dev/null
+++ b/base/tests/unittests/com/google/idea/blaze/base/run/RuleNameHeuristicTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.idea.blaze.base.BlazeTestCase;
+import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+import com.google.idea.blaze.base.model.primitives.Label;
+import com.intellij.openapi.extensions.impl.ExtensionPointImpl;
+import java.io.File;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link RuleNameHeuristic}. */
+@RunWith(JUnit4.class)
+public class RuleNameHeuristicTest extends BlazeTestCase {
+
+  @Override
+  protected void initTest(Container applicationServices, Container projectServices) {
+    super.initTest(applicationServices, projectServices);
+
+    ExtensionPointImpl<TestRuleHeuristic> ep =
+        registerExtensionPoint(TestRuleHeuristic.EP_NAME, TestRuleHeuristic.class);
+    ep.registerExtension(new RuleNameHeuristic());
+  }
+
+  @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();
+  }
+
+  @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();
+  }
+
+  @Test
+  public void testFilterNoMatchesFallBackToFirstRule() throws Exception {
+    File source = new File("java/com/foo/FooTest.java");
+    Collection<RuleIdeInfo> rules =
+        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);
+    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 =
+        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);
+    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 =
+        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);
+    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
new file mode 100644
index 0000000..f75be8a
--- /dev/null
+++ b/base/tests/unittests/com/google/idea/blaze/base/run/TestSizeHeuristicTest.java
@@ -0,0 +1,155 @@
+/*
+ * 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.idea.blaze.base.BlazeTestCase;
+import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+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;
+import com.intellij.openapi.extensions.impl.ExtensionPointImpl;
+import java.io.File;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link TestSizeHeuristic}. */
+@RunWith(JUnit4.class)
+public class TestSizeHeuristicTest extends BlazeTestCase {
+
+  @Override
+  protected void initTest(Container applicationServices, Container projectServices) {
+    super.initTest(applicationServices, projectServices);
+
+    ExtensionPointImpl<TestRuleHeuristic> ep =
+        registerExtensionPoint(TestRuleHeuristic.EP_NAME, TestRuleHeuristic.class);
+    ep.registerExtension(new TestSizeHeuristic());
+  }
+
+  @Test
+  public void testPredicateMatchingSize() throws Exception {
+    File source = new File("java/com/foo/FooTest.java");
+    RuleIdeInfo rule =
+        RuleIdeInfo.builder()
+            .setLabel("//foo:test")
+            .setKind("java_test")
+            .setTestInfo(TestIdeInfo.builder().setTestSize(TestSize.MEDIUM))
+            .build();
+    assertThat(new TestSizeHeuristic().matchesSource(rule, source, TestSize.MEDIUM)).isTrue();
+  }
+
+  @Test
+  public void testPredicateDifferentSize() throws Exception {
+    File source = new File("java/com/foo/FooTest.java");
+    RuleIdeInfo rule =
+        RuleIdeInfo.builder()
+            .setLabel("//foo:test")
+            .setKind("java_test")
+            .setTestInfo(TestIdeInfo.builder().setTestSize(TestSize.MEDIUM))
+            .build();
+    assertThat(new TestSizeHeuristic().matchesSource(rule, source, TestSize.SMALL)).isFalse();
+  }
+
+  @Test
+  public void testPredicateDefaultToSmallSize() throws Exception {
+    File source = new File("java/com/foo/FooTest.java");
+    RuleIdeInfo rule =
+        RuleIdeInfo.builder()
+            .setLabel("//foo:test")
+            .setKind("java_test")
+            .setTestInfo(TestIdeInfo.builder().setTestSize(TestSize.SMALL))
+            .build();
+    assertThat(new TestSizeHeuristic().matchesSource(rule, source, null)).isTrue();
+
+    rule =
+        RuleIdeInfo.builder()
+            .setLabel("//foo:test")
+            .setKind("java_test")
+            .setTestInfo(TestIdeInfo.builder().setTestSize(TestSize.MEDIUM))
+            .build();
+    assertThat(new TestSizeHeuristic().matchesSource(rule, source, null)).isFalse();
+  }
+
+  @Test
+  public void testFilterNoMatchesFallBackToFirstRule() throws Exception {
+    File source = new File("java/com/foo/FooTest.java");
+    ImmutableList<RuleIdeInfo> rules =
+        ImmutableList.of(
+            RuleIdeInfo.builder()
+                .setLabel("//foo:test1")
+                .setKind("java_test")
+                .setTestInfo(TestIdeInfo.builder().setTestSize(TestSize.MEDIUM))
+                .build(),
+            RuleIdeInfo.builder()
+                .setLabel("//foo:test2")
+                .setKind("java_test")
+                .setTestInfo(TestIdeInfo.builder().setTestSize(TestSize.LARGE))
+                .build(),
+            RuleIdeInfo.builder()
+                .setLabel("//foo:test3")
+                .setKind("java_test")
+                .setTestInfo(TestIdeInfo.builder().setTestSize(TestSize.ENORMOUS))
+                .build());
+    Label match = TestRuleHeuristic.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.of(
+            RuleIdeInfo.builder()
+                .setLabel("//foo:test1")
+                .setKind("java_test")
+                .setTestInfo(TestIdeInfo.builder().setTestSize(TestSize.MEDIUM))
+                .build(),
+            RuleIdeInfo.builder()
+                .setLabel("//foo:test2")
+                .setKind("java_test")
+                .setTestInfo(TestIdeInfo.builder().setTestSize(TestSize.SMALL))
+                .build());
+    Label match = TestRuleHeuristic.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.of(
+            RuleIdeInfo.builder()
+                .setLabel("//foo:test1")
+                .setKind("java_test")
+                .setTestInfo(TestIdeInfo.builder().setTestSize(TestSize.MEDIUM))
+                .build(),
+            RuleIdeInfo.builder()
+                .setLabel("//foo:test2")
+                .setKind("java_test")
+                .setTestInfo(TestIdeInfo.builder().setTestSize(TestSize.SMALL))
+                .build(),
+            RuleIdeInfo.builder()
+                .setLabel("//foo:test3")
+                .setKind("java_test")
+                .setTestInfo(TestIdeInfo.builder().setTestSize(TestSize.SMALL))
+                .build());
+    Label match = TestRuleHeuristic.chooseTestTargetForSourceFile(source, rules, TestSize.SMALL);
+    assertThat(match).isEqualTo(new Label("//foo:test2"));
+  }
+}
diff --git a/base/tests/unittests/com/google/idea/blaze/base/run/confighandler/BlazeCommandGenericRunConfigurationHandlerTest.java b/base/tests/unittests/com/google/idea/blaze/base/run/confighandler/BlazeCommandGenericRunConfigurationHandlerTest.java
new file mode 100644
index 0000000..64d3900
--- /dev/null
+++ b/base/tests/unittests/com/google/idea/blaze/base/run/confighandler/BlazeCommandGenericRunConfigurationHandlerTest.java
@@ -0,0 +1,158 @@
+/*
+ * 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.confighandler;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.BlazeTestCase;
+import com.google.idea.blaze.base.command.BlazeCommandName;
+import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
+import com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType;
+import com.google.idea.blaze.base.run.confighandler.BlazeCommandGenericRunConfigurationHandler.BlazeCommandGenericRunConfigurationHandlerEditor;
+import com.google.idea.blaze.base.settings.Blaze;
+import com.google.idea.blaze.base.settings.BlazeImportSettings;
+import com.google.idea.blaze.base.settings.BlazeImportSettingsManager;
+import com.intellij.ide.ui.UISettings;
+import com.intellij.openapi.options.ConfigurationException;
+import com.intellij.openapi.util.InvalidDataException;
+import org.jdom.Element;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link BlazeCommandGenericRunConfigurationHandler}. */
+@RunWith(JUnit4.class)
+public class BlazeCommandGenericRunConfigurationHandlerTest extends BlazeTestCase {
+  private static final BlazeImportSettings DUMMY_IMPORT_SETTINGS =
+      new BlazeImportSettings("", "", "", "", "", Blaze.BuildSystem.Blaze);
+  private static final BlazeCommandName COMMAND = BlazeCommandName.fromString("command");
+
+  private final BlazeCommandRunConfigurationType type = new BlazeCommandRunConfigurationType();
+  private BlazeCommandRunConfiguration configuration;
+  private BlazeCommandGenericRunConfigurationHandler handler;
+
+  @Override
+  protected void initTest(
+      @NotNull Container applicationServices, @NotNull Container projectServices) {
+    super.initTest(applicationServices, projectServices);
+
+    applicationServices.register(UISettings.class, new UISettings());
+    projectServices.register(
+        BlazeImportSettingsManager.class, new BlazeImportSettingsManager(project));
+    BlazeImportSettingsManager.getInstance(getProject()).setImportSettings(DUMMY_IMPORT_SETTINGS);
+
+    configuration = type.getFactory().createTemplateConfiguration(project);
+    handler = new BlazeCommandGenericRunConfigurationHandler(configuration);
+  }
+
+  @Test
+  public void readAndWriteShouldMatch() throws InvalidDataException {
+    handler.setCommand(COMMAND);
+    handler.setBlazeFlags(ImmutableList.of("--flag1", "--flag2"));
+    handler.setExeFlags(ImmutableList.of("--exeFlag1"));
+    handler.setBlazeBinary("/usr/bin/blaze");
+
+    Element element = new Element("test");
+    handler.writeExternal(element);
+    BlazeCommandRunConfiguration readConfiguration =
+        type.getFactory().createTemplateConfiguration(project);
+    BlazeCommandGenericRunConfigurationHandler readHandler =
+        new BlazeCommandGenericRunConfigurationHandler(readConfiguration);
+    readHandler.readExternal(element);
+
+    assertThat(readHandler.getCommand()).isEqualTo(COMMAND);
+    assertThat(readHandler.getAllBlazeFlags()).containsExactly("--flag1", "--flag2").inOrder();
+    assertThat(readHandler.getAllExeFlags()).containsExactly("--exeFlag1");
+    assertThat(readHandler.getBlazeBinary()).isEqualTo("/usr/bin/blaze");
+  }
+
+  @Test
+  public void readAndWriteShouldHandleNulls() throws InvalidDataException {
+    Element element = new Element("test");
+    handler.writeExternal(element);
+    BlazeCommandRunConfiguration readConfiguration =
+        type.getFactory().createTemplateConfiguration(project);
+    BlazeCommandGenericRunConfigurationHandler readHandler =
+        new BlazeCommandGenericRunConfigurationHandler(readConfiguration);
+    readHandler.readExternal(element);
+
+    assertThat(readHandler.getCommand()).isEqualTo(handler.getCommand());
+    assertThat(readHandler.getAllBlazeFlags()).isEqualTo(handler.getAllBlazeFlags());
+    assertThat(readHandler.getAllExeFlags()).isEqualTo(handler.getAllExeFlags());
+    assertThat(readHandler.getBlazeBinary()).isEqualTo(handler.getBlazeBinary());
+  }
+
+  @Test
+  public void readShouldOmitEmptyFlags() throws InvalidDataException {
+    handler.setBlazeFlags(Lists.newArrayList("hi ", "", "I'm", " ", "\t", "Josh\r\n", "\n"));
+    handler.setExeFlags(Lists.newArrayList("hi ", "", "I'm", " ", "\t", "Josh\r\n", "\n"));
+
+    Element element = new Element("test");
+    handler.writeExternal(element);
+    BlazeCommandRunConfiguration readConfiguration =
+        type.getFactory().createTemplateConfiguration(project);
+    BlazeCommandGenericRunConfigurationHandler readHandler =
+        new BlazeCommandGenericRunConfigurationHandler(readConfiguration);
+    readHandler.readExternal(element);
+
+    assertThat(readHandler.getAllBlazeFlags()).containsExactly("hi", "I'm", "Josh").inOrder();
+    assertThat(readHandler.getAllExeFlags()).containsExactly("hi", "I'm", "Josh").inOrder();
+  }
+
+  @Test
+  public void editorApplyToAndResetFromShouldMatch() throws ConfigurationException {
+    BlazeCommandGenericRunConfigurationHandlerEditor editor =
+        new BlazeCommandGenericRunConfigurationHandlerEditor(handler);
+
+    handler.setCommand(COMMAND);
+    handler.setBlazeFlags(ImmutableList.of("--flag1", "--flag2"));
+    handler.setExeFlags(ImmutableList.of("--exeFlag1", "--exeFlag2"));
+    handler.setBlazeBinary("/usr/bin/blaze");
+
+    editor.resetEditorFrom(handler);
+    BlazeCommandRunConfiguration readConfiguration =
+        type.getFactory().createTemplateConfiguration(project);
+    BlazeCommandGenericRunConfigurationHandler readHandler =
+        new BlazeCommandGenericRunConfigurationHandler(readConfiguration);
+    editor.applyEditorTo(readHandler);
+
+    assertThat(readHandler.getCommand()).isEqualTo(handler.getCommand());
+    assertThat(readHandler.getAllBlazeFlags()).isEqualTo(handler.getAllBlazeFlags());
+    assertThat(readHandler.getAllExeFlags()).isEqualTo(handler.getAllExeFlags());
+    assertThat(readHandler.getBlazeBinary()).isEqualTo(handler.getBlazeBinary());
+  }
+
+  @Test
+  public void editorApplyToAndResetFromShouldHandleNulls() throws ConfigurationException {
+    BlazeCommandGenericRunConfigurationHandlerEditor editor =
+        new BlazeCommandGenericRunConfigurationHandlerEditor(handler);
+
+    editor.resetEditorFrom(handler);
+    BlazeCommandRunConfiguration readConfiguration =
+        type.getFactory().createTemplateConfiguration(project);
+    BlazeCommandGenericRunConfigurationHandler readHandler =
+        new BlazeCommandGenericRunConfigurationHandler(readConfiguration);
+    editor.applyEditorTo(readHandler);
+
+    assertThat(readHandler.getCommand()).isEqualTo(handler.getCommand());
+    assertThat(readHandler.getAllBlazeFlags()).isEqualTo(handler.getAllBlazeFlags());
+    assertThat(readHandler.getAllExeFlags()).isEqualTo(handler.getAllExeFlags());
+    assertThat(readHandler.getBlazeBinary()).isEqualTo(handler.getBlazeBinary());
+  }
+}
diff --git a/base/tests/unittests/com/google/idea/blaze/base/run/confighandler/BlazeUnknownRunConfigurationHandlerTest.java b/base/tests/unittests/com/google/idea/blaze/base/run/confighandler/BlazeUnknownRunConfigurationHandlerTest.java
new file mode 100644
index 0000000..7752f26
--- /dev/null
+++ b/base/tests/unittests/com/google/idea/blaze/base/run/confighandler/BlazeUnknownRunConfigurationHandlerTest.java
@@ -0,0 +1,159 @@
+/*
+ * 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.confighandler;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.idea.blaze.base.BlazeTestCase;
+import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
+import com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType;
+import com.google.idea.blaze.base.settings.Blaze;
+import com.google.idea.blaze.base.settings.BlazeImportSettings;
+import com.google.idea.blaze.base.settings.BlazeImportSettingsManager;
+import com.intellij.ide.ui.UISettings;
+import com.intellij.openapi.util.InvalidDataException;
+import java.io.StringReader;
+import org.jdom.Element;
+import org.jdom.input.SAXBuilder;
+import org.jdom.output.Format;
+import org.jdom.output.XMLOutputter;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link BlazeUnknownRunConfigurationHandler}. */
+@RunWith(JUnit4.class)
+public class BlazeUnknownRunConfigurationHandlerTest extends BlazeTestCase {
+  private static final BlazeImportSettings DUMMY_IMPORT_SETTINGS =
+      new BlazeImportSettings("", "", "", "", "", Blaze.BuildSystem.Blaze);
+
+  private final BlazeCommandRunConfigurationType type = new BlazeCommandRunConfigurationType();
+  private BlazeCommandRunConfiguration configuration;
+  private BlazeUnknownRunConfigurationHandler handler;
+
+  @Override
+  protected void initTest(
+      @NotNull Container applicationServices, @NotNull Container projectServices) {
+    super.initTest(applicationServices, projectServices);
+
+    applicationServices.register(UISettings.class, new UISettings());
+    projectServices.register(
+        BlazeImportSettingsManager.class, new BlazeImportSettingsManager(project));
+    BlazeImportSettingsManager.getInstance(getProject()).setImportSettings(DUMMY_IMPORT_SETTINGS);
+
+    configuration = type.getFactory().createTemplateConfiguration(project);
+    handler = new BlazeUnknownRunConfigurationHandler(configuration);
+  }
+
+  @Test
+  public void readAndWriteShouldPreserveOldContent() throws Exception {
+    SAXBuilder saxBuilder = new SAXBuilder();
+    XMLOutputter xmlOutputter = new XMLOutputter(Format.getCompactFormat());
+
+    String inputXml =
+        "<?xml version=\"1.0\"?>"
+            + "<test foo=\"bar\" bar=\"baz\">"
+            + "  <child abc=\"def\">"
+            + "    <grandchild />"
+            + "  </child>"
+            + "  <child foo=\"baz\" />"
+            + "</test>";
+    Element element = saxBuilder.build(new StringReader(inputXml)).getRootElement();
+    handler.readExternal(element);
+
+    Element writeElement = new Element("test");
+    handler.writeExternal(writeElement);
+
+    assertThat(xmlOutputter.outputString(writeElement))
+        .isEqualTo(xmlOutputter.outputString(element));
+  }
+
+  @Test
+  public void readAndWriteShouldHandleEmptyElements() throws InvalidDataException {
+    //<test />
+    Element element = new Element("test");
+    handler.readExternal(element);
+
+    Element writeElement = new Element("test");
+    handler.writeExternal(writeElement);
+
+    assertThat(writeElement.getAttributes()).isEmpty();
+    assertThat(writeElement.getChildren()).isEmpty();
+  }
+
+  @Test
+  public void writeShouldPreserveNewContent() throws Exception {
+    SAXBuilder saxBuilder = new SAXBuilder();
+    XMLOutputter xmlOutputter = new XMLOutputter(Format.getCompactFormat());
+
+    //<test />
+    Element element = new Element("test");
+    handler.readExternal(element);
+
+    String newXml =
+        "<?xml version=\"1.0\"?>"
+            + "<test foo=\"bar\">"
+            + "  <child abc=\"def\" />"
+            + "  <child />"
+            + "</test>";
+    Element writeElement = saxBuilder.build(new StringReader(newXml)).getRootElement();
+    handler.writeExternal(writeElement);
+
+    Element newElement = saxBuilder.build(new StringReader(newXml)).getRootElement();
+    assertThat(xmlOutputter.outputString(writeElement))
+        .isEqualTo(xmlOutputter.outputString(newElement));
+  }
+
+  @Test
+  public void writeShouldMergeAndOverwriteOldContent() throws Exception {
+    SAXBuilder saxBuilder = new SAXBuilder();
+    XMLOutputter xmlOutputter = new XMLOutputter(Format.getCompactFormat());
+
+    String oldXml =
+        "<?xml version=\"1.0\"?>"
+            + "<test foo=\"old\" bar=\"old\">"
+            + "  <child abc=\"old\">"
+            + "    <grandchild />"
+            + "  </child>"
+            + "  <backup foo=\"baz\" />"
+            + "  <backup />"
+            + "</test>";
+    Element element = saxBuilder.build(new StringReader(oldXml)).getRootElement();
+    handler.readExternal(element);
+
+    String newXml =
+        "<?xml version=\"1.0\"?>"
+            + "<test foo=\"bar\">"
+            + "  <child abc=\"def\" />"
+            + "  <child />"
+            + "</test>";
+    Element writeElement = saxBuilder.build(new StringReader(newXml)).getRootElement();
+    handler.writeExternal(writeElement);
+
+    String mergedXml =
+        "<?xml version=\"1.0\"?>"
+            + "<test foo=\"bar\" bar=\"old\">"
+            + "  <child abc=\"def\" />"
+            + "  <child />"
+            + "  <backup foo=\"baz\" />"
+            + "  <backup />"
+            + "</test>";
+    Element mergedElement = saxBuilder.build(new StringReader(mergedXml)).getRootElement();
+    assertThat(xmlOutputter.outputString(writeElement))
+        .isEqualTo(xmlOutputter.outputString(mergedElement));
+  }
+}
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
new file mode 100644
index 0000000..acb1a01
--- /dev/null
+++ b/base/tests/unittests/com/google/idea/blaze/base/run/testmap/TestMapTest.java
@@ -0,0 +1,239 @@
+/*
+ * 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.testmap;
+
+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.RuleMapBuilder;
+import com.google.idea.blaze.base.model.RuleMap;
+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.common.experiments.ExperimentService;
+import com.google.idea.common.experiments.MockExperimentService;
+import java.io.File;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for the test map */
+@RunWith(JUnit4.class)
+public class TestMapTest extends BlazeTestCase {
+  private RuleMapBuilder ruleMapBuilder;
+
+  @Override
+  protected void initTest(
+      @NotNull Container applicationServices, @NotNull Container projectServices) {
+    super.initTest(applicationServices, projectServices);
+    applicationServices.register(ExperimentService.class, new MockExperimentService());
+    ruleMapBuilder = RuleMapBuilder.builder();
+  }
+
+  @Test
+  public void testTrivialTestMap() throws Exception {
+    RuleMap ruleMap =
+        ruleMapBuilder
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setBuildFile(sourceRoot("test/BUILD"))
+                    .setLabel("//test:test")
+                    .setKind("java_test")
+                    .addSource(sourceRoot("test/Test.java")))
+            .build();
+
+    TestMap testMap = new TestMap(project, ruleMap);
+    ImmutableMultimap<Label, Label> reverseDependencies =
+        ReverseDependencyMap.createRdepsMap(ruleMap);
+    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()
+                    .setBuildFile(sourceRoot("test/BUILD"))
+                    .setLabel("//test:test")
+                    .setKind("java_test")
+                    .addDependency("//test:lib"))
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setBuildFile(sourceRoot("test/BUILD"))
+                    .setLabel("//test:lib")
+                    .setKind("java_library")
+                    .addSource(sourceRoot("test/Test.java")))
+            .build();
+
+    TestMap testMap = new TestMap(project, ruleMap);
+    ImmutableMultimap<Label, Label> reverseDependencies =
+        ReverseDependencyMap.createRdepsMap(ruleMap);
+    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()
+                    .setBuildFile(sourceRoot("test/BUILD"))
+                    .setLabel("//test:test")
+                    .setKind("java_test")
+                    .addDependency("//test:lib"))
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setBuildFile(sourceRoot("test/BUILD"))
+                    .setLabel("//test:test2")
+                    .setKind("java_test")
+                    .addDependency("//test:lib"))
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setBuildFile(sourceRoot("test/BUILD"))
+                    .setLabel("//test:lib")
+                    .setKind("java_library")
+                    .addSource(sourceRoot("test/Test.java")))
+            .build();
+
+    TestMap testMap = new TestMap(project, ruleMap);
+    ImmutableMultimap<Label, Label> reverseDependencies =
+        ReverseDependencyMap.createRdepsMap(ruleMap);
+    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()
+                    .setBuildFile(sourceRoot("test/BUILD"))
+                    .setLabel("//test:lib")
+                    .setKind("java_library")
+                    .addSource(sourceRoot("test/Test.java")))
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setBuildFile(sourceRoot("test/BUILD"))
+                    .setLabel("//test:lib2")
+                    .setKind("java_library")
+                    .addDependency("//test:lib"))
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setBuildFile(sourceRoot("test/BUILD"))
+                    .setLabel("//test:test2")
+                    .setKind("java_test")
+                    .addDependency("//test:lib2"))
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setBuildFile(sourceRoot("test/BUILD"))
+                    .setLabel("//test:test")
+                    .setKind("java_test")
+                    .addDependency("//test:lib"))
+            .build();
+
+    TestMap testMap = new TestMap(project, ruleMap);
+    ImmutableMultimap<Label, Label> reverseDependencies =
+        ReverseDependencyMap.createRdepsMap(ruleMap);
+    assertThat(testMap.testTargetsForSourceFile(reverseDependencies, new File("/test/Test.java")))
+        .containsExactly(new Label("//test:test"), new Label("//test:test2"))
+        .inOrder();
+  }
+
+  @Test
+  public void testSourceIncludedMultipleTimesFindsAll() throws Exception {
+    RuleMap ruleMap =
+        ruleMapBuilder
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setBuildFile(sourceRoot("test/BUILD"))
+                    .setLabel("//test:test")
+                    .setKind("java_test")
+                    .addDependency("//test:lib"))
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setBuildFile(sourceRoot("test/BUILD"))
+                    .setLabel("//test:test2")
+                    .setKind("java_test")
+                    .addDependency("//test:lib2"))
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setBuildFile(sourceRoot("test/BUILD"))
+                    .setLabel("//test:lib")
+                    .setKind("java_library")
+                    .addSource(sourceRoot("test/Test.java")))
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setBuildFile(sourceRoot("test/BUILD"))
+                    .setLabel("//test:lib2")
+                    .setKind("java_library")
+                    .addSource(sourceRoot("test/Test.java")))
+            .build();
+
+    TestMap testMap = new TestMap(project, ruleMap);
+    ImmutableMultimap<Label, Label> reverseDependencies =
+        ReverseDependencyMap.createRdepsMap(ruleMap);
+    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()
+                    .setBuildFile(sourceRoot("test/BUILD"))
+                    .setLabel("//test:test")
+                    .setKind("java_test")
+                    .addDependency("//test:lib")
+                    .addDependency("//test:lib2"))
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setBuildFile(sourceRoot("test/BUILD"))
+                    .setLabel("//test:lib")
+                    .setKind("java_library")
+                    .addSource(sourceRoot("test/Test.java")))
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setBuildFile(sourceRoot("test/BUILD"))
+                    .setLabel("//test:lib2")
+                    .setKind("java_library")
+                    .addSource(sourceRoot("test/Test.java")))
+            .build();
+
+    TestMap testMap = new TestMap(project, ruleMap);
+    ImmutableMultimap<Label, Label> reverseDependencies =
+        ReverseDependencyMap.createRdepsMap(ruleMap);
+    assertThat(testMap.testTargetsForSourceFile(reverseDependencies, new File("/test/Test.java")))
+        .containsExactly(new Label("//test:test"));
+  }
+
+  private ArtifactLocation sourceRoot(String relativePath) {
+    return ArtifactLocation.builder()
+        .setRootPath("/")
+        .setRelativePath(relativePath)
+        .setIsSource(true)
+        .build();
+  }
+}
diff --git a/base/tests/unittests/com/google/idea/blaze/base/scope/BlazeContextTest.java b/base/tests/unittests/com/google/idea/blaze/base/scope/BlazeContextTest.java
new file mode 100644
index 0000000..eab311c
--- /dev/null
+++ b/base/tests/unittests/com/google/idea/blaze/base/scope/BlazeContextTest.java
@@ -0,0 +1,372 @@
+/*
+ * 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.scope;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.BlazeTestCase;
+import java.util.List;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link BlazeContext}. */
+@RunWith(JUnit4.class)
+public class BlazeContextTest extends BlazeTestCase {
+
+  @Test
+  public void testScopeBeginsWhenPushedToContext() {
+    BlazeContext context = new BlazeContext();
+    final BlazeScope scope = mock(BlazeScope.class);
+    context.push(scope);
+    verify(scope).onScopeBegin(context);
+  }
+
+  @Test
+  public void testScopeEndsWhenContextEnds() {
+    BlazeContext context = new BlazeContext();
+    final BlazeScope scope = mock(BlazeScope.class);
+    context.push(scope);
+    context.endScope();
+    verify(scope).onScopeEnd(context);
+  }
+
+  @Test
+  public void testEndingTwiceHasNoEffect() {
+    BlazeContext context = new BlazeContext();
+    final BlazeScope scope = mock(BlazeScope.class);
+    context.push(scope);
+    context.endScope();
+    context.endScope();
+    verify(scope).onScopeEnd(context);
+  }
+
+  @Test
+  public void testEndingScopeNormallyDoesntEndParent() {
+    BlazeContext parentContext = new BlazeContext();
+    BlazeContext childContext = new BlazeContext(parentContext);
+    childContext.endScope();
+    assertTrue(childContext.isEnding());
+    assertFalse(parentContext.isEnding());
+  }
+
+  @Test
+  public void testCancellingScopeCancelsParent() {
+    BlazeContext parentContext = new BlazeContext();
+    BlazeContext childContext = new BlazeContext(parentContext);
+    childContext.setCancelled();
+    assertTrue(childContext.isCancelled());
+    assertTrue(parentContext.isCancelled());
+  }
+
+  /** A simple scope that records its start and end by ID. */
+  static class RecordScope implements BlazeScope {
+    private final int id;
+    private final List<String> record;
+
+    public RecordScope(int id, List<String> record) {
+      this.id = id;
+      this.record = record;
+    }
+
+    @Override
+    public void onScopeBegin(@NotNull BlazeContext context) {
+      record.add("begin" + id);
+    }
+
+    @Override
+    public void onScopeEnd(@NotNull BlazeContext context) {
+      record.add("end" + id);
+    }
+  }
+
+  @Test
+  public void testScopesBeginAndEndInStackOrder() {
+    List<String> record = Lists.newArrayList();
+    BlazeContext context = new BlazeContext();
+    context
+        .push(new RecordScope(1, record))
+        .push(new RecordScope(2, record))
+        .push(new RecordScope(3, record));
+    context.endScope();
+    assertThat(record)
+        .isEqualTo(ImmutableList.of("begin1", "begin2", "begin3", "end3", "end2", "end1"));
+  }
+
+  @Test
+  public void testParentFoundInStackOrder() {
+    BlazeContext context = new BlazeContext();
+    BlazeScope scope1 = mock(BlazeScope.class);
+    BlazeScope scope2 = mock(BlazeScope.class);
+    BlazeScope scope3 = mock(BlazeScope.class);
+    context.push(scope1).push(scope2).push(scope3);
+    assertThat(context.getParentScope(scope3)).isEqualTo(scope2);
+    assertThat(context.getParentScope(scope2)).isEqualTo(scope1);
+    assertThat(context.getParentScope(scope1)).isNull();
+  }
+
+  @Test
+  public void testParentFoundInStackOrderAcrossContexts() {
+    BlazeContext parentContext = new BlazeContext();
+    BlazeContext childContext = new BlazeContext(parentContext);
+    BlazeScope scope1 = mock(BlazeScope.class);
+    BlazeScope scope2 = mock(BlazeScope.class);
+    BlazeScope scope3 = mock(BlazeScope.class);
+    parentContext.push(scope1).push(scope2);
+    childContext.push(scope3);
+    assertThat(childContext.getParentScope(scope3)).isEqualTo(scope2);
+  }
+
+  static class TestOutput1 implements Output {}
+
+  static class TestOutput2 implements Output {}
+
+  static class TestOutputSink<T extends Output> implements OutputSink<T> {
+    public boolean gotOutput;
+
+    @Override
+    public Propagation onOutput(@NotNull T output) {
+      gotOutput = true;
+      return Propagation.Continue;
+    }
+  }
+
+  static class TestOutputSink1 extends TestOutputSink<TestOutput1> {}
+
+  static class TestOutputSink2 extends TestOutputSink<TestOutput2> {}
+
+  @Test
+  public void testOutputGoesToRegisteredSink() {
+    BlazeContext context = new BlazeContext();
+    TestOutputSink1 sink = new TestOutputSink1();
+    context.addOutputSink(TestOutput1.class, sink);
+
+    assertFalse(sink.gotOutput);
+    context.output(new TestOutput1());
+    assertTrue(sink.gotOutput);
+  }
+
+  @Test
+  public void testOutputDoesntGoToWrongSink() {
+    BlazeContext context = new BlazeContext();
+    TestOutputSink2 sink = new TestOutputSink2();
+    context.addOutputSink(TestOutput2.class, sink);
+
+    assertFalse(sink.gotOutput);
+    context.output(new TestOutput1());
+    assertFalse(sink.gotOutput);
+  }
+
+  @Test
+  public void testOutputGoesToParentContexts() {
+    BlazeContext parentContext = new BlazeContext();
+    BlazeContext childContext = new BlazeContext(parentContext);
+    TestOutputSink1 sink = new TestOutputSink1();
+    parentContext.addOutputSink(TestOutput1.class, sink);
+
+    assertFalse(sink.gotOutput);
+    childContext.output(new TestOutput1());
+    assertTrue(sink.gotOutput);
+  }
+
+  @Test
+  public void testHoldingPreventsEndingContext() {
+    BlazeContext context = new BlazeContext();
+    context.hold();
+    context.endScope();
+    assertFalse(context.isEnding());
+    context.release();
+    assertTrue(context.isEnding());
+  }
+
+  private static class StringScope implements BlazeScope {
+
+    public final String str;
+
+    public StringScope(String s) {
+      this.str = s;
+    }
+
+    @Override
+    public void onScopeBegin(@NotNull BlazeContext context) {}
+
+    @Override
+    public void onScopeEnd(@NotNull BlazeContext context) {}
+  }
+
+  private static class CollectorScope implements BlazeScope {
+
+    public final List<String> output;
+
+    public CollectorScope(List<String> output) {
+      this.output = output;
+    }
+
+    @Override
+    public void onScopeBegin(@NotNull BlazeContext context) {}
+
+    @Override
+    public void onScopeEnd(@NotNull BlazeContext context) {
+      List<StringScope> scopes = context.getScopes(StringScope.class, this);
+      for (StringScope scope : scopes) {
+        output.add(scope.str);
+      }
+    }
+  }
+
+  @Test
+  public void testGetScopesOnlyReturnsScopesLowerOnTheStack() {
+    List<String> output1 = Lists.newArrayList();
+    List<String> output2 = Lists.newArrayList();
+    List<String> output3 = Lists.newArrayList();
+
+    BlazeContext context = new BlazeContext();
+    context.push(new StringScope("a"));
+    context.push(new StringScope("b"));
+    CollectorScope scope = new CollectorScope(output1);
+    context.push(scope);
+    context.push(new StringScope("c"));
+    context.push(new CollectorScope(output2));
+    context.push(new StringScope("d"));
+    context.push(new StringScope("e"));
+    context.push(new CollectorScope(output3));
+    context.endScope();
+
+    assertThat(output1).isEqualTo(ImmutableList.of("b", "a"));
+    assertThat(output2).isEqualTo(ImmutableList.of("c", "b", "a"));
+    assertThat(output3).isEqualTo(ImmutableList.of("e", "d", "c", "b", "a"));
+  }
+
+  @Test
+  public void testGetScopesOnlyReturnsScopesLowerOnTheStackForMultipleContexts() {
+    List<String> output1 = Lists.newArrayList();
+    List<String> output2 = Lists.newArrayList();
+    List<String> output3 = Lists.newArrayList();
+
+    BlazeContext context1 = new BlazeContext();
+    context1.push(new StringScope("a"));
+    context1.push(new StringScope("b"));
+    CollectorScope scope = new CollectorScope(output1);
+    context1.push(scope);
+
+    BlazeContext context2 = new BlazeContext(context1);
+    context2.push(new StringScope("c"));
+    context2.push(new CollectorScope(output2));
+    context2.push(new StringScope("d"));
+    context2.push(new StringScope("e"));
+
+    BlazeContext context3 = new BlazeContext(context2);
+    context3.push(new CollectorScope(output3));
+    context3.endScope();
+    context2.endScope();
+    context1.endScope();
+
+    assertThat(output1).isEqualTo(ImmutableList.of("b", "a"));
+    assertThat(output2).isEqualTo(ImmutableList.of("c", "b", "a"));
+    assertThat(output3).isEqualTo(ImmutableList.of("e", "d", "c", "b", "a"));
+  }
+
+  @Test
+  public void testGetScopesOnlyReturnsScopesIfStartingScopeInContext() {
+    List<String> output1 = Lists.newArrayList();
+
+    BlazeContext context1 = new BlazeContext();
+    context1.push(new StringScope("a"));
+    context1.push(new StringScope("b"));
+    CollectorScope scope = new CollectorScope(output1);
+    context1.push(scope);
+
+    BlazeContext context2 = new BlazeContext(context1);
+    context2.push(new StringScope("c"));
+
+    List<StringScope> scopes = context2.getScopes(StringScope.class, scope);
+    assertThat(scopes).isEqualTo(ImmutableList.of());
+  }
+
+  @Test
+  public void testGetScopesIncludesStartingScope() {
+    BlazeContext context1 = new BlazeContext();
+    StringScope a = new StringScope("a");
+    context1.push(a);
+    StringScope b = new StringScope("b");
+    context1.push(b);
+
+    List<StringScope> scopes = context1.getScopes(StringScope.class, b);
+    assertThat(scopes).isEqualTo(ImmutableList.of(b, a));
+  }
+
+  @Test
+  public void testGetScopesIndexIsNoninclusive() {
+    BlazeContext context1 = new BlazeContext();
+    StringScope scopeA = new StringScope("a");
+    context1.push(scopeA);
+    StringScope scopeB = new StringScope("b");
+    context1.push(scopeB);
+
+    List<StringScope> scopes = Lists.newArrayList();
+    context1.getScopes(scopes, StringScope.class, 1);
+    assertThat(scopes).isEqualTo(ImmutableList.of(scopeA));
+  }
+
+  @Test
+  public void testGetScopesWithoutStartScopeGetsAll() {
+    BlazeContext context1 = new BlazeContext();
+    StringScope a = new StringScope("a");
+    context1.push(a);
+    StringScope b = new StringScope("b");
+    context1.push(b);
+
+    List<StringScope> scopes = context1.getScopes(StringScope.class);
+    assertThat(scopes).isEqualTo(ImmutableList.of(b, a));
+  }
+
+  static class NonPropagatingOutputSink implements OutputSink<TestOutput1> {
+    boolean gotOutput;
+
+    @Override
+    public Propagation onOutput(@NotNull TestOutput1 output) {
+      this.gotOutput = true;
+      return Propagation.Stop;
+    }
+  }
+
+  @Test
+  public void testOutputIsTerminatedByFirstSink() {
+    NonPropagatingOutputSink sink1 = new NonPropagatingOutputSink();
+    NonPropagatingOutputSink sink2 = new NonPropagatingOutputSink();
+    NonPropagatingOutputSink sink3 = new NonPropagatingOutputSink();
+
+    BlazeContext context1 = new BlazeContext();
+    context1.addOutputSink(TestOutput1.class, sink1);
+
+    BlazeContext context2 = new BlazeContext(context1);
+    context2.addOutputSink(TestOutput1.class, sink2);
+    context2.addOutputSink(TestOutput1.class, sink3);
+
+    context2.output(new TestOutput1());
+
+    assertThat(sink1.gotOutput).isFalse();
+    assertThat(sink2.gotOutput).isFalse();
+    assertThat(sink3.gotOutput).isTrue();
+  }
+}
diff --git a/base/tests/unittests/com/google/idea/blaze/base/scope/ScopeTest.java b/base/tests/unittests/com/google/idea/blaze/base/scope/ScopeTest.java
new file mode 100644
index 0000000..7039030
--- /dev/null
+++ b/base/tests/unittests/com/google/idea/blaze/base/scope/ScopeTest.java
@@ -0,0 +1,118 @@
+/*
+ * 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.scope;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import com.google.idea.blaze.base.BlazeTestCase;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link Scope}. */
+@RunWith(JUnit4.class)
+public class ScopeTest extends BlazeTestCase {
+
+  @Test
+  public void testScopedOperationRuns() {
+    final boolean[] ran = new boolean[1];
+    Scope.root(
+        new ScopedOperation() {
+          @Override
+          public void execute(@NotNull BlazeContext context) {
+            ran[0] = true;
+          }
+        });
+    assertTrue(ran[0]);
+  }
+
+  @Test
+  public void testScopedFunctionReturnsValue() {
+    String result =
+        Scope.root(
+            new ScopedFunction<String>() {
+              @Override
+              public String execute(@NotNull BlazeContext context) {
+                return "test";
+              }
+            });
+    assertThat(result).isEqualTo("test");
+  }
+
+  @Test
+  public void testScopedOperationEndsContext() {
+    final BlazeScope scope = mock(BlazeScope.class);
+    Scope.root(
+        new ScopedOperation() {
+          @Override
+          public void execute(@NotNull BlazeContext context) {
+            context.push(scope);
+          }
+        });
+    verify(scope).onScopeEnd(any(BlazeContext.class));
+  }
+
+  @Test
+  public void testScopedFunctionEndsContext() {
+    final BlazeScope scope = mock(BlazeScope.class);
+    Scope.root(
+        new ScopedFunction<String>() {
+          @Override
+          public String execute(@NotNull BlazeContext context) {
+            context.push(scope);
+            return "";
+          }
+        });
+    verify(scope).onScopeEnd(any(BlazeContext.class));
+  }
+
+  /*
+  @Test
+  public void testThrowingExceptionEndsScopedOperationWithFailure() {
+    final RuntimeException e = new RuntimeException();
+    final BlazeScope scope = mock(BlazeScope.class);
+    Throwable throwable = Scope.root(project, new ScopedOperation() {
+      @Override
+      public void execute(@NotNull BlazeContext context) {
+        context.push(scope);
+        throw e;
+      }
+    }).throwable;
+    verify(scope).onScopeEnd(any(BlazeContext.class));
+    assertThat(e).isEqualTo(throwable);
+  }
+
+  @Test
+  public void testThrowingExceptionEndsScopeFunctionWithFailure() {
+    final RuntimeException e = new RuntimeException();
+    final BlazeScope scope = mock(BlazeScope.class);
+    Throwable throwable = Scope.root(project, new ScopedFunction<String>() {
+      @Override
+      public String execute(@NotNull BlazeContext context) {
+        context.push(scope);
+        throw e;
+      }
+    }).throwable;
+    verify(scope).onScopeEnd(any(BlazeContext.class));
+    assertThat(e).isEqualTo(throwable);
+  }
+  */
+}
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
new file mode 100644
index 0000000..33f2ef4
--- /dev/null
+++ b/base/tests/unittests/com/google/idea/blaze/base/sync/LanguageSupportTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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;
+
+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.ScalarSection;
+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.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;
+
+/** Test cases for {@link LanguageSupport} */
+@RunWith(JUnit4.class)
+public class LanguageSupportTest extends BlazeTestCase {
+  private ErrorCollector errorCollector = new ErrorCollector();
+  private BlazeContext context;
+  private ExtensionPointImpl<BlazeSyncPlugin> syncPlugins;
+
+  @Override
+  protected void initTest(
+      @NotNull Container applicationServices, @NotNull Container projectServices) {
+    super.initTest(applicationServices, projectServices);
+
+    syncPlugins = registerExtensionPoint(BlazeSyncPlugin.EP_NAME, BlazeSyncPlugin.class);
+
+    context = new BlazeContext();
+    context.addOutputSink(IssueOutput.class, errorCollector);
+  }
+
+  @Test
+  public void testSimpleCase() {
+    syncPlugins.registerExtension(
+        new BlazeSyncPlugin.Adapter() {
+          @Override
+          public Set<LanguageClass> getSupportedLanguagesInWorkspace(WorkspaceType workspaceType) {
+            return ImmutableSet.of(LanguageClass.C);
+          }
+        });
+
+    ProjectViewSet projectViewSet =
+        ProjectViewSet.builder()
+            .add(
+                ProjectView.builder()
+                    .add(ScalarSection.builder(WorkspaceTypeSection.KEY).set(WorkspaceType.C))
+                    .build())
+            .build();
+    WorkspaceLanguageSettings workspaceLanguageSettings =
+        LanguageSupport.createWorkspaceLanguageSettings(context, projectViewSet);
+    errorCollector.assertNoIssues();
+    assertThat(workspaceLanguageSettings)
+        .isEqualTo(
+            new WorkspaceLanguageSettings(WorkspaceType.C, ImmutableSet.of(LanguageClass.C)));
+  }
+
+  @Test
+  public void testFailWithUnsupportedLanguage() {
+    ProjectViewSet projectViewSet =
+        ProjectViewSet.builder()
+            .add(
+                ProjectView.builder()
+                    .add(ScalarSection.builder(WorkspaceTypeSection.KEY).set(WorkspaceType.C))
+                    .build())
+            .build();
+    LanguageSupport.createWorkspaceLanguageSettings(context, projectViewSet);
+    errorCollector.assertIssues(
+        "Language 'c' is not supported for this plugin with workspace type: 'c'");
+  }
+
+  /** Tests that we ask for java and android when the workspace type is android. */
+  @Test
+  public void testWorkspaceTypeImpliesLanguages() {
+    syncPlugins.registerExtension(
+        new BlazeSyncPlugin.Adapter() {
+          @Override
+          public Set<LanguageClass> getSupportedLanguagesInWorkspace(WorkspaceType workspaceType) {
+            return ImmutableSet.of(LanguageClass.ANDROID, LanguageClass.JAVA, LanguageClass.C);
+          }
+        });
+
+    ProjectViewSet projectViewSet =
+        ProjectViewSet.builder()
+            .add(
+                ProjectView.builder()
+                    .add(ScalarSection.builder(WorkspaceTypeSection.KEY).set(WorkspaceType.ANDROID))
+                    .build())
+            .build();
+    WorkspaceLanguageSettings workspaceLanguageSettings =
+        LanguageSupport.createWorkspaceLanguageSettings(context, projectViewSet);
+    assertThat(workspaceLanguageSettings)
+        .isEqualTo(
+            new WorkspaceLanguageSettings(
+                WorkspaceType.ANDROID, ImmutableSet.of(LanguageClass.JAVA, LanguageClass.ANDROID)));
+  }
+}
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
new file mode 100644
index 0000000..24a6200
--- /dev/null
+++ b/base/tests/unittests/com/google/idea/blaze/base/sync/aspects/BlazeIdeInterfaceAspectsImplTest.java
@@ -0,0 +1,124 @@
+/*
+ * 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.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+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.io.FileAttributeProvider;
+import com.google.idea.blaze.base.model.RuleMap;
+import com.google.idea.blaze.base.model.primitives.ExecutionRootPath;
+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.WorkspaceRoot;
+import com.google.idea.blaze.base.model.primitives.WorkspaceType;
+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;
+import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolverImpl;
+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 java.io.File;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link BlazeIdeInterfaceAspectsImpl}. */
+@RunWith(JUnit4.class)
+public class BlazeIdeInterfaceAspectsImplTest extends BlazeTestCase {
+
+  private static final File DUMMY_ROOT = new File("/");
+  private static final WorkspaceRoot WORKSPACE_ROOT = new WorkspaceRoot(DUMMY_ROOT);
+  private static final BlazeRoots BLAZE_ROOTS =
+      new BlazeRoots(
+          DUMMY_ROOT,
+          ImmutableList.of(DUMMY_ROOT),
+          new ExecutionRootPath("out/crosstool/bin"),
+          new ExecutionRootPath("out/crosstool/gen"));
+  private static final ArtifactLocationDecoder DUMMY_DECODER =
+      new ArtifactLocationDecoder(
+          BLAZE_ROOTS, new WorkspacePathResolverImpl(WORKSPACE_ROOT, BLAZE_ROOTS));
+
+  @Override
+  protected void initTest(
+      @NotNull Container applicationServices, @NotNull Container projectServices) {
+    super.initTest(applicationServices, projectServices);
+    applicationServices.register(ExperimentService.class, new MockExperimentService());
+    applicationServices.register(FileAttributeProvider.class, new FileAttributeProvider());
+  }
+
+  @Test
+  public void testRuleIdeInfoIsSerializable() {
+    AndroidStudioIdeInfo.RuleIdeInfo ideProto =
+        AndroidStudioIdeInfo.RuleIdeInfo.newBuilder()
+            .setLabel("//test:test")
+            .setKindString("android_binary")
+            .addDependencies("//test:dep")
+            .addTags("tag")
+            .setJavaRuleIdeInfo(
+                AndroidStudioIdeInfo.JavaRuleIdeInfo.newBuilder()
+                    .addJars(
+                        AndroidStudioIdeInfo.LibraryArtifact.newBuilder()
+                            .setJar(artifactLocation("jar.jar"))
+                            .build())
+                    .addGeneratedJars(
+                        AndroidStudioIdeInfo.LibraryArtifact.newBuilder()
+                            .setJar(artifactLocation("jar.jar"))
+                            .build())
+                    .addSources(artifactLocation("source.java")))
+            .setAndroidRuleIdeInfo(
+                AndroidStudioIdeInfo.AndroidRuleIdeInfo.newBuilder()
+                    .addResources(artifactLocation("res"))
+                    .setApk(artifactLocation("apk"))
+                    .addDependencyApk(artifactLocation("apk"))
+                    .setJavaPackage("package"))
+            .build();
+
+    WorkspaceLanguageSettings workspaceLanguageSettings =
+        new WorkspaceLanguageSettings(
+            WorkspaceType.ANDROID, ImmutableSet.of(LanguageClass.ANDROID));
+    RuleIdeInfo ruleIdeInfo =
+        IdeInfoFromProtobuf.makeRuleIdeInfo(workspaceLanguageSettings, DUMMY_DECODER, ideProto);
+    TestUtils.assertIsSerializable(ruleIdeInfo);
+  }
+
+  @Test
+  public void testBlazeStateIsSerializable() {
+    BlazeIdeInterfaceAspectsImpl.State state = new BlazeIdeInterfaceAspectsImpl.State();
+    state.fileToLabel = ImmutableMap.of(new File("fileName"), new Label("//java/com/test:test"));
+    state.fileState = ImmutableMap.of();
+    state.ruleMap =
+        new RuleMap(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();
+  }
+}
diff --git a/base/tests/unittests/com/google/idea/blaze/base/sync/aspects/UnfilteredCompilerOptionsTest.java b/base/tests/unittests/com/google/idea/blaze/base/sync/aspects/UnfilteredCompilerOptionsTest.java
new file mode 100644
index 0000000..fb09872
--- /dev/null
+++ b/base/tests/unittests/com/google/idea/blaze/base/sync/aspects/UnfilteredCompilerOptionsTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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 static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.BlazeTestCase;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for any unfiltered compiler options */
+@RunWith(JUnit4.class)
+public class UnfilteredCompilerOptionsTest extends BlazeTestCase {
+  @Test
+  public void testUnfilteredOptionsParsingForISystemOptions() {
+    ImmutableList<String> unfilteredOptions =
+        ImmutableList.of(
+            "-isystem",
+            "sys/inc1",
+            "-VER2",
+            "-isystem",
+            "sys2/inc1",
+            "-isystem",
+            "sys3/inc1",
+            "-isystm",
+            "sys4/inc1");
+    List<String> sysIncludes = Lists.newArrayList();
+    List<String> flags = Lists.newArrayList();
+    UnfilteredCompilerOptions.splitUnfilteredCompilerOptions(unfilteredOptions, sysIncludes, flags);
+
+    assertThat(sysIncludes).containsExactly("sys/inc1", "sys2/inc1", "sys3/inc1");
+
+    assertThat(flags).containsExactly("-VER2", "-isystm", "sys4/inc1");
+  }
+
+  @Test
+  public void testUnfilteredOptionsParsingForISystemOptionsNoSpaceAfterIsystem() {
+    ImmutableList<String> unfilteredOptions =
+        ImmutableList.of(
+            "-isystem", "sys/inc1", "-VER2", "-isystemsys2/inc1", "-isystem", "sys3/inc1");
+    List<String> sysIncludes = Lists.newArrayList();
+    List<String> flags = Lists.newArrayList();
+    UnfilteredCompilerOptions.splitUnfilteredCompilerOptions(unfilteredOptions, sysIncludes, flags);
+
+    assertThat(sysIncludes).containsExactly("sys/inc1", "sys2/inc1", "sys3/inc1");
+
+    assertThat(flags).containsExactly("-VER2");
+  }
+}
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
new file mode 100644
index 0000000..c6ea540
--- /dev/null
+++ b/base/tests/unittests/com/google/idea/blaze/base/sync/workspace/ArtifactLocationDecoderTest.java
@@ -0,0 +1,214 @@
+/*
+ * 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.workspace;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.idea.blaze.base.BlazeTestCase;
+import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
+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.repackaged.devtools.build.lib.ideinfo.androidstudio.AndroidStudioIdeInfo;
+import com.google.repackaged.devtools.build.lib.ideinfo.androidstudio.PackageManifestOuterClass;
+import java.io.File;
+import java.util.List;
+import java.util.Set;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Test cases for {@link ArtifactLocationDecoder}. */
+@RunWith(JUnit4.class)
+public class ArtifactLocationDecoderTest extends BlazeTestCase {
+
+  private static final WorkspaceRoot WORKSPACE_ROOT = new WorkspaceRoot(new File("/path/to/root"));
+  private static final String EXECUTION_ROOT = "/path/to/_blaze_user/1234bf129e/root";
+
+  private static final BlazeRoots BLAZE_GIT5_ROOTS =
+      new BlazeRoots(
+          new File(EXECUTION_ROOT),
+          ImmutableList.of(
+              WORKSPACE_ROOT.directory(),
+              new File(WORKSPACE_ROOT.directory().getParentFile(), "READONLY/root")),
+          new ExecutionRootPath("root/blaze-out/crosstool/bin"),
+          new ExecutionRootPath("root/blaze-out/crosstool/genfiles"));
+
+  private static final BlazeRoots BLAZE_CITC_ROOTS =
+      new BlazeRoots(
+          new File(EXECUTION_ROOT),
+          ImmutableList.of(WORKSPACE_ROOT.directory()),
+          new ExecutionRootPath("root/blaze-out/crosstool/bin"),
+          new ExecutionRootPath("root/blaze-out/crosstool/genfiles"));
+
+  static class MockFileAttributeProvider extends FileAttributeProvider {
+    final Set<File> files = Sets.newHashSet();
+
+    void addFiles(@NotNull File... files) {
+      this.files.addAll(Lists.newArrayList(files));
+    }
+
+    @Override
+    public boolean exists(@NotNull File file) {
+      return files.contains(file);
+    }
+  }
+
+  private MockFileAttributeProvider fileChecker;
+
+  @Override
+  protected void initTest(
+      @NotNull Container applicationServices, @NotNull Container projectServices) {
+    super.initTest(applicationServices, projectServices);
+
+    fileChecker = new MockFileAttributeProvider();
+    applicationServices.register(FileAttributeProvider.class, fileChecker);
+  }
+
+  @Test
+  public void testManualPackagePaths() throws Exception {
+    List<File> packagePaths =
+        ImmutableList.of(
+            WORKSPACE_ROOT.directory(),
+            new File(WORKSPACE_ROOT.directory().getParentFile(), "READONLY/root"),
+            new File(WORKSPACE_ROOT.directory().getParentFile(), "CUSTOM/root"));
+
+    BlazeRoots blazeRoots =
+        new BlazeRoots(
+            new File(EXECUTION_ROOT),
+            packagePaths,
+            new ExecutionRootPath("root/blaze-out/crosstool/bin"),
+            new ExecutionRootPath("root/blaze-out/crosstool/genfiles"));
+
+    fileChecker.addFiles(
+        new File(packagePaths.get(0), "com/google/Bla.java"),
+        new File(packagePaths.get(1), "com/google/Foo.java"),
+        new File(packagePaths.get(2), "com/other/Test.java"));
+
+    ArtifactLocationDecoder decoder =
+        new ArtifactLocationDecoder(
+            blazeRoots, new WorkspacePathResolverImpl(WORKSPACE_ROOT, blazeRoots));
+
+    ArtifactLocationBuilder builder =
+        new ArtifactLocationBuilder().setRelativePath("com/google/Bla.java").setIsSource(true);
+
+    assertThat(decoder.decode(builder.buildIdeInfoArtifact()).getRootPath())
+        .isEqualTo(packagePaths.get(0).toString());
+
+    builder.setRelativePath("com/google/Foo.java");
+
+    assertThat(decoder.decode(builder.buildIdeInfoArtifact()).getRootPath())
+        .isEqualTo(packagePaths.get(1).toString());
+
+    builder.setRelativePath("com/other/Test.java");
+
+    assertThat(decoder.decode(builder.buildIdeInfoArtifact()).getRootPath())
+        .isEqualTo(packagePaths.get(2).toString());
+
+    builder.setRelativePath("third_party/other/Temp.java");
+
+    assertThat(decoder.decode(builder.buildIdeInfoArtifact())).isNull();
+  }
+
+  @Test
+  public void testDerivedArtifact() throws Exception {
+    ArtifactLocationBuilder builder =
+        new ArtifactLocationBuilder()
+            .setRootExecutionPathFragment("/blaze-out/bin")
+            .setRelativePath("com/google/Bla.java")
+            .setIsSource(false);
+
+    ArtifactLocationDecoder decoder = new ArtifactLocationDecoder(BLAZE_CITC_ROOTS, null);
+
+    ArtifactLocation parsed = decoder.decode(builder.buildIdeInfoArtifact());
+
+    assertThat(parsed).isEqualTo(decoder.decode(builder.buildManifestArtifact()));
+
+    assertThat(parsed)
+        .isEqualTo(
+            ArtifactLocation.builder()
+                .setRootPath(EXECUTION_ROOT + "/blaze-out/bin")
+                .setRootExecutionPathFragment("/blaze-out/bin")
+                .setRelativePath("com/google/Bla.java")
+                .setIsSource(false)
+                .build());
+  }
+
+  @Test
+  public void testSourceArtifactAllVersions() throws Exception {
+    ArtifactLocationBuilder builder =
+        new ArtifactLocationBuilder().setRelativePath("com/google/Bla.java").setIsSource(true);
+
+    ArtifactLocationDecoder decoder =
+        new ArtifactLocationDecoder(
+            BLAZE_CITC_ROOTS, new WorkspacePathResolverImpl(WORKSPACE_ROOT, BLAZE_CITC_ROOTS));
+
+    ArtifactLocation parsed = decoder.decode(builder.buildIdeInfoArtifact());
+
+    assertThat(parsed).isEqualTo(decoder.decode(builder.buildManifestArtifact()));
+
+    assertThat(parsed)
+        .isEqualTo(
+            ArtifactLocation.builder()
+                .setRootPath(WORKSPACE_ROOT.toString())
+                .setRelativePath("com/google/Bla.java")
+                .setIsSource(true)
+                .build());
+  }
+
+  static class ArtifactLocationBuilder {
+    String rootExecutionPathFragment = "";
+    String relativePath;
+    boolean isSource;
+
+    ArtifactLocationBuilder setRootExecutionPathFragment(String rootExecutionPathFragment) {
+      this.rootExecutionPathFragment = rootExecutionPathFragment;
+      return this;
+    }
+
+    ArtifactLocationBuilder setRelativePath(String relativePath) {
+      this.relativePath = relativePath;
+      return this;
+    }
+
+    ArtifactLocationBuilder setIsSource(boolean isSource) {
+      this.isSource = isSource;
+      return this;
+    }
+
+    AndroidStudioIdeInfo.ArtifactLocation buildIdeInfoArtifact() {
+      AndroidStudioIdeInfo.ArtifactLocation.Builder builder =
+          AndroidStudioIdeInfo.ArtifactLocation.newBuilder()
+              .setIsSource(isSource)
+              .setRelativePath(relativePath);
+      builder.setRootExecutionPathFragment(rootExecutionPathFragment);
+      return builder.build();
+    }
+
+    PackageManifestOuterClass.ArtifactLocation buildManifestArtifact() {
+      PackageManifestOuterClass.ArtifactLocation.Builder builder =
+          PackageManifestOuterClass.ArtifactLocation.newBuilder()
+              .setIsSource(isSource)
+              .setRelativePath(relativePath);
+      builder.setRootExecutionPathFragment(rootExecutionPathFragment);
+      return builder.build();
+    }
+  }
+}
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
new file mode 100644
index 0000000..13b71f5
--- /dev/null
+++ b/base/tests/unittests/com/google/idea/blaze/base/sync/workspace/WorkspacePathResolverImplTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.workspace;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.base.BlazeTestCase;
+import com.google.idea.blaze.base.model.primitives.ExecutionRootPath;
+import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
+import java.io.File;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for workspace path resolver */
+@RunWith(JUnit4.class)
+public class WorkspacePathResolverImplTest extends BlazeTestCase {
+  private static final WorkspaceRoot WORKSPACE_ROOT = new WorkspaceRoot(new File("/path/to/root"));
+  private static final String EXECUTION_ROOT = "/path/to/_blaze_user/1234bf129e/root";
+
+  private static final BlazeRoots BLAZE_CITC_ROOTS =
+      new BlazeRoots(
+          new File(EXECUTION_ROOT),
+          ImmutableList.of(WORKSPACE_ROOT.directory()),
+          new ExecutionRootPath("blaze-out/crosstool/bin"),
+          new ExecutionRootPath("blaze-out/crosstool/genfiles"));
+
+  @Test
+  public void testResolveToIncludeDirectories() {
+    WorkspacePathResolver workspacePathResolver =
+        new WorkspacePathResolverImpl(WORKSPACE_ROOT, BLAZE_CITC_ROOTS);
+    ImmutableList<File> files =
+        workspacePathResolver.resolveToIncludeDirectories(new ExecutionRootPath("tools/fast"));
+    assertThat(files).containsExactly(new File("/path/to/root/tools/fast"));
+  }
+
+  @Test
+  public void testResolveToIncludeDirectoriesForExecRootPath() {
+    WorkspacePathResolver workspacePathResolver =
+        new WorkspacePathResolverImpl(WORKSPACE_ROOT, BLAZE_CITC_ROOTS);
+    ImmutableList<File> files =
+        workspacePathResolver.resolveToIncludeDirectories(
+            new ExecutionRootPath("blaze-out/crosstool/bin/tools/fast"));
+    assertThat(files).containsExactly(new File("/path/to/root/blaze-out/crosstool/bin/tools/fast"));
+  }
+}
diff --git a/base/tests/unittests/com/google/idea/blaze/base/vcs/git/GitStatusLineProcessorTest.java b/base/tests/unittests/com/google/idea/blaze/base/vcs/git/GitStatusLineProcessorTest.java
new file mode 100644
index 0000000..06cbe96
--- /dev/null
+++ b/base/tests/unittests/com/google/idea/blaze/base/vcs/git/GitStatusLineProcessorTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.vcs.git;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
+import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
+import java.io.File;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link GitStatusLineProcessor} */
+@RunWith(JUnit4.class)
+public class GitStatusLineProcessorTest {
+
+  @Test
+  public void testGitStatusParser() {
+    GitStatusLineProcessor lineProcessor =
+        new GitStatusLineProcessor(new WorkspaceRoot(new File("/usr/blah")), "/usr/blah");
+    for (String line :
+        ImmutableList.of(
+            "D    root/README",
+            "M    root/blaze-base/src/com/google/idea/blaze/base/root/citc/CitcUtil.java",
+            "A    root/blah",
+            "A    java/com/google/Test.java",
+            "M    java/com/other/")) {
+      lineProcessor.processLine(line);
+    }
+    assertThat(lineProcessor.addedFiles)
+        .containsExactly(
+            new WorkspacePath("root/blah"), new WorkspacePath("java/com/google/Test.java"));
+    assertThat(lineProcessor.modifiedFiles)
+        .containsExactly(
+            new WorkspacePath(
+                "root/blaze-base/src/com/google/idea/blaze/base/root/citc/CitcUtil.java"),
+            new WorkspacePath("java/com/other"));
+    assertThat(lineProcessor.deletedFiles).containsExactly(new WorkspacePath("root/README"));
+  }
+
+  @Test
+  public void testGitStatusParserDifferentRoots() {
+    GitStatusLineProcessor lineProcessor =
+        new GitStatusLineProcessor(new WorkspaceRoot(new File("/usr/blah/root")), "/usr/blah");
+    for (String line :
+        ImmutableList.of(
+            "D    root/README",
+            "M    root/blaze-base/src/com/google/idea/blaze/base/root/citc/CitcUtil.java",
+            "A    root/blah",
+            "A    java/com/google/Test.java",
+            "M    java/com/other/")) {
+      lineProcessor.processLine(line);
+    }
+    assertThat(lineProcessor.addedFiles).containsExactly(new WorkspacePath("blah"));
+    assertThat(lineProcessor.modifiedFiles)
+        .containsExactly(
+            new WorkspacePath("blaze-base/src/com/google/idea/blaze/base/root/citc/CitcUtil.java"));
+    assertThat(lineProcessor.deletedFiles).containsExactly(new WorkspacePath("README"));
+  }
+}
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
new file mode 100644
index 0000000..06596be
--- /dev/null
+++ b/base/tests/utils/integration/com/google/idea/blaze/base/BlazeIntegrationTestCase.java
@@ -0,0 +1,497 @@
+/*
+ * 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.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.WorkspaceRoot;
+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;
+import com.google.idea.blaze.base.sync.BlazeSyncPlugin;
+import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
+import com.google.idea.blaze.base.sync.projectstructure.ModuleEditorProvider;
+import com.intellij.codeInsight.lookup.Lookup;
+import com.intellij.codeInsight.lookup.LookupElement;
+import com.intellij.codeInsight.lookup.LookupElementPresentation;
+import com.intellij.ide.plugins.PluginManagerCore;
+import com.intellij.openapi.actionSystem.IdeActions;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.PathManager;
+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.ExtensionPoint;
+import com.intellij.openapi.extensions.ExtensionPointName;
+import com.intellij.openapi.extensions.Extensions;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.projectRoots.ProjectJdkTable;
+import com.intellij.openapi.util.Disposer;
+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.picocontainer.MutablePicoContainer;
+
+/** Base test class for blaze integration tests. */
+public abstract class BlazeIntegrationTestCase extends UsefulTestCase {
+
+  private static final LightProjectDescriptor projectDescriptor =
+      LightCodeInsightFixtureTestCase.JAVA_8;
+
+  private static boolean isRunThroughBlaze() {
+    return System.getenv("JAVA_RUNFILES") != null;
+  }
+
+  protected CodeInsightTestFixture testFixture;
+  protected WorkspaceRoot workspaceRoot;
+  private String oldPluginPathProperty;
+
+  @Override
+  protected final void setUp() throws Exception {
+    if (!isRunThroughBlaze()) {
+      // If running directly through the IDE, don't try to load plugins from the sandbox environment
+      // Instead we'll rely on the slightly more hermetic module classpath
+      oldPluginPathProperty = System.getProperty(PathManager.PROPERTY_PLUGINS_PATH);
+      System.setProperty(PathManager.PROPERTY_PLUGINS_PATH, "/dev/null");
+    }
+
+    // Some plugins have a since-build and until-build restriction, so we need
+    // to update the build number here
+    PluginManagerCore.BUILD_NUMBER = "162.1447.26";
+
+    super.setUp();
+
+    IdeaTestFixtureFactory factory = IdeaTestFixtureFactory.getFixtureFactory();
+    TestFixtureBuilder<IdeaProjectTestFixture> fixtureBuilder =
+        factory.createLightFixtureBuilder(projectDescriptor);
+    final IdeaProjectTestFixture fixture = fixtureBuilder.getFixture();
+    testFixture = factory.createCodeInsightFixture(fixture, createTempDirFixture());
+    testFixture.setUp();
+
+    Runnable writeAction =
+        () ->
+            ApplicationManager.getApplication()
+                .runWriteAction(
+                    () -> ProjectJdkTable.getInstance().addJdk(IdeaTestUtil.getMockJdk18()));
+    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()));
+
+    registerApplicationService(FileAttributeProvider.class, new TempFileAttributeProvider());
+    registerApplicationService(
+        InputStreamProvider.class,
+        file -> {
+          VirtualFile vf = findFile(file.getPath());
+          if (vf == null) {
+            throw new FileNotFoundException();
+          }
+          return vf.getInputStream();
+        });
+
+    doSetup();
+  }
+
+  /** Override to run tests with bazel specified as the project's build system. */
+  protected BuildSystem buildSystem() {
+    return BuildSystem.Blaze;
+  }
+
+  protected void doSetup() throws Exception {}
+
+  @Override
+  protected final void tearDown() throws Exception {
+    if (oldPluginPathProperty != null) {
+      System.setProperty(PathManager.PROPERTY_PLUGINS_PATH, oldPluginPathProperty);
+    } else {
+      System.clearProperty(PathManager.PROPERTY_PLUGINS_PATH);
+    }
+    testFixture.tearDown();
+    testFixture = null;
+    super.tearDown();
+    clearFields(this);
+    doTearDown();
+  }
+
+  protected void doTearDown() throws Exception {}
+
+  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 void assertFileContents(String filePath, String... contentLines) {
+    assertFileContents(findFile(filePath), contentLines);
+  }
+
+  protected void assertFileContents(VirtualFile file, String... contentLines) {
+    assertFileContents(getPsiFile(file), contentLines);
+  }
+
+  protected void assertFileContents(PsiFile file, String... contentLines) {
+    String contents = Joiner.on("\n").join(contentLines);
+    assertThat(file.getText()).isEqualTo(contents);
+  }
+
+  /** 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 void mockBlazeProjectDataManager(BlazeProjectData data) {
+    BlazeProjectDataManager mockProjectDataManager =
+        new BlazeProjectDataManager() {
+          @Nullable
+          @Override
+          public BlazeProjectData getBlazeProjectData() {
+            return data;
+          }
+
+          @Override
+          public BlazeSyncPlugin.ModuleEditor editModules() {
+            return ModuleEditorProvider.getInstance()
+                .getModuleEditor(
+                    getProject(),
+                    BlazeImportSettingsManager.getInstance(getProject()).getImportSettings());
+          }
+        };
+    registerProjectService(BlazeProjectDataManager.class, mockProjectDataManager);
+  }
+
+  protected <T> void registerApplicationService(Class<T> key, T implementation) {
+    registerComponentInstance(
+        (MutablePicoContainer) ApplicationManager.getApplication().getPicoContainer(),
+        key,
+        implementation);
+  }
+
+  protected <T> void registerProjectService(Class<T> key, T implementation) {
+    registerComponentInstance(
+        (MutablePicoContainer) getProject().getPicoContainer(), key, implementation);
+  }
+
+  protected <T> void registerComponentInstance(
+      MutablePicoContainer container, Class<T> key, T implementation) {
+    Object old = container.getComponentInstance(key);
+    container.unregisterComponent(key.getName());
+    container.registerComponentInstance(key.getName(), implementation);
+    Disposer.register(
+        getTestRootDisposable(),
+        () -> {
+          container.unregisterComponent(key.getName());
+          if (old != null) {
+            container.registerComponentInstance(key.getName(), old);
+          }
+        });
+  }
+
+  protected <T> void registerExtension(ExtensionPointName<T> name, T instance) {
+    ExtensionPoint<T> ep = Extensions.getRootArea().getExtensionPoint(name);
+    ep.registerExtension(instance);
+    Disposer.register(getTestRootDisposable(), () -> ep.unregisterExtension(instance));
+  }
+
+  /** 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/lang/buildfile/BuildFileIntegrationTestCase.java b/base/tests/utils/integration/com/google/idea/blaze/base/lang/buildfile/BuildFileIntegrationTestCase.java
new file mode 100644
index 0000000..fa91385
--- /dev/null
+++ b/base/tests/utils/integration/com/google/idea/blaze/base/lang/buildfile/BuildFileIntegrationTestCase.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.buildfile;
+
+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.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.RuleMap;
+import com.google.idea.blaze.base.model.primitives.ExecutionRootPath;
+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.WorkspacePathResolverImpl;
+import com.intellij.lang.ASTNode;
+import com.intellij.psi.PsiFile;
+
+/** BUILD file specific integration test base */
+public abstract class BuildFileIntegrationTestCase extends BlazeIntegrationTestCase {
+
+  @Override
+  protected void doSetup() {
+    mockBlazeProjectDataManager(getMockBlazeProjectData());
+  }
+
+  /**
+   * 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);
+    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));
+        });
+  }
+
+  private BlazeProjectData getMockBlazeProjectData() {
+    BlazeRoots fakeRoots =
+        new BlazeRoots(
+            null,
+            ImmutableList.of(workspaceRoot.directory()),
+            new ExecutionRootPath("out/crosstool/bin"),
+            new ExecutionRootPath("out/crosstool/gen"));
+    return new BlazeProjectData(
+        0,
+        new RuleMap(ImmutableMap.of()),
+        fakeRoots,
+        new WorkingSet(ImmutableList.of(), ImmutableList.of(), ImmutableList.of()),
+        new WorkspacePathResolverImpl(workspaceRoot, fakeRoots),
+        null,
+        null,
+        null,
+        null);
+  }
+}
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
new file mode 100644
index 0000000..180e1cc
--- /dev/null
+++ b/base/tests/utils/integration/com/google/idea/blaze/base/sync/BlazeSyncIntegrationTestCase.java
@@ -0,0 +1,331 @@
+/*
+ * 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;
+
+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.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+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.io.WorkspaceScanner;
+import com.google.idea.blaze.base.model.RuleMap;
+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;
+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.projectview.parser.ProjectViewParser;
+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;
+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;
+import com.google.idea.blaze.base.sync.aspects.BlazeIdeInterface;
+import com.google.idea.blaze.base.sync.projectstructure.ModuleEditorImpl;
+import com.google.idea.blaze.base.sync.projectstructure.ModuleEditorProvider;
+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.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.vcs.BlazeVcsHandler;
+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;
+
+/** 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 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;
+  private MockBlazeIdeInterface blazeIdeInterface;
+
+  protected ErrorCollector errorCollector;
+  protected BlazeContext context;
+
+  @Override
+  protected void doSetup() throws IOException {
+    // Set up a workspace root outside of the tracked temp file system.
+    tempDirectoryHandler = new LightTempDirTestFixtureImpl();
+    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(
+        ModuleEditorProvider.class,
+        new ModuleEditorProvider() {
+          @Override
+          public ModuleEditorImpl getModuleEditor(
+              Project project, BlazeImportSettings importSettings) {
+            return new ModuleEditorImpl(project, importSettings) {
+              @Override
+              public void commit() {
+                // don't commit module changes,
+                // but make sure they're properly disposed when the test is finished
+                for (ModifiableRootModel model : modifiableModels) {
+                  Disposer.register(myTestRootDisposable, model::dispose);
+                }
+              }
+            };
+          }
+        });
+
+    errorCollector = new ErrorCollector();
+    context = new BlazeContext();
+    context.addOutputSink(IssueOutput.class, errorCollector);
+
+    tempDirectoryHandler.findOrCreateDir(PROJECT_DATA_DIR + "/.blaze/modules");
+
+    setBlazeInfoResults(
+        ImmutableMap.of(
+            BlazeInfo.blazeBinKey(Blaze.getBuildSystem(getProject())),
+            BLAZE_BIN,
+            BlazeInfo.blazeGenfilesKey(Blaze.getBuildSystem(getProject())),
+            BLAZE_GENFILES,
+            BlazeInfo.EXECUTION_ROOT_KEY,
+            EXECUTION_ROOT,
+            BlazeInfo.PACKAGE_PATH_KEY,
+            workspaceRoot.toString()));
+  }
+
+  @Override
+  protected void doTearDown() throws Exception {
+    if (tempDirectoryHandler != null) {
+      tempDirectoryHandler.tearDown();
+    }
+    super.doTearDown();
+  }
+
+  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()
+        .setRootPath(workspaceRoot.toString())
+        .setRelativePath(relativePath)
+        .setIsSource(true)
+        .build();
+  }
+
+  protected void setProjectView(String... contents) {
+    ProjectViewParser projectViewParser =
+        new ProjectViewParser(context, new WorkspacePathResolverImpl(workspaceRoot));
+    projectViewParser.parseProjectView(Joiner.on("\n").join(contents));
+
+    ProjectViewSet result = projectViewParser.getResult();
+    assertThat(result.getProjectViewFiles()).isNotEmpty();
+    assertNoErrors();
+    setProjectViewSet(result);
+  }
+
+  protected void setProjectViewSet(ProjectViewSet projectViewSet) {
+    projectViewManager.projectViewSet = projectViewSet;
+  }
+
+  protected void setRuleMap(RuleMap ruleMap) {
+    blazeIdeInterface.ruleMap = ruleMap;
+  }
+
+  protected void setBlazeInfoResults(Map<String, String> blazeInfoResults) {
+    blazeInfoData.setResults(blazeInfoResults);
+  }
+
+  protected void runBlazeSync(BlazeSyncParams syncParams) {
+    Project project = getProject();
+    final BlazeSyncTask syncTask =
+        new BlazeSyncTask(
+            project,
+            BlazeImportSettingsManager.getInstance(project).getImportSettings(),
+            syncParams);
+    syncTask.syncProject(context);
+  }
+
+  private static class MockProjectViewManager extends ProjectViewManager {
+
+    private ProjectViewSet projectViewSet;
+
+    @Nullable
+    @Override
+    public ProjectViewSet getProjectViewSet() {
+      return projectViewSet;
+    }
+
+    @Nullable
+    @Override
+    public ProjectViewSet reloadProjectView(
+        BlazeContext context, WorkspacePathResolver workspacePathResolver) {
+      return getProjectViewSet();
+    }
+  }
+
+  private static class MockBlazeVcsHandler implements BlazeVcsHandler {
+
+    private List<WorkspacePath> addedFiles = Lists.newArrayList();
+
+    @Override
+    public String getVcsName() {
+      return "Mock";
+    }
+
+    @Override
+    public boolean handlesProject(BuildSystem buildSystem, WorkspaceRoot workspaceRoot) {
+      return true;
+    }
+
+    @Override
+    public ListenableFuture<WorkingSet> getWorkingSet(
+        Project project,
+        BlazeContext context,
+        WorkspaceRoot workspaceRoot,
+        ListeningExecutorService executor) {
+      WorkingSet workingSet =
+          new WorkingSet(ImmutableList.copyOf(addedFiles), ImmutableList.of(), ImmutableList.of());
+      return Futures.immediateFuture(workingSet);
+    }
+
+    @Nullable
+    @Override
+    public BlazeVcsSyncHandler createSyncHandler(Project project, WorkspaceRoot workspaceRoot) {
+      return null;
+    }
+  }
+
+  private static class MockBlazeInfo extends BlazeInfo {
+    private final Map<String, String> results = Maps.newHashMap();
+
+    @Override
+    public ListenableFuture<String> runBlazeInfo(
+        @Nullable BlazeContext context,
+        BuildSystem buildSystem,
+        WorkspaceRoot workspaceRoot,
+        List<String> blazeFlags,
+        String key) {
+      return Futures.immediateFuture(results.get(key));
+    }
+
+    @Override
+    public ListenableFuture<byte[]> runBlazeInfoGetBytes(
+        @Nullable BlazeContext context,
+        BuildSystem buildSystem,
+        WorkspaceRoot workspaceRoot,
+        List<String> blazeFlags,
+        String key) {
+      return Futures.immediateFuture(null);
+    }
+
+    @Override
+    public ListenableFuture<ImmutableMap<String, String>> runBlazeInfo(
+        @Nullable BlazeContext context,
+        BuildSystem buildSystem,
+        WorkspaceRoot workspaceRoot,
+        List<String> blazeFlags) {
+      return Futures.immediateFuture(ImmutableMap.copyOf(results));
+    }
+
+    public void setResults(Map<String, String> results) {
+      this.results.clear();
+      this.results.putAll(results);
+    }
+  }
+
+  private static class MockBlazeIdeInterface implements BlazeIdeInterface {
+    private RuleMap ruleMap = new RuleMap(ImmutableMap.of());
+
+    @Override
+    public IdeResult updateRuleMap(
+        Project project,
+        BlazeContext context,
+        WorkspaceRoot workspaceRoot,
+        ProjectViewSet projectViewSet,
+        List<TargetExpression> targets,
+        WorkspaceLanguageSettings workspaceLanguageSettings,
+        ArtifactLocationDecoder artifactLocationDecoder,
+        SyncState.Builder syncStateBuilder,
+        @Nullable SyncState previousSyncState,
+        boolean mergeWithOldState) {
+      return new IdeResult(ruleMap, BuildResult.SUCCESS);
+    }
+
+    @Override
+    public BuildResult resolveIdeArtifacts(
+        Project project,
+        BlazeContext context,
+        WorkspaceRoot workspaceRoot,
+        ProjectViewSet projectViewSet,
+        List<TargetExpression> targets) {
+      return BuildResult.SUCCESS;
+    }
+  }
+}
diff --git a/base/tests/utils/unit/com/google/idea/blaze/base/BlazeTestCase.java b/base/tests/utils/unit/com/google/idea/blaze/base/BlazeTestCase.java
new file mode 100644
index 0000000..428b180
--- /dev/null
+++ b/base/tests/utils/unit/com/google/idea/blaze/base/BlazeTestCase.java
@@ -0,0 +1,109 @@
+/*
+ * 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.intellij.mock.MockProject;
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.extensions.DefaultPluginDescriptor;
+import com.intellij.openapi.extensions.ExtensionPoint;
+import com.intellij.openapi.extensions.ExtensionPointName;
+import com.intellij.openapi.extensions.Extensions;
+import com.intellij.openapi.extensions.PluginId;
+import com.intellij.openapi.extensions.impl.ExtensionPointImpl;
+import com.intellij.openapi.extensions.impl.ExtensionsAreaImpl;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Disposer;
+import org.jetbrains.annotations.NotNull;
+import org.junit.After;
+import org.junit.Before;
+import org.picocontainer.MutablePicoContainer;
+
+/**
+ * Test base class.
+ *
+ * <p>
+ *
+ * <p>Provides a mock application and a mock project.
+ */
+public class BlazeTestCase {
+
+  protected Project project;
+  private ExtensionsAreaImpl extensionsArea;
+  private Disposable testDisposable;
+
+  private static class RootDisposable implements Disposable {
+    @Override
+    public void dispose() {}
+  }
+
+  /** A wrapper around the pico container used by IntelliJ's DI system */
+  public static class Container {
+    private final MutablePicoContainer container;
+
+    Container(@NotNull MutablePicoContainer container) {
+      this.container = container;
+    }
+
+    public <T> Container register(Class<T> klass, T instance) {
+      this.container.registerComponentInstance(klass.getName(), instance);
+      return this;
+    }
+  }
+
+  @Before
+  public final void setup() {
+    testDisposable = new RootDisposable();
+    TestUtils.createMockApplication(testDisposable);
+    MutablePicoContainer applicationContainer =
+        (MutablePicoContainer) ApplicationManager.getApplication().getPicoContainer();
+    MockProject mockProject = TestUtils.mockProject(applicationContainer, testDisposable);
+
+    Extensions.cleanRootArea(testDisposable);
+    extensionsArea = (ExtensionsAreaImpl) Extensions.getRootArea();
+
+    this.project = mockProject;
+
+    initTest(new Container(applicationContainer), new Container(mockProject.getPicoContainer()));
+  }
+
+  @After
+  public final void tearDown() {
+    Disposer.dispose(testDisposable);
+  }
+
+  public final Project getProject() {
+    return project;
+  }
+
+  protected void initTest(
+      @NotNull Container applicationServices, @NotNull Container projectServices) {}
+
+  protected <T> ExtensionPointImpl<T> registerExtensionPoint(
+      @NotNull ExtensionPointName<T> name, @NotNull Class<T> type) {
+    ExtensionPointImpl<T> extensionPoint =
+        new ExtensionPointImpl<T>(
+            name.getName(),
+            type.getName(),
+            ExtensionPoint.Kind.INTERFACE,
+            extensionsArea,
+            null,
+            new Extensions.SimpleLogProvider(),
+            new DefaultPluginDescriptor(PluginId.getId(type.getName()), type.getClassLoader()));
+    extensionsArea.registerExtensionPoint(extensionPoint);
+    return extensionPoint;
+  }
+}
diff --git a/base/tests/utils/unit/com/google/idea/blaze/base/TestUtils.java b/base/tests/utils/unit/com/google/idea/blaze/base/TestUtils.java
new file mode 100644
index 0000000..f040cf2
--- /dev/null
+++ b/base/tests/utils/unit/com/google/idea/blaze/base/TestUtils.java
@@ -0,0 +1,122 @@
+/*
+ * 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 org.junit.Assert.fail;
+
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.intellij.mock.MockApplicationEx;
+import com.intellij.mock.MockProject;
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.application.Application;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.extensions.Extensions;
+import com.intellij.openapi.fileTypes.FileTypeManager;
+import com.intellij.openapi.util.Disposer;
+import com.intellij.openapi.vfs.encoding.EncodingManager;
+import com.intellij.openapi.vfs.encoding.EncodingManagerImpl;
+import com.intellij.util.pico.DefaultPicoContainer;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.NotSerializableException;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Future;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.picocontainer.PicoContainer;
+
+/** Test utilities. */
+public class TestUtils {
+
+  static class BlazeMockApplication extends MockApplicationEx {
+    private final ListeningExecutorService executor = MoreExecutors.sameThreadExecutor();
+
+    public BlazeMockApplication(@NotNull Disposable parentDisposable) {
+      super(parentDisposable);
+    }
+
+    @NotNull
+    @Override
+    public Future<?> executeOnPooledThread(@NotNull Runnable action) {
+      return executor.submit(action);
+    }
+
+    @NotNull
+    @Override
+    public <T> Future<T> executeOnPooledThread(@NotNull Callable<T> action) {
+      return executor.submit(action);
+    }
+  }
+
+  public static void createMockApplication(Disposable parentDisposable) {
+    final BlazeMockApplication instance = new BlazeMockApplication(parentDisposable);
+
+    // If there was no previous application,
+    // ApplicationManager leaves the MockApplication in place, which can break future tests.
+    Application oldApplication = ApplicationManager.getApplication();
+    if (oldApplication == null) {
+      Disposer.register(
+          parentDisposable,
+          () -> {
+            new ApplicationManager() {
+              {
+                ourApplication = null;
+              }
+            };
+          });
+    }
+
+    ApplicationManager.setApplication(instance, FileTypeManager::getInstance, parentDisposable);
+    instance.registerService(EncodingManager.class, EncodingManagerImpl.class);
+  }
+
+  @NotNull
+  public static MockProject mockProject(
+      @Nullable PicoContainer container, Disposable parentDisposable) {
+    Extensions.registerAreaClass("IDEA_PROJECT", null);
+    container = container != null ? container : new DefaultPicoContainer();
+    return new MockProject(container, parentDisposable);
+  }
+
+  public static void assertIsSerializable(@NotNull Serializable object) {
+    ObjectOutputStream out = null;
+    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+    try {
+      out = new ObjectOutputStream(byteArrayOutputStream);
+      out.writeObject(object);
+    } catch (NotSerializableException e) {
+      fail("An object is not serializable: " + e.getMessage());
+    } catch (IOException e) {
+      fail("Could not serialize object: " + e.getMessage());
+    } finally {
+      if (out != null) {
+        try {
+          out.close();
+        } catch (IOException e) {
+          // ignore
+        }
+      }
+      try {
+        byteArrayOutputStream.close();
+      } catch (IOException e) {
+        // ignore
+      }
+    }
+  }
+}
diff --git a/base/tests/utils/unit/com/google/idea/blaze/base/async/executor/MockBlazeExecutor.java b/base/tests/utils/unit/com/google/idea/blaze/base/async/executor/MockBlazeExecutor.java
new file mode 100644
index 0000000..3e970b4
--- /dev/null
+++ b/base/tests/utils/unit/com/google/idea/blaze/base/async/executor/MockBlazeExecutor.java
@@ -0,0 +1,37 @@
+/*
+ * 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.async.executor;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import java.util.concurrent.Callable;
+
+/** Used in tests. */
+public class MockBlazeExecutor extends BlazeExecutor {
+
+  private final ListeningExecutorService executor = MoreExecutors.sameThreadExecutor();
+
+  @Override
+  public <T> ListenableFuture<T> submit(final Callable<T> callable) {
+    return executor.submit(callable);
+  }
+
+  @Override
+  public ListeningExecutorService getExecutor() {
+    return executor;
+  }
+}
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
new file mode 100644
index 0000000..2586377
--- /dev/null
+++ b/base/tests/utils/unit/com/google/idea/blaze/base/ideinfo/RuleMapBuilder.java
@@ -0,0 +1,52 @@
+/*
+ * 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 com.google.idea.blaze.base.model.RuleMap;
+import com.google.idea.blaze.base.model.primitives.Label;
+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<Label, RuleIdeInfo> ruleMap = ImmutableMap.builder();
+    for (RuleIdeInfo rule : rules) {
+      ruleMap.put(rule.label, rule);
+    }
+    return new RuleMap(ruleMap.build());
+  }
+}
diff --git a/base/tests/utils/unit/com/google/idea/blaze/base/prefetch/MockPrefetchService.java b/base/tests/utils/unit/com/google/idea/blaze/base/prefetch/MockPrefetchService.java
new file mode 100644
index 0000000..8dbac18
--- /dev/null
+++ b/base/tests/utils/unit/com/google/idea/blaze/base/prefetch/MockPrefetchService.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.base.prefetch;
+
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.intellij.openapi.project.Project;
+import java.io.File;
+import java.util.Collection;
+
+/** Mocks the prefetch service. */
+public class MockPrefetchService implements PrefetchService {
+
+  @Override
+  public ListenableFuture<?> prefetchFiles(Project project, Collection<File> files) {
+    return Futures.immediateFuture(null);
+  }
+
+  @Override
+  public ListenableFuture<?> prefetchProjectFiles(
+      Project project, BlazeProjectData blazeProjectData) {
+    return Futures.immediateFuture(null);
+  }
+}
diff --git a/base/tests/utils/unit/com/google/idea/blaze/base/run/MockBlazeCommandRunConfigurationHandlerProvider.java b/base/tests/utils/unit/com/google/idea/blaze/base/run/MockBlazeCommandRunConfigurationHandlerProvider.java
new file mode 100644
index 0000000..92ab14e
--- /dev/null
+++ b/base/tests/utils/unit/com/google/idea/blaze/base/run/MockBlazeCommandRunConfigurationHandlerProvider.java
@@ -0,0 +1,143 @@
+/*
+ * 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.model.primitives.Kind;
+import com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationHandler;
+import com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationHandlerEditor;
+import com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationHandlerProvider;
+import com.intellij.execution.ExecutionException;
+import com.intellij.execution.Executor;
+import com.intellij.execution.configurations.RunConfiguration;
+import com.intellij.execution.configurations.RunProfileState;
+import com.intellij.execution.configurations.RuntimeConfigurationException;
+import com.intellij.execution.runners.ExecutionEnvironment;
+import com.intellij.openapi.util.InvalidDataException;
+import javax.annotation.Nullable;
+import javax.swing.Icon;
+import javax.swing.JComponent;
+import org.jdom.Element;
+
+/** A mock {@link BlazeCommandRunConfigurationHandlerProvider}. */
+public class MockBlazeCommandRunConfigurationHandlerProvider
+    implements BlazeCommandRunConfigurationHandlerProvider {
+  @Override
+  public boolean canHandleKind(Kind kind) {
+    return true;
+  }
+
+  @Override
+  public BlazeCommandRunConfigurationHandler createHandler(BlazeCommandRunConfiguration config) {
+    return new MockBlazeCommandRunConfigurationHandler(config);
+  }
+
+  @Override
+  public String getId() {
+    return "MockBlazeCommandRunConfigurationHandlerProvider";
+  }
+
+  /** A mock {@link BlazeCommandRunConfigurationHandler}. */
+  private static class MockBlazeCommandRunConfigurationHandler
+      implements BlazeCommandRunConfigurationHandler {
+
+    final BlazeCommandRunConfiguration configuration;
+
+    MockBlazeCommandRunConfigurationHandler(BlazeCommandRunConfiguration configuration) {
+      this.configuration = configuration;
+    }
+
+    @Override
+    public void checkConfiguration() throws RuntimeConfigurationException {
+      // Don't throw anything.
+    }
+
+    @Override
+    public void readExternal(Element element) throws InvalidDataException {
+      // Don't read anything.
+    }
+
+    @Override
+    public void writeExternal(Element element) {
+      // Don't write anything.
+    }
+
+    @Override
+    public BlazeCommandRunConfigurationHandler cloneFor(
+        BlazeCommandRunConfiguration configuration) {
+      return new MockBlazeCommandRunConfigurationHandler(configuration);
+    }
+
+    @Override
+    public RunProfileState getState(Executor executor, ExecutionEnvironment environment)
+        throws ExecutionException {
+      return null;
+    }
+
+    @Override
+    public boolean executeBeforeRunTask(ExecutionEnvironment environment) {
+      return true;
+    }
+
+    @Nullable
+    @Override
+    public String suggestedName() {
+      return null;
+    }
+
+    @Override
+    public boolean isGeneratedName(boolean hasGeneratedFlag) {
+      return hasGeneratedFlag;
+    }
+
+    @Nullable
+    @Override
+    public String getCommandName() {
+      return null;
+    }
+
+    @Override
+    public String getHandlerName() {
+      return "Mock Handler";
+    }
+
+    @Override
+    @Nullable
+    public Icon getExecutorIcon(RunConfiguration configuration, Executor executor) {
+      return null;
+    }
+
+    @Override
+    public BlazeCommandRunConfigurationHandlerEditor getHandlerEditor() {
+      return new BlazeCommandRunConfigurationHandlerEditor() {
+        @Override
+        public void resetEditorFrom(BlazeCommandRunConfigurationHandler handler) {
+          // Do nothing.
+        }
+
+        @Override
+        public void applyEditorTo(BlazeCommandRunConfigurationHandler handler) {
+          // Do nothing.
+        }
+
+        @Nullable
+        @Override
+        public JComponent createEditor() {
+          return null;
+        }
+      };
+    }
+  }
+}
diff --git a/base/tests/utils/unit/com/google/idea/blaze/base/scope/ErrorCollector.java b/base/tests/utils/unit/com/google/idea/blaze/base/scope/ErrorCollector.java
new file mode 100644
index 0000000..3362bae
--- /dev/null
+++ b/base/tests/utils/unit/com/google/idea/blaze/base/scope/ErrorCollector.java
@@ -0,0 +1,52 @@
+/*
+ * 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.scope;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.scope.output.IssueOutput;
+import java.util.List;
+import org.jetbrains.annotations.NotNull;
+
+/** Test class that collects issues. */
+public class ErrorCollector implements OutputSink<IssueOutput> {
+  List<IssueOutput> issues = Lists.newArrayList();
+
+  @Override
+  public Propagation onOutput(@NotNull IssueOutput output) {
+    issues.add(output);
+    return Propagation.Continue;
+  }
+
+  public void assertNoIssues() {
+    assertThat(issues).isEmpty();
+  }
+
+  public void assertIssues(@NotNull String... requiredMessages) {
+    List<String> messages = Lists.newArrayList();
+    for (IssueOutput issue : issues) {
+      messages.add(issue.getMessage());
+    }
+    assertThat(messages).containsExactly((Object[]) requiredMessages);
+  }
+
+  public void assertIssueContaining(@NotNull String s) {
+    assertThat(issues.stream().anyMatch((issue) -> issue.getMessage().contains(s)))
+        .named("Issues must contain: " + s)
+        .isTrue();
+  }
+}
diff --git a/blaze-base/BUILD b/blaze-base/BUILD
deleted file mode 100644
index 2473778..0000000
--- a/blaze-base/BUILD
+++ /dev/null
@@ -1,85 +0,0 @@
-package(default_visibility = ["//visibility:public"])
-
-java_library(
-    name = "blaze-base",
-    srcs = glob(["src/**/*.java"]),
-    resources = glob(["resources/**/*"]),
-    deps = [
-        ":proto-deps",
-        "//intellij-platform-sdk:plugin_api",
-        "//third_party:jsr305",
-        "//third_party:trickle",
-    ],
-)
-
-java_import(
-    name = "proto-deps",
-    jars = ["lib/proto_deps.jar"],
-)
-
-filegroup(
-    name = "plugin_xml",
-    srcs = ["src/META-INF/blaze-base.xml"],
-)
-
-java_library(
-    name = "unit_test_utils",
-    srcs = glob(["tests/utils/unit/**/*.java"]),
-    deps = [
-        ":blaze-base",
-        "//intellij-platform-sdk:plugin_api_for_tests",
-        "//third_party:jsr305",
-        "//third_party:test_lib",
-    ],
-)
-
-java_library(
-    name = "integration_test_utils",
-    srcs = glob(["tests/utils/integration/**/*.java"]),
-    deps = [
-        ":blaze-base",
-        ":proto-deps",
-        ":unit_test_utils",
-        "//intellij-platform-sdk:plugin_api_for_tests",
-        "//third_party:jsr305",
-        "//third_party:test_lib",
-    ],
-)
-
-load(
-    "//intellij_test:test_defs.bzl",
-    "intellij_test",
-)
-
-intellij_test(
-    name = "unit_tests",
-    srcs = glob(["tests/unittests/**/*.java"]),
-    test_package_root = "com.google.idea.blaze.base",
-    deps = [
-        ":blaze-base",
-        ":proto-deps",
-        ":unit_test_utils",
-        "//intellij-platform-sdk:plugin_api_for_tests",
-        "//intellij_test:lib",
-        "//third_party:jsr305",
-        "//third_party:test_lib",
-    ],
-)
-
-intellij_test(
-    name = "integration_tests",
-    srcs = glob(["tests/integrationtests/**/*.java"]),
-    integration_tests = True,
-    required_plugins = "com.google.idea.blaze.ijwb",
-    test_package_root = "com.google.idea.blaze.base",
-    deps = [
-        ":blaze-base",
-        ":integration_test_utils",
-        ":unit_test_utils",
-        "//ijwb:ijwb_bazel",
-        "//intellij-platform-sdk:plugin_api_for_tests",
-        "//intellij_test:lib",
-        "//third_party:jsr305",
-        "//third_party:test_lib",
-    ],
-)
diff --git a/blaze-base/lib/proto_deps.jar b/blaze-base/lib/proto_deps.jar
deleted file mode 100755
index d9241ab..0000000
--- a/blaze-base/lib/proto_deps.jar
+++ /dev/null
Binary files differ
diff --git a/blaze-base/resources/binaries/bazel-buildifier b/blaze-base/resources/binaries/bazel-buildifier
deleted file mode 100755
index c7347bb..0000000
--- a/blaze-base/resources/binaries/bazel-buildifier
+++ /dev/null
Binary files differ
diff --git a/blaze-base/src/META-INF/blaze-base.xml b/blaze-base/src/META-INF/blaze-base.xml
deleted file mode 100644
index 4668d85..0000000
--- a/blaze-base/src/META-INF/blaze-base.xml
+++ /dev/null
@@ -1,262 +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.
-  -->
-<idea-plugin>
-  <actions>
-    <action id="MakeBlazeProject" class="com.google.idea.blaze.base.actions.BlazeMakeProjectAction" use-shortcut-of="CompileDirty" icon="AllIcons.Actions.Compile">
-    </action>
-    <action id="MakeBlazeModule" class="com.google.idea.blaze.base.actions.BlazeCompileFileAction">
-    </action>
-    <action id="Blaze.IncrementalSyncProject" class="com.google.idea.blaze.base.sync.actions.IncrementalSyncProjectAction" icon="BlazeIcons.Blaze">
-    </action>
-    <action id="Blaze.FullSyncProject" class="com.google.idea.blaze.base.sync.actions.FullSyncProjectAction" icon="BlazeIcons.BlazeSlow">
-    </action>
-    <action id="Blaze.ExpandSyncToWorkingSet" class="com.google.idea.blaze.base.sync.actions.ExpandSyncToWorkingSetAction" text="Expand Sync to Working Set">
-    </action>
-    <action id="Blaze.ShowPerformanceWarnings" class="com.google.idea.blaze.base.sync.actions.ShowPerformanceWarningsToggleAction" text="Show Performance Warnings">
-    </action>
-    <action id="Blaze.EditProjectView" class="com.google.idea.blaze.base.settings.ui.EditProjectViewAction" text="Edit Project View..." icon="BlazeIcons.Blaze">
-    </action>
-
-    <action class="com.google.idea.blaze.base.buildmap.OpenCorrespondingBuildFile"
-            id="Blaze.OpenCorrespondingBuildFile"
-            icon="BlazeIcons.Blaze"
-            text="Open Corresponding BUILD File">
-    </action>
-    <action class="com.google.idea.blaze.base.sync.actions.PartialSyncAction"
-            id="Blaze.PartialSync"
-            icon="BlazeIcons.Blaze">
-    </action>
-
-    <group id="Blaze.MainMenuActionGroup" class="com.google.idea.blaze.base.actions.BlazeMenuGroup">
-      <add-to-group group-id="MainMenu" anchor="before" relative-to-action="HelpMenu"/>
-      <reference id="MakeBlazeProject"/>
-      <reference id="MakeBlazeModule"/>
-      <separator/>
-      <reference id="Blaze.EditProjectView"/>
-      <separator/>
-      <reference id="Blaze.IncrementalSyncProject"/>
-      <reference id="Blaze.FullSyncProject"/>
-      <reference id="Blaze.PartialSync"/>
-      <reference id="Blaze.ExpandSyncToWorkingSet"/>
-      <reference id="Blaze.ShowPerformanceWarnings"/>
-    </group>
-
-    <group id="Blaze.MainToolBarActionGroup">
-      <add-to-group group-id="MainToolBar" anchor="before" relative-to-action="HelpTopics" />
-      <add-to-group group-id="NavBarToolBarOthers" anchor="last"/>
-      <reference id="Blaze.IncrementalSyncProject"/>
-    </group>
-
-    <group id="Blaze.NewActions" text="Edit Blaze structure" description="Create new Blaze packages, rules, etc.">
-      <add-to-group group-id="NewGroup" anchor="first"/>
-      <action id="Blaze.NewPackageAction" class="com.google.idea.blaze.base.ide.NewBlazePackageAction" popup="true"/>
-      <action id="Blaze.NewRuleAction" class="com.google.idea.blaze.base.ide.NewBlazeRuleAction" popup="true"/>
-      <separator/>
-    </group>
-
-    <group id="Blaze.ProjectViewPopupMenu">
-      <add-to-group anchor="after" group-id="ProjectViewPopupMenu" relative-to-action="EditSource"/>
-      <separator/>
-      <reference ref="Blaze.PartialSync"/>
-      <reference ref="Blaze.OpenCorrespondingBuildFile"/>
-    </group>
-
-    <group id="Blaze.EditorTabPopupMenu">
-      <add-to-group anchor="after" group-id="EditorTabPopupMenu" relative-to-action="CopyReference"/>
-      <separator/>
-      <reference ref="Blaze.PartialSync"/>
-      <reference ref="Blaze.OpenCorrespondingBuildFile"/>
-    </group>
-  </actions>
-
-  <extensions defaultExtensionNs="com.intellij">
-    <postStartupActivity implementation="com.google.idea.blaze.base.sync.BlazeSyncStartupActivity"/>
-
-    <toolWindow id="Blaze Console"
-                      anchor="bottom"
-                      secondary="true"
-                      conditionClass="com.google.idea.blaze.base.settings.IsBlazeProjectCondition"
-                      icon="BlazeIcons.BlazeToolWindow"
-                      factoryClass="com.google.idea.blaze.base.console.BlazeConsoleToolWindowFactory"/>
-    <projectService serviceImplementation="com.google.idea.blaze.base.console.BlazeConsoleView"/>
-    <fileTypeFactory implementation="com.google.idea.blaze.base.plugin.BlazeFileTypeFactory" />
-    <applicationService serviceInterface="com.google.idea.blaze.base.experiments.ExperimentService"
-                        serviceImplementation="com.google.idea.blaze.base.experiments.ExperimentServiceImpl"/>
-
-    <projectConfigurable instance="com.google.idea.blaze.base.settings.ui.BlazeUserSettingsConfigurable"
-                         id ="blaze.view" displayName="Blaze settings"/>
-
-    <projectService serviceInterface="com.google.idea.blaze.base.sync.data.BlazeProjectDataManager"
-                    serviceImplementation="com.google.idea.blaze.base.sync.data.BlazeProjectDataManagerImpl"/>
-    <projectService serviceImplementation="com.google.idea.blaze.base.sync.BlazeSyncManager"/>
-    <projectService serviceInterface="com.google.idea.blaze.base.sync.status.BlazeSyncStatus"
-                    serviceImplementation="com.google.idea.blaze.base.sync.status.BlazeSyncStatusImpl"/>
-
-    <applicationService serviceInterface="com.google.idea.blaze.base.async.executor.BlazeExecutor"
-                        serviceImplementation="com.google.idea.blaze.base.async.executor.BlazeExecutorImpl"/>
-    <projectService serviceInterface="com.intellij.openapi.vcs.impl.DefaultVcsRootPolicy"
-                    serviceImplementation="com.google.idea.blaze.base.vcs.BlazeDefaultVcsRootPolicy"
-                    overrides="true"/>
-    <fileDocumentManagerListener implementation="com.google.idea.blaze.base.buildmodifier.FileSaveHandler" order="first"/>
-    <applicationService serviceInterface="com.google.idea.blaze.base.io.InputStreamProvider"
-                        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.command.info.BlazeInfo"
-                        serviceImplementation="com.google.idea.blaze.base.command.info.BlazeInfoImpl"/>
-
-    <treeStructureProvider implementation="com.google.idea.blaze.base.treeview.BlazeTreeStructureProvider" id="blaze"/>
-
-    <applicationService serviceInterface="com.google.idea.blaze.base.projectview.ProjectViewStorageManager"
-                        serviceImplementation="com.google.idea.blaze.base.projectview.ProjectViewStorageManagerImpl"/>
-    <projectService serviceInterface="com.google.idea.blaze.base.projectview.ProjectViewManager"
-                    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.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 serviceImplementation="com.google.idea.blaze.base.settings.BlazeImportSettingsManager"/>
-    <applicationService serviceImplementation="com.google.idea.blaze.base.settings.BlazeUserSettings"/>
-    <applicationService serviceInterface="com.google.idea.blaze.base.lang.buildfile.language.semantics.BuildLanguageSpecProvider"
-                        serviceImplementation="com.google.idea.blaze.base.lang.buildfile.language.semantics.BuildLanguageSpecProviderImpl"/>
-    <applicationService serviceInterface="com.google.idea.blaze.base.sync.projectstructure.ModuleEditorProvider"
-                        serviceImplementation="com.google.idea.blaze.base.sync.projectstructure.ModuleEditorProviderImpl"/>
-    <applicationService serviceInterface="com.google.idea.blaze.base.prefetch.PrefetchService"
-                        serviceImplementation="com.google.idea.blaze.base.prefetch.PrefetchServiceImpl"/>
-    <applicationService serviceImplementation="com.google.idea.blaze.base.wizard2.BlazeWizardUserSettingsStorage"/>
-    <projectService serviceInterface="com.google.idea.blaze.base.sync.workspace.WorkspacePathResolverProvider"
-                    serviceImplementation="com.google.idea.blaze.base.sync.workspace.WorkspacePathResolverProviderImpl"/>
-  </extensions>
-
-  <extensions defaultExtensionNs="com.intellij">
-    <fileTypeFactory implementation="com.google.idea.blaze.base.lang.projectview.language.ProjectViewFileTypeFactory"/>
-    <lang.parserDefinition language="projectview" implementationClass="com.google.idea.blaze.base.lang.projectview.parser.ProjectViewParserDefinition"/>
-    <lang.commenter language="projectview" implementationClass="com.google.idea.blaze.base.lang.projectview.formatting.ProjectViewCommenter"/>
-    <lang.syntaxHighlighterFactory language="projectview" implementationClass="com.google.idea.blaze.base.lang.projectview.highlighting.ProjectViewSyntaxHighlighterFactory"/>
-    <completion.contributor language="projectview" implementationClass="com.google.idea.blaze.base.lang.projectview.completion.ProjectViewKeywordCompletionContributor"/>
-    <completion.contributor language="projectview" implementationClass="com.google.idea.blaze.base.lang.projectview.completion.WorkspaceTypeCompletionContributor"/>
-    <completion.contributor language="projectview" implementationClass="com.google.idea.blaze.base.lang.projectview.completion.AdditionalLanguagesCompletionContributor"/>
-    <enterHandlerDelegate implementation="com.google.idea.blaze.base.lang.projectview.formatting.ProjectViewEnterHandler"/>
-  </extensions>
-
-  <extensions defaultExtensionNs="com.intellij">
-    <fileTypeFactory implementation="com.google.idea.blaze.base.lang.buildfile.language.BuildFileTypeFactory"/>
-    <annotator language="BUILD" implementationClass="com.google.idea.blaze.base.lang.buildfile.validation.HighlightingAnnotator"/>
-    <!--<annotator language="BUILD" implementationClass="com.google.idea.blaze.base.lang.buildfile.validation.ErrorAnnotator"/>-->
-    <annotator language="BUILD" implementationClass="com.google.idea.blaze.base.lang.buildfile.validation.GlobErrorAnnotator"/>
-    <colorSettingsPage implementation="com.google.idea.blaze.base.lang.buildfile.highlighting.BuildColorsPage"/>
-    <projectService serviceImplementation="com.google.idea.blaze.base.lang.buildfile.psi.util.BuildElementGenerator"/>
-    <projectService serviceImplementation="com.google.idea.blaze.base.lang.buildfile.references.BuildReferenceManager"/>
-    <referencesSearch implementation="com.google.idea.blaze.base.lang.buildfile.search.BuildLabelReferenceSearcher"/>
-    <referencesSearch implementation="com.google.idea.blaze.base.lang.buildfile.search.GlobReferenceSearcher"/>
-    <readWriteAccessDetector implementation="com.google.idea.blaze.base.lang.buildfile.findusages.BuildReadWriteAccessDetector"/>
-    <elementDescriptionProvider implementation="com.google.idea.blaze.base.lang.buildfile.findusages.BuildElementDescriptionProvider"/>
-    <usageGroupingRuleProvider implementation="com.google.idea.blaze.base.lang.buildfile.findusages.BuildUsageGroupingRuleProvider"/>
-    <useScopeOptimizer implementation="com.google.idea.blaze.base.lang.buildfile.search.ExcludeBuildFilesScope"/>
-    <targetElementEvaluator language="BUILD" implementationClass="com.google.idea.blaze.base.lang.buildfile.findusages.BuildTargetElementEvaluator"/>
-    <quoteHandler fileType="BUILD" className="com.google.idea.blaze.base.lang.buildfile.editor.BuildQuoteHandler"/>
-    <enterHandlerDelegate implementation="com.google.idea.blaze.base.lang.buildfile.editor.BuildEnterBetweenBracketsHandler" order="before EnterBetweenBracesHandler"/>
-    <enterHandlerDelegate implementation="com.google.idea.blaze.base.lang.buildfile.editor.BuildEnterHandler" order="after EnterBetweenBracesHandler"/>
-    <completion.contributor language="BUILD" implementationClass="com.google.idea.blaze.base.lang.buildfile.completion.ParameterCompletionContributor"/>
-    <completion.contributor language="BUILD" implementationClass="com.google.idea.blaze.base.lang.buildfile.completion.BuiltInFunctionCompletionContributor"/>
-    <completion.contributor language="BUILD" implementationClass="com.google.idea.blaze.base.lang.buildfile.completion.BuiltInFunctionAttributeCompletionContributor"/>
-    <completion.contributor language="BUILD" implementationClass="com.google.idea.blaze.base.lang.buildfile.completion.ArgumentCompletionContributor"/>
-    <langCodeStyleSettingsProvider implementation="com.google.idea.blaze.base.lang.buildfile.formatting.BuildLanguageCodeStyleSettingsProvider"/>
-    <codeStyleSettingsProvider implementation="com.google.idea.blaze.base.lang.buildfile.formatting.BuildCodeStyleSettingsProvider"/>
-    <editor.backspaceModeOverride language="BUILD" implementationClass="com.intellij.codeInsight.editorActions.SmartBackspaceDisabler"/>
-  </extensions>
-
-  <extensions defaultExtensionNs="com.intellij.lang">
-    <syntaxHighlighterFactory language="BUILD" implementationClass="com.google.idea.blaze.base.lang.buildfile.highlighting.BuildSyntaxHighlighterFactory"/>
-    <parserDefinition language="BUILD" implementationClass="com.google.idea.blaze.base.lang.buildfile.parser.BuildParserDefinition"/>
-    <namesValidator language="BUILD" implementationClass="com.google.idea.blaze.base.lang.buildfile.refactor.BuildNamesValidator"/>
-    <braceMatcher language="BUILD" implementationClass="com.google.idea.blaze.base.lang.buildfile.formatting.BuildBraceMatcher"/>
-    <commenter language="BUILD" implementationClass="com.google.idea.blaze.base.lang.buildfile.formatting.BuildCommenter"/>
-    <foldingBuilder language="BUILD" implementationClass="com.google.idea.blaze.base.lang.buildfile.formatting.BuildFileFoldingBuilder"/>
-    <psiStructureViewFactory language="BUILD" implementationClass="com.google.idea.blaze.base.lang.buildfile.views.BuildStructureViewFactory"/>
-    <findUsagesProvider language="BUILD" implementationClass="com.google.idea.blaze.base.lang.buildfile.findusages.BuildFindUsagesProvider"/>
-    <refactoringSupport language="BUILD" implementationClass="com.google.idea.blaze.base.lang.buildfile.refactor.BuildRefactoringSupportProvider"/>
-  </extensions>
-
-  <extensionPoints>
-    <extensionPoint qualifiedName="com.google.idea.blaze.base.lang.buildfile.DumbAnnotator" interface="com.google.idea.blaze.base.lang.buildfile.validation.BuildAnnotator"/>
-    <extensionPoint qualifiedName="com.google.idea.blaze.base.lang.buildfile.Annotator" interface="com.google.idea.blaze.base.lang.buildfile.validation.BuildAnnotator"/>
-  </extensionPoints>
-
-  <application-components>
-    <component>
-      <implementation-class>com.google.idea.blaze.base.plugin.BlazeSpecificInitializer</implementation-class>
-    </component>
-    <component>
-      <implementation-class>com.google.idea.blaze.base.plugin.dependency.ProjectDependencyMigration</implementation-class>
-    </component>
-  </application-components>
-
-  <project-components>
-    <component>
-      <implementation-class>com.google.idea.blaze.base.prefetch.PrefetchServiceProjectComponent</implementation-class>
-      <skipForDefaultProject/>
-    </component>
-  </project-components>
-
-  <extensionPoints>
-    <extensionPoint qualifiedName="com.google.idea.blaze.SyncListener" interface="com.google.idea.blaze.base.sync.SyncListener"/>
-    <extensionPoint qualifiedName="com.google.idea.blaze.SyncPlugin" interface="com.google.idea.blaze.base.sync.BlazeSyncPlugin"/>
-    <extensionPoint qualifiedName="com.google.idea.blaze.RuleConfigurationFactory" interface="com.google.idea.blaze.base.run.BlazeRuleConfigurationFactory"/>
-    <extensionPoint qualifiedName="com.google.idea.blaze.Prefetcher"
-                    interface="com.google.idea.blaze.base.prefetch.Prefetcher"/>
-    <extensionPoint qualifiedName="com.google.idea.blaze.PsiFileProvider" interface="com.google.idea.blaze.base.lang.buildfile.search.PsiFileProvider"/>
-    <extensionPoint qualifiedName="com.google.idea.blaze.VcsHandler"
-                    interface="com.google.idea.blaze.base.vcs.BlazeVcsHandler"/>
-    <extensionPoint qualifiedName="com.google.idea.blaze.BlazeWizardOptionProvider"
-                    interface="com.google.idea.blaze.base.wizard2.BlazeWizardOptionProvider"/>
-    <extensionPoint qualifiedName="com.google.idea.blaze.DefaultSdkProvider"
-                    interface="com.google.idea.blaze.base.sync.sdk.DefaultSdkProvider"/>
-    <extensionPoint qualifiedName="com.google.idea.blaze.BuildFlagsProvider" interface="com.google.idea.blaze.base.command.BuildFlagsProvider"/>
-    <extensionPoint qualifiedName="com.google.idea.blaze.BuildSystemProvider" interface="com.google.idea.blaze.base.bazel.BuildSystemProvider"/>
-    <extensionPoint qualifiedName="com.google.idea.blaze.BuildifierBinaryProvider" interface="com.google.idea.blaze.base.buildmodifier.BuildifierBinaryProvider"/>
-    <extensionPoint qualifiedName="com.google.idea.blaze.LoggingService" interface="com.google.idea.blaze.base.metrics.LoggingService"/>
-  </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.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"/>
-    <BuildFlagsProvider implementation="com.google.idea.blaze.base.command.BuildFlagsProviderImpl"/>
-    <VcsHandler implementation="com.google.idea.blaze.base.vcs.git.GitBlazeVcsHandler"/>
-    <VcsHandler implementation="com.google.idea.blaze.base.vcs.FallbackBlazeVcsHandler" order="last" id="fallback"/>
-    <BuildSystemProvider implementation="com.google.idea.blaze.base.bazel.BazelBuildSystemProvider" order="last"/>
-    <BuildifierBinaryProvider implementation="com.google.idea.blaze.base.buildmodifier.BazelBuildifierBinaryProvider"/>
-  </extensions>
-
-</idea-plugin>
diff --git a/blaze-base/src/com/google/idea/blaze/base/actions/BlazeAction.java b/blaze-base/src/com/google/idea/blaze/base/actions/BlazeAction.java
deleted file mode 100644
index 47babbc..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/actions/BlazeAction.java
+++ /dev/null
@@ -1,64 +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.actions;
-
-import com.google.idea.blaze.base.settings.Blaze;
-import com.intellij.openapi.actionSystem.AnAction;
-import com.intellij.openapi.actionSystem.AnActionEvent;
-import com.intellij.openapi.project.Project;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import javax.swing.*;
-
-/**
- * Base class action that hides for non-blaze projects.
- */
-public abstract class BlazeAction extends AnAction {
-  protected BlazeAction() {
-  }
-
-  protected BlazeAction(Icon icon) {
-    super(icon);
-  }
-
-  protected BlazeAction(@Nullable String text) {
-    super(text);
-  }
-
-  protected BlazeAction(@Nullable String text, @Nullable String description, @Nullable Icon icon) {
-    super(text, description, icon);
-  }
-
-  @Override
-  public final void update(AnActionEvent e) {
-    if (!isBlazeProject(e)) {
-      e.getPresentation().setEnabledAndVisible(false);
-      return;
-    }
-
-    e.getPresentation().setEnabledAndVisible(true);
-    doUpdate(e);
-  }
-
-  protected void doUpdate(@NotNull AnActionEvent e) {
-  }
-
-  private static boolean isBlazeProject(@NotNull AnActionEvent e) {
-    Project project = e.getProject();
-    return project != null && Blaze.isBlazeProject(project);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/actions/BlazeCompileFileAction.java b/blaze-base/src/com/google/idea/blaze/base/actions/BlazeCompileFileAction.java
deleted file mode 100644
index 13dbaa8..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/actions/BlazeCompileFileAction.java
+++ /dev/null
@@ -1,125 +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.actions;
-
-import com.google.common.collect.ImmutableCollection;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.async.executor.BlazeExecutor;
-import com.google.idea.blaze.base.experiments.ExperimentScope;
-import com.google.idea.blaze.base.metrics.Action;
-import com.google.idea.blaze.base.model.primitives.Label;
-import com.google.idea.blaze.base.model.primitives.TargetExpression;
-import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
-import com.google.idea.blaze.base.model.BlazeProjectData;
-import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
-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.*;
-import com.google.idea.blaze.base.util.SaveUtil;
-import com.google.idea.blaze.base.sync.aspects.BlazeIdeInterface;
-import com.intellij.openapi.actionSystem.AnActionEvent;
-import com.intellij.openapi.actionSystem.CommonDataKeys;
-import com.intellij.openapi.actionSystem.Presentation;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.vfs.LocalFileSystem;
-import com.intellij.openapi.vfs.VirtualFile;
-import org.jetbrains.annotations.NotNull;
-
-import java.io.File;
-import java.util.List;
-
-public class BlazeCompileFileAction extends BlazeAction {
-  private static final Logger LOG = Logger.getInstance(BlazeCompileFileAction.class);
-
-  public BlazeCompileFileAction() {
-    super("Compile file");
-  }
-
-  @Override
-  protected void doUpdate(@NotNull AnActionEvent e) {
-    // IntelliJ uses different logic for 1 vs many module selection. When many modules are selected
-    // modules with more than 1 content root are ignored
-    // (ProjectViewImpl#moduleBySingleContentRoot).
-    if (getTargets(e).isEmpty()) {
-      Presentation presentation = e.getPresentation();
-      presentation.setEnabled(false);
-    }
-  }
-
-  @Override
-  public void actionPerformed(AnActionEvent e) {
-    Project project = e.getProject();
-    if (project != null) {
-      ImmutableCollection<Label> targets = getTargets(e);
-      buildSourceFile(project, targets);
-    }
-  }
-
-  private ImmutableCollection<Label> getTargets(AnActionEvent e) {
-    Project project = e.getProject();
-    VirtualFile virtualFile = e.getData(CommonDataKeys.VIRTUAL_FILE);
-    if (project != null && virtualFile != null) {
-      return SourceToRuleMap.getInstance(project).getTargetsForSourceFile(new File(virtualFile.getPath()));
-    }
-    return ImmutableList.of();
-  }
-
-  private static void buildSourceFile(
-    @NotNull Project project,
-    @NotNull ImmutableCollection<Label> targets) {
-    BlazeProjectData blazeProjectData = BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
-    if (blazeProjectData == null || targets.isEmpty()) {
-      return;
-    }
-    final ProjectViewSet projectViewSet = ProjectViewManager.getInstance(project).getProjectViewSet();
-    if (projectViewSet == null) {
-      return;
-    }
-    BlazeExecutor.submitTask(project, new ScopedTask() {
-      @Override
-      public void execute(@NotNull BlazeContext context) {
-        context
-          .push(new ExperimentScope())
-          .push(new BlazeConsoleScope.Builder(project).build())
-          .push(new IssuesScope(project))
-          .push(new TimingScope("Make"))
-          .push(new LoggedTimingScope(project, Action.MAKE_MODULE_TOTAL_TIME))
-          .push(new NotificationScope(
-            project,
-            "Make",
-            "Make module",
-            "Make module completed successfully",
-            "Make module failed"
-          ))
-        ;
-
-        WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProject(project);
-
-        SaveUtil.saveAllFiles();
-        BlazeIdeInterface blazeIdeInterface = BlazeIdeInterface.getInstance();
-
-        List<TargetExpression> targetExpressions = Lists.newArrayList(targets);
-        blazeIdeInterface.resolveIdeArtifacts(project, context, workspaceRoot, projectViewSet, targetExpressions);
-        LocalFileSystem.getInstance().refresh(true);
-      }
-    });
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/actions/BlazeMakeProjectAction.java b/blaze-base/src/com/google/idea/blaze/base/actions/BlazeMakeProjectAction.java
deleted file mode 100644
index 51acab9..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/actions/BlazeMakeProjectAction.java
+++ /dev/null
@@ -1,99 +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.actions;
-
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.async.executor.BlazeExecutor;
-import com.google.idea.blaze.base.util.SaveUtil;
-import com.google.idea.blaze.base.experiments.ExperimentScope;
-import com.google.idea.blaze.base.sync.aspects.BlazeIdeInterface;
-import com.google.idea.blaze.base.metrics.Action;
-import com.google.idea.blaze.base.model.primitives.TargetExpression;
-import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
-import com.google.idea.blaze.base.model.BlazeProjectData;
-import com.google.idea.blaze.base.projectview.ProjectViewManager;
-import com.google.idea.blaze.base.projectview.ProjectViewSet;
-import com.google.idea.blaze.base.projectview.section.sections.TargetSection;
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.google.idea.blaze.base.scope.ScopedTask;
-import com.google.idea.blaze.base.scope.scopes.*;
-import com.google.idea.blaze.base.settings.Blaze;
-import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
-import com.intellij.openapi.actionSystem.AnActionEvent;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.vfs.LocalFileSystem;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.List;
-
-public class BlazeMakeProjectAction extends BlazeAction {
-
-  public BlazeMakeProjectAction() {
-    super("Make Project");
-  }
-
-  @Override
-  public final void actionPerformed(AnActionEvent e) {
-    Project project = e.getProject();
-    if (project != null && Blaze.isBlazeProject(project)) {
-      buildBlazeProject(project);
-    }
-  }
-
-  protected void buildBlazeProject(@NotNull final Project project) {
-
-    BlazeExecutor.submitTask(project, new ScopedTask() {
-      @Override
-      public void execute(@NotNull BlazeContext context) {
-        context
-          .push(new ExperimentScope())
-          .push(new BlazeConsoleScope.Builder(project).build())
-          .push(new IssuesScope(project))
-          .push(new TimingScope("Make"))
-          .push(new LoggedTimingScope(project, Action.MAKE_PROJECT_TOTAL_TIME))
-          .push(new NotificationScope(
-            project,
-            "Make",
-            "Make project",
-            "Make project completed successfully",
-            "Make project failed"))
-        ;
-
-        BlazeProjectData blazeProjectData = BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
-        if (blazeProjectData == null) {
-          return;
-        }
-        ProjectViewSet projectViewSet = ProjectViewManager.getInstance(project).reloadProjectView(
-          context,
-          blazeProjectData.workspacePathResolver
-        );
-        if (projectViewSet == null) {
-          return;
-        }
-
-        WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProject(project);
-
-        List<TargetExpression> targets = Lists.newArrayList();
-        targets.addAll(projectViewSet.listItems(TargetSection.KEY));
-
-        SaveUtil.saveAllFiles();
-        BlazeIdeInterface blazeIdeInterface = BlazeIdeInterface.getInstance();
-        blazeIdeInterface.resolveIdeArtifacts(project, context, workspaceRoot, projectViewSet, targets);
-        LocalFileSystem.getInstance().refresh(true);
-      }
-    });
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/actions/BlazeMenuGroup.java b/blaze-base/src/com/google/idea/blaze/base/actions/BlazeMenuGroup.java
deleted file mode 100644
index 36b45c9..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/actions/BlazeMenuGroup.java
+++ /dev/null
@@ -1,45 +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.actions;
-
-import com.google.idea.blaze.base.settings.Blaze;
-import com.intellij.openapi.actionSystem.AnActionEvent;
-import com.intellij.openapi.actionSystem.DefaultActionGroup;
-import com.intellij.openapi.project.Project;
-import org.jetbrains.annotations.NotNull;
-
-public class BlazeMenuGroup extends DefaultActionGroup {
-  @Override
-  public final void update(AnActionEvent e) {
-    if (!isBlazeProject(e)) {
-      e.getPresentation().setEnabledAndVisible(false);
-      return;
-    }
-
-    e.getPresentation().setEnabledAndVisible(true);
-    e.getPresentation().setText(Blaze.buildSystemName(e.getProject()));
-  }
-
-  @Override
-  public boolean isDumbAware() {
-    return true;
-  }
-
-  private static boolean isBlazeProject(@NotNull AnActionEvent e) {
-    Project project = e.getProject();
-    return project != null && Blaze.isBlazeProject(project);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/actions/BlazeToggleAction.java b/blaze-base/src/com/google/idea/blaze/base/actions/BlazeToggleAction.java
deleted file mode 100644
index 9480fec..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/actions/BlazeToggleAction.java
+++ /dev/null
@@ -1,61 +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.actions;
-
-import com.google.idea.blaze.base.settings.Blaze;
-import com.intellij.openapi.actionSystem.AnActionEvent;
-import com.intellij.openapi.actionSystem.ToggleAction;
-import com.intellij.openapi.project.Project;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import javax.swing.*;
-
-/**
- * Base class toggle action that hides for non-blaze projects.
- */
-public abstract class BlazeToggleAction extends ToggleAction {
-  protected BlazeToggleAction() {
-  }
-
-  protected BlazeToggleAction(@Nullable String text) {
-    super(text);
-  }
-
-  protected BlazeToggleAction(@Nullable String text, @Nullable String description, @Nullable Icon icon) {
-    super(text, description, icon);
-  }
-
-  @Override
-  public final void update(AnActionEvent e) {
-    if (!isBlazeProject(e)) {
-      e.getPresentation().setEnabledAndVisible(false);
-      return;
-    }
-
-    e.getPresentation().setEnabledAndVisible(true);
-    super.update(e);
-    doUpdate(e);
-  }
-
-  protected void doUpdate(@NotNull AnActionEvent e) {
-  }
-
-  private static boolean isBlazeProject(@NotNull AnActionEvent e) {
-    Project project = e.getProject();
-    return project != null && Blaze.isBlazeProject(project);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/async/AsyncUtil.java b/blaze-base/src/com/google/idea/blaze/base/async/AsyncUtil.java
deleted file mode 100644
index 1e8a525..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/async/AsyncUtil.java
+++ /dev/null
@@ -1,58 +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.async;
-
-import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.util.ui.UIUtil;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Async utilities.
- */
-public class AsyncUtil {
-  public static void executeProjectChangeAction(@NotNull final Runnable task) throws Throwable {
-    final ValueHolder<Throwable> error = new ValueHolder<Throwable>();
-
-    executeOnEdt(new Runnable() {
-      @Override
-      public void run() {
-        ApplicationManager.getApplication().runWriteAction(new Runnable() {
-          @Override
-          public void run() {
-            try {
-              task.run();
-            } catch (Throwable t) {
-              error.value = t;
-            }
-          }
-        });
-      }
-    });
-
-    if (error.value != null) {
-      throw error.value;
-    }
-  }
-
-  private static void executeOnEdt(@NotNull Runnable task) {
-    if (ApplicationManager.getApplication().isDispatchThread()) {
-      task.run();
-    }
-    else {
-      UIUtil.invokeAndWaitIfNeeded(task);
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/async/FutureUtil.java b/blaze-base/src/com/google/idea/blaze/base/async/FutureUtil.java
deleted file mode 100644
index 3c6b9e3..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/async/FutureUtil.java
+++ /dev/null
@@ -1,110 +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.async;
-
-import com.google.common.util.concurrent.ListenableFuture;
-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;
-import com.google.idea.blaze.base.scope.output.PrintOutput;
-import com.google.idea.blaze.base.scope.scopes.TimingScope;
-import com.intellij.openapi.diagnostic.Logger;
-
-import java.util.concurrent.ExecutionException;
-
-/**
- * Utilities operating on futures.
- */
-public class FutureUtil {
-  public static class FutureResult<T> {
-    private final T result;
-    private final boolean success;
-
-    FutureResult(T result) {
-      this.result = result;
-      this.success = true;
-    }
-
-    FutureResult() {
-      this.result = null;
-      this.success = false;
-    }
-
-    public T result() {
-      return result;
-    }
-
-    public boolean success() {
-      return success;
-    }
-  }
-
-  public static class Builder<T> {
-    private static final Logger LOG = Logger.getInstance(FutureUtil.class);
-    private final BlazeContext context;
-    private final ListenableFuture<T> future;
-    private String timingCategory;
-    private String errorMessage;
-    private String progressMessage;
-
-    Builder(BlazeContext context, ListenableFuture<T> future) {
-      this.context = context;
-      this.future = future;
-    }
-
-    public Builder<T> timed(String timingCategory) {
-      this.timingCategory = timingCategory;
-      return this;
-    }
-    public Builder<T> withProgressMessage(String message) {
-      this.progressMessage = message;
-      return this;
-    }
-    public Builder<T> onError(String errorMessage) {
-      this.errorMessage = errorMessage;
-      return this;
-    }
-    public FutureResult<T> run() {
-      return Scope.push(context, (childContext) -> {
-        if (timingCategory != null) {
-          childContext.push(new TimingScope(timingCategory));
-        }
-        if (progressMessage != null) {
-          childContext.output(new PrintOutput(progressMessage));
-        }
-        try {
-          return new FutureResult<>(future.get());
-        }
-        catch (InterruptedException e) {
-          Thread.currentThread().interrupt();
-          context.setCancelled();
-        }
-        catch (ExecutionException e) {
-          LOG.error(e);
-          if (errorMessage != null) {
-            IssueOutput.error(errorMessage).submit(childContext);
-          }
-          context.setHasError();
-        }
-        return new FutureResult<>();
-      });
-    }
-  }
-
-  public static <T> Builder<T> waitForFuture(BlazeContext context, ListenableFuture<T> future) {
-    return new Builder<>(context, future);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/async/ValueHolder.java b/blaze-base/src/com/google/idea/blaze/base/async/ValueHolder.java
deleted file mode 100644
index 97f514d..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/async/ValueHolder.java
+++ /dev/null
@@ -1,23 +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.async;
-
-/**
- * Simple wrapper to work around Java's final limitation.
- */
-public class ValueHolder<T> {
-  public T value;
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/async/executor/BlazeExecutor.java b/blaze-base/src/com/google/idea/blaze/base/async/executor/BlazeExecutor.java
deleted file mode 100644
index 49d11c0..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/async/executor/BlazeExecutor.java
+++ /dev/null
@@ -1,130 +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.async.executor;
-
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.intellij.openapi.components.ServiceManager;
-import com.intellij.openapi.progress.PerformInBackgroundOption;
-import com.intellij.openapi.progress.ProgressManager;
-import com.intellij.openapi.progress.Progressive;
-import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator;
-import com.intellij.openapi.progress.util.AbstractProgressIndicatorExBase;
-import com.intellij.openapi.progress.util.ProgressWindow;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.Computable;
-import com.intellij.util.ui.UIUtil;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.util.concurrent.Callable;
-
-/**
- * Shared thread pool for blaze tasks.
- */
-public abstract class BlazeExecutor {
-  public static enum Modality {
-    MODAL, // This task must start in the foreground and stay there.
-    BACKGROUNDABLE, // This task will start in the foreground, but can be sent to the background.
-    ALWAYS_BACKGROUND // This task will start in the background and stay there.
-  }
-
-  @NotNull
-  public static BlazeExecutor getInstance() {
-    return ServiceManager.getService(BlazeExecutor.class);
-  }
-
-  public abstract <T> ListenableFuture<T> submit(Callable<T> callable);
-
-  public abstract ListeningExecutorService getExecutor();
-
-  public static ListenableFuture<Void> submitTask(
-    @Nullable final Project project,
-    @NotNull final Progressive progressive) {
-    return submitTask(project, "", progressive);
-  }
-
-  public static ListenableFuture<Void> submitTask(
-    @Nullable final Project project,
-    @NotNull final String title,
-    @NotNull final Progressive progressive) {
-    return submitTask(
-      project,
-      title,
-      true /* cancelable */,
-      Modality.ALWAYS_BACKGROUND,
-      progressive);
-  }
-
-  public static ListenableFuture<Void> submitTask(
-    @Nullable final Project project,
-    @NotNull final String title,
-    final boolean cancelable,
-    final Modality modality,
-    @NotNull final Progressive progressive) {
-
-    // The progress indicator must be created on the UI thread.
-    final ProgressWindow indicator = UIUtil.invokeAndWaitIfNeeded(new Computable<ProgressWindow>() {
-      @Override
-      public ProgressWindow compute() {
-        if (modality == Modality.MODAL) {
-          ProgressWindow indicator = new ProgressWindow(cancelable, project);
-          indicator.setTitle(title);
-          return indicator;
-        }
-        else {
-          PerformInBackgroundOption backgroundOption = modality == Modality.BACKGROUNDABLE ?
-                                                       PerformInBackgroundOption.DEAF :
-                                                       PerformInBackgroundOption.ALWAYS_BACKGROUND;
-          return new BackgroundableProcessIndicator(
-            project,
-            title,
-            backgroundOption,
-            "Cancel",
-            "Cancel",
-            cancelable
-          );
-        }
-      }
-    });
-
-    indicator.setIndeterminate(true);
-    indicator.start();
-    final Runnable process = new Runnable() {
-      @Override
-      public void run() {
-        progressive.run(indicator);
-      }
-    };
-    final ListenableFuture<Void> future = getInstance().submit(new Callable<Void>() {
-      @Override
-      public Void call() throws Exception {
-        ProgressManager.getInstance().runProcess(process, indicator);
-        return null;
-      }
-    });
-    if (cancelable) {
-      indicator.addStateDelegate(new AbstractProgressIndicatorExBase() {
-        @Override
-        public void cancel() {
-          super.cancel();
-          future.cancel(true);
-        }
-      });
-    }
-    return future;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/async/executor/BlazeExecutorImpl.java b/blaze-base/src/com/google/idea/blaze/base/async/executor/BlazeExecutorImpl.java
deleted file mode 100644
index f8407b6..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/async/executor/BlazeExecutorImpl.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.async.executor;
-
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.common.util.concurrent.MoreExecutors;
-
-import java.util.concurrent.*;
-
-/**
- * Executes blaze tasks on the an executor.
- */
-public class BlazeExecutorImpl extends BlazeExecutor {
-
-  private final ListeningExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(16));
-
-  @Override
-  public <T> ListenableFuture<T> submit(Callable<T> callable) {
-    return executorService.submit(callable);
-  }
-
-  @Override
-  public ListeningExecutorService getExecutor() {
-    return executorService;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/async/executor/TransientExecutor.java b/blaze-base/src/com/google/idea/blaze/base/async/executor/TransientExecutor.java
deleted file mode 100644
index f08b0c2..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/async/executor/TransientExecutor.java
+++ /dev/null
@@ -1,30 +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.async.executor;
-
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-
-/**
- * An executor that grows to a finite number of threads and times them out quickly.
- */
-public class TransientExecutor extends ThreadPoolExecutor {
-  public TransientExecutor(int maxThreads) {
-    super(maxThreads, maxThreads, 200, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
-    allowCoreThreadTimeOut(true);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/async/process/ExternalTask.java b/blaze-base/src/com/google/idea/blaze/base/async/process/ExternalTask.java
deleted file mode 100644
index 8fd2de2..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/async/process/ExternalTask.java
+++ /dev/null
@@ -1,244 +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.async.process;
-
-import com.google.common.base.Joiner;
-import com.google.common.base.Throwables;
-import com.google.common.io.ByteStreams;
-import com.google.idea.blaze.base.command.BlazeCommand;
-import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.google.idea.blaze.base.scope.BlazeScope;
-import com.google.idea.blaze.base.scope.Scope;
-import com.google.idea.blaze.base.scope.output.IssueOutput;
-import com.google.idea.blaze.base.scope.output.PrintOutput;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.progress.ProcessCanceledException;
-import com.intellij.util.SystemProperties;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.List;
-
-/**
- * Invokes an external process
- */
-public class ExternalTask {
-  private static final Logger LOG = Logger.getInstance(ExternalTask.class);
-
-  static final OutputStream NULL_STREAM = ByteStreams.nullOutputStream();
-
-  public static class Builder {
-    @NotNull
-    private final File workingDirectory;
-    @NotNull
-    private final List<String> command;
-    @Nullable
-    private BlazeContext context;
-    @Nullable
-    private OutputStream stdout;
-    @Nullable
-    private OutputStream stderr;
-    boolean redirectErrorStream = false;
-
-    private Builder(
-      @NotNull WorkspaceRoot workspaceRoot,
-      @NotNull List<String> command) {
-      this(workspaceRoot.directory(), command);
-    }
-
-    private Builder(
-      @NotNull File workingDirectory,
-      @NotNull List<String> command) {
-      this.workingDirectory = workingDirectory;
-      this.command = command;
-    }
-
-    @NotNull
-    public Builder context(@Nullable BlazeContext context) {
-      this.context = context;
-      return this;
-    }
-
-    @NotNull
-    public Builder redirectStderr(boolean redirectStderr) {
-      this.redirectErrorStream = redirectStderr;
-      return this;
-    }
-
-    @NotNull
-    public Builder stdout(@Nullable OutputStream stdout) {
-      this.stdout = stdout;
-      return this;
-    }
-
-    @NotNull
-    public Builder stderr(@Nullable OutputStream stderr) {
-      this.stderr = stderr;
-      return this;
-    }
-
-    @NotNull
-    public ExternalTask build() {
-      return new ExternalTask(
-        context,
-        workingDirectory,
-        command,
-        stdout,
-        stderr,
-        redirectErrorStream
-      );
-    }
-  }
-
-  @NotNull
-  private final File workingDirectory;
-
-  @NotNull
-  private final List<String> command;
-
-  @Nullable
-  private final BlazeContext parentContext;
-
-  private final boolean redirectErrorStream;
-
-  @NotNull
-  private final OutputStream stdout;
-
-  @NotNull
-  private final OutputStream stderr;
-
-  private ExternalTask(
-    @Nullable BlazeContext context,
-    @NotNull File workingDirectory,
-    @NotNull List<String> command,
-    @Nullable OutputStream stdout,
-    @Nullable OutputStream stderr,
-    boolean redirectErrorStream) {
-    this.workingDirectory = workingDirectory;
-    this.command = command;
-    this.parentContext = context;
-    this.redirectErrorStream = redirectErrorStream;
-    this.stdout = stdout != null ? stdout : NULL_STREAM;
-    this.stderr = stderr != null ? stderr : NULL_STREAM;
-  }
-
-  public int run(BlazeScope... scopes) {
-    Integer returnValue = Scope.push(parentContext, context -> {
-      for (BlazeScope scope : scopes) {
-        context.push(scope);
-      }
-      try {
-        return invokeCommand(context);
-      } catch (ProcessCanceledException e) {
-        // Logging a ProcessCanceledException is an IJ error - mark context canceled instead.
-        context.setCancelled();
-      }
-      return -1;
-    });
-    return returnValue != null ? returnValue : -1;
-  }
-
-  private static void closeQuietly(OutputStream stream) {
-    try {
-      stream.close();
-    } catch (IOException e) {
-      Throwables.propagate(e);
-    }
-  }
-
-  private int invokeCommand(BlazeContext context) {
-    String executingTasksText = "Command: "
-                                + Joiner.on(" ").join(command)
-                                + SystemProperties.getLineSeparator()
-                                + SystemProperties.getLineSeparator();
-
-    context.output(new PrintOutput(executingTasksText));
-
-    try {
-      if (context.isEnding()) {
-        return -1;
-      }
-      ProcessBuilder builder = new ProcessBuilder()
-        .command(command)
-        .redirectErrorStream(redirectErrorStream)
-        .directory(workingDirectory);
-      try {
-        final Process process = builder.start();
-        Thread shutdownHook = new Thread(process::destroy);
-        try {
-          Runtime.getRuntime().addShutdownHook(shutdownHook);
-          Thread stdoutThread = ProcessUtil.forwardAsync(process.getInputStream(), stdout);
-          Thread stderrThread = null;
-          if (!redirectErrorStream) {
-            stderrThread = ProcessUtil.forwardAsync(process.getErrorStream(), stderr);
-          }
-          process.waitFor();
-          stdoutThread.join();
-          if (!redirectErrorStream) {
-            stderrThread.join();
-          }
-          int exitValue = process.exitValue();
-          if (exitValue != 0) {
-            context.setHasError();
-          }
-          return exitValue;
-        }
-        catch (InterruptedException e) {
-          process.destroy();
-          throw new ProcessCanceledException();
-        }
-        finally {
-          try {
-            Runtime.getRuntime().removeShutdownHook(shutdownHook);
-          } catch (IllegalStateException e) {
-            // we can't remove a shutdown hook if we are shutting down, do nothing about it
-          }
-        }
-      }
-      catch (IOException e) {
-        LOG.warn(e);
-        IssueOutput.error(e.getMessage()).submit(context);
-      }
-    }
-    finally {
-      closeQuietly(stdout);
-      closeQuietly(stderr);
-    }
-    return -1;
-  }
-
-  public static Builder builder(
-    @NotNull File workingDirectory,
-    @NotNull List<String> command) {
-    return new Builder(workingDirectory, command);
-  }
-
-  public static Builder builder(
-    @NotNull WorkspaceRoot workspaceRoot,
-    @NotNull List<String> command) {
-    return new Builder(workspaceRoot, command);
-  }
-
-  public static Builder builder(
-    @NotNull WorkspaceRoot workspaceRoot,
-    @NotNull BlazeCommand command) {
-    return new Builder(workspaceRoot, command.toList());
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/async/process/LineProcessingOutputStream.java b/blaze-base/src/com/google/idea/blaze/base/async/process/LineProcessingOutputStream.java
deleted file mode 100644
index a0bab3d..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/async/process/LineProcessingOutputStream.java
+++ /dev/null
@@ -1,101 +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.async.process;
-
-import com.google.common.collect.Lists;
-import org.jetbrains.annotations.NotNull;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.List;
-
-/**
- * An base output stream which marshals output into newline-delimited segments for processing.
- */
-public final class LineProcessingOutputStream extends OutputStream {
-
-  public interface LineProcessor {
-    /**
-     * Process a single, complete line of output.
-     *
-     * @return Whether line processing should continue
-     */
-    boolean processLine(@NotNull String line);
-  }
-
-  @NotNull
-  private final StringBuffer stringBuffer = new StringBuffer();
-  private volatile boolean closed;
-  @NotNull
-  private final List<LineProcessor> lineProcessors;
-
-  LineProcessingOutputStream(@NotNull LineProcessor... lineProcessors) {
-    this.lineProcessors = Lists.newArrayList(lineProcessors);
-  }
-
-  public static LineProcessingOutputStream of(@NotNull LineProcessor... lineProcessors) {
-    return new LineProcessingOutputStream(lineProcessors);
-  }
-
-  @Override
-  public synchronized void write(byte[] b, int off, int len) {
-    if (!closed) {
-      String text = new String(b, off, len);
-      stringBuffer.append(text);
-
-      while (true) {
-        int lineBreakIndex = -1;
-        int lineBreakLength = 0;
-        for (int i = 0; i < stringBuffer.length(); ++i) {
-          char c = stringBuffer.charAt(i);
-          if (c == '\r' || c == '\n') {
-            lineBreakIndex = i;
-            lineBreakLength = 1;
-            if (c == '\r' && (i + 1) < stringBuffer.length() && stringBuffer.charAt(i + 1) == '\n') {
-              ++lineBreakLength;
-            }
-            break;
-          }
-        }
-
-        if (lineBreakIndex == -1) {
-          return;
-        }
-
-        String line = stringBuffer.substring(0, lineBreakIndex);
-
-        stringBuffer.delete(0, lineBreakIndex + lineBreakLength);
-
-        for (LineProcessor lineProcessor : lineProcessors) {
-          if (!lineProcessor.processLine(line)) {
-            break;
-          }
-        }
-      }
-    }
-  }
-
-  @Override
-  public void write(int b) throws IOException {
-    write(new byte[]{(byte)b}, 0, 1);
-  }
-
-  @Override
-  public void close() throws IOException {
-    closed = true;
-    super.close();
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/async/process/PrintOutputLineProcessor.java b/blaze-base/src/com/google/idea/blaze/base/async/process/PrintOutputLineProcessor.java
deleted file mode 100644
index 562b648..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/async/process/PrintOutputLineProcessor.java
+++ /dev/null
@@ -1,35 +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.async.process;
-
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.google.idea.blaze.base.scope.output.PrintOutput;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Simple adapter between stdout and context print output.
- */
-public class PrintOutputLineProcessor implements LineProcessingOutputStream.LineProcessor {
-  private final BlazeContext context;
-  public PrintOutputLineProcessor(BlazeContext context) {
-    this.context = context;
-  }
-  @Override
-  public boolean processLine(@NotNull String line) {
-    context.output(new PrintOutput(line));
-    return true;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/async/process/ProcessUtil.java b/blaze-base/src/com/google/idea/blaze/base/async/process/ProcessUtil.java
deleted file mode 100644
index 608f4d3..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/async/process/ProcessUtil.java
+++ /dev/null
@@ -1,69 +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.async.process;
-
-import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
-import com.intellij.openapi.diagnostic.Logger;
-import org.jetbrains.annotations.NotNull;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.List;
-
-public class ProcessUtil {
-  private static final Logger LOG = Logger.getInstance(ProcessUtil.class);
-
-  public static Thread forwardAsync(final InputStream input, final OutputStream output) {
-    Thread thread = new Thread(new Runnable() {
-      @Override
-      public void run() {
-        int bufferSize = 4096;
-        byte[] buffer = new byte[bufferSize];
-
-        int read = 0;
-        try {
-          read = input.read(buffer);
-          while (read != -1) {
-            output.write(buffer, 0, read);
-            read = input.read(buffer);
-          }
-        }
-        catch (IOException e) {
-          LOG.warn("Error redirecting output", e);
-        }
-      }
-    });
-    thread.start();
-    return thread;
-  }
-
-  @NotNull
-  public static String runCommand(
-    @NotNull WorkspaceRoot workspaceRoot,
-    @NotNull List<String> command
-  ) {
-    ByteArrayOutputStream output = new ByteArrayOutputStream();
-    ExternalTask.Builder builder = ExternalTask.builder(workspaceRoot, command);
-    ExternalTask task = builder
-      .redirectStderr(true)
-      .stdout(output)
-      .build();
-    task.run();
-    return output.toString();
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/bazel/BazelBuildSystemProvider.java b/blaze-base/src/com/google/idea/blaze/base/bazel/BazelBuildSystemProvider.java
deleted file mode 100644
index a0e22a2..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/bazel/BazelBuildSystemProvider.java
+++ /dev/null
@@ -1,48 +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.bazel;
-
-import com.google.common.collect.ImmutableList;
-import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
-import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
-
-/**
- * Provides the bazel build system name string.
- */
-public class BazelBuildSystemProvider implements BuildSystemProvider {
-  @Override
-  public BuildSystem buildSystem() {
-    return BuildSystem.Bazel;
-  }
-
-  @Override
-  public WorkspaceRootProvider getWorkspaceRootProvider() {
-    return BazelWorkspaceRootProvider.INSTANCE;
-  }
-
-  @Override
-  public ImmutableList<String> buildArtifactDirectories(WorkspaceRoot root) {
-    String rootDir = root.directory().getName();
-    return ImmutableList.of(
-      "bazel-bin",
-      "bazel-genfiles",
-      "bazel-out",
-      "bazel-testlogs",
-      "bazel-" + rootDir
-    );
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/bazel/BazelWorkspaceRootProvider.java b/blaze-base/src/com/google/idea/blaze/base/bazel/BazelWorkspaceRootProvider.java
deleted file mode 100644
index 95512d8..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/bazel/BazelWorkspaceRootProvider.java
+++ /dev/null
@@ -1,52 +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.bazel;
-
-import com.google.idea.blaze.base.io.FileAttributeProvider;
-import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
-
-import javax.annotation.Nullable;
-import java.io.File;
-
-/**
- * Implementation of WorkspaceHelper.
- */
-public class BazelWorkspaceRootProvider implements WorkspaceRootProvider {
-
-  public static final BazelWorkspaceRootProvider INSTANCE = new BazelWorkspaceRootProvider();
-
-  private BazelWorkspaceRootProvider() {}
-
-  /**
-   * Checks for the existence of a WORKSPACE file in the given directory.
-   */
-  @Override
-  public boolean isWorkspaceRoot(File file) {
-    return FileAttributeProvider.getInstance().isFile(new File(file, "WORKSPACE"));
-  }
-
-  @Nullable
-  @Override
-  public WorkspaceRoot findWorkspaceRoot(File file) {
-    while (file != null) {
-      if (isWorkspaceRoot(file)) {
-        return new WorkspaceRoot(file);
-      }
-      file = file.getParentFile();
-    }
-    return null;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/bazel/BuildSystemProvider.java b/blaze-base/src/com/google/idea/blaze/base/bazel/BuildSystemProvider.java
deleted file mode 100644
index 030e599..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/bazel/BuildSystemProvider.java
+++ /dev/null
@@ -1,80 +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.bazel;
-
-import com.google.common.collect.ImmutableList;
-import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
-import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
-import com.intellij.openapi.components.ServiceManager;
-import com.intellij.openapi.extensions.ExtensionPointName;
-
-import javax.annotation.Nullable;
-
-/**
- * Extension points specify the build systems supported by this plugin.<br>
- * The order of the extension points establishes a priority (highest priority first),
- * for situations where we don't have an existing project to use for context
- * (e.g. the 'import project' action).
- */
-public interface BuildSystemProvider {
-
-  ExtensionPointName<BuildSystemProvider> EP_NAME = ExtensionPointName.create("com.google.idea.blaze.BuildSystemProvider");
-
-  static BuildSystemProvider defaultBuildSystem() {
-    return EP_NAME.getExtensions()[0];
-  }
-
-  @Nullable
-  static BuildSystemProvider getBuildSystemProvider(BuildSystem buildSystem) {
-    for (BuildSystemProvider provider : EP_NAME.getExtensions()) {
-      if (provider.buildSystem() == buildSystem) {
-        return provider;
-      }
-    }
-    return null;
-  }
-
-  static boolean isBuildSystemAvailable(BuildSystem buildSystem) {
-    return getBuildSystemProvider(buildSystem) != null;
-  }
-
-  static WorkspaceRootProvider getWorkspaceRootProvider(BuildSystem buildSystem) {
-    BuildSystemProvider provider = getBuildSystemProvider(buildSystem);
-    if (provider == null) {
-      throw new RuntimeException(String.format("Build system '%s' not supported by this plugin", buildSystem));
-    }
-    return provider.getWorkspaceRootProvider();
-  }
-
-  static BuildSystemProvider getInstance() {
-    return ServiceManager.getService(BuildSystemProvider.class);
-  }
-
-  /**
-   * Returns the default build system for this application. This should only be
-   * called in situations where it doesn't make sense to use the current project.<br>
-   * Otherwise, use {@link com.google.idea.blaze.base.settings.Blaze#getBuildSystem}
-   */
-  BuildSystem buildSystem();
-
-  WorkspaceRootProvider getWorkspaceRootProvider();
-
-  /**
-   * Directories containing artifacts produced during the build process.
-   */
-  ImmutableList<String> buildArtifactDirectories(WorkspaceRoot root);
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/bazel/WorkspaceRootProvider.java b/blaze-base/src/com/google/idea/blaze/base/bazel/WorkspaceRootProvider.java
deleted file mode 100644
index a9bf30d..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/bazel/WorkspaceRootProvider.java
+++ /dev/null
@@ -1,47 +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.bazel;
-
-import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
-
-import javax.annotation.Nullable;
-import java.io.File;
-
-/**
- * Utility methods for working with workspace paths.
- */
-public interface WorkspaceRootProvider {
-
-  /**
-   * Checks whether the given directory is a valid workspace root.
-   */
-  boolean isWorkspaceRoot(File file);
-
-  /**
-   * Checks whether the file or one of its ancestors is a valid workspace root.<br>
-   * This should only be called when no Project is available. Otherwise, use WorkspaceRoot.isInWorkspace.
-   */
-  default boolean isInWorkspace(File file) {
-    return findWorkspaceRoot(file) != null;
-  }
-
-  /**
-   * If the given file is inside a workspace, returns the workspace root. Otherwise returns null.
-   */
-  @Nullable
-  WorkspaceRoot findWorkspaceRoot(File file);
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/buildmap/FileToBuildMap.java b/blaze-base/src/com/google/idea/blaze/base/buildmap/FileToBuildMap.java
deleted file mode 100644
index ca19f19..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/buildmap/FileToBuildMap.java
+++ /dev/null
@@ -1,59 +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.buildmap;
-
-import com.google.common.collect.ImmutableList;
-import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
-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.intellij.openapi.components.ServiceManager;
-import com.intellij.openapi.project.Project;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.Objects;
-import java.util.stream.Collectors;
-
-/**
- * Map of file -> BUILD files.
- */
-public class FileToBuildMap {
-  private final Project project;
-
-  public static FileToBuildMap getInstance(Project project) {
-    return ServiceManager.getService(project, FileToBuildMap.class);
-  }
-
-  public FileToBuildMap(Project project) {
-    this.project = project;
-  }
-
-  public Collection<File> getBuildFilesForFile(File file) {
-    BlazeProjectData blazeProjectData = BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
-    if (blazeProjectData == null) {
-      return ImmutableList.of();
-    }
-    return SourceToRuleMap.getInstance(project).getTargetsForSourceFile(file)
-      .stream()
-      .map(blazeProjectData.ruleMap::get)
-      .filter(Objects::nonNull)
-      .map((ruleIdeInfo) -> ruleIdeInfo.buildFile)
-      .filter(Objects::nonNull)
-      .map(ArtifactLocation::getFile)
-      .collect(Collectors.toList());
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/buildmap/OpenCorrespondingBuildFile.java b/blaze-base/src/com/google/idea/blaze/base/buildmap/OpenCorrespondingBuildFile.java
deleted file mode 100644
index afb3c62..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/buildmap/OpenCorrespondingBuildFile.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.buildmap;
-
-import com.google.common.collect.Iterables;
-import com.google.idea.blaze.base.actions.BlazeAction;
-import com.google.idea.blaze.base.metrics.Action;
-import com.google.idea.blaze.base.metrics.LoggingService;
-import com.intellij.ide.actions.OpenFileAction;
-import com.intellij.openapi.actionSystem.*;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.vfs.VirtualFile;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.io.File;
-import java.util.Collection;
-
-public class OpenCorrespondingBuildFile extends BlazeAction {
-  @Override
-  public void actionPerformed(AnActionEvent e) {
-    Project project = e.getProject();
-    if (project == null) {
-      return;
-    }
-    VirtualFile virtualFile = e.getData(CommonDataKeys.VIRTUAL_FILE);
-    File file = getBuildFile(project, virtualFile);
-    if (file == null) {
-      return;
-    }
-    OpenFileAction.openFile(file.getPath(), project);
-    LoggingService.reportEvent(project, Action.OPEN_CORRESPONDING_BUILD_FILE);
-  }
-
-  @Nullable
-  private File getBuildFile(@Nullable Project project, @Nullable VirtualFile virtualFile) {
-    if (project == null) {
-      return null;
-    }
-    if (virtualFile == null) {
-      return null;
-    }
-    File file = new File(virtualFile.getPath());
-    Collection<File> fileInfoList = FileToBuildMap.getInstance(project).getBuildFilesForFile(file);
-    return Iterables.getFirst(fileInfoList, null);
-  }
-
-  @Override
-  protected void doUpdate(@NotNull AnActionEvent e) {
-    Presentation presentation = e.getPresentation();
-    DataContext dataContext = e.getDataContext();
-    VirtualFile virtualFile = CommonDataKeys.VIRTUAL_FILE.getData(dataContext);
-    Project project = CommonDataKeys.PROJECT.getData(dataContext);
-    boolean visible = (project != null && virtualFile != null);
-    boolean enabled = getBuildFile(project, virtualFile) != null;
-    presentation.setVisible(visible || ActionPlaces.isMainMenuOrActionSearch(e.getPlace()));
-    presentation.setEnabled(enabled);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/buildmodifier/BazelBuildifierBinaryProvider.java b/blaze-base/src/com/google/idea/blaze/base/buildmodifier/BazelBuildifierBinaryProvider.java
deleted file mode 100644
index caf6389..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/buildmodifier/BazelBuildifierBinaryProvider.java
+++ /dev/null
@@ -1,35 +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.buildmodifier;
-
-import com.google.idea.blaze.base.util.BlazeHelperBinaryUtil;
-
-import javax.annotation.Nullable;
-import java.io.File;
-
-/**
- * Provides the bazel buildifier binary.
- */
-public class BazelBuildifierBinaryProvider implements BuildifierBinaryProvider {
-
-  private static final String BUILDIFIER_BINARY_PATH = "/blaze-base/resources/binaries/bazel-buildifier";
-
-  @Nullable
-  @Override
-  public File getBuildifierBinary() {
-    return BlazeHelperBinaryUtil.getBlazeHelperBinary(BUILDIFIER_BINARY_PATH);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/buildmodifier/BuildFileFormatter.java b/blaze-base/src/com/google/idea/blaze/base/buildmodifier/BuildFileFormatter.java
deleted file mode 100644
index bb7f66d..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/buildmodifier/BuildFileFormatter.java
+++ /dev/null
@@ -1,83 +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.buildmodifier;
-
-import javax.annotation.Nullable;
-import java.io.*;
-
-/**
- * Formats BUILD files using 'buildifier'
- */
-public class BuildFileFormatter {
-
-  @Nullable
-  private static File getBuildifierBinary() {
-    for (BuildifierBinaryProvider provider : BuildifierBinaryProvider.EP_NAME.getExtensions()) {
-      File file = provider.getBuildifierBinary();
-      if (file != null) {
-        return file;
-      }
-    }
-    return null;
-  }
-
-  static String formatText(String text) {
-    try {
-      File buildifierBinary = getBuildifierBinary();
-      if (buildifierBinary == null) {
-        return null;
-      }
-      File file = createTempFile(text);
-      formatFile(file, buildifierBinary.getPath());
-      String formattedFile = readFile(file);
-      file.delete();
-      return formattedFile;
-    } catch (IOException e) {
-      e.printStackTrace();
-    }
-    return text;
-  }
-
-  private static void formatFile(File file, String buildifierBinaryPath) throws IOException {
-    ProcessBuilder builder = new ProcessBuilder(buildifierBinaryPath, file.getAbsolutePath());
-    try {
-      builder.start().waitFor();
-    } catch (InterruptedException e) {
-      throw new IOException("buildifier execution failed", e);
-    }
-  }
-
-
-  private static String readFile(File file) throws IOException {
-    try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
-      StringBuilder formattedFile = new StringBuilder();
-      char[] buf = new char[1024];
-      int numRead;
-      while ((numRead = reader.read(buf)) >= 0) {
-        formattedFile.append(buf, 0, numRead);
-      }
-      return formattedFile.toString();
-    }
-  }
-
-  private static File createTempFile(String text) throws IOException {
-    File file = File.createTempFile("ijwb", ".tmp");
-    try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
-      writer.write(text);
-    }
-    return file;
-  }
-}
\ No newline at end of file
diff --git a/blaze-base/src/com/google/idea/blaze/base/buildmodifier/BuildFileModifier.java b/blaze-base/src/com/google/idea/blaze/base/buildmodifier/BuildFileModifier.java
deleted file mode 100644
index c8c570c..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/buildmodifier/BuildFileModifier.java
+++ /dev/null
@@ -1,46 +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.buildmodifier;
-
-import com.google.idea.blaze.base.model.primitives.Kind;
-import com.google.idea.blaze.base.model.primitives.Label;
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.intellij.openapi.components.ServiceManager;
-import com.intellij.openapi.project.Project;
-
-public interface BuildFileModifier {
-
-  static BuildFileModifier getInstance() {
-    return ServiceManager.getService(BuildFileModifier.class);
-  }
-
-  /**
-   * Add a new rule to a build file. The rule name and rule kind must be validated before this
-   * method, but no guarantees are made about actually being able to add this rule to the build
-   * file. An example of why it might fail is the build file might already have a rule with the
-   * requested name.
-   *
-   * @param context  Blaze context to operate in
-   * @param newRule  new rule to create
-   * @param ruleKind valid kind of rule (android_library, java_library, etc.)
-   * @return true if rule is added to file, false otherwise
-   */
-  boolean addRule(
-    Project project,
-    final BlazeContext context,
-    final Label newRule,
-    final Kind ruleKind);
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/buildmodifier/BuildifierBinaryProvider.java b/blaze-base/src/com/google/idea/blaze/base/buildmodifier/BuildifierBinaryProvider.java
deleted file mode 100644
index 3390002..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/buildmodifier/BuildifierBinaryProvider.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.base.buildmodifier;
-
-import com.intellij.openapi.extensions.ExtensionPointName;
-
-import javax.annotation.Nullable;
-import java.io.File;
-
-/**
- * Provides a buildifier binary.
- */
-public interface BuildifierBinaryProvider {
-
-  ExtensionPointName<BuildifierBinaryProvider> EP_NAME = ExtensionPointName.create("com.google.idea.blaze.BuildifierBinaryProvider");
-
-  @Nullable
-  File getBuildifierBinary();
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/buildmodifier/FileSaveHandler.java b/blaze-base/src/com/google/idea/blaze/base/buildmodifier/FileSaveHandler.java
deleted file mode 100644
index c77d34b..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/buildmodifier/FileSaveHandler.java
+++ /dev/null
@@ -1,65 +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.buildmodifier;
-
-import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.command.CommandProcessor;
-import com.intellij.openapi.editor.Document;
-import com.intellij.openapi.editor.DocumentRunnable;
-import com.intellij.openapi.fileEditor.FileDocumentManager;
-import com.intellij.openapi.fileEditor.FileDocumentManagerAdapter;
-import com.intellij.openapi.vfs.VirtualFile;
-
-/**
- * Runs the buildifier command on file save.
- */
-public class FileSaveHandler extends FileDocumentManagerAdapter {
-
-  @Override
-  public void beforeDocumentSaving(final Document document) {
-    if (!document.isWritable()) {
-      return;
-    }
-    FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance();
-    VirtualFile file = fileDocumentManager.getFile(document);
-    if (file == null || !file.isValid()) {
-      return;
-    }
-
-    if (!isBuildFile(file)) {
-      return;
-    }
-    int lines = document.getLineCount();
-    if (lines > 0) {
-      String text = document.getText();
-      String formattedText = BuildFileFormatter.formatText(text);
-      updateDocument(document, formattedText);
-    }
-  }
-
-  private void updateDocument(final Document document, final String formattedContent) {
-    ApplicationManager.getApplication().runWriteAction(new DocumentRunnable(document, null) {
-      @Override
-      public void run() {
-        CommandProcessor.getInstance().runUndoTransparentAction(() -> document.setText(formattedContent));
-      }
-    });
-  }
-
-  private static boolean isBuildFile(VirtualFile file) {
-    return file.getName().equals("BUILD");
-  }
-}
\ No newline at end of file
diff --git a/blaze-base/src/com/google/idea/blaze/base/buildmodifier/FileSystemModifier.java b/blaze-base/src/com/google/idea/blaze/base/buildmodifier/FileSystemModifier.java
deleted file mode 100644
index 6606b23..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/buildmodifier/FileSystemModifier.java
+++ /dev/null
@@ -1,37 +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.buildmodifier;
-
-import com.google.idea.blaze.base.model.primitives.WorkspacePath;
-import com.intellij.openapi.components.ServiceManager;
-import com.intellij.openapi.project.Project;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.io.File;
-
-public abstract class FileSystemModifier {
-
-  public static FileSystemModifier getInstance(@NotNull Project project) {
-    return ServiceManager.getService(project, FileSystemModifier.class);
-  }
-
-  @Nullable
-  public abstract File makeWorkspacePathDirs(@NotNull WorkspacePath workspacePath);
-
-  @Nullable
-  public abstract File createFile(@NotNull WorkspacePath parentDir, @NotNull String name);
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/buildmodifier/FileSystemModifierImpl.java b/blaze-base/src/com/google/idea/blaze/base/buildmodifier/FileSystemModifierImpl.java
deleted file mode 100644
index 40470d7..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/buildmodifier/FileSystemModifierImpl.java
+++ /dev/null
@@ -1,61 +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.buildmodifier;
-
-import com.google.idea.blaze.base.model.primitives.WorkspacePath;
-import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
-import com.intellij.openapi.project.Project;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.io.File;
-import java.io.IOException;
-
-public class FileSystemModifierImpl extends FileSystemModifier {
-
-  private final Project project;
-
-  public FileSystemModifierImpl(@NotNull Project project) {
-    this.project = project;
-  }
-
-  @Nullable
-  @Override
-  public File makeWorkspacePathDirs(@NotNull WorkspacePath workspacePath) {
-    WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProject(project);
-    File dir = workspaceRoot.fileForPath(workspacePath);
-    boolean success = dir.mkdirs();
-    return success ? dir : null;
-  }
-
-  @Nullable
-  @Override
-  public File createFile(
-    @NotNull WorkspacePath parentDirectory,
-    @NotNull String name) {
-    WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProject(project);
-    File dir = workspaceRoot.fileForPath(parentDirectory);
-    File f = new File(dir, name);
-    boolean success;
-    try {
-      success = f.createNewFile();
-    }
-    catch (IOException e) {
-      success = false;
-    }
-    return success ? f : null;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/command/BlazeCommand.java b/blaze-base/src/com/google/idea/blaze/base/command/BlazeCommand.java
deleted file mode 100644
index 21a8a4d..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/command/BlazeCommand.java
+++ /dev/null
@@ -1,132 +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.command;
-
-import com.google.common.base.Joiner;
-import com.google.common.collect.ImmutableList;
-import com.google.idea.blaze.base.model.primitives.TargetExpression;
-import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
-import com.google.idea.blaze.base.settings.BlazeUserSettings;
-
-import java.util.Arrays;
-import java.util.List;
-
-import javax.annotation.concurrent.Immutable;
-
-/**
- * A command to issue to Blaze/Bazel on the command line.
- */
-@Immutable
-public final class BlazeCommand {
-
-  private final BuildSystem buildSystem;
-  private final BlazeCommandName name;
-  private final ImmutableList<String> arguments;
-
-  private BlazeCommand(BuildSystem buildSystem, BlazeCommandName name, ImmutableList<String> arguments) {
-    this.buildSystem = buildSystem;
-    this.name = name;
-    this.arguments = arguments;
-  }
-
-  public BlazeCommandName getName() {
-    return name;
-  }
-
-  public ImmutableList<String> toList() {
-    ImmutableList.Builder<String> commandLine = ImmutableList.builder();
-    commandLine.add(getBinaryPath(buildSystem), name.toString());
-    commandLine.addAll(arguments);
-    return commandLine.build();
-  }
-
-  private static String getBinaryPath(BuildSystem buildSystem) {
-    BlazeUserSettings settings = BlazeUserSettings.getInstance();
-    switch (buildSystem) {
-      case Blaze:
-        return settings.getBlazeBinaryPath();
-      case Bazel:
-        return settings.getBazelBinaryPath();
-      default:
-        throw new RuntimeException("Unrecognized build system type: " + buildSystem);
-    }
-  }
-
-  @Override
-  public String toString() {
-    return Joiner.on(' ').join(toList());
-  }
-
-  public static Builder builder(BuildSystem buildSystem, BlazeCommandName name) {
-    return new Builder(buildSystem, name);
-  }
-
-  public static class Builder {
-    private final BuildSystem buildSystem;
-    private final BlazeCommandName name;
-    private final ImmutableList.Builder<TargetExpression> targets = ImmutableList.builder();
-    private final ImmutableList.Builder<String> blazeFlags = ImmutableList.builder();
-    private final ImmutableList.Builder<String> exeFlags = ImmutableList.builder();
-
-    public Builder(BuildSystem buildSystem, BlazeCommandName name) {
-      this.buildSystem = buildSystem;
-      this.name = name;
-      // Tell forge what tool we used to call blaze so we can track usage.
-      addBlazeFlags(BlazeFlags.getToolTagFlag());
-    }
-
-      public BlazeCommand build() {
-      ImmutableList.Builder<String> arguments = ImmutableList.builder();
-      arguments.addAll(blazeFlags.build());
-      arguments.add("--");
-
-      // Trust the user's ordering of the targets since order matters to blaze
-      for (TargetExpression targetExpression : targets.build()) {
-        arguments.add(targetExpression.toString());
-      }
-
-      arguments.addAll(exeFlags.build());
-      return new BlazeCommand(buildSystem, name, arguments.build());
-    }
-
-      public Builder addTargets(TargetExpression... targets) {
-      return this.addTargets(Arrays.asList(targets));
-    }
-
-      public Builder addTargets(List<? extends TargetExpression> targets) {
-      this.targets.addAll(targets);
-      return this;
-    }
-
-      public Builder addExeFlags(String... flags) {
-      return addExeFlags(Arrays.asList(flags));
-    }
-
-      public Builder addExeFlags(List<String> flags) {
-      this.exeFlags.addAll(flags);
-      return this;
-    }
-
-      public Builder addBlazeFlags(String... flags) {
-      return addBlazeFlags(Arrays.asList(flags));
-    }
-
-      public Builder addBlazeFlags(List<String> flags) {
-      this.blazeFlags.addAll(flags);
-      return this;
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/command/BlazeCommandName.java b/blaze-base/src/com/google/idea/blaze/base/command/BlazeCommandName.java
deleted file mode 100644
index dc27d08..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/command/BlazeCommandName.java
+++ /dev/null
@@ -1,84 +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.command;
-
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Maps;
-import org.jetbrains.annotations.NotNull;
-
-import javax.annotation.concurrent.Immutable;
-import java.util.Collection;
-import java.util.concurrent.ConcurrentMap;
-
-/**
- * A class for Blaze/Bazel command names. We enumerate the commands we use (and that we expect users to be
- * interested in), but do NOT use an enum because we want to allow users to specify arbitrary
- * commands.
- */
-@Immutable
-public final class BlazeCommandName {
-  @NotNull
-  private static final ConcurrentMap<String, BlazeCommandName> knownCommands = Maps.newConcurrentMap();
-
-  public static final BlazeCommandName BUILD = fromString("build");
-  public static final BlazeCommandName TEST = fromString("test");
-  public static final BlazeCommandName MOBILE_INSTALL = fromString("mobile-install");
-  public static final BlazeCommandName RUN = fromString("run");
-  public static final BlazeCommandName QUERY = fromString("query");
-  public static final BlazeCommandName INFO = fromString("info");
-
-
-  public static BlazeCommandName fromString(@NotNull String name) {
-    knownCommands.putIfAbsent(name, new BlazeCommandName(name));
-    return knownCommands.get(name);
-  }
-
-  private final String name;
-
-  private BlazeCommandName(@NotNull String name) {
-    Preconditions.checkArgument(!name.isEmpty(), "Command should be non-empty.");
-    this.name = name;
-  }
-
-  @Override
-  public String toString() {
-    return name;
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (!(o instanceof BlazeCommandName)) {
-      return false;
-    }
-    BlazeCommandName that = (BlazeCommandName)o;
-    return name.equals(that.name);
-  }
-
-  @Override
-  public int hashCode() {
-    return name.hashCode();
-  }
-
-  /**
-   * @return An unmodifiable view of the Blaze commands we know about (including those that the user
-   * has specified, in addition to those we have hard-coded).
-   */
-  @NotNull
-  public static Collection<BlazeCommandName> knownCommands() {
-    return ImmutableList.copyOf(knownCommands.values());
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/command/BlazeFlags.java b/blaze-base/src/com/google/idea/blaze/base/command/BlazeFlags.java
deleted file mode 100644
index 7e83d40..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/command/BlazeFlags.java
+++ /dev/null
@@ -1,126 +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.command;
-
-import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.projectview.ProjectViewSet;
-import com.google.idea.blaze.base.projectview.section.sections.BuildFlagsSection;
-import com.google.idea.blaze.base.settings.Blaze;
-import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
-
-import com.intellij.openapi.project.Project;
-import com.intellij.util.PlatformUtils;
-
-import java.util.List;
-
-import javax.annotation.Nullable;
-
-/**
- * The collection of all the Bazel flag strings we use.
- */
-public final class BlazeFlags {
-
-  // Build the maximum number of possible dependencies of the project and to show all the build
-  // errors in single go.
-  public static final String KEEP_GOING = "--keep_going";
-  // Tells Blaze to open a debug port and wait for a connection while running tests
-  // It expands to: --test_arg=--wrapper_script_flag=--debug --test_output=streamed
-  //   --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";
-  // 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).
-  public static final String TEST_FILTER = "--test_filter";
-  // Skips checking for output file modifications (reduced statting -> faster).
-  public static final String NO_CHECK_OUTPUTS = "--noexperimental_check_output_files";
-  // Ignores implicit dependencies (e.g. java_library rules depending implicitly on
-  // "//transconsole/tools:aggregate_messages" in order to support translations).
-  public static final String NO_IMPLICIT_DEPS = "--noimplicit_deps";
-  // Ignores host dependencies.
-  public static final String NO_HOST_DEPS = "--nohost_deps";
-  // When used with mobile-install, deploys the an app incrementally.
-  public static final String INCREMENTAL = "--incremental";
-  // When used with mobile-install, deploys the an app incrementally
-  // can be used for API 23 or higher, for which it is preferred to --incremental
-  public static final String SPLIT_APKS = "--split_apks";
-  // Re-run the test even if the results are cached.
-  public static final String NO_CACHE_TEST_RESULTS = "--nocache_test_results";
-  // Avoids node GC between ide_build_info and blaze build
-  public static final String VERSION_WINDOW_FOR_DIRTY_NODE_GC =
-    "--version_window_for_dirty_node_gc=-1";
-
-  public static final String EXPERIMENTAL_SHOW_ARTIFACTS =
-    "--experimental_show_artifacts";
-
-  public static List<String> buildFlags(Project project, ProjectViewSet projectViewSet) {
-    BuildSystem buildSystem = Blaze.getBuildSystem(project);
-    List<String> flags = Lists.newArrayList();
-    for (BuildFlagsProvider buildFlagsProvider : BuildFlagsProvider.EP_NAME.getExtensions()) {
-      buildFlagsProvider.addBuildFlags(buildSystem, projectViewSet, flags);
-    }
-    flags.addAll(projectViewSet.listItems(BuildFlagsSection.KEY));
-    return flags;
-  }
-
-  // Pass-through arg for sending adb options during mobile-install.
-  public static final String ADB_ARG = "--adb_arg=";
-
-  public static ImmutableList<String> adbSerialFlags(String serial) {
-    return ImmutableList.of(ADB_ARG + "-s ", ADB_ARG + serial);
-  }
-
-  // Pass-through arg for sending test arguments.
-  public static final String TEST_ARG = "--test_arg=";
-
-  private static final String TOOL_TAG = "--tool_tag=ijwb:";
-
-  // We add this to every single BlazeCommand instance. It's for tracking usage.
-  public static String getToolTagFlag() {
-    String platformPrefix = PlatformUtils.getPlatformPrefix();
-
-    // IDEA Community Edition is "Idea", whereas IDEA Ultimate Edition is "idea". That's dumb. Let's make them more useful.
-    if (PlatformUtils.isIdeaCommunity()) {
-      platformPrefix = "IDEA:community";
-    } else if (PlatformUtils.isIdeaUltimate()) {
-      platformPrefix = "IDEA:ultimate";
-    }
-    return TOOL_TAG + platformPrefix;
-  }
-
-  public static String testFilterFlagForClass(String className) {
-    return testFilterFlagForClassAndMethod(className, null);
-  }
-
-  public static String testFilterFlagForClassAndMethod(String className, @Nullable String methodName) {
-    StringBuilder output = new StringBuilder(TEST_FILTER);
-    output.append('=');
-    output.append(className);
-
-    if (!Strings.isNullOrEmpty(methodName)) {
-      output.append('#');
-      output.append(methodName);
-    }
-
-    return output.toString();
-  }
-
-  private BlazeFlags() {
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/command/BlazeQuery.java b/blaze-base/src/com/google/idea/blaze/base/command/BlazeQuery.java
deleted file mode 100644
index cb5c583..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/command/BlazeQuery.java
+++ /dev/null
@@ -1,58 +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.command;
-
-import com.google.idea.blaze.base.async.process.ExternalTask;
-import com.google.idea.blaze.base.async.process.LineProcessingOutputStream;
-import com.google.idea.blaze.base.issueparser.IssueOutputLineProcessor;
-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.BlazeImportSettings;
-import com.google.idea.blaze.base.settings.BlazeImportSettingsManager;
-import com.intellij.openapi.project.Project;
-import org.jetbrains.annotations.NotNull;
-
-import java.io.OutputStream;
-
-/**
- * Code for issuing a blaze query.
- */
-public class BlazeQuery {
-
-  public static void query(
-    @NotNull final Project project,
-    @NotNull BlazeContext context,
-    @NotNull final String query,
-    @NotNull OutputStream stdout) {
-    BlazeImportSettings importSettings = BlazeImportSettingsManager.getInstance(project)
-      .getImportSettings();
-    assert importSettings != null;
-    final WorkspaceRoot workspaceRoot = WorkspaceRoot.fromImportSettings(importSettings);
-
-    final BlazeCommand command = BlazeCommand.builder(Blaze.getBuildSystem(project), BlazeCommandName.QUERY)
-      .addBlazeFlags(BlazeFlags.KEEP_GOING, BlazeFlags.NO_IMPLICIT_DEPS, BlazeFlags.NO_HOST_DEPS)
-      .addBlazeFlags(query)
-      .build();
-
-    ExternalTask.builder(workspaceRoot, command)
-      .context(context)
-      .stdout(stdout)
-      .stderr(LineProcessingOutputStream.of(new IssueOutputLineProcessor(project, context, workspaceRoot)))
-      .build()
-      .run();
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/command/BuildFlagsProvider.java b/blaze-base/src/com/google/idea/blaze/base/command/BuildFlagsProvider.java
deleted file mode 100644
index 2533bdc..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/command/BuildFlagsProvider.java
+++ /dev/null
@@ -1,33 +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.command;
-
-import com.google.idea.blaze.base.projectview.ProjectViewSet;
-import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
-import com.intellij.openapi.extensions.ExtensionPointName;
-
-import java.util.List;
-
-/**
- * Provides additional build flags for bazel/blaze commands.
- */
-public interface BuildFlagsProvider {
-
-  ExtensionPointName<BuildFlagsProvider> EP_NAME = ExtensionPointName
-    .create("com.google.idea.blaze.BuildFlagsProvider");
-
-  void addBuildFlags(BuildSystem buildSystem, ProjectViewSet projectViewSet, List<String> flags);
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/command/BuildFlagsProviderImpl.java b/blaze-base/src/com/google/idea/blaze/base/command/BuildFlagsProviderImpl.java
deleted file mode 100644
index f6c3a7f..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/command/BuildFlagsProviderImpl.java
+++ /dev/null
@@ -1,64 +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.command;
-
-import com.google.idea.blaze.base.experiments.BoolExperiment;
-import com.google.idea.blaze.base.experiments.IntExperiment;
-import com.google.idea.blaze.base.projectview.ProjectViewSet;
-import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
-
-import java.util.List;
-
-import static com.google.idea.blaze.base.command.BlazeFlags.NO_CHECK_OUTPUTS;
-import static com.google.idea.blaze.base.command.BlazeFlags.VERSION_WINDOW_FOR_DIRTY_NODE_GC;
-
-/**
- * Flags added to blaze/bazel build commands.
- */
-public class BuildFlagsProviderImpl implements BuildFlagsProvider {
-
-  private static final BoolExperiment EXPERIMENT_USE_VERSION_WINDOW_FOR_DIRTY_NODE_GC =
-    new BoolExperiment("ide_build_info.use_version_window_for_dirty_node_gc", false);
-  private static final BoolExperiment EXPERIMENT_NO_EXPERIMENTAL_CHECK_OUTPUT_FILES =
-    new BoolExperiment("build.noexperimental_check_output_files", false);
-  private static final IntExperiment EXPERIMENT_MIN_PKG_COUNT_FOR_CT_NODE_EVICTION =
-    new IntExperiment("min_pkg_count_for_ct_node_eviction", 0);
-
-  // Avoids blaze state invalidation from non-overlapping transitive closures
-  // This is caused by our search for the android_sdk
-  private static final String MIN_PKG_COUNT_FOR_CT_NODE_EVICTION =
-    "--min_pkg_count_for_ct_node_eviction=";
-
-  private static String minPkgCountForCtNodeEviction(int value) {
-    return MIN_PKG_COUNT_FOR_CT_NODE_EVICTION + value;
-  }
-
-  @Override
-  public void addBuildFlags(BuildSystem buildSystem, ProjectViewSet projectViewSet, List<String> flags) {
-    if (EXPERIMENT_USE_VERSION_WINDOW_FOR_DIRTY_NODE_GC.getValue()) {
-      flags.add(VERSION_WINDOW_FOR_DIRTY_NODE_GC);
-    }
-    if (EXPERIMENT_NO_EXPERIMENTAL_CHECK_OUTPUT_FILES.getValue()) {
-      flags.add(NO_CHECK_OUTPUTS);
-    }
-    int minPkgCountForCtNodeEviction = EXPERIMENT_MIN_PKG_COUNT_FOR_CT_NODE_EVICTION.getValue();
-    if (minPkgCountForCtNodeEviction > 0) {
-      flags.add(minPkgCountForCtNodeEviction(minPkgCountForCtNodeEviction));
-    }
-  }
-
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/command/ExperimentalShowArtifactsLineProcessor.java b/blaze-base/src/com/google/idea/blaze/base/command/ExperimentalShowArtifactsLineProcessor.java
deleted file mode 100644
index b8d82d2..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/command/ExperimentalShowArtifactsLineProcessor.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.base.command;
-
-import com.google.idea.blaze.base.async.process.LineProcessingOutputStream;
-import org.jetbrains.annotations.NotNull;
-
-import java.io.File;
-import java.util.List;
-
-/**
- * Collects the output of --experimental_show_artifacts
- */
-public class ExperimentalShowArtifactsLineProcessor implements LineProcessingOutputStream.LineProcessor {
-  private static final String OUTPUT_START = "Build artifacts:";
-  private static final String OUTPUT_MARKER = ">>>";
-
-  final List<File> fileList;
-  private final String fileType;
-  boolean insideBuildResult = false;
-
-  public ExperimentalShowArtifactsLineProcessor(List<File> fileList,
-                                                String fileType) {
-    this.fileList = fileList;
-    this.fileType = fileType;
-  }
-
-  @Override
-  public boolean processLine(@NotNull String line) {
-    if (insideBuildResult) {
-      // Workaround for --experimental_ui: Extra newlines are inserted
-      if (line.isEmpty()) {
-        return false;
-      }
-
-      insideBuildResult = line.startsWith(OUTPUT_MARKER);
-      if (insideBuildResult) {
-        String fileName = line.substring(OUTPUT_MARKER.length());
-        if (fileName.endsWith(fileType)) {
-          fileList.add(new File(fileName));
-        }
-      }
-    }
-    if (!insideBuildResult) {
-      insideBuildResult = line.equals(OUTPUT_START);
-    }
-    return !insideBuildResult;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/command/info/BlazeInfo.java b/blaze-base/src/com/google/idea/blaze/base/command/info/BlazeInfo.java
deleted file mode 100644
index 510d042..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/command/info/BlazeInfo.java
+++ /dev/null
@@ -1,99 +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.command.info;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.util.concurrent.ListenableFuture;
-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.BuildSystem;
-import com.intellij.openapi.components.ServiceManager;
-
-import javax.annotation.Nullable;
-import java.util.List;
-
-/**
- * Runs the blaze info command. The results may be cached in the workspace.
- */
-public abstract class BlazeInfo {
-  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 String blazeBinKey(BuildSystem buildSystem) {
-    switch (buildSystem) {
-      case Blaze:
-        return "blaze-bin";
-      case Bazel:
-        return "bazel-bin";
-      default:
-        throw new IllegalArgumentException("Unrecognized build system: " + buildSystem);
-    }
-  }
-
-  public static String blazeGenfilesKey(BuildSystem buildSystem) {
-    switch (buildSystem) {
-      case Blaze:
-        return "blaze-genfiles";
-      case Bazel:
-        return "bazel-genfiles";
-      default:
-        throw new IllegalArgumentException("Unrecognized build system: " + buildSystem);
-    }
-  }
-
-  public static BlazeInfo getInstance() {
-    return ServiceManager.getService(BlazeInfo.class);
-  }
-
-  /**
-   * @param blazeFlags The blaze flags that will be passed to Blaze.
-   * @param key The key passed to blaze info
-   * @return The blaze info value associated with the specified key
-   */
-  public abstract ListenableFuture<String> runBlazeInfo(
-    @Nullable BlazeContext context,
-    BuildSystem buildSystem,
-    WorkspaceRoot workspaceRoot,
-    List<String> blazeFlags,
-    String key);
-
-  /**
-   * @param blazeFlags The blaze flags that will be passed to Blaze.
-   * @param key The key passed to blaze info
-   * @return The blaze info value associated with the specified key
-   */
-  public abstract ListenableFuture<byte[]> runBlazeInfoGetBytes(
-    @Nullable BlazeContext context,
-    BuildSystem buildSystem,
-    WorkspaceRoot workspaceRoot,
-    List<String> blazeFlags,
-    String key);
-
-  /**
-   * This calls blaze info without any specific key so blaze info will return all keys and values that it has. There could be a performance
-   * cost for doing this, so the user should verify that calling this method is actually faster than calling
-   * {@link #runBlazeInfo(WorkspaceRoot, List, String)}.
-   *
-   * @param blazeFlags The blaze flags that will be passed to Blaze.
-   * @return The blaze info data fields.
-   */
-  public abstract ListenableFuture<ImmutableMap<String, String>> runBlazeInfo(
-    @Nullable BlazeContext context,
-    BuildSystem buildSystem,
-    WorkspaceRoot workspaceRoot,
-    List<String> blazeFlags);
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/command/info/BlazeInfoException.java b/blaze-base/src/com/google/idea/blaze/base/command/info/BlazeInfoException.java
deleted file mode 100644
index aeef5a1..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/command/info/BlazeInfoException.java
+++ /dev/null
@@ -1,48 +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.command.info;
-
-import javax.annotation.concurrent.Immutable;
-
-@Immutable
-public final class BlazeInfoException extends Exception {
-  private final int exitCode;
-  private final String stdout;
-  private final String stderr;
-
-  public BlazeInfoException(int exitCode, String stdout, String stderr) {
-    this.exitCode = exitCode;
-    this.stdout = stdout;
-    this.stderr = stderr;
-  }
-
-  @Override
-  public String getMessage() {
-    return "blaze info failed with exit code: " + exitCode + " and error stream: " + stderr;
-  }
-
-  public int getExitCode() {
-    return exitCode;
-  }
-
-  public String getStdout() {
-    return stdout;
-  }
-
-  public String getStderr() {
-    return stderr;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/command/info/BlazeInfoImpl.java b/blaze-base/src/com/google/idea/blaze/base/command/info/BlazeInfoImpl.java
deleted file mode 100644
index 285f276..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/command/info/BlazeInfoImpl.java
+++ /dev/null
@@ -1,103 +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.command.info;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.idea.blaze.base.async.executor.BlazeExecutor;
-import com.google.idea.blaze.base.async.process.ExternalTask;
-import com.google.idea.blaze.base.command.BlazeCommand;
-import com.google.idea.blaze.base.command.BlazeCommandName;
-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.BuildSystem;
-import com.intellij.openapi.diagnostic.Logger;
-
-import javax.annotation.Nullable;
-import java.io.ByteArrayOutputStream;
-import java.util.List;
-
-public class BlazeInfoImpl extends BlazeInfo {
-  private static final Logger LOG = Logger.getInstance(BlazeInfoImpl.class);
-
-  @Override
-  public ListenableFuture<String> runBlazeInfo(@Nullable BlazeContext context,
-                                               BuildSystem buildSystem,
-                                               WorkspaceRoot workspaceRoot,
-                                               List<String> blazeFlags,
-                                               String key) {
-    return BlazeExecutor.getInstance().submit(() -> runBlazeInfo(buildSystem, workspaceRoot, key, blazeFlags, context).toString().trim());
-  }
-
-  @Override
-  public ListenableFuture<byte[]> runBlazeInfoGetBytes(@Nullable BlazeContext context,
-                                                       BuildSystem buildSystem,
-                                                       WorkspaceRoot workspaceRoot,
-                                                       List<String> blazeFlags,
-                                                       String key) {
-    return BlazeExecutor.getInstance().submit(() -> runBlazeInfo(buildSystem, workspaceRoot, key, blazeFlags, context).toByteArray());
-  }
-
-  @Override
-  public ListenableFuture<ImmutableMap<String, String>> runBlazeInfo(@Nullable BlazeContext context,
-                                                                     BuildSystem buildSystem,
-                                                                     WorkspaceRoot workspaceRoot,
-                                                                     List<String> blazeFlags) {
-    return BlazeExecutor.getInstance().submit(() -> {
-      String blazeInfoString = runBlazeInfo(buildSystem, workspaceRoot, null /* key */, blazeFlags, context).toString().trim();
-      return parseBlazeInfoResult(blazeInfoString);
-    });
-  }
-
-  private static ByteArrayOutputStream runBlazeInfo(
-    BuildSystem buildSystem,
-    WorkspaceRoot workspaceRoot,
-    @Nullable String key,
-    List<String> blazeFlags,
-    @Nullable BlazeContext context) throws BlazeInfoException {
-    BlazeCommand.Builder builder = BlazeCommand.builder(buildSystem, BlazeCommandName.INFO);
-    if (key != null) {
-      builder.addBlazeFlags(key);
-    }
-    BlazeCommand command = builder.addBlazeFlags(blazeFlags).build();
-    ByteArrayOutputStream stdout = new ByteArrayOutputStream();
-    ByteArrayOutputStream stderr = new ByteArrayOutputStream();
-    int exitCode = ExternalTask.builder(workspaceRoot, command)
-      .context(context)
-      .stdout(stdout)
-      .stderr(stderr)
-      .build()
-      .run();
-    if (exitCode != 0) {
-      throw new BlazeInfoException(exitCode, stdout.toString(), stderr.toString());
-    }
-    return stdout;
-  }
-
-  private static ImmutableMap<String, String> parseBlazeInfoResult(String blazeInfoString) {
-    ImmutableMap.Builder<String, String> blazeInfoMapBuilder = ImmutableMap.builder();
-    String[] blazeInfoLines = blazeInfoString.split("\n");
-    for (String blazeInfoLine : blazeInfoLines) {
-      // Just split on the first ":".
-      String[] keyValue = blazeInfoLine.split(":", 2);
-      LOG.assertTrue(keyValue.length == 2, blazeInfoLine);
-      String key = keyValue[0].trim();
-      String value = keyValue[1].trim();
-      blazeInfoMapBuilder.put(key, value);
-    }
-    return blazeInfoMapBuilder.build();
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/console/BlazeConsoleService.java b/blaze-base/src/com/google/idea/blaze/base/console/BlazeConsoleService.java
deleted file mode 100644
index 3a92c75..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/console/BlazeConsoleService.java
+++ /dev/null
@@ -1,39 +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.console;
-
-import com.intellij.execution.ui.ConsoleViewContentType;
-import com.intellij.openapi.components.ServiceManager;
-import com.intellij.openapi.project.Project;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * Prints text to the blaze console.
- */
-public interface BlazeConsoleService {
-  static BlazeConsoleService getInstance(@NotNull Project project) {
-    return ServiceManager.getService(project, BlazeConsoleService.class);
-  }
-
-  void print(@NotNull String text, @NotNull ConsoleViewContentType contentType);
-
-  void clear();
-
-  void setStopHandler(@Nullable Runnable runnable);
-
-  void activateConsoleWindow();
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/console/BlazeConsoleServiceImpl.java b/blaze-base/src/com/google/idea/blaze/base/console/BlazeConsoleServiceImpl.java
deleted file mode 100644
index 61e8826..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/console/BlazeConsoleServiceImpl.java
+++ /dev/null
@@ -1,60 +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.console;
-
-import com.intellij.execution.ui.ConsoleViewContentType;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.wm.ToolWindow;
-import com.intellij.openapi.wm.ToolWindowManager;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * Implementation for BlazeConsoleService
- */
-public class BlazeConsoleServiceImpl implements BlazeConsoleService {
-  @NotNull private final Project project;
-  @NotNull private final BlazeConsoleView blazeConsoleView;
-
-  BlazeConsoleServiceImpl(@NotNull Project project) {
-    this.project = project;
-    blazeConsoleView = BlazeConsoleView.getInstance(project);
-  }
-
-  @Override
-  public void print(@NotNull String text, @NotNull ConsoleViewContentType contentType) {
-    blazeConsoleView.print(text, contentType);
-  }
-
-  @Override
-  public void clear() {
-    blazeConsoleView.clear();
-  }
-
-  @Override
-  public void setStopHandler(@Nullable Runnable runnable) {
-    blazeConsoleView.setStopHandler(runnable);
-  }
-
-  @Override
-  public void activateConsoleWindow() {
-    ToolWindow toolWindow = ToolWindowManager.getInstance(project).getToolWindow(
-      BlazeConsoleToolWindowFactory.ID);
-    if (toolWindow != null) {
-      toolWindow.activate(null);
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/console/BlazeConsoleToolWindowFactory.java b/blaze-base/src/com/google/idea/blaze/base/console/BlazeConsoleToolWindowFactory.java
deleted file mode 100644
index 2ff82fa..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/console/BlazeConsoleToolWindowFactory.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.base.console;
-
-import com.google.idea.blaze.base.settings.Blaze;
-import com.intellij.openapi.project.DumbAware;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.wm.ToolWindow;
-import com.intellij.openapi.wm.ToolWindowFactory;
-import org.jetbrains.annotations.NotNull;
-
-public class BlazeConsoleToolWindowFactory implements DumbAware, ToolWindowFactory {
-
-  public static final String ID = "Blaze Console";
-
-  @Override
-  public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) {
-    String title = Blaze.buildSystemName(project) + " Console";
-    toolWindow.setTitle(title);
-    toolWindow.setStripeTitle(title);
-    BlazeConsoleView.getInstance(project).createToolWindowContent(toolWindow);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/console/BlazeConsoleView.java b/blaze-base/src/com/google/idea/blaze/base/console/BlazeConsoleView.java
deleted file mode 100644
index ebbb630..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/console/BlazeConsoleView.java
+++ /dev/null
@@ -1,149 +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.console;
-
-import com.intellij.codeEditor.printing.PrintAction;
-import com.intellij.execution.impl.ConsoleViewImpl;
-import com.intellij.execution.ui.ConsoleViewContentType;
-import com.intellij.execution.ui.RunnerLayoutUi;
-import com.intellij.execution.ui.layout.PlaceInGrid;
-import com.intellij.icons.AllIcons;
-import com.intellij.ide.IdeBundle;
-import com.intellij.ide.actions.NextOccurenceToolbarAction;
-import com.intellij.ide.actions.PreviousOccurenceToolbarAction;
-import com.intellij.openapi.Disposable;
-import com.intellij.openapi.actionSystem.*;
-import com.intellij.openapi.components.ServiceManager;
-import com.intellij.openapi.project.DumbAwareAction;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.Disposer;
-import com.intellij.openapi.wm.ToolWindow;
-import com.intellij.ui.content.Content;
-import com.intellij.ui.content.ContentFactory;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import javax.swing.*;
-import java.awt.*;
-
-public class BlazeConsoleView implements Disposable {
-
-  private static final Class<?>[] IGNORED_CONSOLE_ACTION_TYPES =
-    {PreviousOccurenceToolbarAction.class, NextOccurenceToolbarAction.class,
-      ConsoleViewImpl.ClearAllAction.class, PrintAction.class};
-
-  @NotNull
-  private final Project myProject;
-  @NotNull
-  private final ConsoleViewImpl myConsoleView;
-
-  private JPanel myConsolePanel;
-  private volatile Runnable myStopHandler;
-
-  public BlazeConsoleView(@NotNull Project project) {
-    myProject = project;
-    myConsoleView = new ConsoleViewImpl(myProject, false);
-    Disposer.register(this, myConsoleView);
-    setupUI();
-  }
-
-  public static BlazeConsoleView getInstance(@NotNull Project project) {
-    return ServiceManager.getService(project, BlazeConsoleView.class);
-  }
-
-  public void setStopHandler(@Nullable Runnable stopHandler) {
-    myStopHandler = stopHandler;
-  }
-
-  private static boolean shouldIgnoreAction(@NotNull AnAction action) {
-    for (Class<?> actionType : IGNORED_CONSOLE_ACTION_TYPES) {
-      if (actionType.isInstance(action)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  public void createToolWindowContent(@NotNull ToolWindow toolWindow) {
-    //Create runner UI layout
-    RunnerLayoutUi.Factory factory = RunnerLayoutUi.Factory.getInstance(myProject);
-    RunnerLayoutUi layoutUi = factory.create("", "", "session", myProject);
-
-    Content console = layoutUi.createContent(BlazeConsoleToolWindowFactory.ID, myConsoleView.getComponent(), "", null, null);
-    layoutUi.addContent(console, 0, PlaceInGrid.right, false);
-
-    // Adding actions
-    DefaultActionGroup group = new DefaultActionGroup();
-    layoutUi.getOptions().setLeftToolbar(group, ActionPlaces.UNKNOWN);
-
-    AnAction[] consoleActions = myConsoleView.createConsoleActions();
-    for (AnAction action : consoleActions) {
-      if (!shouldIgnoreAction(action)) {
-        group.add(action);
-      }
-    }
-    group.add(new StopAction());
-
-    JComponent layoutComponent = layoutUi.getComponent();
-    myConsolePanel.add(layoutComponent, BorderLayout.CENTER);
-
-    //noinspection ConstantConditions
-    Content content =
-      ContentFactory.SERVICE.getInstance().createContent(layoutComponent, null, true);
-    toolWindow.getContentManager().addContent(content);
-  }
-
-  public void clear() {
-    myConsoleView.clear();
-  }
-
-  public void print(@NotNull String text, @NotNull ConsoleViewContentType contentType) {
-    myConsoleView.print(text, contentType);
-  }
-
-  @Override
-  public void dispose() {
-  }
-
-  private void setupUI() {
-    myConsolePanel = new JPanel();
-    myConsolePanel.setLayout(new BorderLayout(0, 0));
-  }
-
-  private class StopAction extends DumbAwareAction {
-    public StopAction() {
-      super(IdeBundle.message("action.stop"), null, AllIcons.Actions.Suspend);
-    }
-
-    @Override
-    public void actionPerformed(AnActionEvent e) {
-      Runnable handler = myStopHandler;
-      if (handler != null) {
-        handler.run();
-        myStopHandler = null;
-      }
-    }
-
-    @Override
-    public void update(AnActionEvent event) {
-      Presentation presentation = event.getPresentation();
-      boolean isNowVisible = myStopHandler != null;
-      if (presentation.isEnabled() != isNowVisible) {
-        presentation.setEnabled(isNowVisible);
-      }
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/experiments/BoolExperiment.java b/blaze-base/src/com/google/idea/blaze/base/experiments/BoolExperiment.java
deleted file mode 100644
index 98da296..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/experiments/BoolExperiment.java
+++ /dev/null
@@ -1,34 +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.experiments;
-
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Boolean-valued experiment.
- */
-public class BoolExperiment extends Experiment {
-  private final boolean defaultValue;
-
-  public BoolExperiment(@NotNull String key, boolean defaultValue) {
-    super(key);
-    this.defaultValue = defaultValue;
-  }
-
-  public boolean getValue() {
-    return ExperimentService.getInstance().getExperiment(key, defaultValue);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/experiments/DevOverride.java b/blaze-base/src/com/google/idea/blaze/base/experiments/DevOverride.java
deleted file mode 100644
index d990178..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/experiments/DevOverride.java
+++ /dev/null
@@ -1,35 +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.experiments;
-
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Developer override for string experiments.
- */
-public class DevOverride extends Experiment {
-  private final String defaultValue;
-
-  public DevOverride(@NotNull String key, @NotNull String defaultValue) {
-    super(key);
-    this.defaultValue = defaultValue;
-  }
-
-  @NotNull
-  public String getValue() {
-    return ExperimentService.getInstance().getExperimentString(key, defaultValue);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/experiments/DeveloperFlag.java b/blaze-base/src/com/google/idea/blaze/base/experiments/DeveloperFlag.java
deleted file mode 100644
index 6813bde..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/experiments/DeveloperFlag.java
+++ /dev/null
@@ -1,27 +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.experiments;
-
-import org.jetbrains.annotations.NotNull;
-
-/**
- * This is an experiment that always defaults to off. Use to document that this is for devs only.
- */
-public class DeveloperFlag extends BoolExperiment {
-  public DeveloperFlag(@NotNull String key) {
-    super(key, false);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/experiments/Experiment.java b/blaze-base/src/com/google/idea/blaze/base/experiments/Experiment.java
deleted file mode 100644
index a320391..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/experiments/Experiment.java
+++ /dev/null
@@ -1,34 +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.experiments;
-
-import com.intellij.openapi.components.ServiceManager;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Experiment class.
- */
-public abstract class Experiment {
-  @NotNull protected final String key;
-
-  protected Experiment(@NotNull String key) {
-    this.key = key;
-  }
-
-  public String getKey() {
-    return key;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/experiments/ExperimentLoader.java b/blaze-base/src/com/google/idea/blaze/base/experiments/ExperimentLoader.java
deleted file mode 100644
index b9450c8..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/experiments/ExperimentLoader.java
+++ /dev/null
@@ -1,24 +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.experiments;
-
-import org.jetbrains.annotations.NotNull;
-
-import java.util.Map;
-
-interface ExperimentLoader {
-  Map<String, String> getExperiments();
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/experiments/ExperimentScope.java b/blaze-base/src/com/google/idea/blaze/base/experiments/ExperimentScope.java
deleted file mode 100644
index 3787678..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/experiments/ExperimentScope.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.base.experiments;
-
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.google.idea.blaze.base.scope.BlazeScope;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Reloads experiments at the start of the scope.
- */
-public class ExperimentScope implements BlazeScope {
-  @Override
-  public void onScopeBegin(@NotNull BlazeContext context) {
-    ExperimentService.getInstance().reloadExperiments();
-    ExperimentService.getInstance().startExperimentScope();
-  }
-
-  @Override
-  public void onScopeEnd(@NotNull BlazeContext context) {
-    ExperimentService.getInstance().endExperimentScope();
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/experiments/ExperimentService.java b/blaze-base/src/com/google/idea/blaze/base/experiments/ExperimentService.java
deleted file mode 100644
index 4a80be3..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/experiments/ExperimentService.java
+++ /dev/null
@@ -1,61 +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.experiments;
-
-import com.intellij.openapi.components.ServiceManager;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * Reads experiments.
- */
-public interface ExperimentService {
-
-  static ExperimentService getInstance() {
-    return ServiceManager.getService(ExperimentService.class);
-  }
-
-  /**
-   * Returns an experiment if it exists, else defaultValue
-   */
-  boolean getExperiment(@NotNull String key, boolean defaultValue);
-
-  /**
-   * Returns a string-valued experiment if it exists, else defaultValue.
-   */
-  String getExperimentString(@NotNull String key, @Nullable String defaultValue);
-
-  /**
-   * Returns an int-valued experiment if it exists, else defaultValue.
-   */
-  int getExperimentInt(@NotNull String key, int defaultValue);
-
-  /**
-   * Reloads all experiments.
-   */
-  void reloadExperiments();
-
-  /**
-   * Starts an experiment scope. During an experiment scope,
-   * experiments won't be reloaded.
-   */
-  void startExperimentScope();
-
-  /**
-   * Ends an experiment scope.
-   */
-  void endExperimentScope();
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/experiments/ExperimentServiceImpl.java b/blaze-base/src/com/google/idea/blaze/base/experiments/ExperimentServiceImpl.java
deleted file mode 100644
index 2c2911d..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/experiments/ExperimentServiceImpl.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.experiments;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.util.SystemProperties;
-
-import org.jetbrains.annotations.NotNull;
-
-import java.io.File;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- * An experiment service that delegates to {@link ExperimentLoader ExperimentLoaders}, in a
- * specific order.
- *
- * It will check system properties first, then an experiment file in the user's home directory, then
- * finally all files specified by the system property blaze.experiments.file.
- */
-public class ExperimentServiceImpl implements ExperimentService {
-  private static final Logger LOG = Logger.getInstance(ExperimentServiceImpl.class);
-
-  private static final String USER_EXPERIMENT_OVERRIDES_FILE =
-      SystemProperties.getUserHome() + File.separator + ".blaze-experiments";
-
-  private final List<ExperimentLoader> services;
-  private Map<String, String> experiments;
-  private AtomicInteger experimentScopeCounter = new AtomicInteger(0);
-
-  public ExperimentServiceImpl() {
-    this(
-        new SystemPropertyExperimentLoader(),
-        new FileExperimentLoader(USER_EXPERIMENT_OVERRIDES_FILE),
-        new WebExperimentLoader());
-  }
-
-  @VisibleForTesting
-  protected ExperimentServiceImpl(ExperimentLoader... loaders) {
-    services = ImmutableList.copyOf(loaders);
-  }
-
-  @Override
-  public synchronized void reloadExperiments() {
-    if (experimentScopeCounter.get() > 0) {
-      return;
-    }
-
-    Map<String, String> experiments = Maps.newHashMap();
-    for (ExperimentLoader loader : Lists.reverse(services)) {
-      experiments.putAll(loader.getExperiments());
-    }
-    this.experiments = experiments;
-  }
-
-  @Override
-  public synchronized void startExperimentScope() {
-    this.experimentScopeCounter.incrementAndGet();
-  }
-
-  @Override
-  public synchronized void endExperimentScope() {
-    int value = this.experimentScopeCounter.decrementAndGet();
-    LOG.assertTrue(value >= 0);
-  }
-
-  @Override
-  public boolean getExperiment(@NotNull String key, boolean defaultValue) {
-    String property = getExperiment(key);
-    return property != null ? property.equals("1") : defaultValue;
-  }
-
-  @Override
-  public String getExperimentString(@NotNull String key, String defaultValue) {
-    String property = getExperiment(key);
-    return property != null ? property : defaultValue;
-  }
-
-  @Override
-  public int getExperimentInt(@NotNull String key, int defaultValue) {
-    String property = getExperiment(key);
-    try {
-      return property != null ? Integer.parseInt(property) : defaultValue;
-    } catch (NumberFormatException e) {
-      LOG.warn("Could not parse int for experiment: " + key, e);
-      return defaultValue;
-    }
-  }
-
-  String getExperiment(@NotNull String key) {
-    if (experiments == null) {
-      reloadExperiments();
-    }
-    LOG.assertTrue(experiments != null, "Failure to load experiments.");
-    return experiments.get(key);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/experiments/FileExperimentLoader.java b/blaze-base/src/com/google/idea/blaze/base/experiments/FileExperimentLoader.java
deleted file mode 100644
index 07d5f55..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/experiments/FileExperimentLoader.java
+++ /dev/null
@@ -1,69 +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.experiments;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Maps;
-import com.intellij.openapi.diagnostic.Logger;
-
-import org.jetbrains.annotations.NotNull;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Enumeration;
-import java.util.Map;
-import java.util.Properties;
-
-/**
- * Reads experiments from a property file.
- */
-class FileExperimentLoader implements ExperimentLoader {
-
-  private static final Logger LOG = Logger.getInstance(FileExperimentLoader.class);
-
-  private final String filename;
-
-  public FileExperimentLoader(String filename) {
-    this.filename = filename;
-  }
-
-  @Override
-  public Map<String, String> getExperiments() {
-    Properties properties = new Properties();
-
-    File file = new File(filename);
-    if (!file.exists()) {
-      LOG.info("File " + filename + " does not exist, skipping.");
-      return ImmutableMap.of();
-    }
-
-    Map<String, String> result = Maps.newHashMap();
-    try (InputStream inputStream = new FileInputStream(filename)) {
-      properties.load(inputStream);
-      Enumeration<?> enumeration = properties.propertyNames();
-      while (enumeration.hasMoreElements()) {
-        String key = (String)enumeration.nextElement();
-        String value = properties.getProperty(key);
-        result.put(key, value);
-      }
-    } catch (IOException e) {
-      LOG.warn("Could not load experiments from file: " + filename, e);
-    }
-    return result;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/experiments/IntExperiment.java b/blaze-base/src/com/google/idea/blaze/base/experiments/IntExperiment.java
deleted file mode 100644
index 412e6f1..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/experiments/IntExperiment.java
+++ /dev/null
@@ -1,34 +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.experiments;
-
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Integer valued experiment.
- */
-public class IntExperiment extends Experiment {
-  private final int defaultValue;
-
-  public IntExperiment(@NotNull String key, int defaultValue) {
-    super(key);
-    this.defaultValue = defaultValue;
-  }
-
-  public int getValue() {
-    return ExperimentService.getInstance().getExperimentInt(key, defaultValue);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/experiments/StringExperiment.java b/blaze-base/src/com/google/idea/blaze/base/experiments/StringExperiment.java
deleted file mode 100644
index b881bf1..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/experiments/StringExperiment.java
+++ /dev/null
@@ -1,34 +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.experiments;
-
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * String-valued experiment.
- */
-public class StringExperiment extends Experiment {
-
-  public StringExperiment(@NotNull String key) {
-    super(key);
-  }
-
-  @Nullable
-  public String getValue() {
-    return ExperimentService.getInstance().getExperimentString(key, null);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/experiments/SystemPropertyExperimentLoader.java b/blaze-base/src/com/google/idea/blaze/base/experiments/SystemPropertyExperimentLoader.java
deleted file mode 100644
index 8a02670..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/experiments/SystemPropertyExperimentLoader.java
+++ /dev/null
@@ -1,41 +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.experiments;
-
-import com.google.common.collect.ImmutableMap;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.Map;
-
-class SystemPropertyExperimentLoader implements ExperimentLoader {
-  private static final String BLAZE_EXPERIMENT_OVERRIDE = "blaze.experiment.";
-
-  @Override
-  public Map<String, String> getExperiments() {
-    // Cache the current values of the experiments so that they don't change in the
-    // current ExperimentScope.
-    ImmutableMap.Builder<String, String> mapBuilder = ImmutableMap.builder();
-    System.getProperties()
-        .stringPropertyNames()
-        .stream()
-        .filter(name -> name.startsWith(BLAZE_EXPERIMENT_OVERRIDE))
-        .forEach(
-            name ->
-                mapBuilder.put(
-                    name.substring(BLAZE_EXPERIMENT_OVERRIDE.length()), System.getProperty(name)));
-    return mapBuilder.build();
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/experiments/WebExperimentLoader.java b/blaze-base/src/com/google/idea/blaze/base/experiments/WebExperimentLoader.java
deleted file mode 100644
index 67e51f3..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/experiments/WebExperimentLoader.java
+++ /dev/null
@@ -1,28 +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.experiments;
-
-import com.google.common.collect.ImmutableMap;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.Map;
-
-class WebExperimentLoader implements ExperimentLoader {
-  @Override
-  public Map<String, String> getExperiments() {
-    return WebExperimentSyncer.getInstance().getExperimentValues();
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/experiments/WebExperimentSyncer.java b/blaze-base/src/com/google/idea/blaze/base/experiments/WebExperimentSyncer.java
deleted file mode 100644
index e4675f9..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/experiments/WebExperimentSyncer.java
+++ /dev/null
@@ -1,196 +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.experiments;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Maps;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListenableScheduledFuture;
-import com.google.common.util.concurrent.ListeningScheduledExecutorService;
-import com.google.common.util.concurrent.MoreExecutors;
-import com.google.gson.JsonParseException;
-import com.google.idea.blaze.base.util.SerializationUtil;
-import com.intellij.openapi.application.PathManager;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.util.io.HttpRequests;
-import org.jetbrains.io.JsonReaderEx;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.*;
-
-/**
- * A singleton class that retrieves the experiments from the experiments service</a>.
- *
- * The first time {@link #getExperimentValues()} is called, fresh data will be retrieved in the
- * current thread. Thereafter, data will be retrieved every 5 minutes in a background thread. If
- * there is a failure retrieving data, new attempts will be made every minute.
- */
-class WebExperimentSyncer {
-  private static final String DEFAULT_EXPERIMENT_URL =
-      "https://intellij-experiments.appspot.com/api/experiments";
-  private static final String EXPERIMENTS_URL_PROPERTY = "blaze.experiments.url";
-
-  private static final int SUCESSFUL_DOWNLOAD_DELAY_MINUTES = 5;
-  private static final int DOWNLOAD_FAILURE_DELAY_MINUTES = 1;
-
-  private static final String CACHE_FILE_NAME = "blaze.experiments.cache.dat";
-
-  private static final Logger LOG = Logger.getInstance(WebExperimentSyncer.class);
-
-  private static final WebExperimentSyncer INSTANCE = new WebExperimentSyncer();
-
-  // null indicates no fetch attempt has been made. After the first attempt, this will always be a
-  // (possibly empty) map.
-  private Map<String, String> experimentValues = null;
-
-  private ListeningScheduledExecutorService executor =
-      MoreExecutors.listeningDecorator(
-          MoreExecutors.getExitingScheduledExecutorService(
-              new ScheduledThreadPoolExecutor(1), 0, TimeUnit.SECONDS));
-
-  private WebExperimentSyncer() {}
-
-  public static WebExperimentSyncer getInstance() {
-    return INSTANCE;
-  }
-
-  /**
-   * Get the last-retrieved set of experiment values.
-   *
-   * The first time this method is called, an attempt to retrieve the values will take place.
-   * Thereafter, the values will be retrieved every five minutes on a background thread and this
-   * method will return the most recent successfully retrieved values.
-   */
-  public synchronized Map<String, String> getExperimentValues() {
-    if (experimentValues == null) {
-      initialize();
-    }
-
-    return experimentValues;
-  }
-
-  private synchronized void setExperimentValues(HashMap<String, String> experimentValues) {
-    this.experimentValues = experimentValues;
-    saveCache(experimentValues);
-  }
-
-  /**
-   * Fetch and process the experiments on the current thread.
-   */
-  private void initialize() {
-    ListenableFuture<String> response =
-        MoreExecutors.sameThreadExecutor().submit(new WebExperimentsDownloader());
-    response.addListener(
-        new WebExperimentsResultProcessor(response, false), MoreExecutors.sameThreadExecutor());
-
-    // Failed to fetch, try to load cache from disk
-    if (experimentValues == null) {
-      experimentValues = loadCache();
-    }
-
-    // There must have been an error retrieving the experiments.
-    if (experimentValues == null) {
-      experimentValues = ImmutableMap.of();
-    }
-  }
-
-  private void scheduleNextRefresh(boolean refreshWasSuccessful) {
-    int delayInMinutes =
-        refreshWasSuccessful ? SUCESSFUL_DOWNLOAD_DELAY_MINUTES : DOWNLOAD_FAILURE_DELAY_MINUTES;
-    ListenableScheduledFuture<String> refreshResults =
-        executor.schedule(new WebExperimentsDownloader(), delayInMinutes, TimeUnit.MINUTES);
-    refreshResults.addListener(
-        new WebExperimentsResultProcessor(refreshResults, true), MoreExecutors.sameThreadExecutor());
-  }
-
-  private static class WebExperimentsDownloader implements Callable<String> {
-
-    @Override
-    public String call() throws Exception {
-      LOG.debug("About to fetch experiments.");
-      return HttpRequests.request(
-              System.getProperty(EXPERIMENTS_URL_PROPERTY, DEFAULT_EXPERIMENT_URL))
-          .readString(null /* progress indicator */);
-    }
-  }
-
-  private class WebExperimentsResultProcessor implements Runnable {
-
-    private final Future<String> resultFuture;
-    private final boolean triggerExperimentsReload;
-
-    private WebExperimentsResultProcessor(Future<String> resultFuture,
-                                          boolean triggerExperimentsReload) {
-      this.resultFuture = resultFuture;
-      this.triggerExperimentsReload = triggerExperimentsReload;
-    }
-
-    @Override
-    public void run() {
-      LOG.debug("Experiments fetched. Processing results.");
-      try {
-        HashMap<String, String> mapBuilder = Maps.newHashMap();
-        String result = resultFuture.get();
-        try (JsonReaderEx reader = new JsonReaderEx(result)) {
-          reader.beginObject();
-          while (reader.hasNext()) {
-            String experimentName = reader.nextName();
-            String experimentValue = reader.nextString();
-            mapBuilder.put(experimentName, experimentValue);
-          }
-        }
-        setExperimentValues(mapBuilder);
-
-        if (triggerExperimentsReload) {
-          ExperimentService.getInstance().reloadExperiments();
-        }
-        LOG.debug("Successfully fetched experiments: " + getExperimentValues());
-        scheduleNextRefresh(true /* refreshWasSuccessful */);
-      } catch (InterruptedException | ExecutionException | JsonParseException e) {
-        LOG.debug("Error fetching experiments", e);
-        scheduleNextRefresh(false /* refreshWasSuccessful */);
-      }
-    }
-  }
-
-  private static void saveCache(HashMap<String, String> experiments) {
-    try {
-      SerializationUtil.saveToDisk(getCacheFile(), experiments);
-    } catch (IOException e) {
-      LOG.warn("Could not save experiments cache to disk: " + getCacheFile());
-    }
-  }
-
-  @SuppressWarnings("unchecked")
-  private static HashMap<String, String> loadCache() {
-    try {
-      return (HashMap<String, String>)SerializationUtil.loadFromDisk(getCacheFile(), ImmutableList.of());
-    }
-    catch (IOException e) {
-      // This is normal, we might be offline and have never loaded the cache.
-      LOG.info("Could not load experiments file: " + getCacheFile());
-    }
-    return null;
-  }
-
-  private static File getCacheFile() {
-    return new File(new File(PathManager.getSystemPath(), "blaze"), CACHE_FILE_NAME).getAbsoluteFile();
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/help/BlazeHelpHandler.java b/blaze-base/src/com/google/idea/blaze/base/help/BlazeHelpHandler.java
deleted file mode 100644
index 145748c..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/help/BlazeHelpHandler.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.help;
-
-import com.intellij.openapi.components.ServiceManager;
-
-/**
- * Handles help requests.
- */
-public interface BlazeHelpHandler {
-  static BlazeHelpHandler getInstance() {
-    return ServiceManager.getService(BlazeHelpHandler.class);
-  }
-
-  void handleHelp(String urlFragment);
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/ide/NewBlazePackageAction.java b/blaze-base/src/com/google/idea/blaze/base/ide/NewBlazePackageAction.java
deleted file mode 100644
index 3471e5f..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/ide/NewBlazePackageAction.java
+++ /dev/null
@@ -1,236 +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.ide;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.actions.BlazeAction;
-import com.google.idea.blaze.base.buildmodifier.BuildFileModifier;
-import com.google.idea.blaze.base.buildmodifier.FileSystemModifier;
-import com.google.idea.blaze.base.metrics.Action;
-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.WorkspacePath;
-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.scope.BlazeContext;
-import com.google.idea.blaze.base.scope.Scope;
-import com.google.idea.blaze.base.scope.ScopedOperation;
-import com.google.idea.blaze.base.scope.output.PrintOutput;
-import com.google.idea.blaze.base.scope.output.StatusOutput;
-import com.google.idea.blaze.base.scope.scopes.LoggedTimingScope;
-import com.google.idea.blaze.base.settings.Blaze;
-import com.google.idea.blaze.base.sync.projectview.ImportRoots;
-import com.intellij.history.LocalHistory;
-import com.intellij.history.LocalHistoryAction;
-import com.intellij.ide.IdeView;
-import com.intellij.ide.util.DirectoryChooserUtil;
-import com.intellij.openapi.actionSystem.AnActionEvent;
-import com.intellij.openapi.actionSystem.CommonDataKeys;
-import com.intellij.openapi.actionSystem.LangDataKeys;
-import com.intellij.openapi.actionSystem.Presentation;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.project.DumbAware;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.io.FileUtil;
-import com.intellij.openapi.vfs.VfsUtil;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.psi.PsiDirectory;
-import com.intellij.psi.PsiFile;
-import com.intellij.psi.PsiManager;
-import com.intellij.util.PlatformIcons;
-import org.jetbrains.annotations.NotNull;
-
-import javax.annotation.Nullable;
-import java.io.File;
-import java.util.List;
-import java.util.stream.Collectors;
-
-public class NewBlazePackageAction extends BlazeAction implements DumbAware {
-  private static final Logger LOG = Logger.getInstance(NewBlazePackageAction.class);
-
-  private static final String BUILD_FILE_NAME = "BUILD";
-
-  public NewBlazePackageAction() {
-    super();
-  }
-
-  @Override
-  public void actionPerformed(AnActionEvent event) {
-    final IdeView view = event.getData(LangDataKeys.IDE_VIEW);
-    final Project project = event.getData(CommonDataKeys.PROJECT);
-    Scope.root(new ScopedOperation() {
-      @Override
-      public void execute(@NotNull final BlazeContext context) {
-        context.push(new LoggedTimingScope(project, Action.CREATE_BLAZE_PACKAGE));
-
-        if (view == null || project == null) {
-          return;
-        }
-        PsiDirectory directory = getOrChooseDirectory(project, view);
-
-        if (directory == null) {
-          return;
-        }
-
-        NewBlazePackageDialog newBlazePackageDialog = new NewBlazePackageDialog(project, directory);
-        boolean isOk = newBlazePackageDialog.showAndGet();
-        if (!isOk) {
-          return;
-        }
-
-        final Label newRule = newBlazePackageDialog.getNewRule();
-        final Kind newRuleKind = newBlazePackageDialog.getNewRuleKind();
-        // If we returned OK, we should have a non null result
-        LOG.assertTrue(newRule != null);
-        LOG.assertTrue(newRuleKind != null);
-
-        context.output(new StatusOutput(String.format("Setting up a new %s package", Blaze.buildSystemName(project))));
-
-        boolean success = createPackageOnDisk(
-          project,
-          context,
-          newRule,
-          newRuleKind
-        );
-
-        if (!success) {
-          return;
-        }
-
-        File newDirectory = WorkspaceRoot.fromProject(project).fileForPath(newRule.blazePackage());
-        VirtualFile virtualFile = VfsUtil.findFileByIoFile(newDirectory, true);
-        // We just created this file, it should exist
-        LOG.assertTrue(virtualFile != null);
-        PsiFile psiFile = PsiManager.getInstance(project).findFile(virtualFile);
-        view.selectElement(psiFile);
-      }
-    });
-  }
-
-  private static Boolean createPackageOnDisk(
-    @NotNull Project project,
-    @NotNull BlazeContext context,
-    @NotNull Label newRule,
-    @NotNull Kind ruleKind) {
-    LocalHistoryAction action;
-
-    String actionName = String.format("Creating %s package: %s", Blaze.buildSystemName(project), newRule.toString());
-    LocalHistory localHistory = LocalHistory.getInstance();
-    action = localHistory.startAction(actionName);
-
-    // Create the package + BUILD file + rule
-    FileSystemModifier fileSystemModifier = FileSystemModifier.getInstance(project);
-    WorkspacePath newWorkspacePath = newRule.blazePackage();
-    File newDirectory = fileSystemModifier.makeWorkspacePathDirs(newWorkspacePath);
-    if (newDirectory == null) {
-      String errorMessage = "Could not create new package directory: " + newWorkspacePath.toString();
-      context.output(PrintOutput.error(errorMessage));
-      return false;
-    }
-    File buildFile = fileSystemModifier.createFile(newWorkspacePath, BUILD_FILE_NAME);
-    if (buildFile == null) {
-      String errorMessage =
-        "Could not create new BUILD file in package: " + newWorkspacePath.toString();
-      context.output(PrintOutput.error(errorMessage));
-      return false;
-    }
-    BuildFileModifier buildFileModifier = BuildFileModifier.getInstance();
-    buildFileModifier.addRule(project, context, newRule, ruleKind);
-    action.finish();
-
-    return true;
-  }
-
-  @Override
-  protected void doUpdate(@NotNull AnActionEvent event) {
-    Presentation presentation = event.getPresentation();
-    if (isEnabled(event)) {
-      String text = String.format("New %s Package", Blaze.buildSystemName(event.getProject()));
-      presentation.setEnabledAndVisible(true);
-      presentation.setText(text);
-      presentation.setDescription(text);
-      presentation.setIcon(PlatformIcons.PACKAGE_ICON);
-    } else {
-      presentation.setEnabledAndVisible(false);
-    }
-  }
-
-  private boolean isEnabled(AnActionEvent event) {
-    Project project = event.getProject();
-    IdeView view = event.getData(LangDataKeys.IDE_VIEW);
-    if (project == null || view == null) {
-      return false;
-    }
-
-    List<PsiDirectory> directories = filterDirectories(project, view.getDirectories());
-    if (directories.isEmpty()) {
-      return false;
-    }
-
-    return true;
-  }
-
-  /**
-   * Filter out directories that do not live under the project's directories.
-   */
-  private static List<PsiDirectory> filterDirectories(Project project, PsiDirectory[] directories) {
-    if (directories.length == 0) {
-      return ImmutableList.of();
-    }
-    ProjectViewSet projectViewSet = ProjectViewManager.getInstance(project).getProjectViewSet();
-    if (projectViewSet == null) {
-      return ImmutableList.of();
-    }
-    WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProject(project);
-    ImportRoots importRoots = ImportRoots.builder(workspaceRoot, Blaze.getBuildSystem(project)).add(projectViewSet).build();
-    return Lists.newArrayList(directories).stream()
-      .filter(directory -> isUnderProjectViewDirectory(workspaceRoot, importRoots, directory))
-      .collect(Collectors.toList());
-  }
-
-  private static boolean isUnderProjectViewDirectory(WorkspaceRoot workspaceRoot,
-                                                     ImportRoots importRoots,
-                                                     PsiDirectory directory) {
-    VirtualFile virtualFile = directory.getVirtualFile();
-    // Ignore jars, etc. and their contents, which are in an ArchiveFileSystem.
-    if (!(virtualFile.isInLocalFileSystem())) {
-      return false;
-    }
-    if (!workspaceRoot.isInWorkspace(virtualFile)) {
-      return false;
-    }
-    WorkspacePath workspacePath = workspaceRoot.workspacePathFor(virtualFile);
-    return importRoots.rootDirectories().stream()
-      .anyMatch(importRoot -> FileUtil.isAncestor(importRoot.relativePath(), workspacePath.relativePath(), false));
-  }
-
-  @Nullable
-  private static PsiDirectory getOrChooseDirectory(Project project, IdeView view) {
-    List<PsiDirectory> dirs = filterDirectories(project, view.getDirectories());
-    if (dirs.size() == 0) {
-      return null;
-    }
-    if (dirs.size() == 1) {
-      return dirs.get(0);
-    }
-    else {
-      return DirectoryChooserUtil.selectDirectory(project, dirs.toArray(new PsiDirectory[dirs.size()]), null, "");
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/ide/NewBlazePackageDialog.java b/blaze-base/src/com/google/idea/blaze/base/ide/NewBlazePackageDialog.java
deleted file mode 100644
index d99d254..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/ide/NewBlazePackageDialog.java
+++ /dev/null
@@ -1,144 +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.ide;
-
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.model.primitives.*;
-import com.google.idea.blaze.base.model.primitives.Label;
-import com.google.idea.blaze.base.ui.BlazeValidationError;
-import com.google.idea.blaze.base.ui.UiUtil;
-import com.intellij.CommonBundle;
-import com.intellij.ide.IdeBundle;
-import com.intellij.ide.actions.CreateElementActionBase;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.ui.DialogWrapper;
-import com.intellij.openapi.ui.Messages;
-import com.intellij.openapi.ui.ValidationInfo;
-import com.intellij.psi.PsiDirectory;
-import com.intellij.ui.components.JBLabel;
-import com.intellij.ui.components.JBTextField;
-import com.intellij.util.IncorrectOperationException;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import javax.swing.*;
-import java.awt.*;
-import java.io.File;
-import java.util.List;
-
-public class NewBlazePackageDialog extends DialogWrapper {
-  private static final Logger LOG = Logger.getInstance(NewBlazePackageDialog.class);
-
-  @NotNull private final Project project;
-  @NotNull private final PsiDirectory parentDirectory;
-
-  @Nullable private Label newRule;
-  @Nullable private Kind newRuleKind;
-
-  private static final int UI_INDENT_LEVEL = 0;
-  private static final int TEXT_FIELD_LENGTH = 40;
-  @NotNull private final JPanel component = new JPanel(new GridBagLayout());
-  @NotNull private final JBLabel packageLabel = new JBLabel("Package name:");
-  @NotNull private final JBTextField packageNameField = new JBTextField(TEXT_FIELD_LENGTH);
-  @NotNull private final NewRuleUI newRuleUI = new NewRuleUI(TEXT_FIELD_LENGTH);
-
-  public NewBlazePackageDialog(@NotNull Project project,
-                               @NotNull PsiDirectory currentDirectory) {
-    super(project);
-    this.project = project;
-    this.parentDirectory = currentDirectory;
-
-    initializeUI();
-  }
-
-  private void initializeUI() {
-    component.add(packageLabel);
-    component.add(packageNameField, UiUtil.getFillLineConstraints(UI_INDENT_LEVEL));
-    newRuleUI.fillUI(component, UI_INDENT_LEVEL);
-    UiUtil.fillBottom(component);
-    init();
-  }
-
-  @Nullable
-  @Override
-  protected JComponent createCenterPanel() {
-    return component;
-  }
-
-  @Nullable
-  @Override
-  protected ValidationInfo doValidate() {
-    String packageName = packageNameField.getText();
-    if (packageName == null) {
-      return new ValidationInfo("Internal error, package was null");
-    }
-    if (packageName.length() == 0) {
-      return new ValidationInfo(
-        IdeBundle.message("error.name.should.be.specified"),
-        packageNameField
-      );
-    }
-    List<BlazeValidationError> errors = Lists.newArrayList();
-    if (!Label.validatePackagePath(packageName, errors)) {
-      BlazeValidationError validationResult = errors.get(0);
-      return new ValidationInfo(validationResult.getError(), packageNameField);
-    }
-
-    return newRuleUI.validate();
-  }
-
-  @Override
-  protected void doOKAction() {
-    WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProject(project);
-    LOG.assertTrue(parentDirectory.getVirtualFile().isInLocalFileSystem());
-    File parentDirectoryFile = new File(parentDirectory.getVirtualFile().getPath());
-    String newPackageName = packageNameField.getText();
-    File newPackageDirectory = new File(parentDirectoryFile, newPackageName);
-    WorkspacePath newPackagePath = workspaceRoot.workspacePathFor(newPackageDirectory);
-
-    RuleName newRuleName = newRuleUI.getRuleName();
-    Label newRule = new Label(newPackagePath, newRuleName);
-    Kind ruleKind = newRuleUI.getSelectedRuleKind();
-    try {
-      parentDirectory.checkCreateSubdirectory(newPackageName);
-    }
-    catch (IncorrectOperationException ex) {
-      showErrorDialog(CreateElementActionBase.filterMessage(ex.getMessage()));
-      // do not close the dialog
-      return;
-    }
-    this.newRule = newRule;
-    this.newRuleKind = ruleKind;
-    super.doOKAction();
-  }
-
-  private void showErrorDialog(@NotNull String message) {
-    String title = CommonBundle.getErrorTitle();
-    Icon icon = Messages.getErrorIcon();
-    Messages.showMessageDialog(component, message, title, icon);
-  }
-
-  @Nullable
-  public Label getNewRule() {
-    return newRule;
-  }
-
-  @Nullable
-  public Kind getNewRuleKind() {
-    return newRuleKind;
-  }
-}
\ No newline at end of file
diff --git a/blaze-base/src/com/google/idea/blaze/base/ide/NewBlazeRuleAction.java b/blaze-base/src/com/google/idea/blaze/base/ide/NewBlazeRuleAction.java
deleted file mode 100644
index 4efe8d6..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/ide/NewBlazeRuleAction.java
+++ /dev/null
@@ -1,85 +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.ide;
-
-import com.google.idea.blaze.base.actions.BlazeAction;
-import com.google.idea.blaze.base.experiments.ExperimentScope;
-import com.google.idea.blaze.base.metrics.Action;
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.google.idea.blaze.base.scope.Scope;
-import com.google.idea.blaze.base.scope.ScopedOperation;
-import com.google.idea.blaze.base.scope.scopes.BlazeConsoleScope;
-import com.google.idea.blaze.base.scope.scopes.IssuesScope;
-import com.google.idea.blaze.base.scope.scopes.LoggedTimingScope;
-import com.google.idea.blaze.base.settings.Blaze;
-import com.intellij.openapi.actionSystem.*;
-import com.intellij.openapi.project.DumbAware;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.vfs.VirtualFile;
-import org.jetbrains.annotations.NotNull;
-
-import javax.annotation.Nullable;
-
-public class NewBlazeRuleAction extends BlazeAction implements DumbAware {
-
-  public NewBlazeRuleAction() {
-    super();
-  }
-
-  @Override
-  public void actionPerformed(AnActionEvent event) {
-    final Project project = event.getData(CommonDataKeys.PROJECT);
-    if (project == null) {
-      return;
-    }
-    final VirtualFile virtualFile = event.getData(CommonDataKeys.VIRTUAL_FILE);
-    if (virtualFile == null) {
-      return;
-    }
-
-    Scope.root(new ScopedOperation() {
-      @Override
-      public void execute(@NotNull BlazeContext context) {
-        context
-          .push(new ExperimentScope())
-          .push(new BlazeConsoleScope.Builder(project).build())
-          .push(new IssuesScope(project))
-          .push(new LoggedTimingScope(project, Action.CREATE_BLAZE_RULE))
-        ;
-        NewBlazeRuleDialog newBlazeRuleDialog = new NewBlazeRuleDialog(context, project, virtualFile);
-        newBlazeRuleDialog.show();
-      }
-    });
-  }
-
-  @Override
-  protected void doUpdate(@NotNull AnActionEvent event) {
-    Presentation presentation = event.getPresentation();
-    DataContext dataContext = event.getDataContext();
-    VirtualFile file = CommonDataKeys.VIRTUAL_FILE.getData(dataContext);
-    Project project = CommonDataKeys.PROJECT.getData(dataContext);
-    boolean enabled = (project != null && file != null && file.getName().equals("BUILD"));
-    presentation.setVisible(enabled || ActionPlaces.isMainMenuOrActionSearch(event.getPlace()));
-    presentation.setEnabled(enabled);
-    presentation.setText(getText(project));
-  }
-
-  private static String getText(@Nullable Project project) {
-    String buildSystem = Blaze.buildSystemName(project);
-    return String.format("New %s Rule", buildSystem);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/ide/NewBlazeRuleDialog.java b/blaze-base/src/com/google/idea/blaze/base/ide/NewBlazeRuleDialog.java
deleted file mode 100644
index 5cfc981..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/ide/NewBlazeRuleDialog.java
+++ /dev/null
@@ -1,106 +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.ide;
-
-import com.google.idea.blaze.base.buildmodifier.BuildFileModifier;
-import com.google.idea.blaze.base.model.primitives.*;
-import com.google.idea.blaze.base.model.primitives.Label;
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.google.idea.blaze.base.settings.Blaze;
-import com.google.idea.blaze.base.ui.UiUtil;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.ui.DialogWrapper;
-import com.intellij.openapi.ui.ValidationInfo;
-import com.intellij.openapi.vfs.VirtualFile;
-
-import javax.annotation.Nullable;
-import javax.swing.*;
-import java.awt.*;
-import java.io.File;
-
-public class NewBlazeRuleDialog extends DialogWrapper {
-  private static final Logger LOG = Logger.getInstance(NewBlazeRuleDialog.class);
-
-  private static final int UI_INDENT = 0;
-  private static final int TEXT_BOX_WIDTH = 40;
-
-  private final BlazeContext context;
-  private final Project project;
-  private final VirtualFile buildFile;
-  private final String buildSystemName;
-
-  private JPanel component = new JPanel(new GridBagLayout());
-  private final NewRuleUI newRuleUI = new NewRuleUI(TEXT_BOX_WIDTH);
-  private static final Dimension componentSize = new Dimension(500, 500);
-
-  public NewBlazeRuleDialog(BlazeContext context,
-                            Project project,
-                            VirtualFile buildFile) {
-    super(project);
-    this.context = context;
-    this.project = project;
-    this.buildFile = buildFile;
-    this.buildSystemName = Blaze.buildSystemName(project);
-    initComponent();
-  }
-
-  private void initComponent() {
-    setTitle(String.format("Create a New %s Rule", buildSystemName));
-    setOKButtonText("Create");
-    setCancelButtonText("Cancel");
-
-    component.setPreferredSize(componentSize);
-    component.setMinimumSize(componentSize);
-
-    newRuleUI.fillUI(component, UI_INDENT);
-    UiUtil.fillBottom(component);
-
-    init();
-  }
-
-  @Nullable
-  @Override
-  protected JComponent createCenterPanel() {
-    return component;
-  }
-
-  @Nullable
-  @Override
-  protected ValidationInfo doValidate() {
-    return newRuleUI.validate();
-  }
-
-  @Override
-  protected void doOKAction() {
-    RuleName ruleName = 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);
-    BuildFileModifier buildFileModifier = BuildFileModifier.getInstance();
-    boolean success = buildFileModifier.addRule(project, context, newRule, ruleKind);
-
-    if (success) {
-      super.doOKAction();
-    }
-    else {
-      super.setErrorText(String.format("Could not create new rule, see %s Console for details", buildSystemName));
-    }
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/ide/NewRuleUI.java b/blaze-base/src/com/google/idea/blaze/base/ide/NewRuleUI.java
deleted file mode 100644
index de090d9..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/ide/NewRuleUI.java
+++ /dev/null
@@ -1,88 +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.ide;
-
-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.ui.BlazeValidationError;
-import com.google.idea.blaze.base.ui.UiUtil;
-import com.intellij.ide.IdeBundle;
-import com.intellij.openapi.ui.ComboBox;
-import com.intellij.openapi.ui.ValidationInfo;
-import com.intellij.ui.components.JBLabel;
-import com.intellij.ui.components.JBTextField;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import javax.swing.*;
-import java.util.Collection;
-import java.util.List;
-
-final class NewRuleUI {
-
-  private static final String[] POSSIBLE_RULES = {
-    "android_library",
-    "java_library",
-    "cc_library",
-    "cc_binary",
-    "proto_library"
-  };
-
-  @NotNull private final ComboBox ruleComboBox = new ComboBox(POSSIBLE_RULES);
-  @NotNull private final JBLabel ruleNameLabel = new JBLabel("Rule name:");
-  @NotNull private final JBTextField ruleNameField;
-
-  public NewRuleUI(int textFieldLength) {
-    this.ruleNameField = new JBTextField(textFieldLength);
-  }
-
-  public void fillUI(@NotNull JPanel component, int indentLevel) {
-    component.add(ruleNameLabel);
-    component.add(ruleNameField, UiUtil.getFillLineConstraints(indentLevel));
-    component.add(ruleComboBox, UiUtil.getFillLineConstraints(indentLevel));
-  }
-
-  @NotNull
-  public Kind getSelectedRuleKind() {
-    return Kind.fromString((String)ruleComboBox.getSelectedItem());
-  }
-
-  @NotNull
-  public RuleName getRuleName() {
-    return RuleName.create(ruleNameField.getText());
-  }
-
-  @Nullable
-  public ValidationInfo validate() {
-    String ruleName = ruleNameField.getText();
-    List<BlazeValidationError> errors = Lists.newArrayList();
-    if (!validateRuleName(ruleName, errors)) {
-      BlazeValidationError issue = errors.get(0);
-      return new ValidationInfo(issue.getError(), ruleNameField);
-    }
-    return null;
-  }
-
-  private static boolean validateRuleName(@NotNull String inputString, @Nullable Collection<BlazeValidationError> errors) {
-    if (inputString.length() == 0) {
-      BlazeValidationError.collect(errors, new BlazeValidationError(IdeBundle.message("error.name.should.be.specified")));
-      return false;
-    }
-
-    return RuleName.validate(inputString, errors);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/ideinfo/AndroidRuleIdeInfo.java b/blaze-base/src/com/google/idea/blaze/base/ideinfo/AndroidRuleIdeInfo.java
deleted file mode 100644
index fa567a5..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/ideinfo/AndroidRuleIdeInfo.java
+++ /dev/null
@@ -1,123 +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.Lists;
-import com.google.idea.blaze.base.model.primitives.Label;
-import org.jetbrains.annotations.Nullable;
-
-import java.io.Serializable;
-import java.util.Collection;
-
-/**
- * Ide info specific to android rules.
- */
-public final class AndroidRuleIdeInfo implements Serializable {
-  private static final long serialVersionUID = 5L;
-
-  public final Collection<ArtifactLocation> resources;
-  @Nullable public final ArtifactLocation manifest;
-  @Nullable public final LibraryArtifact idlJar;
-  @Nullable public final LibraryArtifact resourceJar;
-  public final boolean hasIdlSources;
-  @Nullable public final String resourceJavaPackage;
-  public boolean generateResourceClass;
-  @Nullable public Label legacyResources;
-
-  public AndroidRuleIdeInfo(Collection<ArtifactLocation> resources,
-                            @Nullable String resourceJavaPackage,
-                            boolean generateResourceClass,
-                            @Nullable ArtifactLocation manifest,
-                            @Nullable LibraryArtifact idlJar,
-                            @Nullable LibraryArtifact resourceJar,
-                            boolean hasIdlSources,
-                            @Nullable Label legacyResources) {
-    this.resources = resources;
-    this.resourceJavaPackage = resourceJavaPackage;
-    this.generateResourceClass = generateResourceClass;
-    this.manifest = manifest;
-    this.idlJar = idlJar;
-    this.resourceJar = resourceJar;
-    this.hasIdlSources = hasIdlSources;
-    this.legacyResources = legacyResources;
-  }
-
-  public static Builder builder() {
-    return new Builder();
-  }
-
-  public static class Builder {
-    private Collection<ArtifactLocation> resources = Lists.newArrayList();
-    private ArtifactLocation manifest;
-    private LibraryArtifact idlJar;
-    private LibraryArtifact resourceJar;
-    private boolean hasIdlSources;
-    private String resourceJavaPackage;
-    private boolean generateResourceClass;
-    private Label legacyResources;
-
-    public Builder setManifestFile(ArtifactLocation artifactLocation) {
-      this.manifest = artifactLocation;
-      return this;
-    }
-    public Builder addResource(ArtifactLocation artifactLocation) {
-      this.resources.add(artifactLocation);
-      return this;
-    }
-    public Builder setIdlJar(LibraryArtifact idlJar) {
-      this.idlJar = idlJar;
-      return this;
-    }
-    public Builder setHasIdlSources(boolean hasIdlSources) {
-      this.hasIdlSources = hasIdlSources;
-      return this;
-    }
-    public Builder setResourceJar(LibraryArtifact.Builder resourceJar) {
-      this.resourceJar = resourceJar.build();
-      return this;
-    }
-    public Builder setResourceJavaPackage(@Nullable String resourceJavaPackage) {
-      this.resourceJavaPackage = resourceJavaPackage;
-      return this;
-    }
-    public Builder setGenerateResourceClass(boolean generateResourceClass) {
-      this.generateResourceClass = generateResourceClass;
-      return this;
-    }
-    public Builder setLegacyResources(@Nullable Label legacyResources) {
-      this.legacyResources = legacyResources;
-      return this;
-    }
-    public AndroidRuleIdeInfo build() {
-      if (!resources.isEmpty() || manifest != null) {
-        if (!generateResourceClass) {
-          throw new IllegalStateException("Must set generateResourceClass if manifest or resources set");
-        }
-      }
-
-      return new AndroidRuleIdeInfo(
-        resources,
-        resourceJavaPackage,
-        generateResourceClass,
-        manifest,
-        idlJar,
-        resourceJar,
-        hasIdlSources,
-        legacyResources
-      );
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/ideinfo/ArtifactLocation.java b/blaze-base/src/com/google/idea/blaze/base/ideinfo/ArtifactLocation.java
deleted file mode 100644
index f40e633..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/ideinfo/ArtifactLocation.java
+++ /dev/null
@@ -1,134 +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 java.io.File;
-import java.io.Serializable;
-import java.nio.file.Paths;
-
-/**
- * Represents a blaze-produced artifact.
- */
-public final class ArtifactLocation implements Serializable {
-  private static final long serialVersionUID = 2L;
-
-  public final String rootPath;
-  public final String rootExecutionPathFragment;
-  public final String relativePath;
-  public final boolean isSource;
-
-  private ArtifactLocation(String rootPath,
-                           String rootExecutionPathFragment,
-                           String relativePath,
-                           boolean isSource) {
-    this.rootPath = rootPath;
-    this.rootExecutionPathFragment = rootExecutionPathFragment;
-    this.relativePath = relativePath;
-    this.isSource = isSource;
-  }
-
-  /**
-   * Returns the root path of the artifact, eg. blaze-out
-   */
-  public String getRootPath() {
-    return rootPath;
-  }
-
-  /**
-   * Gets the path relative to the root path.
-   */
-  public String getRelativePath() {
-    return relativePath;
-  }
-
-  public boolean isSource() {
-    return isSource;
-  }
-
-  public boolean isGenerated() {
-    return !isSource;
-  }
-
-  public File getFile() {
-    return new File(getRootPath(), getRelativePath());
-  }
-
-  /**
-   * Returns rootExecutionPathFragment + relativePath.
-   * For source artifacts, this is simply relativePath
-   */
-  public String getExecutionRootRelativePath() {
-    return Paths.get(rootExecutionPathFragment, relativePath).toString();
-  }
-
-  public static Builder builder() {
-    return new Builder();
-  }
-
-  public static class Builder {
-    String rootPath;
-    String relativePath;
-    String rootExecutionPathFragment = "";
-    boolean isSource;
-
-    public Builder setRootPath(String rootPath) {
-      this.rootPath = rootPath;
-      return this;
-    }
-
-    public Builder setRelativePath(String relativePath) {
-      this.relativePath = relativePath;
-      return this;
-    }
-
-    public Builder setRootExecutionPathFragment(String rootExecutionPathFragment) {
-      this.rootExecutionPathFragment = rootExecutionPathFragment;
-      return this;
-    }
-
-    public Builder setIsSource(boolean isSource) {
-      this.isSource = isSource;
-      return this;
-    }
-
-    public ArtifactLocation build() {
-      return new ArtifactLocation(rootPath, rootExecutionPathFragment, relativePath, isSource);
-    }
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) return true;
-    if (o == null || getClass() != o.getClass()) return false;
-    ArtifactLocation that = (ArtifactLocation)o;
-    return Objects.equal(rootPath, that.rootPath)
-           && Objects.equal(rootExecutionPathFragment, that.rootExecutionPathFragment)
-           && Objects.equal(relativePath, that.relativePath)
-           && Objects.equal(isSource, that.isSource);
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hashCode(rootPath, rootExecutionPathFragment, relativePath, isSource);
-  }
-
-  @Override
-  public String toString() {
-    return getFile().toString();
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/ideinfo/CRuleIdeInfo.java b/blaze-base/src/com/google/idea/blaze/base/ideinfo/CRuleIdeInfo.java
deleted file mode 100644
index 641bf35..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/ideinfo/CRuleIdeInfo.java
+++ /dev/null
@@ -1,109 +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.ImmutableList;
-import com.google.idea.blaze.base.model.primitives.ExecutionRootPath;
-
-import java.io.Serializable;
-
-/**
- * Sister class to {@link JavaRuleIdeInfo}
- */
-public class CRuleIdeInfo implements Serializable {
-  private static final long serialVersionUID = 6L;
-
-  public final ImmutableList<ArtifactLocation> sources;
-
-  // From the cpp compilation context provider. These should all be for the entire transitive closure.
-  public final ImmutableList<ExecutionRootPath> transitiveIncludeDirectories;
-  public final ImmutableList<ExecutionRootPath> transitiveQuoteIncludeDirectories;
-  public final ImmutableList<String> transitiveDefines;
-  public final ImmutableList<ExecutionRootPath> transitiveSystemIncludeDirectories;
-
-  public CRuleIdeInfo(
-    ImmutableList<ArtifactLocation> sources,
-    ImmutableList<ExecutionRootPath> transitiveIncludeDirectories,
-    ImmutableList<ExecutionRootPath> transitiveQuoteIncludeDirectories,
-    ImmutableList<String> transitiveDefines,
-    ImmutableList<ExecutionRootPath> transitiveSystemIncludeDirectories
-  ) {
-    this.sources = sources;
-    this.transitiveIncludeDirectories = transitiveIncludeDirectories;
-    this.transitiveQuoteIncludeDirectories = transitiveQuoteIncludeDirectories;
-    this.transitiveDefines = transitiveDefines;
-    this.transitiveSystemIncludeDirectories = transitiveSystemIncludeDirectories;
-  }
-
-  public static Builder builder() {
-    return new Builder();
-  }
-
-  public static class Builder {
-    private final ImmutableList.Builder<ArtifactLocation> sources = ImmutableList.builder();
-
-    private final ImmutableList.Builder<ExecutionRootPath> transitiveIncludeDirectories = ImmutableList.builder();
-    private final ImmutableList.Builder<ExecutionRootPath> transitiveQuoteIncludeDirectories = ImmutableList.builder();
-    private final ImmutableList.Builder<String> transitiveDefines = ImmutableList.builder();
-    private final ImmutableList.Builder<ExecutionRootPath> transitiveSystemIncludeDirectories = ImmutableList.builder();
-
-    public Builder addSources(Iterable<ArtifactLocation> sources) {
-      this.sources.addAll(sources);
-      return this;
-    }
-
-    public Builder addTransitiveIncludeDirectories(Iterable<ExecutionRootPath> transitiveIncludeDirectories) {
-      this.transitiveIncludeDirectories.addAll(transitiveIncludeDirectories);
-      return this;
-    }
-
-    public Builder addTransitiveQuoteIncludeDirectories(Iterable<ExecutionRootPath> transitiveQuoteIncludeDirectories) {
-      this.transitiveQuoteIncludeDirectories.addAll(transitiveQuoteIncludeDirectories);
-      return this;
-    }
-
-    public Builder addTransitiveDefines(Iterable<String> transitiveDefines) {
-      this.transitiveDefines.addAll(transitiveDefines);
-      return this;
-    }
-
-    public Builder addTransitiveSystemIncludeDirectories(Iterable<ExecutionRootPath> transitiveSystemIncludeDirectories) {
-      this.transitiveSystemIncludeDirectories.addAll(transitiveSystemIncludeDirectories);
-      return this;
-    }
-
-    public CRuleIdeInfo build() {
-      return new CRuleIdeInfo(
-        sources.build(),
-        transitiveIncludeDirectories.build(),
-        transitiveQuoteIncludeDirectories.build(),
-        transitiveDefines.build(),
-        transitiveSystemIncludeDirectories.build()
-      );
-    }
-  }
-
-  @Override
-  public String toString() {
-    return "CRuleIdeInfo{" + "\n" +
-           "  sources=" + sources + "\n" +
-           "  transitiveIncludeDirectories=" + transitiveIncludeDirectories + "\n" +
-           "  transitiveQuoteIncludeDirectories=" + transitiveQuoteIncludeDirectories + "\n" +
-           "  transitiveDefines=" + transitiveDefines + "\n" +
-           "  transitiveSystemIncludeDirectories=" + transitiveSystemIncludeDirectories + "\n" +
-           '}';
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/ideinfo/CToolchainIdeInfo.java b/blaze-base/src/com/google/idea/blaze/base/ideinfo/CToolchainIdeInfo.java
deleted file mode 100644
index 1f6b0f0..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/ideinfo/CToolchainIdeInfo.java
+++ /dev/null
@@ -1,206 +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.common.collect.ImmutableList;
-import com.google.idea.blaze.base.model.primitives.ExecutionRootPath;
-
-import java.io.Serializable;
-
-/**
- * Sister class to {@link JavaRuleIdeInfo}
- */
-public class CToolchainIdeInfo implements Serializable {
-  private static final long serialVersionUID = 3L;
-
-  public final ImmutableList<String> baseCompilerOptions;
-  public final ImmutableList<String> cCompilerOptions;
-  public final ImmutableList<String> cppCompilerOptions;
-  public final ImmutableList<String> linkOptions;
-  public final ImmutableList<ExecutionRootPath> builtInIncludeDirectories;
-  public final ExecutionRootPath cppExecutable;
-  public final ExecutionRootPath preprocessorExecutable;
-  public final String targetName;
-
-  public final ImmutableList<String> unfilteredCompilerOptions;
-  public final ImmutableList<ExecutionRootPath> unfilteredToolchainSystemIncludes;
-
-  public CToolchainIdeInfo(
-    ImmutableList<String> baseCompilerOptions,
-    ImmutableList<String> cCompilerOptions,
-    ImmutableList<String> cppCompilerOptions,
-    ImmutableList<String> linkOptions,
-    ImmutableList<ExecutionRootPath> builtInIncludeDirectories,
-    ExecutionRootPath cppExecutable,
-    ExecutionRootPath preprocessorExecutable,
-    String targetName,
-    ImmutableList<String> unfilteredCompilerOptions,
-    ImmutableList<ExecutionRootPath> unfilteredToolchainSystemIncludes
-  ) {
-    this.baseCompilerOptions = baseCompilerOptions;
-    this.cCompilerOptions = cCompilerOptions;
-    this.cppCompilerOptions = cppCompilerOptions;
-    this.linkOptions = linkOptions;
-    this.builtInIncludeDirectories = builtInIncludeDirectories;
-    this.cppExecutable = cppExecutable;
-    this.preprocessorExecutable = preprocessorExecutable;
-    this.targetName = targetName;
-    this.unfilteredCompilerOptions = unfilteredCompilerOptions;
-    this.unfilteredToolchainSystemIncludes = unfilteredToolchainSystemIncludes;
-  }
-
-  public static Builder builder() {
-    return new Builder();
-  }
-
-  public static class Builder {
-    private final ImmutableList.Builder<String> baseCompilerOptions = ImmutableList.builder();
-    private final ImmutableList.Builder<String> cCompilerOptions = ImmutableList.builder();
-    private final ImmutableList.Builder<String> cppCompilerOptions = ImmutableList.builder();
-    private final ImmutableList.Builder<String> linkOptions = ImmutableList.builder();
-
-    private final ImmutableList.Builder<ExecutionRootPath> builtInIncludeDirectories = ImmutableList.builder();
-
-    ExecutionRootPath cppExecutable;
-    ExecutionRootPath preprocessorExecutable;
-
-    String targetName = "";
-
-    private final ImmutableList.Builder<String> unfilteredCompilerOptions = ImmutableList.builder();
-    private final ImmutableList.Builder<ExecutionRootPath> unfilteredToolchainSystemIncludes = ImmutableList.builder();
-
-    public Builder addBaseCompilerOptions(Iterable<String> baseCompilerOptions) {
-      this.baseCompilerOptions.addAll(baseCompilerOptions);
-      return this;
-    }
-
-    public Builder addCCompilerOptions(Iterable<String> cCompilerOptions) {
-      this.cCompilerOptions.addAll(cCompilerOptions);
-      return this;
-    }
-
-    public Builder addCppCompilerOptions(Iterable<String> cppCompilerOptions) {
-      this.cppCompilerOptions.addAll(cppCompilerOptions);
-      return this;
-    }
-
-    public Builder addLinkOptions(Iterable<String> linkOptions) {
-      this.linkOptions.addAll(linkOptions);
-      return this;
-    }
-
-    public Builder addBuiltInIncludeDirectories(Iterable<ExecutionRootPath> builtInIncludeDirectories) {
-      this.builtInIncludeDirectories.addAll(builtInIncludeDirectories);
-      return this;
-    }
-
-    public Builder setCppExecutable(ExecutionRootPath cppExecutable) {
-      this.cppExecutable = cppExecutable;
-      return this;
-    }
-
-    public Builder setPreprocessorExecutable(ExecutionRootPath preprocessorExecutable) {
-      this.preprocessorExecutable = preprocessorExecutable;
-      return this;
-    }
-
-    public Builder setTargetName(String targetName) {
-      this.targetName = targetName;
-      return this;
-    }
-
-    public Builder addUnfilteredCompilerOptions(Iterable<String> unfilteredCompilerOptions) {
-      this.unfilteredCompilerOptions.addAll(unfilteredCompilerOptions);
-      return this;
-    }
-
-    public Builder addUnfilteredToolchainSystemIncludes(Iterable<ExecutionRootPath> unfilteredToolchainSystemIncludes) {
-      this.unfilteredToolchainSystemIncludes.addAll(unfilteredToolchainSystemIncludes);
-      return this;
-    }
-
-    public CToolchainIdeInfo build() {
-      return new CToolchainIdeInfo(
-        baseCompilerOptions.build(),
-        cCompilerOptions.build(),
-        cppCompilerOptions.build(),
-        linkOptions.build(),
-        builtInIncludeDirectories.build(),
-        cppExecutable,
-        preprocessorExecutable,
-        targetName,
-        unfilteredCompilerOptions.build(),
-        unfilteredToolchainSystemIncludes.build()
-      );
-    }
-  }
-
-  @Override
-  public String toString() {
-    return "CToolchainIdeInfo{" + "\n" +
-           "  baseCompilerOptions=" + baseCompilerOptions + "\n" +
-           "  cCompilerOptions=" + cCompilerOptions + "\n" +
-           "  cppCompilerOptions=" + cppCompilerOptions + "\n" +
-           "  linkOptions=" + linkOptions + "\n" +
-           "  builtInIncludeDirectories=" + builtInIncludeDirectories + "\n" +
-           "  cppExecutable='" + cppExecutable + '\'' + "\n" +
-           "  preprocessorExecutable='" + preprocessorExecutable + '\'' + "\n" +
-           "  targetName='" + targetName + '\'' + "\n" +
-           "  unfilteredCompilerOptions=" + unfilteredCompilerOptions + "\n" +
-           "  unfilteredToolchainSystemIncludes=" + unfilteredToolchainSystemIncludes + "\n" +
-           '}';
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) {
-      return true;
-    }
-    if (o == null || getClass() != o.getClass()) {
-      return false;
-    }
-    CToolchainIdeInfo that = (CToolchainIdeInfo)o;
-    return
-      Objects.equal(baseCompilerOptions, that.baseCompilerOptions) &&
-      Objects.equal(cCompilerOptions, that.cCompilerOptions) &&
-      Objects.equal(cppCompilerOptions, that.cppCompilerOptions) &&
-      Objects.equal(linkOptions, that.linkOptions) &&
-      Objects.equal(builtInIncludeDirectories, that.builtInIncludeDirectories) &&
-      Objects.equal(cppExecutable, that.cppExecutable) &&
-      Objects.equal(preprocessorExecutable, that.preprocessorExecutable) &&
-      Objects.equal(targetName, that.targetName) &&
-      Objects.equal(unfilteredCompilerOptions, that.unfilteredCompilerOptions) &&
-      Objects.equal(unfilteredToolchainSystemIncludes, that.unfilteredToolchainSystemIncludes)
-      ;
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hashCode(
-      baseCompilerOptions,
-      cCompilerOptions,
-      cppCompilerOptions,
-      linkOptions,
-      builtInIncludeDirectories,
-      cppExecutable,
-      preprocessorExecutable,
-      targetName,
-      unfilteredCompilerOptions,
-      unfilteredToolchainSystemIncludes
-    );
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/ideinfo/JavaRuleIdeInfo.java b/blaze-base/src/com/google/idea/blaze/base/ideinfo/JavaRuleIdeInfo.java
deleted file mode 100644
index 31eb533..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/ideinfo/JavaRuleIdeInfo.java
+++ /dev/null
@@ -1,84 +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.ImmutableList;
-import org.jetbrains.annotations.Nullable;
-
-import java.io.Serializable;
-import java.util.Collection;
-
-/**
- * Ide info specific to java rules.
- */
-public final class JavaRuleIdeInfo implements Serializable {
-  private static final long serialVersionUID = 1L;
-
-  /**
-   * The main jar(s) produced by this java rule.
-   *
-   * <p>Usually this will be a single jar, but java_imports support importing multiple jars.
-   */
-  public final Collection<LibraryArtifact> jars;
-
-  /**
-   * A jar containing annotation processing.
-   */
-  public final Collection<LibraryArtifact> generatedJars;
-
-  /**
-   * File containing a map from .java files to their corresponding package.
-   */
-  @Nullable public final ArtifactLocation packageManifest;
-
-  /**
-   * File containing dependencies.
-   */
-  @Nullable public final ArtifactLocation jdepsFile;
-
-  public JavaRuleIdeInfo(Collection<LibraryArtifact> jars,
-                         Collection<LibraryArtifact> generatedJars,
-                         @Nullable ArtifactLocation packageManifest,
-                         @Nullable ArtifactLocation jdepsFile) {
-    this.jars = jars;
-    this.generatedJars = generatedJars;
-    this.packageManifest = packageManifest;
-    this.jdepsFile = jdepsFile;
-  }
-
-  public static Builder builder() {
-    return new Builder();
-  }
-
-  public static class Builder {
-    ImmutableList.Builder<LibraryArtifact> jars = ImmutableList.builder();
-    ImmutableList.Builder<LibraryArtifact> generatedJars = ImmutableList.builder();
-
-    public Builder addJar(LibraryArtifact.Builder jar) {
-      jars.add(jar.build());
-      return this;
-    }
-
-    public Builder addGeneratedJar(LibraryArtifact.Builder jar) {
-      generatedJars.add(jar.build());
-      return this;
-    }
-
-    public JavaRuleIdeInfo build() {
-      return new JavaRuleIdeInfo(jars.build(), generatedJars.build(), null, null);
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/ideinfo/JavaToolchainIdeInfo.java b/blaze-base/src/com/google/idea/blaze/base/ideinfo/JavaToolchainIdeInfo.java
deleted file mode 100644
index 1fcbf41..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/ideinfo/JavaToolchainIdeInfo.java
+++ /dev/null
@@ -1,64 +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 java.io.Serializable;
-
-/**
- * Represents the java_toolchain class
- */
-public class JavaToolchainIdeInfo implements Serializable {
-  private static final long serialVersionUID = 1L;
-
-  public final String sourceVersion;
-  public final String targetVersion;
-
-  public JavaToolchainIdeInfo(String sourceVersion, String targetVersion) {
-    this.sourceVersion = sourceVersion;
-    this.targetVersion = targetVersion;
-  }
-
-  @Override
-  public String toString() {
-    return "JavaToolchainIdeInfo{" + "\n" +
-           "  sourceVersion=" + sourceVersion + "\n" +
-           "  targetVersion=" + targetVersion + "\n" +
-           '}';
-  }
-
-  public static Builder builder() {
-    return new Builder();
-  }
-
-  public static class Builder {
-    String sourceVersion;
-    String targetVersion;
-
-    public Builder setSourceVersion(String sourceVersion) {
-      this.sourceVersion = sourceVersion;
-      return this;
-    }
-
-    public Builder setTargetVersion(String targetVersion) {
-      this.targetVersion = targetVersion;
-      return this;
-    }
-
-    public JavaToolchainIdeInfo build() {
-      return new JavaToolchainIdeInfo(sourceVersion, targetVersion);
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/ideinfo/LibraryArtifact.java b/blaze-base/src/com/google/idea/blaze/base/ideinfo/LibraryArtifact.java
deleted file mode 100644
index 44f581c..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/ideinfo/LibraryArtifact.java
+++ /dev/null
@@ -1,85 +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 org.jetbrains.annotations.Nullable;
-
-import java.io.Serializable;
-
-/**
- * Represents a jar artifact.
- */
-public class LibraryArtifact implements Serializable {
-  private static final long serialVersionUID = 1L;
-
-  public final ArtifactLocation jar;
-  @Nullable public final ArtifactLocation runtimeJar;
-  @Nullable public final ArtifactLocation sourceJar;
-
-  public LibraryArtifact(ArtifactLocation jar, @Nullable ArtifactLocation runtimeJar, @Nullable ArtifactLocation sourceJar) {
-    this.jar = jar;
-    this.runtimeJar = runtimeJar;
-    this.sourceJar = sourceJar;
-  }
-
-  @Override
-  public String toString() {
-    return String.format("jar=%s, ijar=%s, srcjar=%s", runtimeJar, jar, sourceJar);
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) return true;
-    if (o == null || getClass() != o.getClass()) return false;
-    LibraryArtifact that = (LibraryArtifact)o;
-    return
-      Objects.equal(jar, that.jar) &&
-      Objects.equal(runtimeJar, that.runtimeJar) &&
-      Objects.equal(sourceJar, that.sourceJar);
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hashCode(jar, runtimeJar, sourceJar);
-  }
-
-  public static Builder builder() {
-    return new Builder();
-  }
-
-  public static class Builder {
-    private ArtifactLocation jar;
-    private ArtifactLocation runtimeJar;
-    private ArtifactLocation sourceJar;
-
-    public Builder setJar(ArtifactLocation artifactLocation) {
-      this.jar = artifactLocation;
-      return this;
-    }
-    public Builder setRuntimeJar(@Nullable ArtifactLocation artifactLocation) {
-      this.runtimeJar = artifactLocation;
-      return this;
-    }
-    public Builder setSourceJar(@Nullable ArtifactLocation artifactLocation) {
-      this.sourceJar = artifactLocation;
-      return this;
-    }
-    public LibraryArtifact build() {
-      return new LibraryArtifact(jar, runtimeJar, sourceJar);
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/ideinfo/ProtoLibraryLegacyInfo.java b/blaze-base/src/com/google/idea/blaze/base/ideinfo/ProtoLibraryLegacyInfo.java
deleted file mode 100644
index 18f6b69..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/ideinfo/ProtoLibraryLegacyInfo.java
+++ /dev/null
@@ -1,88 +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.ImmutableList;
-
-import java.io.Serializable;
-import java.util.Collection;
-
-/**
- * Proto library info for legacy proto libraries.
- *
- * Replicates blaze semantics.
- */
-public class ProtoLibraryLegacyInfo implements Serializable {
-  private static final long serialVersionUID = 1L;
-
-  public enum ApiFlavor {
-    VERSION_1,
-    MUTABLE,
-    IMMUTABLE,
-    BOTH,
-    NONE,
-  }
-
-  public final ApiFlavor apiFlavor;
-
-  public final Collection<LibraryArtifact> jarsV1;
-  public final Collection<LibraryArtifact> jarsMutable;
-  public final Collection<LibraryArtifact> jarsImmutable;
-
-  public ProtoLibraryLegacyInfo(ApiFlavor apiFlavor,
-                                Collection<LibraryArtifact> jarsV1,
-                                Collection<LibraryArtifact> jarsMutable,
-                                Collection<LibraryArtifact> jarsImmutable) {
-    this.apiFlavor = apiFlavor;
-    this.jarsV1 = jarsV1;
-    this.jarsMutable = jarsMutable;
-    this.jarsImmutable = jarsImmutable;
-  }
-
-  public static Builder builder(ApiFlavor apiFlavor) {
-    return new Builder(apiFlavor);
-  }
-
-  public static class Builder {
-    private final ApiFlavor apiFlavor;
-    private ImmutableList.Builder<LibraryArtifact> jarsV1 = ImmutableList.builder();
-    private ImmutableList.Builder<LibraryArtifact> jarsMutable = ImmutableList.builder();
-    private ImmutableList.Builder<LibraryArtifact> jarsImmutable = ImmutableList.builder();
-
-    Builder(ApiFlavor apiFlavor) {
-      this.apiFlavor = apiFlavor;
-    }
-
-    public Builder addJarV1(LibraryArtifact.Builder library) {
-      jarsV1.add(library.build());
-      return this;
-    }
-
-    public Builder addJarMutable(LibraryArtifact.Builder library) {
-      jarsMutable.add(library.build());
-      return this;
-    }
-
-    public Builder addJarImmutable(LibraryArtifact.Builder library) {
-      jarsImmutable.add(library.build());
-      return this;
-    }
-
-    public ProtoLibraryLegacyInfo build() {
-      return new ProtoLibraryLegacyInfo(apiFlavor, jarsV1.build(), jarsMutable.build(), jarsImmutable.build());
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/ideinfo/RuleIdeInfo.java b/blaze-base/src/com/google/idea/blaze/base/ideinfo/RuleIdeInfo.java
deleted file mode 100644
index 896e60d..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/ideinfo/RuleIdeInfo.java
+++ /dev/null
@@ -1,221 +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.Lists;
-import com.google.idea.blaze.base.model.primitives.Kind;
-import com.google.idea.blaze.base.model.primitives.Label;
-
-import javax.annotation.Nullable;
-import java.io.Serializable;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * Simple implementation of RuleIdeInfo.
- */
-public final class RuleIdeInfo implements Serializable {
-  private static final long serialVersionUID = 10L;
-
-  public final Label label;
-  public final Kind kind;
-  @Nullable public final ArtifactLocation buildFile;
-  public final Collection<Label> dependencies;
-  public final Collection<Label> runtimeDeps;
-  public final Collection<String> tags;
-  public final Collection<ArtifactLocation> sources;
-  @Nullable public final CRuleIdeInfo cRuleIdeInfo;
-  @Nullable public final CToolchainIdeInfo cToolchainIdeInfo;
-  @Nullable public final JavaRuleIdeInfo javaRuleIdeInfo;
-  @Nullable public final AndroidRuleIdeInfo androidRuleIdeInfo;
-  @Nullable public final TestIdeInfo testIdeInfo;
-  @Nullable public final ProtoLibraryLegacyInfo protoLibraryLegacyInfo;
-  @Nullable public final JavaToolchainIdeInfo javaToolchainIdeInfo;
-
-  public RuleIdeInfo(Label label,
-                     Kind kind,
-                     @Nullable ArtifactLocation buildFile,
-                     Collection<Label> dependencies,
-                     Collection<Label> runtimeDeps,
-                     Collection<String> tags,
-                     Collection<ArtifactLocation> sources,
-                     @Nullable CRuleIdeInfo cRuleIdeInfo,
-                     @Nullable CToolchainIdeInfo cToolchainIdeInfo,
-                     @Nullable JavaRuleIdeInfo javaRuleIdeInfo,
-                     @Nullable AndroidRuleIdeInfo androidRuleIdeInfo,
-                     @Nullable TestIdeInfo testIdeInfo,
-                     @Nullable ProtoLibraryLegacyInfo protoLibraryLegacyInfo,
-                     @Nullable JavaToolchainIdeInfo javaToolchainIdeInfo) {
-    this.label = label;
-    this.kind = kind;
-    this.buildFile = buildFile;
-    this.dependencies = dependencies;
-    this.runtimeDeps = runtimeDeps;
-    this.tags = tags;
-    this.sources = sources;
-    this.cRuleIdeInfo = cRuleIdeInfo;
-    this.cToolchainIdeInfo = cToolchainIdeInfo;
-    this.javaRuleIdeInfo = javaRuleIdeInfo;
-    this.androidRuleIdeInfo = androidRuleIdeInfo;
-    this.testIdeInfo = testIdeInfo;
-    this.protoLibraryLegacyInfo = protoLibraryLegacyInfo;
-    this.javaToolchainIdeInfo = javaToolchainIdeInfo;
-  }
-
-  @Override
-  public String toString() {
-    return label.toString();
-  }
-
-  /**
-   * Returns whether this rule is one of the kinds.
-   */
-  public boolean kindIsOneOf(Kind... kinds) {
-    return kindIsOneOf(Arrays.asList(kinds));
-  }
-
-  /**
-   * Returns whether this rule is one of the kinds.
-   */
-  public boolean kindIsOneOf(List<Kind> kinds) {
-    if (kind != null) {
-      return kind.isOneOf(kinds);
-    }
-    return false;
-  }
-
-  public static Builder builder() {
-    return new Builder();
-  }
-
-  public static class Builder {
-    private Label label;
-    private Kind kind;
-    private ArtifactLocation buildFile;
-    private final List<Label> dependencies = Lists.newArrayList();
-    private final List<Label> runtimeDeps = 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 CToolchainIdeInfo cToolchainIdeInfo;
-    private JavaRuleIdeInfo javaRuleIdeInfo;
-    private AndroidRuleIdeInfo androidRuleIdeInfo;
-    private TestIdeInfo testIdeInfo;
-    private ProtoLibraryLegacyInfo protoLibraryLegacyInfo;
-    private JavaToolchainIdeInfo javaToolchainIdeInfo;
-
-    public Builder setLabel(String label) {
-      return setLabel(new Label(label));
-    }
-    public Builder setLabel(Label label) {
-      this.label = label;
-      return this;
-    }
-    public Builder setBuildFile(ArtifactLocation buildFile) {
-      this.buildFile = buildFile;
-      return this;
-    }
-    public Builder setKind(String kind) {
-      return setKind(Kind.fromString(kind));
-    }
-    public Builder setKind(Kind kind) {
-      this.kind = kind;
-      return this;
-    }
-    public Builder addSource(ArtifactLocation source) {
-      this.sources.add(source);
-      return this;
-    }
-    public Builder addSource(ArtifactLocation.Builder source) {
-      return addSource(source.build());
-    }
-    public Builder setJavaInfo(JavaRuleIdeInfo.Builder builder) {
-      javaRuleIdeInfo = builder.build();
-      return this;
-    }
-    public Builder setCInfo(CRuleIdeInfo cInfo) {
-      this.cRuleIdeInfo = cInfo;
-      return this;
-    }
-    public Builder setCInfo(CRuleIdeInfo.Builder cInfo) {
-      return setCInfo(cInfo.build());
-    }
-    public Builder setCToolchainInfo(CToolchainIdeInfo info) {
-      this.cToolchainIdeInfo = info;
-      return this;
-    }
-    public Builder setCToolchainInfo(CToolchainIdeInfo.Builder info) {
-      return setCToolchainInfo(info.build());
-    }
-    public Builder setAndroidInfo(AndroidRuleIdeInfo androidInfo) {
-      this.androidRuleIdeInfo = androidInfo;
-      return this;
-    }
-    public Builder setAndroidInfo(AndroidRuleIdeInfo.Builder androidInfo) {
-      return setAndroidInfo(androidInfo.build());
-    }
-    public Builder setTestInfo(TestIdeInfo.Builder testInfo) {
-      this.testIdeInfo = testInfo.build();
-      return this;
-    }
-    public Builder setProtoLibraryLegacyInfo(ProtoLibraryLegacyInfo.Builder protoLibraryLegacyInfo) {
-      this.protoLibraryLegacyInfo = protoLibraryLegacyInfo.build();
-      return this;
-    }
-    public Builder setJavaToolchainIdeInfo(JavaToolchainIdeInfo.Builder javaToolchainIdeInfo) {
-      this.javaToolchainIdeInfo = javaToolchainIdeInfo.build();
-      return this;
-    }
-    public Builder addTag(String s) {
-      this.tags.add(s);
-      return this;
-    }
-    public Builder addDependency(String s) {
-      return addDependency(new Label(s));
-    }
-    public Builder addDependency(Label label) {
-      this.dependencies.add(label);
-      return this;
-    }
-    public Builder addRuntimeDep(String s) {
-      return addRuntimeDep(new Label(s));
-    }
-    public Builder addRuntimeDep(Label label) {
-      this.runtimeDeps.add(label);
-      return this;
-    }
-    public RuleIdeInfo build() {
-      return new RuleIdeInfo(
-        label,
-        kind,
-        buildFile,
-        dependencies,
-        runtimeDeps,
-        tags,
-        sources,
-        cRuleIdeInfo,
-        cToolchainIdeInfo,
-        javaRuleIdeInfo,
-        androidRuleIdeInfo,
-        testIdeInfo,
-        protoLibraryLegacyInfo,
-        javaToolchainIdeInfo
-      );
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/ideinfo/Tags.java b/blaze-base/src/com/google/idea/blaze/base/ideinfo/Tags.java
deleted file mode 100644
index 82af46f..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/ideinfo/Tags.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.base.ideinfo;
-
-/**
- * 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 RULE_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";
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/ideinfo/TestIdeInfo.java b/blaze-base/src/com/google/idea/blaze/base/ideinfo/TestIdeInfo.java
deleted file mode 100644
index 534f09c..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/ideinfo/TestIdeInfo.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 javax.annotation.Nullable;
-import java.io.Serializable;
-
-/**
- * Test info.
- */
-public class TestIdeInfo implements Serializable {
-  private static final long serialVersionUID = 1L;
-
-  public enum TestSize {
-    SMALL,
-    MEDIUM,
-    LARGE,
-    ENORMOUS
-  }
-
-  // Rules are "medium" test size by default
-  public static final TestSize DEFAULT_RULE_TEST_SIZE = TestSize.MEDIUM;
-
-  // Non-annotated methods and classes are "small" by default
-  public static final TestSize DEFAULT_NON_ANNOTATED_TEST_SIZE = TestSize.SMALL;
-
-  public final TestSize testSize;
-
-  public TestIdeInfo(TestSize testSize) {
-    this.testSize = testSize;
-  }
-
-  @Nullable
-  static public TestSize getTestSize(RuleIdeInfo rule) {
-    TestIdeInfo testIdeInfo = rule.testIdeInfo;
-    if (testIdeInfo == null) {
-      return null;
-    }
-    return testIdeInfo.testSize;
-  }
-
-  public static Builder builder() {
-    return new Builder();
-  }
-
-  public static class Builder {
-    private TestSize testSize = DEFAULT_RULE_TEST_SIZE;
-
-    public Builder setTestSize(TestSize testSize) {
-      this.testSize = testSize;
-      return this;
-    }
-
-    public TestIdeInfo build() {
-      return new TestIdeInfo(testSize);
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/io/FileAttributeProvider.java b/blaze-base/src/com/google/idea/blaze/base/io/FileAttributeProvider.java
deleted file mode 100644
index 829d262..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/io/FileAttributeProvider.java
+++ /dev/null
@@ -1,47 +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.intellij.openapi.components.ServiceManager;
-
-import java.io.File;
-
-/**
- * Simple file system checks (existence, isDirectory)
- */
-public class FileAttributeProvider {
-
-  public static FileAttributeProvider getInstance() {
-    return ServiceManager.getService(FileAttributeProvider.class);
-  }
-
-  public boolean exists(File file) {
-    return file.exists();
-  }
-
-  public boolean isDirectory(File file) {
-    return file.isDirectory();
-  }
-
-  public boolean isFile(File file) {
-    return file.isFile();
-  }
-
-  public long getFileModifiedTime(File file) {
-    return file.lastModified();
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/io/InputStreamProvider.java b/blaze-base/src/com/google/idea/blaze/base/io/InputStreamProvider.java
deleted file mode 100644
index 542992f..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/io/InputStreamProvider.java
+++ /dev/null
@@ -1,34 +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.intellij.openapi.components.ServiceManager;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * Provides input streams for files.
- */
-public interface InputStreamProvider {
-
-  static InputStreamProvider getInstance() {
-    return ServiceManager.getService(InputStreamProvider.class);
-  }
-
-  InputStream getFile(File file) throws IOException;
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/io/InputStreamProviderImpl.java b/blaze-base/src/com/google/idea/blaze/base/io/InputStreamProviderImpl.java
deleted file mode 100644
index a4f0d64..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/io/InputStreamProviderImpl.java
+++ /dev/null
@@ -1,34 +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 org.jetbrains.annotations.NotNull;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.InputStream;
-
-/**
- * Default implementation of InputStreamProvider.
- */
-final class InputStreamProviderImpl implements InputStreamProvider {
-
-  @Override
-  public InputStream getFile(@NotNull File file) throws FileNotFoundException {
-    return new FileInputStream(file);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/io/VfsWorkspaceScanner.java b/blaze-base/src/com/google/idea/blaze/base/io/VfsWorkspaceScanner.java
deleted file mode 100644
index 5ca8daf..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/io/VfsWorkspaceScanner.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.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/blaze-base/src/com/google/idea/blaze/base/io/WorkspaceScanner.java b/blaze-base/src/com/google/idea/blaze/base/io/WorkspaceScanner.java
deleted file mode 100644
index bf4b4e0..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/io/WorkspaceScanner.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.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/blaze-base/src/com/google/idea/blaze/base/issueparser/BlazeIssueParser.java b/blaze-base/src/com/google/idea/blaze/base/issueparser/BlazeIssueParser.java
deleted file mode 100644
index 0890bf5..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/issueparser/BlazeIssueParser.java
+++ /dev/null
@@ -1,331 +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.issueparser;
-
-import com.google.common.base.Predicate;
-import com.google.common.collect.Lists;
-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.ProjectViewManager;
-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.Section;
-import com.google.idea.blaze.base.projectview.section.SectionKey;
-import com.google.idea.blaze.base.projectview.section.sections.TargetSection;
-import com.google.idea.blaze.base.scope.output.IssueOutput;
-import com.intellij.openapi.project.Project;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import static com.google.common.base.Preconditions.checkState;
-
-/**
- * Parses blaze output for compile errors.
- */
-public class BlazeIssueParser {
-
-  private static class ParseResult {
-
-    public static final ParseResult NEEDS_MORE_INPUT = new ParseResult(true, null);
-
-    public static final ParseResult NO_RESULT = new ParseResult(false, null);
-
-    private boolean needsMoreInput;
-    @Nullable private IssueOutput output;
-
-    private ParseResult(boolean needsMoreInput, IssueOutput output) {
-      this.needsMoreInput = needsMoreInput;
-      this.output = output;
-    }
-
-    public static ParseResult needsMoreInput() {
-      return NEEDS_MORE_INPUT;
-    }
-
-    public static ParseResult output(IssueOutput output) {
-      return new ParseResult(false, output);
-    }
-
-    public static ParseResult noResult() {
-      return NO_RESULT;
-    }
-  }
-
-  interface Parser {
-    @NotNull
-    ParseResult parse(@NotNull String currentLine, @NotNull List<String> previousLines);
-  }
-
-  static abstract class SingleLineParser implements Parser {
-    @NotNull
-    Pattern pattern;
-
-    SingleLineParser(@NotNull String regex) {
-      pattern = Pattern.compile(regex);
-    }
-
-    @Override
-    public ParseResult parse(@NotNull String currentLine, @NotNull List<String> multilineMatchResult) {
-      checkState(multilineMatchResult.isEmpty(), "SingleLineParser recieved multiple lines of input");
-      return parse(currentLine);
-    }
-
-    ParseResult parse(@NotNull String line) {
-      Matcher matcher = pattern.matcher(line);
-      if (matcher.find()) {
-        return ParseResult.output(createIssue(matcher));
-      }
-      return ParseResult.noResult();
-    }
-
-    @Nullable
-    protected abstract IssueOutput createIssue(@NotNull Matcher matcher);
-  }
-
-  static class CompileParser extends SingleLineParser {
-    @NotNull
-    private final WorkspaceRoot workspaceRoot;
-
-    public CompileParser(@NotNull WorkspaceRoot workspaceRoot) {
-      super("(.*?):([0-9]+):([0-9]+:)? (error|warning): (.*)");
-      this.workspaceRoot = workspaceRoot;
-    }
-
-    @Override
-    protected IssueOutput createIssue(@NotNull Matcher matcher) {
-      final File file;
-      try {
-        String fileName = matcher.group(1);
-        final WorkspacePath workspacePath;
-        if (fileName.startsWith("//depot/google3/")) {
-          workspacePath = new WorkspacePath(fileName.substring("//depot/google3/".length()));
-        } else if (fileName.startsWith("/")) {
-          workspacePath = workspaceRoot.workspacePathFor(new File(fileName));
-        } else {
-          workspacePath = new WorkspacePath(fileName);
-        }
-        file = workspaceRoot.fileForPath(workspacePath);
-      }
-      catch (IllegalArgumentException e) {
-        // Ignore -- malformed error message
-        return null;
-      }
-
-      IssueOutput.Category type = matcher.group(4).equals("error")
-                                  ? IssueOutput.Category.ERROR
-                                  : IssueOutput.Category.WARNING;
-      return IssueOutput.issue(type, matcher.group(5))
-        .inFile(file)
-        .onLine(Integer.parseInt(matcher.group(2)))
-        .build();
-    }
-  }
-
-  static class TracebackParser implements Parser {
-    private static final Pattern PATTERN = Pattern.compile("(ERROR): (.*?):([0-9]+):([0-9]+): (Traceback \\(most recent call last\\):)");
-
-    @NotNull
-    @Override
-    public ParseResult parse(@NotNull String currentLine, @NotNull List<String> previousLines) {
-      if (previousLines.isEmpty()) {
-        if (PATTERN.matcher(currentLine).find()) {
-          return ParseResult.needsMoreInput();
-        }
-        else {
-          return ParseResult.noResult();
-        }
-      }
-
-      if (currentLine.startsWith("\t")) {
-        return ParseResult.needsMoreInput();
-      }
-      else {
-        Matcher matcher = PATTERN.matcher(previousLines.get(0));
-        checkState(matcher.find(), "Found a match in the first line previously, but now it isn't there.");
-        StringBuilder message = new StringBuilder(matcher.group(5));
-        for (int i = 1; i < previousLines.size(); ++i) {
-          message.append(System.lineSeparator())
-            .append(previousLines.get(i));
-        }
-        message.append(System.lineSeparator())
-          .append(currentLine);
-        return ParseResult.output(IssueOutput.error(message.toString())
-                                    .inFile(new File(matcher.group(2)))
-                                    .onLine(Integer.parseInt(matcher.group(3)))
-                                    .build());
-      }
-    }
-  }
-
-  static class BuildParser extends SingleLineParser {
-    BuildParser() {
-      super("(ERROR): (.*?):([0-9]+):([0-9]+): (.*)");
-    }
-
-    @Override
-    protected IssueOutput createIssue(@NotNull Matcher matcher) {
-      return IssueOutput.error(matcher.group(5))
-        .inFile(new File(matcher.group(2)))
-        .onLine(Integer.parseInt(matcher.group(3)))
-        .build();
-    }
-  }
-
-  static class LinelessBuildParser extends SingleLineParser {
-    LinelessBuildParser() {
-      super("(ERROR): (.*?):char offsets [0-9]+--[0-9]+: (.*)");
-    }
-
-    @Override
-    protected IssueOutput createIssue(@NotNull Matcher matcher) {
-      return IssueOutput.error(matcher.group(3))
-        .inFile(new File(matcher.group(2)))
-        .build();
-    }
-  }
-
-  static class ProjectViewLabelParser extends SingleLineParser {
-
-    @Nullable private final ProjectViewSet projectViewSet;
-
-    ProjectViewLabelParser(
-      @Nullable ProjectViewSet projectViewSet) {
-      super("no such target '(.*)': target .*? not declared in package .*? defined by");
-      this.projectViewSet = projectViewSet;
-    }
-
-    @Override
-    protected IssueOutput createIssue(@NotNull Matcher matcher) {
-      File file = null;
-      if (projectViewSet != null) {
-        String targetString = matcher.group(1);
-        final TargetExpression targetExpression = TargetExpression.fromString(targetString);
-        file = projectViewFileWithSection(projectViewSet, TargetSection.KEY, new Predicate<ListSection<TargetExpression>>() {
-          @Override
-          public boolean apply(@NotNull ListSection<TargetExpression> targetSection) {
-            return targetSection.items().contains(targetExpression);
-          }
-        });
-      }
-
-      return IssueOutput.error(matcher.group(0))
-        .inFile(file)
-        .build();
-    }
-  }
-
-  static class InvalidTargetProjectViewPackageParser extends SingleLineParser {
-    @Nullable private final ProjectViewSet projectViewSet;
-
-    InvalidTargetProjectViewPackageParser(
-      @Nullable ProjectViewSet projectViewSet,
-      String regex) {
-      super(regex);
-      this.projectViewSet = projectViewSet;
-    }
-
-    @Override
-    protected IssueOutput createIssue(@NotNull Matcher matcher) {
-      File file = null;
-      if (projectViewSet != null) {
-        final String packageString = matcher.group(1);
-        file = projectViewFileWithSection(projectViewSet, TargetSection.KEY, targetSection -> {
-          for (TargetExpression targetExpression : targetSection.items()) {
-            if (targetExpression.toString().startsWith("//" + packageString + ":")) {
-              return true;
-            }
-          }
-          return false;
-        });
-      }
-
-      return IssueOutput.error(matcher.group(0))
-        .inFile(file)
-        .build();
-    }
-  }
-
-  @Nullable
-  private static <T, SectionType extends Section<T>> File projectViewFileWithSection(
-    @NotNull ProjectViewSet projectViewSet,
-    @NotNull SectionKey<T, SectionType> key,
-    @NotNull Predicate<SectionType> predicate) {
-    for (ProjectViewSet.ProjectViewFile projectViewFile : projectViewSet.getProjectViewFiles()) {
-      SectionType section = projectViewFile.projectView.getSectionOfType(key);
-      if (section != null && predicate.apply(section)) {
-        return projectViewFile.projectViewFile;
-      }
-    }
-    return null;
-  }
-
-  @NotNull private List<Parser> parsers = Lists.newArrayList();
-  /** The parser that requested more lines of input during the last call to {@link #parseIssue(String)}. */
-  @Nullable private Parser multilineMatchingParser;
-  @NotNull private List<String> multilineMatchResult = new ArrayList<>();
-
-  public BlazeIssueParser(
-    @Nullable Project project,
-    @NotNull WorkspaceRoot workspaceRoot) {
-
-    ProjectViewSet projectViewSet = project != null ? ProjectViewManager.getInstance(project).getProjectViewSet() : null;
-
-    parsers.add(new CompileParser(workspaceRoot));
-    parsers.add(new TracebackParser());
-    parsers.add(new BuildParser());
-    parsers.add(new LinelessBuildParser());
-    parsers.add(new ProjectViewLabelParser(projectViewSet));
-    parsers.add(new InvalidTargetProjectViewPackageParser(projectViewSet, "no such package '(.*)': BUILD file not found on package path"));
-    parsers.add(new InvalidTargetProjectViewPackageParser(projectViewSet, "no targets found beneath '(.*)'"));
-    parsers.add(new InvalidTargetProjectViewPackageParser(projectViewSet, "ERROR: invalid target format '(.*)'"));
-  }
-
-
-  @Nullable
-  public IssueOutput parseIssue(String line) {
-
-    List<Parser> parsers = this.parsers;
-    if (multilineMatchingParser != null) {
-      parsers = Lists.newArrayList(multilineMatchingParser);
-    }
-
-    for (Parser parser : parsers) {
-      ParseResult issue = parser.parse(line, multilineMatchResult);
-      if (issue.needsMoreInput) {
-        multilineMatchingParser = parser;
-        multilineMatchResult.add(line);
-        return null;
-      }
-      else {
-        multilineMatchingParser = null;
-        multilineMatchResult = new ArrayList<>();
-      }
-
-      if (issue.output != null) {
-        return issue.output;
-      }
-    }
-
-    return null;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/issueparser/IssueOutputLineProcessor.java b/blaze-base/src/com/google/idea/blaze/base/issueparser/IssueOutputLineProcessor.java
deleted file mode 100644
index 3b34a42..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/issueparser/IssueOutputLineProcessor.java
+++ /dev/null
@@ -1,67 +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.issueparser;
-
-import com.google.idea.blaze.base.async.process.LineProcessingOutputStream;
-import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
-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.PrintOutput;
-import com.google.idea.blaze.base.scope.output.PrintOutput.OutputType;
-import com.intellij.openapi.project.Project;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * Forwards output to PrintOutputs, colored by whether or not
- * an issue is found per-line.
- * <p/>
- * Also creates IssueOutput if issues are found.
- */
-public class IssueOutputLineProcessor
-  implements LineProcessingOutputStream.LineProcessor {
-
-  @NotNull
-  private final BlazeContext context;
-
-  @NotNull
-  private final BlazeIssueParser blazeIssueParser;
-
-  public IssueOutputLineProcessor(
-    @Nullable Project project,
-    @NotNull BlazeContext context,
-    @NotNull WorkspaceRoot workspaceRoot) {
-    this.context = context;
-    this.blazeIssueParser = new BlazeIssueParser(project, workspaceRoot);
-  }
-
-  @Override
-  public boolean processLine(@NotNull String line) {
-    IssueOutput issue = blazeIssueParser.parseIssue(line);
-    if (issue != null) {
-      if (issue.getCategory() == IssueOutput.Category.ERROR) {
-        context.setHasError();
-      }
-      context.output(issue);
-    }
-
-    OutputType outputType = issue == null
-                            ? OutputType.NORMAL : OutputType.ERROR;
-
-    context.output(new PrintOutput(line, outputType));
-    return true;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/actions/BuildFileModifierImpl.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/actions/BuildFileModifierImpl.java
deleted file mode 100644
index e455a6e..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/actions/BuildFileModifierImpl.java
+++ /dev/null
@@ -1,78 +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.actions;
-
-import com.google.common.base.Joiner;
-import com.google.common.collect.ImmutableList;
-import com.google.idea.blaze.base.buildmodifier.BuildFileModifier;
-import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
-import com.google.idea.blaze.base.lang.buildfile.psi.Expression;
-import com.google.idea.blaze.base.lang.buildfile.psi.FuncallExpression;
-import com.google.idea.blaze.base.lang.buildfile.psi.util.BuildElementGenerator;
-import com.google.idea.blaze.base.lang.buildfile.references.BuildReferenceManager;
-import com.google.idea.blaze.base.model.primitives.Kind;
-import com.google.idea.blaze.base.model.primitives.Label;
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.intellij.openapi.command.WriteCommandAction;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.Computable;
-import com.intellij.openapi.vfs.LocalFileSystem;
-import com.intellij.psi.PsiElement;
-
-import java.io.File;
-
-/**
- * Implementation of BuildFileModifier. Modifies the PSI tree directly.
- */
-public class BuildFileModifierImpl implements BuildFileModifier {
-
-  private static final Logger LOG = Logger.getInstance(BuildFileModifierImpl.class);
-
-  @Override
-  public boolean addRule(Project project,
-                         BlazeContext context,
-                         Label newRule,
-                         Kind ruleKind) {
-    return WriteCommandAction.runWriteCommandAction(project, (Computable<Boolean>) () -> {
-      BuildReferenceManager manager = BuildReferenceManager.getInstance(project);
-      File file = manager.resolvePackage(newRule.blazePackage());
-      if (file == null) {
-        return null;
-      }
-      LocalFileSystem.getInstance().refreshIoFiles(ImmutableList.of(file));
-      BuildFile buildFile = manager.resolveBlazePackage(newRule.blazePackage());
-      if (buildFile == null) {
-        LOG.error("No BUILD file found at location: " + newRule.blazePackage());
-        return false;
-      }
-      buildFile.add(createRule(project, ruleKind, newRule.ruleName().toString()));
-      return true;
-    });
-  }
-
-  private PsiElement createRule(Project project, Kind ruleKind, String ruleName) {
-    String text = Joiner.on("\n").join(
-      ruleKind.toString() + "(",
-      "    name = \"" + ruleName + "\"",
-      ")"
-    );
-    Expression expr = BuildElementGenerator.getInstance(project).createExpressionFromText(text);
-    assert(expr instanceof FuncallExpression);
-    return expr;
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/completion/ArgumentCompletionContributor.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/completion/ArgumentCompletionContributor.java
deleted file mode 100644
index f0eff50..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/completion/ArgumentCompletionContributor.java
+++ /dev/null
@@ -1,73 +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.completion;
-
-import com.google.idea.blaze.base.lang.buildfile.language.BuildFileLanguage;
-import com.google.idea.blaze.base.lang.buildfile.lexer.BuildToken;
-import com.google.idea.blaze.base.lang.buildfile.lexer.TokenKind;
-import com.google.idea.blaze.base.lang.buildfile.psi.Argument;
-import com.google.idea.blaze.base.lang.buildfile.psi.ReferenceExpression;
-import com.intellij.codeInsight.completion.*;
-import com.intellij.codeInsight.lookup.LookupElement;
-import com.intellij.psi.util.PsiTreeUtil;
-import com.intellij.util.ProcessingContext;
-
-import static com.intellij.patterns.PlatformPatterns.psiElement;
-
-/**
- * We can't rely solely keyword arg references, because as the user is typing a new keyword arg,
- * the PsiElement will be a ReferenceExpression (with different completion results
- * not relevant to keyword args).
- */
-public class ArgumentCompletionContributor extends CompletionContributor {
-
-  @Override
-  public AutoCompletionDecision handleAutoCompletionPossibility(AutoCompletionContext context) {
-    // auto-insert the obvious only case; else show other cases.
-    final LookupElement[] items = context.getItems();
-    if (items.length == 1) {
-      return AutoCompletionDecision.insertItem(items[0]);
-    }
-    return AutoCompletionDecision.SHOW_LOOKUP;
-  }
-
-  public ArgumentCompletionContributor() {
-    extend(
-      CompletionType.BASIC,
-      psiElement()
-        .withLanguage(BuildFileLanguage.INSTANCE)
-        .withElementType(BuildToken.fromKind(TokenKind.IDENTIFIER))
-        .withParents(ReferenceExpression.class, Argument.Positional.class)
-        .andNot(psiElement().afterLeaf("="))
-        .andNot(psiElement().afterLeaf(psiElement(BuildToken.fromKind(TokenKind.IDENTIFIER)))),
-      new CompletionProvider<CompletionParameters>() {
-        @Override
-        protected void addCompletions(CompletionParameters parameters, ProcessingContext context, CompletionResultSet result) {
-          Argument.Positional arg = PsiTreeUtil.getParentOfType(parameters.getPosition(), Argument.Positional.class);
-          if (arg != null) {
-            Object[] lookups = arg.getReference().getVariants();
-            for (Object lookup : lookups) {
-              if (lookup instanceof LookupElement) {
-                result.addElement((LookupElement) lookup);
-              }
-            }
-          }
-        }
-      }
-    );
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/completion/BuildLookupElement.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/completion/BuildLookupElement.java
deleted file mode 100644
index 812c2b5..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/completion/BuildLookupElement.java
+++ /dev/null
@@ -1,108 +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.completion;
-
-import com.google.idea.blaze.base.lang.buildfile.references.QuoteType;
-import com.intellij.codeInsight.completion.InsertionContext;
-import com.intellij.codeInsight.lookup.LookupElement;
-import com.intellij.codeInsight.lookup.LookupElementPresentation;
-import com.intellij.openapi.editor.Document;
-import com.intellij.psi.PsiElement;
-
-import javax.annotation.Nullable;
-import javax.swing.*;
-
-/**
- * Handles some boilerplate, and allows lazy calculation of some expensive
- * components, which aren't required if the element is filtered out by IJ.
- */
-public abstract class BuildLookupElement extends LookupElement {
-
-  public static final BuildLookupElement[] EMPTY_ARRAY = new BuildLookupElement[0];
-
-  protected final String baseName;
-  protected final QuoteType quoteWrapping;
-  protected final boolean wrapWithQuotes;
-
-  public BuildLookupElement(String baseName, QuoteType quoteWrapping) {
-    this.baseName = baseName;
-    this.quoteWrapping = quoteWrapping;
-    this.wrapWithQuotes = quoteWrapping != QuoteType.NoQuotes;
-  }
-
-  @Override
-  public String getLookupString() {
-    return quoteWrapping.wrap(baseName);
-  }
-
-  @Nullable
-  public abstract Icon getIcon();
-
-  protected String getItemText() {
-    return baseName;
-  }
-
-  @Nullable
-  protected String getTypeText() {
-    return null;
-  }
-
-  @Nullable
-  protected String getTailText() {
-    return null;
-  }
-
-  @Override
-  public void renderElement(LookupElementPresentation presentation) {
-    presentation.setItemText(getItemText());
-    presentation.setTailText(getTailText());
-    presentation.setTypeText(getTypeText());
-    presentation.setIcon(getIcon());
-  }
-
-  /**
-   * If we're wrapping with quotes, handle the (very common) case where we have
-   * a closing quote after the caret -- we want to remove this quote.
-   * @param context
-   */
-  @Override
-  public void handleInsert(InsertionContext context) {
-    if (!wrapWithQuotes) {
-      super.handleInsert(context);
-      return;
-    }
-    Document document = context.getDocument();
-    context.commitDocument();
-    PsiElement suffix = context.getFile().findElementAt(context.getTailOffset());
-    if (suffix.getText().startsWith(quoteWrapping.quoteString)) {
-      int offset = suffix.getTextOffset();
-      document.deleteString(offset, offset + 1);
-      context.commitDocument();
-    }
-    if (caretInsideQuotes()) {
-      context.getEditor().getCaretModel().moveCaretRelatively(-1, 0, false, false, true);
-    }
-  }
-
-  /**
-   * If true, and we're wrapping with quotes, the caret is moved inside
-   * the closing quote after the insert operation is performed.
-   */
-  protected boolean caretInsideQuotes() {
-    return false;
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/completion/BuiltInFunctionAttributeCompletionContributor.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/completion/BuiltInFunctionAttributeCompletionContributor.java
deleted file mode 100644
index 9970994..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/completion/BuiltInFunctionAttributeCompletionContributor.java
+++ /dev/null
@@ -1,94 +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.completion;
-
-import com.google.idea.blaze.base.lang.buildfile.language.BuildFileLanguage;
-import com.google.idea.blaze.base.lang.buildfile.language.semantics.BuildLanguageSpec;
-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.lexer.BuildToken;
-import com.google.idea.blaze.base.lang.buildfile.lexer.TokenKind;
-import com.google.idea.blaze.base.lang.buildfile.psi.Argument;
-import com.google.idea.blaze.base.lang.buildfile.psi.FuncallExpression;
-import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
-import com.intellij.codeInsight.completion.*;
-import com.intellij.codeInsight.lookup.LookupElement;
-import com.intellij.codeInsight.lookup.LookupElementBuilder;
-import com.intellij.icons.AllIcons;
-import com.intellij.psi.PsiElement;
-import com.intellij.util.ProcessingContext;
-
-import javax.annotation.Nullable;
-
-import static com.intellij.patterns.PlatformPatterns.psiElement;
-
-/**
- * Known attributes for built-in blaze functions.
- */
-public class BuiltInFunctionAttributeCompletionContributor extends CompletionContributor {
-
-  @Override
-  public AutoCompletionDecision handleAutoCompletionPossibility(AutoCompletionContext context) {
-    // auto-insert the obvious only case; else show other cases.
-    final LookupElement[] items = context.getItems();
-    if (items.length == 1) {
-      return AutoCompletionDecision.insertItem(items[0]);
-    }
-    return AutoCompletionDecision.SHOW_LOOKUP;
-  }
-
-  public BuiltInFunctionAttributeCompletionContributor() {
-    extend(
-      CompletionType.BASIC,
-      psiElement()
-        .withLanguage(BuildFileLanguage.INSTANCE)
-        .inside(psiElement(FuncallExpression.class))
-        .andNot(psiElement().afterLeaf("."))
-        .andOr(
-          psiElement().withSuperParent(2, FuncallExpression.class),
-          psiElement().withSuperParent(2, Argument.class)
-            .andNot(psiElement().afterLeaf("="))
-            .andNot(psiElement().afterLeaf(psiElement(BuildToken.fromKind(TokenKind.IDENTIFIER))))
-        ),
-      new CompletionProvider<CompletionParameters>() {
-        @Override
-        protected void addCompletions(CompletionParameters parameters, ProcessingContext context, CompletionResultSet result) {
-          BuildLanguageSpec spec = BuildLanguageSpecProvider.getInstance().getLanguageSpec(parameters.getPosition().getProject());
-          if (spec == null) {
-            return;
-          }
-          RuleDefinition rule = spec.getRule(getEnclosingFuncallName(parameters.getPosition()));
-          if (rule == null) {
-            return;
-          }
-          for (String attributeName : rule.getKnownAttributeNames()) {
-            result.addElement(
-              LookupElementBuilder
-                .create(attributeName)
-                .withIcon(AllIcons.Nodes.Parameter));
-          }
-        }
-      }
-    );
-  }
-
-  @Nullable
-  private static String getEnclosingFuncallName(PsiElement element) {
-    FuncallExpression funcall = PsiUtils.getParentOfType(element, FuncallExpression.class);
-    return funcall != null ? funcall.getFunctionName() : null;
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/completion/BuiltInFunctionCompletionContributor.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/completion/BuiltInFunctionCompletionContributor.java
deleted file mode 100644
index f3bff83..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/completion/BuiltInFunctionCompletionContributor.java
+++ /dev/null
@@ -1,82 +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.completion;
-
-import com.google.idea.blaze.base.lang.buildfile.language.BuildFileLanguage;
-import com.google.idea.blaze.base.lang.buildfile.language.semantics.BuildLanguageSpec;
-import com.google.idea.blaze.base.lang.buildfile.language.semantics.BuildLanguageSpecProvider;
-import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
-import com.google.idea.blaze.base.lang.buildfile.psi.FunctionStatement;
-import com.google.idea.blaze.base.lang.buildfile.psi.ReferenceExpression;
-import com.google.idea.blaze.base.lang.buildfile.psi.StatementList;
-import com.intellij.codeInsight.completion.*;
-import com.intellij.codeInsight.completion.util.ParenthesesInsertHandler;
-import com.intellij.codeInsight.lookup.LookupElement;
-import com.intellij.codeInsight.lookup.LookupElementBuilder;
-import com.intellij.util.ProcessingContext;
-import icons.BlazeIcons;
-
-import static com.intellij.patterns.PlatformPatterns.psiElement;
-
-/**
- * Completes built-in blaze function names.
- */
-public class BuiltInFunctionCompletionContributor extends CompletionContributor {
-
-  @Override
-  public AutoCompletionDecision handleAutoCompletionPossibility(AutoCompletionContext context) {
-    // auto-insert the obvious only case; else show other cases.
-    final LookupElement[] items = context.getItems();
-    if (items.length == 1) {
-      return AutoCompletionDecision.insertItem(items[0]);
-    }
-    return AutoCompletionDecision.SHOW_LOOKUP;
-  }
-
-  public BuiltInFunctionCompletionContributor() {
-    extend(
-      CompletionType.BASIC,
-      psiElement()
-        .withLanguage(BuildFileLanguage.INSTANCE)
-        .andOr(
-          // Handles only top-level rules, and rules inside a function statement.
-          // There are several other possibilities (e.g. inside top-level list comprehension), but leaving out less common cases
-          // to avoid cluttering the autocomplete suggestions when it's not valid to enter a rule.
-          psiElement().withParents(ReferenceExpression.class, BuildFile.class), // leaf node => BuildReference => BuildFile
-          psiElement()
-            .inside(psiElement(StatementList.class).inside(psiElement(FunctionStatement.class)))
-            .afterLeaf(psiElement().withText(".").afterLeaf(psiElement().withText("native")))
-        ),
-      new CompletionProvider<CompletionParameters>() {
-        @Override
-        protected void addCompletions(CompletionParameters parameters, ProcessingContext context, CompletionResultSet result) {
-          BuildLanguageSpec spec = BuildLanguageSpecProvider.getInstance().getLanguageSpec(parameters.getPosition().getProject());
-          if (spec == null) {
-            return;
-          }
-          for (String ruleName : spec.getKnownRuleNames()) {
-            result.addElement(
-              LookupElementBuilder
-                .create(ruleName)
-                .withIcon(BlazeIcons.BuildRule)
-                .withInsertHandler(ParenthesesInsertHandler.getInstance(true)));
-          }
-        }
-      }
-    );
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/completion/CompletionResultsProcessor.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/completion/CompletionResultsProcessor.java
deleted file mode 100644
index 65b0e21..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/completion/CompletionResultsProcessor.java
+++ /dev/null
@@ -1,64 +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.completion;
-
-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.references.QuoteType;
-import com.intellij.codeInsight.lookup.LookupElement;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiNamedElement;
-import com.intellij.util.Processor;
-
-import java.util.Collection;
-import java.util.Map;
-
-/**
- * Collects completion results, removing duplicate entries.
- */
-public class CompletionResultsProcessor implements Processor<BuildElement> {
-
-  private final Map<String, LookupElement> results = Maps.newHashMap();
-  private final PsiElement originalElement;
-  private final QuoteType quoteType;
-
-  public CompletionResultsProcessor(PsiElement originalElement, QuoteType quoteType) {
-    this.originalElement = originalElement;
-    this.quoteType = quoteType;
-  }
-
-  @Override
-  public boolean process(BuildElement buildElement) {
-    if (buildElement == originalElement) {
-      return true;
-    }
-    if (buildElement instanceof StringLiteral) {
-      StringLiteral literal = (StringLiteral)buildElement;
-      results.put(literal.getStringContents(), new StringLiteralReferenceLookupElement((StringLiteral)buildElement, quoteType));
-    }
-    else if (buildElement instanceof PsiNamedElement) {
-      PsiNamedElement namedElement = (PsiNamedElement)buildElement;
-      results.put(namedElement.getName(), new NamedBuildLookupElement((PsiNamedElement)buildElement, quoteType));
-    }
-    return true;
-  }
-
-  public Collection<LookupElement> getResults() {
-    return results.values();
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/completion/FilePathLookupElement.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/completion/FilePathLookupElement.java
deleted file mode 100644
index 28292e5..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/completion/FilePathLookupElement.java
+++ /dev/null
@@ -1,55 +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.completion;
-
-import com.google.idea.blaze.base.lang.buildfile.references.QuoteType;
-import com.intellij.openapi.util.NullableLazyValue;
-
-import javax.annotation.Nullable;
-import javax.swing.*;
-
-/**
- * Code completion support for package paths.
- */
-public class FilePathLookupElement extends BuildLookupElement {
-
-  private final String itemText;
-  private final NullableLazyValue<Icon> icon;
-
-  public FilePathLookupElement(String fullLabel, String itemText, QuoteType quoteWrapping, NullableLazyValue<Icon> icon) {
-    super(fullLabel, quoteWrapping);
-    this.itemText = itemText;
-    this.icon = icon;
-  }
-
-  @Override
-  protected String getItemText() {
-    return itemText;
-  }
-
-  @Nullable
-  @Override
-  public Icon getIcon() {
-    return icon.getValue();
-  }
-
-  @Override
-  protected boolean caretInsideQuotes() {
-    // after completing, leave the caret inside the closing quote, so the user can
-    // continue typing the path.
-    return true;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/completion/FilterPatterns.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/completion/FilterPatterns.java
deleted file mode 100644
index b0d9f20..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/completion/FilterPatterns.java
+++ /dev/null
@@ -1,47 +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.completion;
-
-import com.intellij.patterns.ElementPattern;
-import com.intellij.patterns.PatternCondition;
-import com.intellij.psi.PsiElement;
-import com.intellij.util.ProcessingContext;
-import org.jetbrains.annotations.NotNull;
-
-import static com.intellij.patterns.PlatformPatterns.psiElement;
-
-/**
- * Filter patterns used by completion contributors.
- */
-public class FilterPatterns {
-
-  public static final ElementPattern<PsiElement> indexInParentsChildren(final int childIndex) {
-    return psiElement().with(new PatternCondition<PsiElement>("isIndexInParentsChildren") {
-      @Override
-      public boolean accepts(@NotNull PsiElement psiElement, ProcessingContext context) {
-        final PsiElement parent = psiElement.getParent();
-        if (parent != null) {
-          final PsiElement[] children = parent.getChildren();
-          if (childIndex < children.length  && psiElement.equals(children[childIndex])) {
-            return true;
-          }
-        }
-        return false;
-      }
-    });
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/completion/LabelRuleLookupElement.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/completion/LabelRuleLookupElement.java
deleted file mode 100644
index b7f0314..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/completion/LabelRuleLookupElement.java
+++ /dev/null
@@ -1,93 +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.completion;
-
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.lang.buildfile.language.semantics.BuildLanguageSpec;
-import com.google.idea.blaze.base.lang.buildfile.language.semantics.BuildLanguageSpecProvider;
-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.references.QuoteType;
-import com.google.idea.blaze.base.lang.buildfile.references.LabelUtils;
-
-import javax.annotation.Nullable;
-import javax.swing.*;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * Given a label fragment containing a (possibly implicit) package path,
- * provides a lookup element to a rule target in that package.
- */
-public class LabelRuleLookupElement extends BuildLookupElement {
-
-  public static BuildLookupElement[] collectAllRules(
-    BuildFile file,
-    String originalString,
-    String packagePrefix,
-    @Nullable String excluded,
-    QuoteType quoteType) {
-    if (packagePrefix.startsWith("//") || originalString.startsWith(":")) {
-      packagePrefix += ":";
-    }
-
-    String ruleFragment = LabelUtils.getRuleComponent(originalString);
-    List<BuildLookupElement> lookups = Lists.newArrayList();
-    // TODO: Handle rules generated via functions? (e.g. via blaze sync)
-    BuildLanguageSpec spec = BuildLanguageSpecProvider.getInstance().getLanguageSpec(file.getProject());
-    for (FuncallExpression target : file.findChildrenByClass(FuncallExpression.class)) {
-      String targetName = target.getName();
-      if (targetName == null || Objects.equals(target.getName(), excluded) || !targetName.startsWith(ruleFragment)) {
-        continue;
-      }
-      String ruleType = target.getFunctionName();
-      if (ruleType == null || (spec != null && !spec.hasRule(ruleType))) {
-        continue;
-      }
-      lookups.add(new LabelRuleLookupElement(packagePrefix, target, targetName, ruleType, quoteType));
-    }
-    return lookups.isEmpty() ? BuildLookupElement.EMPTY_ARRAY : lookups.toArray(new BuildLookupElement[lookups.size()]);
-  }
-
-  private final FuncallExpression target;
-  private final String targetName;
-  private final String ruleType;
-
-  private LabelRuleLookupElement(String packagePrefix, FuncallExpression target, String targetName, String ruleType, QuoteType quoteType) {
-    super(packagePrefix + targetName, quoteType);
-    this.target = target;
-    this.targetName = targetName;
-    this.ruleType = ruleType;
-
-    assert(packagePrefix.isEmpty() || packagePrefix.endsWith(":"));
-  }
-
-  @Override
-  public Icon getIcon() {
-    return target.getIcon(0);
-  }
-
-  @Override
-  protected String getTypeText() {
-    return ruleType;
-  }
-
-  @Override
-  protected String getItemText() {
-    return targetName;
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/completion/NamedBuildLookupElement.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/completion/NamedBuildLookupElement.java
deleted file mode 100644
index 287e823..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/completion/NamedBuildLookupElement.java
+++ /dev/null
@@ -1,41 +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.completion;
-
-import com.google.idea.blaze.base.lang.buildfile.references.QuoteType;
-import com.intellij.psi.PsiNamedElement;
-
-import javax.annotation.Nullable;
-import javax.swing.*;
-
-/**
- * Generic implementation for {@link com.intellij.psi.PsiNamedElement}s
- */
-public class NamedBuildLookupElement extends BuildLookupElement {
-
-  private final PsiNamedElement element;
-
-  public NamedBuildLookupElement(PsiNamedElement element, QuoteType quoteType) {
-    super(element.getName(), quoteType);
-    this.element = element;
-  }
-
-  @Nullable
-  @Override
-  public Icon getIcon() {
-    return element.getIcon(0);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/completion/ParameterCompletionContributor.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/completion/ParameterCompletionContributor.java
deleted file mode 100644
index 48eb0f3..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/completion/ParameterCompletionContributor.java
+++ /dev/null
@@ -1,55 +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.completion;
-
-import com.google.idea.blaze.base.lang.buildfile.psi.ParameterList;
-import com.intellij.codeInsight.completion.*;
-import com.intellij.codeInsight.lookup.LookupElementBuilder;
-import com.intellij.icons.AllIcons;
-import com.intellij.util.ProcessingContext;
-
-import static com.intellij.patterns.PlatformPatterns.psiElement;
-
-/**
- * {@link CompletionContributor} for starred function parameters.
- */
-public class ParameterCompletionContributor extends CompletionContributor {
-
-  public ParameterCompletionContributor() {
-    extend(CompletionType.BASIC,
-           psiElement().inside(ParameterList.class).afterLeaf("*"),
-           new ParameterCompletionProvider("args"));
-    extend(CompletionType.BASIC,
-           psiElement().inside(ParameterList.class).afterLeaf("**"),
-           new ParameterCompletionProvider("kwargs"));
-  }
-
-  private static class ParameterCompletionProvider extends CompletionProvider<CompletionParameters> {
-    private String myName;
-
-    private ParameterCompletionProvider(String name) {
-      myName = name;
-    }
-
-    @Override
-    protected void addCompletions(CompletionParameters parameters,
-                                  ProcessingContext context,
-                                  CompletionResultSet result) {
-      result.addElement(LookupElementBuilder.create(myName).withIcon(AllIcons.Nodes.Parameter));
-    }
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/completion/StringLiteralReferenceLookupElement.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/completion/StringLiteralReferenceLookupElement.java
deleted file mode 100644
index 0d8f3fe..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/completion/StringLiteralReferenceLookupElement.java
+++ /dev/null
@@ -1,53 +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.completion;
-
-import com.google.idea.blaze.base.lang.buildfile.psi.StringLiteral;
-import com.google.idea.blaze.base.lang.buildfile.references.QuoteType;
-import com.intellij.openapi.util.NullableLazyValue;
-import com.intellij.psi.PsiElement;
-
-import javax.annotation.Nullable;
-import javax.swing.*;
-
-/**
- * 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 {
-
-  private final StringLiteral literal;
-  private NullableLazyValue<PsiElement> referencedElement = new NullableLazyValue<PsiElement>() {
-    @Nullable
-    @Override
-    protected PsiElement compute() {
-      return literal.getReferencedElement();
-    }
-  };
-
-  public StringLiteralReferenceLookupElement(StringLiteral literal, QuoteType quoteType) {
-    super(literal.getStringContents(), quoteType);
-    this.literal = literal;
-  }
-
-  @Nullable
-  @Override
-  public Icon getIcon() {
-    PsiElement ref = referencedElement.getValue();
-    return ref != null ? ref.getIcon(0) : null;
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/editor/BuildEnterBetweenBracketsHandler.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/editor/BuildEnterBetweenBracketsHandler.java
deleted file mode 100644
index 3231550..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/editor/BuildEnterBetweenBracketsHandler.java
+++ /dev/null
@@ -1,49 +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.editor;
-
-import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
-import com.intellij.codeInsight.editorActions.enter.EnterBetweenBracesHandler;
-import com.intellij.openapi.actionSystem.DataContext;
-import com.intellij.openapi.editor.Editor;
-import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
-import com.intellij.openapi.util.Ref;
-import com.intellij.psi.PsiFile;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Extends IntelliJ's {@link EnterBetweenBracesHandler}, including square brackets as a valid
- * brace pair type.
- */
-public class BuildEnterBetweenBracketsHandler extends EnterBetweenBracesHandler {
-  @Override
-  public Result preprocessEnter(@NotNull PsiFile file,
-                                @NotNull Editor editor,
-                                @NotNull Ref<Integer> caretOffsetRef,
-                                @NotNull Ref<Integer> caretAdvance,
-                                @NotNull DataContext dataContext,
-                                EditorActionHandler originalHandler) {
-    if (!(file instanceof BuildFile)) {
-      return Result.Continue;
-    }
-    return super.preprocessEnter(file, editor, caretOffsetRef, caretAdvance, dataContext, originalHandler);
-  }
-
-  @Override
-  protected boolean isBracePair(char c1, char c2) {
-    return c1 == '[' && c2  == ']';
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/editor/BuildEnterHandler.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/editor/BuildEnterHandler.java
deleted file mode 100644
index 044280c..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/editor/BuildEnterHandler.java
+++ /dev/null
@@ -1,251 +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.editor;
-
-import com.google.idea.blaze.base.lang.buildfile.lexer.BuildToken;
-import com.google.idea.blaze.base.lang.buildfile.psi.*;
-import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
-import com.intellij.codeInsight.editorActions.enter.EnterHandlerDelegateAdapter;
-import com.intellij.ide.DataManager;
-import com.intellij.injected.editor.EditorWindow;
-import com.intellij.lang.injection.InjectedLanguageManager;
-import com.intellij.openapi.actionSystem.DataContext;
-import com.intellij.openapi.editor.Document;
-import com.intellij.openapi.editor.Editor;
-import com.intellij.openapi.editor.LogicalPosition;
-import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
-import com.intellij.openapi.editor.actions.SplitLineAction;
-import com.intellij.openapi.util.Ref;
-import com.intellij.openapi.util.TextRange;
-import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.psi.*;
-import com.intellij.psi.codeStyle.CodeStyleSettings;
-import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
-import com.intellij.psi.codeStyle.CommonCodeStyleSettings.IndentOptions;
-import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
-import com.intellij.util.text.CharArrayUtil;
-
-import javax.annotation.Nullable;
-
-/**
- * Inserts indents as appropriate when enter is pressed.<br>
- * This is a substitute for implementing a full FormattingModel for the BUILD language.
- * If we ever decide to do that, this code should be removed.
- */
-public class BuildEnterHandler extends EnterHandlerDelegateAdapter {
-
-  @Override
-  public Result preprocessEnter(PsiFile file,
-                                Editor editor,
-                                Ref<Integer> caretOffset,
-                                Ref<Integer> caretAdvance,
-                                DataContext dataContext,
-                                EditorActionHandler originalHandler) {
-    int offset = caretOffset.get();
-    if (editor instanceof EditorWindow) {
-      file = InjectedLanguageManager.getInstance(file.getProject()).getTopLevelFile(file);
-      editor = InjectedLanguageUtil.getTopLevelEditor(editor);
-      offset = editor.getCaretModel().getOffset();
-    }
-    if (!isApplicable(file, dataContext)) {
-      return Result.Continue;
-    }
-
-    // Previous enter handler's (e.g. EnterBetweenBracesHandler) can introduce a mismatch
-    // between the editor's caret model and the offset we've been provided with.
-    editor.getCaretModel().moveToOffset(offset);
-
-    Document doc = editor.getDocument();
-    PsiDocumentManager.getInstance(file.getProject()).commitDocument(doc);
-
-    CodeStyleSettings currentSettings = CodeStyleSettingsManager.getSettings(file.getProject());
-    IndentOptions indentOptions = currentSettings.getIndentOptions(file.getFileType());
-
-    Integer indent = determineIndent(file, editor, offset, indentOptions);
-    if (indent == null) {
-      return Result.Continue;
-    }
-
-    removeTrailingWhitespace(doc, file, offset);
-    originalHandler.execute(editor, editor.getCaretModel().getCurrentCaret(), dataContext);
-    LogicalPosition position = editor.getCaretModel().getLogicalPosition();
-    if (position.column == indent) {
-      return Result.Stop;
-    }
-    if (position.column > indent) {
-      //default enter handler has added too many spaces -- remove them
-      int excess = position.column - indent;
-      doc.deleteString(editor.getCaretModel().getOffset() - excess, editor.getCaretModel().getOffset());
-    } else if (position.column < indent) {
-      String spaces = StringUtil.repeatSymbol(' ', indent - position.column);
-      doc.insertString(editor.getCaretModel().getOffset(), spaces);
-    }
-    editor.getCaretModel().moveToLogicalPosition(new LogicalPosition(position.line, indent));
-    return Result.Stop;
-  }
-
-  private static void removeTrailingWhitespace(Document doc, PsiFile file, int offset) {
-    CharSequence chars = doc.getCharsSequence();
-    int start = offset;
-    while (offset < chars.length() && chars.charAt(offset) == ' ') {
-      PsiElement element = file.findElementAt(offset);
-      if (element == null || !(element instanceof PsiWhiteSpace)) {
-        break;
-      }
-      offset++;
-    }
-    if (start != offset) {
-      doc.deleteString(start, offset);
-    }
-  }
-
-  private static boolean isApplicable(PsiFile file, DataContext dataContext) {
-    if (!(file instanceof BuildFile)) {
-      return false;
-    }
-    Boolean isSplitLine = DataManager.getInstance().loadFromDataContext(dataContext, SplitLineAction.SPLIT_LINE_KEY);
-    if (isSplitLine != null) {
-      return false;
-    }
-    return true;
-  }
-
-  /**
-   * Returns null if an appropriate indent cannot be found. In that case we do nothing,
-   * and pass it along to the next EnterHandler.
-   */
-  @Nullable
-  private static Integer determineIndent(PsiFile file, Editor editor, int offset, IndentOptions indentOptions) {
-    if (offset == 0) {
-      return null;
-    }
-    Document doc = editor.getDocument();
-    PsiElement element = getRelevantElement(file, doc, offset);
-    PsiElement parent = element != null ? element.getParent() : null;
-    if (parent == null) {
-      return null;
-    }
-    if (endsBlock(element)) {
-      // current line indent subtract block indent
-      return Math.max(0, getIndent(doc, element) - indentOptions.INDENT_SIZE);
-    }
-
-    if (parent instanceof BuildListType) {
-      BuildListType list = (BuildListType) parent;
-      int listOffset = list.getStartOffset();
-      LogicalPosition caretPosition = editor.getCaretModel().getLogicalPosition();
-      LogicalPosition listStart = editor.offsetToLogicalPosition(listOffset);
-      if (listStart.line != caretPosition.line) {
-        // take the minimum of the current line's indent and the current caret position
-        return indentOfLineUpToCaret(doc, caretPosition.line, offset);
-      }
-      BuildElement firstChild = ((BuildListType) parent).getFirstElement();
-      if (firstChild != null && firstChild.getNode().getStartOffset() < offset) {
-        return getIndent(doc, firstChild);
-      }
-      return lineIndent(doc, listStart.line) + additionalIndent(parent, indentOptions);
-    }
-    if (parent instanceof StatementListContainer && afterColon(doc, offset)) {
-      return getIndent(doc, parent) + additionalIndent(parent, indentOptions);
-    }
-    return null;
-  }
-
-  private static int additionalIndent(PsiElement parent, IndentOptions indentOptions) {
-    return parent instanceof StatementListContainer
-      ? indentOptions.INDENT_SIZE : indentOptions.CONTINUATION_INDENT_SIZE;
-  }
-
-  private static int lineIndent(Document doc, int line) {
-    int startOffset = doc.getLineStartOffset(line);
-    int indentOffset = CharArrayUtil.shiftForward(doc.getCharsSequence(), startOffset, " \t");
-    return indentOffset - startOffset;
-  }
-
-  private static int getIndent(Document doc, PsiElement element) {
-    int offset = element.getNode().getStartOffset();
-    int lineNumber = doc.getLineNumber(offset);
-    return offset - doc.getLineStartOffset(lineNumber);
-  }
-
-  private static int indentOfLineUpToCaret(Document doc, int line, int caretOffset) {
-    int startOffset = doc.getLineStartOffset(line);
-    int indentOffset = CharArrayUtil.shiftForward(doc.getCharsSequence(), startOffset, " \t");
-    return Math.min(indentOffset, caretOffset) - startOffset;
-  }
-
-  private static boolean endsBlock(PsiElement element) {
-    return element instanceof ReturnStatement
-      || element instanceof PassStatement;
-  }
-
-  private static PsiElement getBlockEndingParent(PsiElement element) {
-    while (element != null && !(element instanceof PsiFileSystemItem)) {
-      if (endsBlock(element)) {
-        return element;
-      }
-      element = element.getParent();
-    }
-    return null;
-  }
-
-  @Nullable
-  private static PsiElement getRelevantElement(PsiFile file, Document doc, int offset) {
-    if (offset == 0) {
-      return null;
-    }
-    if (offset == doc.getTextLength()) {
-      offset--;
-    }
-    PsiElement element = file.findElementAt(offset);
-    while (element != null && isWhiteSpace(element)) {
-      element = PsiUtils.getPreviousNodeInTree(element);
-    }
-    PsiElement blockTerminator = getBlockEndingParent(element);
-    if (blockTerminator != null
-        && blockTerminator.getTextRange().getEndOffset() == element.getTextRange().getEndOffset()) {
-      return blockTerminator;
-    }
-    while (element != null && skipElement(element, offset)) {
-      element = element.getParent();
-    }
-    return element;
-  }
-
-  private static boolean isWhiteSpace(PsiElement element) {
-    if (element instanceof PsiWhiteSpace) {
-      return true;
-    }
-    return BuildToken.WHITESPACE_AND_NEWLINE.contains(element.getNode().getElementType());
-  }
-
-  private static boolean skipElement(PsiElement element, int offset) {
-    PsiElement parent = element.getParent();
-    if (parent == null || parent.getNode() == null || parent instanceof PsiFileSystemItem) {
-      return false;
-    }
-    TextRange childRange = element.getNode().getTextRange();
-    return childRange.equals(parent.getNode().getTextRange())
-      || childRange.getStartOffset() == offset && (parent instanceof Argument || parent instanceof Parameter);
-  }
-
-  private static boolean afterColon(Document doc, int offset) {
-    CharSequence text = doc.getCharsSequence();
-    int previousOffset = CharArrayUtil.shiftBackward(text, offset - 1, " \t");
-    return text.charAt(previousOffset) == ':';
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/editor/BuildQuoteHandler.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/editor/BuildQuoteHandler.java
deleted file mode 100644
index ecde586..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/editor/BuildQuoteHandler.java
+++ /dev/null
@@ -1,142 +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.editor;
-
-import com.google.idea.blaze.base.lang.buildfile.lexer.BuildToken;
-import com.google.idea.blaze.base.lang.buildfile.lexer.TokenKind;
-import com.intellij.codeInsight.editorActions.MultiCharQuoteHandler;
-import com.intellij.codeInsight.editorActions.SimpleTokenSetQuoteHandler;
-import com.intellij.openapi.editor.Document;
-import com.intellij.openapi.editor.highlighter.HighlighterIterator;
-import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.psi.tree.IElementType;
-
-import javax.annotation.Nullable;
-
-/**
- * Provides quote auto-closing support.
- */
-public class BuildQuoteHandler extends SimpleTokenSetQuoteHandler implements MultiCharQuoteHandler {
-
-  public BuildQuoteHandler() {
-    super(BuildToken.fromKind(TokenKind.STRING));
-  }
-
-  @Override
-  public boolean isOpeningQuote(HighlighterIterator iterator, int offset) {
-    if (!myLiteralTokenSet.contains(iterator.getTokenType())) {
-      return false;
-    }
-    int start = iterator.getStart();
-    if (offset == start) {
-      return true;
-    }
-    final Document document = iterator.getDocument();
-    if (document == null) {
-      return false;
-    }
-    CharSequence text = document.getCharsSequence();
-    char theQuote = text.charAt(offset);
-    if (offset >= 2 &&
-        text.charAt(offset - 1) == theQuote &&
-        text.charAt(offset - 2) == theQuote &&
-        (offset < 3 || text.charAt(offset - 3) != theQuote))
-    if (super.isOpeningQuote(iterator, offset)) {
-      return true;
-    }
-    return false;
-  }
-
-  private static int getLiteralStartOffset(CharSequence text, int start) {
-    char c = Character.toUpperCase(text.charAt(start));
-    if (c == 'U' || c == 'B') {
-      start++;
-      c = Character.toUpperCase(text.charAt(start));
-    }
-    if (c == 'R') {
-      start++;
-    }
-    return start;
-  }
-
-  @Override
-  protected boolean isNonClosedLiteral(HighlighterIterator iterator, CharSequence chars) {
-    int end = iterator.getEnd();
-    if (getLiteralStartOffset(chars, iterator.getStart()) >= end - 1) {
-      return true;
-    }
-    char endSymbol = chars.charAt(end - 1);
-    if (endSymbol != '"' && endSymbol != '\'') {
-      return true;
-    }
-
-    //for triple quoted string
-    if (end >= 3 &&
-        (endSymbol == chars.charAt(end - 2)) && (chars.charAt(end - 2) == chars.charAt(end - 3)) &&
-        (end < 4 || chars.charAt(end - 4) != endSymbol)) {
-      return true;
-    }
-
-    return false;
-  }
-
-  @Override
-  public boolean isClosingQuote(HighlighterIterator iterator, int offset) {
-    final IElementType tokenType = iterator.getTokenType();
-    if (!myLiteralTokenSet.contains(tokenType)) {
-      return false;
-    }
-    int start = iterator.getStart();
-    int end = iterator.getEnd();
-    if (end - start >= 1 && offset == end - 1) {
-      return true; // single quote
-    }
-    if (end - start < 3 || offset < end - 3) {
-      return false;
-    }
-    // check for triple quote
-    Document doc = iterator.getDocument();
-    if (doc == null) {
-      return false;
-    }
-    CharSequence chars = doc.getCharsSequence();
-    char quote = chars.charAt(start);
-    boolean tripleQuote = quote == chars.charAt(start + 1)
-      && quote == chars.charAt(start + 2);
-    if (!tripleQuote) {
-      return false;
-    }
-    for (int i = offset; i < Math.min(offset + 2, end); i++) {
-      if (quote != chars.charAt(i)) {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  @Nullable
-  @Override
-  public CharSequence getClosingQuote(HighlighterIterator iterator, int offset) {
-    char theQuote = iterator.getDocument().getCharsSequence().charAt(offset - 1);
-    if (super.isOpeningQuote(iterator, offset - 1)) {
-      return String.valueOf(theQuote);
-    }
-    if (super.isOpeningQuote(iterator, offset - 3)) {
-      return StringUtil.repeat(String.valueOf(theQuote), 3);
-    }
-    return null;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/findusages/BuildElementDescriptionProvider.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/findusages/BuildElementDescriptionProvider.java
deleted file mode 100644
index 817a158..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/findusages/BuildElementDescriptionProvider.java
+++ /dev/null
@@ -1,50 +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.findusages;
-
-import com.google.idea.blaze.base.lang.buildfile.psi.BuildElement;
-import com.intellij.psi.ElementDescriptionLocation;
-import com.intellij.psi.ElementDescriptionProvider;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiNameIdentifierOwner;
-import com.intellij.usageView.UsageViewLongNameLocation;
-import com.intellij.usageView.UsageViewShortNameLocation;
-
-import javax.annotation.Nullable;
-
-/**
- * Controls text shown for target in the 'find usages' dialog.
- */
-public class BuildElementDescriptionProvider implements ElementDescriptionProvider {
-  @Nullable
-  @Override
-  public String getElementDescription(PsiElement element, ElementDescriptionLocation location) {
-    if (!(element instanceof BuildElement)) {
-      return null;
-    }
-    if (location instanceof UsageViewLongNameLocation) {
-      return ((BuildElement) element).getPresentableText();
-    }
-    if (location instanceof UsageViewShortNameLocation) {
-      if (element instanceof PsiNameIdentifierOwner) {
-        // this is used by rename operations, so needs to be accurate
-        return ((PsiNameIdentifierOwner) element).getName();
-      }
-      return ((BuildElement) element).getPresentableText();
-    }
-    return null;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/findusages/BuildFileGroupingRuleProvider.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/findusages/BuildFileGroupingRuleProvider.java
deleted file mode 100644
index 73a9d4f..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/findusages/BuildFileGroupingRuleProvider.java
+++ /dev/null
@@ -1,93 +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.findusages;
-
-import com.google.idea.blaze.base.lang.buildfile.language.BuildFileType;
-import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.usages.Usage;
-import com.intellij.usages.UsageGroup;
-import com.intellij.usages.UsageView;
-import com.intellij.usages.impl.FileStructureGroupRuleProvider;
-import com.intellij.usages.impl.rules.FileGroupingRule;
-import com.intellij.usages.rules.UsageGroupingRule;
-import com.intellij.usages.rules.UsageInFile;
-
-import javax.swing.*;
-
-/**
- * Allows us to customize the filename string in the 'find usages' dialog, rather than displaying them all as 'BUILD'.
- */
-public class BuildFileGroupingRuleProvider implements FileStructureGroupRuleProvider {
-
-  public static UsageGroupingRule getGroupingRule(Project project) {
-    return new BuildFileGroupingRule(project);
-  }
-
-  @Override
-  public UsageGroupingRule getUsageGroupingRule(Project project) {
-    return getGroupingRule(project);
-  }
-
-  private static class BuildFileGroupingRule extends FileGroupingRule {
-
-    private final Project project;
-
-    BuildFileGroupingRule(Project project) {
-      super(project);
-      this.project = project;
-    }
-
-    @Override
-    public UsageGroup groupUsage(Usage usage) {
-      if (!(usage instanceof UsageInFile)) {
-        return null;
-      }
-      final VirtualFile virtualFile = ((UsageInFile) usage).getFile();
-      if (virtualFile.getFileType() != BuildFileType.INSTANCE) {
-        return null;
-      }
-      return new FileUsageGroup(project, virtualFile) {
-        String name;
-
-        @Override
-        public void update() {
-          if (isValid()) {
-            super.update();
-            name = BuildFile.getBuildFileString(project, virtualFile.getPath());
-          }
-        }
-
-        @Override
-        public String getPresentableName() {
-          return name;
-        }
-
-        @Override
-        public String getText(UsageView view) {
-          return name;
-        }
-
-        @Override
-        public Icon getIcon(boolean isOpen) {
-          return null; // already shown by default usage group (which we can't remove...)
-        }
-      };
-    }
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/findusages/BuildFindUsagesProvider.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/findusages/BuildFindUsagesProvider.java
deleted file mode 100644
index 792bda8..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/findusages/BuildFindUsagesProvider.java
+++ /dev/null
@@ -1,107 +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.findusages;
-
-import com.google.idea.blaze.base.lang.buildfile.lexer.BuildLexer;
-import com.google.idea.blaze.base.lang.buildfile.lexer.BuildLexerBase.LexerMode;
-import com.google.idea.blaze.base.lang.buildfile.lexer.BuildToken;
-import com.google.idea.blaze.base.lang.buildfile.lexer.TokenKind;
-import com.google.idea.blaze.base.lang.buildfile.psi.*;
-import com.intellij.lang.HelpID;
-import com.intellij.lang.cacheBuilder.DefaultWordsScanner;
-import com.intellij.lang.cacheBuilder.WordsScanner;
-import com.intellij.lang.findUsages.FindUsagesProvider;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiNamedElement;
-import com.intellij.psi.tree.TokenSet;
-
-/**
- * Required for highlighting references (among other things we don't currently support).
- * Currently only used by the fallback 'DefaultFindUsagesHandlerFactory'.
- */
-public class BuildFindUsagesProvider implements FindUsagesProvider {
-
-  @Override
-  public boolean canFindUsagesFor(PsiElement psiElement) {
-    return psiElement instanceof FuncallExpression
-      || psiElement instanceof PsiNamedElement
-      || psiElement instanceof ReferenceExpression;
-  }
-
-  @Override
-  public String getHelpId(PsiElement psiElement) {
-    if (psiElement instanceof FunctionStatement) {
-      return "reference.dialogs.findUsages.method";
-    }
-    if (psiElement instanceof TargetExpression
-      || psiElement instanceof Parameter
-      || psiElement instanceof ReferenceExpression) {
-      return "reference.dialogs.findUsages.variable";
-    }
-    // typically build rules and imported Skylark functions, but also all other function calls
-    return HelpID.FIND_OTHER_USAGES;
-  }
-
-  @Override
-  public String getType(PsiElement element) {
-    if (element instanceof FunctionStatement) {
-      return "function";
-    }
-    if (element instanceof Parameter) {
-      return "parameter";
-    }
-    if (element instanceof ReferenceExpression
-      || element instanceof TargetExpression) {
-      return "variable";
-    }
-    if (element instanceof Argument.Keyword) {
-      return "keyword argument";
-    }
-    if (element instanceof FuncallExpression) {
-      return "rule";
-    }
-    return "";
-  }
-
-  /**
-   * Controls text shown for target element in the 'find usages' dialog
-   */
-  @Override
-  public String getDescriptiveName(PsiElement element) {
-    if (element instanceof BuildElement) {
-      return ((BuildElement) element).getPresentableText();
-    }
-    return element.toString();
-  }
-
-  @Override
-  public String getNodeText(PsiElement element, boolean useFullName) {
-    return getDescriptiveName(element);
-  }
-
-  @Override
-  public WordsScanner getWordsScanner() {
-    return new DefaultWordsScanner(
-      new BuildLexer(LexerMode.SyntaxHighlighting),
-      tokenSet(TokenKind.IDENTIFIER),
-      tokenSet(TokenKind.COMMENT),
-      tokenSet(TokenKind.STRING));
-  }
-
-  private static TokenSet tokenSet(TokenKind token) {
-    return TokenSet.create(BuildToken.fromKind(token));
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/findusages/BuildReadWriteAccessDetector.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/findusages/BuildReadWriteAccessDetector.java
deleted file mode 100644
index 91d6810..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/findusages/BuildReadWriteAccessDetector.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.lang.buildfile.findusages;
-
-import com.google.idea.blaze.base.lang.buildfile.psi.AugmentedAssignmentStatement;
-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.intellij.codeInsight.highlighting.ReadWriteAccessDetector;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiReference;
-
-/**
- * Used by find usages tools.
- */
-public class BuildReadWriteAccessDetector extends ReadWriteAccessDetector {
-  @Override
-  public boolean isReadWriteAccessible(PsiElement element) {
-    return element instanceof TargetExpression || element instanceof ReferenceExpression;
-  }
-
-  @Override
-  public boolean isDeclarationWriteAccess(PsiElement element) {
-    return element instanceof TargetExpression;
-  }
-
-  @Override
-  public Access getReferenceAccess(PsiElement referencedElement, PsiReference reference) {
-    return getExpressionAccess(reference.getElement());
-  }
-
-  @Override
-  public Access getExpressionAccess(PsiElement expression) {
-    if (isDeclarationWriteAccess(expression)) {
-      return Access.Write;
-    }
-    if (expression instanceof ReferenceExpression) {
-      if (PsiUtils.getParentOfType(expression, AugmentedAssignmentStatement.class) != null) {
-        return Access.ReadWrite;
-      }
-    }
-    return Access.Read;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/findusages/BuildTargetElementEvaluator.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/findusages/BuildTargetElementEvaluator.java
deleted file mode 100644
index 8c0ca79..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/findusages/BuildTargetElementEvaluator.java
+++ /dev/null
@@ -1,109 +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.findusages;
-
-import com.google.idea.blaze.base.lang.buildfile.psi.Argument.Keyword;
-import com.google.idea.blaze.base.lang.buildfile.psi.ArgumentList;
-import com.google.idea.blaze.base.lang.buildfile.psi.FuncallExpression;
-import com.google.idea.blaze.base.lang.buildfile.psi.StringLiteral;
-import com.intellij.codeInsight.TargetElementEvaluatorEx2;
-import com.intellij.openapi.util.TextRange;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiReference;
-import com.intellij.psi.impl.source.resolve.reference.impl.PsiMultiReference;
-
-import javax.annotation.Nullable;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.Objects;
-
-/**
- * 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 {
-
-  @Override
-  public boolean includeSelfInGotoImplementation(PsiElement element) {
-    return false;
-  }
-
-  /**
-   * Returns null in the cases where we're happy with the default behavior.
-   */
-  @Nullable
-  @Override
-  public PsiElement getElementByReference(PsiReference ref, int flags) {
-    if (!(ref instanceof PsiMultiReference) || !(ref.getElement() instanceof StringLiteral)) {
-      return null;
-    }
-    // choose the outer-most reference
-    PsiReference[] refs = ((PsiMultiReference) ref).getReferences().clone();
-    Arrays.sort(refs, COMPARATOR);
-    return refs[0].resolve();
-  }
-
-  private static final Comparator<PsiReference> COMPARATOR = new Comparator<PsiReference>() {
-    @Override
-    public int compare(final PsiReference ref1, final PsiReference ref2) {
-      boolean resolves1 = ref1.resolve() != null;
-      boolean resolves2 = ref2.resolve() != null;
-      if (resolves1 && !resolves2) return -1;
-      if (!resolves1 && resolves2) return 1;
-
-      final TextRange range1 = ref1.getRangeInElement();
-      final TextRange range2 = ref2.getRangeInElement();
-
-      if(TextRange.areSegmentsEqual(range1, range2)) return 0;
-      if(range1.getStartOffset() >= range2.getStartOffset() && range1.getEndOffset() <= range2.getEndOffset()) return 1;
-      if(range2.getStartOffset() >= range1.getStartOffset() && range2.getEndOffset() <= range1.getEndOffset()) return -1;
-
-      return 0;
-    }
-  };
-
-  /**
-   * Redirect 'name' funcall argument values to the funcall expression (b/29088829).
-   */
-  @Nullable
-  @Override
-  public PsiElement getNamedElement(PsiElement element) {
-    return getParentFuncallIfNameString(element);
-  }
-
-  @Nullable
-  private static FuncallExpression getParentFuncallIfNameString(PsiElement element) {
-    PsiElement parent = element.getParent();
-    if (!(parent instanceof StringLiteral)) {
-      return null;
-    }
-    parent = parent.getParent();
-    if (!(parent instanceof Keyword)) {
-      return null;
-    }
-    if (!Objects.equals(((Keyword) parent).getName(), "name")) {
-      return null;
-    }
-    parent = parent.getParent();
-    if (!(parent instanceof ArgumentList)) {
-      return null;
-    }
-    parent = parent.getParent();
-    return parent instanceof FuncallExpression ? (FuncallExpression) parent : null;
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/findusages/BuildUsageGroupingRuleProvider.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/findusages/BuildUsageGroupingRuleProvider.java
deleted file mode 100644
index 9371a92..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/findusages/BuildUsageGroupingRuleProvider.java
+++ /dev/null
@@ -1,39 +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.findusages;
-
-import com.intellij.openapi.actionSystem.AnAction;
-import com.intellij.openapi.project.Project;
-import com.intellij.usages.UsageView;
-import com.intellij.usages.rules.UsageGroupingRule;
-import com.intellij.usages.rules.UsageGroupingRuleProvider;
-
-/**
- * This is a gross hack. We want to always include file paths for BUILD files in the 'find usages' dialog.<br>
- * This achieves that by inserting an additional UsageGroupingRule for each file usage,
- * regardless of whether we're grouping by file structure
- */
-public class BuildUsageGroupingRuleProvider implements UsageGroupingRuleProvider {
-  @Override
-  public UsageGroupingRule[] getActiveRules(Project project) {
-    return new UsageGroupingRule[] {BuildFileGroupingRuleProvider.getGroupingRule(project)};
-  }
-
-  @Override
-  public AnAction[] createGroupingActions(UsageView view) {
-    return new AnAction[0];
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/formatting/BuildBraceMatcher.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/formatting/BuildBraceMatcher.java
deleted file mode 100644
index 29f9528..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/formatting/BuildBraceMatcher.java
+++ /dev/null
@@ -1,75 +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.formatting;
-
-import com.google.idea.blaze.base.lang.buildfile.lexer.BuildToken;
-import com.google.idea.blaze.base.lang.buildfile.lexer.TokenKind;
-import com.intellij.lang.BracePair;
-import com.intellij.lang.PairedBraceMatcher;
-import com.intellij.psi.PsiFile;
-import com.intellij.psi.tree.IElementType;
-import com.intellij.psi.tree.TokenSet;
-
-import javax.annotation.Nullable;
-import java.util.Arrays;
-
-/**
- * This adds a close brace automatically once an opening brace is typed by the user in the editor.
- */
-public class BuildBraceMatcher implements PairedBraceMatcher {
-
-  private static final BracePair[] PAIRS = new BracePair[] {
-    new BracePair(BuildToken.fromKind(TokenKind.LPAREN), BuildToken.fromKind(TokenKind.RPAREN), true),
-    new BracePair(BuildToken.fromKind(TokenKind.LBRACKET), BuildToken.fromKind(TokenKind.RBRACKET), true),
-    new BracePair(BuildToken.fromKind(TokenKind.LBRACE), BuildToken.fromKind(TokenKind.RBRACE), true)
-  };
-
-  private static final TokenSet BRACES_ALLOWED_BEFORE = tokenSet(
-    TokenKind.NEWLINE,
-    TokenKind.WHITESPACE,
-    TokenKind.COMMENT,
-    TokenKind.COLON,
-    TokenKind.COMMA,
-    TokenKind.RPAREN,
-    TokenKind.RBRACKET,
-    TokenKind.RBRACE,
-    TokenKind.LBRACE
-  );
-
-  @Override
-  public BracePair[] getPairs() {
-    return PAIRS;
-  }
-
-  @Override
-  public boolean isPairedBracesAllowedBeforeType(IElementType lbraceType, @Nullable IElementType contextType) {
-    return contextType == null || BRACES_ALLOWED_BEFORE.contains(contextType);
-  }
-
-  @Override
-  public int getCodeConstructStart(PsiFile file, int openingBraceOffset) {
-    return openingBraceOffset;
-  }
-
-  private static TokenSet tokenSet(TokenKind... kind) {
-    return TokenSet.create(
-      Arrays.stream(kind)
-        .map(BuildToken::fromKind)
-        .toArray(IElementType[]::new)
-    );
-  }
-}
-
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/formatting/BuildCodeStyleSettingsProvider.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/formatting/BuildCodeStyleSettingsProvider.java
deleted file mode 100644
index ac31695..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/formatting/BuildCodeStyleSettingsProvider.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.base.lang.buildfile.formatting;
-
-import com.google.idea.blaze.base.lang.buildfile.language.BuildFileLanguage;
-import com.google.idea.blaze.base.lang.buildfile.language.BuildFileType;
-import com.intellij.application.options.CodeStyleAbstractConfigurable;
-import com.intellij.application.options.CodeStyleAbstractPanel;
-import com.intellij.application.options.TabbedLanguageCodeStylePanel;
-import com.intellij.openapi.options.Configurable;
-import com.intellij.psi.codeStyle.CodeStyleSettings;
-import com.intellij.psi.codeStyle.CodeStyleSettingsProvider;
-import com.intellij.psi.codeStyle.LanguageCodeStyleSettingsProvider;
-
-import javax.annotation.Nullable;
-
-/**
- * Separate configurable code-style settings for BUILD language.
- */
-public class BuildCodeStyleSettingsProvider extends CodeStyleSettingsProvider {
-
-  @Override
-  public Configurable createSettingsPage(CodeStyleSettings settings, CodeStyleSettings originalSettings) {
-    return new CodeStyleAbstractConfigurable(settings, originalSettings, BuildFileType.INSTANCE.getDescription()) {
-      @Override
-      protected CodeStyleAbstractPanel createPanel(final CodeStyleSettings settings) {
-        return new TabbedLanguageCodeStylePanel(BuildFileLanguage.INSTANCE, getCurrentSettings(), settings) {
-          @Override
-          protected void initTabs(CodeStyleSettings settings) {
-            LanguageCodeStyleSettingsProvider provider = LanguageCodeStyleSettingsProvider.forLanguage(getDefaultLanguage());
-            addIndentOptionsTab(settings);
-          }
-        };
-      }
-
-      @Override
-      public String getHelpTopic() {
-        return null;
-      }
-    };
-  }
-
-  @Nullable
-  @Override
-  public String getConfigurableDisplayName() {
-    return BuildFileType.INSTANCE.getDescription();
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/formatting/BuildCommenter.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/formatting/BuildCommenter.java
deleted file mode 100644
index ad9d849..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/formatting/BuildCommenter.java
+++ /dev/null
@@ -1,95 +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.formatting;
-
-import com.google.idea.blaze.base.lang.buildfile.lexer.BuildToken;
-import com.google.idea.blaze.base.lang.buildfile.lexer.TokenKind;
-import com.intellij.lang.CodeDocumentationAwareCommenter;
-import com.intellij.psi.PsiComment;
-import com.intellij.psi.tree.IElementType;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * Supports (un)commenting lines via IntelliJ
- */
-public class BuildCommenter implements CodeDocumentationAwareCommenter {
-
-  @Nullable
-  @Override
-  public String getLineCommentPrefix() {
-    return "#";
-  }
-
-  @Nullable
-  @Override
-  public String getBlockCommentPrefix() {
-    return null;
-  }
-
-  @Nullable
-  @Override
-  public String getBlockCommentSuffix() {
-    return null;
-  }
-
-  @Nullable
-  @Override
-  public String getCommentedBlockCommentPrefix() {
-    return null;
-  }
-
-  @Nullable
-  @Override
-  public String getCommentedBlockCommentSuffix() {
-    return null;
-  }
-
-  @Override
-  public IElementType getLineCommentTokenType() {
-    return BuildToken.fromKind(TokenKind.COMMENT);
-  }
-
-  @Override
-  public IElementType getBlockCommentTokenType() {
-    return null;
-  }
-
-  @Override
-  public IElementType getDocumentationCommentTokenType() {
-    return null;
-  }
-
-  @Override
-  public String getDocumentationCommentPrefix() {
-    return null;
-  }
-
-  @Override
-  public String getDocumentationCommentLinePrefix() {
-    return null;
-  }
-
-  @Override
-  public String getDocumentationCommentSuffix() {
-    return null;
-  }
-
-  @Override
-  public boolean isDocumentationComment(PsiComment element) {
-    return false;
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/formatting/BuildFileFoldingBuilder.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/formatting/BuildFileFoldingBuilder.java
deleted file mode 100644
index d17ebfe..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/formatting/BuildFileFoldingBuilder.java
+++ /dev/null
@@ -1,144 +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.formatting;
-
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.lang.buildfile.lexer.BuildToken;
-import com.google.idea.blaze.base.lang.buildfile.lexer.TokenKind;
-import com.google.idea.blaze.base.lang.buildfile.psi.BuildElementTypes;
-import com.google.idea.blaze.base.lang.buildfile.psi.FuncallExpression;
-import com.google.idea.blaze.base.lang.buildfile.psi.LoadStatement;
-import com.intellij.lang.ASTNode;
-import com.intellij.lang.FileASTNode;
-import com.intellij.lang.folding.FoldingBuilder;
-import com.intellij.lang.folding.FoldingDescriptor;
-import com.intellij.openapi.editor.Document;
-import com.intellij.openapi.util.TextRange;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.TokenType;
-import com.intellij.psi.tree.IElementType;
-
-import javax.annotation.Nullable;
-import java.util.List;
-
-/**
- * Simple code block folding for BUILD files.
- */
-public class BuildFileFoldingBuilder implements FoldingBuilder {
-
-  /**
-   * Currently only folding top-level nodes.
-   */
-  @Override
-  public FoldingDescriptor[] buildFoldRegions(ASTNode node, Document document) {
-    List<FoldingDescriptor> descriptors = Lists.newArrayList();
-    if (node instanceof FileASTNode) {
-      for (ASTNode child : node.getChildren(null)) {
-        addDescriptors(descriptors, child);
-      }
-    } else if (isTopLevel(node)) {
-      addDescriptors(descriptors, node);
-    }
-    return descriptors.toArray(new FoldingDescriptor[0]);
-  }
-
-  /**
-   * Only folding top-level statements
-   */
-  private void addDescriptors(List<FoldingDescriptor> descriptors, ASTNode node) {
-    IElementType type = node.getElementType();
-    if (type == BuildElementTypes.FUNCTION_STATEMENT) {
-      ASTNode colon = node.findChildByType(BuildToken.fromKind(TokenKind.COLON));
-      if (colon == null) {
-        return;
-      }
-      ASTNode stmtList = node.findChildByType(BuildElementTypes.STATEMENT_LIST);
-      if (stmtList == null) {
-        return;
-      }
-      int start = colon.getStartOffset() + 1;
-      int end = endOfList(stmtList);
-      descriptors.add(new FoldingDescriptor(node, range(start, end)));
-
-    } else if (type == BuildElementTypes.FUNCALL_EXPRESSION || type == BuildElementTypes.LOAD_STATEMENT) {
-      ASTNode listNode = type == BuildElementTypes.FUNCALL_EXPRESSION ? node.findChildByType(BuildElementTypes.ARGUMENT_LIST) : node;
-      if (listNode == null) {
-        return;
-      }
-      ASTNode lParen = listNode.findChildByType(BuildToken.fromKind(TokenKind.LPAREN));
-      ASTNode rParen = listNode.findChildByType(BuildToken.fromKind(TokenKind.RPAREN));
-      if (lParen == null || rParen == null) {
-        return;
-      }
-      int start = lParen.getStartOffset() + 1;
-      int end = rParen.getTextRange().getEndOffset() - 1;
-      descriptors.add(new FoldingDescriptor(node, range(start, end)));
-    }
-  }
-
-  private static TextRange range(int start, int end) {
-    if (start >= end) {
-      return new TextRange(start, start + 1);
-    }
-    return new TextRange(start, end);
-  }
-
-  /**
-   * Don't include whitespace and newlines at the end of the function.<br>
-   * Could do this in the lexer instead, with additional look-ahead checks.
-   */
-  private int endOfList(ASTNode stmtList) {
-    ASTNode child = stmtList.getLastChildNode();
-    while (child != null) {
-      IElementType type = child.getElementType();
-      if (type != TokenType.WHITE_SPACE
-        && type != BuildToken.fromKind(TokenKind.NEWLINE)) {
-        return child.getTextRange().getEndOffset();
-      }
-      child = child.getTreePrev();
-    }
-    return stmtList.getTextRange().getEndOffset();
-  }
-
-  private boolean isTopLevel(ASTNode node) {
-    return node.getTreeParent() instanceof FileASTNode;
-  }
-
-  @Override
-  @Nullable
-  public String getPlaceholderText(ASTNode node) {
-    PsiElement psi = node.getPsi();
-    if (psi instanceof FuncallExpression) {
-      FuncallExpression expr = (FuncallExpression) psi;
-      String name = expr.getNameArgumentValue();
-      if (name != null) {
-        return "name = \"" + name + "\"...";
-      }
-    }
-    if (psi instanceof LoadStatement) {
-      String fileName = ((LoadStatement) psi).getImportedPath();
-      if (fileName != null) {
-        return "\"" + fileName + "\"...";
-      }
-    }
-    return "...";
-  }
-
-  @Override
-  public boolean isCollapsedByDefault(ASTNode node) {
-    return false;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/formatting/BuildLanguageCodeStyleSettingsProvider.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/formatting/BuildLanguageCodeStyleSettingsProvider.java
deleted file mode 100644
index 3300a97..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/formatting/BuildLanguageCodeStyleSettingsProvider.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.lang.buildfile.formatting;
-
-import com.google.idea.blaze.base.lang.buildfile.language.BuildFileLanguage;
-import com.intellij.application.options.IndentOptionsEditor;
-import com.intellij.application.options.SmartIndentOptionsEditor;
-import com.intellij.lang.Language;
-import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
-import com.intellij.psi.codeStyle.LanguageCodeStyleSettingsProvider;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * Allows BUILD language-specific code style settings
- */
-public class BuildLanguageCodeStyleSettingsProvider extends LanguageCodeStyleSettingsProvider {
-
-  @Override
-  public Language getLanguage() {
-    return BuildFileLanguage.INSTANCE;
-  }
-
-  @Override
-  public IndentOptionsEditor getIndentOptionsEditor() {
-    return new SmartIndentOptionsEditor();
-  }
-
-  @Override
-  public String getCodeSample(@NotNull SettingsType settingsType) {
-    return "";
-  }
-
-  @Nullable
-  @Override
-  public CommonCodeStyleSettings getDefaultCommonSettings() {
-    CommonCodeStyleSettings defaultSettings = new CommonCodeStyleSettings(BuildFileLanguage.INSTANCE);
-    CommonCodeStyleSettings.IndentOptions indentOptions = defaultSettings.initIndentOptions();
-    indentOptions.TAB_SIZE = 2;
-    indentOptions.INDENT_SIZE = 2;
-    indentOptions.CONTINUATION_INDENT_SIZE = 4;
-    return defaultSettings;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/globbing/UnixGlob.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/globbing/UnixGlob.java
deleted file mode 100644
index 6a0200d..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/globbing/UnixGlob.java
+++ /dev/null
@@ -1,705 +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.globbing;
-
-import com.google.common.base.Preconditions;
-import com.google.common.base.Splitter;
-import com.google.common.base.Throwables;
-import com.google.common.cache.Cache;
-import com.google.common.cache.CacheBuilder;
-import com.google.common.cache.CacheLoader;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Ordering;
-import com.google.common.collect.Sets;
-import com.google.common.util.concurrent.ForwardingListenableFuture;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.SettableFuture;
-import com.google.idea.blaze.base.io.FileAttributeProvider;
-import com.google.idea.blaze.base.lang.buildfile.validation.GlobPatternValidator;
-import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.progress.ProgressManager;
-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 javax.annotation.Nullable;
-import java.io.File;
-import java.io.IOException;
-import java.util.*;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Predicate;
-import java.util.regex.Pattern;
-
-/**
- * Implementation of a subset of UNIX-style file globbing, expanding "*" and "?" as wildcards, but
- * not [a-z] ranges.
- *
- * <p><code>**</code> gets special treatment in include patterns. If it is used as a complete path
- * segment it matches the filenames in subdirectories recursively.
- *
- * Largely copied from {@link com.google.devtools.build.lib.vfs.UnixGlob}
- */
-public final class UnixGlob {
-  private UnixGlob() {}
-
-  private static List<File> globInternal(File base,
-                                         Collection<String> patterns,
-                                         Collection<String> excludePatterns,
-                                         boolean excludeDirectories,
-                                         Predicate<File> dirPred,
-                                         ThreadPoolExecutor threadPool)
-    throws IOException, InterruptedException {
-
-    GlobVisitor visitor = (threadPool == null)
-                          ? new GlobVisitor()
-                          : new GlobVisitor(threadPool);
-    return visitor.glob(base, patterns, excludePatterns, excludeDirectories, dirPred);
-  }
-
-  private static Future<List<File>> globAsyncInternal(File base,
-                                                      Collection<String> patterns,
-                                                      Collection<String> excludePatterns,
-                                                      boolean excludeDirectories,
-                                                      Predicate<File> dirPred,
-                                                      ThreadPoolExecutor threadPool) {
-    Preconditions.checkNotNull(threadPool, "%s %s", base, patterns);
-    try {
-      return new GlobVisitor(threadPool)
-        .globAsync(base, patterns, excludePatterns, excludeDirectories, dirPred);
-    } catch (IOException e) {
-      // We are evaluating asynchronously, so no exceptions should be thrown until the future is
-      // retrieved.
-      throw new IllegalStateException(e);
-    }
-  }
-
-  /**
-   * Checks that each pattern is valid, splits it into segments and checks
-   * that each segment contains only valid wildcards.
-   *
-   * @return list of segment arrays
-   */
-  private static List<String[]> checkAndSplitPatterns(Collection<String> patterns) {
-    List<String[]> list = Lists.newArrayListWithCapacity(patterns.size());
-    for (String pattern : patterns) {
-      String error = GlobPatternValidator.validate(pattern);
-      if (error != null) {
-        throw new IllegalArgumentException(error);
-      }
-      Iterable<String> segments = Splitter.on('/').split(pattern);
-      list.add(Iterables.toArray(segments, String.class));
-    }
-    return list;
-  }
-
-  private static boolean excludedOnMatch(File path,
-                                         List<String[]> excludePatterns,
-                                         int idx,
-                                         Cache<String, Pattern> cache) {
-    for (String[] excludePattern : excludePatterns) {
-      String text = path.getName();
-      if (idx == excludePattern.length
-          && matches(excludePattern[idx - 1], text, cache)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  /**
-   * Returns the exclude patterns in {@code excludePatterns} which could
-   * apply to the children of {@code base}
-   *
-   * @param idx index into {@code excludePatterns} for the part of the pattern
-   *        which might match {@code base}
-   */
-  private static List<String[]> getRelevantExcludes(
-    final File base,
-    List<String[]> excludePatterns,
-    final int idx,
-    final Cache<String, Pattern> cache) {
-
-    if (excludePatterns.isEmpty()) {
-      return excludePatterns;
-    }
-    List<String[]> list = new ArrayList<>();
-    for (String[] patterns : excludePatterns) {
-      if (excludePatternMatches(patterns, idx, base, cache)) {
-        list.add(patterns);
-      }
-    }
-    return list;
-  }
-
-  /**
-   * @param patterns a list of patterns
-   * @param idx index into {@code patterns}
-   */
-  private static boolean excludePatternMatches(String[] patterns,
-                                               int idx,
-                                               File base,
-                                               Cache<String, Pattern> cache) {
-    if (idx == 0) {
-      return true;
-    }
-    String text = base.getName();
-    return patterns.length > idx && matches(patterns[idx - 1], text, cache);
-  }
-
-  /**
-   * Calls {@link #matches(String, String, Cache) matches(pattern, str, null)}
-   */
-  public static boolean matches(String pattern, String str) {
-    return matches(pattern, str, null);
-  }
-
-  /**
-   * Returns whether {@code str} matches the glob pattern {@code pattern}. This
-   * method may use the {@code patternCache} to speed up the matching process.
-   *
-   * @param pattern a glob pattern
-   * @param str the string to match
-   * @param patternCache a cache from patterns to compiled Pattern objects, or
-   *        {@code null} to skip caching
-   */
-  public static boolean matches(String pattern,
-                                String str,
-                                Cache<String, Pattern> patternCache) {
-    if (pattern.length() == 0 || str.length() == 0) {
-      return false;
-    }
-
-    // Common case: **
-    if (pattern.equals("**")) {
-      return true;
-    }
-
-    // Common case: *
-    if (pattern.equals("*")) {
-      return true;
-    }
-
-    // If a filename starts with '.', this char must be matched explicitly.
-    if (str.charAt(0) == '.' && pattern.charAt(0) != '.') {
-      return false;
-    }
-
-    // Common case: *.xyz
-    if (pattern.charAt(0) == '*' && pattern.lastIndexOf('*') == 0) {
-      return str.endsWith(pattern.substring(1));
-    }
-    // Common case: xyz*
-    int lastIndex = pattern.length() - 1;
-    // The first clause of this if statement is unnecessary, but is an
-    // optimization--charAt runs faster than indexOf.
-    if (pattern.charAt(lastIndex) == '*' && pattern.indexOf('*') == lastIndex) {
-      return str.startsWith(pattern.substring(0, lastIndex));
-    }
-
-    Pattern regex = patternCache == null ? null : patternCache.getIfPresent(pattern);
-    if (regex == null) {
-      regex = makePatternFromWildcard(pattern);
-      if (patternCache != null) {
-        patternCache.put(pattern, regex);
-      }
-    }
-    return regex.matcher(str).matches();
-  }
-
-  /**
-   * Returns a regular expression implementing a matcher for "pattern", in which
-   * "*" and "?" are wildcards.
-   *
-   * <p>e.g. "foo*bar?.java" -> "foo.*bar.\\.java"
-   */
-  private static Pattern makePatternFromWildcard(String pattern) {
-    StringBuilder regexp = new StringBuilder();
-    for(int i = 0, len = pattern.length(); i < len; i++) {
-      char c = pattern.charAt(i);
-      switch(c) {
-        case '*':
-          int toIncrement = 0;
-          if (len > i + 1 && pattern.charAt(i + 1) == '*') {
-            // The pattern '**' is interpreted to match 0 or more directory separators, not 1 or
-            // more. We skip the next * and then find a trailing/leading '/' and get rid of it.
-            toIncrement = 1;
-            if (len > i + 2 && pattern.charAt(i + 2) == '/') {
-              // We have '**/' -- skip the '/'.
-              toIncrement = 2;
-            } else if (len == i + 2 && i > 0 && pattern.charAt(i - 1) == '/') {
-              // We have '/**' -- remove the '/'.
-              regexp.delete(regexp.length() - 1, regexp.length());
-            }
-          }
-          regexp.append(".*");
-          i += toIncrement;
-          break;
-        case '?':
-          regexp.append('.');
-          break;
-        //escape the regexp special characters that are allowed in wildcards
-        case '^': case '$': case '|': case '+':
-        case '{': case '}': case '[': case ']':
-        case '\\': case '.':
-          regexp.append('\\');
-          regexp.append(c);
-          break;
-        default:
-          regexp.append(c);
-          break;
-      }
-    }
-    return Pattern.compile(regexp.toString());
-  }
-
-  public static Builder forPath(File path) {
-    return new Builder(path);
-  }
-
-  /**
-   * Builder class for UnixGlob.
-   *
-   *
-   */
-  public static class Builder {
-    private File base;
-    private List<String> patterns;
-    private List<String> excludes;
-    private boolean excludeDirectories;
-    private Predicate<File> pathFilter;
-    private ThreadPoolExecutor threadPool;
-
-    /**
-     * Creates a glob builder with the given base path.
-     */
-    public Builder(File base) {
-      this.base = base;
-      this.patterns = Lists.newArrayList();
-      this.excludes = Lists.newArrayList();
-      this.excludeDirectories = false;
-      this.pathFilter = file -> true;
-    }
-
-    /**
-     * Adds a pattern to include to the glob builder.
-     *
-     * <p>For a description of the syntax of the patterns, see {@link UnixGlob}.
-     */
-    public Builder addPattern(String pattern) {
-      this.patterns.add(pattern);
-      return this;
-    }
-
-    /**
-     * Adds a pattern to include to the glob builder.
-     *
-     * <p>For a description of the syntax of the patterns, see {@link UnixGlob}.
-     */
-    public Builder addPatterns(String... patterns) {
-      Collections.addAll(this.patterns, patterns);
-      return this;
-    }
-
-    /**
-     * Adds a pattern to include to the glob builder.
-     *
-     * <p>For a description of the syntax of the patterns, see {@link UnixGlob}.
-     */
-    public Builder addPatterns(Collection<String> patterns) {
-      this.patterns.addAll(patterns);
-      return this;
-    }
-
-    /**
-     * Adds patterns to exclude from the results to the glob builder.
-     *
-     * <p>For a description of the syntax of the patterns, see {@link UnixGlob}.
-     */
-    public Builder addExcludes(String... excludes) {
-      Collections.addAll(this.excludes, excludes);
-      return this;
-    }
-
-    /**
-     * Adds patterns to exclude from the results to the glob builder.
-     *
-     * <p>For a description of the syntax of the patterns, see {@link UnixGlob}.
-     */
-    public Builder addExcludes(Collection<String> excludes) {
-      this.excludes.addAll(excludes);
-      return this;
-    }
-
-    /**
-     * If set to true, directories are not returned in the glob result.
-     */
-    public Builder setExcludeDirectories(boolean excludeDirectories) {
-      this.excludeDirectories = excludeDirectories;
-      return this;
-    }
-
-
-    /**
-     * Sets the threadpool to use for parallel glob evaluation.
-     * If unset, evaluation is done in-thread.
-     */
-    public Builder setThreadPool(ThreadPoolExecutor pool) {
-      this.threadPool = pool;
-      return this;
-    }
-
-
-    /**
-     * If set, the given predicate is called for every directory
-     * encountered. If it returns false, the corresponding item is not
-     * returned in the output and directories are not traversed either.
-     */
-    public Builder setDirectoryFilter(Predicate<File> pathFilter) {
-      this.pathFilter = pathFilter;
-      return this;
-    }
-
-    /**
-     * Executes the glob.
-     *
-     * @throws InterruptedException if the thread is interrupted.
-     */
-    public List<File> glob() throws IOException, InterruptedException {
-      return globInternal(base, patterns, excludes, excludeDirectories, pathFilter, threadPool);
-    }
-
-    /**
-     * Executes the glob asynchronously. {@link #setThreadPool} must have been called already with a
-     * non-null argument.
-     *
-     * @param checkForInterrupt if the returned future may throw InterruptedException.
-     */
-    public Future<List<File>> globAsync() {
-      return globAsyncInternal(base, patterns, excludes, excludeDirectories, pathFilter, threadPool);
-    }
-  }
-
-  /**
-   * Adapts the result of the glob visitation as a Future.
-   */
-  private static class GlobFuture extends ForwardingListenableFuture<List<File>> {
-    private final GlobVisitor visitor;
-    private final SettableFuture<List<File>> delegate = SettableFuture.create();
-
-    public GlobFuture(GlobVisitor visitor) {
-      this.visitor = visitor;
-    }
-
-    @Override
-    public List<File> get() throws InterruptedException, ExecutionException {
-      return super.get();
-    }
-
-    @Override
-    protected ListenableFuture<List<File>> delegate() {
-      return delegate;
-    }
-
-    public void setException(IOException exception) {
-      delegate.setException(exception);
-    }
-
-    public void set(List<File> paths) {
-      delegate.set(paths);
-    }
-
-    @Override
-    public boolean cancel(boolean mayInterruptIfRunning) {
-      // Best-effort interrupt of the in-flight visitation.
-      visitor.cancel();
-      return true;
-    }
-
-    public void markCanceled() {
-      super.cancel(true);
-    }
-  }
-
-  /**
-   * GlobVisitor executes a glob using parallelism, which is useful when
-   * the glob() requires many readdir() calls on high latency filesystems.
-   */
-  private static final class GlobVisitor {
-    // These collections are used across workers and must therefore be thread-safe.
-    private final Collection<File> results = Sets.newConcurrentHashSet();
-    private final Cache<String, Pattern> cache = CacheBuilder.newBuilder().build(
-      new CacheLoader<String, Pattern>() {
-        @Override
-        public Pattern load(String wildcard) {
-          return makePatternFromWildcard(wildcard);
-        }
-      });
-
-    private final GlobFuture result;
-    private final ThreadPoolExecutor executor;
-    private final AtomicLong pendingOps = new AtomicLong(0);
-    private final AtomicReference<IOException> failure = new AtomicReference<>();
-    private final FileAttributeProvider fileAttributeProvider = FileAttributeProvider.getInstance();
-    private volatile boolean canceled = false;
-
-    public GlobVisitor(ThreadPoolExecutor executor) {
-      this.executor = executor;
-      this.result = new GlobFuture(this);
-    }
-
-    public GlobVisitor() {
-      this(null);
-    }
-
-    /**
-     * Performs wildcard globbing: returns the sorted list of filenames that match any of
-     * {@code patterns} relative to {@code base}, but which do not match {@code excludePatterns}.
-     * Directories are traversed if and only if they match {@code dirPred}. The predicate is also
-     * called for the root of the traversal.
-     *
-     * <p>Patterns may include "*" and "?", but not "[a-z]".
-     *
-     * <p><code>**</code> gets special treatment in include patterns. If it is
-     * used as a complete path segment it matches the filenames in
-     * subdirectories recursively.
-     *
-     * @throws IllegalArgumentException if any glob or exclude pattern
-     *         {@linkplain #checkPatternForError(String) contains errors} or if
-     *         any exclude pattern segment contains <code>**</code> or if any
-     *         include pattern segment contains <code>**</code> but not equal to
-     *         it.
-     */
-    public List<File> glob(File base,
-                           Collection<String> patterns,
-                           Collection<String> excludePatterns,
-                           boolean excludeDirectories,
-                           Predicate<File> dirPred) throws IOException, InterruptedException {
-      try {
-        return globAsync(base, patterns, excludePatterns, excludeDirectories, dirPred).get();
-      } catch (ExecutionException e) {
-        Throwable cause = e.getCause();
-        Throwables.propagateIfPossible(cause, IOException.class);
-        throw new RuntimeException(e);
-      }
-    }
-
-    public Future<List<File>> globAsync(File base, Collection<String> patterns,
-                                        Collection<String> excludePatterns, boolean excludeDirectories,
-                                        Predicate<File> dirPred) throws IOException {
-
-      if (!fileAttributeProvider.exists(base) || patterns.isEmpty()) {
-        return Futures.immediateFuture(Collections.emptyList());
-      }
-      boolean baseIsDirectory = fileAttributeProvider.isDirectory(base);
-
-      List<String[]> splitPatterns = checkAndSplitPatterns(patterns);
-      List<String[]> splitExcludes = checkAndSplitPatterns(excludePatterns);
-
-      // We do a dumb loop, even though it will likely duplicate work
-      // (e.g., readdir calls). In order to optimize, we would need
-      // to keep track of which patterns shared sub-patterns and which did not
-      // (for example consider the glob [*/*.java, sub/*.java, */*.txt]).
-      pendingOps.incrementAndGet();
-      try {
-        for (String[] splitPattern : splitPatterns) {
-          queueGlob(base, baseIsDirectory, splitPattern, 0, excludeDirectories, splitExcludes, 0, results, cache, dirPred);
-        }
-      } finally {
-        decrementAndCheckDone();
-      }
-
-      return result;
-    }
-
-    private void queueGlob(File base,
-                           boolean baseIsDirectory,
-                           String[] patternParts,
-                           int idx,
-                           boolean excludeDirectories,
-                           List<String[]> excludePatterns,
-                           int excludeIdx,
-                           Collection<File> results,
-                           Cache<String, Pattern> cache,
-                           Predicate<File> dirPred) throws IOException {
-      enqueue(() -> {
-        try {
-          reallyGlob(base, baseIsDirectory, patternParts, idx, excludeDirectories,
-                     excludePatterns, excludeIdx, results, cache, dirPred);
-        } catch (IOException e) {
-          failure.set(e);
-        }
-      });
-    }
-
-    protected void enqueue(final Runnable r) {
-      pendingOps.incrementAndGet();
-
-      Runnable wrapped = () -> {
-        try {
-          if (!canceled && failure.get() == null) {
-            r.run();
-          }
-        } finally {
-          decrementAndCheckDone();
-        }
-      };
-
-      if (executor == null) {
-        wrapped.run();
-      } else {
-        executor.execute(wrapped);
-      }
-    }
-
-    protected void cancel() {
-      this.canceled = true;
-    }
-
-    private void decrementAndCheckDone() {
-      if (pendingOps.decrementAndGet() == 0) {
-        // We get to 0 iff we are done all the relevant work. This is because we always increment
-        // the pending ops count as we're enqueuing, and don't decrement until the task is complete
-        // (which includes accounting for any additional tasks that one enqueues).
-        if (canceled) {
-          result.markCanceled();
-        } else if (failure.get() != null) {
-          result.setException(failure.get());
-        } else {
-          result.set(Ordering.<File>natural().immutableSortedCopy(results));
-        }
-      }
-    }
-
-    /**
-     * Expressed in Haskell:
-     * <pre>
-     *  reallyGlob base []     = { base }
-     *  reallyGlob base [x:xs] = union { reallyGlob(f, xs) | f results "base/x" }
-     * </pre>
-     */
-    private void reallyGlob(File base,
-                            boolean baseIsDirectory,
-                            String[] patternParts,
-                            int idx,
-                            boolean excludeDirectories,
-                            List<String[]> excludePatterns,
-                            int excludeIdx,
-                            Collection<File> results,
-                            Cache<String, Pattern> cache,
-                            Predicate<File> dirPred) throws IOException {
-      ProgressManager.checkCanceled();
-      if (baseIsDirectory && !dirPred.test(base)) {
-        return;
-      }
-
-      if (idx == patternParts.length) { // Base case.
-        if (!(excludeDirectories && baseIsDirectory) &&
-            !excludedOnMatch(base, excludePatterns, excludeIdx, cache)) {
-          results.add(base);
-        }
-        return;
-      }
-
-      if (!baseIsDirectory) {
-        // Nothing to find here.
-        return;
-      }
-
-      List<String[]> relevantExcludes = getRelevantExcludes(base, excludePatterns, excludeIdx, cache);
-      final String pattern = patternParts[idx];
-
-      // ** is special: it can match nothing at all.
-      // For example, x/** matches x, **/y matches y, and x/**/y matches x/y.
-      if ("**".equals(pattern)) {
-        queueGlob(base, baseIsDirectory, patternParts, idx + 1, excludeDirectories, excludePatterns, excludeIdx, results, cache, dirPred);
-      }
-
-      if (!pattern.contains("*") && !pattern.contains("?")) {
-        // We do not need to do a readdir in this case, just a stat.
-        File child = new File(base, pattern);
-        boolean childIsDir = fileAttributeProvider.isDirectory(child);
-        if (!childIsDir && !fileAttributeProvider.isFile(child)) {
-          // The file is a dangling symlink, fifo, does not exist, etc.
-          return;
-        }
-
-        queueGlob(child, childIsDir, patternParts, idx + 1, excludeDirectories,
-                  relevantExcludes, excludeIdx + 1, results, cache, dirPred);
-        return;
-      }
-
-      File[] children = getChildren(base);
-      if (children == null) {
-        return;
-      }
-      for (File child : children) {
-        boolean childIsDir = fileAttributeProvider.isDirectory(child);
-
-        if ("**".equals(pattern)) {
-          // Recurse without shifting the pattern.
-          if (childIsDir) {
-            queueGlob(child, childIsDir, patternParts, idx, excludeDirectories,
-                      relevantExcludes, excludeIdx + 1, results, cache, dirPred);
-          }
-        }
-        if (matches(pattern, child.getName(), cache)) {
-          // Recurse and consume one segment of the pattern.
-          if (childIsDir) {
-            queueGlob(child, childIsDir, patternParts, idx + 1, excludeDirectories,
-                      relevantExcludes, excludeIdx + 1, results, cache, dirPred);
-          } else {
-            // Instead of using an async call, just repeat the base case above.
-            if (idx + 1 == patternParts.length &&
-                !excludedOnMatch(child, relevantExcludes, excludeIdx + 1, cache)) {
-              results.add(child);
-            }
-          }
-        }
-      }
-    }
-
-    private static VirtualFileSystem getFileSystem() {
-      if (ApplicationManager.getApplication().isUnitTestMode()) {
-        return TempFileSystem.getInstance();
-      }
-      return LocalFileSystem.getInstance();
-    }
-
-    @Nullable
-    private File[] getChildren(File file) {
-      if (!ApplicationManager.getApplication().isUnitTestMode()) {
-        return file.listFiles();
-      }
-      TempFileSystem fs = TempFileSystem.getInstance();
-      VirtualFile vf = fs.findFileByIoFile(file);
-      VirtualFile[] children = vf.getChildren();
-      if (children == null) {
-        return null;
-      }
-      return Arrays.stream(children)
-        .map(VirtualFile::getPath)
-        .map(File::new)
-        .toArray(File[]::new);
-    }
-  }
-}
\ No newline at end of file
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/highlighting/BuildColorsPage.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/highlighting/BuildColorsPage.java
deleted file mode 100644
index 79d1d2c..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/highlighting/BuildColorsPage.java
+++ /dev/null
@@ -1,112 +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.highlighting;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.idea.blaze.base.lang.buildfile.language.BuildFileType;
-import com.google.idea.blaze.base.settings.Blaze;
-import com.intellij.application.options.colors.InspectionColorSettingsPage;
-import com.intellij.openapi.editor.colors.TextAttributesKey;
-import com.intellij.openapi.fileTypes.SyntaxHighlighter;
-import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory;
-import com.intellij.openapi.options.colors.AttributesDescriptor;
-import com.intellij.openapi.options.colors.ColorDescriptor;
-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 javax.swing.*;
-import java.util.Map;
-
-/**
- * Allows user to customize colors.
- */
-public class BuildColorsPage implements ColorSettingsPage, InspectionColorSettingsPage, DisplayPrioritySortable {
-  private static final AttributesDescriptor[] ATTRS = new AttributesDescriptor[] {
-    new AttributesDescriptor("Keyword", BuildSyntaxHighlighter.BUILD_KEYWORD),
-    new AttributesDescriptor("String", BuildSyntaxHighlighter.BUILD_STRING),
-    new AttributesDescriptor("Number", BuildSyntaxHighlighter.BUILD_NUMBER),
-    new AttributesDescriptor("Line Comment", BuildSyntaxHighlighter.BUILD_LINE_COMMENT),
-    new AttributesDescriptor("Operation Sign", BuildSyntaxHighlighter.BUILD_OPERATION_SIGN),
-    new AttributesDescriptor("Parentheses", BuildSyntaxHighlighter.BUILD_PARENS),
-    new AttributesDescriptor("Brackets", BuildSyntaxHighlighter.BUILD_BRACKETS),
-    new AttributesDescriptor("Braces", BuildSyntaxHighlighter.BUILD_BRACES),
-    new AttributesDescriptor("Comma", BuildSyntaxHighlighter.BUILD_COMMA),
-    new AttributesDescriptor("Dot", BuildSyntaxHighlighter.BUILD_DOT),
-    new AttributesDescriptor("Function definition", BuildSyntaxHighlighter.BUILD_FN_DEFINITION),
-    new AttributesDescriptor("Parameter", BuildSyntaxHighlighter.BUILD_PARAMETER),
-    new AttributesDescriptor("Keyword argument", BuildSyntaxHighlighter.BUILD_KEYWORD_ARG),
-  };
-
-  private static final Map<String,TextAttributesKey> ourTagToDescriptorMap = ImmutableMap.<String, TextAttributesKey>builder()
-    .put("funcDef", BuildSyntaxHighlighter.BUILD_FN_DEFINITION)
-    .put("param", BuildSyntaxHighlighter.BUILD_PARAMETER)
-    .put("kwarg", BuildSyntaxHighlighter.BUILD_KEYWORD_ARG)
-    .put("comma", BuildSyntaxHighlighter.BUILD_COMMA)
-    .put("number", BuildSyntaxHighlighter.BUILD_NUMBER)
-    .build();
-
-  @Override
-  public String getDisplayName() {
-    return Blaze.defaultBuildSystemName() + " BUILD files";
-  }
-
-  @Override
-  public Icon getIcon() {
-    return BuildFileType.INSTANCE.getIcon();
-  }
-
-  @Override
-  public AttributesDescriptor[] getAttributeDescriptors() {
-    return ATTRS;
-  }
-
-  @Override
-  public ColorDescriptor[] getColorDescriptors() {
-    return ColorDescriptor.EMPTY_ARRAY;
-  }
-
-  @Override
-  public SyntaxHighlighter getHighlighter() {
-    final SyntaxHighlighter highlighter = SyntaxHighlighterFactory.getSyntaxHighlighter(BuildFileType.INSTANCE, null, null);
-    assert highlighter != null;
-    return highlighter;
-  }
-
-  @Override
-  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"+
-        "\n"+
-        "java_library(\n" +
-        "    <kwarg>name</kwarg> = \"lib\",\n" +
-        "    <kwarg>srcs</kwarg> = glob([\"**/*.java\"]),\n" +
-        ")\n";
-  }
-
-  @Override
-  public Map<String, TextAttributesKey> getAdditionalHighlightingTagToDescriptorMap() {
-    return ourTagToDescriptorMap;
-  }
-
-  @Override
-  public DisplayPriority getPriority() {
-    return PlatformUtils.isPyCharm() ? DisplayPriority.KEY_LANGUAGE_SETTINGS : DisplayPriority.LANGUAGE_SETTINGS;
-  }
-}
\ No newline at end of file
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/highlighting/BuildSyntaxHighlighter.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/highlighting/BuildSyntaxHighlighter.java
deleted file mode 100644
index 537027c..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/highlighting/BuildSyntaxHighlighter.java
+++ /dev/null
@@ -1,97 +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.highlighting;
-
-import com.google.common.collect.Maps;
-import com.google.idea.blaze.base.lang.buildfile.lexer.BuildLexer;
-import com.google.idea.blaze.base.lang.buildfile.lexer.BuildLexerBase;
-import com.google.idea.blaze.base.lang.buildfile.lexer.BuildToken;
-import com.google.idea.blaze.base.lang.buildfile.lexer.TokenKind;
-import com.intellij.lexer.Lexer;
-import com.intellij.openapi.editor.colors.TextAttributesKey;
-import com.intellij.openapi.fileTypes.SyntaxHighlighterBase;
-import com.intellij.psi.tree.IElementType;
-
-import java.util.Map;
-
-import static com.intellij.openapi.editor.DefaultLanguageHighlighterColors.*;
-
-/**
- * This class maps tokens to highlighting attributes. Each attribute contains the font properties.
- */
-public class BuildSyntaxHighlighter extends SyntaxHighlighterBase {
-
-  public static final TextAttributesKey BUILD_KEYWORD = key("BUILD.MODIFIER", KEYWORD);
-  public static final TextAttributesKey BUILD_STRING = key("BUILD.STRING", STRING);
-  public static final TextAttributesKey BUILD_NUMBER = key("BUILD.NUMBER", NUMBER);
-  public static final TextAttributesKey BUILD_LINE_COMMENT = key("BUILD.LINE_COMMENT", LINE_COMMENT);
-  public static final TextAttributesKey BUILD_BRACES = key("BUILD.BRACES", BRACES);
-  public static final TextAttributesKey BUILD_PARENS = key("BUILD.PARENS", PARENTHESES);
-  public static final TextAttributesKey BUILD_BRACKETS = key("BUILD.BRACKETS", BRACKETS);
-  public static final TextAttributesKey BUILD_OPERATION_SIGN = key("BUILD.OPERATION_SIGN", OPERATION_SIGN);
-  public static final TextAttributesKey BUILD_DOT = key("BUILD.DOT", DOT);
-  public static final TextAttributesKey BUILD_SEMICOLON = key("BUILD.SEMICOLON", SEMICOLON);
-  public static final TextAttributesKey BUILD_COMMA = key("BUILD.COMMA", COMMA);
-  public static final TextAttributesKey BUILD_PARAMETER = key("BUILD.PARAMETER", PARAMETER);
-  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);
-
-  private static TextAttributesKey key(String name, TextAttributesKey fallbackKey) {
-    return TextAttributesKey.createTextAttributesKey(name, fallbackKey);
-  }
-
-  private static final Map<IElementType, TextAttributesKey> keys = Maps.newHashMap();
-
-  static {
-    addAttribute(TokenKind.COMMENT, BUILD_LINE_COMMENT);
-    addAttribute(TokenKind.INT, BUILD_NUMBER);
-    addAttribute(TokenKind.STRING, BUILD_STRING);
-
-    addAttribute(TokenKind.LBRACE, BUILD_BRACES);
-    addAttribute(TokenKind.RBRACE, BUILD_BRACES);
-    addAttribute(TokenKind.LBRACKET, BUILD_BRACKETS);
-    addAttribute(TokenKind.RBRACKET, BUILD_BRACKETS);
-
-    addAttribute(TokenKind.LPAREN, BUILD_PARENS);
-    addAttribute(TokenKind.RPAREN, BUILD_PARENS);
-
-    addAttribute(TokenKind.DOT, BUILD_DOT);
-    addAttribute(TokenKind.SEMI, BUILD_SEMICOLON);
-    addAttribute(TokenKind.COMMA, BUILD_COMMA);
-
-    for (TokenKind kind : TokenKind.OPERATIONS) {
-      addAttribute(kind, BUILD_OPERATION_SIGN);
-    }
-
-    for (TokenKind kind : TokenKind.KEYWORDS) {
-      addAttribute(kind, BUILD_KEYWORD);
-    }
-  }
-
-  private static void addAttribute(TokenKind kind, TextAttributesKey key) {
-    keys.put(BuildToken.fromKind(kind), key);
-  }
-
-  @Override
-  public Lexer getHighlightingLexer() {
-    return new BuildLexer(BuildLexerBase.LexerMode.SyntaxHighlighting);
-  }
-
-  @Override
-  public TextAttributesKey[] getTokenHighlights(IElementType iElementType) {
-    return pack(keys.get(iElementType));
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/highlighting/BuildSyntaxHighlighterFactory.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/highlighting/BuildSyntaxHighlighterFactory.java
deleted file mode 100644
index a1ef7d5..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/highlighting/BuildSyntaxHighlighterFactory.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.base.lang.buildfile.highlighting;
-
-import com.intellij.openapi.fileTypes.SyntaxHighlighter;
-import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.vfs.VirtualFile;
-
-/**
- * Factory for BuildSyntaxHighlighter
- */
-public class BuildSyntaxHighlighterFactory extends SyntaxHighlighterFactory {
-
-  @Override
-  public SyntaxHighlighter getSyntaxHighlighter(Project project, VirtualFile virtualFile) {
-    return new BuildSyntaxHighlighter();
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/language/BuildFileLanguage.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/language/BuildFileLanguage.java
deleted file mode 100644
index 46c0b85..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/language/BuildFileLanguage.java
+++ /dev/null
@@ -1,49 +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.language;
-
-import com.google.idea.blaze.base.experiments.BoolExperiment;
-import com.google.idea.blaze.base.settings.BlazeUserSettings;
-import com.intellij.lang.Language;
-
-/**
- * BUILD file language
- */
-public class BuildFileLanguage extends Language {
-
-  public static final BoolExperiment BUILD_FILE_SUPPORT_ENABLED = new BoolExperiment("build.file.support.enabled", true);
-
-  public static boolean buildFileSupportEnabled() {
-    return BUILD_FILE_SUPPORT_ENABLED.getValue() && BlazeUserSettings.getInstance().getBuildFileSupportEnabled();
-  }
-
-  public static final BuildFileLanguage INSTANCE = new BuildFileLanguage();
-
-  private BuildFileLanguage() {
-    super("BUILD", "text/python");
-  }
-
-  @Override
-  public String getDisplayName() {
-    return "BUILD file";
-  }
-
-  @Override
-  public boolean isCaseSensitive() {
-    return true;
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/language/BuildFileType.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/language/BuildFileType.java
deleted file mode 100644
index c09c8b8..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/language/BuildFileType.java
+++ /dev/null
@@ -1,58 +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.language;
-
-import com.google.idea.blaze.base.settings.Blaze;
-import com.intellij.openapi.fileTypes.LanguageFileType;
-import icons.BlazeIcons;
-
-import javax.annotation.Nullable;
-import javax.swing.*;
-
-/**
- * BUILD file type
- */
-public class BuildFileType extends LanguageFileType {
-
-  public static final BuildFileType INSTANCE = new BuildFileType();
-
-  private BuildFileType() {
-    super(BuildFileLanguage.INSTANCE);
-  }
-
-  @Override
-  public String getName() {
-    // Warning: this is conflated with Language.myID in several places...
-    // They must be identical.
-    return BuildFileLanguage.INSTANCE.getID();
-  }
-
-  @Override
-  public String getDescription() {
-    return Blaze.defaultBuildSystemName() + " BUILD language";
-  }
-
-  @Override
-  public String getDefaultExtension() {
-    return "";
-  }
-
-  @Override
-  @Nullable
-  public Icon getIcon() {
-    return BlazeIcons.BuildFile;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/language/BuildFileTypeFactory.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/language/BuildFileTypeFactory.java
deleted file mode 100644
index 864a036..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/language/BuildFileTypeFactory.java
+++ /dev/null
@@ -1,52 +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.language;
-
-import com.google.common.collect.ImmutableList;
-import com.intellij.openapi.fileTypes.*;
-import com.intellij.openapi.fileTypes.ex.FileTypeManagerEx;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Factory for BuildFileType
- */
-public class BuildFileTypeFactory extends FileTypeFactory {
-
-  private static ImmutableList<FileNameMatcher> DEFAULT_ASSOCIATIONS = ImmutableList.of(
-    new ExactFileNameMatcher("BUILD"),
-    new ExtensionFileNameMatcher("bzl")
-  );
-
-  @Override
-  public void createFileTypes(@NotNull final FileTypeConsumer consumer) {
-    consumer.consume(BuildFileType.INSTANCE, DEFAULT_ASSOCIATIONS.toArray(new FileNameMatcher[0]));
-  }
-
-  private static volatile boolean enabled = true;
-
-  public static void updateBuildFileLanguageEnabled(boolean supportEnabled) {
-    if (enabled == supportEnabled) {
-      return;
-    }
-    enabled = supportEnabled;
-    if (!supportEnabled) {
-      FileTypeManagerEx.getInstanceEx().unregisterFileType(BuildFileType.INSTANCE);
-    } else {
-      FileTypeManager.getInstance().registerFileType(BuildFileType.INSTANCE, DEFAULT_ASSOCIATIONS);
-    }
-  }
-
-}
\ No newline at end of file
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/language/semantics/AttributeDefinition.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/language/semantics/AttributeDefinition.java
deleted file mode 100644
index 3907a58..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/language/semantics/AttributeDefinition.java
+++ /dev/null
@@ -1,78 +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.language.semantics;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.ImmutableList;
-import com.google.repackaged.devtools.build.lib.query2.proto.proto2api.Build;
-
-import javax.annotation.Nullable;
-import java.io.Serializable;
-
-/**
- * Simple implementation of AttributeDefinition, from build.proto
- */
-public class AttributeDefinition implements Serializable {
-
-  public static AttributeDefinition fromProto(Build.AttributeDefinition attr) {
-    return new AttributeDefinition(
-      attr.getName(),
-      attr.getType(),
-      attr.getMandatory(),
-      attr.hasDocumentation() ? attr.getDocumentation() : null,
-      getAllowedRuleClasses(attr)
-    );
-  }
-
-  @Nullable
-  private static ImmutableList<String> getAllowedRuleClasses(Build.AttributeDefinition attr) {
-    if (!attr.hasAllowedRuleClasses()) {
-      return null;
-    }
-    return ImmutableList.copyOf(attr.getAllowedRuleClasses().getAllowedRuleClassList());
-  }
-
-  public final String name;
-  public final Build.Attribute.Discriminator type;
-  public final boolean mandatory;
-  public final String documentation;
-
-  // the names of rules allowed in this attribute, or null if any rules are allowed.
-  @Nullable private final ImmutableList<String> allowedRuleClasses;
-
-  @VisibleForTesting
-  public AttributeDefinition(
-    String name,
-    Build.Attribute.Discriminator type,
-    boolean mandatory,
-    String documentation,
-    @Nullable ImmutableList<String> allowedRuleClasses) {
-
-    this.name = name;
-    this.type = type;
-    this.mandatory = mandatory;
-    this.documentation = documentation;
-    this.allowedRuleClasses = allowedRuleClasses;
-  }
-
-  /**
-   * Only relevant for attributes of type LABEL and LABEL_LIST.
-   * Some such attributes can only contain certain rule types.
-   */
-  public boolean isRuleTypeAllowed(RuleDefinition rule) {
-    return allowedRuleClasses == null || allowedRuleClasses.contains(rule.name);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/language/semantics/BuildLanguageSpec.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/language/semantics/BuildLanguageSpec.java
deleted file mode 100644
index 7c2b14e..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/language/semantics/BuildLanguageSpec.java
+++ /dev/null
@@ -1,65 +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.language.semantics;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.repackaged.devtools.build.lib.query2.proto.proto2api.Build;
-
-import javax.annotation.Nullable;
-import java.io.Serializable;
-
-/**
- * Specification of the BUILD language, as provided by "blaze info build-language".<p>
- *
- * This constitutes a set of rules, along with their supported attributes, and other
- * useful information. We query this once per blaze workspace (it won't change unless
- * the blaze binary is also changed).<p>
- *
- * This rule list is not exhaustive; it's intended to give information about known
- * rules, not enumerate all possibilities.
- */
-public class BuildLanguageSpec implements Serializable {
-
-  public static BuildLanguageSpec fromProto(Build.BuildLanguage proto) {
-    ImmutableMap.Builder<String, RuleDefinition> builder = ImmutableMap.builder();
-    for (Build.RuleDefinition rule : proto.getRuleList()) {
-      builder.put(rule.getName(), RuleDefinition.fromProto(rule));
-    }
-    return new BuildLanguageSpec(builder.build());
-  }
-
-  public final ImmutableMap<String, RuleDefinition> rules;
-
-  @VisibleForTesting
-  public BuildLanguageSpec(ImmutableMap<String, RuleDefinition> rules) {
-    this.rules = rules;
-  }
-
-  public ImmutableSet<String> getKnownRuleNames() {
-    return rules.keySet();
-  }
-
-  public boolean hasRule(@Nullable String ruleName) {
-    return getRule(ruleName) != null;
-  }
-
-  @Nullable
-  public RuleDefinition getRule(@Nullable String ruleName) {
-    return ruleName != null ? rules.get(ruleName) : null;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/language/semantics/BuildLanguageSpecProvider.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/language/semantics/BuildLanguageSpecProvider.java
deleted file mode 100644
index 984c286..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/language/semantics/BuildLanguageSpecProvider.java
+++ /dev/null
@@ -1,39 +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.language.semantics;
-
-import com.intellij.openapi.components.ServiceManager;
-import com.intellij.openapi.project.Project;
-
-import javax.annotation.Nullable;
-
-/**
- * Provides a BuildLanguageSpec for the given project.
- */
-public interface BuildLanguageSpecProvider {
-
-  static BuildLanguageSpecProvider getInstance() {
-    return ServiceManager.getService(BuildLanguageSpecProvider.class);
-  }
-
-  /**
-   * Returns null if cancelled or unsuccessful. Also returns null if no WorkspaceRoot can be found for the
-   * project (i.e. because BlazeImportSettings has not been set).
-   */
-  @Nullable
-  BuildLanguageSpec getLanguageSpec(Project project);
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/language/semantics/BuildLanguageSpecProviderImpl.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/language/semantics/BuildLanguageSpecProviderImpl.java
deleted file mode 100644
index eae6d8c..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/language/semantics/BuildLanguageSpecProviderImpl.java
+++ /dev/null
@@ -1,52 +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.language.semantics;
-
-import com.google.common.collect.Maps;
-import com.google.idea.blaze.base.lang.buildfile.sync.LanguageSpecResult;
-import com.google.idea.blaze.base.model.BlazeProjectData;
-import com.google.idea.blaze.base.projectview.ProjectViewSet;
-import com.google.idea.blaze.base.settings.BlazeImportSettings;
-import com.google.idea.blaze.base.sync.SyncListener;
-import com.intellij.openapi.project.Project;
-
-import java.util.Map;
-
-/**
- * Calls 'blaze info build-language', to retrieve the language spec.
- */
-public class BuildLanguageSpecProviderImpl extends SyncListener.Adapter implements BuildLanguageSpecProvider {
-
-  private static final Map<Project, LanguageSpecResult> calculatedSpecs = Maps.newHashMap();
-
-  @Override
-  public BuildLanguageSpec getLanguageSpec(Project project) {
-    LanguageSpecResult result = calculatedSpecs.get(project);
-    return result != null ? result.spec : null;
-  }
-
-  @Override
-  public void onSyncComplete(Project project,
-                             BlazeImportSettings importSettings,
-                             ProjectViewSet projectViewSet,
-                             BlazeProjectData blazeProjectData) {
-    LanguageSpecResult spec = blazeProjectData.syncState.get(LanguageSpecResult.class);
-    if (spec != null) {
-      calculatedSpecs.put(project, spec);
-    }
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/language/semantics/RuleDefinition.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/language/semantics/RuleDefinition.java
deleted file mode 100644
index 6f93ea1..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/language/semantics/RuleDefinition.java
+++ /dev/null
@@ -1,70 +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.language.semantics;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.repackaged.devtools.build.lib.query2.proto.proto2api.Build;
-
-import javax.annotation.Nullable;
-import java.io.Serializable;
-
-/**
- * Simple implementation of RuleDefinition, from build.proto
- */
-public class RuleDefinition implements Serializable {
-
-  /**
-   * This isn't included in the proto -- all other documented attributes seem to be.
-   */
-  private static final AttributeDefinition NAME_ATTRIBUTE = new AttributeDefinition(
-    "name", Build.Attribute.Discriminator.STRING, true, null, null);
-
-  public static RuleDefinition fromProto(Build.RuleDefinition rule) {
-    ImmutableMap.Builder<String, AttributeDefinition> map = ImmutableMap.builder();
-    for (Build.AttributeDefinition attr : rule.getAttributeList()) {
-      map.put(attr.getName(), AttributeDefinition.fromProto(attr));
-    }
-    map.put(NAME_ATTRIBUTE.name, NAME_ATTRIBUTE);
-    return new RuleDefinition(
-      rule.getName(),
-      map.build(),
-      rule.hasDocumentation() ? rule.getDocumentation() : null);
-  }
-
-  public final String name;
-  /**
-   * This map is not exhaustive; it only contains documented attributes.
-   */
-  public final ImmutableMap<String, AttributeDefinition> attributes;
-  @Nullable public final String documentation;
-
-  public RuleDefinition(String name, ImmutableMap<String, AttributeDefinition> attributes, @Nullable String documentation) {
-    this.name = name;
-    this.attributes = attributes;
-    this.documentation = documentation;
-  }
-
-  public ImmutableSet<String> getKnownAttributeNames() {
-    return attributes.keySet();
-  }
-
-  @Nullable
-  public AttributeDefinition getAttribute(@Nullable String attributeName) {
-    return attributeName != null ? attributes.get(attributeName) : null;
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/lexer/BuildLexer.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/lexer/BuildLexer.java
deleted file mode 100644
index 2ac477c..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/lexer/BuildLexer.java
+++ /dev/null
@@ -1,131 +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.lexer;
-
-import com.google.idea.blaze.base.lang.buildfile.lexer.BuildLexerBase.LexerMode;
-import com.intellij.lexer.LexerBase;
-
-import java.util.Iterator;
-import java.util.List;
-
-/**
- * Implementation of LexerBase using BuildLexerBase to tokenize the input.
- */
-public class BuildLexer extends LexerBase {
-
-  private final LexerMode mode;
-
-  private int offsetEnd;
-  private int offsetStart;
-  private CharSequence buffer;
-  private Iterator<Token> tokens;
-  private Token currentToken;
-  private int currentState;
-
-  public BuildLexer(LexerMode mode) {
-    this.mode = mode;
-  }
-
-  @Override
-  public void start(CharSequence charSequence, int startOffset, int endOffset, int initialState) {
-    buffer = charSequence;
-    this.offsetEnd = endOffset;
-    this.offsetStart = startOffset;
-
-    BuildLexerBase lexer = new BuildLexerBase(charSequence.subSequence(startOffset, endOffset), initialState, mode);
-    checkNoCharactersMissing(charSequence.subSequence(startOffset, endOffset).length(), lexer.getTokens());
-    tokens = lexer.getTokens().iterator();
-    currentToken = null;
-    if (tokens.hasNext()) {
-      currentToken = tokens.next();
-    }
-    currentState = lexer.getOpenParenStackDepth();
-  }
-
-  /**
-   * Temporary debugging code. We need to tokenize every character in the input string.
-   */
-  private void checkNoCharactersMissing(int totalLength, List<Token> tokens) {
-    if (!tokens.isEmpty() && tokens.get(tokens.size() - 1).right != totalLength) {
-      String error = String.format("Lengths don't match: %s instead of %s",
-        tokens.get(tokens.size() - 1).right,
-        totalLength);
-      throw new RuntimeException(error);
-    }
-    int start = 0;
-    for (int i = 0; i < tokens.size(); i++) {
-      Token token = tokens.get(i);
-      if (token.left != start) {
-        throw new RuntimeException("Gap/inconsistency at: " + start);
-      }
-      start = token.right;
-    }
-  }
-
-
-  @Override
-  public int getState() {
-    return currentState;
-  }
-
-  @Override
-  public BuildToken getTokenType() {
-    if (currentToken != null) {
-      return BuildToken.fromKind(currentToken.kind);
-    }
-    return null;
-  }
-
-  @Override
-  public int getTokenStart() {
-    if (currentToken == null) {
-      return 0;
-    }
-    return currentToken.left + offsetStart;
-  }
-
-  @Override
-  public int getTokenEnd() {
-    if (currentToken == null) {
-      return 0;
-    }
-    return currentToken.right + offsetStart;
-  }
-
-  @Override
-  public void advance() {
-    if (tokens.hasNext()) {
-      currentToken = tokens.next();
-    } else {
-      currentToken = null;
-    }
-  }
-
-  public TokenKind getTokenKind() {
-    return currentToken.kind;
-  }
-
-  @Override
-  public CharSequence getBufferSequence() {
-    return buffer;
-  }
-
-  @Override
-  public int getBufferEnd() {
-    return offsetEnd;
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/lexer/BuildLexerBase.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/lexer/BuildLexerBase.java
deleted file mode 100644
index a71c884..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/lexer/BuildLexerBase.java
+++ /dev/null
@@ -1,755 +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.lexer;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Lists;
-
-import javax.annotation.Nullable;
-import java.util.List;
-import java.util.Map;
-import java.util.Stack;
-
-/**
- * A tokenizer for the BUILD language.
- *
- * Copied from blaze/bazel's lexer. The differences are:
- * 1. Blaze's lexer isn't 'faithful', in that it reorders characters, skips characters, and adds
- * ghost characters. We can't do that, because we need to match the editor's view of the document.
- * 2. Blaze's lexer only lexes entire files (it can't incrementally lex part of a file, starting
- * from a given indent stack depth).
- */
-public class BuildLexerBase {
-
-  /**
-   * When tokenizing for the purposes of parsing, we handle indentation.<br>
-   * For syntax highlighting, we need to tokenize every character, and don't care about indentation.
-   */
-  public enum LexerMode {
-    Parsing,
-    SyntaxHighlighting
-  }
-
-  // Characters that can come immediately prior to an '=' character to generate
-  // a different token
-  private static final Map<Character, TokenKind> EQUAL_TOKENS =
-    ImmutableMap.<Character, TokenKind>builder()
-      .put('=', TokenKind.EQUALS_EQUALS)
-      .put('!', TokenKind.NOT_EQUALS)
-      .put('>', TokenKind.GREATER_EQUALS)
-      .put('<', TokenKind.LESS_EQUALS)
-      .put('+', TokenKind.PLUS_EQUALS)
-      .put('-', TokenKind.MINUS_EQUALS)
-      .put('*', TokenKind.STAR_EQUALS)
-      .put('/', TokenKind.SLASH_EQUALS)
-      .put('%', TokenKind.PERCENT_EQUALS)
-      .build();
-
-  private final LexerMode mode;
-
-  // Input buffer and position
-  private final char[] buffer;
-  private int pos;
-
-  private final List<Token> tokens;
-
-  // The number of unclosed open-parens ("(", '{', '[') at the current point in
-  // the stream. Whitespace is handled differently when this is nonzero.
-  private int openParenStackDepth = 0;
-
-  // The stack of enclosing indentation levels; always contains '0' at the
-  // bottom.
-  private final Stack<Integer> indentStack = new Stack<>();
-
-  private boolean containsErrors;
-
-  /**
-   * Constructs a lexer which tokenizes the contents of the specified
-   * InputBuffer. Any errors during lexing are reported on "handler".
-   */
-  public BuildLexerBase(CharSequence input, int initialStackDepth, LexerMode mode) {
-    this.buffer = input.toString().toCharArray();
-    // Empirical measurements show roughly 1 token per 8 characters in buffer.
-    this.tokens = Lists.newArrayListWithExpectedSize(buffer.length / 8);
-    this.pos = 0;
-    this.openParenStackDepth = initialStackDepth;
-    this.mode = mode;
-
-    indentStack.push(0);
-    tokenize();
-  }
-
-  /**
-   * The number of unclosed open-parens ("(", '{', '[') at the end of this string.
-   */
-  public int getOpenParenStackDepth() {
-    return openParenStackDepth;
-  }
-
-  /**
-   * Returns true if there were errors during scanning of this input file or
-   * string. The BuildLexerBase may attempt to recover from errors, but clients should
-   * not rely on the results of scanning if this flag is set.
-   */
-  public boolean containsErrors() {
-    return containsErrors;
-  }
-
-  /**
-   * Returns the (mutable) list of tokens generated by the BuildLexerBase.
-   */
-  public List<Token> getTokens() {
-    return tokens;
-  }
-
-  private void popParen() {
-    if (openParenStackDepth == 0) {
-      error("indentation error");
-    } else {
-      openParenStackDepth--;
-    }
-  }
-
-  private void error(String message) {
-    error(message, pos - 1, pos - 1);
-  }
-
-  protected void error(String message, int start, int end)  {
-    this.containsErrors = true;
-  }
-
-  /** invariant: symbol positions are half-open intervals. */
-  private void addToken(TokenKind kind, int left, int right) {
-    addToken(kind, left, right, null);
-  }
-
-  private void addToken(TokenKind kind, int left, int right, @Nullable Object value) {
-    tokens.add(new Token(kind, left, right, value));
-  }
-
-  /**
-   * Parses an end-of-line sequence, handling statement indentation correctly.
-   *
-   * <p>UNIX newlines are assumed (LF). Carriage returns are always ignored.
-   *
-   * <p>ON ENTRY: 'pos' is the index of the char after '\n'.
-   * ON EXIT: 'pos' is the index of the next non-space char after '\n'.
-   */
-  protected void newline() {
-    if (mode == LexerMode.SyntaxHighlighting) {
-      addToken(TokenKind.NEWLINE, pos - 1, pos);
-      return;
-    }
-    if (openParenStackDepth > 0) {
-      newlineInsideExpression(); // in an expression: ignore space
-    } else {
-      newlineOutsideExpression(); // generate NEWLINE/INDENT/DEDENT tokens
-    }
-  }
-
-  private void newlineInsideExpression() {
-    int oldPos = pos - 1;
-    while (pos < buffer.length) {
-      switch (buffer[pos]) {
-        case ' ': case '\t': case '\r':
-          pos++;
-          break;
-        default:
-          // ignored by the parser
-          addToken(TokenKind.WHITESPACE, oldPos, pos);
-          return;
-      }
-    }
-    addToken(TokenKind.WHITESPACE, oldPos, pos);
-  }
-
-  /**
-   * Handle INDENT and DEDENT within statements.<p>
-   * Note these tokens have zero length -- this is because we can have an arbitrary number of
-   * dedents squeezed into some number of characters, and the size of all the lexical elements
-   * must match the number of characters in the file.
-   */
-  private void newlineOutsideExpression() {
-    int oldPos = pos - 1;
-    if (pos > 1) { // skip over newline at start of file
-      addToken(TokenKind.NEWLINE, oldPos, pos);
-      oldPos = pos;
-    }
-
-    // we're in a stmt: suck up space at beginning of next line
-    int indentLen = 0;
-    while (pos < buffer.length) {
-      char c = buffer[pos];
-      if (c == ' ') {
-        indentLen++;
-        pos++;
-      } else if (c == '\t') {
-        indentLen += 8 - indentLen % 8;
-        pos++;
-      } else if (c == '\n') { // entirely blank line: ignore
-        indentLen = 0;
-        pos++;
-      } else if (c == '#') { // line containing only indented comment
-        if (oldPos != pos) {
-          addToken(TokenKind.WHITESPACE, oldPos, pos);
-          oldPos = pos;
-        }
-        while (pos < buffer.length && c != '\n') {
-          c = buffer[pos++];
-        }
-        addToken(TokenKind.COMMENT, oldPos, pos - 1, bufferSlice(oldPos, pos - 1));
-        oldPos = pos - 1;
-        indentLen = 0;
-      } else { // printing character
-        break;
-      }
-    }
-
-    if (oldPos != pos) {
-      addToken(TokenKind.WHITESPACE, oldPos, pos);
-    }
-    if (pos == buffer.length) {
-      indentLen = 0;
-    } // trailing space on last line
-
-    int peekedIndent = indentStack.peek();
-    if (peekedIndent < indentLen) { // push a level
-      indentStack.push(indentLen);
-      addToken(TokenKind.INDENT, pos, pos);
-
-    } else if (peekedIndent > indentLen) { // pop one or more levels
-      while (peekedIndent > indentLen) {
-        indentStack.pop();
-        addToken(TokenKind.DEDENT, pos, pos);
-        peekedIndent = indentStack.peek();
-      }
-
-      if (peekedIndent < indentLen) {
-        error("indentation error");
-      }
-    }
-  }
-
-  /**
-   * Collapse adjacent whitespace characters into a single token
-   */
-  private void addWhitespace() {
-    int oldPos = pos - 1;
-    while (pos < buffer.length) {
-      switch (buffer[pos]) {
-        case ' ': case '\t': case '\r':
-          pos++;
-          break;
-        default:
-          addToken(TokenKind.WHITESPACE, oldPos, pos, bufferSlice(oldPos, pos));
-          return;
-      }
-    }
-    addToken(TokenKind.WHITESPACE, oldPos, pos, bufferSlice(oldPos, pos));
-  }
-
-  /**
-   * Returns true if current position is in the middle of a triple quote
-   * delimiter (3 x quot), and advances 'pos' by two if so.
-   */
-  private boolean skipTripleQuote(char quot) {
-    if (pos + 1 < buffer.length && buffer[pos] == quot && buffer[pos + 1] == quot) {
-      pos += 2;
-      return true;
-    } else {
-      return false;
-    }
-  }
-
-  /**
-   * Scans a string literal delimited by 'quot', containing escape sequences.
-   *
-   * <p>ON ENTRY: 'pos' is 1 + the index of the first delimiter
-   * ON EXIT: 'pos' is 1 + the index of the last delimiter.
-   */
-  private void escapedStringLiteral(char quot, boolean isRaw) {
-    int oldPos = isRaw ? pos - 2 : pos - 1;
-    boolean inTripleQuote = skipTripleQuote(quot);
-
-    // more expensive second choice that expands escaped into a buffer
-    StringBuilder literal = new StringBuilder();
-    while (pos < buffer.length) {
-      char c = buffer[pos];
-      pos++;
-      switch (c) {
-        case '\n':
-          if (inTripleQuote) {
-            literal.append(c);
-            break;
-          } else {
-            error("unterminated string literal at eol", oldPos, pos);
-            addToken(TokenKind.STRING, oldPos, pos, literal.toString());
-            newline();
-            return;
-          }
-        case '\\':
-          if (pos == buffer.length) {
-            error("unterminated string literal at eof", oldPos, pos);
-            addToken(TokenKind.STRING, oldPos, pos, literal.toString());
-            return;
-          }
-          if (isRaw) {
-            // Insert \ and the following character.
-            // As in Python, it means that a raw string can never end with a single \.
-            literal.append('\\');
-            literal.append(buffer[pos]);
-            pos++;
-            break;
-          }
-          c = buffer[pos];
-          pos++;
-          switch (c) {
-            case '\n':
-              // ignore end of line character
-              break;
-            case 'n':
-              literal.append('\n');
-              break;
-            case 'r':
-              literal.append('\r');
-              break;
-            case 't':
-              literal.append('\t');
-              break;
-            case '\\':
-              literal.append('\\');
-              break;
-            case '\'':
-              literal.append('\'');
-              break;
-            case '"':
-              literal.append('"');
-              break;
-            case '0': case '1': case '2': case '3':
-            case '4': case '5': case '6': case '7': { // octal escape
-              int octal = c - '0';
-              if (pos < buffer.length) {
-                c = buffer[pos];
-                if (c >= '0' && c <= '7') {
-                  pos++;
-                  octal = (octal << 3) | (c - '0');
-                  if (pos < buffer.length) {
-                    c = buffer[pos];
-                    if (c >= '0' && c <= '7') {
-                      pos++;
-                      octal = (octal << 3) | (c - '0');
-                    }
-                  }
-                }
-              }
-              literal.append((char) (octal & 0xff));
-              break;
-            }
-            case 'a': case 'b': case 'f': case 'N': case 'u': case 'U': case 'v': case 'x':
-              // exists in Python but not implemented in Blaze => error
-              error("escape sequence not implemented: \\" + c, oldPos, pos);
-              break;
-            default:
-              // unknown char escape => "\literal"
-              literal.append('\\');
-              literal.append(c);
-              break;
-          }
-          break;
-        case '\'':
-        case '"':
-          if (c != quot || (inTripleQuote && !skipTripleQuote(quot))) {
-            // Non-matching quote, treat it like a regular char.
-            literal.append(c);
-          } else {
-            // Matching close-delimiter, all done.
-            addToken(TokenKind.STRING, oldPos, pos, literal.toString());
-            return;
-          }
-          break;
-        default:
-          literal.append(c);
-          break;
-      }
-    }
-    error("unterminated string literal at eof", oldPos, pos);
-    addToken(TokenKind.STRING, oldPos, pos, literal.toString());
-  }
-
-  /**
-   * Scans a string literal delimited by 'quot'.
-   *
-   * <ul>
-   * <li> ON ENTRY: 'pos' is 1 + the index of the first delimiter
-   * <li> ON EXIT: 'pos' is 1 + the index of the last delimiter.
-   * </ul>
-   *
-   * @param isRaw if true, do not escape the string.
-   */
-  private void addStringLiteral(char quot, boolean isRaw) {
-    int oldPos = isRaw ? pos - 2 : pos - 1;
-    int start = pos;
-
-    // Don't even attempt to parse triple-quotes here.
-    if (skipTripleQuote(quot)) {
-      pos -= 2;
-      escapedStringLiteral(quot, isRaw);
-      return;
-    }
-
-    // first quick optimistic scan for a simple non-escaped string
-    while (pos < buffer.length) {
-      char c = buffer[pos++];
-      switch (c) {
-        case '\n':
-          error("unterminated string literal at eol", oldPos, pos);
-          addToken(TokenKind.STRING, oldPos, pos - 1, bufferSlice(start, pos - 1));
-          newline();
-          return;
-        case '\\':
-          if (isRaw) {
-            // skip the next character
-            pos++;
-            break;
-          }
-          // oops, hit an escape, need to start over & build a new string buffer
-          pos = oldPos + 1;
-          escapedStringLiteral(quot, false);
-          return;
-        case '\'':
-        case '"':
-          if (c == quot) {
-            // close-quote, all done.
-            addToken(TokenKind.STRING, oldPos, pos, bufferSlice(start, pos - 1));
-            return;
-          }
-      }
-    }
-
-    error("unterminated string literal at eof", oldPos, pos);
-    addToken(TokenKind.STRING, oldPos, pos, bufferSlice(start, pos));
-  }
-
-  private static final ImmutableMap<String, TokenKind> KEYWORD_MAP = createKeywordMap();
-
-  private static ImmutableMap<String, TokenKind> createKeywordMap() {
-    ImmutableMap.Builder<String, TokenKind> builder = ImmutableMap.builder();
-    for (TokenKind kind : TokenKind.KEYWORDS) {
-      builder.put(kind.toString(), kind);
-    }
-    return builder.build();
-  }
-
-  private TokenKind getTokenKindForIdentfier(String id) {
-    TokenKind kind = KEYWORD_MAP.get(id);
-    return kind == null ? TokenKind.IDENTIFIER : kind;
-  }
-
-  private String scanIdentifier() {
-    int oldPos = pos - 1;
-    while (pos < buffer.length) {
-      switch (buffer[pos]) {
-        case '_':
-        case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
-        case 'g': case 'h': case 'i': case 'j': case 'k': case 'l':
-        case 'm': case 'n': case 'o': case 'p': case 'q': case 'r':
-        case 's': case 't': case 'u': case 'v': case 'w': case 'x':
-        case 'y': case 'z':
-        case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
-        case 'G': case 'H': case 'I': case 'J': case 'K': case 'L':
-        case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R':
-        case 'S': case 'T': case 'U': case 'V': case 'W': case 'X':
-        case 'Y': case 'Z':
-        case '0': case '1': case '2': case '3': case '4': case '5':
-        case '6': case '7': case '8': case '9':
-          pos++;
-          break;
-        default:
-          return bufferSlice(oldPos, pos);
-      }
-    }
-    return bufferSlice(oldPos, pos);
-  }
-
-  /**
-   * Scans an identifier or keyword.
-   *
-   * <p>ON ENTRY: 'pos' is 1 + the index of the first char in the identifier.
-   * ON EXIT: 'pos' is 1 + the index of the last char in the identifier.
-   *
-   * @return the identifier or keyword token.
-   */
-  private void addIdentifierOrKeyword() {
-    int oldPos = pos - 1;
-    String id = scanIdentifier();
-    TokenKind kind = getTokenKindForIdentfier(id);
-    addToken(kind, oldPos, pos,
-        (kind == TokenKind.IDENTIFIER) ? id : null);
-  }
-
-  private String scanInteger() {
-    int oldPos = pos - 1;
-    while (pos < buffer.length) {
-      char c = buffer[pos];
-      switch (c) {
-        case 'X': case 'x':
-        case 'a': case 'A':
-        case 'b': case 'B':
-        case 'c': case 'C':
-        case 'd': case 'D':
-        case 'e': case 'E':
-        case 'f': case 'F':
-        case '0': case '1':
-        case '2': case '3':
-        case '4': case '5':
-        case '6': case '7':
-        case '8': case '9':
-          pos++;
-          break;
-        default:
-          return bufferSlice(oldPos, pos);
-      }
-    }
-    return bufferSlice(oldPos, pos);
-  }
-
-  /**
-   * Scans an addInteger literal.
-   *
-   * <p>ON ENTRY: 'pos' is 1 + the index of the first char in the literal.
-   * ON EXIT: 'pos' is 1 + the index of the last char in the literal.
-   */
-  private void addInteger() {
-    int oldPos = pos - 1;
-    String literal = scanInteger();
-
-    final String substring;
-    final int radix;
-    if (literal.startsWith("0x") || literal.startsWith("0X")) {
-      radix = 16;
-      substring = literal.substring(2);
-    } else if (literal.startsWith("0") && literal.length() > 1) {
-      radix = 8;
-      substring = literal.substring(1);
-    } else {
-      radix = 10;
-      substring = literal;
-    }
-
-    int value = 0;
-    try {
-      value = Integer.parseInt(substring, radix);
-    } catch (NumberFormatException e) {
-      error("invalid base-" + radix + " integer constant: " + literal);
-    }
-
-    addToken(TokenKind.INT, oldPos, pos, value);
-  }
-
-  /**
-   * Tokenizes a two-char operator.
-   * @return true if it tokenized an operator
-   */
-  private boolean tokenizeTwoChars() {
-    if (pos + 2 >= buffer.length) {
-      return false;
-    }
-    char c1 = buffer[pos];
-    char c2 = buffer[pos + 1];
-    TokenKind tok = null;
-    if (c2 == '=') {
-      tok = EQUAL_TOKENS.get(c1);
-    } else if (c2 == '*' && c1 == '*') {
-      tok = TokenKind.STAR_STAR;
-    }
-    if (tok == null) {
-      return false;
-    }
-    addToken(tok, pos, pos + 2);
-    return true;
-  }
-
-  /**
-   * Performs tokenization of the character buffer of file contents provided to
-   * the constructor.
-   */
-  private void tokenize() {
-    while (pos < buffer.length) {
-      if (tokenizeTwoChars()) {
-        pos += 2;
-        continue;
-      }
-      char c = buffer[pos];
-      pos++;
-      switch (c) {
-        case '{': {
-          addToken(TokenKind.LBRACE, pos - 1, pos);
-          openParenStackDepth++;
-          break;
-        }
-        case '}': {
-          addToken(TokenKind.RBRACE, pos - 1, pos);
-          popParen();
-          break;
-        }
-        case '(': {
-          addToken(TokenKind.LPAREN, pos - 1, pos);
-          openParenStackDepth++;
-          break;
-        }
-        case ')': {
-          addToken(TokenKind.RPAREN, pos - 1, pos);
-          popParen();
-          break;
-        }
-        case '[': {
-          addToken(TokenKind.LBRACKET, pos - 1, pos);
-          openParenStackDepth++;
-          break;
-        }
-        case ']': {
-          addToken(TokenKind.RBRACKET, pos - 1, pos);
-          popParen();
-          break;
-        }
-        case '>': {
-          addToken(TokenKind.GREATER, pos - 1, pos);
-          break;
-        }
-        case '<': {
-          addToken(TokenKind.LESS, pos - 1, pos);
-          break;
-        }
-        case ':': {
-          addToken(TokenKind.COLON, pos - 1, pos);
-          break;
-        }
-        case ',': {
-          addToken(TokenKind.COMMA, pos - 1, pos);
-          break;
-        }
-        case '+': {
-          addToken(TokenKind.PLUS, pos - 1, pos);
-          break;
-        }
-        case '-': {
-          addToken(TokenKind.MINUS, pos - 1, pos);
-          break;
-        }
-        case '|': {
-          addToken(TokenKind.PIPE, pos - 1, pos);
-          break;
-        }
-        case '=': {
-          addToken(TokenKind.EQUALS, pos - 1, pos);
-          break;
-        }
-        case '%': {
-          addToken(TokenKind.PERCENT, pos - 1, pos);
-          break;
-        }
-        case '/': {
-          addToken(TokenKind.SLASH, pos - 1, pos);
-          break;
-        }
-        case ';': {
-          addToken(TokenKind.SEMI, pos - 1, pos);
-          break;
-        }
-        case '.': {
-          addToken(TokenKind.DOT, pos - 1, pos);
-          break;
-        }
-        case '*': {
-          addToken(TokenKind.STAR, pos - 1, pos);
-          break;
-        }
-        case ' ':
-        case '\t':
-        case '\r': {
-          addWhitespace();
-          break;
-        }
-        case '\\': {
-          // Backslash character is valid only at the end of a line (or in a string)
-          if (pos + 1 < buffer.length && buffer[pos] == '\n') {
-            // treat end of line backslash and newline char as whitespace (they're ignored by the parser)
-            pos++;
-            addToken(TokenKind.WHITESPACE, pos - 2, pos, Character.toString(c));
-          } else {
-            addToken(TokenKind.ILLEGAL, pos - 1, pos, Character.toString(c));
-          }
-          break;
-        }
-        case '\n': {
-          newline();
-          break;
-        }
-        case '#': {
-          int oldPos = pos - 1;
-          while (pos < buffer.length) {
-            c = buffer[pos];
-            if (c == '\n') {
-              break;
-            } else {
-              pos++;
-            }
-          }
-          addToken(TokenKind.COMMENT, oldPos, pos, bufferSlice(oldPos, pos));
-          break;
-        }
-        case '\'':
-        case '\"': {
-          addStringLiteral(c, false);
-          break;
-        }
-        default: {
-          // detect raw strings, e.g. r"str"
-          if (c == 'r' && pos < buffer.length
-              && (buffer[pos] == '\'' || buffer[pos] == '\"')) {
-            c = buffer[pos];
-            pos++;
-            addStringLiteral(c, true);
-            break;
-          }
-
-          if (Character.isDigit(c)) {
-            addInteger();
-          } else if (Character.isJavaIdentifierStart(c) && c != '$') {
-            addIdentifierOrKeyword();
-          } else {
-            // Some characters in Python are not recognized in Blaze syntax (e.g. '!')
-            addToken(TokenKind.ILLEGAL, pos - 1, pos, Character.toString(c));
-            error("invalid character: '" + c + "'");
-          }
-          break;
-        } // default
-      } // switch
-    } // while
-  }
-
-  /**
-   * Returns parts of the source buffer based on offsets
-   *
-   * @param start the beginning offset for the slice
-   * @param end the offset immediately following the slice
-   * @return the text at offset start with length end - start
-   */
-  private String bufferSlice(int start, int end) {
-    return new String(this.buffer, start, end - start);
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/lexer/BuildToken.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/lexer/BuildToken.java
deleted file mode 100644
index 2253bca..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/lexer/BuildToken.java
+++ /dev/null
@@ -1,63 +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.lexer;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.idea.blaze.base.lang.buildfile.language.BuildFileType;
-import com.intellij.psi.tree.IElementType;
-import com.intellij.psi.tree.TokenSet;
-
-/**
- * The IElementTypes used by the BUILD language
- */
-public class BuildToken extends IElementType {
-
-
-  private static ImmutableMap<TokenKind, BuildToken> types = createMap();
-
-  private static ImmutableMap<TokenKind, BuildToken> createMap() {
-    ImmutableMap.Builder<TokenKind, BuildToken> builder = ImmutableMap.builder();
-    for (TokenKind kind : TokenKind.values()) {
-      builder.put(kind, new BuildToken(kind));
-    }
-    return builder.build();
-  }
-
-  public static BuildToken fromKind(TokenKind kind) {
-    return types.get(kind);
-  }
-
-  public static final BuildToken IDENTIFIER = fromKind(TokenKind.IDENTIFIER);
-
-  public static final TokenSet WHITESPACE_AND_NEWLINE = TokenSet.create(
-    fromKind(TokenKind.WHITESPACE),
-    fromKind(TokenKind.NEWLINE)
-  );
-
-  public final TokenKind kind;
-
-  private BuildToken(TokenKind kind) {
-    super(kind.name(), BuildFileType.INSTANCE.getLanguage());
-    this.kind = kind;
-  }
-
-  @Override
-  public String toString() {
-    return kind.toString();
-  }
-}
-
-
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/lexer/Token.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/lexer/Token.java
deleted file mode 100644
index 84e8fe3..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/lexer/Token.java
+++ /dev/null
@@ -1,53 +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.lexer;
-
-/**
- * A Token represents an actual lexeme; that is, a lexical unit, its location in
- * the input text, its lexical kind, and any associated value.
- */
-public class Token {
-
-  public final TokenKind kind;
-  public final int left;
-  public final int right;
-  public final Object value;
-
-  public Token(TokenKind kind, int left, int right) {
-    this(kind, left, right, null);
-  }
-
-  public Token(TokenKind kind, int left, int right, Object value) {
-    this.kind = kind;
-    this.left = left;
-    this.right = right;
-    this.value = value;
-  }
-
-  /**
-   * Constructs an easy-to-read string representation of token, suitable for use
-   * in user error messages.
-   */
-  @Override
-  public String toString() {
-    if (kind == TokenKind.STRING) {
-      return "\"" + value + "\"";
-    }
-    return value == null ? kind.toString() : value.toString();
-  }
-
-}
-
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/lexer/TokenKind.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/lexer/TokenKind.java
deleted file mode 100644
index 7ed2e11..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/lexer/TokenKind.java
+++ /dev/null
@@ -1,131 +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.lexer;
-
-import com.google.common.collect.ImmutableSet;
-
-/**
- * A TokenKind is an enumeration of each different kind of lexical symbol.
- */
-public enum TokenKind {
-
-  ASSERT("assert"),
-  AND("and"),
-  AS("as"),
-  BREAK("break"),
-  CLASS("class"),
-  COLON(":"),
-  COMMA(","),
-  COMMENT("comment"),
-  CONTINUE("continue"),
-  DEF("def"),
-  DEL("del"),
-  DOT("."),
-  ELIF("elif"),
-  ELSE("else"),
-  EOF("EOF"),
-  EQUALS("="),
-  EQUALS_EQUALS("=="),
-  EXCEPT("except"),
-  FALSE("False"),
-  FINALLY("finally"),
-  FOR("for"),
-  FROM("from"),
-  GLOBAL("global"),
-  GREATER(">"),
-  GREATER_EQUALS(">="),
-  IDENTIFIER("identifier"),
-  IF("if"),
-  ILLEGAL("illegal character"),
-  IMPORT("import"),
-  IN("in"),
-  INDENT("indent"),
-  INT("integer"),
-  IS("is"),
-  LAMBDA("lambda"),
-  LBRACE("{"),
-  LBRACKET("["),
-  LESS("<"),
-  LESS_EQUALS("<="),
-  LOAD("load"),
-  LPAREN("("),
-  MINUS("-"),
-  NEWLINE("newline"),
-  NONLOCAL("nonlocal"),
-  NOT("not"),
-  NOT_EQUALS("!="),
-  NOT_IN("not in"), // used internally by the parser; not directly created by the lexer
-  OR("or"),
-  DEDENT("dedent"),
-  PASS("pass"),
-  PERCENT("%"),
-  PIPE("|"),
-  PLUS("+"),
-  PLUS_EQUALS("+="),
-  MINUS_EQUALS("-="),
-  STAR_EQUALS("*="),
-  SLASH_EQUALS("/="),
-  PERCENT_EQUALS("%="),
-  RAISE("raise"),
-  RBRACE("}"),
-  RBRACKET("]"),
-  RETURN("return"),
-  RPAREN(")"),
-  SEMI(";"),
-  SLASH("/"),
-  STAR("*"),
-  STAR_STAR("**"),
-  STRING("string"),
-  TRUE("True"),
-  TRY("try"),
-  WHILE("while"),
-  WITH("with"),
-  YIELD("yield"),
-  // We need to tokenize all characters.
-  // Whitespace will be used for all tokens which should be ignored by the parser.
-  WHITESPACE("whitespace");
-
-  private final String name;
-
-  TokenKind(String name) {
-    this.name = name;
-  }
-
-  /**
-   * This is a user-friendly name. For keywords (if, yield, True, etc.), it's also
-   * the exact character sequence used by the lexer.
-   */
-  @Override
-  public String toString() {
-    return name;
-  }
-
-  public static 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
-  );
-
-  public static ImmutableSet<TokenKind> OPERATIONS = ImmutableSet.of(
-    AND, EQUALS_EQUALS, GREATER, GREATER_EQUALS, IN, LESS, LESS_EQUALS,
-    MINUS, NOT_EQUALS, NOT_IN, OR, PERCENT, SLASH, PLUS, PIPE, STAR
-  );
-
-  public static ImmutableSet<TokenKind> AUGMENTED_ASSIGNMENT_OPS = ImmutableSet.of(
-    PLUS_EQUALS, MINUS_EQUALS, STAR_EQUALS, SLASH_EQUALS, PERCENT_EQUALS
-  );
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/parser/BuildParserDefinition.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/parser/BuildParserDefinition.java
deleted file mode 100644
index 07c26af..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/parser/BuildParserDefinition.java
+++ /dev/null
@@ -1,119 +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.parser;
-
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.experiments.DeveloperFlag;
-import com.google.idea.blaze.base.lang.buildfile.lexer.BuildLexer;
-import com.google.idea.blaze.base.lang.buildfile.lexer.BuildLexerBase;
-import com.google.idea.blaze.base.lang.buildfile.lexer.BuildToken;
-import com.google.idea.blaze.base.lang.buildfile.lexer.TokenKind;
-import com.google.idea.blaze.base.lang.buildfile.psi.BuildElementType;
-import com.google.idea.blaze.base.lang.buildfile.psi.BuildElementTypes;
-import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
-import com.intellij.extapi.psi.ASTWrapperPsiElement;
-import com.intellij.lang.ASTNode;
-import com.intellij.lang.ParserDefinition;
-import com.intellij.lang.PsiBuilder;
-import com.intellij.lang.PsiParser;
-import com.intellij.lexer.Lexer;
-import com.intellij.openapi.project.Project;
-import com.intellij.psi.FileViewProvider;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiFile;
-import com.intellij.psi.impl.source.resolve.FileContextUtil;
-import com.intellij.psi.tree.IElementType;
-import com.intellij.psi.tree.IFileElementType;
-import com.intellij.psi.tree.TokenSet;
-
-/**
- * Defines the BUILD file parser
- */
-public class BuildParserDefinition implements ParserDefinition {
-
-  private static final DeveloperFlag DEBUG = new DeveloperFlag("build.file.debug.mode");
-
-  @Override
-  public Lexer createLexer(Project project) {
-    return new BuildLexer(BuildLexerBase.LexerMode.Parsing);
-  }
-
-  @Override
-  public PsiParser createParser(Project project) {
-    return new BuildParser();
-  }
-
-  @Override
-  public IFileElementType getFileNodeType() {
-    return BuildElementTypes.BUILD_FILE;
-  }
-
-  @Override
-  public TokenSet getWhitespaceTokens() {
-    return convert(TokenKind.WHITESPACE, TokenKind.ILLEGAL);
-  }
-
-  @Override
-  public TokenSet getCommentTokens() {
-    return convert(TokenKind.COMMENT);
-  }
-
-  @Override
-  public TokenSet getStringLiteralElements() {
-    return convert(TokenKind.STRING);
-  }
-
-  @Override
-  public PsiElement createElement(ASTNode node) {
-    IElementType type = node.getElementType();
-    if (type instanceof BuildElementType) {
-      return ((BuildElementType) type).createElement(node);
-    }
-    return new ASTWrapperPsiElement(node);
-  }
-
-  @Override
-  public PsiFile createFile(FileViewProvider viewProvider) {
-    return new BuildFile(viewProvider);
-  }
-
-  @Override
-  public SpaceRequirements spaceExistanceTypeBetweenTokens(ASTNode left, ASTNode right) {
-    return SpaceRequirements.MAY;
-  }
-
-  private static TokenSet convert(TokenKind... blazeTokens) {
-    return TokenSet.create(Lists.newArrayList(blazeTokens)
-        .stream()
-        .map(BuildToken::fromKind)
-        .toArray(IElementType[]::new));
-  }
-
-  private static class BuildParser implements PsiParser {
-    @Override
-    public ASTNode parse(IElementType root, PsiBuilder builder) {
-      if (DEBUG.getValue()) {
-        System.err.println(builder.getUserDataUnprotected(FileContextUtil.CONTAINING_FILE_KEY));
-      }
-      PsiBuilder.Marker rootMarker = builder.mark();
-      ParsingContext context = new ParsingContext(builder);
-      context.statementParser.parseFileInput();
-      rootMarker.done(root);
-      return builder.getTreeBuilt();
-    }
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/parser/ExpressionParsing.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/parser/ExpressionParsing.java
deleted file mode 100644
index 949e74e..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/parser/ExpressionParsing.java
+++ /dev/null
@@ -1,543 +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.parser;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.idea.blaze.base.lang.buildfile.lexer.TokenKind;
-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);
-
-  private static final ImmutableSet<TokenKind> DICT_TERMINATOR_SET =
-    ImmutableSet.of(
-      TokenKind.EOF,
-      TokenKind.RBRACE,
-      TokenKind.SEMI);
-
-  private static final ImmutableSet<TokenKind> EXPR_LIST_TERMINATOR_SET =
-    ImmutableSet.of(
-      TokenKind.EOF,
-      TokenKind.NEWLINE,
-      TokenKind.EQUALS,
-      TokenKind.RBRACE,
-      TokenKind.RBRACKET,
-      TokenKind.RPAREN,
-      TokenKind.SEMI);
-
-  private static final ImmutableSet<TokenKind> EXPR_TERMINATOR_SET =
-    ImmutableSet.of(
-      TokenKind.EOF,
-      TokenKind.COLON,
-      TokenKind.COMMA,
-      TokenKind.FOR,
-      TokenKind.MINUS,
-      TokenKind.PERCENT,
-      TokenKind.PLUS,
-      TokenKind.RBRACKET,
-      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);
-
-  /**
-   * Highest precedence goes last.
-   * Based on: http://docs.python.org/2/reference/expressions.html#operator-precedence
-   **/
-  private static final List<EnumSet<TokenKind>> OPERATOR_PRECEDENCE = ImmutableList.of(
-    EnumSet.of(TokenKind.OR),
-    EnumSet.of(TokenKind.AND),
-    EnumSet.of(TokenKind.NOT),
-    EnumSet.of(TokenKind.EQUALS_EQUALS, TokenKind.NOT_EQUALS, TokenKind.LESS, TokenKind.LESS_EQUALS,
-      TokenKind.GREATER, TokenKind.GREATER_EQUALS, TokenKind.IN, TokenKind.NOT_IN),
-    EnumSet.of(TokenKind.PIPE),
-    EnumSet.of(TokenKind.MINUS, TokenKind.PLUS),
-    EnumSet.of(TokenKind.SLASH, TokenKind.STAR, TokenKind.PERCENT));
-
-  public ExpressionParsing(ParsingContext context) {
-    super(context);
-  }
-
-  public void parseExpression(boolean insideParens) {
-    // handle lists without parens (e.g. 'a,b,c = 1')
-    PsiBuilder.Marker marker = insideParens ? null : builder.mark();
-    parseNonTupleExpression();
-    if (currentToken() == TokenKind.COMMA) {
-      parseExpressionList();
-      if (marker != null) {
-        marker.done(BuildElementTypes.LIST_LITERAL);
-      }
-    } else if (marker != null) {
-      marker.drop();
-    }
-  }
-
-  // expr_list ::= ( ',' expr )* ','?
-  private void parseExpressionList() {
-    while (matches(TokenKind.COMMA)) {
-      if (atAnyOfTokens(EXPR_LIST_TERMINATOR_SET)) {
-        break;
-      }
-      parseNonTupleExpression();
-    }
-  }
-
-  protected void parseNonTupleExpression() {
-    parseNonTupleExpression(0);
-    // don't bother including conditional expressions for now, just include their components serially
-    if (matches(TokenKind.IF)) {
-      parseNonTupleExpression(0);
-      if (matches(TokenKind.ELSE)) {
-        parseNonTupleExpression();
-      }
-    }
-  }
-
-  private void parseNonTupleExpression(int prec) {
-    if (prec >= OPERATOR_PRECEDENCE.size()) {
-      parsePrimaryWithSuffix();
-      return;
-    }
-    if (currentToken() == TokenKind.NOT && OPERATOR_PRECEDENCE.get(prec).contains(TokenKind.NOT)) {
-      // special case handling of multi-token 'NOT IN' binary operator
-      if (kindFromElement(builder.lookAhead(1)) != TokenKind.IN) {
-        // skip the 'not' -- no need for a specific 'not' expression
-        builder.advanceLexer();
-        parseNonTupleExpression(prec + 1);
-        return;
-      }
-    }
-    parseBinOpExpression(prec);
-  }
-
-  /**
-   * binop_expression ::= binop_expression OP binop_expression
-   *                    | parsePrimaryWithSuffix
-   * This function takes care of precedence between operators (see OPERATOR_PRECEDENCE for
-   * the order), and it assumes left-to-right associativity.
-   */
-  private void parseBinOpExpression(int prec) {
-    PsiBuilder.Marker marker = builder.mark();
-    parseNonTupleExpression(prec + 1);
-
-    while (true) {
-      if (!atBinaryOperator(prec)) {
-        marker.drop();
-        return;
-      }
-      parseNonTupleExpression(prec + 1);
-      marker.done(BuildElementTypes.BINARY_OP_EXPRESSION);
-      marker = marker.precede();
-    }
-  }
-
-  /**
-   * Consumes current token iff it's a binary operator at the given precedence level
-   * (with special-case handling of 'NOT' 'IN' double token binary operator)
-   */
-  private boolean atBinaryOperator(int prec) {
-    if (matchesAnyOf(OPERATOR_PRECEDENCE.get(prec))) {
-      return true;
-    }
-    if (matchesSequence(TokenKind.NOT, TokenKind.IN)) {
-      return true;
-    }
-    return false;
-  }
-
-  // primary_with_suffix ::= primary (selector_suffix | substring_suffix)*
-  private void parsePrimaryWithSuffix() {
-    PsiBuilder.Marker marker = builder.mark();
-    parsePrimary();
-    while (true) {
-      if (matches(TokenKind.DOT)) {
-        marker = parseSelectorSuffix(marker);
-      } else if (matches(TokenKind.LBRACKET)) {
-        marker = parseSubstringSuffix(marker);
-      } else {
-        break;
-      }
-    }
-    marker.drop();
-  }
-
-  // selector_suffix ::= '.' IDENTIFIER [funcall_suffix]
-  private PsiBuilder.Marker parseSelectorSuffix(PsiBuilder.Marker marker) {
-    if (!atToken(TokenKind.IDENTIFIER)) {
-      builder.error("expected identifier after dot");
-      syncPast(EXPR_TERMINATOR_SET);
-      return marker;
-    }
-    parseTargetOrReferenceIdentifier();
-    if (atToken(TokenKind.LPAREN)) {
-      parseFuncallSuffix();
-      marker.done(BuildElementTypes.FUNCALL_EXPRESSION);
-    } else {
-      marker.done(BuildElementTypes.DOT_EXPRESSION);
-    }
-    return marker.precede();
-  }
-
-  // substring_suffix ::= '[' expression? ':' expression? ':' expression? ']'
-  private PsiBuilder.Marker parseSubstringSuffix(PsiBuilder.Marker marker) {
-    if (!atToken(TokenKind.COLON)) {
-      PsiBuilder.Marker pos = builder.mark();
-      parseExpression(false);
-      pos.done(BuildElementTypes.POSITIONAL);
-    }
-    while (!matches(TokenKind.RBRACKET)) {
-      if (expect(TokenKind.COLON)) {
-        if (!atAnyOfTokens(TokenKind.COLON, TokenKind.RBRACKET)) {
-          parseNonTupleExpression();
-        }
-      } else {
-        syncPast(EXPR_LIST_TERMINATOR_SET);
-        break;
-      }
-    }
-    marker.done(BuildElementTypes.FUNCALL_EXPRESSION);
-    return marker.precede();
-  }
-
-  private void parseTargetOrReferenceIdentifier() {
-    if (!atToken(TokenKind.IDENTIFIER)) {
-      builder.error("expected an identifier");
-      return;
-    }
-    // TODO: handle assigning to a list of targets (e.g. "a,b = 1")
-    TokenKind next = kindFromElement(builder.lookAhead(1));
-    if (next == TokenKind.EQUALS || next == TokenKind.IN) {
-      buildTokenElement(BuildElementTypes.TARGET_EXPRESSION);
-    } else {
-      buildTokenElement(BuildElementTypes.REFERENCE_EXPRESSION);
-    }
-  }
-
-  private void parsePrimary() {
-    TokenKind current = currentToken();
-    switch (current) {
-      case INT:
-        buildTokenElement(BuildElementTypes.INTEGER_LITERAL);
-        return;
-      case STRING:
-        parseStringLiteral(true);
-        return;
-      case IDENTIFIER:
-        PsiBuilder.Marker marker = builder.mark();
-        String tokenText = builder.getTokenText();
-        parseTargetOrReferenceIdentifier();
-        if (atToken(TokenKind.LPAREN)) {
-          parseFuncallSuffix();
-          marker.done(getFuncallExpressionType(tokenText));
-        } else {
-          marker.drop();
-        }
-        return;
-      case TRUE: // intentional fall-through -- both treated as vanilla identifiers
-      case FALSE:
-        buildTokenElement(BuildElementTypes.BOOLEAN_LITERAL);
-        return;
-      case LBRACKET:
-        parseListMaker();
-        return;
-      case LBRACE:
-        parseDictExpression();
-        return;
-      case LPAREN:
-        marker = builder.mark();
-        builder.advanceLexer();
-        if (matches(TokenKind.RPAREN)) {
-          marker.done(BuildElementTypes.LIST_LITERAL);
-          return;
-        }
-        parseExpression(true);
-        expect(TokenKind.RPAREN, true);
-        marker.done(BuildElementTypes.LIST_LITERAL);
-        return;
-      case MINUS:
-        marker = builder.mark();
-        builder.advanceLexer();
-        parsePrimaryWithSuffix();
-        marker.done(BuildElementTypes.POSITIONAL);
-        return;
-      default:
-        builder.error("expected an expression");
-        syncPast(EXPR_TERMINATOR_SET);
-    }
-  }
-
-  /**
-   * funcall_suffix ::= '(' arg_list? ')'
-   * arg_list ::= ((arg ',')* arg ','? )?
-   */
-  private void parseFuncallSuffix() {
-    PsiBuilder.Marker mark = builder.mark();
-    expect(TokenKind.LPAREN, true);
-    if (matches(TokenKind.RPAREN)) {
-      mark.done(BuildElementTypes.ARGUMENT_LIST);
-      return;
-    }
-    parseFuncallArgument();
-    while (!atAnyOfTokens(FUNCALL_TERMINATOR_SET)) {
-      expect(TokenKind.COMMA);
-      if (atAnyOfTokens(FUNCALL_TERMINATOR_SET)) {
-        break;
-      }
-      parseFuncallArgument();
-    }
-    expect(TokenKind.RPAREN, true);
-    mark.done(BuildElementTypes.ARGUMENT_LIST);
-  }
-
-  private BuildElementType getFuncallExpressionType(String functionName) {
-    if ("glob".equals(functionName)) {
-      return BuildElementTypes.GLOB_EXPRESSION;
-    }
-    return BuildElementTypes.FUNCALL_EXPRESSION;
-  }
-
-  protected void parseFunctionParameters() {
-    if (atToken(TokenKind.RPAREN)) {
-      return;
-    }
-    parseFunctionParameter();
-    while (!atAnyOfTokens(FUNCALL_TERMINATOR_SET)) {
-      expect(TokenKind.COMMA);
-      if (atAnyOfTokens(FUNCALL_TERMINATOR_SET)) {
-        break;
-      }
-      parseFunctionParameter();
-    }
-  }
-
-  // arg ::= IDENTIFIER '=' nontupleexpr
-  //       | expr
-  //       | *args
-  //       | **kwargs
-  private void parseFuncallArgument() {
-    PsiBuilder.Marker marker = builder.mark();
-    if (matches(TokenKind.STAR_STAR)) {
-      parseNonTupleExpression();
-      marker.done(BuildElementTypes.STAR_STAR);
-      return;
-    }
-    if (matches(TokenKind.STAR)) {
-      parseNonTupleExpression();
-      marker.done(BuildElementTypes.STAR);
-      return;
-    }
-    if (matchesSequence(TokenKind.IDENTIFIER, TokenKind.EQUALS)) {
-      parseNonTupleExpression();
-      marker.done(BuildElementTypes.KEYWORD);
-      return;
-    }
-    parseNonTupleExpression();
-    marker.done(BuildElementTypes.POSITIONAL);
-  }
-
-  /**
-   * arg ::= IDENTIFIER ['=' nontupleexpr]
-   */
-  private void parseFunctionParameter() {
-    PsiBuilder.Marker marker = builder.mark();
-    if (matches(TokenKind.STAR_STAR)) {
-      expectIdentifier("invalid parameter name");
-      marker.done(BuildElementTypes.PARAM_STAR_STAR);
-      return;
-    }
-    if (matches(TokenKind.STAR)) {
-      if (atToken(TokenKind.IDENTIFIER)) {
-        builder.advanceLexer();
-      }
-      marker.done(BuildElementTypes.PARAM_STAR);
-      return;
-    }
-    expectIdentifier("invalid parameter name");
-    if (matches(TokenKind.EQUALS)) {
-      parseNonTupleExpression();
-      marker.done(BuildElementTypes.PARAM_OPTIONAL);
-      return;
-    }
-    marker.done(BuildElementTypes.PARAM_MANDATORY);
-  }
-
-  protected void expectIdentifier(String error) {
-    expect(TokenKind.IDENTIFIER, error, true);
-  }
-
-  // list_maker ::= '[' ']'
-  //               |'[' expr ']'
-  //               |'[' expr expr_list ']'
-  //               |'[' expr ('FOR' loop_variables 'IN' expr)+ ']'
-  private void parseListMaker() {
-    PsiBuilder.Marker marker = builder.mark();
-    expect(TokenKind.LBRACKET);
-    if (matches(TokenKind.RBRACKET)) {
-      marker.done(BuildElementTypes.LIST_LITERAL);
-      return;
-    }
-    parseNonTupleExpression();
-    switch (currentToken()) {
-      case RBRACKET:
-        builder.advanceLexer();
-        marker.done(BuildElementTypes.LIST_LITERAL);
-        return;
-      case FOR:
-        parseComprehensionSuffix(TokenKind.RBRACKET);
-        marker.done(BuildElementTypes.LIST_COMPREHENSION_EXPR);
-        return;
-      case COMMA:
-        parseExpressionList();
-        if (!matches(TokenKind.RBRACKET)) {
-          builder.error("expected 'for' or ']'");
-          syncPast(LIST_TERMINATOR_SET);
-        }
-        marker.done(BuildElementTypes.LIST_LITERAL);
-        return;
-      default:
-        builder.error("expected ',', 'for' or ']'");
-        syncPast(LIST_TERMINATOR_SET);
-        marker.done(BuildElementTypes.LIST_LITERAL);
-    }
-  }
-
-  // dict_expression ::= '{' '}'
-  //                    |'{' dict_entry_list '}'
-  //                    |'{' dict_entry 'FOR' loop_variables 'IN' expr '}'
-  private void parseDictExpression() {
-    PsiBuilder.Marker marker = builder.mark();
-    expect(TokenKind.LBRACE, true);
-    if (matches(TokenKind.RBRACE)) {
-      marker.done(BuildElementTypes.DICTIONARY_LITERAL);
-      return;
-    }
-    parseDictEntry();
-    if (currentToken() == TokenKind.FOR) {
-      parseComprehensionSuffix(TokenKind.RBRACE);
-      marker.done(BuildElementTypes.LIST_COMPREHENSION_EXPR);
-      return;
-    }
-    if (matches(TokenKind.COMMA)) {
-      parseDictEntryList();
-    }
-    expect(TokenKind.RBRACE, true);
-    marker.done(BuildElementTypes.DICTIONARY_LITERAL);
-  }
-
-  // dict_entry_list ::= ( (dict_entry ',')* dict_entry ','? )?
-  private void parseDictEntryList() {
-    if (atAnyOfTokens(DICT_TERMINATOR_SET)) {
-      return;
-    }
-    parseDictEntry();
-    while (matches(TokenKind.COMMA)) {
-      if (atAnyOfTokens(DICT_TERMINATOR_SET)) {
-        return;
-      }
-      parseDictEntry();
-    }
-  }
-
-  // dict_entry ::= nontupleexpr ':' nontupleexpr
-  private void parseDictEntry() {
-    PsiBuilder.Marker marker = builder.mark();
-    parseNonTupleExpression();
-    expect(TokenKind.COLON);
-    parseNonTupleExpression();
-    marker.done(BuildElementTypes.DICTIONARY_ENTRY_LITERAL);
-  }
-
-  // comprehension_suffix ::= 'FOR' loop_variables 'IN' expr comprehension_suffix
-  //                        | 'IF' expr comprehension_suffix
-  //                        | ']'
-  private void parseComprehensionSuffix(TokenKind closingBracket) {
-    while (true) {
-      if (matches(TokenKind.FOR)) {
-        parseForLoopVariables();
-        expect(TokenKind.IN);
-        parseNonTupleExpression(0);
-      } else if (matches(TokenKind.IF)) {
-        parseExpression(true);
-      } else if (matches(closingBracket)) {
-        return;
-      } else {
-        builder.error("expected " + closingBracket + ", 'for' or 'if'");
-        syncPast(EXPR_LIST_TERMINATOR_SET);
-        return;
-      }
-    }
-  }
-
-  // Equivalent to 'exprlist' rule in Python grammar.
-  // loop_variables ::= primary_with_suffix ( ',' primary_with_suffix )* ','?
-  protected void parseForLoopVariables() {
-    PsiBuilder.Marker marker = builder.mark();
-    parsePrimaryWithSuffix();
-    if (currentToken() != TokenKind.COMMA) {
-      marker.drop();
-      return;
-    }
-    while (matches(TokenKind.COMMA)) {
-      if (atAnyOfTokens(EXPR_LIST_TERMINATOR_SET)) {
-        break;
-      }
-      parsePrimaryWithSuffix();
-    }
-    marker.done(BuildElementTypes.LIST_LITERAL);
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/parser/Parsing.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/parser/Parsing.java
deleted file mode 100644
index 2188c83..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/parser/Parsing.java
+++ /dev/null
@@ -1,255 +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.parser;
-
-import com.google.idea.blaze.base.lang.buildfile.lexer.BuildToken;
-import com.google.idea.blaze.base.lang.buildfile.lexer.TokenKind;
-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.psi.tree.IElementType;
-
-import javax.annotation.Nullable;
-import java.util.EnumSet;
-import java.util.Set;
-
-/**
- * Base class for BUILD file component parsers
- */
-public abstract class Parsing {
-
-  // Keywords that exist in Python which we don't parse.
-  protected static final EnumSet<TokenKind> FORBIDDEN_KEYWORDS =
-    EnumSet.of(TokenKind.AS, TokenKind.ASSERT,
-      TokenKind.DEL, TokenKind.EXCEPT, TokenKind.FINALLY, TokenKind.FROM, TokenKind.GLOBAL,
-      TokenKind.IMPORT, TokenKind.IS, TokenKind.LAMBDA, TokenKind.NONLOCAL, TokenKind.RAISE,
-      TokenKind.TRY, TokenKind.WITH, TokenKind.WHILE, TokenKind.YIELD);
-
-  protected ParsingContext context;
-  protected PsiBuilder builder;
-
-  public Parsing(ParsingContext context) {
-    this.context = context;
-    this.builder = context.builder;
-  }
-
-  protected ExpressionParsing getExpressionParser() {
-    return context.expressionParser;
-  }
-
-  /**
-   * @return true if a string was parsed
-   */
-  protected boolean parseStringLiteral(boolean alwaysConsume) {
-    if (currentToken() != TokenKind.STRING) {
-      expect(TokenKind.STRING, alwaysConsume);
-      return false;
-    }
-    buildTokenElement(BuildElementTypes.STRING_LITERAL);
-    if (currentToken() == TokenKind.STRING) {
-      builder.error("implicit string concatenation is forbidden; use the '+' operator");
-    }
-    return true;
-  }
-
-  protected void buildTokenElement(BuildElementType type) {
-    PsiBuilder.Marker marker = builder.mark();
-    builder.advanceLexer();
-    marker.done(type);
-  }
-
-  /**
-   * Consume tokens until we reach the first token that has a kind that is in the set of terminatingTokens.
-   */
-  protected void syncTo(Set<TokenKind> terminatingTokens) {
-    // read past the problematic token
-    while (!atAnyOfTokens(terminatingTokens)) {
-      builder.advanceLexer();
-    }
-  }
-
-  /**
-   * Consume tokens until we consume the first token that has a kind that is in the set of terminatingTokens.
-   */
-  protected void syncPast(Set<TokenKind> terminatingTokens) {
-    // read past the problematic token
-    while (!matchesAnyOf(terminatingTokens)) {
-      builder.advanceLexer();
-    }
-  }
-
-  /**
-   * Consumes the current token iff it's one of the expected types.<br>
-   * Otherwise, returns false and reports an error.
-   */
-  protected boolean expect(TokenKind kind) {
-    return expect(kind, false);
-  }
-
-  /**
-   * Consumes the current token if 'alwaysConsume' is true or if it's one of the expected types.<br>
-   * Otherwise, returns false and reports an error.
-   */
-  protected boolean expect(TokenKind kind, boolean alwaysConsume) {
-    return expect(kind, String.format("'%s' expected", kind), alwaysConsume);
-  }
-
-  /**
-   * Consumes the current token if 'alwaysConsume' is true or if it's one of the expected types.<br>
-   * Otherwise, returns false and reports an error.
-   */
-  protected boolean expect(TokenKind kind, String message, boolean alwaysConsume) {
-    TokenKind current = currentToken();
-    if (current == kind || alwaysConsume) {
-      builder.advanceLexer();
-    }
-    if (current != kind) {
-      builder.error(message);
-      return false;
-    }
-    return true;
-  }
-
-  /**
-   * Checks if we're at the current sequence of tokens. If so, consumes them.
-   */
-  protected boolean matchesSequence(TokenKind... kinds) {
-    PsiBuilder.Marker marker = builder.mark();
-    for (TokenKind kind : kinds) {
-      if (!matches(kind)) {
-        marker.rollbackTo();
-        return false;
-      }
-    }
-    marker.drop();
-    return true;
-  }
-
-  /**
-   * Consumes the current token iff it matches the expected type. Otherwise, returns false
-   */
-  protected boolean matches(TokenKind kind) {
-    if (currentToken() == kind) {
-      builder.advanceLexer();
-      return true;
-    }
-    return false;
-  }
-
-  /**
-   * Consumes the current token iff it matches one of the expected types. Otherwise, returns false
-   */
-  protected boolean matchesAnyOf(TokenKind... kinds) {
-    TokenKind current = currentToken();
-    for (TokenKind kind : kinds) {
-      if (kind == current) {
-        builder.advanceLexer();
-        return true;
-      }
-    }
-    return false;
-  }
-
-  /**
-   * Consumes the current token iff it's one of the expected types. Otherwise, returns false
-   */
-  protected boolean matchesAnyOf(Set<TokenKind> kinds) {
-    if (kinds.contains(currentToken())) {
-      builder.advanceLexer();
-      return true;
-    }
-    return false;
-  }
-
-  /**
-   * Checks if the upcoming sequence of tokens match that expected. Doesn't advance the parser.
-   */
-  protected boolean atTokenSequence(TokenKind... kinds) {
-    for (int i = 0; i < kinds.length; i++) {
-      if (kindFromElement(builder.lookAhead(i)) != kinds[i]) {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  /**
-   * Checks if the current token matches the expected kind. Doesn't advance the parser.
-   */
-  protected boolean atToken(TokenKind kind) {
-    return currentToken() == kind;
-  }
-
-  /**
-   * Checks if the current token matches any one of the expected kinds. Doesn't advance the parser.
-   */
-  protected boolean atAnyOfTokens(TokenKind... kinds) {
-    TokenKind current = currentToken();
-    for (TokenKind kind : kinds) {
-      if (current == kind) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  /**
-   * Checks if the current token matches any one of the expected kinds. Doesn't advance the parser.
-   */
-  protected boolean atAnyOfTokens(Set<TokenKind> kinds) {
-    return kinds.contains(currentToken());
-  }
-
-  @Nullable
-  protected TokenKind currentToken() {
-    return builder.eof() ? TokenKind.EOF : kindFromElement(builder.getTokenType());
-  }
-
-  @Nullable
-  protected TokenKind kindFromElement(IElementType type) {
-    if (type == null) {
-      return null;
-    }
-    if (!(type instanceof BuildToken)) {
-      throw new RuntimeException("Invalid type: " + type + " of class " + type.getClass());
-    }
-    TokenKind kind = ((BuildToken) type).kind;
-    checkForbiddenKeywords(kind);
-    return kind;
-  }
-
-  private void checkForbiddenKeywords(TokenKind kind) {
-    if (!FORBIDDEN_KEYWORDS.contains(kind)) {
-      return;
-    }
-    builder.error(forbiddenKeywordError(kind));
-  }
-
-  protected String forbiddenKeywordError(TokenKind kind) {
-    assert FORBIDDEN_KEYWORDS.contains(kind);
-    switch (kind) {
-      case ASSERT: return "'assert' not supported, use 'fail' instead";
-      case TRY: return "'try' not supported, all exceptions are fatal";
-      case IMPORT: return "'import' not supported, use 'load' instead";
-      case IS: return "'is' not supported, use '==' instead";
-      case LAMBDA: return "'lambda' not supported, declare a function instead";
-      case RAISE: return "'raise' not supported, use 'fail' instead";
-      case WHILE: return "'while' not supported, use 'for' instead";
-      default: return "keyword '" + kind + "' not supported";
-    }
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/parser/ParsingContext.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/parser/ParsingContext.java
deleted file mode 100644
index de8e5b7..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/parser/ParsingContext.java
+++ /dev/null
@@ -1,35 +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.parser;
-
-import com.intellij.lang.PsiBuilder;
-
-/**
- * Shared context between BUILD file parsing components
- */
-public class ParsingContext {
-
-  public final StatementParsing statementParser;
-  public final ExpressionParsing expressionParser;
-  public final PsiBuilder builder;
-
-  public ParsingContext(final PsiBuilder builder) {
-    this.builder = builder;
-    statementParser = new StatementParsing(this);
-    expressionParser = new ExpressionParsing(this);
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/parser/StatementParsing.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/parser/StatementParsing.java
deleted file mode 100644
index 762db00..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/parser/StatementParsing.java
+++ /dev/null
@@ -1,227 +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.parser;
-
-import com.google.common.collect.ImmutableSet;
-import com.google.idea.blaze.base.lang.buildfile.lexer.TokenKind;
-import com.google.idea.blaze.base.lang.buildfile.psi.BuildElementTypes;
-import com.intellij.lang.PsiBuilder;
-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);
-  }
-
-  /**
-   * Called at the start of parsing. Parses an entire file
-   */
-  public void parseFileInput() {
-    builder.setDebugMode(true);
-    while (!builder.eof()) {
-      if (matches(TokenKind.NEWLINE)) {
-        continue;
-      }
-      parseTopLevelStatement();
-    }
-  }
-
-  // Unlike in Python grammar, 'load' and 'def' are only allowed as a top-level statement
-  public void parseTopLevelStatement() {
-    if (currentToken() == TokenKind.LOAD) {
-      parseLoadStatement();
-    } else if (currentToken() == TokenKind.DEF) {
-      parseFunctionDefStatement();
-    } else {
-      parseStatement();
-    }
-  }
-
-  // simple_stmt | compound_stmt
-  public void parseStatement() {
-    TokenKind current = currentToken();
-    if (current == TokenKind.IF) {
-      parseIfStatement();
-    } else if (current == TokenKind.FOR) {
-      parseForStatement();
-    } else if (FORBIDDEN_KEYWORDS.contains(current)) {
-      PsiBuilder.Marker mark = builder.mark();
-      syncTo(STATEMENT_TERMINATOR_SET);
-      mark.error(forbiddenKeywordError(current));
-      builder.advanceLexer();
-    } else {
-      parseSimpleStatement();
-    }
-  }
-
-  // func_def_stmt ::= DEF IDENTIFIER funcall_suffix ':' suite
-  private void parseFunctionDefStatement() {
-    PsiBuilder.Marker marker = builder.mark();
-    expect(TokenKind.DEF);
-    getExpressionParser().expectIdentifier("expected a function name");
-    PsiBuilder.Marker listMarker = builder.mark();
-    expect(TokenKind.LPAREN);
-    getExpressionParser().parseFunctionParameters();
-    expect(TokenKind.RPAREN, true);
-    listMarker.done(BuildElementTypes.PARAMETER_LIST);
-    expect(TokenKind.COLON);
-    parseSuite();
-    marker.done(BuildElementTypes.FUNCTION_STATEMENT);
-  }
-
-  // load '(' STRING (',' [IDENTIFIER '='] STRING)* [','] ')'
-  private void parseLoadStatement() {
-    PsiBuilder.Marker marker = builder.mark();
-    expect(TokenKind.LOAD);
-    expect(TokenKind.LPAREN);
-    parseStringLiteral(false);
-    // Not implementing [IDENTIFIER EQUALS] option -- not a documented feature, so wait for users to complain
-    boolean hasSymbols = false;
-    while (!matches(TokenKind.RPAREN) && !matchesAnyOf(STATEMENT_TERMINATOR_SET)) {
-      expect(TokenKind.COMMA);
-      if (matches(TokenKind.RPAREN) || matchesAnyOf(STATEMENT_TERMINATOR_SET)) {
-        break;
-      }
-      hasSymbols |= parseStringLiteral(true);
-    }
-    if (!hasSymbols) {
-      builder.error("'load' statements must include at least one loaded function");
-    }
-    marker.done(BuildElementTypes.LOAD_STATEMENT);
-  }
-
-  /**
-   * if_stmt ::= IF expr ':' suite (ELIF expr ':' suite)* [ELSE ':' suite]
-   */
-  private void parseIfStatement() {
-    PsiBuilder.Marker marker = builder.mark();
-    parseIfStatementPart(TokenKind.IF, BuildElementTypes.IF_PART, true);
-    while (currentToken() == TokenKind.ELIF) {
-      parseIfStatementPart(TokenKind.ELIF, BuildElementTypes.ELSE_IF_PART, true);
-    }
-    if (currentToken() == TokenKind.ELSE) {
-      parseIfStatementPart(TokenKind.ELSE, BuildElementTypes.ELSE_PART, false);
-    }
-    marker.done(BuildElementTypes.IF_STATEMENT);
-  }
-
-  // cond_stmts ::= [EL]IF expr ':' suite
-  private void parseIfStatementPart(TokenKind tokenKind, IElementType type, boolean conditional) {
-    PsiBuilder.Marker marker = builder.mark();
-    expect(tokenKind);
-    if (conditional) {
-      getExpressionParser().parseNonTupleExpression();
-    }
-    expect(TokenKind.COLON);
-    parseSuite();
-    marker.done(type);
-  }
-
-  // for_stmt ::= FOR IDENTIFIER IN expr ':' suite
-  private void parseForStatement() {
-    PsiBuilder.Marker marker = builder.mark();
-    expect(TokenKind.FOR);
-    getExpressionParser().parseForLoopVariables();
-    expect(TokenKind.IN);
-    getExpressionParser().parseExpression(false);
-    expect(TokenKind.COLON);
-    parseSuite();
-    marker.done(BuildElementTypes.FOR_STATEMENT);
-  }
-
-  // simple_stmt ::= small_stmt (';' small_stmt)* ';'? NEWLINE
-  private void parseSimpleStatement() {
-    parseSmallStatementOrPass();
-    while (matches(TokenKind.SEMI)) {
-      if (matches(TokenKind.NEWLINE)) {
-        return;
-      }
-      parseSmallStatementOrPass();
-    }
-    if (!builder.eof()) {
-      expect(TokenKind.NEWLINE);
-    }
-  }
-
-  // small_stmt | 'pass'
-  private void parseSmallStatementOrPass() {
-    if (currentToken() == TokenKind.PASS) {
-      buildTokenElement(BuildElementTypes.PASS_STATMENT);
-      return;
-    }
-    parseSmallStatement();
-  }
-
-  private void parseSmallStatement() {
-    if (currentToken() == TokenKind.RETURN) {
-      parseReturnStatement();
-      return;
-    }
-    if (atAnyOfTokens(TokenKind.BREAK, TokenKind.CONTINUE)) {
-      buildTokenElement(BuildElementTypes.FLOW_STATEMENT);
-      return;
-    }
-    PsiBuilder.Marker refMarker = builder.mark();
-    getExpressionParser().parseExpression(false);
-    if (matches(TokenKind.EQUALS)) {
-      getExpressionParser().parseExpression(false);
-      refMarker.done(BuildElementTypes.ASSIGNMENT_STATEMENT);
-    } else if (matchesAnyOf(TokenKind.AUGMENTED_ASSIGNMENT_OPS)) {
-      getExpressionParser().parseExpression(false);
-      refMarker.done(BuildElementTypes.AUGMENTED_ASSIGNMENT);
-    } else {
-      refMarker.drop();
-    }
-  }
-
-  private void parseReturnStatement() {
-    PsiBuilder.Marker marker = builder.mark();
-    expect(TokenKind.RETURN);
-    if (!STATEMENT_TERMINATOR_SET.contains(currentToken())) {
-      getExpressionParser().parseExpression(false);
-    }
-    marker.done(BuildElementTypes.RETURN_STATEMENT);
-  }
-
-  // suite ::= simple_stmt | (NEWLINE INDENT stmt+ DEDENT)
-  private void parseSuite() {
-    if (!matches(TokenKind.NEWLINE)) {
-      parseSimpleStatement();
-      return;
-    }
-    PsiBuilder.Marker marker = builder.mark();
-    if (expect(TokenKind.INDENT)) {
-      while (!builder.eof() && !matches(TokenKind.DEDENT)) {
-        parseStatement();
-      }
-    }
-    marker.done(BuildElementTypes.STATEMENT_LIST);
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/Argument.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/Argument.java
deleted file mode 100644
index 16ed4f4..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/Argument.java
+++ /dev/null
@@ -1,125 +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.google.idea.blaze.base.lang.buildfile.lexer.BuildToken;
-import com.google.idea.blaze.base.lang.buildfile.lexer.TokenKind;
-import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
-import com.google.idea.blaze.base.lang.buildfile.references.ArgumentReference;
-import com.google.idea.blaze.base.lang.buildfile.references.KeywordArgumentReference;
-import com.intellij.lang.ASTNode;
-import com.intellij.openapi.util.TextRange;
-import com.intellij.psi.PsiReference;
-import com.intellij.psi.tree.IElementType;
-
-import javax.annotation.Nullable;
-
-/**
- * PSI element for an argument, passed via a function call.
- */
-public abstract class Argument extends BuildElementImpl {
-
-  public static final Argument[] EMPTY_ARRAY = new Argument[0];
-
-  public Argument(ASTNode node) {
-    super(node);
-  }
-
-  @Override
-  protected void acceptVisitor(BuildElementVisitor visitor) {
-    visitor.visitArgument(this);
-  }
-
-  /**
-   * The value passed by this argument
-   */
-  @Nullable
-  public Expression getValue() {
-    // for *args, **kwargs, this should be 'args' or 'kwargs' identifiers.
-    // otherwise the expression after the (optional) '='
-    ASTNode node = getNode().getLastChildNode();
-    while (node != null) {
-      IElementType type = node.getElementType();
-      if (BuildElementTypes.EXPRESSIONS.contains(type)) {
-        return (Expression) node.getPsi();
-      }
-      if (type == BuildToken.fromKind(TokenKind.EQUALS)
-        || type == BuildToken.fromKind(TokenKind.STAR)
-        || type == BuildToken.fromKind(TokenKind.STAR_STAR)) {
-        break;
-      }
-      node = node.getTreePrev();
-    }
-    return null;
-  }
-
-  public static class Keyword extends Argument {
-    public Keyword(ASTNode node) {
-      super(node);
-    }
-
-    @Override
-    protected void acceptVisitor(BuildElementVisitor visitor) {
-      visitor.visitKeywordArgument(this);
-    }
-
-    @Nullable
-    public ASTNode getNameNode() {
-      return getNode().findChildByType(BuildToken.IDENTIFIER);
-    }
-
-    @Override
-    @Nullable
-    public String getName() {
-      ASTNode node = getNameNode();
-      return node != null ? node.getText() : null;
-    }
-
-    @Override
-    public KeywordArgumentReference getReference() {
-      ASTNode keywordNode = getNameNode();
-      if (keywordNode != null) {
-        TextRange range = PsiUtils.childRangeInParent(getTextRange(), keywordNode.getTextRange());
-        return new KeywordArgumentReference(this, range);
-      }
-      return null;
-    }
-  }
-
-  public static class Positional extends Argument {
-    public Positional(ASTNode node) {
-      super(node);
-    }
-
-    @Override
-    public PsiReference getReference() {
-      return new ArgumentReference<>(this, getTextRange(), true);
-    }
-  }
-
-  public static class Star extends Argument {
-    public Star(ASTNode node) {
-      super(node);
-    }
-  }
-
-  public static class StarStar extends Argument {
-    public StarStar(ASTNode node) {
-      super(node);
-    }
-  }
-
-}
\ No newline at end of file
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/ArgumentList.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/ArgumentList.java
deleted file mode 100644
index 6c44e32..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/ArgumentList.java
+++ /dev/null
@@ -1,61 +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;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * Argument list of a function call
- */
-public class ArgumentList extends BuildListType<Argument> {
-
-  public ArgumentList(ASTNode astNode) {
-    super(astNode, Argument.class);
-  }
-
-  @Override
-  protected void acceptVisitor(BuildElementVisitor visitor) {
-    visitor.visitFuncallArgList(this);
-  }
-
-  public Argument[] getArguments() {
-    return getElements();
-  }
-
-  @Nullable
-  public Argument.Keyword getKeywordArgument(String name) {
-    ASTNode node = getNode().getFirstChildNode();
-    while (node != null) {
-      if (node.getElementType() == BuildElementTypes.KEYWORD) {
-        Argument.Keyword arg = (Argument.Keyword) node.getPsi();
-        String keyword = arg.getName();
-        if (keyword != null && keyword.equals(name)) {
-          return arg;
-        }
-      }
-      node = node.getTreeNext();
-    }
-    return null;
-  }
-
-  @Nullable
-  public Expression getKeywordArgumentValue(String name) {
-    Argument.Keyword keyword = getKeywordArgument(name);
-    return keyword != null ? keyword.getValue() : null;
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/AssignmentStatement.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/AssignmentStatement.java
deleted file mode 100644
index 225ab46..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/AssignmentStatement.java
+++ /dev/null
@@ -1,65 +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;
-import com.intellij.util.PlatformIcons;
-
-import javax.annotation.Nullable;
-import javax.swing.*;
-
-/**
- * PSI element for an assignment statement [expr ASSIGN_OP expr]
- */
-public class AssignmentStatement extends BuildElementImpl implements Statement {
-
-  public AssignmentStatement(ASTNode astNode) {
-    super(astNode);
-  }
-
-  /**
-   * Returns the LHS of the assignment
-   */
-  @Nullable
-  public TargetExpression getLeftHandSideExpression() {
-    return findChildByClass(TargetExpression.class);
-  }
-
-  /**
-   * Returns the RHS of the assignment
-   */
-  @Nullable
-  public Expression getAssignedValue() {
-    return childToPsi(BuildElementTypes.EXPRESSIONS, 1);
-  }
-
-  @Override
-  protected void acceptVisitor(BuildElementVisitor visitor) {
-    visitor.visitAssignmentStatement(this);
-  }
-
-  @Nullable
-  @Override
-  public String getName() {
-    TargetExpression target = getLeftHandSideExpression();
-    return target != null ? target.getName() : super.getName();
-  }
-
-  @Override
-  public Icon getIcon(int flags) {
-    return PlatformIcons.FIELD_ICON;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/AugmentedAssignmentStatement.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/AugmentedAssignmentStatement.java
deleted file mode 100644
index acc41cb..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/AugmentedAssignmentStatement.java
+++ /dev/null
@@ -1,52 +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;
-
-import javax.annotation.Nullable;
-
-/**
- * PSI element for an augmented assignment statement [expr += expr]
- */
-public class AugmentedAssignmentStatement extends BuildElementImpl implements Statement {
-
-  public AugmentedAssignmentStatement(ASTNode astNode) {
-    super(astNode);
-  }
-
-  /**
-   * Returns the LHS of the assignment
-   */
-  @Nullable
-  public TargetExpression getLeftHandSideExpression() {
-    return childToPsi(BuildElementTypes.EXPRESSIONS, 0);
-  }
-
-  /**
-   * Returns the RHS of the assignment
-   */
-  @Nullable
-  public Expression getAssignedValue() {
-    return childToPsi(BuildElementTypes.EXPRESSIONS, 1);
-  }
-
-  @Override
-  protected void acceptVisitor(BuildElementVisitor visitor) {
-    visitor.visitAugmentedAssignmentStatement(this);
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/BinaryOpExpression.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/BinaryOpExpression.java
deleted file mode 100644
index 3a35fb1..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/BinaryOpExpression.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.lang.buildfile.psi;
-
-import com.intellij.lang.ASTNode;
-
-import javax.annotation.Nullable;
-
-/**
- * PSI element for an binary operation expression [expr BIN_OP expr]
- */
-public class BinaryOpExpression extends BuildElementImpl implements Expression {
-
-  public BinaryOpExpression(ASTNode astNode) {
-    super(astNode);
-  }
-
-  @Override
-  protected void acceptVisitor(BuildElementVisitor visitor) {
-    visitor.visitBinaryOpExpression(this);
-  }
-
-  /**
-   * Returns the LHS of the expression
-   */
-  @Nullable
-  public Expression getLhs() {
-    return childToPsi(BuildElementTypes.EXPRESSIONS, 0);
-  }
-
-  /**
-   * Returns the RHS of the expression
-   */
-  @Nullable
-  public Expression getRhs() {
-    return childToPsi(BuildElementTypes.EXPRESSIONS, 1);
-  }
-}
\ No newline at end of file
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/BooleanLiteral.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/BooleanLiteral.java
deleted file mode 100644
index 4c8d9f3..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/BooleanLiteral.java
+++ /dev/null
@@ -1,34 +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/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildElement.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildElement.java
deleted file mode 100644
index 8e46c7a..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildElement.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.lang.buildfile.psi;
-
-import com.google.idea.blaze.base.model.primitives.WorkspacePath;
-import com.google.idea.blaze.base.lang.buildfile.search.BlazePackage;
-import com.intellij.psi.NavigatablePsiElement;
-import com.intellij.psi.PsiElement;
-
-import javax.annotation.Nullable;
-
-/**
- * Base class for all BUILD file PSI elements
- */
-public interface BuildElement extends NavigatablePsiElement {
-
-  @Nullable
-  static BuildElement asBuildElement(PsiElement psiElement) {
-    return psiElement instanceof BuildElement ? (BuildElement) psiElement : null;
-  }
-
-  Statement[] EMPTY_ARRAY = new Statement[0];
-
-  String getPresentableText();
-
-  @Nullable
-  PsiElement getReferencedElement();
-
-  <P extends PsiElement> P[] childrenOfClass(Class<P> psiClass);
-
-  <P extends PsiElement> P firstChildOfClass(Class<P> psiClass);
-
-  WorkspacePath getWorkspacePath();
-
-  @Nullable
-  BlazePackage getBlazePackage();
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildElementImpl.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildElementImpl.java
deleted file mode 100644
index 9195201..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildElementImpl.java
+++ /dev/null
@@ -1,178 +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.google.idea.blaze.base.lang.buildfile.search.BlazePackage;
-import com.google.idea.blaze.base.model.primitives.WorkspacePath;
-import com.intellij.extapi.psi.ASTWrapperPsiElement;
-import com.intellij.lang.ASTNode;
-import com.intellij.navigation.ItemPresentation;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiElementVisitor;
-import com.intellij.psi.PsiFile;
-import com.intellij.psi.PsiReference;
-import com.intellij.psi.tree.IElementType;
-import com.intellij.psi.tree.TokenSet;
-
-import javax.annotation.Nullable;
-import javax.swing.*;
-import java.lang.reflect.Array;
-import java.util.Arrays;
-
-/**
- * Base PSI class for the BUILD language
- */
-public abstract class BuildElementImpl extends ASTWrapperPsiElement implements BuildElement {
-
-  public BuildElementImpl(ASTNode astNode) {
-    super(astNode);
-  }
-
-  public <P extends PsiElement> P getPsiChild(IElementType type, Class<P> psiClass) {
-    ASTNode childNode = getNode().findChildByType(type);
-    return childNode != null ? (P) childNode.getPsi() : null;
-  }
-
-  @Override
-  public <P extends PsiElement> P[] childrenOfClass(Class<P> psiClass) {
-    return findChildrenByClass(psiClass);
-  }
-
-  @Override
-  public <P extends PsiElement> P firstChildOfClass(Class<P> psiClass) {
-    return findChildByClass(psiClass);
-  }
-
-  /**
-   * Returns the BuildElement child at the specified index,
-   * where index is calculated after filtering out non-BuildElement children.
-   * @return null if index >= number of BuildElement children
-   */
-  @Nullable
-  protected BuildElement getBuildElementChild(int index) {
-    BuildElement[] children = buildElementChildren();
-    return children.length <= index ? null : children[index];
-  }
-
-  protected BuildElement[] buildElementChildren() {
-    return Arrays.stream(getNode().getChildren(null))
-      .map(ASTNode::getPsi)
-      .filter(psiElement -> psiElement instanceof BuildElement)
-      .toArray(BuildElement[]::new);
-  }
-
-  protected <T extends BuildElement> T[] childrenToPsi(TokenSet filterSet, T[] array) {
-    final ASTNode[] nodes = getNode().getChildren(filterSet);
-    T[] psiElements = (T[]) Array.newInstance(array.getClass().getComponentType(), nodes.length);
-    for (int i = 0; i < nodes.length; i++) {
-      psiElements[i] = (T) nodes[i].getPsi();
-    }
-    return psiElements;
-  }
-
-  @Nullable
-  protected <T extends BuildElement> T childToPsi(TokenSet filterSet, int index) {
-    final ASTNode[] nodes = getNode().getChildren(filterSet);
-    if (nodes.length <= index) {
-      return null;
-    }
-    return (T) nodes[index].getPsi();
-  }
-
-  @Nullable
-  protected IElementType getParentType() {
-    ASTNode node = getNode().getTreeParent();
-    return node != null ? node.getElementType() : null;
-  }
-
-  public String nonNullName() {
-    String name = getName();
-    return name != null ? name : "<unnamed>";
-  }
-
-  @Override
-  public String getPresentableText() {
-    return nonNullName();
-  }
-
-  @Override
-  public String toString() {
-    return super.toString() + ": " + getPresentableText();
-  }
-
-  @Override
-  public void accept(PsiElementVisitor visitor) {
-    if (visitor instanceof BuildElementVisitor) {
-      acceptVisitor(((BuildElementVisitor) visitor));
-    } else {
-      super.accept(visitor);
-    }
-  }
-
-  protected abstract void acceptVisitor(BuildElementVisitor visitor);
-
-  @Nullable
-  @Override
-  public PsiElement getReferencedElement() {
-    PsiReference[] refs = getReferences();
-    for (PsiReference ref : refs) {
-      PsiElement element = ref.resolve();
-      if (element != null) {
-        return element;
-      }
-    }
-    return null;
-  }
-
-  @Override
-  public ItemPresentation getPresentation() {
-    final BuildElement element = this;
-    return new ItemPresentation() {
-      @Override
-      public String getPresentableText() {
-        return element.getPresentableText();
-      }
-      @Override
-      public String getLocationString() {
-        return null;
-      }
-      @Override
-      public Icon getIcon(boolean unused) {
-        return element.getIcon(0);
-      }
-    };
-  }
-
-  @Nullable
-  @Override
-  public WorkspacePath getWorkspacePath() {
-    BuildFile file = (BuildFile) getContainingFile();
-    return file.getWorkspacePath();
-  }
-
-  @Nullable
-  @Override
-  public BlazePackage getBlazePackage() {
-    PsiFile file = getContainingFile();
-    return file != null ? BlazePackage.getContainingPackage(file) : null;
-  }
-
-  @Nullable
-  @Override
-  public BuildFile getContainingFile() {
-    return (BuildFile) super.getContainingFile();
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildElementType.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildElementType.java
deleted file mode 100644
index a93a210..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildElementType.java
+++ /dev/null
@@ -1,50 +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.google.idea.blaze.base.lang.buildfile.language.BuildFileType;
-import com.intellij.lang.ASTNode;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.tree.IElementType;
-
-import java.lang.reflect.Constructor;
-
-/**
- * IElementTypes used in the AST by the parser (as opposed to the types used by the lexer).<br>
- * Modelled on IntelliJ's java and python language support conventions.
- */
-public class BuildElementType extends IElementType {
-
-  private static final Class[] PARAMETER_TYPES = new Class[]{ASTNode.class};
-  private final Class<? extends PsiElement> psiElementClass;
-  private Constructor<? extends PsiElement> constructor;
-
-  public BuildElementType(String name, Class<? extends PsiElement> psiElementClass) {
-    super(name, BuildFileType.INSTANCE.getLanguage());
-    this.psiElementClass = psiElementClass;
-  }
-
-  public PsiElement createElement(ASTNode node) {
-    try {
-      if (constructor == null) {
-        constructor = psiElementClass.getConstructor(PARAMETER_TYPES);
-      }
-      return constructor.newInstance(node);
-    } catch (Exception e) {
-      throw new IllegalStateException("No necessary constructor for " + node.getElementType(), e);
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildElementTypes.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildElementTypes.java
deleted file mode 100644
index 44ead8a..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildElementTypes.java
+++ /dev/null
@@ -1,119 +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.google.idea.blaze.base.lang.buildfile.language.BuildFileType;
-import com.intellij.psi.tree.IFileElementType;
-import com.intellij.psi.tree.TokenSet;
-
-/**
- * Collects the types used by the PsiBuilder to construct the AST
- */
-public interface BuildElementTypes {
-
-  IFileElementType BUILD_FILE = new IFileElementType(BuildFileType.INSTANCE.getLanguage());
-
-  // Statements
-  BuildElementType RETURN_STATEMENT = new BuildElementType("return", ReturnStatement.class);
-  BuildElementType PASS_STATMENT = new BuildElementType("pass", PassStatement.class);
-  BuildElementType ASSIGNMENT_STATEMENT = new BuildElementType("assignment", AssignmentStatement.class);
-  BuildElementType AUGMENTED_ASSIGNMENT = new BuildElementType("aug_assign", AugmentedAssignmentStatement.class);
-  BuildElementType FLOW_STATEMENT = new BuildElementType("flow", FlowStatement.class);
-  BuildElementType LOAD_STATEMENT = new BuildElementType("load", LoadStatement.class);
-  BuildElementType FUNCTION_STATEMENT = new BuildElementType("function_def", FunctionStatement.class);
-  BuildElementType FOR_STATEMENT = new BuildElementType("for", ForStatement.class);
-  BuildElementType IF_STATEMENT = new BuildElementType("if", IfStatement.class);
-
-  BuildElementType IF_PART = new BuildElementType("if_part", IfPart.class);
-  BuildElementType ELSE_IF_PART = new BuildElementType("else_if_part", ElseIfPart.class);
-  BuildElementType ELSE_PART = new BuildElementType("else_part", ElsePart.class);
-
-  BuildElementType STATEMENT_LIST = new BuildElementType("stmt_list", StatementList.class);
-
-  // passed arguments
-  BuildElementType ARGUMENT_LIST = new BuildElementType("arg_list", ArgumentList.class);
-  BuildElementType KEYWORD = new BuildElementType("keyword", Argument.Keyword.class);
-  BuildElementType POSITIONAL = new BuildElementType("positional", Argument.Positional.class);
-  BuildElementType STAR = new BuildElementType("*", Argument.Star.class);
-  BuildElementType STAR_STAR = new BuildElementType("**", Argument.StarStar.class);
-
-  // parameters
-  BuildElementType PARAMETER_LIST = new BuildElementType("parameter_list", ParameterList.class);
-  BuildElementType PARAM_OPTIONAL = new BuildElementType("optional_param", Parameter.Optional.class);
-  BuildElementType PARAM_MANDATORY = new BuildElementType("mandatory_param", Parameter.Mandatory.class);
-  BuildElementType PARAM_STAR = new BuildElementType("*", Parameter.Star.class);
-  BuildElementType PARAM_STAR_STAR = new BuildElementType("**", Parameter.StarStar.class);
-
-  // Expressions
-  BuildElementType DICTIONARY_LITERAL = new BuildElementType("dict", DictionaryLiteral.class);
-  BuildElementType DICTIONARY_ENTRY_LITERAL = new BuildElementType("dict_entry", DictionaryEntryLiteral.class);
-  BuildElementType BINARY_OP_EXPRESSION = new BuildElementType("binary_op", BinaryOpExpression.class);
-  BuildElementType FUNCALL_EXPRESSION = new BuildElementType("function_call", FuncallExpression.class);
-  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 = new BuildElementType("reference", ReferenceExpression.class);
-  BuildElementType TARGET_EXPRESSION = new BuildElementType("target", TargetExpression.class);
-  BuildElementType LIST_COMPREHENSION_EXPR = new BuildElementType("list_comp", ListComprehensionExpression.class);
-
-  TokenSet EXPRESSIONS = TokenSet.create(
-    FUNCALL_EXPRESSION,
-    DICTIONARY_LITERAL,
-    DICTIONARY_ENTRY_LITERAL,
-    BINARY_OP_EXPRESSION,
-    DOT_EXPRESSION,
-    STRING_LITERAL,
-    INTEGER_LITERAL,
-    BOOLEAN_LITERAL,
-    LIST_LITERAL,
-    REFERENCE_EXPRESSION,
-    TARGET_EXPRESSION,
-    LIST_COMPREHENSION_EXPR,
-    GLOB_EXPRESSION
-  );
-
-  TokenSet STATEMENTS = TokenSet.create(
-    RETURN_STATEMENT,
-    PASS_STATMENT,
-    ASSIGNMENT_STATEMENT,
-    FLOW_STATEMENT,
-    LOAD_STATEMENT,
-    FUNCTION_STATEMENT,
-    FOR_STATEMENT,
-    IF_STATEMENT
-  );
-
-  TokenSet ARGUMENTS = TokenSet.create(
-    KEYWORD,
-    POSITIONAL,
-    STAR,
-    STAR_STAR
-  );
-
-  TokenSet PARAMETERS = TokenSet.create(
-    PARAM_OPTIONAL,
-    PARAM_MANDATORY,
-    PARAM_STAR,
-    PARAM_STAR_STAR
-  );
-
-  TokenSet STRINGS = TokenSet.create(STRING_LITERAL);
-  TokenSet FUNCTIONS = TokenSet.create(FUNCTION_STATEMENT);
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildElementVisitor.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildElementVisitor.java
deleted file mode 100644
index 464fa93..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildElementVisitor.java
+++ /dev/null
@@ -1,148 +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.psi.PsiElementVisitor;
-
-/**
- * Visitor for BUILD file PSI nodes
- */
-public class BuildElementVisitor extends PsiElementVisitor {
-
-  public void visitAssignmentStatement(AssignmentStatement node) {
-    visitElement(node);
-  }
-
-  public void visitAugmentedAssignmentStatement(AugmentedAssignmentStatement node) {
-    visitElement(node);
-  }
-
-  public void visitReturnStatement(ReturnStatement node) {
-    visitElement(node);
-  }
-
-  public void visitArgument(Argument node) {
-    visitElement(node);
-  }
-
-  public void visitKeywordArgument(Argument.Keyword node) {
-    visitElement(node);
-  }
-
-  public void visitParameter(Parameter node) {
-    visitElement(node);
-  }
-
-  public void visitLoadStatement(LoadStatement node) {
-    visitElement(node);
-  }
-
-  public void visitIfStatement(IfStatement node) {
-    visitElement(node);
-  }
-
-  public void visitIfPart(IfPart node) {
-    visitElement(node);
-  }
-
-  public void visitElsePart(ElsePart node) {
-    visitElement(node);
-  }
-
-  public void visitElseIfPart(ElseIfPart node) {
-    visitElement(node);
-  }
-
-  public void visitFunctionStatement(FunctionStatement node) {
-    visitElement(node);
-  }
-
-  public void visitFuncallExpression(FuncallExpression node) {
-    visitElement(node);
-  }
-
-  public void visitForStatement(ForStatement node) {
-    visitElement(node);
-  }
-
-  public void visitFlowStatement(FlowStatement node) {
-    visitElement(node);
-  }
-
-  public void visitDotExpression(DotExpression node) {
-    visitElement(node);
-  }
-
-  public void visitDictionaryLiteral(DictionaryLiteral node) {
-    visitElement(node);
-  }
-
-  public void visitDictionaryEntryLiteral(DictionaryEntryLiteral node) {
-    visitElement(node);
-  }
-
-  public void visitBinaryOpExpression(BinaryOpExpression node) {
-    visitElement(node);
-  }
-
-  public void visitStringLiteral(StringLiteral node) {
-    visitElement(node);
-  }
-
-  public void visitIntegerLiteral(IntegerLiteral node) {
-    visitElement(node);
-  }
-
-  public void visitBooleanLiteral(BooleanLiteral node) {
-    visitElement(node);
-  }
-
-  public void visitListLiteral(ListLiteral node) {
-    visitElement(node);
-  }
-
-  public void visitStatementList(StatementList node) {
-    visitElement(node);
-  }
-
-  public void visitFuncallArgList(ArgumentList node) {
-    visitElement(node);
-  }
-
-  public void visitReferenceExpression(ReferenceExpression node) {
-    visitElement(node);
-  }
-
-  public void visitTargetExpression(TargetExpression node) {
-    visitElement(node);
-  }
-
-  public void visitListComprehensionSuffix(ListComprehensionExpression node) {
-    visitElement(node);
-  }
-
-  public void visitFunctionParameterList(ParameterList node) {
-    visitElement(node);
-  }
-
-  public void visitGlobExpression(GlobExpression node)  {
-    visitElement(node);
-  }
-
-  public void visitPassStatement(PassStatement node) {
-    visitElement(node);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildFile.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildFile.java
deleted file mode 100644
index 4c87720..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildFile.java
+++ /dev/null
@@ -1,283 +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.google.common.collect.Lists;
-import com.google.idea.blaze.base.lang.buildfile.language.BuildFileType;
-import com.google.idea.blaze.base.lang.buildfile.references.BuildReferenceManager;
-import com.google.idea.blaze.base.lang.buildfile.search.BlazePackage;
-import com.google.idea.blaze.base.lang.buildfile.search.ResolveUtil;
-import com.google.idea.blaze.base.model.primitives.WorkspacePath;
-import com.intellij.extapi.psi.PsiFileBase;
-import com.intellij.lang.ASTNode;
-import com.intellij.openapi.fileTypes.FileType;
-import com.intellij.openapi.project.Project;
-import com.intellij.psi.FileViewProvider;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiNamedElement;
-import com.intellij.psi.tree.TokenSet;
-import com.intellij.util.PathUtil;
-import com.intellij.util.Processor;
-import icons.BlazeIcons;
-
-import javax.annotation.Nullable;
-import javax.swing.*;
-import java.io.File;
-import java.util.List;
-
-/**
- * Build file PSI element
- */
-public class BuildFile extends PsiFileBase implements BuildElement {
-
-  public enum BlazeFileType {
-    SkylarkExtension,
-    BuildPackage // "BUILD", plus hacks such as "BUILD.tools", "BUILD.bazel"
-  }
-
-  @Nullable
-  public static WorkspacePath getWorkspacePath(Project project, String filePath) {
-    return BuildReferenceManager.getInstance(project).getWorkspaceRelativePath(filePath);
-  }
-
-  public static String getBuildFileString(Project project, String filePath) {
-    WorkspacePath workspacePath = getWorkspacePath(project, PathUtil.getParentPath(filePath));
-    if (workspacePath == null) {
-      return "BUILD file: " + filePath;
-    }
-    String fileName = PathUtil.getFileName(filePath);
-    if (fileName.startsWith("BUILD")) {
-      return "//" + workspacePath + "/" + fileName;
-    }
-    return "//" + workspacePath + ":" + fileName;
-  }
-
-  public BuildFile(FileViewProvider viewProvider) {
-    super(viewProvider, BuildFileType.INSTANCE.getLanguage());
-  }
-
-  @Override
-  public FileType getFileType() {
-    return BuildFileType.INSTANCE;
-  }
-
-  public BlazeFileType getBlazeFileType() {
-    String fileName = getFileName();
-    if (fileName.startsWith("BUILD")) {
-      return BlazeFileType.BuildPackage;
-    }
-    return BlazeFileType.SkylarkExtension;
-  }
-
-  @Nullable
-  @Override
-  public BlazePackage getBlazePackage() {
-    return BlazePackage.getContainingPackage(this);
-  }
-
-  public String getFileName() {
-    return getViewProvider().getVirtualFile().getName();
-  }
-
-  public String getFilePath() {
-    return getOriginalFile().getViewProvider().getVirtualFile().getPath();
-  }
-
-  public File getFile() {
-    return new File(getFilePath());
-  }
-
-  @Nullable
-  @Override
-  public WorkspacePath getWorkspacePath() {
-    return getWorkspacePath(getProject(), getFilePath());
-  }
-
-  /**
-   * The workspace path of the containing blaze package
-   * (this is always the parent directory for BUILD files, but may be a more distant ancestor for Skylark extensions)
-   */
-  @Nullable
-  public WorkspacePath getPackageWorkspacePath() {
-    BlazePackage parentPackage = getBlazePackage();
-    if (parentPackage == null) {
-      return null;
-    }
-    String filePath = parentPackage.buildFile.getFilePath();
-    return filePath != null ? getWorkspacePath(getProject(), PathUtil.getParentPath(filePath)) : null;
-  }
-
-  @Nullable
-  public String getWorkspaceRelativePackagePath() {
-    WorkspacePath packagePath = getPackageWorkspacePath();
-    return packagePath != null ? packagePath.relativePath() : null;
-  }
-
-  /**
-   * Finds a top-level rule with a "name" keyword argument with the given value.
-   */
-  @Nullable
-  public FuncallExpression findRule(String name) {
-    for (FuncallExpression expr : findChildrenByClass(FuncallExpression.class)) {
-      String ruleName = expr.getNameArgumentValue();
-      if (name.equals(ruleName)) {
-        return expr;
-      }
-    }
-    return null;
-  }
-
-  /**
-   * .bzl files referenced in 'load' statements
-   */
-  @Nullable
-  public String[] getImportedPaths() {
-    ASTNode[] loadStatements = getNode().getChildren(TokenSet.create(BuildElementTypes.LOAD_STATEMENT));
-    if (loadStatements.length == 0) {
-      return null;
-    }
-    List<String> importedPaths = Lists.newArrayListWithCapacity(loadStatements.length);
-    for (int i = 0; i < loadStatements.length; i++) {
-      String path = ((LoadStatement) loadStatements[i].getPsi()).getImportedPath();
-      if (path != null) {
-        importedPaths.add(path);
-      }
-    }
-    return importedPaths.toArray(new String[importedPaths.size()]);
-  }
-
-  @Nullable
-  public FunctionStatement findDeclaredFunction(String name) {
-    for (FunctionStatement fn : getFunctionDeclarations()) {
-      if (name.equals(fn.getName())) {
-        return fn;
-      }
-    }
-    return null;
-  }
-
-  @Nullable
-  public TargetExpression findTopLevelVariable(String name) {
-    return ResolveUtil.searchChildAssignmentStatements(this, name);
-  }
-
-  @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();
-          return element instanceof FunctionStatement ? (FunctionStatement) element : null;
-        }
-      }
-    }
-    return null;
-  }
-
-  public BuildElement findSymbolInScope(String name) {
-    BuildElement[] resultHolder = new BuildElement[1];
-    Processor<BuildElement> processor = buildElement -> {
-      if (buildElement instanceof StringLiteral) {
-        buildElement = BuildElement.asBuildElement(buildElement.getReferencedElement());
-      }
-      if (buildElement instanceof PsiNamedElement
-          && name.equals(buildElement.getName())) {
-        resultHolder[0] = buildElement;
-        return false;
-      }
-      return true;
-    };
-    searchSymbolsInScope(processor, null);
-    return resultHolder[0];
-  }
-
-  /**
-   * Iterates over all top-level assignment statements, function definitions and loaded symbols.
-   * @return false if searching was stopped (e.g. because the desired element was found).
-   */
-  public boolean searchSymbolsInScope(Processor<BuildElement> processor, @Nullable PsiElement stopAtElement) {
-    for (BuildElement child : findChildrenByClass(BuildElement.class)) {
-      if (child == stopAtElement) {
-        return true;
-      }
-      if (child instanceof AssignmentStatement) {
-        TargetExpression target = ((AssignmentStatement) child).getLeftHandSideExpression();
-        if (target != null && !processor.process(target)) {
-          return false;
-        }
-      } else if (child instanceof FunctionStatement) {
-        if (!processor.process(child)) {
-          return false;
-        }
-      } else if (child instanceof LoadStatement) {
-        for (StringLiteral importedSymbol : ((LoadStatement) child).getImportedSymbolElements()) {
-          if (!processor.process(importedSymbol)) {
-            return false;
-          }
-        }
-      }
-    }
-    return true;
-  }
-
-  /**
-   * Searches functions declared in this file, then loaded Skylark extensions, if relevant.
-   */
-  @Nullable
-  public FunctionStatement findFunctionInScope(String name) {
-    FunctionStatement localFn = findDeclaredFunction(name);
-    if (localFn != null) {
-      return localFn;
-    }
-    return findLoadedFunction(name);
-  }
-
-  public FunctionStatement[] getFunctionDeclarations() {
-    return findChildrenByClass(FunctionStatement.class);
-  }
-
-  @Override
-  public Icon getIcon(int flags) {
-    return BlazeIcons.BuildFile;
-  }
-
-  @Override
-  public String getPresentableText() {
-    return toString();
-  }
-
-  @Override
-  public String toString() {
-    return getBuildFileString(getProject(), getFilePath());
-  }
-
-  @Nullable
-  @Override
-  public PsiElement getReferencedElement() {
-    return null;
-  }
-
-  @Override
-  public <P extends PsiElement> P[] childrenOfClass(Class<P> psiClass) {
-    return findChildrenByClass(psiClass);
-  }
-
-  @Override
-  public <P extends PsiElement> P firstChildOfClass(Class<P> psiClass) {
-    return findChildByClass(psiClass);
-  }
-}
-
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildListType.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildListType.java
deleted file mode 100644
index 2510bc1..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildListType.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.lang.buildfile.psi;
-
-import com.intellij.lang.ASTNode;
-
-import javax.annotation.Nullable;
-
-/**
- * Common interface for BUILD psi elements containing a list / sequence of child elements.
- */
-public abstract class BuildListType<E extends BuildElement> extends BuildElementImpl {
-
-  private final Class<E> elementClass;
-
-  public BuildListType(ASTNode astNode, Class<E> elementClass) {
-    super(astNode);
-    this.elementClass = elementClass;
-  }
-
-  public E[] getElements() {
-    return findChildrenByClass(elementClass);
-  }
-
-  @Nullable
-  public E getFirstElement() {
-    return findChildByClass(elementClass);
-  }
-
-  public boolean isEmpty() {
-    return getFirstElement() != null;
-  }
-
-  /**
-   * The offset into the document at which child elements start.
-   * For lists wrapped in braces, this is the offset after the opening brace.
-   * For statement lists, this is the offset after the colon.
-   */
-  public int getStartOffset() {
-    return getNode().getStartOffset() + 1;
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/DictionaryEntryLiteral.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/DictionaryEntryLiteral.java
deleted file mode 100644
index 26eb53b..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/DictionaryEntryLiteral.java
+++ /dev/null
@@ -1,34 +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 element for an dictionary entry literal (dictEntry ::= nonTupleExpr ':' nonTupleExpr)
- */
-public class DictionaryEntryLiteral extends BuildElementImpl implements LiteralExpression {
-
-  public DictionaryEntryLiteral(ASTNode astNode) {
-    super(astNode);
-  }
-
-  @Override
-  protected void acceptVisitor(BuildElementVisitor visitor) {
-    visitor.visitDictionaryEntryLiteral(this);
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/DictionaryLiteral.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/DictionaryLiteral.java
deleted file mode 100644
index 2b6cdba..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/DictionaryLiteral.java
+++ /dev/null
@@ -1,34 +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 element for an dictionary literal.
- */
-public class DictionaryLiteral extends BuildListType<DictionaryEntryLiteral> implements LiteralExpression {
-
-  public DictionaryLiteral(ASTNode astNode) {
-    super(astNode, DictionaryEntryLiteral.class);
-  }
-
-  @Override
-  protected void acceptVisitor(BuildElementVisitor visitor) {
-    visitor.visitDictionaryLiteral(this);
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/DotExpression.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/DotExpression.java
deleted file mode 100644
index aceedc4..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/DotExpression.java
+++ /dev/null
@@ -1,34 +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 element for a dot expression.
- */
-public class DotExpression extends BuildElementImpl implements Expression {
-
-  public DotExpression(ASTNode astNode) {
-    super(astNode);
-  }
-
-  @Override
-  protected void acceptVisitor(BuildElementVisitor visitor) {
-    visitor.visitDotExpression(this);
-  }
-
-}
\ No newline at end of file
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/ElseIfPart.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/ElseIfPart.java
deleted file mode 100644
index cfce805..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/ElseIfPart.java
+++ /dev/null
@@ -1,34 +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 element for an elif part of an IfStatement.
- */
-public class ElseIfPart extends BuildElementImpl implements Statement, StatementListContainer {
-
-  public ElseIfPart(ASTNode astNode) {
-    super(astNode);
-  }
-
-  @Override
-  protected void acceptVisitor(BuildElementVisitor visitor) {
-    visitor.visitElseIfPart(this);
-  }
-
-}
\ No newline at end of file
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/ElsePart.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/ElsePart.java
deleted file mode 100644
index 911089a..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/ElsePart.java
+++ /dev/null
@@ -1,34 +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 element for an else part of an IfStatement.
- */
-public class ElsePart extends BuildElementImpl implements Statement, StatementListContainer {
-
-  public ElsePart(ASTNode astNode) {
-    super(astNode);
-  }
-
-  @Override
-  protected void acceptVisitor(BuildElementVisitor visitor) {
-    visitor.visitElsePart(this);
-  }
-
-}
\ No newline at end of file
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/Expression.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/Expression.java
deleted file mode 100644
index 12b8166..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/Expression.java
+++ /dev/null
@@ -1,26 +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;
-
-/**
- * Interface for all expression PSI elements in a BUILD file
- */
-public interface Expression extends BuildElement {
-
-  Expression[] EMPTY_ARRAY = new Expression[0];
-
-}
-
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/FlowStatement.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/FlowStatement.java
deleted file mode 100644
index e7e93d1..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/FlowStatement.java
+++ /dev/null
@@ -1,34 +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 element for a flow statement.
- */
-public class FlowStatement extends BuildElementImpl implements Statement {
-
-  public FlowStatement(ASTNode astNode) {
-    super(astNode);
-  }
-
-  @Override
-  protected void acceptVisitor(BuildElementVisitor visitor) {
-    visitor.visitFlowStatement(this);
-  }
-
-}
\ No newline at end of file
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/ForStatement.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/ForStatement.java
deleted file mode 100644
index f87744f..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/ForStatement.java
+++ /dev/null
@@ -1,61 +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.google.common.collect.Lists;
-import com.google.idea.blaze.base.lang.buildfile.lexer.BuildToken;
-import com.google.idea.blaze.base.lang.buildfile.lexer.TokenKind;
-import com.intellij.lang.ASTNode;
-import com.intellij.psi.PsiElement;
-
-import java.util.List;
-
-/**
- * PSI element for a for statement.
- */
-public class ForStatement extends BuildElementImpl implements Statement, StatementListContainer {
-
-  public ForStatement(ASTNode astNode) {
-    super(astNode);
-  }
-
-  @Override
-  protected void acceptVisitor(BuildElementVisitor visitor) {
-    visitor.visitForStatement(this);
-  }
-
-  public List<Expression> getForLoopVariables() {
-    List<Expression> loopVariableExpressions = Lists.newArrayList();
-    for (PsiElement child : getChildren()) {
-      if (child.getNode().getElementType() == BuildToken.fromKind(TokenKind.IN)) {
-        return loopVariableExpressions;
-      }
-      if (child instanceof Expression) {
-        loopVariableExpressions.add((Expression) child);
-      } else if (child instanceof ListLiteral) {
-        for (Expression expr : ((ListLiteral) child).childrenOfClass(Expression.class)) {
-          loopVariableExpressions.add(expr);
-        }
-      }
-    }
-    return loopVariableExpressions;
-  }
-
-  @Override
-  public String getPresentableText() {
-    return "for loop";
-  }
-}
\ No newline at end of file
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/FuncallExpression.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/FuncallExpression.java
deleted file mode 100644
index d8ff4c7..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/FuncallExpression.java
+++ /dev/null
@@ -1,211 +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.google.idea.blaze.base.lang.buildfile.language.semantics.BuildLanguageSpec;
-import com.google.idea.blaze.base.lang.buildfile.language.semantics.BuildLanguageSpecProvider;
-import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
-import com.google.idea.blaze.base.lang.buildfile.references.FuncallReference;
-import com.google.idea.blaze.base.lang.buildfile.references.LabelUtils;
-import com.google.idea.blaze.base.model.primitives.Label;
-import com.intellij.lang.ASTNode;
-import com.intellij.openapi.util.TextRange;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiNameIdentifierOwner;
-import com.intellij.util.IncorrectOperationException;
-import com.intellij.util.Processor;
-import icons.BlazeIcons;
-
-import javax.annotation.Nullable;
-import javax.swing.*;
-
-/**
- * PSI element for an function call.<br>
- * Could be a top-level rule, Skylark function reference, or general some other python function call
- */
-public class FuncallExpression extends BuildElementImpl implements Expression, PsiNameIdentifierOwner {
-
-  public FuncallExpression(ASTNode astNode) {
-    super(astNode);
-  }
-
-  @Override
-  protected void acceptVisitor(BuildElementVisitor visitor) {
-    visitor.visitFuncallExpression(this);
-  }
-
-  /**
-   * The name of the function being called.
-   */
-  @Nullable
-  public String getFunctionName() {
-    ASTNode node = getFunctionNameNode();
-    return node != null ? node.getText() : null;
-  }
-
-  @Override
-  @Nullable
-  public String getName() {
-    return getNameArgumentValue();
-  }
-
-  @Nullable
-  @Override
-  public PsiElement getNameIdentifier() {
-    Argument.Keyword name = getNameArgument();
-    return name != null ? name.getValue() : null;
-  }
-
-  @Override
-  public PsiElement setName(String name) throws IncorrectOperationException {
-    StringLiteral nameNode = getNameArgumentValueNode();
-    if (nameNode == null) {
-      return this;
-    }
-    ASTNode newChild = PsiUtils.createNewLabel(getProject(), name);
-    nameNode.getNode().replaceChild(nameNode.getNode().getFirstChildNode(), newChild);
-    return this;
-  }
-
-  /**
-   * The function name
-   */
-  @Nullable
-  public ASTNode getFunctionNameNode() {
-    PsiElement argList = getArgList();
-    if (argList != null) {
-      // We want the reference expr directly prior to the open parenthesis.
-      // This accounts for Skylark native.rule calls.
-      PsiElement prev = argList.getPrevSibling();
-      if (prev instanceof ReferenceExpression) {
-        return prev.getNode();
-      }
-    }
-    return getNode().findChildByType(BuildElementTypes.REFERENCE_EXPRESSION);
-  }
-
-  /**
-   * Top-level funcalls are almost always BUILD rules.
-   */
-  public boolean isTopLevel() {
-    ASTNode parent = getNode().getTreeParent();
-    return parent == null || parent.getElementType() == BuildElementTypes.BUILD_FILE;
-  }
-
-  public boolean mightBeBuildRule() {
-    return isTopLevel() || getNameArgument() != null;
-  }
-
-  @Nullable
-  public Label resolveBuildLabel() {
-    return LabelUtils.createLabelFromRuleName(getBlazePackage(), getNameArgumentValue());
-  }
-
-  @Nullable
-  public ArgumentList getArgList() {
-    return findChildByType(BuildElementTypes.ARGUMENT_LIST);
-  }
-
-  public Argument[] getArguments() {
-    ArgumentList argList = getArgList();
-    return argList != null ? argList.getArguments() : Argument.EMPTY_ARRAY;
-  }
-
-  /**
-   * Keyword argument with name "name", if one is present.
-   */
-  @Nullable
-  public Argument.Keyword getNameArgument() {
-    return getKeywordArgument("name");
-  }
-
-  public Argument.Keyword getKeywordArgument(String name) {
-    ArgumentList argList = getArgList();
-    return argList != null ? argList.getKeywordArgument(name) : null;
-  }
-
-  /**
-   * StringLiteral value of keyword argument with name "name", if one is present.
-   */
-  @Nullable
-  public StringLiteral getNameArgumentValueNode() {
-    Argument.Keyword name = getNameArgument();
-    Expression expr = name != null ? name.getValue() : null;
-    if (expr instanceof StringLiteral) {
-      return ((StringLiteral) expr);
-    }
-    return null;
-  }
-
-  /**
-   * Value of keyword argument with name "name", if one is present.
-   */
-  @Nullable
-  public String getNameArgumentValue() {
-    StringLiteral node = getNameArgumentValueNode();
-    return node != null ? node.getStringContents() : null;
-  }
-
-  @Override
-  public Icon getIcon(int flags) {
-    return mightBeBuildRule() ? BlazeIcons.BuildRule : null;
-  }
-
-  @Override
-  public String getPresentableText() {
-    String name = getFunctionName();
-    if (name == null) {
-      return super.getPresentableText();
-    }
-    String targetName = getNameArgumentValue();
-    return targetName != null ? name + "(\"" + targetName + "\")" : name;
-  }
-
-  @Override
-  @Nullable
-  public FuncallReference getReference() {
-    ASTNode nameNode = getFunctionNameNode();
-    if (nameNode == null) {
-      return null;
-    }
-    BuildLanguageSpec spec = BuildLanguageSpecProvider.getInstance().getLanguageSpec(getProject());
-    if (spec != null && spec.hasRule(nameNode.getText())) {
-      // don't try to follow references to built-in rules
-      return null;
-    }
-    TextRange range =  PsiUtils.childRangeInParent(getTextRange(), nameNode.getTextRange());
-    return new FuncallReference(this, range);
-  }
-
-  /**
-   * Searches all StringLiteral children of this element, for one which references the desired target expression.
-   */
-  @Nullable
-  public StringLiteral findChildReferenceToTarget(final FuncallExpression targetRule) {
-    final StringLiteral[] child = new StringLiteral[1];
-    Processor<StringLiteral> processor = stringLiteral -> {
-      PsiElement ref = stringLiteral.getReferencedElement();
-      if (targetRule.equals(ref)) {
-        child[0] = stringLiteral;
-        return false;
-      }
-      return true;
-    };
-    PsiUtils.processChildrenOfType(this, processor, StringLiteral.class);
-    return child[0];
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/FunctionStatement.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/FunctionStatement.java
deleted file mode 100644
index ad1dcc8..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/FunctionStatement.java
+++ /dev/null
@@ -1,59 +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;
-import com.intellij.util.PlatformIcons;
-import org.jetbrains.annotations.Nullable;
-
-import javax.swing.*;
-
-/**
- * PSI element for a function definition statement.
- */
-public class FunctionStatement extends NamedBuildElement implements Statement, StatementListContainer {
-
-  public FunctionStatement(ASTNode astNode) {
-    super(astNode);
-  }
-
-  @Override
-  protected void acceptVisitor(BuildElementVisitor visitor) {
-    visitor.visitFunctionStatement(this);
-  }
-
-  @Nullable
-  @Override
-  public Icon getIcon(int flags) {
-    return PlatformIcons.FUNCTION_ICON;
-  }
-
-  @Nullable
-  public ParameterList getParameterList() {
-    return getPsiChild(BuildElementTypes.PARAMETER_LIST, ParameterList.class);
-  }
-
-  public Parameter[] getParameters() {
-    ParameterList list = getParameterList();
-    return list != null ? list.getElements() : Parameter.EMPTY_ARRAY;
-  }
-
-  @Override
-  public String getPresentableText() {
-    return nonNullName() + getParameterList().getPresentableText();
-  }
-
-}
\ No newline at end of file
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/GlobExpression.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/GlobExpression.java
deleted file mode 100644
index 622e427..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/GlobExpression.java
+++ /dev/null
@@ -1,125 +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.google.idea.blaze.base.lang.buildfile.references.GlobReference;
-import com.intellij.lang.ASTNode;
-import com.intellij.openapi.util.TextRange;
-
-import javax.annotation.Nullable;
-
-/**
- * PSI element for a glob expression.
- */
-public class GlobExpression extends BuildElementImpl implements Expression {
-
-  public GlobExpression(ASTNode astNode) {
-    super(astNode);
-  }
-
-  @Override
-  protected void acceptVisitor(BuildElementVisitor visitor) {
-    visitor.visitGlobExpression(this);
-  }
-
-  @Nullable
-  public ArgumentList getArgList() {
-    return findChildByType(BuildElementTypes.ARGUMENT_LIST);
-  }
-
-  public Argument[] getArguments() {
-    ArgumentList argList = getArgList();
-    return argList != null ? argList.getArguments() : Argument.EMPTY_ARRAY;
-  }
-
-  @Nullable
-  public Argument.Keyword getKeywordArgument(String name) {
-    ArgumentList list = getArgList();
-    return list != null ? list.getKeywordArgument(name) : null;
-  }
-
-  @Nullable
-  public Expression getIncludes() {
-    Argument arg = getKeywordArgument("include");
-    if (arg == null) {
-      Argument[] allArgs = getArguments();
-      if (allArgs.length != 0 && allArgs[0] instanceof Argument.Positional) {
-        arg = allArgs[0];
-      }
-    }
-    return getArgValue(arg);
-  }
-
-  @Nullable
-  public Expression getExcludes() {
-    return getArgValue(getKeywordArgument("exclude"));
-  }
-
-  @Nullable
-  private static Expression getArgValue(@Nullable Argument arg) {
-    return arg != null ? arg.getValue() : null;
-  }
-
-  public boolean areDirectoriesExcluded() {
-    Argument.Keyword arg = getKeywordArgument("exclude_directories");
-    if (arg != null) {
-      // '0' and '1' are the only accepted values
-      Expression value = arg.getValue();
-      return value == null || !value.getText().equals("0");
-    }
-    return true;
-  }
-
-  @Nullable
-  public ASTNode getGlobFuncallElement() {
-    return getNode().findChildByType(BuildElementTypes.REFERENCE_EXPRESSION);
-  }
-
-  private volatile GlobReference reference = null;
-
-  @Override
-  public GlobReference getReference() {
-    GlobReference ref = reference;
-    if (ref != null) {
-      return ref;
-    }
-    synchronized (this) {
-      if (reference == null) {
-        reference = new GlobReference(this);
-      }
-      return reference;
-    }
-  }
-
-  /**
-   * The text range within the glob expression used for references.
-   * This is the text the user needs to click on for navigation support,
-   * and also the destination when finding usages in a glob.
-   */
-  public TextRange getReferenceTextRange() {
-    // Ideally, this would be either the full range of the expression, or the range of the specific pattern matching
-    // a given file. However, that leads to conflicts with the individual string references, causing unnecessary
-    // and expensive de-globbing.
-    // e.g. while typing the glob patterns, IJ will be looking for code-completion possibilities, and need to
-    // de-glob to do this (due to a lack of communication between the different code-completion components).
-
-    return new TextRange(0, 4);
-  }
-
-  public boolean matches(String packageRelativePath, boolean isDirectory) {
-    return getReference().matches(packageRelativePath, isDirectory);
-  }
-}
\ No newline at end of file
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/IfPart.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/IfPart.java
deleted file mode 100644
index a2477d2..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/IfPart.java
+++ /dev/null
@@ -1,34 +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 element for an if part of an IfStatement.
- */
-public class IfPart extends BuildElementImpl implements Statement, StatementListContainer {
-
-  public IfPart(ASTNode astNode) {
-    super(astNode);
-  }
-
-  @Override
-  protected void acceptVisitor(BuildElementVisitor visitor) {
-    visitor.visitIfPart(this);
-  }
-
-}
\ No newline at end of file
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/IfStatement.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/IfStatement.java
deleted file mode 100644
index 4747ca8..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/IfStatement.java
+++ /dev/null
@@ -1,34 +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 element for an if statement.
- */
-public class IfStatement extends BuildElementImpl implements Statement {
-
-  public IfStatement(ASTNode astNode) {
-    super(astNode);
-  }
-
-  @Override
-  protected void acceptVisitor(BuildElementVisitor visitor) {
-    visitor.visitIfStatement(this);
-  }
-
-}
\ No newline at end of file
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/IntegerLiteral.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/IntegerLiteral.java
deleted file mode 100644
index d9bb2ee..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/IntegerLiteral.java
+++ /dev/null
@@ -1,34 +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 integer literal expressions
- */
-public class IntegerLiteral extends BuildElementImpl implements LiteralExpression {
-
-  public IntegerLiteral(ASTNode astNode) {
-    super(astNode);
-  }
-
-  @Override
-  protected void acceptVisitor(BuildElementVisitor visitor) {
-    visitor.visitIntegerLiteral(this);
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/ListComprehensionExpression.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/ListComprehensionExpression.java
deleted file mode 100644
index c341550..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/ListComprehensionExpression.java
+++ /dev/null
@@ -1,34 +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 element for a list comprehension expression
- * (comprehension suffix of: '[' expr ('FOR' loop_variables 'IN' expr)+ ']')
- */
-public class ListComprehensionExpression extends BuildElementImpl implements Expression {
-
-  public ListComprehensionExpression(ASTNode astNode) {
-    super(astNode);
-  }
-
-  @Override
-  protected void acceptVisitor(BuildElementVisitor visitor) {
-    visitor.visitListComprehensionSuffix(this);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/ListLiteral.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/ListLiteral.java
deleted file mode 100644
index d9b0247..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/ListLiteral.java
+++ /dev/null
@@ -1,52 +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 element for list and tuple literals
- */
-public class ListLiteral extends BuildListType<Expression> implements LiteralExpression {
-
-  public ListLiteral(ASTNode astNode) {
-    super(astNode, Expression.class);
-  }
-
-  @Override
-  protected void acceptVisitor(BuildElementVisitor visitor) {
-    visitor.visitListLiteral(this);
-  }
-
-  @Override
-  public String getPresentableText() {
-    return "list";
-  }
-
-  public Expression[] getChildExpressions() {
-    return findChildrenByClass(Expression.class);
-  }
-
-  @Override
-  public Expression[] getElements() {
-    return getChildExpressions();
-  }
-
-  @Override
-  public boolean isEmpty() {
-    return findChildByClass(Expression.class) != null;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/LiteralExpression.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/LiteralExpression.java
deleted file mode 100644
index f5c7f20..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/LiteralExpression.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.lang.buildfile.psi;
-
-/**
- * Interface for literal expressions.
- */
-public interface LiteralExpression extends Expression {
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/LoadStatement.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/LoadStatement.java
deleted file mode 100644
index 34fe7e7..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/LoadStatement.java
+++ /dev/null
@@ -1,107 +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.google.idea.blaze.base.lang.buildfile.references.LabelUtils;
-import com.intellij.lang.ASTNode;
-import com.intellij.util.PlatformIcons;
-
-import javax.annotation.Nullable;
-import javax.swing.*;
-import java.util.Arrays;
-
-/**
- * PSI element for a load statement.
- */
-public class LoadStatement extends BuildElementImpl implements Statement {
-
-  public LoadStatement(ASTNode astNode) {
-    super(astNode);
-  }
-
-  @Override
-  protected void acceptVisitor(BuildElementVisitor visitor) {
-    visitor.visitLoadStatement(this);
-  }
-
-  @Nullable
-  public ASTNode getImportNode() {
-    return getNode().findChildByType(BuildElementTypes.STRING_LITERAL);
-  }
-
-  @Nullable
-  public StringLiteral getImportPsiElement() {
-    return findChildByType(BuildElementTypes.STRING_LITERAL);
-  }
-
-  @Nullable
-  public String getImportedPath() {
-    ASTNode firstString = getImportNode();
-    return firstString != null ? StringLiteral.parseStringContents(firstString.getText()) : null;
-  }
-
-  /**
-   * The string nodes referencing imported functions.
-   */
-  public FunctionStatement[] getImportedFunctionReferences() {
-    return Arrays.stream(getChildStrings())
-      .skip(1)
-      .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))
-      .map(ASTNode::getPsi)
-      .toArray(StringLiteral[]::new);
-  }
-
-  @Override
-  public Icon getIcon(int flags) {
-    return PlatformIcons.IMPORT_ICON;
-  }
-
-  @Override
-  public String getPresentableText() {
-    String path = LabelUtils.getNiceSkylarkFileName(getImportedPath());
-    return path != null ? "load: " + path : "load";
-  }
-}
\ No newline at end of file
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/NamedBuildElement.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/NamedBuildElement.java
deleted file mode 100644
index 3de500a..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/NamedBuildElement.java
+++ /dev/null
@@ -1,75 +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.google.idea.blaze.base.lang.buildfile.lexer.BuildToken;
-import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
-import com.intellij.lang.ASTNode;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiNameIdentifierOwner;
-
-import javax.annotation.Nullable;
-
-/**
- * Base class for PsiNamedElements in BUILD files.
- */
-public abstract class NamedBuildElement extends BuildElementImpl implements PsiNameIdentifierOwner {
-
-  public NamedBuildElement(ASTNode astNode) {
-    super(astNode);
-  }
-
-  @Nullable
-  public ASTNode getNameNode() {
-    return getNode().findChildByType(BuildToken.IDENTIFIER);
-  }
-
-  @Override
-  @Nullable
-  public String getName() {
-    ASTNode node = getNameNode();
-    return node != null ? node.getText() : null;
-  }
-
-  @Override
-  @Nullable
-  public PsiElement getNameIdentifier() {
-    final ASTNode nameNode = getNameNode();
-    return nameNode != null ? nameNode.getPsi() : null;
-  }
-
-  @Override
-  public PsiElement setName(String name) {
-    final ASTNode nameElement = PsiUtils.createNewName(getProject(), name);
-    final ASTNode nameNode = getNameNode();
-    if (nameNode != null) {
-      getNode().replaceChild(nameNode, nameElement);
-    }
-    return this;
-  }
-
-  @Override
-  public int getTextOffset() {
-    final ASTNode name = getNameNode();
-    return name != null ? name.getStartOffset() : super.getTextOffset();
-  }
-
-  @Override
-  public String toString() {
-    return super.toString() + "('" + getName() + "')";
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/Parameter.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/Parameter.java
deleted file mode 100644
index efe5822..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/Parameter.java
+++ /dev/null
@@ -1,125 +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.google.idea.blaze.base.lang.buildfile.lexer.BuildToken;
-import com.google.idea.blaze.base.lang.buildfile.lexer.TokenKind;
-import com.intellij.icons.AllIcons;
-import com.intellij.lang.ASTNode;
-import org.jetbrains.annotations.Nullable;
-
-import javax.swing.*;
-
-/**
- * PSI nodes for parameters in a function declaration
- */
-public abstract class Parameter extends NamedBuildElement {
-
-  public static final Parameter[] EMPTY_ARRAY = new Parameter[0];
-
-  public Parameter(ASTNode node) {
-    super(node);
-  }
-
-  public boolean hasDefaultValue() {
-    return false;
-  }
-
-  @Nullable
-  @Override
-  public Icon getIcon(int flags) {
-    return AllIcons.Nodes.Parameter;
-  }
-
-  /**
-   * Includes stars where relevant.
-   */
-  public String getPresentableName() {
-    return getName();
-  }
-
-  public static class Optional extends Parameter {
-    public Optional(ASTNode node) {
-      super(node);
-    }
-    @Override
-    protected void acceptVisitor(BuildElementVisitor visitor) {
-      visitor.visitParameter(this);
-    }
-
-    public Expression getDefaultValue() {
-      ASTNode node = getNode().getLastChildNode();
-      while (node != null) {
-        if (BuildElementTypes.EXPRESSIONS.contains(node.getElementType())) {
-          return (Expression) node.getPsi();
-        }
-        if (node.getElementType() == BuildToken.fromKind(TokenKind.EQUALS)) {
-          break;
-        }
-        node = node.getTreePrev();
-      }
-      return null;
-    }
-
-    @Override
-    public boolean hasDefaultValue() {
-      return true;
-    }
-  }
-
-  public static class Mandatory extends Parameter {
-    public Mandatory(ASTNode node) {
-      super(node);
-    }
-    @Override
-    protected void acceptVisitor(BuildElementVisitor visitor) {
-      visitor.visitParameter(this);
-    }
-  }
-
-  public static class Star extends Parameter {
-    public Star(ASTNode node) {
-      super(node);
-    }
-
-    @Override
-    protected void acceptVisitor(BuildElementVisitor visitor) {
-      visitor.visitParameter(this);
-    }
-
-    @Override
-    public String getPresentableName() {
-      return "*" + getName();
-    }
-  }
-
-  public static class StarStar extends Parameter {
-    public StarStar(ASTNode node) {
-      super(node);
-    }
-
-    @Override
-    protected void acceptVisitor(BuildElementVisitor visitor) {
-      visitor.visitParameter(this);
-    }
-
-    @Override
-    public String getPresentableName() {
-      return "**" + getName();
-    }
-  }
-
-}
\ No newline at end of file
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/ParameterList.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/ParameterList.java
deleted file mode 100644
index 623baa8..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/ParameterList.java
+++ /dev/null
@@ -1,70 +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;
-
-import javax.annotation.Nullable;
-import java.util.StringJoiner;
-
-/**
- * Parameter list in a function declaration
- */
-public class ParameterList extends BuildListType<Parameter> {
-
-  public ParameterList(ASTNode astNode) {
-    super(astNode, Parameter.class);
-  }
-
-  @Override
-  protected void acceptVisitor(BuildElementVisitor visitor) {
-    visitor.visitFunctionParameterList(this);
-  }
-
-  @Nullable
-  public Parameter findParameterByName(String name) {
-    ASTNode node = getNode().getFirstChildNode();
-    while (node != null) {
-      if (node.getElementType() == BuildElementTypes.PARAM_OPTIONAL
-        || node.getElementType() == BuildElementTypes.PARAM_MANDATORY) {
-        Parameter param = (Parameter) node.getPsi();
-        if (name.equals(param.getName())) {
-          return param;
-        }
-      }
-      node = node.getTreeNext();
-    }
-    return null;
-  }
-
-  public boolean hasStarStar() {
-    return !findChildrenByType(BuildElementTypes.PARAM_STAR_STAR).isEmpty();
-  }
-
-  public boolean hasStar() {
-    return !findChildrenByType(BuildElementTypes.PARAM_STAR).isEmpty();
-  }
-
-  @Override
-  public String getPresentableText() {
-    StringJoiner joiner = new StringJoiner(", ", "(", ")");
-    for (Parameter param : getElements()) {
-      joiner.add(param.getPresentableName());
-    }
-    return joiner.toString();
-  }
-
-}
\ No newline at end of file
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/PassStatement.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/PassStatement.java
deleted file mode 100644
index ec1f01f..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/PassStatement.java
+++ /dev/null
@@ -1,33 +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 element for pass statement.
- */
-public class PassStatement extends BuildElementImpl implements Statement {
-
-  public PassStatement(ASTNode node) {
-    super(node);
-  }
-
-  @Override
-  protected void acceptVisitor(BuildElementVisitor visitor) {
-    visitor.visitPassStatement(this);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/ReferenceExpression.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/ReferenceExpression.java
deleted file mode 100644
index 198a778..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/ReferenceExpression.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.lang.buildfile.psi;
-
-import com.google.idea.blaze.base.lang.buildfile.lexer.BuildToken;
-import com.google.idea.blaze.base.lang.buildfile.references.LocalReference;
-import com.intellij.lang.ASTNode;
-import com.intellij.psi.PsiReference;
-import com.intellij.psi.tree.IElementType;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * References a PsiNamedElement
- */
-public class ReferenceExpression extends BuildElementImpl implements Expression {
-
-  public ReferenceExpression(ASTNode astNode) {
-    super(astNode);
-  }
-
-  @Override
-  protected void acceptVisitor(BuildElementVisitor visitor) {
-    visitor.visitReferenceExpression(this);
-  }
-
-  @Nullable
-  public ASTNode getNameElement() {
-    return getNode().findChildByType(BuildToken.IDENTIFIER);
-  }
-
-  @Nullable
-  public String getReferencedName() {
-    ASTNode node = getNameElement();
-    return node != null ? node.getText() : null;
-  }
-
-  @Override
-  public PsiReference getReference() {
-    IElementType parentType = getParentType();
-    // function names are resolved by the parent funcall node
-    if (BuildElementTypes.FUNCALL_EXPRESSION.equals(parentType)) {
-      return null;
-    }
-    if (BuildElementTypes.DOT_EXPRESSION.equals(parentType) && afterDot(getNode())) {
-      return null;
-    }
-    return new LocalReference(this);
-  }
-
-  @Override
-  public String getName() {
-    return getReferencedName();
-  }
-
-  private static boolean afterDot(ASTNode node) {
-    ASTNode prev = node.getTreePrev();
-    return prev != null && prev.getText().equals(".");
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/ReturnStatement.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/ReturnStatement.java
deleted file mode 100644
index bde88a3..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/ReturnStatement.java
+++ /dev/null
@@ -1,42 +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;
-
-import javax.annotation.Nullable;
-
-/**
- * A wrapper Statement class for return expressions.
- */
-public class ReturnStatement extends BuildElementImpl implements Statement {
-
-  public ReturnStatement(ASTNode node) {
-    super(node);
-  }
-
-  @Nullable
-  public Expression getReturnExpression() {
-    return childToPsi(BuildElementTypes.EXPRESSIONS, 0);
-  }
-
-  @Override
-  protected void acceptVisitor(BuildElementVisitor visitor) {
-    visitor.visitReturnStatement(this);
-  }
-}
-
-
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/Statement.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/Statement.java
deleted file mode 100644
index e76c2b5..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/Statement.java
+++ /dev/null
@@ -1,25 +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;
-
-/**
- * Base class for all statements nodes in the PSI tree
- */
-public interface Statement extends BuildElement {
-
-  Statement[] EMPTY_ARRAY = new Statement[0];
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/StatementList.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/StatementList.java
deleted file mode 100644
index d64a99a..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/StatementList.java
+++ /dev/null
@@ -1,46 +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;
-import com.intellij.psi.PsiElement;
-
-/**
- * PSI element for a list of statements
- */
-public class StatementList extends BuildListType<Statement> {
-
-  public StatementList(ASTNode astNode) {
-    super(astNode, Statement.class);
-  }
-
-  @Override
-  protected void acceptVisitor(BuildElementVisitor visitor) {
-    visitor.visitStatementList(this);
-  }
-
-  @Override
-  public int getStartOffset() {
-    PsiElement prevSibling = getPrevSibling();
-    while (prevSibling != null) {
-      if (prevSibling.getText().equals(":")) {
-        return prevSibling.getNode().getStartOffset() + 1;
-      }
-      prevSibling = prevSibling.getPrevSibling();
-    }
-    return getNode().getStartOffset();
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/StatementListContainer.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/StatementListContainer.java
deleted file mode 100644
index c640b0a..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/StatementListContainer.java
+++ /dev/null
@@ -1,23 +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;
-
-/**
- * Anything of the form type ':' suite
- */
-public interface StatementListContainer extends BuildElement {
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/StringLiteral.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/StringLiteral.java
deleted file mode 100644
index e6f05e4..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/StringLiteral.java
+++ /dev/null
@@ -1,134 +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.google.idea.blaze.base.lang.buildfile.references.LabelReference;
-import com.google.idea.blaze.base.lang.buildfile.references.LoadedSymbolReference;
-import com.google.idea.blaze.base.lang.buildfile.references.PackageReferenceFragment;
-import com.google.idea.blaze.base.lang.buildfile.references.QuoteType;
-import com.intellij.lang.ASTNode;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiReference;
-
-import javax.annotation.Nullable;
-
-/**
- * PSI node for string literal expressions
- */
-public class StringLiteral extends BuildElementImpl implements LiteralExpression {
-
-  public static String stripEndpointQuotes(ASTNode node) {
-    assert(node.getElementType() == BuildElementTypes.STRING_LITERAL);
-    return parseStringContents(node.getText());
-  }
-
-  /**
-   * Removes the leading and trailing quotes. Naive implementation intended for resolving references
-   * (in which case escaped characters, raw strings, etc. are unlikely).
-   */
-  public static String parseStringContents(String string) {
-    // TODO: Handle escaped characters, etc. here? (extract logic from BuildLexerBase.addStringLiteral)
-    if (string.startsWith("\"\"\"") || string.startsWith("'''")) {
-      return string.length() < 6 ? "" : string.substring(3, string.length() - 3);
-    }
-    return string.length() < 2 ? "" : string.substring(1, string.length() - 1);
-  }
-
-  public static QuoteType getQuoteType(@Nullable String rawText) {
-    if (rawText == null) {
-      return QuoteType.NoQuotes;
-    }
-    if (rawText.startsWith("\"\"\"")) {
-      return QuoteType.TripleDouble;
-    }
-    if (rawText.startsWith("'''")) {
-      return QuoteType.TripleSingle;
-    }
-    if (rawText.startsWith("'")) {
-      return QuoteType.Single;
-    }
-    if (rawText.startsWith("\"")) {
-      return QuoteType.Double;
-    }
-    return QuoteType.NoQuotes;
-  }
-
-  public StringLiteral(ASTNode astNode) {
-    super(astNode);
-  }
-
-  @Override
-  protected void acceptVisitor(BuildElementVisitor visitor) {
-    visitor.visitStringLiteral(this);
-  }
-
-  /**
-   * Removes the leading and trailing quotes
-   */
-  public String getStringContents() {
-    return parseStringContents(getText());
-  }
-
-  public QuoteType getQuoteType() {
-    return getQuoteType(getText());
-  }
-
-  /**
-   * Labels are taken to reference:
-   *   - the actual target they reference
-   *   - the BUILD package specified before the colon (only if explicitly present)
-   */
-  @Override
-  public PsiReference[] getReferences() {
-    PsiReference primaryReference = getReference();
-    if (primaryReference instanceof LabelReference) {
-      return new PsiReference[] {primaryReference, new PackageReferenceFragment((LabelReference) primaryReference)};
-    }
-    return primaryReference != null ? new PsiReference[] {primaryReference} : PsiReference.EMPTY_ARRAY;
-  }
-
-  /**
-   * The primary reference -- this is the target referenced by the full label
-   */
-  @Nullable
-  @Override
-  public PsiReference getReference() {
-    PsiElement parent = getParent();
-    if (parent instanceof LoadStatement) {
-      LoadStatement load = (LoadStatement) parent;
-      StringLiteral importNode = load.getImportPsiElement();
-      if (importNode == null) {
-        return null;
-      }
-      LabelReference importReference = new LabelReference(importNode, false);
-      if (this.equals(importNode)) {
-        return importReference;
-      }
-      return new LoadedSymbolReference(this, importReference);
-    }
-    return new LabelReference(this, true);
-  }
-
-  public boolean insideLoadStatement() {
-    return getParentType() == BuildElementTypes.LOAD_STATEMENT;
-  }
-
-  @Override
-  public String getPresentableText() {
-    return getText();
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/TargetExpression.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/TargetExpression.java
deleted file mode 100644
index d172ede..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/TargetExpression.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.lang.buildfile.psi;
-
-import com.google.idea.blaze.base.lang.buildfile.references.TargetReference;
-import com.intellij.lang.ASTNode;
-import com.intellij.psi.PsiReference;
-import com.intellij.util.PlatformIcons;
-import org.jetbrains.annotations.Nullable;
-
-import javax.swing.*;
-
-/**
- * References a PsiNamedElement
- */
-public class TargetExpression extends NamedBuildElement implements Expression {
-
-  public TargetExpression(ASTNode astNode) {
-    super(astNode);
-  }
-
-  @Override
-  protected void acceptVisitor(BuildElementVisitor visitor) {
-    visitor.visitTargetExpression(this);
-  }
-
-  @Override
-  public PsiReference getReference() {
-    return new TargetReference(this);
-  }
-
-  @Nullable
-  @Override
-  public Icon getIcon(int flags) {
-    return PlatformIcons.VARIABLE_ICON;
-  }
-}
-
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/util/BuildElementGenerator.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/util/BuildElementGenerator.java
deleted file mode 100644
index ce39f11..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/util/BuildElementGenerator.java
+++ /dev/null
@@ -1,92 +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.util;
-
-import com.google.idea.blaze.base.lang.buildfile.language.BuildFileLanguage;
-import com.google.idea.blaze.base.lang.buildfile.language.BuildFileType;
-import com.google.idea.blaze.base.lang.buildfile.lexer.BuildToken;
-import com.google.idea.blaze.base.lang.buildfile.lexer.TokenKind;
-import com.google.idea.blaze.base.lang.buildfile.psi.Argument;
-import com.google.idea.blaze.base.lang.buildfile.psi.Expression;
-import com.google.idea.blaze.base.lang.buildfile.psi.FuncallExpression;
-import com.intellij.lang.ASTNode;
-import com.intellij.openapi.components.ServiceManager;
-import com.intellij.openapi.project.Project;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiFile;
-import com.intellij.psi.PsiFileFactory;
-import com.intellij.psi.impl.PsiFileFactoryImpl;
-import com.intellij.testFramework.LightVirtualFile;
-
-/**
- * Creates dummy BuildElements, e.g. for renaming purposes.
- */
-public class BuildElementGenerator {
-
-  public static BuildElementGenerator getInstance(Project project) {
-    return ServiceManager.getService(project, BuildElementGenerator.class);
-  }
-
-  private static final String DUMMY_FILENAME = "dummy.bzl";
-
-  private final Project project;
-
-  public BuildElementGenerator(Project project) {
-    this.project = project;
-  }
-
-  public PsiFile createDummyFile(String contents) {
-    PsiFileFactory factory = PsiFileFactory.getInstance(project);
-    LightVirtualFile virtualFile = new LightVirtualFile(DUMMY_FILENAME, BuildFileType.INSTANCE, contents);
-    PsiFile psiFile = ((PsiFileFactoryImpl) factory).trySetupPsiForFile(virtualFile, BuildFileLanguage.INSTANCE, false, true);
-    assert psiFile != null;
-    return psiFile;
-  }
-
-  public ASTNode createNameIdentifier(String name) {
-    PsiFile dummyFile = createDummyFile(name);
-    ASTNode referenceNode = dummyFile.getNode().getFirstChildNode();
-    ASTNode nameNode = referenceNode.getFirstChildNode();
-    if (nameNode.getElementType() != BuildToken.IDENTIFIER) {
-      throw new RuntimeException("Expecting an IDENTIFIER node directly below the BuildFile PSI element");
-    }
-    return nameNode;
-  }
-
-  public ASTNode createStringNode(String contents) {
-    PsiFile dummyFile = createDummyFile('"' + contents + '"');
-    ASTNode literalNode = dummyFile.getNode().getFirstChildNode();
-    ASTNode stringNode = literalNode.getFirstChildNode();
-    assert(stringNode.getElementType() == BuildToken.fromKind(TokenKind.STRING));
-    return stringNode;
-  }
-
-  public Argument.Keyword createKeywordArgument(String keyword, String value) {
-    String dummyText = String.format("foo(%s = \"%s\")", keyword, value);
-    FuncallExpression funcall = (FuncallExpression) createExpressionFromText(dummyText);
-    return (Argument.Keyword) funcall.getArguments()[0];
-  }
-
-  public Expression createExpressionFromText(String text) {
-    PsiFile dummyFile = createDummyFile(text);
-    PsiElement element = dummyFile.getFirstChild();
-    if (element instanceof Expression) {
-      return (Expression) element;
-    }
-    throw new RuntimeException("Could not parse text as expression: '" + text + "'");
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/util/PsiUtils.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/util/PsiUtils.java
deleted file mode 100644
index e27794f..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/psi/util/PsiUtils.java
+++ /dev/null
@@ -1,190 +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.util;
-
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.lang.buildfile.psi.AssignmentStatement;
-import com.google.idea.blaze.base.lang.buildfile.psi.Expression;
-import com.google.idea.blaze.base.lang.buildfile.psi.ReferenceExpression;
-import com.google.idea.blaze.base.lang.buildfile.psi.TargetExpression;
-import com.intellij.lang.ASTNode;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.TextRange;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.psi.PsiDirectory;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiFile;
-import com.intellij.util.CommonProcessors;
-import com.intellij.util.Processor;
-
-import javax.annotation.Nullable;
-import java.util.List;
-
-/**
- * Utility methods for working with PSI elements
- */
-public class PsiUtils {
-
-  public static ASTNode createNewName(Project project, String name) {
-    return BuildElementGenerator.getInstance(project).createNameIdentifier(name);
-  }
-
-  public static ASTNode createNewLabel(Project project, String labelString) {
-    return BuildElementGenerator.getInstance(project).createStringNode(labelString);
-  }
-
-  @Nullable
-  public static PsiElement getPreviousNodeInTree(PsiElement element) {
-    PsiElement prevSibling = null;
-    while (element != null && (prevSibling = element.getPrevSibling()) == null) {
-      element = element.getParent();
-    }
-    return prevSibling != null ? lastElementInSubtree(prevSibling) : null;
-  }
-
-  /**
-   * The last element in the tree rooted at the given element.
-   */
-  public static PsiElement lastElementInSubtree(PsiElement element) {
-    PsiElement lastChild;
-    while ((lastChild = element.getLastChild()) != null) {
-      element = lastChild;
-    }
-    return element;
-  }
-
-  /**
-   * Walks up PSI tree, looking for a parent of the specified class. Stops searching when it reaches a
-   * parent of type PsiDirectory.
-   */
-  @Nullable
-  public static <T extends PsiElement> T getParentOfType(PsiElement element, Class<T> psiClass) {
-    PsiElement parent = element.getParent();
-    while (parent != null && !(parent instanceof PsiDirectory)) {
-      if (psiClass.isInstance(parent)) {
-        return (T) parent;
-      }
-      parent = parent.getParent();
-    }
-    return null;
-  }
-
-  @Nullable
-  public static <T extends PsiElement> T findFirstChildOfClassRecursive(PsiElement parent, Class<T> psiClass) {
-    List<T> holder = Lists.newArrayListWithExpectedSize(1);
-    Processor<T> getFirst = t -> {
-      holder.add(t);
-      return false;
-    };
-    processChildrenOfType(parent, getFirst, psiClass);
-    return holder.isEmpty() ? null : holder.get(0);
-  }
-
-  @Nullable
-  public static <T extends PsiElement> T findLastChildOfClassRecursive(PsiElement parent, Class<T> psiClass) {
-    List<T> holder = Lists.newArrayListWithExpectedSize(1);
-    Processor<T> getFirst = t -> {
-      holder.add(t);
-      return false;
-    };
-    processChildrenOfType(parent, getFirst, psiClass, true);
-    return holder.isEmpty() ? null : holder.get(0);
-  }
-
-  public static <T extends PsiElement> List<T> findAllChildrenOfClassRecursive(PsiElement parent, Class<T> psiClass) {
-    List<T> result = Lists.newArrayList();
-    processChildrenOfType(parent, new CommonProcessors.CollectProcessor(result), psiClass);
-    return result;
-  }
-
-  /**
-   * Walk through entire PSI tree rooted at 'element', processing all children of the given type.
-   * @return true if processing was stopped by the processor
-   */
-  public static <T extends PsiElement> boolean processChildrenOfType(
-    PsiElement element,
-    Processor<T> processor,
-    Class<T> psiClass) {
-    return processChildrenOfType(element, processor, psiClass, false);
-  }
-
-  /**
-   * Walk through entire PSI tree rooted at 'element', processing all children of the given type.
-   * @return true if processing was stopped by the processor
-   */
-  private static <T extends PsiElement> boolean processChildrenOfType(
-    PsiElement element,
-    Processor<T> processor,
-    Class<T> psiClass,
-    boolean reverseOrder) {
-    PsiElement child = reverseOrder ? element.getLastChild() : element.getFirstChild();
-    while (child != null) {
-      if (psiClass.isInstance(child)) {
-        if (!processor.process((T) child)) {
-          return true;
-        }
-      }
-      if (processChildrenOfType(child, processor, psiClass, reverseOrder)) {
-        return true;
-      }
-      child = reverseOrder ? child.getPrevSibling() : child.getNextSibling();
-    }
-    return false;
-  }
-
-  public static TextRange childRangeInParent(TextRange parentRange, TextRange childRange) {
-    return childRange.shiftRight(-parentRange.getStartOffset());
-  }
-
-  @Nullable
-  public static String getFilePath(@Nullable PsiFile file) {
-    VirtualFile virtualFile = file != null ? file.getVirtualFile() : null;
-    return virtualFile != null ? virtualFile.getPath() : null;
-  }
-
-  /**
-   * For ReferenceExpressions, follows the chain of references until it hits a non-ReferenceExpression.
-   * For other types, returns the input expression.
-   */
-  public static PsiElement getReferencedTarget(Expression expr) {
-    PsiElement element = expr;
-    while (element instanceof ReferenceExpression) {
-      PsiElement referencedElement = ((ReferenceExpression) element).getReferencedElement();
-      if (referencedElement == null) {
-        return element;
-      }
-      element = referencedElement;
-    }
-    return element;
-  }
-
-  /**
-   * For ReferenceExpressions, follows the chain of references until it hits a non-ReferenceExpression, then
-   * evaluates the value of that target.
-   * For other types, returns the input expression.
-   */
-  public static PsiElement getReferencedTargetValue(Expression expr) {
-    PsiElement element = getReferencedTarget(expr);
-    if (element instanceof TargetExpression) {
-      PsiElement parent = element.getParent();
-      if (parent instanceof AssignmentStatement) {
-        return ((AssignmentStatement) parent).getAssignedValue();
-      }
-    }
-    return element;
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/refactor/BuildNamesValidator.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/refactor/BuildNamesValidator.java
deleted file mode 100644
index 595fe39..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/refactor/BuildNamesValidator.java
+++ /dev/null
@@ -1,42 +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.refactor;
-
-import com.google.idea.blaze.base.lang.buildfile.lexer.BuildLexer;
-import com.google.idea.blaze.base.lang.buildfile.lexer.BuildLexerBase;
-import com.google.idea.blaze.base.lang.buildfile.lexer.TokenKind;
-import com.intellij.lang.refactoring.NamesValidator;
-import com.intellij.openapi.project.Project;
-
-/**
- * Used for rename validation
- */
-public class BuildNamesValidator implements NamesValidator {
-
-  @Override
-  public boolean isKeyword(String s, Project project) {
-    return false;
-  }
-
-  @Override
-  public boolean isIdentifier(String s, Project project) {
-    BuildLexer lexer = new BuildLexer(BuildLexerBase.LexerMode.Parsing);
-    lexer.start(s);
-    return lexer.getTokenEnd() == s.length() && lexer.getTokenKind() == TokenKind.IDENTIFIER;
-  }
-
-}
-
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/refactor/BuildRefactoringSupportProvider.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/refactor/BuildRefactoringSupportProvider.java
deleted file mode 100644
index 352252e..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/refactor/BuildRefactoringSupportProvider.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.lang.buildfile.refactor;
-
-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.TargetExpression;
-import com.intellij.lang.refactoring.RefactoringSupportProvider;
-import com.intellij.psi.PsiElement;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Supports 'safe delete'
- */
-public class BuildRefactoringSupportProvider extends RefactoringSupportProvider {
-
-  @Override
-  public boolean isSafeDeleteAvailable(@NotNull PsiElement element) {
-    // basically a promise that 'find usages' works for this element
-    return element instanceof FunctionStatement
-      || element instanceof TargetExpression
-      || element instanceof FuncallExpression;
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/references/ArgumentReference.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/references/ArgumentReference.java
deleted file mode 100644
index e3b4f13..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/references/ArgumentReference.java
+++ /dev/null
@@ -1,68 +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.references;
-
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.lang.buildfile.completion.NamedBuildLookupElement;
-import com.google.idea.blaze.base.lang.buildfile.psi.*;
-import com.intellij.codeInsight.lookup.LookupElement;
-import com.intellij.openapi.util.TextRange;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiReferenceBase;
-import com.intellij.psi.util.PsiTreeUtil;
-
-import javax.annotation.Nullable;
-import java.util.List;
-
-/**
- * Only keyword arguments resolve, but we include this class for code completion purposes.
- * As the user is typing a keyword arg, they'll start with a positional arg element.
- */
-public class ArgumentReference<T extends Argument> extends PsiReferenceBase<T> {
-
-  public ArgumentReference(T element, TextRange rangeInElement, boolean soft) {
-    super(element, rangeInElement, false);
-  }
-
-  @Nullable
-  protected FunctionStatement resolveFunction() {
-    FuncallExpression call = PsiTreeUtil.getParentOfType(myElement, FuncallExpression.class);
-    if (call == null) {
-      return null;
-    }
-    PsiElement callee = call.getReferencedElement();
-    return callee instanceof FunctionStatement ? (FunctionStatement) callee : null;
-  }
-
-  @Nullable
-  @Override
-  public PsiElement resolve() {
-    return null;
-  }
-
-  @Override
-  public Object[] getVariants() {
-    FunctionStatement function = resolveFunction();
-    if (function == null) {
-      return EMPTY_ARRAY;
-    }
-    List<LookupElement> params = Lists.newArrayList();
-    for (Parameter param : function.getParameters()) {
-      params.add(new NamedBuildLookupElement(param, QuoteType.NoQuotes));
-    }
-    return params.toArray();
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/references/BuildReferenceManager.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/references/BuildReferenceManager.java
deleted file mode 100644
index 7ee9a70..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/references/BuildReferenceManager.java
+++ /dev/null
@@ -1,264 +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.references;
-
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.io.FileAttributeProvider;
-import com.google.idea.blaze.base.lang.buildfile.completion.BuildLookupElement;
-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.WorkspacePath;
-import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
-import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver;
-import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolverProvider;
-import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.components.ServiceManager;
-import com.intellij.openapi.project.Project;
-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.PsiFile;
-import com.intellij.psi.PsiFileSystemItem;
-import com.intellij.psi.PsiManager;
-import com.intellij.util.PathUtil;
-
-import javax.annotation.Nullable;
-import java.io.File;
-import java.util.List;
-
-/**
- * Handles reference caching and resolving labels to PSI elements.
- */
-public class BuildReferenceManager {
-
-  public static BuildReferenceManager getInstance(Project project) {
-    return ServiceManager.getService(project, BuildReferenceManager.class);
-  }
-
-  private final Project project;
-
-  public BuildReferenceManager(Project project) {
-    this.project = project;
-  }
-
-  /**
-   * Finds the PSI element associated with the given label.
-   */
-  @Nullable
-  public PsiElement resolveLabel(Label label) {
-    return resolveLabel(label.blazePackage(), label.ruleName(), false);
-  }
-
-  /**
-   * Finds the PSI element associated with the given label.
-   */
-  @Nullable
-  public PsiElement resolveLabel(WorkspacePath packagePath, RuleName ruleName, boolean excludeRules) {
-    File packageDir = resolvePackage(packagePath);
-    if (packageDir == null) {
-      return null;
-    }
-
-    if (!excludeRules) {
-      FuncallExpression target = findRule(packageDir, ruleName);
-      if (target != null) {
-        return target;
-      }
-    }
-
-    // try a direct file reference (e.g. ":a.java")
-    File fullFile = new File(packageDir, ruleName.toString());
-    if (FileAttributeProvider.getInstance().exists(fullFile)) {
-      return resolveFile(fullFile);
-    }
-
-    return null;
-  }
-
-  private FuncallExpression findRule(File packageDir, RuleName ruleName) {
-    BuildFile psiFile = findBuildFile(packageDir);
-    return psiFile != null ? psiFile.findRule(ruleName.toString()) : null;
-  }
-
-  @Nullable
-  public PsiFileSystemItem resolveFile(File file) {
-    VirtualFile vf = getFileSystem().findFileByPath(file.getPath());
-    if (vf == null) {
-      return null;
-    }
-    PsiManager manager = PsiManager.getInstance(project);
-    return vf.isDirectory() ? manager.findDirectory(vf) : manager.findFile(vf);
-  }
-
-  @Nullable
-  public File resolvePackage(@Nullable WorkspacePath packagePath) {
-    return resolveWorkspaceRelativePath(packagePath != null ? packagePath.relativePath() : null);
-  }
-
-  @Nullable
-  private File resolveWorkspaceRelativePath(@Nullable String relativePath) {
-    WorkspacePathResolver pathResolver = getWorkspacePathResolver();
-    if (pathResolver == null || relativePath == null) {
-      return null;
-    }
-    return pathResolver.resolveToFile(relativePath);
-  }
-
-  @Nullable
-  private WorkspacePathResolver getWorkspacePathResolver() {
-    return WorkspacePathResolverProvider.getInstance(project).getPathResolver();
-  }
-
-  /**
-   * Finds all child directories. If exactly one is found, continue traversing (and appending to LookupElement string)
-   * until there are multiple options.<br>
-   * Used for package path completion suggestions.
-   */
-  public BuildLookupElement[] resolvePackageLookupElements(FileLookupData lookupData) {
-    String relativePath = lookupData.filePathFragment;
-    File file = resolveWorkspaceRelativePath(relativePath);
-
-    FileAttributeProvider provider = FileAttributeProvider.getInstance();
-    String pathFragment = "";
-    if (file == null || (!provider.isDirectory(file) && !relativePath.endsWith("/"))) {
-      // we might be partway through a file name. Try the parent directory
-      relativePath = PathUtil.getParentPath(relativePath);
-      file = resolveWorkspaceRelativePath(relativePath);
-      pathFragment = StringUtil.trimStart(lookupData.filePathFragment.substring(relativePath.length()), "/");
-    }
-    if (file == null || !provider.isDirectory(file)) {
-      return BuildLookupElement.EMPTY_ARRAY;
-    }
-    VirtualFile vf = getFileSystem().findFileByPath(file.getPath());
-    if (vf == null || !vf.isDirectory()) {
-      return BuildLookupElement.EMPTY_ARRAY;
-    }
-    BuildLookupElement[] uniqueLookup = new BuildLookupElement[1];
-    while (true) {
-      VirtualFile[] children = vf.getChildren();
-      if (children == null || children.length == 0) {
-        return uniqueLookup[0] != null ? uniqueLookup : BuildLookupElement.EMPTY_ARRAY;
-      }
-      List<VirtualFile> validChildren = Lists.newArrayListWithCapacity(children.length);
-      for (VirtualFile child : children) {
-        if (child.getName().startsWith(pathFragment) && lookupData.acceptFile(child)) {
-          validChildren.add(child);
-        }
-      }
-      if (validChildren.isEmpty()) {
-        return uniqueLookup[0] != null ? uniqueLookup : BuildLookupElement.EMPTY_ARRAY;
-      }
-      if (validChildren.size() > 1) {
-        return uniqueLookup[0] != null ?
-               uniqueLookup : lookupsForFiles(validChildren, lookupData);
-      }
-      // continue traversing while there's only one option
-      uniqueLookup[0] = lookupForFile(validChildren.get(0), lookupData);
-      pathFragment = "";
-      vf = validChildren.get(0);
-    }
-  }
-
-  private BuildLookupElement[] lookupsForFiles(List<VirtualFile> files, FileLookupData lookupData) {
-    BuildLookupElement[] lookups = new BuildLookupElement[files.size()];
-    for (int i = 0; i < files.size(); i++) {
-      lookups[i] = lookupForFile(files.get(i), lookupData);
-    }
-    return lookups;
-  }
-
-  private BuildLookupElement lookupForFile(VirtualFile file, FileLookupData lookupData) {
-    WorkspacePath workspacePath = getWorkspaceRelativePath(file.getPath());
-    return lookupData.lookupElementForFile(project, file, workspacePath);
-  }
-
-  @Nullable
-  public BuildFile resolveBlazePackage(String workspaceRelativePath) {
-    workspaceRelativePath = StringUtil.trimStart(workspaceRelativePath, "//");
-    return resolveBlazePackage(WorkspacePath.createIfValid(workspaceRelativePath));
-  }
-
-  @Nullable
-  public BuildFile resolveBlazePackage(@Nullable WorkspacePath path) {
-    return findBuildFile(resolvePackage(path));
-  }
-
-  @Nullable
-  private BuildFile findBuildFile(@Nullable File packageDirectory) {
-    FileAttributeProvider provider = FileAttributeProvider.getInstance();
-    if (packageDirectory == null || !provider.isDirectory(packageDirectory)) {
-      return null;
-    }
-    File buildFile = new File(packageDirectory, "BUILD");
-    if (!provider.exists(buildFile)) {
-      return null;
-    }
-    VirtualFile vf = getFileSystem().findFileByPath(buildFile.getPath());
-    if (vf == null) {
-      return null;
-    }
-    PsiFile psiFile = PsiManager.getInstance(project).findFile(vf);
-    return psiFile instanceof BuildFile ? (BuildFile) psiFile : null;
-  }
-
-  /**
-   * For files references, returns the parent directory.<br>
-   * For rule references, return the blaze package directory.
-   */
-  @Nullable
-  public File resolveParentDirectory(@Nullable Label label) {
-    return label != null ? resolveParentDirectory(label.blazePackage(), label.ruleName()) : null;
-  }
-
-  @Nullable
-  private File resolveParentDirectory(WorkspacePath packagePath, RuleName ruleName) {
-    File packageFile = resolvePackage(packagePath);
-    if (packageFile == null) {
-      return null;
-    }
-    String rulePathParent = PathUtil.getParentPath(ruleName.toString());
-    return new File(packageFile, rulePathParent);
-  }
-
-  @Nullable
-  public WorkspacePath getWorkspaceRelativePath(String absolutePath) {
-    WorkspacePathResolver pathResolver = getWorkspacePathResolver();
-    WorkspaceRoot workspaceRoot = pathResolver != null ? pathResolver.getWorkspaceRoot() : null;
-    return workspaceRoot != null ? getWorkspaceRelativePath(workspaceRoot, absolutePath) : null;
-  }
-
-  @Nullable
-  static private WorkspacePath getWorkspaceRelativePath(WorkspaceRoot workspaceRoot, String absolutePath) {
-    File file = new File(absolutePath);
-    if (workspaceRoot.isInWorkspace(file)) {
-      return workspaceRoot.workspacePathFor(file);
-    }
-    return null;
-  }
-
-  private static VirtualFileSystem getFileSystem() {
-    if (ApplicationManager.getApplication().isUnitTestMode()) {
-      return TempFileSystem.getInstance();
-    }
-    return LocalFileSystem.getInstance();
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/references/FileLookupData.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/references/FileLookupData.java
deleted file mode 100644
index 8f2ac8a..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/references/FileLookupData.java
+++ /dev/null
@@ -1,194 +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.references;
-
-import com.google.idea.blaze.base.lang.buildfile.completion.FilePathLookupElement;
-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.BlazePackage;
-import com.google.idea.blaze.base.model.primitives.WorkspacePath;
-import com.intellij.icons.AllIcons;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.NullableLazyValue;
-import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.openapi.vfs.VirtualFileFilter;
-import com.intellij.psi.PsiFile;
-import com.intellij.psi.PsiManager;
-import com.intellij.util.PathUtil;
-import com.intellij.util.PlatformIcons;
-import icons.BlazeIcons;
-import org.jetbrains.annotations.Nullable;
-
-import javax.swing.*;
-
-/**
- * The data relevant to finding file lookups.
- */
-public class FileLookupData {
-
-  public enum PathFormat {
-    /** BUILD label without a leading '//', which can only reference targets in the same package. */
-    PackageLocal,
-    /** a BUILD label with leading '//', which can reference targets in other packages. */
-    NonLocal,
-    /** a path string which can reference any files, and has no leading '//'. */
-    NonLocalWithoutInitialBackslashes
-  }
-
-  @Nullable
-  public static FileLookupData nonLocalFileLookup(String originalLabel, StringLiteral element) {
-    return nonLocalFileLookup(originalLabel, element.getContainingFile(), element.getQuoteType(), PathFormat.NonLocal);
-  }
-
-  @Nullable
-  public static FileLookupData nonLocalFileLookup(String originalLabel,
-                                                  @Nullable BuildFile containingFile,
-                                                  QuoteType quoteType,
-                                                  PathFormat pathFormat) {
-    if (originalLabel.indexOf(':') != -1) {
-      // it's a package-local reference
-      return null;
-    }
-    // handle the single '/' case by calling twice.
-    String relativePath = StringUtil.trimStart(StringUtil.trimStart(originalLabel, "/"), "/");
-    if (relativePath.startsWith("/")) {
-      return null;
-    }
-    return new FileLookupData(originalLabel, containingFile, null, relativePath, pathFormat, quoteType, null);
-  }
-
-  @Nullable
-  public static FileLookupData packageLocalFileLookup(String originalLabel, StringLiteral element) {
-    if (originalLabel.startsWith("/")) {
-      return null;
-    }
-    BlazePackage blazePackage = element.getBlazePackage();
-    BuildFile baseBuildFile = blazePackage != null ? blazePackage.buildFile : null;
-    return packageLocalFileLookup(originalLabel, element, baseBuildFile, null);
-  }
-
-  @Nullable
-  public static FileLookupData packageLocalFileLookup(String originalLabel,
-                                                      StringLiteral element,
-                                                      @Nullable BuildFile basePackage,
-                                                      @Nullable VirtualFileFilter fileFilter) {
-    String basePackagePath = basePackage != null ? basePackage.getWorkspaceRelativePackagePath() : null;
-    if (basePackagePath == null) {
-      return null;
-    }
-    String filePath = basePackagePath + "/" + LabelUtils.getRuleComponent(originalLabel);
-    return new FileLookupData(originalLabel, basePackage, basePackagePath, filePath, PathFormat.PackageLocal, element.getQuoteType(), fileFilter);
-  }
-
-
-  private final String originalLabel;
-  private final BuildFile containingFile;
-  @Nullable
-  private final String containingPackage;
-  public final String filePathFragment;
-  public final PathFormat pathFormat;
-  private final QuoteType quoteType;
-  @Nullable
-  private final VirtualFileFilter fileFilter;
-
-  private FileLookupData(
-    String originalLabel,
-    @Nullable BuildFile containingFile,
-    @Nullable String containingPackage,
-    String filePathFragment,
-    PathFormat pathFormat,
-    QuoteType quoteType,
-    @Nullable VirtualFileFilter fileFilter) {
-
-    this.originalLabel = originalLabel;
-    this.containingFile = containingFile;
-    this.containingPackage = containingPackage;
-    this.fileFilter = fileFilter;
-    this.filePathFragment = filePathFragment;
-    this.pathFormat = pathFormat;
-    this.quoteType = quoteType;
-
-    assert(pathFormat != PathFormat.PackageLocal || (containingPackage != null && containingFile != null));
-  }
-
-  public boolean acceptFile(VirtualFile file) {
-    if (fileFilter != null && !fileFilter.accept(file)) {
-      return false;
-    }
-    if (pathFormat != PathFormat.PackageLocal) {
-      return file.isDirectory();
-    }
-    if (file.equals(containingFile.getOriginalFile().getVirtualFile())) {
-      return false;
-    }
-    boolean blazePackage = file.findChild("BUILD") != null;
-    return !blazePackage;
-  }
-
-  public FilePathLookupElement lookupElementForFile(Project project, VirtualFile file, @Nullable WorkspacePath workspacePath) {
-    NullableLazyValue<Icon> icon = new NullableLazyValue<Icon>() {
-      @Override
-      protected Icon compute() {
-        if (file.findChild("BUILD") != null) {
-          return BlazeIcons.BuildFile;
-        }
-        if (file.isDirectory()) {
-          return PlatformIcons.FOLDER_ICON;
-        }
-        PsiFile psiFile = PsiManager.getInstance(project).findFile(file);
-        return psiFile != null ? psiFile.getIcon(0) : AllIcons.FileTypes.Any_type;
-      }
-    };
-    String fullLabel = workspacePath != null ? getFullLabel(workspacePath.relativePath()) : file.getPath();
-    String itemText = workspacePath != null ? getItemText(workspacePath.relativePath()) : fullLabel;
-    return new FilePathLookupElement(fullLabel, itemText, quoteType, icon);
-  }
-
-  private String getFullLabel(String relativePath) {
-    if (pathFormat != PathFormat.PackageLocal) {
-      if (pathFormat == PathFormat.NonLocal) {
-        relativePath = "//" + relativePath;
-      }
-      return relativePath;
-    }
-    String prefix;
-    int colonIndex = originalLabel.indexOf(':');
-    if (originalLabel.startsWith("/")) {
-      prefix = colonIndex == -1 ? originalLabel + ":" : originalLabel.substring(0, colonIndex + 1);
-    } else {
-      prefix = originalLabel.substring(0, colonIndex + 1);
-    }
-    return prefix + getItemText(relativePath);
-  }
-
-  private String getItemText(String relativePath) {
-    if (pathFormat == PathFormat.PackageLocal) {
-      return StringUtil.trimStart(relativePath.substring(containingPackage.length()), "/");
-    }
-    String parentPath = PathUtil.getParentPath(relativePath);
-    while (!parentPath.isEmpty()) {
-      if (filePathFragment.startsWith(parentPath + "/")) {
-        return StringUtil.trimStart(relativePath, parentPath + "/");
-      } else if (filePathFragment.startsWith(parentPath)) {
-        return StringUtil.trimStart(relativePath, parentPath);
-      }
-      parentPath = PathUtil.getParentPath(parentPath);
-    }
-    return relativePath;
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/references/FuncallReference.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/references/FuncallReference.java
deleted file mode 100644
index a028a05..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/references/FuncallReference.java
+++ /dev/null
@@ -1,64 +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.references;
-
-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.intellij.lang.ASTNode;
-import com.intellij.openapi.util.TextRange;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiReferenceBase;
-import com.intellij.util.IncorrectOperationException;
-
-import javax.annotation.Nullable;
-
-/**
- * Reference from a function call to the function declaration
- */
-public class FuncallReference extends PsiReferenceBase<FuncallExpression> {
-
-  public FuncallReference(FuncallExpression element, TextRange rangeInElement) {
-    super(element, rangeInElement, /*soft*/ true);
-  }
-
-  @Nullable
-  @Override
-  public FunctionStatement resolve() {
-    String functionName = myElement.getFunctionName();
-    BuildFile file = (BuildFile) myElement.getContainingFile();
-    if (functionName == null || file == null) {
-      return null;
-    }
-    return file.findFunctionInScope(functionName);
-  }
-
-  @Override
-  public Object[] getVariants() {
-    return EMPTY_ARRAY;
-  }
-
-  @Override
-  public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException {
-    ASTNode oldNode = myElement.getFunctionNameNode();
-    if (oldNode != null) {
-      ASTNode newNode = PsiUtils.createNewName(myElement.getProject(), newElementName);
-      myElement.getNode().replaceChild(oldNode, newNode);
-    }
-    return myElement;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/references/GlobReference.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/references/GlobReference.java
deleted file mode 100644
index 3dfbc81..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/references/GlobReference.java
+++ /dev/null
@@ -1,205 +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.references;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.io.FileAttributeProvider;
-import com.google.idea.blaze.base.lang.buildfile.globbing.UnixGlob;
-import com.google.idea.blaze.base.lang.buildfile.psi.*;
-import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.util.TextRange;
-import com.intellij.psi.*;
-import com.intellij.psi.impl.source.resolve.reference.impl.PsiPolyVariantCachingReference;
-import com.intellij.util.IncorrectOperationException;
-
-import java.io.File;
-import java.util.List;
-import java.util.function.Predicate;
-
-/**
- * References from a glob to a list of files contained in the same blaze package.
- */
-public class GlobReference extends PsiPolyVariantCachingReference {
-
-  private static final Logger LOG = Logger.getInstance(GlobReference.class);
-
-  private final GlobExpression element;
-
-  public GlobReference(GlobExpression element) {
-    this.element = element;
-  }
-
-  /**
-   * Returns true iff the complete, resolved glob references the specified file.<p>
-   * In particular, it's not concerned with individual patterns referencing
-   * the file, only whether the overall glob does
-   * (i.e. returns false if the file is explicitly excluded).
-   */
-  public boolean matches(String packageRelativePath, boolean isDirectory) {
-    if (isDirectory && element.areDirectoriesExcluded()) {
-      return false;
-    }
-    for (String exclude : resolveListContents(element.getExcludes())) {
-      if (UnixGlob.matches(exclude, packageRelativePath)) {
-        return false;
-      }
-    }
-    for (String include : resolveListContents(element.getIncludes())) {
-      if (UnixGlob.matches(include, packageRelativePath)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  /**
-   * Returns true iff an include pattern *without wildcards* matches the given path and
-   * it's not excluded.
-   */
-  public boolean matchesDirectly(String packageRelativePath, boolean isDirectory) {
-    if (isDirectory && element.areDirectoriesExcluded()) {
-      return false;
-    }
-    for (String exclude : resolveListContents(element.getExcludes())) {
-      if (UnixGlob.matches(exclude, packageRelativePath)) {
-        return false;
-      }
-    }
-    for (String include : resolveListContents(element.getIncludes())) {
-      if (!hasWildcard(include) && UnixGlob.matches(include, packageRelativePath)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  private static boolean hasWildcard(String pattern) {
-    return pattern.contains("*");
-  }
-
-  @Override
-  protected ResolveResult[] resolveInner(boolean incompleteCode, PsiFile containingFile) {
-    File containingDirectory = ((BuildFile) containingFile).getFile().getParentFile();
-    if (containingDirectory == null) {
-      return ResolveResult.EMPTY_ARRAY;
-    }
-    List<String> includes = resolveListContents(element.getIncludes());
-    List<String> excludes = resolveListContents(element.getExcludes());
-    boolean directoriesExcluded = element.areDirectoriesExcluded();
-    if (includes.isEmpty()) {
-      return ResolveResult.EMPTY_ARRAY;
-    }
-
-    try {
-      List<File> files = UnixGlob.forPath(containingDirectory)
-        .addPatterns(includes)
-        .addExcludes(excludes)
-        .setExcludeDirectories(directoriesExcluded)
-        .setDirectoryFilter(directoryFilter(containingDirectory.getPath()))
-        .glob();
-      List<ResolveResult> results = Lists.newArrayListWithCapacity(files.size());
-      for (File file : files) {
-        PsiFileSystemItem psiFile = BuildReferenceManager.getInstance(element.getProject()).resolveFile(file);
-        if (psiFile != null) {
-          results.add(new PsiElementResolveResult(psiFile));
-        }
-      }
-      return results.toArray(ResolveResult.EMPTY_ARRAY);
-
-    } catch (Exception e) {
-      return ResolveResult.EMPTY_ARRAY;
-    }
-  }
-
-  private static Predicate<File> directoryFilter(String base) {
-    return file -> {
-      if (base.equals(file.getPath())) {
-        return true;
-      }
-      File child = new File(file, "BUILD");
-      FileAttributeProvider attributeProvider = FileAttributeProvider.getInstance();
-      return !attributeProvider.exists(child) || attributeProvider.isDirectory(child);
-    };
-  }
-
-  private static List<String> resolveListContents(Expression expr) {
-    if (expr == null) {
-      return ImmutableList.of();
-    }
-    PsiElement rootElement = PsiUtils.getReferencedTargetValue(expr);
-    if (!(rootElement instanceof ListLiteral)) {
-      return ImmutableList.of();
-    }
-    Expression[] children = ((ListLiteral) rootElement).getElements();
-    List<String> strings = Lists.newArrayListWithCapacity(children.length);
-    for (Expression child : children) {
-      if (child instanceof StringLiteral) {
-        strings.add(((StringLiteral) child).getStringContents());
-      }
-    }
-    return strings;
-  }
-
-  @Override
-  public GlobExpression getElement() {
-    return element;
-  }
-
-  @Override
-  public TextRange getRangeInElement() {
-    return element.getReferenceTextRange();
-  }
-
-  @Override
-  public boolean isSoft() {
-    return true;
-  }
-
-  @Override
-  public Object[] getVariants() {
-    return EMPTY_ARRAY;
-  }
-
-  @Override
-  public String getCanonicalText() {
-    return getValue();
-  }
-
-  @Override
-  public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException {
-    return element;
-  }
-
-  @Override
-  public PsiElement bindToElement(PsiElement element) throws IncorrectOperationException {
-    return this.element;
-  }
-
-  public String getValue() {
-    String text = element.getText();
-    final TextRange range = getRangeInElement();
-    try {
-      return range.substring(text);
-    }
-    catch (StringIndexOutOfBoundsException e) {
-      LOG.error("Wrong range in reference " + this + ": " + range + ". Reference text: '" + text + "'", e);
-      return text;
-    }
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/references/KeywordArgumentReference.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/references/KeywordArgumentReference.java
deleted file mode 100644
index da59a2f..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/references/KeywordArgumentReference.java
+++ /dev/null
@@ -1,82 +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.references;
-
-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.util.PsiUtils;
-import com.intellij.lang.ASTNode;
-import com.intellij.openapi.util.TextRange;
-import com.intellij.psi.PsiElement;
-import com.intellij.util.IncorrectOperationException;
-
-import javax.annotation.Nullable;
-
-/**
- * Reference from keyword argument to a named function parameter.
- * TODO: This is soft, because we can't always find the function. However we should implement error highlighting
- * for imported Skylark functions.
- */
-public class KeywordArgumentReference extends ArgumentReference<Argument.Keyword> {
-
-  public KeywordArgumentReference(Argument.Keyword element, TextRange rangeInElement) {
-    super(element, rangeInElement, false);
-  }
-
-  /**
-   * Find the referenced function. If it has a keyword parameter with matching name,
-   * return that. Otherwise if it has a **kwargs param, return that. Else return the
-   * function itself.
-   */
-  @Nullable
-  @Override
-  public PsiElement resolve() {
-    String keyword = myElement.getName();
-    if (keyword == null) {
-      return null;
-    }
-    FunctionStatement function = resolveFunction();
-    if (function == null) {
-      return null;
-    }
-    Parameter.StarStar kwargsParameter = null;
-    for (Parameter param : function.getParameters()) {
-      if (param instanceof Parameter.StarStar) {
-        kwargsParameter = (Parameter.StarStar) param;
-        continue;
-      }
-      if (keyword.equals(param.getName())) {
-        return param;
-      }
-    }
-    if (kwargsParameter != null) {
-      return kwargsParameter;
-    }
-    return null;
-  }
-
-  @Override
-  public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException {
-    ASTNode oldNode = myElement.getNameNode();
-    if (oldNode != null) {
-      ASTNode newNode = PsiUtils.createNewName(myElement.getProject(), newElementName);
-      myElement.getNode().replaceChild(oldNode, newNode);
-    }
-    return myElement;
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/references/LabelReference.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/references/LabelReference.java
deleted file mode 100644
index 41870e7..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/references/LabelReference.java
+++ /dev/null
@@ -1,247 +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.references;
-
-import com.google.idea.blaze.base.lang.buildfile.completion.BuildLookupElement;
-import com.google.idea.blaze.base.lang.buildfile.completion.LabelRuleLookupElement;
-import com.google.idea.blaze.base.lang.buildfile.psi.*;
-import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile.BlazeFileType;
-import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
-import com.google.idea.blaze.base.lang.buildfile.search.BlazePackage;
-import com.google.idea.blaze.base.lang.buildfile.search.ResolveUtil;
-import com.google.idea.blaze.base.model.primitives.Label;
-import com.intellij.lang.ASTNode;
-import com.intellij.openapi.util.TextRange;
-import com.intellij.openapi.vfs.VirtualFileFilter;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiFile;
-import com.intellij.psi.PsiReferenceBase;
-import com.intellij.util.ArrayUtil;
-import com.intellij.util.IncorrectOperationException;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * Converts a blaze label into an absolute path, then resolves that path to a PsiElements
- */
-public class LabelReference extends PsiReferenceBase<StringLiteral> {
-
-  public LabelReference(StringLiteral element, boolean soft) {
-    super(element, new TextRange(0, element.getTextLength()), soft);
-  }
-
-  @Nullable
-  @Override
-  public PsiElement resolve() {
-    /* Possibilities:
-     * - target
-     * - data file (.java, .txt, etc.)
-     * - glob contents (not yet handling globs)
-     */
-    return resolveTarget(myElement.getStringContents());
-  }
-
-  @Nullable
-  private PsiElement resolveTarget(String labelString) {
-    Label label = getLabel(labelString);
-    if (label == null) {
-      return null;
-    }
-    if (!validLabelLocation(myElement)) {
-      return null;
-    }
-    if (!labelString.startsWith("//") && insideSkylarkExtension(myElement)) {
-      return getReferenceManager().resolveLabel(label.blazePackage(), label.ruleName(), true);
-    }
-    return getReferenceManager().resolveLabel(label);
-  }
-
-  /**
-   * Hack: don't include 'name' keyword arguments -- they'll be a reference to the enclosing function call / rule,
-   * and show up as unnecessary references to that rule.
-   */
-  private static boolean validLabelLocation(StringLiteral element) {
-    PsiElement parent = element.getParent();
-    if (parent instanceof Argument.Keyword) {
-      String argName = ((Argument.Keyword) parent).getName();
-      if ("name".equals(argName)) {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  @NotNull
-  @Override
-  public Object[] getVariants() {
-    if (!validLabelLocation(myElement)) {
-      return EMPTY_ARRAY;
-    }
-    String labelString = LabelUtils.trimToDummyIdentifier(myElement.getStringContents());
-    return ArrayUtil.mergeArrays(
-      getRuleLookups(labelString),
-      getFileLookups(labelString));
-  }
-
-  private BuildLookupElement[] getRuleLookups(String labelString) {
-    if (labelString.endsWith("/")
-        || (labelString.startsWith("/") && !labelString.contains(":"))
-        || skylarkExtensionReference(myElement)) {
-      return BuildLookupElement.EMPTY_ARRAY;
-    }
-    String packagePrefix = LabelUtils.getPackagePathComponent(labelString);
-    BuildFile referencedBuildFile = LabelUtils.getReferencedBuildFile(myElement.getContainingFile(), packagePrefix);
-    if (referencedBuildFile == null) {
-      return BuildLookupElement.EMPTY_ARRAY;
-    }
-    String self = null;
-    if (referencedBuildFile == myElement.getContainingFile()) {
-      FuncallExpression funcall = PsiUtils.getParentOfType(myElement, FuncallExpression.class);
-      if (funcall != null) {
-        self = funcall.getName();
-      }
-    }
-    return LabelRuleLookupElement.collectAllRules(referencedBuildFile, labelString, packagePrefix, self, myElement.getQuoteType());
-  }
-
-  private BuildLookupElement[] getFileLookups(String labelString) {
-    if (labelString.startsWith("//") || labelString.equals("/")) {
-      return getNonLocalFileLookups(labelString);
-    }
-    return getPackageLocalFileLookups(labelString);
-  }
-
-  private BuildLookupElement[] getNonLocalFileLookups(String labelString) {
-    BuildLookupElement[] skylarkExtLookups = getSkylarkExtensionLookups(labelString);
-    FileLookupData lookupData = FileLookupData.nonLocalFileLookup(labelString, myElement);
-    BuildLookupElement[] packageLookups = lookupData != null
-                                          ? getReferenceManager().resolvePackageLookupElements(lookupData)
-                                          : BuildLookupElement.EMPTY_ARRAY;
-    return ArrayUtil.mergeArrays(skylarkExtLookups, packageLookups);
-  }
-
-  private BuildLookupElement[] getPackageLocalFileLookups(String labelString) {
-    if (skylarkExtensionReference(myElement)) {
-      return getSkylarkExtensionLookups(labelString);
-    }
-    FileLookupData lookupData = FileLookupData.packageLocalFileLookup(labelString, myElement);
-    return lookupData != null
-           ? getReferenceManager().resolvePackageLookupElements(lookupData)
-           : BuildLookupElement.EMPTY_ARRAY;
-  }
-
-  private BuildLookupElement[] getSkylarkExtensionLookups(String labelString) {
-    if (!skylarkExtensionReference(myElement)) {
-      return BuildLookupElement.EMPTY_ARRAY;
-    }
-    String packagePrefix = LabelUtils.getPackagePathComponent(labelString);
-    BuildFile parentFile = myElement.getContainingFile();
-    if (parentFile == null) {
-      return BuildLookupElement.EMPTY_ARRAY;
-    }
-    BlazePackage containingPackage = BlazePackage.getContainingPackage(parentFile);
-    if (containingPackage == null) {
-      return BuildLookupElement.EMPTY_ARRAY;
-    }
-    BuildFile referencedBuildFile = LabelUtils.getReferencedBuildFile(containingPackage.buildFile, packagePrefix);
-    VirtualFileFilter filter = file ->
-      file.isDirectory() || ("bzl".equals(file.getExtension()) && !file.getPath().equals(parentFile.getFilePath()));
-    FileLookupData lookupData = FileLookupData.packageLocalFileLookup(labelString, myElement, referencedBuildFile, filter);
-
-    return lookupData != null
-           ? getReferenceManager().resolvePackageLookupElements(lookupData)
-           : BuildLookupElement.EMPTY_ARRAY;
-  }
-
-  private BuildReferenceManager getReferenceManager() {
-    return BuildReferenceManager.getInstance(myElement.getProject());
-  }
-
-  @Override
-  public PsiElement bindToElement(PsiElement element) throws IncorrectOperationException {
-    PsiFile file = ResolveUtil.asFileSearch(element);
-    if (file == null) {
-      return super.bindToElement(element);
-    }
-    if (file.equals(resolve())) {
-      return myElement;
-    }
-    BlazePackage currentPackageDir = myElement.getBlazePackage();
-    if (currentPackageDir == null) {
-      return myElement;
-    }
-    BlazePackage newPackageDir = BlazePackage.getContainingPackage(file);
-    if (!currentPackageDir.equals(newPackageDir)) {
-      return myElement;
-    }
-
-    String newRuleName = newPackageDir.getPackageRelativePath(file.getViewProvider().getVirtualFile().getPath());
-    return handleRename(newRuleName);
-  }
-
-  @Override
-  public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException {
-    String currentString = myElement.getStringContents();
-    Label label = getLabel(currentString);
-    if (label == null) {
-      return myElement;
-    }
-    String ruleName = label.ruleName().toString();
-    String newRuleName = newElementName;
-
-    // handle subdirectories
-    int lastSlashIndex = ruleName.lastIndexOf('/');
-    if (lastSlashIndex != -1) {
-      newRuleName = ruleName.substring(0, lastSlashIndex + 1) + newElementName;
-    }
-
-    String packageString = LabelUtils.getPackagePathComponent(currentString);
-    if (packageString.isEmpty() && !currentString.contains(":")) {
-      return handleRename(newRuleName);
-    }
-    return handleRename(packageString + ":" + newRuleName);
-  }
-
-  private PsiElement handleRename(String newStringContents) {
-    ASTNode node = myElement.getNode();
-    node.replaceChild(node.getFirstChildNode(), PsiUtils.createNewLabel(myElement.getProject(), newStringContents));
-    return myElement;
-  }
-
-  @Nullable
-  private Label getLabel(String labelString) {
-    if (labelString.indexOf('*') != -1) {
-      // don't even try to handle globs, yet.
-      return null;
-    }
-    BlazePackage blazePackage = myElement.getBlazePackage();
-    return LabelUtils.createLabelFromString(blazePackage != null ? blazePackage.buildFile : null, labelString);
-  }
-
-  private static boolean skylarkExtensionReference(StringLiteral element) {
-    PsiElement parent = element.getParent();
-    if (!(parent instanceof LoadStatement)) {
-      return false;
-    }
-    return ((LoadStatement) parent).getImportPsiElement() == element;
-  }
-
-  private static boolean insideSkylarkExtension(StringLiteral element) {
-    BuildFile containingFile = element.getContainingFile();
-    return containingFile != null && containingFile.getBlazeFileType() == BlazeFileType.SkylarkExtension;
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/references/LabelUtils.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/references/LabelUtils.java
deleted file mode 100644
index 43d84b8..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/references/LabelUtils.java
+++ /dev/null
@@ -1,175 +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.references;
-
-import com.google.common.collect.Lists;
-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.WorkspacePath;
-import com.intellij.codeInsight.completion.CompletionUtilCore;
-import com.intellij.util.PathUtil;
-
-import javax.annotation.Nullable;
-import java.util.List;
-
-/**
- * Utility methods for working with blaze labels.
- */
-public class LabelUtils {
-
-  /**
-   * Label referring to the given file, or null if it cannot be determined.
-   */
-  @Nullable
-  public static Label createLabelForFile(BlazePackage blazePackage, @Nullable String filePath) {
-    if (blazePackage == null || filePath == null) {
-      return null;
-    }
-    String relativeFilePath = blazePackage.getPackageRelativePath(filePath);
-    if (relativeFilePath == null) {
-      return null;
-    }
-    return createLabelFromRuleName(blazePackage, relativeFilePath);
-  }
-
-  /**
-   * Returns null if this is not a valid Label (if either the package path or rule name are invalid)
-   */
-  @Nullable
-  public static Label createLabelFromRuleName(@Nullable BlazePackage blazePackage, @Nullable String ruleName) {
-    if (blazePackage == null || ruleName == null) {
-      return null;
-    }
-    WorkspacePath packagePath = blazePackage.buildFile.getPackageWorkspacePath();
-    RuleName name = RuleName.createIfValid(ruleName);
-    if (packagePath == null || name == null) {
-      return null;
-    }
-    // TODO: Is Label too inefficient?
-    // (validation done twice,creating List during constructor, re-parsing to extract the package/rule each time)
-    return new Label(packagePath, name);
-  }
-
-  /**
-   * Canonicalizes the label (to the form //packagePath:packageRelativeTarget).
-   * Returns null if the string does not represent a valid label.
-   */
-  @Nullable
-  public static Label createLabelFromString(@Nullable BuildFile file, @Nullable String labelString) {
-    if (labelString == null) {
-      return null;
-    }
-    int colonIndex = labelString.indexOf(':');
-    if (labelString.startsWith("//")) {
-      if (colonIndex == -1) {
-        // add the implicit rule name
-        labelString += ":" + PathUtil.getFileName(labelString);
-      }
-      return Label.createIfValid(labelString);
-    }
-    WorkspacePath packagePath = file != null ? file.getPackageWorkspacePath() : null;
-    if (packagePath == null) {
-      return null;
-    }
-    String localPath = colonIndex == -1 ? labelString : labelString.substring(1);
-    return Label.createIfValid("//" + packagePath.relativePath() + ":" + localPath);
-  }
-
-  /**
-   * The blaze file referenced by the label.
-   */
-  @Nullable
-  public static BuildFile getReferencedBuildFile(@Nullable BuildFile containingFile, String packagePathComponent) {
-    if (containingFile == null) {
-      return null;
-    }
-    if (!packagePathComponent.startsWith("//")) {
-      return containingFile;
-    }
-    return BuildReferenceManager.getInstance(containingFile.getProject()).resolveBlazePackage(packagePathComponent);
-  }
-
-  public static String getRuleComponent(String labelString) {
-    if (labelString.startsWith("/")) {
-      int colonIndex = labelString.indexOf(':');
-      return colonIndex == -1 ? "" : labelString.substring(colonIndex + 1);
-    }
-    return labelString.startsWith(":") ? labelString.substring(1) : labelString;
-  }
-
-  public static String getPackagePathComponent(String labelString) {
-    if (!labelString.startsWith("//")) {
-      return "";
-    }
-    int colonIndex = labelString.indexOf(':');
-    return colonIndex == -1 ? labelString : labelString.substring(0, colonIndex);
-  }
-
-  /**
-   * 'load' reference. Of the form [path][/ or :][extra_path/]file_name.bzl
-   */
-  @Nullable
-  public static String getNiceSkylarkFileName(@Nullable String path) {
-    if (path == null) {
-      return null;
-    }
-    int colonIndex = path.lastIndexOf(":");
-    if (colonIndex != -1) {
-      path = path.substring(colonIndex + 1);
-    }
-    int lastSlash = path.lastIndexOf("/");
-    if (lastSlash == -1) {
-      return path;
-    }
-    return path.substring(lastSlash + 1);
-  }
-
-  /**
-   * All the possible strings which could resolve to the given target.
-   * @param includePackageLocalLabels if true, include strings omitting the package path
-   */
-  public static List<String> getAllValidLabelStrings(Label label, boolean includePackageLocalLabels) {
-    List<String> strings = Lists.newArrayList();
-    strings.add(label.toString());
-    String packagePath = label.blazePackage().relativePath();
-    if (packagePath.isEmpty()) {
-      return strings;
-    }
-    String ruleName = label.ruleName().toString();
-    if (PathUtil.getFileName(packagePath).equals(ruleName)) {
-      strings.add("//" + packagePath); // implicit rule name equal to package name
-    }
-    if (includePackageLocalLabels) {
-      strings.add(":" + ruleName);
-      strings.add(ruleName);
-    }
-    return strings;
-  }
-
-  /**
-   * IntelliJ inserts an identifier string at the caret position during code completion.<br>
-   * We're only interested in the portion of the string before the caret, so trim the rest.
-   */
-  public static String trimToDummyIdentifier(String string) {
-    int dummyIdentifierIndex = string.indexOf(CompletionUtilCore.DUMMY_IDENTIFIER);
-    if (dummyIdentifierIndex == -1) {
-      dummyIdentifierIndex = string.indexOf(CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED);
-    }
-    return dummyIdentifierIndex == -1 ? string : string.substring(0, dummyIdentifierIndex);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/references/LoadedSymbolReference.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/references/LoadedSymbolReference.java
deleted file mode 100644
index 5359f94..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/references/LoadedSymbolReference.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.lang.buildfile.references;
-
-import com.google.idea.blaze.base.lang.buildfile.completion.CompletionResultsProcessor;
-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.StringLiteral;
-import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
-import com.intellij.lang.ASTNode;
-import com.intellij.openapi.util.TextRange;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiReferenceBase;
-import com.intellij.util.IncorrectOperationException;
-
-import javax.annotation.Nullable;
-
-/**
- * References from load statement string to a function or variable in a Skylark extension
- */
-public class LoadedSymbolReference extends PsiReferenceBase<StringLiteral> {
-
-  private final LabelReference bzlFileReference;
-
-  public LoadedSymbolReference(StringLiteral element, LabelReference bzlFileReference) {
-    super(element, new TextRange(0, element.getTextLength()), /*soft*/ false);
-    this.bzlFileReference = bzlFileReference;
-  }
-
-  @Nullable
-  @Override
-  public BuildElement resolve() {
-    PsiElement bzlFile = bzlFileReference.resolve();
-    if (!(bzlFile instanceof BuildFile)) {
-      return null;
-    }
-    return ((BuildFile) bzlFile).findSymbolInScope(myElement.getStringContents());
-  }
-
-  @Override
-  public Object[] getVariants() {
-    PsiElement bzlFile = bzlFileReference.resolve();
-    if (!(bzlFile instanceof BuildFile)) {
-      return EMPTY_ARRAY;
-    }
-    CompletionResultsProcessor processor = new CompletionResultsProcessor(myElement, myElement.getQuoteType());
-    ((BuildFile) bzlFile).searchSymbolsInScope(processor, null);
-    return processor.getResults().toArray();
-  }
-
-  @Override
-  public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException {
-    ASTNode newNode = PsiUtils.createNewLabel(myElement.getProject(), newElementName);
-    myElement.getNode().replaceChild(myElement.getNode().getFirstChildNode(), newNode);
-    return myElement;
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/references/LocalReference.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/references/LocalReference.java
deleted file mode 100644
index eb420d5..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/references/LocalReference.java
+++ /dev/null
@@ -1,66 +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.references;
-
-import com.google.idea.blaze.base.lang.buildfile.completion.CompletionResultsProcessor;
-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.lang.buildfile.search.ResolveUtil;
-import com.intellij.lang.ASTNode;
-import com.intellij.openapi.util.TextRange;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiReferenceBase;
-import com.intellij.util.IncorrectOperationException;
-
-import javax.annotation.Nullable;
-
-/**
- * Reference from a ReferenceExpression to a PsiNamedElement in the same scope.
- * This includes symbols accessible via 'load' statments.
- */
-public class LocalReference extends PsiReferenceBase<ReferenceExpression> {
-
-  public LocalReference(ReferenceExpression element) {
-    super(element, new TextRange(0, element.getTextLength()), /*soft*/ false);
-  }
-
-  @Nullable
-  @Override
-  public PsiElement resolve() {
-    String referencedName = myElement.getReferencedName();
-    if (referencedName == null) {
-      return null;
-    }
-    return ResolveUtil.findInScope(myElement, referencedName);
-  }
-
-  @Override
-  public Object[] getVariants() {
-    CompletionResultsProcessor processor = new CompletionResultsProcessor(myElement, QuoteType.NoQuotes);
-    ResolveUtil.searchInScope(myElement, processor);
-    return processor.getResults().toArray();
-  }
-
-  @Override
-  public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException {
-    ASTNode oldNode = myElement.getNameElement();
-    if (oldNode != null) {
-      ASTNode newNode = PsiUtils.createNewName(myElement.getProject(), newElementName);
-      myElement.getNode().replaceChild(oldNode, newNode);
-    }
-    return myElement;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/references/PackageReferenceFragment.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/references/PackageReferenceFragment.java
deleted file mode 100644
index 572d9bf..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/references/PackageReferenceFragment.java
+++ /dev/null
@@ -1,108 +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.references;
-
-import com.google.idea.blaze.base.model.primitives.WorkspacePath;
-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.intellij.lang.ASTNode;
-import com.intellij.openapi.util.TextRange;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiReferenceBase;
-import com.intellij.util.IncorrectOperationException;
-import com.intellij.util.PathUtil;
-
-import javax.annotation.Nullable;
-
-/**
- * The label component preceeding the colon.
- */
-public class PackageReferenceFragment extends PsiReferenceBase<StringLiteral> {
-
-  public PackageReferenceFragment(LabelReference labelReference) {
-    super(labelReference.getElement(), labelReference.getRangeInElement(), labelReference.isSoft());
-  }
-
-  @Nullable
-  private WorkspacePath getWorkspacePath(String labelString) {
-    if (!labelString.startsWith("//")) {
-      return null;
-    }
-    int colonIndex = labelString.indexOf(':');
-    int endIndex = colonIndex != -1 ? colonIndex : labelString.length();
-    return WorkspacePath.createIfValid(labelString.substring(2, endIndex));
-  }
-
-  @Override
-  public TextRange getRangeInElement() {
-    String rawText = myElement.getText();
-    boolean valid = getWorkspacePath(myElement.getStringContents()) != null;
-    if (!valid) {
-      return TextRange.EMPTY_RANGE;
-    }
-    int endIndex = rawText.indexOf(':');
-    if (endIndex == -1) {
-      endIndex = rawText.length() - 1;
-    }
-    return new TextRange(1, endIndex);
-  }
-
-  @Nullable
-  @Override
-  public BuildFile resolve() {
-    WorkspacePath workspacePath = getWorkspacePath(myElement.getStringContents());
-    return BuildReferenceManager.getInstance(myElement.getProject()).resolveBlazePackage(workspacePath);
-  }
-
-  @Override
-  public Object[] getVariants() {
-    return EMPTY_ARRAY;
-  }
-
-  @Override
-  public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException {
-    return myElement; // renaming a BUILD file has no effect on the package label fragments
-  }
-
-  @Override
-  public PsiElement bindToElement(PsiElement element) throws IncorrectOperationException {
-    if (!(element instanceof BuildFile)) {
-      return super.bindToElement(element);
-    }
-    if (element.equals(resolve())) {
-      return myElement;
-    }
-    WorkspacePath newPath = ((BuildFile) element).getPackageWorkspacePath();
-    if (newPath == null) {
-      return myElement;
-    }
-    String labelString = myElement.getStringContents();
-    int colonIndex = labelString.indexOf(':');
-    if (colonIndex != -1) {
-      return handleRename("//" + newPath + labelString.substring(colonIndex));
-    }
-    // need to assume there's an implicit rule name
-    return handleRename("//" + newPath + ":" + PathUtil.getFileName(labelString));
-  }
-
-  private PsiElement handleRename(String newStringContents) {
-    ASTNode node = myElement.getNode();
-    node.replaceChild(node.getFirstChildNode(), PsiUtils.createNewLabel(myElement.getProject(), newStringContents));
-    return myElement;
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/references/QuoteType.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/references/QuoteType.java
deleted file mode 100644
index 40125fd..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/references/QuoteType.java
+++ /dev/null
@@ -1,37 +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.references;
-
-/**
- * The type of quotes surrounding a PSI element.
- */
-public enum QuoteType {
-  Single("'"),
-  Double("\""),
-  TripleSingle("'''"),
-  TripleDouble("\"\"\""),
-  NoQuotes("");
-
-  public final String quoteString;
-
-  QuoteType(String quoteString) {
-    this.quoteString = quoteString;
-  }
-
-  public String wrap(String unquotedText) {
-    return quoteString + unquotedText + quoteString;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/references/TargetReference.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/references/TargetReference.java
deleted file mode 100644
index b8144bf..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/references/TargetReference.java
+++ /dev/null
@@ -1,69 +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.references;
-
-import com.google.idea.blaze.base.lang.buildfile.completion.CompletionResultsProcessor;
-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.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;
-
-/**
- * A reference to an earlier declaration of this symbol (to handle cases where a symbol
- * is the target of multiple assignment statements).
- */
-public class TargetReference extends PsiReferenceBase<TargetExpression> {
-
-  public TargetReference(TargetExpression element) {
-    super(element, new TextRange(0, element.getTextLength()), /*soft*/ true);
-  }
-
-  @Nullable
-  @Override
-  public PsiElement resolve() {
-    String referencedName = myElement.getName();
-    if (referencedName == null) {
-      return null;
-    }
-    PsiNamedElement target = ResolveUtil.findInScope(myElement, referencedName);
-    return target != null ? target : null;
-  }
-
-  @Override
-  public Object[] getVariants() {
-    CompletionResultsProcessor processor = new CompletionResultsProcessor(myElement, QuoteType.NoQuotes);
-    ResolveUtil.searchInScope(myElement, processor);
-    return processor.getResults().toArray();
-  }
-
-  @Override
-  public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException {
-    ASTNode oldNode = myElement.getNameNode();
-    if (oldNode != null) {
-      ASTNode newNode = PsiUtils.createNewName(myElement.getProject(), newElementName);
-      myElement.getNode().replaceChild(oldNode, newNode);
-    }
-    return myElement;
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/search/BlazePackage.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/search/BlazePackage.java
deleted file mode 100644
index 301ce93..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/search/BlazePackage.java
+++ /dev/null
@@ -1,167 +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.search;
-
-import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
-import com.google.idea.blaze.base.settings.Blaze;
-import com.intellij.history.core.Paths;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.psi.PackagePrefixFileSystemItem;
-import com.intellij.psi.PsiDirectory;
-import com.intellij.psi.PsiFile;
-import com.intellij.psi.PsiFileSystemItem;
-import com.intellij.psi.search.GlobalSearchScope;
-import com.intellij.util.PathUtil;
-import com.intellij.util.Processor;
-
-import javax.annotation.Nullable;
-import java.util.Objects;
-
-/**
- * Defines the files accessible by a given blaze package.
- */
-public class BlazePackage {
-
-  @Nullable
-  public static BlazePackage getContainingPackage(PsiFileSystemItem file) {
-    if (file instanceof PsiFile) {
-      file = ((PsiFile) file).getOriginalFile();
-    }
-    if (file instanceof BuildFile && file.getName().equals("BUILD")) {
-      return new BlazePackage((BuildFile) file);
-    }
-    return getContainingPackage(getPsiDirectory(file));
-  }
-
-  @Nullable
-  private static PsiDirectory getPsiDirectory(PsiFileSystemItem file) {
-    if (file instanceof PsiDirectory) {
-      return (PsiDirectory) file;
-    }
-    if (file instanceof PsiFile) {
-      return ((PsiFile) file).getContainingDirectory();
-    }
-    if (file instanceof PackagePrefixFileSystemItem) {
-      return ((PackagePrefixFileSystemItem) file).getDirectory();
-    }
-    return null;
-  }
-
-  @Nullable
-  public static BlazePackage getContainingPackage(@Nullable PsiDirectory dir) {
-    while (dir != null) {
-      PsiFile buildFile = dir.findFile("BUILD");
-      if (buildFile != null) {
-        return buildFile instanceof BuildFile ? new BlazePackage((BuildFile) buildFile) : null;
-      }
-      dir = dir.getParentDirectory();
-    }
-    return null;
-  }
-
-  public final BuildFile buildFile;
-
-  private BlazePackage(BuildFile buildFile) {
-    this.buildFile = buildFile;
-  }
-
-  @Nullable
-  public PsiDirectory getContainingDirectory() {
-    return buildFile.getParent();
-  }
-
-  /**
-   * The search scope corresponding to this package (i.e. not crossing package boundaries).
-   * @param onlyBlazeFiles if true, the scope is limited to BUILD and Skylark files.
-   */
-  public GlobalSearchScope getSearchScope(boolean onlyBlazeFiles) {
-    return new BlazePackageSearchScope(this, onlyBlazeFiles);
-  }
-
-  /**
-   * Returns the file path relative to this blaze package, or null if it does lie inside this package
-   */
-  @Nullable
-  public String getPackageRelativePath(String filePath) {
-    String packageFilePath = PathUtil.getParentPath(buildFile.getFilePath());
-    return Paths.relativeIfUnder(filePath, packageFilePath);
-  }
-
-  /**
-   * The path from the blaze package directory to the child file, or null if
-   * the package directory is not an ancestor of the provided file.
-   */
-  @Nullable
-  public String getRelativePathToChild(@Nullable VirtualFile child) {
-    if (child == null) {
-      return null;
-    }
-    String packagePath = PathUtil.getParentPath(buildFile.getFilePath());
-    return Paths.relativeIfUnder(child.getPath(), packagePath);
-  }
-
-  /**
-   * Walks the directory tree, processing all files accessible by this package (i.e. not processing child packages).
-   */
-  public void processPackageFiles(Processor<PsiFile> processor) {
-    PsiDirectory dir = getContainingDirectory();
-    if (dir == null) {
-      return;
-    }
-    processPackageFiles(processor, dir);
-  }
-
-  private static void processPackageFiles(Processor<PsiFile> processor, PsiDirectory directory) {
-    processDirectory(processor, directory);
-    for (PsiDirectory child : directory.getSubdirectories()) {
-      if (!isBlazePackage(child)) {
-        processPackageFiles(processor, directory);
-      }
-    }
-  }
-
-  private static boolean isBlazePackage(PsiDirectory directory) {
-    return directory.findFile("BUILD") != null;
-  }
-
-  private static void processDirectory(Processor<PsiFile> processor, PsiDirectory directory) {
-    for (PsiFile file : directory.getFiles()) {
-      processor.process(file);
-    }
-  }
-
-  @Override
-  public boolean equals(Object obj) {
-    if (!(obj instanceof BlazePackage)) {
-      return false;
-    }
-    if (obj == this) {
-      return true;
-    }
-    BlazePackage that = (BlazePackage) obj;
-    return Objects.equals(buildFile.getFilePath(), that.buildFile.getFilePath());
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hash(buildFile.getFilePath());
-  }
-
-  @Override
-  public String toString() {
-    return String.format("%s package: %s", Blaze.buildSystemName(buildFile.getProject()), buildFile.getPackageWorkspacePath());
-  }
-}
\ No newline at end of file
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/search/BlazePackageSearchScope.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/search/BlazePackageSearchScope.java
deleted file mode 100644
index 750a1be..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/search/BlazePackageSearchScope.java
+++ /dev/null
@@ -1,106 +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.search;
-
-import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
-import com.google.idea.blaze.base.settings.Blaze;
-import com.intellij.openapi.module.Module;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.psi.PsiFile;
-import com.intellij.psi.PsiManager;
-import com.intellij.psi.search.GlobalSearchScope;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.Objects;
-
-/**
- * A scope limited to a single blaze/bazel package, which doesn't cross package boundaries.
- */
-public class BlazePackageSearchScope extends GlobalSearchScope {
-
-  private final BlazePackage blazePackage;
-  private final boolean onlyBlazeFiles;
-
-  public BlazePackageSearchScope(BlazePackage blazePackage, boolean onlyBlazeFiles) {
-    super(blazePackage.buildFile.getProject());
-    this.blazePackage = blazePackage;
-    this.onlyBlazeFiles = onlyBlazeFiles;
-  }
-
-  @Override
-  public boolean contains(@NotNull VirtualFile file) {
-    PsiFile psiFile = PsiManager.getInstance(getProject()).findFile(file);
-    if (onlyBlazeFiles && !(psiFile instanceof BuildFile)) {
-      return false;
-    }
-    return blazePackage.equals(BlazePackage.getContainingPackage(psiFile));
-  }
-
-  @Override
-  public int compare(@NotNull VirtualFile file1, @NotNull VirtualFile file2) {
-    return 0;
-  }
-
-  @Override
-  public boolean isSearchInModuleContent(@NotNull Module aModule) {
-    return true;
-  }
-
-  @Override
-  public boolean isSearchInLibraries() {
-    return false;
-  }
-
-  @Override
-  public String toString() {
-    return String.format("%s directory scope: %s", Blaze.buildSystemName(getProject()), blazePackage.buildFile.getPackageWorkspacePath());
-  }
-
-  @Override
-  public boolean equals(Object obj) {
-    if (!(obj instanceof BlazePackageSearchScope)) {
-      return false;
-    }
-    if (obj == this) {
-      return true;
-    }
-    BlazePackageSearchScope other = (BlazePackageSearchScope) obj;
-    return blazePackage.equals(other.blazePackage) && onlyBlazeFiles == other.onlyBlazeFiles;
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hash(blazePackage, onlyBlazeFiles);
-  }
-
-  @Override
-  public String getDisplayName() {
-    return blazePackage.toString();
-  }
-
-  @Override
-  public GlobalSearchScope uniteWith(@NotNull GlobalSearchScope scope) {
-    if (scope instanceof BlazePackageSearchScope) {
-      BlazePackageSearchScope other = (BlazePackageSearchScope) scope;
-      if (!blazePackage.equals(other.blazePackage)) {
-        return GlobalSearchScope.EMPTY_SCOPE;
-      }
-      return onlyBlazeFiles ? this : other;
-    }
-    return super.uniteWith(scope);
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/search/BuildLabelReferenceSearcher.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/search/BuildLabelReferenceSearcher.java
deleted file mode 100644
index f8ae2e6..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/search/BuildLabelReferenceSearcher.java
+++ /dev/null
@@ -1,163 +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.search;
-
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.model.primitives.Label;
-import com.google.idea.blaze.base.model.primitives.WorkspacePath;
-import com.google.idea.blaze.base.lang.buildfile.language.BuildFileType;
-import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
-import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile.BlazeFileType;
-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.references.LabelUtils;
-import com.intellij.openapi.application.QueryExecutorBase;
-import com.intellij.psi.PsiDirectory;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiFile;
-import com.intellij.psi.PsiReference;
-import com.intellij.psi.search.*;
-import com.intellij.psi.search.searches.ReferencesSearch.SearchParameters;
-import com.intellij.util.Processor;
-
-import javax.annotation.Nullable;
-import java.util.List;
-
-/**
- * String search for label references in BUILD files
- */
-public class BuildLabelReferenceSearcher extends QueryExecutorBase<PsiReference, SearchParameters> {
-
-  public BuildLabelReferenceSearcher() {
-    super(true);
-  }
-
-  @Override
-  public void processQuery(SearchParameters params, Processor<PsiReference> consumer) {
-    PsiElement element = params.getElementToSearch();
-    if (element instanceof FunctionStatement) {
-      String fnName = ((FunctionStatement) element).getName();
-      if (fnName != null) {
-        searchForString(params, element, fnName);
-      }
-      return;
-    }
-    PsiFile file = ResolveUtil.asFileSearch(element);
-    if (file != null) {
-      processFileReferences(params, file);
-      return;
-    }
-
-    if (!(element instanceof FuncallExpression)) {
-      return;
-    }
-
-    Label label = ((FuncallExpression) element).resolveBuildLabel();
-    PsiFile localFile = element.getContainingFile();
-    if (label == null || localFile == null) {
-      return;
-    }
-    List<String> stringsToSearch = LabelUtils.getAllValidLabelStrings(label, true);
-    for (String string : stringsToSearch) {
-      if (string.startsWith("//")) {
-        searchForString(params, element, string);
-      } else {
-        // only a valid reference from local package -- restrict the search scope accordingly
-        SearchScope scope = limitScopeToFile(params.getScopeDeterminedByUser(), localFile);
-        if (scope != null) {
-          searchForString(params, scope, element, string);
-        }
-      }
-    }
-  }
-
-  /**
-   * Find all references to the given file within BUILD files.
-   */
-  private void processFileReferences(SearchParameters params, PsiFile file) {
-    if (file instanceof BuildFile) {
-      BuildFile buildFile = (BuildFile) file;
-      processBuildFileReferences(params, buildFile);
-      if (buildFile.getBlazeFileType() == BlazeFileType.BuildPackage) {
-        return;
-      }
-      // for skylark extensions, we also check for package-local references, below
-    }
-    BlazePackage blazePackage = BlazePackage.getContainingPackage(file);
-    PsiDirectory directory = blazePackage != null ? blazePackage.getContainingDirectory() : null;
-    if (directory == null) {
-      return;
-    }
-    Label label = LabelUtils.createLabelForFile(blazePackage, PsiUtils.getFilePath(file));
-    if (label == null) {
-      return;
-    }
-
-    // files can only be directly referenced in the containing blaze package
-    List<String> stringsToSearch = LabelUtils.getAllValidLabelStrings(label, true);
-    SearchScope scope = params.getScopeDeterminedByUser()
-      .intersectWith(blazePackage.getSearchScope(true));
-
-    for (String string : stringsToSearch) {
-      searchForString(params, scope, file, string);
-    }
-  }
-
-  /**
-   * Find references to both the file itself, and build targets defined in the file.
-   */
-  private void processBuildFileReferences(SearchParameters params, BuildFile file) {
-    WorkspacePath workspacePath = file.getPackageWorkspacePath();
-    if (workspacePath == null) {
-      return;
-    }
-    List<String> stringsToSearch = Lists.newArrayList();
-    if (file.getBlazeFileType() == BlazeFileType.BuildPackage) {
-      stringsToSearch.add("//" + workspacePath);
-    } else {
-      stringsToSearch.add("//" + workspacePath + ":" + file.getName());
-      stringsToSearch.add("//" + workspacePath + "/" + file.getName()); // deprecated load/subinclude format
-    }
-    for (String string : stringsToSearch) {
-      searchForString(params, file, string);
-    }
-  }
-
-  /**
-   * Search for package-local references.<br>
-   * Returns null if the resulting scope is empty
-   */
-  @Nullable
-  private static SearchScope limitScopeToFile(SearchScope scope, PsiFile file) {
-    if (scope instanceof LocalSearchScope) {
-      return ((LocalSearchScope) scope).isInScope(file.getVirtualFile()) ? new LocalSearchScope(file) : null;
-    }
-    return scope.intersectWith(new LocalSearchScope(file));
-  }
-
-  private static void searchForString(SearchParameters params, PsiElement element, String string) {
-    searchForString(params, params.getScopeDeterminedByUser(), element, string);
-  }
-
-  private static void searchForString(SearchParameters params, SearchScope scope, PsiElement element, String string) {
-    if (scope instanceof GlobalSearchScope) {
-      scope = GlobalSearchScope.getScopeRestrictedByFileTypes((GlobalSearchScope) scope, BuildFileType.INSTANCE);
-    }
-    params.getOptimizer().searchWord(string, scope, UsageSearchContext.IN_STRINGS, true, element);
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/search/ExcludeBuildFilesScope.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/search/ExcludeBuildFilesScope.java
deleted file mode 100644
index 60c73a4..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/search/ExcludeBuildFilesScope.java
+++ /dev/null
@@ -1,48 +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.search;
-
-import com.google.idea.blaze.base.lang.buildfile.language.BuildFileType;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiFileSystemItem;
-import com.intellij.psi.search.EverythingGlobalScope;
-import com.intellij.psi.search.GlobalSearchScope;
-import com.intellij.psi.search.UseScopeOptimizer;
-
-import javax.annotation.Nullable;
-
-/**
- * Causes all calls to PsiSearchHelper.getUseScope to exclude BUILD files, when searching for files.<br>
- * BUILD file / BUILD package references are handled by a separate reference searcher.<p>
- *
- * This is a hack, but greatly improves efficiency. The reasoning behind this:
- *  - BUILD files have very strict file reference patterns, and very narrow direct reference scopes (a package can't
- *  directly reference files in another package).
- *  - IJ *constantly* performs global searches on strings when manipulating files (e.g. searching for file uses for
- *  highlighting, rename, move operations). This causes us to re-parse every BUILD file in the project, multiple times.
- *
- */
-public class ExcludeBuildFilesScope extends UseScopeOptimizer {
-
-  @Nullable
-  @Override
-  public GlobalSearchScope getScopeToExclude(PsiElement element) {
-    if (element instanceof PsiFileSystemItem) {
-      return GlobalSearchScope.getScopeRestrictedByFileTypes(new EverythingGlobalScope(), BuildFileType.INSTANCE);
-    }
-    return null;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/search/FindUsages.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/search/FindUsages.java
deleted file mode 100644
index 24c9aa8..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/search/FindUsages.java
+++ /dev/null
@@ -1,45 +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.search;
-
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiReference;
-import com.intellij.psi.search.GlobalSearchScope;
-import com.intellij.psi.search.PsiSearchHelper;
-import com.intellij.psi.search.SearchScope;
-import com.intellij.psi.search.searches.ReferencesSearch;
-
-/**
- * Utility methods for finding all references to a PsiElement
- */
-public class FindUsages {
-
-  public static PsiReference[] findAllReferences(PsiElement element) {
-    return findReferencesInScope(element, GlobalSearchScope.allScope(element.getProject()));
-  }
-
-  /**
-   * Search scope taken from PsiSearchHelper::getUseScope, which incorporates UseScopeEnlarger / UseScopeOptimizer EPs.
-   */
-  public static PsiReference[] findReferencesInElementScope(PsiElement element) {
-    return findReferencesInScope(element, PsiSearchHelper.SERVICE.getInstance(element.getProject()).getUseScope(element));
-  }
-
-  public static PsiReference[] findReferencesInScope(PsiElement element, SearchScope scope) {
-    return ReferencesSearch.search(element, scope, true).toArray(new PsiReference[0]);
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/search/GlobReferenceSearcher.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/search/GlobReferenceSearcher.java
deleted file mode 100644
index d9e2e58..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/search/GlobReferenceSearcher.java
+++ /dev/null
@@ -1,91 +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.search;
-
-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.intellij.openapi.application.QueryExecutorBase;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiFileSystemItem;
-import com.intellij.psi.PsiReference;
-import com.intellij.psi.PsiReferenceBase;
-import com.intellij.psi.search.GlobalSearchScope;
-import com.intellij.psi.search.LocalSearchScope;
-import com.intellij.psi.search.SearchScope;
-import com.intellij.psi.search.searches.ReferencesSearch.SearchParameters;
-import com.intellij.util.IncorrectOperationException;
-import com.intellij.util.Processor;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.List;
-
-/**
- * Searches for references to a file in globs. These aren't picked up by a standard string search, and are only
- * evaluated on demand, so we can't just check a reference cache.<p>
- *
- * Unlike resolving a glob, this requires no file system calls (beyond finding the parent blaze package),
- * because we're only interested in a single file, which is already known to exist.<p>
- *
- * This is always a local search (as glob references can't cross package boundaries).
- */
-public class GlobReferenceSearcher extends QueryExecutorBase<PsiReference, SearchParameters> {
-
-  public GlobReferenceSearcher() {
-    super(true);
-  }
-
-  @Override
-  public void processQuery(SearchParameters queryParameters, Processor<PsiReference> consumer) {
-    PsiFileSystemItem file = ResolveUtil.asFileSystemItemSearch(queryParameters.getElementToSearch());
-    if (file == null) {
-      return;
-    }
-    BlazePackage containingPackage = BlazePackage.getContainingPackage(file);
-    if (containingPackage == null || !inScope(queryParameters, containingPackage.buildFile)) {
-      return;
-    }
-    String relativePath = containingPackage.getRelativePathToChild(file.getVirtualFile());
-    if (relativePath == null) {
-      return;
-    }
-
-    List<GlobExpression> globs = PsiUtils.findAllChildrenOfClassRecursive(containingPackage.buildFile, GlobExpression.class);
-    for (GlobExpression glob : globs) {
-      if (glob.matches(relativePath, file.isDirectory())) {
-        consumer.process(globReference(glob, file));
-      }
-    }
-  }
-
-  private static PsiReference globReference(GlobExpression glob, PsiFileSystemItem file) {
-    return new PsiReferenceBase.Immediate<GlobExpression>(glob, glob.getReferenceTextRange(), file) {
-      @Override
-      public PsiElement bindToElement(@NotNull PsiElement element) throws IncorrectOperationException {
-        return glob;
-      }
-    };
-  }
-
-  private static boolean inScope(SearchParameters queryParameters, BuildFile buildFile) {
-    SearchScope scope = queryParameters.getScopeDeterminedByUser();
-    if (scope instanceof GlobalSearchScope) {
-      return ((GlobalSearchScope) scope).contains(buildFile.getVirtualFile());
-    }
-    return ((LocalSearchScope) scope).isInScope(buildFile.getVirtualFile());
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/search/PsiFileProvider.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/search/PsiFileProvider.java
deleted file mode 100644
index 0230744..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/search/PsiFileProvider.java
+++ /dev/null
@@ -1,34 +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.search;
-
-import com.intellij.openapi.extensions.ExtensionPointName;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiFile;
-
-import javax.annotation.Nullable;
-
-/**
- * Checks if the PsiElement represents a top-level PsiFile (e.g. a top-level java PsiClass can be interchanged with the
- * corresponding PsiFile, when searching for usages).
- */
-public interface PsiFileProvider {
-
-  ExtensionPointName<PsiFileProvider> EP_NAME = ExtensionPointName.create("com.google.idea.blaze.PsiFileProvider");
-
-  @Nullable
-  PsiFile asFileSearch(PsiElement elementToSearch);
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/search/ResolveUtil.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/search/ResolveUtil.java
deleted file mode 100644
index 4e3a2e2..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/search/ResolveUtil.java
+++ /dev/null
@@ -1,166 +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.search;
-
-import com.google.idea.blaze.base.lang.buildfile.psi.*;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiFile;
-import com.intellij.psi.PsiFileSystemItem;
-import com.intellij.psi.PsiNamedElement;
-import com.intellij.util.Processor;
-
-import javax.annotation.Nullable;
-
-/**
- * Utilities methods for resolving references
- */
-public class ResolveUtil {
-
-  /**
-   * Walks up PSI tree of local file, checking PsiNamedElements
-   */
-  public static void searchInScope(PsiElement originalElement, Processor<BuildElement> processor) {
-    // TODO: Handle list comprehension (where variable is defined *later* in the code)
-    boolean topLevelScope = true;
-    PsiElement element = originalElement;
-    while (!(element instanceof PsiFileSystemItem)) {
-      PsiElement parent = element.getParent();
-      if (parent instanceof BuildFile) {
-        if (!((BuildFile) parent).searchSymbolsInScope(processor, topLevelScope ? element : null)) {
-          return;
-        }
-      } else if (parent instanceof FunctionStatement) {
-        topLevelScope = false;
-        for (Parameter param : ((FunctionStatement) parent).getParameters()) {
-          if (!processor.process(param)) {
-            return;
-          }
-        }
-      } else if (parent instanceof ForStatement) {
-        for (Expression expr : ((ForStatement) parent).getForLoopVariables()) {
-          if (expr instanceof TargetExpression && !processor.process(expr)) {
-            return;
-          }
-        }
-      } else if (parent instanceof StatementList)  {
-        if (!visitChildAssignmentStatements((BuildElement) parent, (Processor) processor)) {
-          return;
-        }
-      }
-      element = parent;
-    }
-  }
-
-  /**
-   * Walks up PSI tree of local file, checking PsiNamedElements
-   */
-  @Nullable
-  public static PsiNamedElement findInScope(PsiElement element, String name) {
-    PsiNamedElement[] resultHolder = new PsiNamedElement[1];
-    Processor<BuildElement> processor = buildElement -> {
-      if (buildElement == element) {
-        return true;
-      }
-      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;
-            return false;
-          }
-        }
-      }
-      return true;
-    };
-    searchInScope(element, processor);
-    return resultHolder[0];
-  }
-
-  /**
-   * @return false if processing was stopped
-   */
-  public static boolean visitChildAssignmentStatements(BuildElement parent, Processor<TargetExpression> processor) {
-    for (AssignmentStatement stmt : parent.childrenOfClass(AssignmentStatement.class)) {
-      TargetExpression target = stmt.getLeftHandSideExpression();
-      if (target != null && !processor.process(target)) {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  @Nullable
-  public static TargetExpression searchChildAssignmentStatements(BuildElement parent, String name) {
-    TargetExpression[] resultHolder = new TargetExpression[1];
-    visitChildAssignmentStatements(parent, targetExpr -> {
-      if (name.equals(targetExpr.getName())) {
-        resultHolder[0] = targetExpr;
-        return false;
-      }
-      return true;
-    });
-    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.
-   */
-  @Nullable
-  public static PsiFileSystemItem asFileSystemItemSearch(PsiElement elementToSearch) {
-    if (elementToSearch instanceof PsiFileSystemItem) {
-      return (PsiFileSystemItem) elementToSearch;
-    }
-    return asFileSearch(elementToSearch);
-  }
-
-  /**
-   * Checks if the element we're searching for is represented by a file.<br>
-   * e.g. a java class PSI element, or an actual PsiFile element.
-   */
-  @Nullable
-  public static PsiFile asFileSearch(PsiElement elementToSearch) {
-    if (elementToSearch instanceof PsiFile) {
-      return (PsiFile) elementToSearch;
-    }
-    for (PsiFileProvider provider : PsiFileProvider.EP_NAME.getExtensions()) {
-      PsiFile file = provider.asFileSearch(elementToSearch);
-      if (file != null) {
-        return file;
-      }
-    }
-    return null;
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/sync/BuildLangSyncPlugin.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/sync/BuildLangSyncPlugin.java
deleted file mode 100644
index d4405bd..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/sync/BuildLangSyncPlugin.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.lang.buildfile.sync;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.idea.blaze.base.command.info.BlazeInfo;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-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.Label;
-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.Scope;
-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.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.projectview.WorkspaceLanguageSettings;
-import com.google.repackaged.devtools.build.lib.query2.proto.proto2api.Build;
-import com.google.repackaged.protobuf.InvalidProtocolBufferException;
-import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.project.Project;
-
-import javax.annotation.Nullable;
-import java.io.File;
-import java.util.concurrent.ExecutionException;
-
-/**
- * Updates the language specification during the blaze sync process
- */
-public class BuildLangSyncPlugin extends BlazeSyncPlugin.Adapter {
-
-  private static final Logger LOG = Logger.getInstance(BuildLangSyncPlugin.class);
-
-  @Override
-  public void updateSyncState(Project project,
-                              BlazeContext context,
-                              WorkspaceRoot workspaceRoot,
-                              ProjectViewSet projectViewSet,
-                              WorkspaceLanguageSettings workspaceLanguageSettings,
-                              BlazeRoots blazeRoots,
-                              @Nullable WorkingSet workingSet,
-                              WorkspacePathResolver workspacePathResolver,
-                              ImmutableMap<Label, RuleIdeInfo> ruleMap,
-                              @Deprecated @Nullable File androidPlatformDirectory,
-                              SyncState.Builder syncStateBuilder,
-                              @Nullable SyncState previousSyncState) {
-
-    LanguageSpecResult spec = getBuildLanguageSpec(project, workspaceRoot, previousSyncState, context);
-    if (spec != null) {
-      syncStateBuilder.put(LanguageSpecResult.class, spec);
-    }
-  }
-
-  @Nullable
-  private static LanguageSpecResult getBuildLanguageSpec(
-    Project project,
-    WorkspaceRoot workspace,
-    @Nullable SyncState previousSyncState,
-    BlazeContext parentContext) {
-    LanguageSpecResult oldResult = previousSyncState != null ? previousSyncState.get(LanguageSpecResult.class) : null;
-    if (oldResult != null && !oldResult.shouldRecalculateSpec()) {
-      return oldResult;
-    }
-    LanguageSpecResult result = Scope.push(parentContext, (context) -> {
-      context.push(new TimingScope("BUILD language spec"));
-      BuildLanguageSpec spec = parseLanguageSpec(project, workspace, context);
-      if (spec != null) {
-        return new LanguageSpecResult(spec, System.currentTimeMillis());
-      }
-      return null;
-    });
-    return result != null ? result : oldResult;
-  }
-
-  @Nullable
-  private static BuildLanguageSpec parseLanguageSpec(Project project, WorkspaceRoot workspace, BlazeContext context) {
-    try {
-      // it's wasteful converting to a string and back, but uses existing code, and has a very minor cost
-      // (this is only run once per workspace)
-      ListenableFuture<byte[]> future = BlazeInfo.getInstance()
-        .runBlazeInfoGetBytes(context, Blaze.getBuildSystem(project), workspace, ImmutableList.of(), BlazeInfo.BUILD_LANGUAGE);
-
-      return BuildLanguageSpec.fromProto(Build.BuildLanguage.parseFrom(future.get()));
-
-    } catch (InterruptedException e) {
-      Thread.currentThread().interrupt();
-      return null;
-    } catch (ExecutionException | InvalidProtocolBufferException | NullPointerException e) {
-      if (!ApplicationManager.getApplication().isUnitTestMode()) {
-        LOG.error(e);
-      }
-      return null;
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/sync/LanguageSpecResult.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/sync/LanguageSpecResult.java
deleted file mode 100644
index 1cd3886..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/sync/LanguageSpecResult.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.lang.buildfile.sync;
-
-import com.google.idea.blaze.base.lang.buildfile.language.semantics.BuildLanguageSpec;
-
-import java.io.Serializable;
-
-/**
- * The BUILD language specifications, serialized along with the sync data.
- */
-public class LanguageSpecResult implements Serializable {
-
-  private static final long ONE_DAY_IN_MILLISECONDS = 1000 * 60 * 60;
-
-  public final BuildLanguageSpec spec;
-  public final long timestampMillis;
-
-  public LanguageSpecResult(BuildLanguageSpec spec, long timestampMillis) {
-    this.spec = spec;
-    this.timestampMillis = timestampMillis;
-  }
-
-  public boolean shouldRecalculateSpec() {
-    return System.currentTimeMillis() - timestampMillis > ONE_DAY_IN_MILLISECONDS;
-  }
-}
\ No newline at end of file
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/validation/BuildAnnotator.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/validation/BuildAnnotator.java
deleted file mode 100644
index c1ab33a..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/validation/BuildAnnotator.java
+++ /dev/null
@@ -1,47 +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.validation;
-
-import com.google.idea.blaze.base.lang.buildfile.psi.BuildElementVisitor;
-import com.intellij.lang.annotation.AnnotationHolder;
-import com.intellij.lang.annotation.Annotator;
-import com.intellij.psi.PsiElement;
-
-/**
- * Base class for Annotator implementations using type-specific methods in BuildElementVisitor
- */
-public abstract class BuildAnnotator extends BuildElementVisitor implements Annotator {
-
-  private volatile AnnotationHolder holder;
-
-  protected AnnotationHolder getHolder() {
-    return holder;
-  }
-
-  @Override
-  public synchronized void annotate(PsiElement element, AnnotationHolder holder) {
-    this.holder = holder;
-    try {
-      element.accept(this);
-    } finally {
-      this.holder = null;
-    }
-  }
-
-  protected void markError(PsiElement element, String message) {
-    getHolder().createErrorAnnotation(element, message);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/validation/ErrorAnnotator.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/validation/ErrorAnnotator.java
deleted file mode 100644
index 2ae4808..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/validation/ErrorAnnotator.java
+++ /dev/null
@@ -1,81 +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.validation;
-
-import com.google.idea.blaze.base.lang.buildfile.psi.*;
-import com.google.idea.blaze.base.lang.buildfile.references.LabelReference;
-import com.intellij.psi.PsiElement;
-
-/**
- * Additional error annotations, post parsing.<p>
- * This has been turned off because it's unusable. BuildFile is re-parsed *every* time it's touched, and is never
- * cached. Until this is fixed, we can't run any annotators touching the file.<p>
- *
- * One option: try moving all expensive checks to 'visitFile', so they're not run in parallel
- */
-public class ErrorAnnotator extends BuildAnnotator {
-
-  @Override
-  public void visitLoadStatement(LoadStatement node) {
-    StringLiteral[] strings = node.getChildStrings();
-    if (strings.length == 0) {
-      return;
-    }
-    PsiElement skylarkRef = new LabelReference(strings[0], false).resolve();
-    if (skylarkRef == null) {
-      markError(strings[0], "Cannot find this Skylark module");
-      return;
-    }
-    if (!(skylarkRef instanceof BuildFile)) {
-      markError(strings[0], strings[0].getText() + " is not a Skylark module");
-      return;
-    }
-    if (strings.length == 1) {
-      markError(node, "No definitions imported from Skylark module");
-      return;
-    }
-    BuildFile skylarkModule = (BuildFile) skylarkRef;
-    for (int i = 1; i < strings.length; i++) {
-      String text = strings[i].getStringContents();
-      FunctionStatement fn = skylarkModule.findDeclaredFunction(text);
-      if (fn == null) {
-        markError(strings[i], "Function '" + text + "' not found in Skylark module " + skylarkModule.getFileName());
-      }
-    }
-  }
-
-  @Override
-  public void visitFuncallExpression(FuncallExpression node) {
-    FunctionStatement function = (FunctionStatement) node.getReferencedElement();
-    if (function == null) {
-      // likely a built-in rule. We don't yet recognize these.
-      return;
-    }
-    // check keyword args match function parameters
-    ParameterList params = function.getParameterList();
-    if (params == null || params.hasStarStar()) {
-      return;
-    }
-    for (Argument arg : node.getArguments()) {
-      if (arg instanceof Argument.Keyword) {
-        String name = arg.getName();
-        if (name != null && params.findParameterByName(name) == null) {
-          markError(arg, "No parameter found in '" + node.getFunctionName() + "' with name '" + name + "'");
-        }
-      }
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/validation/GlobErrorAnnotator.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/validation/GlobErrorAnnotator.java
deleted file mode 100644
index f8fa833..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/validation/GlobErrorAnnotator.java
+++ /dev/null
@@ -1,147 +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.validation;
-
-import com.google.idea.blaze.base.lang.buildfile.psi.*;
-import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
-import com.intellij.psi.PsiElement;
-
-import javax.annotation.Nullable;
-
-/**
- * Checks that glob expressions are valid
- */
-public class GlobErrorAnnotator extends BuildAnnotator {
-
-  @Override
-  public void visitGlobExpression(GlobExpression node) {
-    Argument[] args = node.getArguments();
-    boolean hasIncludes = false;
-    for (int i = 0; i < args.length; i++) {
-      Argument arg = args[i];
-      String name = arg instanceof Argument.Keyword ? arg.getName() : null;
-      if ("include".equals(name) || (arg instanceof Argument.Positional && i == 0)) {
-        hasIncludes = checkIncludes(arg.getValue());
-      } else if ("exclude".equals(name)) {
-        checkListContents("exclude", arg.getValue());
-      } else if ("exclude_directories".equals(name)) {
-        checkExcludeDirsNode(arg);
-      } else {
-        markError(arg, "Unrecognized glob argument");
-      }
-    }
-    if (!hasIncludes) {
-      markError(node, "Glob expression must contain at least one included string");
-    }
-  }
-
-  private void checkExcludeDirsNode(Argument arg) {
-    Expression value = arg.getValue();
-    if (value == null || !(value.getText().equals("0") || value.getText().equals("1"))) {
-      markError(arg, "exclude_directories parameter to glob must be 0 or 1");
-    }
-  }
-
-  /**
-   * @return true if glob contains at least one included string
-   */
-  private boolean checkIncludes(@Nullable Expression expr) {
-    return checkListContents("include", expr);
-  }
-
-  /**
-   * @return false if 'expr' is known with certainty not to be a list containing at least one string
-   */
-  private boolean checkListContents(String keyword, @Nullable Expression expr) {
-    if (expr == null) {
-      return false;
-    }
-    PsiElement rootElement = PsiUtils.getReferencedTargetValue(expr);
-    if (rootElement instanceof ListLiteral) {
-      return validatePatternList(keyword, ((ListLiteral) rootElement).getChildExpressions());
-    }
-    if (rootElement instanceof ReferenceExpression || !possiblyValidListLiteral(rootElement)) {
-      markError(expr, "Glob parameter '" + keyword + "' must be a list of strings");
-      return false;
-    }
-    // might possibly be a list, default to not showing any errors
-    return true;
-  }
-
-  /**
-   * @return false if 'expr' is known with certainty not to contain at least one string
-   */
-  private boolean validatePatternList(String keyword, Expression[] expressions) {
-    boolean possiblyHasString = false;
-    for (Expression expr : expressions) {
-      PsiElement rootElement = PsiUtils.getReferencedTargetValue(expr);
-      if (rootElement instanceof ReferenceExpression || !possiblyValidStringLiteral(rootElement)) {
-        markError(expr, "Glob parameter '" + keyword + "' must be a list of strings");
-      } else {
-        possiblyHasString = true;
-        if (rootElement instanceof StringLiteral) {
-          validatePattern((StringLiteral) rootElement);
-        }
-      }
-    }
-    return possiblyHasString;
-  }
-
-  private void validatePattern(StringLiteral pattern) {
-    String error = GlobPatternValidator.validate(pattern.getStringContents());
-    if (error != null) {
-      markError(pattern, error);
-    }
-  }
-
-  /**
-   * Returns false iff we know with certainty that the element cannot resolve to a list literal.
-   */
-  private static boolean possiblyValidListLiteral(PsiElement element) {
-    if (element instanceof ListLiteral || element instanceof GlobExpression) {
-      return true; // these evaluate directly to list literals
-    }
-    if (element instanceof LiteralExpression) {
-      return false; // all other literals cannot evaluate to a ListLiteral
-    }
-    if (element instanceof LoadStatement
-        || element instanceof FunctionStatement) {
-      return false;
-    }
-    // everything else treated as possibly evaluating to a list
-    return true;
-  }
-
-  /**
-   * Returns false iff we know with certainty that the element cannot resolve to a string literal.
-   */
-  private static boolean possiblyValidStringLiteral(PsiElement element) {
-    if (element instanceof StringLiteral ) {
-      return true;
-    }
-    if (element instanceof LiteralExpression) {
-      return false; // all other literals cannot evaluate to a StringLiteral
-    }
-    if (element instanceof LoadStatement
-        || element instanceof FunctionStatement
-        || element instanceof GlobExpression) {
-      return false;
-    }
-    // everything else treated as possibly evaluating to a string
-    return true;
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/validation/GlobPatternValidator.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/validation/GlobPatternValidator.java
deleted file mode 100644
index 46a0752..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/validation/GlobPatternValidator.java
+++ /dev/null
@@ -1,75 +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.validation;
-
-import com.google.common.base.Splitter;
-
-import javax.annotation.Nullable;
-
-/**
- * Support for resolving globs.<p>
- */
-public class GlobPatternValidator {
-
-  /**
-   * Validate a single glob pattern. If it's invalid, returns an error message.
-   * Otherwise, returns null.<p>
-   */
-  @Nullable
-  public static String validate(String pattern) {
-    String error = checkPatternForError(pattern);
-    if (error != null) {
-      return "Invalid glob pattern: " + error;
-    }
-    return null;
-  }
-
-  @Nullable
-  private static String checkPatternForError(String pattern) {
-    if (pattern.isEmpty()) {
-      return "pattern cannot be empty";
-    }
-    if (pattern.charAt(0) == '/') {
-      return "pattern cannot be absolute";
-    }
-    for (int i = 0; i < pattern.length(); i++) {
-      char c = pattern.charAt(i);
-      switch (c) {
-        case '(': case ')':
-        case '{': case '}':
-        case '[': case ']':
-          return "illegal character '" + c + "'";
-      }
-    }
-    Iterable<String> segments = Splitter.on('/').split(pattern);
-    for (String segment : segments) {
-      if (segment.isEmpty()) {
-        return "empty segment not permitted";
-      }
-      if (segment.equals(".") || segment.equals("..")) {
-        return "segment '" + segment + "' not permitted";
-      }
-      if (segment.contains("**") && !segment.equals("**")) {
-        return "recursive wildcard must be its own segment";
-      }
-    }
-    return null;
-  }
-
-
-
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/validation/HighlightingAnnotator.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/validation/HighlightingAnnotator.java
deleted file mode 100644
index 5f5a1bb..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/validation/HighlightingAnnotator.java
+++ /dev/null
@@ -1,60 +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.validation;
-
-import com.google.idea.blaze.base.lang.buildfile.highlighting.BuildSyntaxHighlighter;
-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.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
- */
-public class HighlightingAnnotator extends BuildAnnotator {
-
-  @Override
-  public void visitParameter(Parameter node) {
-    FunctionStatement function = PsiTreeUtil.getParentOfType(node, FunctionStatement.class);
-    if (function != null) {
-      PsiElement anchor = node.hasDefaultValue() ? node.getFirstChild() : node;
-      final Annotation annotation = getHolder().createInfoAnnotation(anchor, null);
-      annotation.setTextAttributes(BuildSyntaxHighlighter.BUILD_PARAMETER);
-    }
-  }
-
-  @Override
-  public void visitKeywordArgument(Argument.Keyword node) {
-    ASTNode keywordNode = node.getNameNode();
-    if (keywordNode != null) {
-      Annotation annotation = getHolder().createInfoAnnotation(keywordNode, null);
-      annotation.setTextAttributes(BuildSyntaxHighlighter.BUILD_KEYWORD_ARG);
-    }
-  }
-
-  @Override
-  public void visitFunctionStatement(FunctionStatement node) {
-    ASTNode nameNode = node.getNameNode();
-    if (nameNode != null) {
-      Annotation annotation = getHolder().createInfoAnnotation(nameNode, null);
-      annotation.setTextAttributes(BuildSyntaxHighlighter.BUILD_FN_DEFINITION);
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/views/BuildStructureViewElement.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/views/BuildStructureViewElement.java
deleted file mode 100644
index 2042ef9..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/views/BuildStructureViewElement.java
+++ /dev/null
@@ -1,64 +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.views;
-
-import com.google.common.collect.ImmutableList;
-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.ListLiteral;
-import com.intellij.ide.structureView.StructureViewTreeElement;
-import com.intellij.ide.structureView.impl.common.PsiTreeElementBase;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.util.Collection;
-
-/**
- * Handles nodes in Structure View.
- */
-public class BuildStructureViewElement extends PsiTreeElementBase<BuildElement> {
-
-  private final BuildElement element;
-
-  public BuildStructureViewElement(BuildElement element) {
-    super(element);
-    this.element = element;
-  }
-
-  @NotNull
-  @Override
-  public Collection<StructureViewTreeElement> getChildrenBase() {
-    if (element instanceof ListLiteral) {
-
-    }
-    if (!(element instanceof BuildFile)) {
-      // TODO: show inner build rules in Skylark .bzl extensions
-      return ImmutableList.of();
-    }
-    ImmutableList.Builder<StructureViewTreeElement> builder = ImmutableList.builder();
-    for (BuildElement child : ((BuildFile) element).findChildrenByClass(BuildElement.class)) {
-      builder.add(new BuildStructureViewElement(child));
-    }
-    return builder.build();
-  }
-
-  @Nullable
-  @Override
-  public String getPresentableText() {
-    return element.getPresentableText();
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/views/BuildStructureViewFactory.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/views/BuildStructureViewFactory.java
deleted file mode 100644
index 3bf0369..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/views/BuildStructureViewFactory.java
+++ /dev/null
@@ -1,45 +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.views;
-
-import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
-import com.intellij.ide.structureView.StructureViewBuilder;
-import com.intellij.ide.structureView.StructureViewModel;
-import com.intellij.ide.structureView.TreeBasedStructureViewBuilder;
-import com.intellij.lang.PsiStructureViewFactory;
-import com.intellij.openapi.editor.Editor;
-import com.intellij.psi.PsiFile;
-
-import javax.annotation.Nullable;
-
-/**
- * PsiStructureViewFactory implementation
- */
-public class BuildStructureViewFactory implements PsiStructureViewFactory {
-  @Override
-  @Nullable
-  public StructureViewBuilder getStructureViewBuilder(final PsiFile psiFile) {
-     if (!(psiFile instanceof BuildFile)) {
-      return null;
-    }
-     return new TreeBasedStructureViewBuilder() {
-      @Override
-      public StructureViewModel createStructureViewModel(@Nullable Editor editor) {
-        return new BuildStructureViewModel((BuildFile) psiFile, editor);
-      }
-    };
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/views/BuildStructureViewModel.java b/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/views/BuildStructureViewModel.java
deleted file mode 100644
index 5ce32dd..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/buildfile/views/BuildStructureViewModel.java
+++ /dev/null
@@ -1,70 +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.views;
-
-import com.google.idea.blaze.base.lang.buildfile.psi.*;
-import com.intellij.ide.structureView.StructureViewModel;
-import com.intellij.ide.structureView.StructureViewModelBase;
-import com.intellij.ide.structureView.StructureViewTreeElement;
-import com.intellij.ide.util.treeView.smartTree.Sorter;
-import com.intellij.openapi.editor.Editor;
-import com.intellij.psi.PsiFile;
-
-import javax.annotation.Nullable;
-
-/**
- * Implements structure view for a BUILD file.
- * TODO: Include inner build rules for Skylark files (when we can identify them -- e.g. via list of blaze rule types)
- */
-public class BuildStructureViewModel extends StructureViewModelBase
-  implements StructureViewModel.ElementInfoProvider, StructureViewModel.ExpandInfoProvider {
-
-  public BuildStructureViewModel(BuildFile psiFile, @Nullable Editor editor) {
-    this(psiFile, editor, new BuildStructureViewElement(psiFile));
-    withSorters(Sorter.ALPHA_SORTER);
-    withSuitableClasses(FunctionStatement.class, LoadStatement.class, FuncallExpression.class);
-  }
-
-  public BuildStructureViewModel(PsiFile file, @Nullable Editor editor, StructureViewTreeElement element) {
-    super(file, editor, element);
-  }
-
-  @Override
-  public boolean isAlwaysShowsPlus(StructureViewTreeElement element) {
-    final Object value = element.getValue();
-    return value instanceof BuildFile;
-  }
-
-  @Override
-  public boolean isAlwaysLeaf(StructureViewTreeElement element) {
-    return element.getValue() instanceof TargetExpression;
-  }
-
-  @Override
-  public boolean shouldEnterElement(Object element) {
-    return element instanceof BuildFile; // only show top-level elements
-  }
-
-  @Override
-  public boolean isAutoExpand(StructureViewTreeElement element) {
-    return element.getValue() instanceof PsiFile;
-  }
-
-  @Override
-  public boolean isSmartExpand() {
-    return false;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/completion/AdditionalLanguagesCompletionContributor.java b/blaze-base/src/com/google/idea/blaze/base/lang/projectview/completion/AdditionalLanguagesCompletionContributor.java
deleted file mode 100644
index 10184a9..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/completion/AdditionalLanguagesCompletionContributor.java
+++ /dev/null
@@ -1,63 +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.projectview.completion;
-
-import com.google.idea.blaze.base.lang.projectview.language.ProjectViewLanguage;
-import com.google.idea.blaze.base.lang.projectview.psi.ProjectViewPsiListSection;
-import com.google.idea.blaze.base.model.primitives.LanguageClass;
-import com.google.idea.blaze.base.projectview.section.sections.AdditionalLanguagesSection;
-import com.intellij.codeInsight.completion.*;
-import com.intellij.codeInsight.lookup.LookupElement;
-import com.intellij.codeInsight.lookup.LookupElementBuilder;
-import com.intellij.patterns.StandardPatterns;
-import com.intellij.util.ProcessingContext;
-
-import static com.intellij.patterns.PlatformPatterns.psiElement;
-
-/**
- * Code completion for additional language types.
- */
-public class AdditionalLanguagesCompletionContributor extends CompletionContributor {
-
-  @Override
-  public AutoCompletionDecision handleAutoCompletionPossibility(AutoCompletionContext context) {
-    // auto-insert the obvious only case; else show other cases.
-    final LookupElement[] items = context.getItems();
-    if (items.length == 1) {
-      return AutoCompletionDecision.insertItem(items[0]);
-    }
-    return AutoCompletionDecision.SHOW_LOOKUP;
-  }
-
-  public AdditionalLanguagesCompletionContributor() {
-    extend(
-      CompletionType.BASIC,
-      psiElement()
-        .withLanguage(ProjectViewLanguage.INSTANCE)
-        .inside(
-          psiElement(ProjectViewPsiListSection.class)
-            .withText(StandardPatterns.string().startsWith(AdditionalLanguagesSection.KEY.getName()))),
-      new CompletionProvider<CompletionParameters>() {
-        @Override
-        protected void addCompletions(CompletionParameters parameters, ProcessingContext context, CompletionResultSet result) {
-          for (LanguageClass type : LanguageClass.values()) {
-            result.addElement(LookupElementBuilder.create(type.getName()));
-          }
-        }
-      }
-    );
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/completion/ProjectViewKeywordCompletionContributor.java b/blaze-base/src/com/google/idea/blaze/base/lang/projectview/completion/ProjectViewKeywordCompletionContributor.java
deleted file mode 100644
index 41d9ad9..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/completion/ProjectViewKeywordCompletionContributor.java
+++ /dev/null
@@ -1,122 +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.projectview.completion;
-
-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;
-import com.google.idea.blaze.base.projectview.section.ListSectionParser;
-import com.google.idea.blaze.base.projectview.section.ScalarSectionParser;
-import com.google.idea.blaze.base.projectview.section.SectionParser;
-import com.google.idea.blaze.base.projectview.section.sections.Sections;
-import com.intellij.codeInsight.completion.*;
-import com.intellij.codeInsight.lookup.LookupElement;
-import com.intellij.codeInsight.lookup.LookupElementBuilder;
-import com.intellij.lang.ASTNode;
-import com.intellij.openapi.editor.Document;
-import com.intellij.openapi.editor.Editor;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiFile;
-import com.intellij.util.ProcessingContext;
-import org.jetbrains.annotations.Nullable;
-
-import java.util.List;
-
-import static com.intellij.patterns.PlatformPatterns.psiElement;
-
-/**
- * Completes project view section names.
- */
-public class ProjectViewKeywordCompletionContributor extends CompletionContributor {
-
-  @Override
-  public AutoCompletionDecision handleAutoCompletionPossibility(AutoCompletionContext context) {
-    // auto-insert the obvious only case; else show other cases.
-    final LookupElement[] items = context.getItems();
-    if (items.length == 1) {
-      return AutoCompletionDecision.insertItem(items[0]);
-    }
-    return AutoCompletionDecision.SHOW_LOOKUP;
-  }
-
-  public ProjectViewKeywordCompletionContributor() {
-    extend(
-      CompletionType.BASIC,
-      psiElement()
-        .withLanguage(ProjectViewLanguage.INSTANCE)
-        .withElementType(ProjectViewTokenType.IDENTIFIERS)
-        .andOr(
-          psiElement().afterLeaf("\n"),
-          psiElement().afterLeaf(psiElement().isNull())
-        ),
-      new CompletionProvider<CompletionParameters>() {
-        @Override
-        protected void addCompletions(CompletionParameters parameters, ProcessingContext context, CompletionResultSet result) {
-          result.addAllElements(keywordLookups);
-        }
-      }
-    );
-  }
-
-  private static final List<LookupElement> keywordLookups = getLookups();
-
-  private static List<LookupElement> getLookups() {
-    ImmutableList.Builder<LookupElement> list = ImmutableList.builder();
-    for (SectionParser parser : Sections.getUndeprecatedParsers()) {
-      list.add(forSectionParser(parser));
-    }
-    return list.build();
-  }
-
-  private static LookupElement forSectionParser(SectionParser parser) {
-    return LookupElementBuilder.create(parser.getName())
-      .withInsertHandler(insertDivider(parser));
-  }
-
-  private static InsertHandler<LookupElement> insertDivider(SectionParser parser) {
-    return (context, item) -> {
-      Editor editor = context.getEditor();
-      Document document = editor.getDocument();
-      context.commitDocument();
-
-      String nextTokenText = findNextTokenText(context);
-      if (nextTokenText == null || nextTokenText == "\n") {
-        document.insertString(context.getTailOffset(), getDivider(parser));
-        editor.getCaretModel().moveToOffset(context.getTailOffset());
-      }
-    };
-  }
-
-  private static String getDivider(SectionParser parser) {
-    if (parser instanceof ListSectionParser) {
-      return ":\n  ";
-    }
-    char div = ((ScalarSectionParser) parser).getDivider();
-    return div == ' ' ? String.valueOf(div) : (div + " ");
-  }
-
-  @Nullable
-  protected static String findNextTokenText(final InsertionContext context) {
-    final PsiFile file = context.getFile();
-    PsiElement element = file.findElementAt(context.getTailOffset());
-    while (element != null && element.getTextLength() == 0) {
-      ASTNode next = element.getNode().getTreeNext();
-      element = next != null ? next.getPsi() : null;
-    }
-    return element != null ? element.getText() : null;
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/completion/WorkspaceTypeCompletionContributor.java b/blaze-base/src/com/google/idea/blaze/base/lang/projectview/completion/WorkspaceTypeCompletionContributor.java
deleted file mode 100644
index ffb2c85..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/completion/WorkspaceTypeCompletionContributor.java
+++ /dev/null
@@ -1,61 +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.projectview.completion;
-
-import com.google.idea.blaze.base.lang.projectview.language.ProjectViewLanguage;
-import com.google.idea.blaze.base.lang.projectview.psi.ProjectViewPsiScalarSection;
-import com.google.idea.blaze.base.model.primitives.WorkspaceType;
-import com.google.idea.blaze.base.projectview.section.sections.WorkspaceTypeSection;
-import com.intellij.codeInsight.completion.*;
-import com.intellij.codeInsight.lookup.LookupElement;
-import com.intellij.codeInsight.lookup.LookupElementBuilder;
-import com.intellij.util.ProcessingContext;
-
-import static com.intellij.patterns.PlatformPatterns.psiElement;
-
-/**
- * Code completion for workspace types.
- */
-public class WorkspaceTypeCompletionContributor extends CompletionContributor {
-
-  @Override
-  public AutoCompletionDecision handleAutoCompletionPossibility(AutoCompletionContext context) {
-    // auto-insert the obvious only case; else show other cases.
-    final LookupElement[] items = context.getItems();
-    if (items.length == 1) {
-      return AutoCompletionDecision.insertItem(items[0]);
-    }
-    return AutoCompletionDecision.SHOW_LOOKUP;
-  }
-
-  public WorkspaceTypeCompletionContributor() {
-    extend(
-      CompletionType.BASIC,
-      psiElement()
-        .withLanguage(ProjectViewLanguage.INSTANCE)
-        .inside(ProjectViewPsiScalarSection.class)
-        .afterLeaf(psiElement().withText(":").afterLeaf(WorkspaceTypeSection.KEY.getName())),
-      new CompletionProvider<CompletionParameters>() {
-        @Override
-        protected void addCompletions(CompletionParameters parameters, ProcessingContext context, CompletionResultSet result) {
-          for (WorkspaceType type : WorkspaceType.values()) {
-            result.addElement(LookupElementBuilder.create(type.getName()));
-          }
-        }
-      }
-    );
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/formatting/ProjectViewCommenter.java b/blaze-base/src/com/google/idea/blaze/base/lang/projectview/formatting/ProjectViewCommenter.java
deleted file mode 100644
index fb00ab6..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/formatting/ProjectViewCommenter.java
+++ /dev/null
@@ -1,94 +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.projectview.formatting;
-
-import com.google.idea.blaze.base.lang.projectview.lexer.ProjectViewTokenType;
-import com.intellij.lang.CodeDocumentationAwareCommenter;
-import com.intellij.psi.PsiComment;
-import com.intellij.psi.tree.IElementType;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * Supports (un)commenting lines via IntelliJ
- */
-public class ProjectViewCommenter implements CodeDocumentationAwareCommenter {
-
-  @Nullable
-  @Override
-  public String getLineCommentPrefix() {
-    return "#";
-  }
-
-  @Nullable
-  @Override
-  public String getBlockCommentPrefix() {
-    return null;
-  }
-
-  @Nullable
-  @Override
-  public String getBlockCommentSuffix() {
-    return null;
-  }
-
-  @Nullable
-  @Override
-  public String getCommentedBlockCommentPrefix() {
-    return null;
-  }
-
-  @Nullable
-  @Override
-  public String getCommentedBlockCommentSuffix() {
-    return null;
-  }
-
-  @Override
-  public IElementType getLineCommentTokenType() {
-    return ProjectViewTokenType.COMMENT;
-  }
-
-  @Override
-  public IElementType getBlockCommentTokenType() {
-    return null;
-  }
-
-  @Override
-  public IElementType getDocumentationCommentTokenType() {
-    return null;
-  }
-
-  @Override
-  public String getDocumentationCommentPrefix() {
-    return null;
-  }
-
-  @Override
-  public String getDocumentationCommentLinePrefix() {
-    return null;
-  }
-
-  @Override
-  public String getDocumentationCommentSuffix() {
-    return null;
-  }
-
-  @Override
-  public boolean isDocumentationComment(PsiComment element) {
-    return false;
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/formatting/ProjectViewEnterHandler.java b/blaze-base/src/com/google/idea/blaze/base/lang/projectview/formatting/ProjectViewEnterHandler.java
deleted file mode 100644
index 6afd0b9..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/formatting/ProjectViewEnterHandler.java
+++ /dev/null
@@ -1,104 +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.projectview.formatting;
-
-import com.google.idea.blaze.base.lang.projectview.lexer.ProjectViewTokenType;
-import com.google.idea.blaze.base.lang.projectview.psi.ProjectViewPsiFile;
-import com.google.idea.blaze.base.projectview.section.SectionParser;
-import com.intellij.codeInsight.editorActions.enter.EnterHandlerDelegateAdapter;
-import com.intellij.ide.DataManager;
-import com.intellij.injected.editor.EditorWindow;
-import com.intellij.lang.ASTNode;
-import com.intellij.lang.injection.InjectedLanguageManager;
-import com.intellij.openapi.actionSystem.DataContext;
-import com.intellij.openapi.editor.Document;
-import com.intellij.openapi.editor.Editor;
-import com.intellij.openapi.editor.LogicalPosition;
-import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
-import com.intellij.openapi.editor.actions.SplitLineAction;
-import com.intellij.openapi.util.Ref;
-import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.psi.PsiDocumentManager;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiFile;
-import com.intellij.psi.PsiWhiteSpace;
-import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
-
-/**
- * Inserts indents as appropriate when enter is pressed.
- */
-public class ProjectViewEnterHandler extends EnterHandlerDelegateAdapter {
-
-  @Override
-  public Result preprocessEnter(PsiFile file,
-                                Editor editor,
-                                Ref<Integer> caretOffset,
-                                Ref<Integer> caretAdvance,
-                                DataContext dataContext,
-                                EditorActionHandler originalHandler) {
-    int offset = caretOffset.get();
-    if (editor instanceof EditorWindow) {
-      file = InjectedLanguageManager.getInstance(file.getProject()).getTopLevelFile(file);
-      editor = InjectedLanguageUtil.getTopLevelEditor(editor);
-      offset = editor.getCaretModel().getOffset();
-    }
-    if (!isApplicable(file, dataContext) || !insertIndent(file, offset)) {
-      return Result.Continue;
-    }
-    int indent = SectionParser.INDENT;
-
-    editor.getCaretModel().moveToOffset(offset);
-    Document doc = editor.getDocument();
-    PsiDocumentManager.getInstance(file.getProject()).commitDocument(doc);
-
-
-    originalHandler.execute(editor, editor.getCaretModel().getCurrentCaret(), dataContext);
-    LogicalPosition position = editor.getCaretModel().getLogicalPosition();
-    if (position.column < indent) {
-      String spaces = StringUtil.repeatSymbol(' ', indent - position.column);
-      doc.insertString(editor.getCaretModel().getOffset(), spaces);
-    }
-    editor.getCaretModel().moveToLogicalPosition(new LogicalPosition(position.line, indent));
-    return Result.Stop;
-  }
-
-  private static boolean isApplicable(PsiFile file, DataContext dataContext) {
-    if (!(file instanceof ProjectViewPsiFile)) {
-      return false;
-    }
-    Boolean isSplitLine = DataManager.getInstance().loadFromDataContext(dataContext, SplitLineAction.SPLIT_LINE_KEY);
-    if (isSplitLine != null) {
-      return false;
-    }
-    return true;
-  }
-
-  private static boolean insertIndent(PsiFile file, int offset) {
-    if (offset == 0) {
-      return false;
-    }
-    PsiElement element = file.findElementAt(offset - 1);
-    while (element != null && element instanceof PsiWhiteSpace) {
-      element = element.getPrevSibling();
-    }
-    if (element == null || element.getText() != ":") {
-      return false;
-    }
-    ASTNode prev = element.getNode().getTreePrev();
-    return prev != null && prev.getElementType() == ProjectViewTokenType.LIST_KEYWORD;
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/highlighting/ProjectViewSyntaxHighlighter.java b/blaze-base/src/com/google/idea/blaze/base/lang/projectview/highlighting/ProjectViewSyntaxHighlighter.java
deleted file mode 100644
index 6653e00..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/highlighting/ProjectViewSyntaxHighlighter.java
+++ /dev/null
@@ -1,52 +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.projectview.highlighting;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.idea.blaze.base.lang.projectview.lexer.ProjectViewLexer;
-import com.google.idea.blaze.base.lang.projectview.lexer.ProjectViewTokenType;
-import com.intellij.lexer.Lexer;
-import com.intellij.openapi.editor.colors.TextAttributesKey;
-import com.intellij.openapi.fileTypes.SyntaxHighlighterBase;
-import com.intellij.psi.tree.IElementType;
-
-import java.util.Map;
-
-import static com.intellij.openapi.editor.DefaultLanguageHighlighterColors.*;
-
-/**
- * This class maps tokens to highlighting attributes. Each attribute contains the font properties.
- */
-public class ProjectViewSyntaxHighlighter extends SyntaxHighlighterBase {
-
-  private static final Map<IElementType, TextAttributesKey> keys = ImmutableMap.of(
-    ProjectViewTokenType.COMMENT, LINE_COMMENT,
-    ProjectViewTokenType.COLON, SEMICOLON,
-    ProjectViewTokenType.IDENTIFIER, IDENTIFIER,
-    ProjectViewTokenType.LIST_KEYWORD, KEYWORD,
-    ProjectViewTokenType.SCALAR_KEYWORD, KEYWORD
-  );
-
-  @Override
-  public Lexer getHighlightingLexer() {
-    return new ProjectViewLexer();
-  }
-
-  @Override
-  public TextAttributesKey[] getTokenHighlights(IElementType iElementType) {
-    return pack(keys.get(iElementType));
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/highlighting/ProjectViewSyntaxHighlighterFactory.java b/blaze-base/src/com/google/idea/blaze/base/lang/projectview/highlighting/ProjectViewSyntaxHighlighterFactory.java
deleted file mode 100644
index b1166f8..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/highlighting/ProjectViewSyntaxHighlighterFactory.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.base.lang.projectview.highlighting;
-
-import com.intellij.openapi.fileTypes.SyntaxHighlighter;
-import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.vfs.VirtualFile;
-
-/**
- * Factory for BuildSyntaxHighlighter
- */
-public class ProjectViewSyntaxHighlighterFactory extends SyntaxHighlighterFactory {
-
-  @Override
-  public SyntaxHighlighter getSyntaxHighlighter(Project project, VirtualFile virtualFile) {
-    return new ProjectViewSyntaxHighlighter();
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/language/ProjectViewFileType.java b/blaze-base/src/com/google/idea/blaze/base/lang/projectview/language/ProjectViewFileType.java
deleted file mode 100644
index d372937..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/language/ProjectViewFileType.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.base.lang.projectview.language;
-
-import com.google.idea.blaze.base.settings.Blaze;
-import com.intellij.openapi.fileTypes.LanguageFileType;
-import icons.BlazeIcons;
-
-import javax.annotation.Nullable;
-import javax.swing.*;
-
-/**
- * Blaze project view file type
- */
-public class ProjectViewFileType extends LanguageFileType {
-
-  public static final ProjectViewFileType INSTANCE = new ProjectViewFileType();
-
-
-  private ProjectViewFileType() {
-    super(ProjectViewLanguage.INSTANCE);
-  }
-
-  @Override
-  public String getName() {
-    // Warning: this is conflated with Language.myID in several places...
-    // They must be identical.
-    return ProjectViewLanguage.INSTANCE.getID();
-  }
-
-  @Override
-  public String getDescription() {
-    return Blaze.defaultBuildSystemName() + " project view files";
-  }
-
-  @Override
-  public String getDefaultExtension() {
-    // Ideally we'd return a build-system specific extension here, but that would require
-    // a hack to guess the current project, or choosing either the blaze or bazel
-    // extension. Instead don't specify a default extension.
-    return "";
-  }
-
-  @Override
-  @Nullable
-  public Icon getIcon() {
-    return BlazeIcons.Blaze;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/language/ProjectViewFileTypeFactory.java b/blaze-base/src/com/google/idea/blaze/base/lang/projectview/language/ProjectViewFileTypeFactory.java
deleted file mode 100644
index 73b2605..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/language/ProjectViewFileTypeFactory.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.lang.projectview.language;
-
-import com.google.idea.blaze.base.projectview.ProjectViewStorageManager;
-import com.intellij.openapi.fileTypes.ExtensionFileNameMatcher;
-import com.intellij.openapi.fileTypes.FileNameMatcher;
-import com.intellij.openapi.fileTypes.FileTypeConsumer;
-import com.intellij.openapi.fileTypes.FileTypeFactory;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Factory for ProjectViewFileType
- */
-public class ProjectViewFileTypeFactory extends FileTypeFactory {
-
-  @Override
-  public void createFileTypes(@NotNull final FileTypeConsumer consumer) {
-    FileNameMatcher[] matchers = ProjectViewStorageManager.VALID_EXTENSIONS.stream()
-      .map(ExtensionFileNameMatcher::new)
-      .toArray(ExtensionFileNameMatcher[]::new);
-    consumer.consume(ProjectViewFileType.INSTANCE, matchers);
-  }
-
-}
\ No newline at end of file
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/language/ProjectViewKeywords.java b/blaze-base/src/com/google/idea/blaze/base/lang/projectview/language/ProjectViewKeywords.java
deleted file mode 100644
index e22a838..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/language/ProjectViewKeywords.java
+++ /dev/null
@@ -1,64 +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.projectview.language;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.idea.blaze.base.projectview.section.ListSectionParser;
-import com.google.idea.blaze.base.projectview.section.ScalarSectionParser;
-import com.google.idea.blaze.base.projectview.section.SectionParser;
-import com.google.idea.blaze.base.projectview.section.SectionParser.ItemType;
-import com.google.idea.blaze.base.projectview.section.sections.Sections;
-
-/**
- * Section parser keywords accepted in project view files.
- */
-public class ProjectViewKeywords {
-
-  public static final ImmutableMap<String, ListSectionParser> LIST_KEYWORD_MAP = getListKeywordMap();
-  public static final ImmutableMap<String, ScalarSectionParser> SCALAR_KEYWORD_MAP = getScalarKeywordMap();
-  public static final ImmutableMap<String, ItemType> ITEM_TYPES = getItemTypes();
-
-  private static ImmutableMap<String, ListSectionParser> getListKeywordMap() {
-    ImmutableMap.Builder<String, ListSectionParser> builder = ImmutableMap.builder();
-    for (SectionParser parser : Sections.getParsers()) {
-      if (parser instanceof ListSectionParser) {
-        builder.put(parser.getName(), (ListSectionParser) parser);
-      }
-    }
-    return builder.build();
-  }
-
-  /**
-   * We get the parser so we have access to both the keyword and the divider char.
-   */
-  private static ImmutableMap<String, ScalarSectionParser> getScalarKeywordMap() {
-    ImmutableMap.Builder<String, ScalarSectionParser> builder = ImmutableMap.builder();
-    for (SectionParser parser : Sections.getParsers()) {
-      if (parser instanceof ScalarSectionParser) {
-        builder.put(parser.getName(), (ScalarSectionParser) parser);
-      }
-    }
-    return builder.build();
-  }
-
-  private static ImmutableMap<String, ItemType> getItemTypes() {
-    ImmutableMap.Builder<String, ItemType> builder = ImmutableMap.builder();
-    for (SectionParser parser : Sections.getParsers()) {
-      builder.put(parser.getName(), parser.getItemType());
-    }
-    return builder.build();
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/language/ProjectViewLanguage.java b/blaze-base/src/com/google/idea/blaze/base/lang/projectview/language/ProjectViewLanguage.java
deleted file mode 100644
index e0ded02..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/language/ProjectViewLanguage.java
+++ /dev/null
@@ -1,42 +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.projectview.language;
-
-import com.google.idea.blaze.base.settings.Blaze;
-import com.intellij.lang.Language;
-
-/**
- * Blaze project file language
- */
-public class ProjectViewLanguage extends Language {
-
-  public static final ProjectViewLanguage INSTANCE = new ProjectViewLanguage();
-
-  private ProjectViewLanguage() {
-    super("projectview");
-  }
-
-  @Override
-  public String getDisplayName() {
-    return Blaze.defaultBuildSystemName() + " project view";
-  }
-
-  @Override
-  public boolean isCaseSensitive() {
-    return true;
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/lexer/ProjectViewLexer.java b/blaze-base/src/com/google/idea/blaze/base/lang/projectview/lexer/ProjectViewLexer.java
deleted file mode 100644
index 783a692..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/lexer/ProjectViewLexer.java
+++ /dev/null
@@ -1,120 +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.projectview.lexer;
-
-import com.google.idea.blaze.base.lang.projectview.lexer.ProjectViewLexerBase.Token;
-import com.intellij.lexer.LexerBase;
-import com.intellij.psi.tree.IElementType;
-
-import java.util.Iterator;
-import java.util.List;
-
-/**
- * Implementation of LexerBase using BuildLexerBase to tokenize the input.
- */
-public class ProjectViewLexer extends LexerBase {
-
-  private int offsetEnd;
-  private int offsetStart;
-  private CharSequence buffer;
-  private Iterator<Token> tokens;
-  private Token currentToken;
-
-  @Override
-  public void start(CharSequence charSequence, int startOffset, int endOffset, int initialState) {
-    buffer = charSequence;
-    this.offsetEnd = endOffset;
-    this.offsetStart = startOffset;
-
-    ProjectViewLexerBase lexer = new ProjectViewLexerBase(charSequence.subSequence(startOffset, endOffset));
-    checkNoCharactersMissing(charSequence.subSequence(startOffset, endOffset).length(), lexer.getTokens());
-    tokens = lexer.getTokens().iterator();
-    currentToken = null;
-    if (tokens.hasNext()) {
-      currentToken = tokens.next();
-    }
-  }
-
-  /**
-   * Temporary debugging code. We need to tokenize every character in the input string.
-   */
-  private static void checkNoCharactersMissing(int totalLength, List<Token> tokens) {
-    if (!tokens.isEmpty() && tokens.get(tokens.size() - 1).right != totalLength) {
-      String error = String.format("Lengths don't match: %s instead of %s",
-                                   tokens.get(tokens.size() - 1).right,
-                                   totalLength);
-      throw new RuntimeException(error);
-    }
-    int start = 0;
-    for (int i = 0; i < tokens.size(); i++) {
-      Token token = tokens.get(i);
-      if (token.left != start) {
-        throw new RuntimeException("Gap/inconsistency at: " + start);
-      }
-      start = token.right;
-    }
-  }
-
-
-  @Override
-  public int getState() {
-    return 0;
-  }
-
-  @Override
-  public IElementType getTokenType() {
-    if (currentToken != null) {
-      return currentToken.type;
-    }
-    return null;
-  }
-
-  @Override
-  public int getTokenStart() {
-    if (currentToken == null) {
-      return 0;
-    }
-    return currentToken.left + offsetStart;
-  }
-
-  @Override
-  public int getTokenEnd() {
-    if (currentToken == null) {
-      return 0;
-    }
-    return currentToken.right + offsetStart;
-  }
-
-  @Override
-  public void advance() {
-    if (tokens.hasNext()) {
-      currentToken = tokens.next();
-    } else {
-      currentToken = null;
-    }
-  }
-
-  @Override
-  public CharSequence getBufferSequence() {
-    return buffer;
-  }
-
-  @Override
-  public int getBufferEnd() {
-    return offsetEnd;
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/lexer/ProjectViewLexerBase.java b/blaze-base/src/com/google/idea/blaze/base/lang/projectview/lexer/ProjectViewLexerBase.java
deleted file mode 100644
index 3a6378b..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/lexer/ProjectViewLexerBase.java
+++ /dev/null
@@ -1,162 +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.projectview.lexer;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.lang.projectview.language.ProjectViewKeywords;
-
-import java.util.List;
-
-/**
- * Lexer for project view files.
- */
-public class ProjectViewLexerBase {
-
-  @VisibleForTesting
-  static class Token {
-    final ProjectViewTokenType type;
-    final int left;
-    final int right;
-
-    private Token(ProjectViewTokenType type, int left, int right) {
-      this.type = type;
-      this.left = left;
-      this.right = right;
-    }
-  }
-
-  private final List<Token> tokens;
-
-  // Input buffer and position
-  private final char[] buffer;
-  private int pos;
-
-  private int identifierStart = -1;
-  private boolean lineHasPrecedingNonWhitespaceChar = false;
-
-  public ProjectViewLexerBase(CharSequence input) {
-    this.buffer = input.toString().toCharArray();
-    this.tokens = Lists.newArrayList();
-    this.pos = 0;
-    tokenize();
-  }
-
-  public List<Token> getTokens() {
-    return tokens;
-  }
-
-  /**
-   * Performs tokenization of the character buffer of file contents provided to
-   * the constructor.
-   */
-  private void tokenize() {
-    while (pos < buffer.length) {
-      char c = buffer[pos];
-      pos++;
-      switch (c) {
-        case '\n':
-          addPrecedingIdentifier(pos - 1);
-          tokens.add(new Token(ProjectViewTokenType.NEWLINE, pos - 1, pos));
-          lineHasPrecedingNonWhitespaceChar = false;
-          break;
-        case ' ':
-        case '\t':
-        case '\r':
-          addPrecedingIdentifier(pos - 1);
-          handleWhitespace();
-          break;
-        case ':':
-          addPrecedingIdentifier(pos - 1);
-          tokens.add(new Token(ProjectViewTokenType.COLON, pos - 1, pos));
-          break;
-        case '#':
-          if (!lineHasPrecedingNonWhitespaceChar) {
-            addPrecedingIdentifier(pos - 1);
-            addCommentLine(pos - 1);
-            break;
-          }
-          // otherwise '#' treated as part of the identifier; intentional fall-through
-        default:
-          lineHasPrecedingNonWhitespaceChar = true;
-          // all other characters combined into an 'identifier' lexical token
-          if (identifierStart == -1) {
-            identifierStart = pos - 1;
-          }
-      }
-    }
-    addPrecedingIdentifier(pos);
-  }
-
-  private void addPrecedingIdentifier(int end) {
-    if (identifierStart != -1) {
-      tokens.add(new Token(getIdentifierToken(identifierStart, end), identifierStart, end));
-      identifierStart = -1;
-    }
-  }
-
-  private void addCommentLine(int start) {
-    while (pos < buffer.length) {
-      char c = buffer[pos];
-      if (c == '\n') {
-        break;
-      }
-      pos++;
-    }
-    tokens.add(new Token(ProjectViewTokenType.COMMENT, start, pos));
-  }
-
-  /**
-   * If the whitespace is followed by an end-of-line comment or a newline, it's combined with those
-   * tokens.
-   */
-  private void handleWhitespace() {
-    int oldPos = pos - 1;
-    while (pos < buffer.length) {
-      char c = buffer[pos];
-      switch (c) {
-        case ' ': case '\t': case '\r':
-          pos++;
-          break;
-        default:
-          if (lineHasPrecedingNonWhitespaceChar || c == '#' || c == '\n') {
-            tokens.add(new Token(ProjectViewTokenType.WHITESPACE, oldPos, pos));
-          } else {
-            tokens.add(new Token(ProjectViewTokenType.INDENT, oldPos, pos));
-          }
-          return;
-      }
-    }
-    tokens.add(new Token(ProjectViewTokenType.WHITESPACE, oldPos, pos));
-  }
-
-  private ProjectViewTokenType getIdentifierToken(int start, int end) {
-    String string = bufferSlice(start, end);
-    if (ProjectViewKeywords.LIST_KEYWORD_MAP.keySet().contains(string)) {
-      return ProjectViewTokenType.LIST_KEYWORD;
-    }
-    if (ProjectViewKeywords.SCALAR_KEYWORD_MAP.keySet().contains(string)) {
-      return ProjectViewTokenType.SCALAR_KEYWORD;
-    }
-    return ProjectViewTokenType.IDENTIFIER;
-  }
-
-
-  private String bufferSlice(int start, int end) {
-    return new String(this.buffer, start, end - start);
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/lexer/ProjectViewTokenType.java b/blaze-base/src/com/google/idea/blaze/base/lang/projectview/lexer/ProjectViewTokenType.java
deleted file mode 100644
index 80f7495..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/lexer/ProjectViewTokenType.java
+++ /dev/null
@@ -1,52 +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.projectview.lexer;
-
-import com.google.idea.blaze.base.lang.projectview.language.ProjectViewLanguage;
-import com.intellij.psi.tree.IElementType;
-import com.intellij.psi.tree.TokenSet;
-
-/**
- * Lexical elements for the project view language.
- */
-public class ProjectViewTokenType extends IElementType {
-
-  // only start-of-line (ignoring whitespace) comments are valid
-  public static final ProjectViewTokenType COMMENT = create("comment");
-  public static final ProjectViewTokenType WHITESPACE = create("whitespace");
-  public static final ProjectViewTokenType NEWLINE = create("newline");
-  public static final ProjectViewTokenType COLON = create(":");
-
-  // any amount of whitespace at the start of a line, followed by a non-'#', non-newline character
-  public static final ProjectViewTokenType INDENT = create("indent");
-
-  // all remaining characters that aren't preceded by a start-of-line comments
-  public static final ProjectViewTokenType IDENTIFIER = create("identifier");
-
-  public static final ProjectViewTokenType LIST_KEYWORD = create("list_keyword");
-  public static final ProjectViewTokenType SCALAR_KEYWORD = create("scalar_keyword");
-
-  private static ProjectViewTokenType create(String debugName) {
-    return new ProjectViewTokenType(debugName);
-  }
-
-  private ProjectViewTokenType(String debugName) {
-    super(debugName, ProjectViewLanguage.INSTANCE);
-  }
-
-  public static final TokenSet IDENTIFIERS = TokenSet.create(IDENTIFIER, LIST_KEYWORD, SCALAR_KEYWORD);
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/parser/ProjectViewParserDefinition.java b/blaze-base/src/com/google/idea/blaze/base/lang/projectview/parser/ProjectViewParserDefinition.java
deleted file mode 100644
index 902f6e3..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/parser/ProjectViewParserDefinition.java
+++ /dev/null
@@ -1,96 +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.projectview.parser;
-
-import com.google.idea.blaze.base.lang.projectview.lexer.ProjectViewLexer;
-import com.google.idea.blaze.base.lang.projectview.lexer.ProjectViewTokenType;
-import com.google.idea.blaze.base.lang.projectview.psi.ProjectViewElementType;
-import com.google.idea.blaze.base.lang.projectview.psi.ProjectViewElementTypes;
-import com.google.idea.blaze.base.lang.projectview.psi.ProjectViewPsiFile;
-import com.intellij.extapi.psi.ASTWrapperPsiElement;
-import com.intellij.lang.ASTNode;
-import com.intellij.lang.ParserDefinition;
-import com.intellij.lang.PsiBuilder;
-import com.intellij.lang.PsiParser;
-import com.intellij.lexer.Lexer;
-import com.intellij.openapi.project.Project;
-import com.intellij.psi.FileViewProvider;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiFile;
-import com.intellij.psi.tree.IElementType;
-import com.intellij.psi.tree.IFileElementType;
-import com.intellij.psi.tree.TokenSet;
-
-/**
- * Defines the project view file parser
- */
-public class ProjectViewParserDefinition implements ParserDefinition {
-
-  @Override
-  public Lexer createLexer(Project project) {
-    return new ProjectViewLexer();
-  }
-
-  @Override
-  public PsiParser createParser(Project project) {
-    return (root, builder) -> {
-      PsiBuilder.Marker rootMarker = builder.mark();
-      new ProjectViewPsiParser(builder).parseFile();
-      rootMarker.done(root);
-      return builder.getTreeBuilt();
-    };
-  }
-
-  @Override
-  public IFileElementType getFileNodeType() {
-    return ProjectViewElementTypes.FILE;
-  }
-
-  @Override
-  public TokenSet getWhitespaceTokens() {
-    return TokenSet.create(ProjectViewTokenType.WHITESPACE);
-  }
-
-  @Override
-  public TokenSet getCommentTokens() {
-    return TokenSet.create(ProjectViewTokenType.COMMENT);
-  }
-
-  @Override
-  public TokenSet getStringLiteralElements() {
-    return TokenSet.EMPTY;
-  }
-
-  @Override
-  public PsiElement createElement(ASTNode node) {
-    IElementType type = node.getElementType();
-    if (type instanceof ProjectViewElementType) {
-      return ((ProjectViewElementType) type).createElement(node);
-    }
-    return new ASTWrapperPsiElement(node);
-  }
-
-  @Override
-  public PsiFile createFile(FileViewProvider viewProvider) {
-    return new ProjectViewPsiFile(viewProvider);
-  }
-
-  @Override
-  public SpaceRequirements spaceExistanceTypeBetweenTokens(ASTNode left, ASTNode right) {
-    return null;
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/parser/ProjectViewPsiParser.java b/blaze-base/src/com/google/idea/blaze/base/lang/projectview/parser/ProjectViewPsiParser.java
deleted file mode 100644
index 2dcc3ea..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/parser/ProjectViewPsiParser.java
+++ /dev/null
@@ -1,213 +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.projectview.parser;
-
-import com.google.idea.blaze.base.lang.projectview.language.ProjectViewKeywords;
-import com.google.idea.blaze.base.lang.projectview.lexer.ProjectViewTokenType;
-import com.google.idea.blaze.base.lang.projectview.psi.ProjectViewElementType;
-import com.google.idea.blaze.base.lang.projectview.psi.ProjectViewElementTypes;
-import com.google.idea.blaze.base.projectview.section.ScalarSectionParser;
-import com.intellij.lang.PsiBuilder;
-
-import javax.annotation.Nullable;
-
-/**
- * Project view psi parser.
- */
-public class ProjectViewPsiParser {
-
-  private final PsiBuilder builder;
-
-  public ProjectViewPsiParser(PsiBuilder builder) {
-    this.builder = builder;
-  }
-
-  public void parseFile() {
-    builder.setDebugMode(true);
-    while (!builder.eof()) {
-      if (matches(ProjectViewTokenType.NEWLINE)) {
-        continue;
-      }
-      parseSection();
-    }
-  }
-
-  /**
-   * A block is one of:
-   * - scalar section
-   * - list section
-   */
-  private void parseSection() {
-    PsiBuilder.Marker marker = builder.mark();
-    if (matches(ProjectViewTokenType.LIST_KEYWORD)) {
-      expect(ProjectViewTokenType.COLON);
-      skipPastNewline();
-      parseListItems();
-      marker.done(ProjectViewElementTypes.LIST_SECTION);
-      return;
-    }
-    if (currentToken() == ProjectViewTokenType.SCALAR_KEYWORD) {
-      ScalarSectionParser parser = ProjectViewKeywords.SCALAR_KEYWORD_MAP.get(builder.getTokenText());
-      if (parser != null) {
-        parseScalarSection(parser);
-        marker.done(ProjectViewElementTypes.SCALAR_SECTION);
-        return;
-      }
-    }
-    // handle each of the error cases
-    if (matches(ProjectViewTokenType.INDENT)) {
-      skipBlockAndError(marker, "Invalid indentation. Indented lines must be preceded by a list keyword");
-      return;
-    }
-    if (matches(ProjectViewTokenType.COLON)) {
-      skipBlockAndError(marker, "Invalid section: lines cannot begin with a colon.");
-      return;
-    }
-    skipBlockAndError(marker, "Unrecognized keyword: " + builder.getTokenText());
-  }
-
-  private void parseListItems() {
-    while (!builder.eof()) {
-      if (matches(ProjectViewTokenType.NEWLINE)) {
-        continue;
-      }
-      if (!matches(ProjectViewTokenType.INDENT)) {
-        return;
-      }
-      PsiBuilder.Marker marker = builder.mark();
-      skipToNewlineToken();
-      marker.done(ProjectViewElementTypes.LIST_ITEM);
-      builder.advanceLexer();
-    }
-  }
-
-  private void parseScalarSection(ScalarSectionParser parser) {
-    boolean whitespaceDivider = builder.rawLookup(1) == ProjectViewTokenType.WHITESPACE;
-    builder.advanceLexer();
-
-    char divider = parser.getDivider();
-    if (divider == ' ') {
-      if (!whitespaceDivider) {
-        builder.error("Whitespace divider expected after '" + parser.getName() + "'");
-        builder.advanceLexer();
-      }
-      parseScalarItem();
-      return;
-    }
-    if (whitespaceDivider || !Character.toString(divider).equals(builder.getTokenText())) {
-      builder.error(String.format("'%s' expected", divider));
-    }
-    if (!whitespaceDivider) {
-      builder.advanceLexer();
-    }
-    parseScalarItem();
-  }
-
-  private void parseScalarItem() {
-    PsiBuilder.Marker marker = builder.mark();
-    skipToNewlineToken();
-    marker.done(ProjectViewElementTypes.SCALAR_ITEM);
-    builder.advanceLexer();
-  }
-
-  /**
-   * Consumes the current token iff it matches the expected type. Otherwise, returns false
-   */
-  private boolean matches(ProjectViewTokenType kind) {
-    if (currentToken() == kind) {
-      builder.advanceLexer();
-      return true;
-    }
-    return false;
-  }
-
-  /**
-   * Consumes the current token if it's of the expected type. Otherwise, returns false and reports an error.
-   */
-  private boolean expect(ProjectViewTokenType kind) {
-    if (matches(kind)) {
-      return true;
-    }
-    builder.error(String.format("'%s' expected", kind));
-    return false;
-  }
-
-  /**
-   * Checks if the upcoming sequence of tokens match that expected. Doesn't advance the parser.
-   */
-  private boolean atTokenSequence(ProjectViewTokenType... kinds) {
-    for (int i = 0; i < kinds.length; i++) {
-      if (builder.lookAhead(i) != kinds[i]) {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  @Nullable
-  private ProjectViewTokenType currentToken() {
-    return (ProjectViewTokenType) builder.getTokenType();
-  }
-
-  private void skipBlockAndError(PsiBuilder.Marker marker, String message) {
-    skipToNextBlock();
-    marker.error(message);
-  }
-
-  /**
-   * Skip to the start of the next unindented line
-   */
-  private void skipToNextBlock() {
-    while (!builder.eof()) {
-      if (atTokenSequence(ProjectViewTokenType.NEWLINE, ProjectViewTokenType.IDENTIFIER)) {
-        builder.advanceLexer();
-        return;
-      }
-      builder.advanceLexer();
-    }
-  }
-
-  /**
-   * Skip to the start of the next line
-   */
-  private void skipPastNewline() {
-    while (!builder.eof()) {
-      if (matches(ProjectViewTokenType.NEWLINE)) {
-        return;
-      }
-      builder.advanceLexer();
-    }
-  }
-
-  /**
-   * Skip to the end of the current line
-   */
-  private void skipToNewlineToken() {
-    while (!builder.eof()) {
-      if (currentToken() == ProjectViewTokenType.NEWLINE) {
-        return;
-      }
-      builder.advanceLexer();
-    }
-  }
-
-  private void buildTokenElement(ProjectViewElementType type) {
-    PsiBuilder.Marker marker = builder.mark();
-    builder.advanceLexer();
-    marker.done(type);
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewElementType.java b/blaze-base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewElementType.java
deleted file mode 100644
index 1c0644f..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewElementType.java
+++ /dev/null
@@ -1,50 +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.projectview.psi;
-
-import com.google.idea.blaze.base.lang.projectview.language.ProjectViewFileType;
-import com.intellij.lang.ASTNode;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.tree.IElementType;
-
-import java.lang.reflect.Constructor;
-
-/**
- * IElementTypes used in the AST by the parser (as opposed to the types used by the lexer).<br>
- * Modelled on IntelliJ's core language conventions.
- */
-public class ProjectViewElementType extends IElementType {
-
-  private static final Class[] PARAMETER_TYPES = new Class[]{ASTNode.class};
-  private final Class<? extends PsiElement> psiElementClass;
-  private Constructor<? extends PsiElement> constructor;
-
-  public ProjectViewElementType(String name, Class<? extends PsiElement> psiElementClass) {
-    super(name, ProjectViewFileType.INSTANCE.getLanguage());
-    this.psiElementClass = psiElementClass;
-  }
-
-  public PsiElement createElement(ASTNode node) {
-    try {
-      if (constructor == null) {
-        constructor = psiElementClass.getConstructor(PARAMETER_TYPES);
-      }
-      return constructor.newInstance(node);
-    } catch (Exception e) {
-      throw new IllegalStateException("No necessary constructor for " + node.getElementType(), e);
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewElementTypes.java b/blaze-base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewElementTypes.java
deleted file mode 100644
index 1dfb3d5..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewElementTypes.java
+++ /dev/null
@@ -1,34 +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.projectview.psi;
-
-import com.google.idea.blaze.base.lang.projectview.language.ProjectViewFileType;
-import com.intellij.psi.tree.IFileElementType;
-
-/**
- * Collects the types used by the PsiBuilder to construct the AST
- */
-public interface ProjectViewElementTypes {
-
-  IFileElementType FILE = new IFileElementType(ProjectViewFileType.INSTANCE.getLanguage());
-
-  ProjectViewElementType LIST_SECTION = new ProjectViewElementType("list_section", ProjectViewPsiListSection.class);
-  ProjectViewElementType SCALAR_SECTION = new ProjectViewElementType("scalar_section", ProjectViewPsiScalarSection.class);
-
-  ProjectViewElementType LIST_ITEM = new ProjectViewElementType("list_item", ProjectViewPsiListItem.class);
-  ProjectViewElementType SCALAR_ITEM = new ProjectViewElementType("scalar_item", ProjectViewPsiScalarItem.class);
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiElement.java b/blaze-base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiElement.java
deleted file mode 100644
index 0bd331b..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiElement.java
+++ /dev/null
@@ -1,39 +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.projectview.psi;
-
-import com.intellij.extapi.psi.ASTWrapperPsiElement;
-import com.intellij.lang.ASTNode;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiReference;
-
-/**
- * Base psi element for project view files.
- */
-public abstract class ProjectViewPsiElement extends ASTWrapperPsiElement {
-  public ProjectViewPsiElement(ASTNode node) {
-    super(node);
-  }
-
-  @Override
-  public PsiReference[] getReferences() {
-    return PsiReference.EMPTY_ARRAY;
-  }
-
-  public <P extends PsiElement> P[] childrenOfClass(Class<P> psiClass) {
-    return findChildrenByClass(psiClass);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiFile.java b/blaze-base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiFile.java
deleted file mode 100644
index 48ff619..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiFile.java
+++ /dev/null
@@ -1,37 +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.projectview.psi;
-
-import com.google.idea.blaze.base.lang.projectview.language.ProjectViewFileType;
-import com.intellij.extapi.psi.PsiFileBase;
-import com.intellij.openapi.fileTypes.FileType;
-import com.intellij.psi.FileViewProvider;
-
-/**
- * PSI file for project view file.
- */
-public class ProjectViewPsiFile extends PsiFileBase {
-
-  public ProjectViewPsiFile(FileViewProvider viewProvider) {
-    super(viewProvider, ProjectViewFileType.INSTANCE.getLanguage());
-  }
-
-  @Override
-  public FileType getFileType() {
-    return ProjectViewFileType.INSTANCE;
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiListItem.java b/blaze-base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiListItem.java
deleted file mode 100644
index d198325..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiListItem.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.lang.projectview.psi;
-
-import com.intellij.lang.ASTNode;
-
-/**
- * Psi element for a list item.
- */
-public class ProjectViewPsiListItem extends ProjectViewPsiSectionItem {
-
-  public ProjectViewPsiListItem(ASTNode node) {
-    super(node);
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiListSection.java b/blaze-base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiListSection.java
deleted file mode 100644
index b942788..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiListSection.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.lang.projectview.psi;
-
-import com.intellij.lang.ASTNode;
-
-/**
- * Psi element for list section.
- */
-public class ProjectViewPsiListSection extends ProjectViewPsiElement {
-
-  public ProjectViewPsiListSection(ASTNode node) {
-    super(node);
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiScalarItem.java b/blaze-base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiScalarItem.java
deleted file mode 100644
index 397dcb9..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiScalarItem.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.lang.projectview.psi;
-
-import com.intellij.lang.ASTNode;
-
-/**
- * Psi element for a scalar item.
- */
-public class ProjectViewPsiScalarItem extends ProjectViewPsiSectionItem {
-
-  public ProjectViewPsiScalarItem(ASTNode node) {
-    super(node);
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiScalarSection.java b/blaze-base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiScalarSection.java
deleted file mode 100644
index 6d82256..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiScalarSection.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.lang.projectview.psi;
-
-import com.intellij.lang.ASTNode;
-
-/**
- * Psi element for scalar section.
- */
-public class ProjectViewPsiScalarSection extends ProjectViewPsiElement {
-
-  public ProjectViewPsiScalarSection(ASTNode node) {
-    super(node);
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiSectionItem.java b/blaze-base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiSectionItem.java
deleted file mode 100644
index d827f10..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/psi/ProjectViewPsiSectionItem.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.lang.projectview.psi;
-
-import com.google.idea.blaze.base.lang.buildfile.references.FileLookupData.PathFormat;
-import com.google.idea.blaze.base.lang.projectview.language.ProjectViewKeywords;
-import com.google.idea.blaze.base.lang.projectview.lexer.ProjectViewTokenType;
-import com.google.idea.blaze.base.lang.projectview.references.ProjectViewLabelReference;
-import com.google.idea.blaze.base.projectview.section.SectionParser.ItemType;
-import com.intellij.lang.ASTNode;
-import com.intellij.psi.PsiReference;
-import com.intellij.psi.impl.SharedPsiElementImplUtil;
-
-import javax.annotation.Nullable;
-
-/**
- * Psi element for a list or scalar item.
- */
-public abstract class ProjectViewPsiSectionItem extends ProjectViewPsiElement {
-
-  public ProjectViewPsiSectionItem(ASTNode node) {
-    super(node);
-  }
-
-  @Override
-  public PsiReference[] getReferences() {
-    return SharedPsiElementImplUtil.getReferences(this);
-  }
-
-  @Override
-  public PsiReference getReference() {
-    ASTNode identifier = getNode().findChildByType(ProjectViewTokenType.IDENTIFIER);
-    PathFormat pathFormat = getLabelType();
-    if (identifier != null && pathFormat != null) {
-      return new ProjectViewLabelReference(this, pathFormat);
-    }
-    return null;
-  }
-
-  @Nullable
-  public PathFormat getLabelType() {
-    ASTNode parent = getNode().getTreeParent();
-    ASTNode identifier = parent != null ? parent.getFirstChildNode() : null;
-    if (identifier == null) {
-      return null;
-    }
-    ItemType itemType = ProjectViewKeywords.ITEM_TYPES.get(identifier.getText());
-    if (itemType == null) {
-      return null;
-    }
-    switch (itemType) {
-      case Label: return PathFormat.NonLocal;
-      case FileSystemItem: return PathFormat.NonLocalWithoutInitialBackslashes;
-      default: return null;
-    }
-  }
-
-}
-
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/psi/util/ProjectViewElementGenerator.java b/blaze-base/src/com/google/idea/blaze/base/lang/projectview/psi/util/ProjectViewElementGenerator.java
deleted file mode 100644
index ccf5996..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/psi/util/ProjectViewElementGenerator.java
+++ /dev/null
@@ -1,66 +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.projectview.psi.util;
-
-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.lang.projectview.psi.ProjectViewPsiElement;
-import com.google.idea.blaze.base.lang.projectview.psi.ProjectViewPsiSectionItem;
-import com.intellij.lang.ASTNode;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.TextRange;
-import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiFile;
-import com.intellij.psi.PsiFileFactory;
-import com.intellij.psi.impl.PsiFileFactoryImpl;
-import com.intellij.testFramework.LightVirtualFile;
-
-import javax.annotation.Nullable;
-
-/**
- * Creates dummy BuildElements, e.g. for renaming purposes.
- */
-public class ProjectViewElementGenerator {
-
-  private static final String DUMMY_FILENAME = "dummy.bazelproject";
-
-  private static PsiFile createDummyFile(Project project, String contents) {
-    PsiFileFactory factory = PsiFileFactory.getInstance(project);
-    LightVirtualFile virtualFile = new LightVirtualFile(DUMMY_FILENAME, ProjectViewFileType.INSTANCE, contents);
-    PsiFile psiFile = ((PsiFileFactoryImpl) factory).trySetupPsiForFile(virtualFile, ProjectViewLanguage.INSTANCE, false, true);
-    assert psiFile != null;
-    return psiFile;
-  }
-
-  @Nullable
-  public static ASTNode createReplacementItemNode(ProjectViewPsiSectionItem sectionItem, String newStringContents) {
-    TextRange itemRange = sectionItem.getTextRange();
-    ProjectViewPsiElement parent = (ProjectViewPsiElement) sectionItem.getParent();
-    if (parent == null) {
-      return sectionItem.getNode();
-    }
-    int startOffset = sectionItem.getStartOffsetInParent();
-    String originalSectionText = parent.getText();
-    String newSectionText = StringUtil.replaceSubstring(originalSectionText,
-                                                        new TextRange(startOffset, startOffset + itemRange.getLength()),
-                                                        newStringContents);
-    PsiFile dummyFile = createDummyFile(sectionItem.getProject(), newSectionText);
-    PsiElement leafElement = dummyFile.findElementAt(startOffset);
-    return leafElement != null ? leafElement.getParent().getNode() : null;
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/references/ProjectViewLabelReference.java b/blaze-base/src/com/google/idea/blaze/base/lang/projectview/references/ProjectViewLabelReference.java
deleted file mode 100644
index 431f481..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/lang/projectview/references/ProjectViewLabelReference.java
+++ /dev/null
@@ -1,136 +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.projectview.references;
-
-import com.google.idea.blaze.base.lang.buildfile.completion.BuildLookupElement;
-import com.google.idea.blaze.base.lang.buildfile.completion.LabelRuleLookupElement;
-import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
-import com.google.idea.blaze.base.lang.buildfile.references.BuildReferenceManager;
-import com.google.idea.blaze.base.lang.buildfile.references.FileLookupData;
-import com.google.idea.blaze.base.lang.buildfile.references.FileLookupData.PathFormat;
-import com.google.idea.blaze.base.lang.buildfile.references.LabelUtils;
-import com.google.idea.blaze.base.lang.buildfile.references.QuoteType;
-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.intellij.lang.ASTNode;
-import com.intellij.openapi.util.TextRange;
-import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiReferenceBase;
-import com.intellij.util.ArrayUtil;
-import com.intellij.util.IncorrectOperationException;
-
-import javax.annotation.Nullable;
-
-/**
- * A blaze label reference.
- */
-public class ProjectViewLabelReference extends PsiReferenceBase<ProjectViewPsiSectionItem> {
-
-  private final PathFormat pathFormat;
-
-  public ProjectViewLabelReference(ProjectViewPsiSectionItem element, PathFormat pathFormat) {
-    super(element, new TextRange(0, element.getTextLength()));
-    this.pathFormat = pathFormat;
-  }
-
-  @Nullable
-  @Override
-  public PsiElement resolve() {
-    Label label = getLabel(myElement.getText());
-    if (label == null) {
-      return null;
-    }
-    return BuildReferenceManager.getInstance(myElement.getProject()).resolveLabel(label);
-  }
-
-  @Nullable
-  private static Label getLabel(@Nullable String labelString) {
-    if (labelString == null || !labelString.startsWith("//") || labelString.indexOf('*') != -1) {
-      return null;
-    }
-    return LabelUtils.createLabelFromString(null, labelString);
-  }
-
-  @Override
-  public Object[] getVariants() {
-    String labelString = LabelUtils.trimToDummyIdentifier(myElement.getText());
-    return ArrayUtil.mergeArrays(
-      getRuleLookups(labelString),
-      getFileLookups(labelString));
-  }
-
-  private BuildLookupElement[] getRuleLookups(String labelString) {
-    if (!labelString.startsWith("//") || !labelString.contains(":")) {
-      return BuildLookupElement.EMPTY_ARRAY;
-    }
-    String packagePrefix = LabelUtils.getPackagePathComponent(labelString);
-    BuildFile referencedBuildFile =  BuildReferenceManager.getInstance(myElement.getProject()).resolveBlazePackage(packagePrefix);
-    if (referencedBuildFile == null) {
-      return BuildLookupElement.EMPTY_ARRAY;
-    }
-    return LabelRuleLookupElement.collectAllRules(referencedBuildFile, labelString, packagePrefix, null, QuoteType.NoQuotes);
-  }
-
-  private BuildLookupElement[] getFileLookups(String labelString) {
-    if (pathFormat == PathFormat.NonLocalWithoutInitialBackslashes) {
-      labelString = StringUtil.trimStart(labelString, "-");
-    }
-    FileLookupData lookupData = FileLookupData.nonLocalFileLookup(labelString, null, QuoteType.NoQuotes, pathFormat);
-    if (lookupData == null) {
-      return BuildLookupElement.EMPTY_ARRAY;
-    }
-    return BuildReferenceManager.getInstance(myElement.getProject()).resolvePackageLookupElements(lookupData);
-  }
-
-  @Override
-  public PsiElement bindToElement(PsiElement element) throws IncorrectOperationException {
-    return myElement;
-  }
-
-  @Override
-  public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException {
-    String currentString = myElement.getText();
-    Label label = getLabel(currentString);
-    if (label == null) {
-      return myElement;
-    }
-    String ruleName = label.ruleName().toString();
-    String newRuleName = newElementName;
-
-    // handle subdirectories
-    int lastSlashIndex = ruleName.lastIndexOf('/');
-    if (lastSlashIndex != -1) {
-      newRuleName = ruleName.substring(0, lastSlashIndex + 1) + newElementName;
-    }
-
-    String packageString = LabelUtils.getPackagePathComponent(currentString);
-    if (packageString.isEmpty() && !currentString.contains(":")) {
-      return handleRename(newRuleName);
-    }
-    return handleRename(packageString + ":" + newRuleName);
-  }
-
-  private PsiElement handleRename(String newStringContents) {
-    ASTNode replacement = ProjectViewElementGenerator.createReplacementItemNode(myElement, newStringContents);
-    if (replacement != null) {
-      myElement.getNode().replaceAllChildrenToChildrenOf(replacement);
-    }
-    return myElement;
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/metrics/Action.java b/blaze-base/src/com/google/idea/blaze/base/metrics/Action.java
deleted file mode 100644
index 7faa893..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/metrics/Action.java
+++ /dev/null
@@ -1,85 +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.metrics;
-
-import org.jetbrains.annotations.NonNls;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * An item that can be logged. All actions contain a name that is used as a primary key. The name
- * should be immutable forever to keep the logs sane.
- * <p/>
- * The name used by each {@link Action} should be [a-zA-Z0-9]* to keep things robust since various
- * log back ends may have different rules about what may or may not be in a key.
- * <p/>
- * Do not use any of the following retired values for enums:
- * INDEX_TOTAL_TIME("index")
- * REBUILD_TOTAL_TIME("rtt")
- * SYNC_SAVE_FILES("ssf")
- * SYNC_COMPUTE_MODULE_DIFF("scmd")
- * RUN_TOTAL_TIME("ttrp")
- * DEBUG_TOTAL_TIME("ttsbp")
- * RUN_TOTAL_TIME_FOR_ANDROID_TEST("ttrpat")
- * DEBUG_TOTAL_TIME_FOR_ANDROID_TEST("ttsbpat")
- * IMPORT_TOTAL_TIME("tip")
- * IDE_BUILD_INFO_RESPONSE("ibi")
- * RULES_EXTRACTION("re")
- * BLAZE_MODULES_CREATION("mvc")
- * INTELLIJ_MODULE_CREATION("imc")
- * SYNC_RESET_PROJECT("srp")
- * <p/>
- */
-public enum Action {
-
-  MAKE_PROJECT_TOTAL_TIME("mtt"),
-  MAKE_MODULE_TOTAL_TIME("mmtt"),
-
-  SYNC_TOTAL_TIME("stt"),
-  SYNC_IMPORT_DATA_TIME("sidt"),
-  BLAZE_BUILD_DURING_SYNC("bb"),
-  BLAZE_BUILD("bld"),
-
-  APK_BUILD_AND_INSTALL("apkbi"),
-
-  BLAZE_COMMAND_USAGE("ttrpbc"),
-
-  OPEN_IN_CODESEARCH("oics"),
-  COPY_GOOGLE3_PATH("cg3p"),
-  OPEN_CORRESPONDING_BUILD_FILE("ocbf"),
-
-  CREATE_BLAZE_RULE("cbr"),
-  CREATE_BLAZE_PACKAGE("cbp"),
-
-  SYNC_SDK("ssdk"),
-
-  C_RESOLVE_FILE("crf"),
-  BLAZE_CLION_TEST_RUN("ctr"),
-  BLAZE_CLION_TEST_DEBUG("ctd")
-  ;
-
-  @NotNull
-  @NonNls
-  final private String name;
-
-  Action(@NotNull String name) {
-    this.name = name;
-  }
-
-  @NotNull
-  public String getName() {
-    return name;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/metrics/LoggingService.java b/blaze-base/src/com/google/idea/blaze/base/metrics/LoggingService.java
deleted file mode 100644
index 01ecc7a..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/metrics/LoggingService.java
+++ /dev/null
@@ -1,63 +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.metrics;
-
-import com.intellij.openapi.extensions.ExtensionPointName;
-import com.intellij.openapi.project.Project;
-
-import javax.annotation.Nullable;
-
-/**
- * Logging service that handles logging timing, hit, and other events to an external sink for
- * later analysis.
- */
-public interface LoggingService {
-
-  ExtensionPointName<LoggingService> EP_NAME = ExtensionPointName.create("com.google.idea.blaze.LoggingService");
-
-  /**
-   * Report a value for an event to the available logging services.
-   *  @param variable The variable to report to. Once a value is selected for a logical
-   *                 measurement, the variable's name should never change, even if the colloquial name for the
-   *                 variable changes.
-   */
-  static void reportEvent(Project project, Action variable) {
-    reportEvent(project, variable, 0);
-  }
-
-  /**
-   * Report a value for an event to the available logging services.
-   *  @param variable The variable to report to. Once a value is selected for a logical
-   *                 measurement, the variable's name should never change, even if the colloquial name for the
-   *                 variable changes.
-   * @param value    should be >= 0, set the value to 0 if the value is meaningless
-   */
-  static void reportEvent(Project project, Action variable, long value) {
-    for (LoggingService service : EP_NAME.getExtensions()) {
-      service.doReportEvent(project, variable, value);
-    }
-  }
-
-  /**
-   * Report a value for an event to the logging service
-   *  @param variable The variable to report to. Once a value is selected for a logical
-   *                 measurement, the variable's name should never change, even if the colloquial name for the
-   *                 variable changes.
-   * @param value    should be >= 0, set the value to 0 if the value is meaningless
-   */
-  void doReportEvent(@Nullable Project project, Action variable, long value);
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/model/BlazeProjectData.java b/blaze-base/src/com/google/idea/blaze/base/model/BlazeProjectData.java
deleted file mode 100644
index 4b4e1a9..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/model/BlazeProjectData.java
+++ /dev/null
@@ -1,67 +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;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableMultimap;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.model.primitives.Label;
-import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
-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 javax.annotation.Nullable;
-import javax.annotation.concurrent.Immutable;
-import java.io.Serializable;
-
-/**
- * The top-level object serialized to cache.
- */
-@Immutable
-public class BlazeProjectData implements Serializable {
-  private static final long serialVersionUID = 18L;
-
-  public final long syncTime;
-  public final ImmutableMap<Label, RuleIdeInfo> ruleMap;
-  public final BlazeRoots blazeRoots;
-  @Nullable
-  public final WorkingSet workingSet;
-  public final WorkspacePathResolver workspacePathResolver;
-  public final WorkspaceLanguageSettings workspaceLanguageSettings;
-  public final SyncState syncState;
-  public final ImmutableMultimap<Label, Label> reverseDependencies;
-
-  public BlazeProjectData(
-    long syncTime,
-    ImmutableMap<Label, RuleIdeInfo> ruleMap,
-    BlazeRoots blazeRoots,
-    @Nullable WorkingSet workingSet,
-    WorkspacePathResolver workspacePathResolver,
-    WorkspaceLanguageSettings workspaceLangaugeSettings,
-    SyncState syncState,
-    ImmutableMultimap<Label, Label> reverseDependencies
-  ) {
-    this.syncTime = syncTime;
-    this.ruleMap = ruleMap;
-    this.blazeRoots = blazeRoots;
-    this.workingSet = workingSet;
-    this.workspacePathResolver = workspacePathResolver;
-    this.workspaceLanguageSettings = workspaceLangaugeSettings;
-    this.syncState = syncState;
-    this.reverseDependencies = reverseDependencies;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/model/BlazeWorkspaceType.java b/blaze-base/src/com/google/idea/blaze/base/model/BlazeWorkspaceType.java
deleted file mode 100644
index 46380bf..0000000
--- a/blaze-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;
-
-public enum BlazeWorkspaceType {
-  BASE,
-  JAVA,
-  ANDROID
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/model/SyncState.java b/blaze-base/src/com/google/idea/blaze/base/model/SyncState.java
deleted file mode 100644
index d91ac1f..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/model/SyncState.java
+++ /dev/null
@@ -1,55 +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;
-
-import com.google.common.collect.ImmutableMap;
-
-import javax.annotation.Nullable;
-import java.io.Serializable;
-import java.util.Map;
-
-/**
- * Used to save arbitrary state with the sync task.
- */
-public class SyncState implements Serializable {
-  private static final long serialVersionUID = 1L;
-  private final ImmutableMap<String, Serializable> syncStateMap;
-
-  @SuppressWarnings("unchecked")
-  @Nullable
-  public <T extends Serializable> T get(Class<T> klass) {
-    return (T) syncStateMap.get(klass.getName());
-  }
-
-  public static class Builder {
-    ImmutableMap.Builder<Class, Serializable> syncStateMap = ImmutableMap.builder();
-    public <K extends Serializable, V extends K> Builder put(Class<K> klass, V instance) {
-      syncStateMap.put(klass, instance);
-      return this;
-    }
-    public SyncState build() {
-      return new SyncState(syncStateMap.build());
-    }
-  }
-
-  SyncState(ImmutableMap<Class, Serializable> syncStateMap) {
-    ImmutableMap.Builder<String, Serializable> extraProjectSyncStateMap = ImmutableMap.builder();
-    for (Map.Entry<Class, Serializable> entry : syncStateMap.entrySet()) {
-      extraProjectSyncStateMap.put(entry.getKey().getName(), entry.getValue());
-    }
-    this.syncStateMap = extraProjectSyncStateMap.build();
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/model/primitives/ExecutionRootPath.java b/blaze-base/src/com/google/idea/blaze/base/model/primitives/ExecutionRootPath.java
deleted file mode 100644
index 1ae5775..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/model/primitives/ExecutionRootPath.java
+++ /dev/null
@@ -1,131 +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 com.google.common.base.Objects;
-import com.intellij.openapi.util.io.FileUtil;
-
-import javax.annotation.Nullable;
-import java.io.File;
-import java.io.Serializable;
-
-/**
- * An absolute or relative path returned from Blaze. If it is a relative path, it is relative to the execution root.
- */
-public final class ExecutionRootPath implements Serializable {
-  public static final long serialVersionUID = 3L;
-
-  private final File path;
-
-  public ExecutionRootPath(String path) {
-    this.path = new File(path);
-  }
-
-  public ExecutionRootPath(File path) {
-    this.path = path;
-  }
-
-  public File getAbsoluteOrRelativeFile() {
-    return path;
-  }
-
-  public File getFileRootedAt(File absoluteRoot) {
-    if (path.isAbsolute()) {
-      return path;
-    }
-    return new File(absoluteRoot, path.getPath());
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) {
-      return true;
-    }
-    if (o == null || getClass() != o.getClass()) {
-      return false;
-    }
-    ExecutionRootPath that = (ExecutionRootPath)o;
-    return Objects.equal(path, that.path);
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hashCode(path);
-  }
-
-  @Override
-  public String toString() {
-    return "ExecutionRootPath{" +
-           "path='" + path + '\'' +
-           '}';
-  }
-
-  /**
-   * Returns the relative {@link ExecutionRootPath} if {@code root} is an ancestor of {@code path} otherwise returns null.
-   */
-  @Nullable
-  public static ExecutionRootPath createAncestorRelativePath(File root, File path) {
-    // We cannot find the relative path between an absolute and relative path. The underlying code will make the relative path absolute
-    // by rooting it at the current working directory which is almost never what you want.
-    if (root.isAbsolute() != path.isAbsolute()) {
-      return null;
-    }
-    if (!isAncestor(root.getPath(), path.getPath(), false /* strict */)) {
-      return null;
-    }
-    String relativePath = FileUtil.getRelativePath(root, path);
-    if (relativePath == null) {
-      return null;
-    }
-    return new ExecutionRootPath(new File(relativePath));
-  }
-
-  /**
-   * @param possibleParent
-   * @param possibleChild
-   * @param strict if {@code false} then this method returns {@code true} if {@code possibleParent} equals to {@code possibleChild}.
-   */
-  public static boolean isAncestor(ExecutionRootPath possibleParent, ExecutionRootPath possibleChild, boolean strict) {
-    return isAncestor(possibleParent.getAbsoluteOrRelativeFile().getPath(), possibleChild.getAbsoluteOrRelativeFile().getPath(), strict);
-  }
-
-  /**
-   * @param possibleParentPath
-   * @param possibleChild
-   * @param strict if {@code false} then this method returns {@code true} if {@code possibleParent} equals to {@code possibleChild}.
-   */
-  public static boolean isAncestor(String possibleParentPath, ExecutionRootPath possibleChild, boolean strict) {
-    return isAncestor(possibleParentPath, possibleChild.getAbsoluteOrRelativeFile().getPath(), strict);
-  }
-
-  /**
-   * @param possibleParent
-   * @param possibleChildPath
-   * @param strict if {@code false} then this method returns {@code true} if {@code possibleParent} equals to {@code possibleChild}.
-   */
-  public static boolean isAncestor(ExecutionRootPath possibleParent, String possibleChildPath, boolean strict) {
-    return isAncestor(possibleParent.getAbsoluteOrRelativeFile().getPath(), possibleChildPath, strict);
-  }
-
-  /**
-   * @param possibleParentPath
-   * @param possibleChildPath
-   * @param strict if {@code false} then this method returns {@code true} if {@code possibleParent} equals to {@code possibleChild}.
-   */
-  public static boolean isAncestor(String possibleParentPath, String possibleChildPath, boolean strict) {
-    return FileUtil.isAncestor(possibleParentPath, possibleChildPath, strict);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/model/primitives/Kind.java b/blaze-base/src/com/google/idea/blaze/base/model/primitives/Kind.java
deleted file mode 100644
index 2272a1f..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/model/primitives/Kind.java
+++ /dev/null
@@ -1,95 +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 com.google.common.collect.ImmutableMap;
-
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Wrapper around a string for a blaze kind (android_library, android_test...)
- */
-public enum Kind {
-
-  ANDROID_BINARY("android_binary", LanguageClass.ANDROID),
-  ANDROID_LIBRARY("android_library", LanguageClass.ANDROID),
-  ANDROID_TEST("android_test", LanguageClass.ANDROID),
-  ANDROID_ROBOLECTRIC_TEST("android_robolectric_test", LanguageClass.ANDROID),
-  JAVA_LIBRARY("java_library", LanguageClass.JAVA),
-  JAVA_TEST("java_test", LanguageClass.JAVA),
-  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
-  JAVA_PLUGIN("java_plugin", LanguageClass.JAVA),
-  ANDROID_RESOURCES("android_resources", LanguageClass.ANDROID),
-  CC_LIBRARY("cc_library", LanguageClass.C),
-  CC_BINARY("cc_binary", LanguageClass.C),
-  CC_TEST("cc_test", LanguageClass.C),
-  CC_INC_LIBRARY("cc_inc_library", LanguageClass.C),
-  CC_TOOLCHAIN("cc_toolchain", LanguageClass.C),
-  JAVA_WRAP_CC("java_wrap_cc", LanguageClass.JAVA),
-  GWT_APPLICATION("gwt_application", LanguageClass.JAVA),
-  GWT_HOST("gwt_host", LanguageClass.JAVA),
-  GWT_MODULE("gwt_module", LanguageClass.JAVA),
-  GWT_TEST("gwt_test", LanguageClass.JAVA),
-  ;
-
-  static final ImmutableMap<String, Kind> STRING_TO_KIND = makeStringToKindMap();
-
-  private static ImmutableMap<String, Kind> makeStringToKindMap() {
-    ImmutableMap.Builder<String, Kind> result = ImmutableMap.builder();
-    for (Kind kind : Kind.values()) {
-      result.put(kind.toString(), kind);
-    }
-    return result.build();
-  }
-
-  public static Kind fromString(String kindString) {
-    return STRING_TO_KIND.get(kindString);
-  }
-
-  private final String kind;
-  private final LanguageClass languageClass;
-
-  Kind(String kind, LanguageClass languageClass) {
-    this.kind = kind;
-    this.languageClass = languageClass;
-  }
-
-  @Override
-  public String toString() {
-    return kind;
-  }
-
-  public LanguageClass getLanguageClass() {
-    return languageClass;
-  }
-
-  public boolean isOneOf(Kind... kinds) {
-    return isOneOf(Arrays.asList(kinds));
-  }
-
-  public boolean isOneOf(List<Kind> kinds) {
-    for (Kind kind : kinds) {
-      if (this.equals(kind)) {
-        return true;
-      }
-    }
-    return false;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/model/primitives/Label.java b/blaze-base/src/com/google/idea/blaze/base/model/primitives/Label.java
deleted file mode 100644
index c4bf28a..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/model/primitives/Label.java
+++ /dev/null
@@ -1,153 +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 com.google.common.collect.Lists;
-import com.google.idea.blaze.base.ui.BlazeValidationError;
-import com.intellij.openapi.diagnostic.Logger;
-
-import javax.annotation.Nullable;
-import javax.annotation.concurrent.Immutable;
-import java.util.Collection;
-import java.util.Comparator;
-import java.util.List;
-
-/**
- * Wrapper around a string for a blaze label (//package:rule).
- */
-@Immutable
-public final class Label extends TargetExpression {
-  private static final Logger LOG = Logger.getInstance(Label.class);
-
-  public static final Comparator<Label> COMPARATOR = (o1, o2) -> String.CASE_INSENSITIVE_ORDER.compare(o1.toString(), o2.toString());
-
-  public static final long serialVersionUID = 2L;
-
-  /**
-   * Silently returns null if this is not a valid Label
-   */
-  @Nullable
-  public static Label createIfValid(String label) {
-    if (validate(label)) {
-      return new Label(label);
-    }
-    return null;
-  }
-
-  public Label(String label) {
-    super(label);
-    List<BlazeValidationError> errors = Lists.newArrayList();
-    if (!validate(label, errors)) {
-      BlazeValidationError.throwError(errors);
-    }
-  }
-
-  public Label(
-    WorkspacePath packageName,
-    RuleName newRuleName) {
-    this("//" + packageName.toString() + ":" + newRuleName.toString());
-  }
-
-  public static boolean validate(String label) {
-    return validate(label, null);
-  }
-
-  public static boolean validate(String label, @Nullable Collection<BlazeValidationError> errors) {
-    int colonIndex = label.indexOf(':');
-    if (label.startsWith("//") && colonIndex >= 0) {
-      String packageName = label.substring("//".length(), colonIndex);
-      if (!validatePackagePath(packageName, errors)) {
-        return false;
-      }
-      String ruleName = label.substring(colonIndex + 1);
-      if (!RuleName.validate(ruleName, errors)) {
-        return false;
-      }
-      return true;
-    }
-    if (label.startsWith("@") && colonIndex >= 0) {
-      // a bazel-specific label pointing to a different repository
-      int slashIndex = label.indexOf("//");
-      if (slashIndex >= 0) {
-        return validate(label.substring(slashIndex), errors);
-      }
-    }
-    if (errors != null) {
-      errors.add(new BlazeValidationError("Not a valid label, no target name found: " + label));
-    }
-    return false;
-  }
-
-  /**
-   * Extract the rule name from a label. The rule name follows a colon at the end of the label.
-   *
-   * @return the rule name
-   */
-  public RuleName ruleName() {
-    String labelStr = toString();
-    int colonLocation = labelStr.lastIndexOf(':');
-    int ruleNameStart = colonLocation + 1;
-    String ruleNameStr = labelStr.substring(ruleNameStart);
-    return RuleName.create(ruleNameStr);
-  }
-
-  /**
-   * Return the workspace path for the package label for the given label. For example, if the
-   * package is //j/c/g/a/apps/docs:release, it returns j/c/g/a/apps/docs.
-   */
-  public WorkspacePath blazePackage() {
-    String labelStr = toString();
-    int startIndex = labelStr.indexOf("//") + "//".length();
-    int colonIndex = labelStr.lastIndexOf(':');
-    LOG.assertTrue(colonIndex >= 0);
-    return new WorkspacePath(labelStr.substring(startIndex, colonIndex));
-  }
-
-  public static boolean validatePackagePath(String path) {
-    return validatePackagePath(path, null);
-  }
-
-  public static boolean validatePackagePath(String path, @Nullable Collection<BlazeValidationError> errors) {
-    // Empty packages are legal but not recommended
-    if (path.isEmpty()) {
-      return true;
-    }
-
-    if (path.charAt(0) < 'a' || path.charAt(0) > 'z') {
-      BlazeValidationError.collect(errors, new BlazeValidationError(
-        "Invalid package name: " + path + "\n "
-        + "Package names must start with a lowercase ASCII letter"
-      ));
-      return false;
-    }
-    if (path.contains("//")) {
-      BlazeValidationError.collect(errors, new BlazeValidationError(
-        "Invalid package name: " + path + "\n "
-        + "package names may not contain \"//\" path separators."
-      ));
-      return false;
-    }
-    if (path.endsWith("/")) {
-      BlazeValidationError.collect(errors, new BlazeValidationError(
-        "Invalid package name: " + path + "\n "
-        + "package names may not end with \"/\""
-      ));
-      return false;
-    }
-    return true;
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/model/primitives/LanguageClass.java b/blaze-base/src/com/google/idea/blaze/base/model/primitives/LanguageClass.java
deleted file mode 100644
index 86cedf7..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/model/primitives/LanguageClass.java
+++ /dev/null
@@ -1,47 +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;
-
-/**
- * Language classes.
- */
-public enum LanguageClass {
-  GENERIC("generic"),
-  C("c"),
-  JAVA("java"),
-  ANDROID("android"),
-  JAVASCRIPT("javascript"),
-  TYPESCRIPT("typescript"),
-  DART("dart");
-
-  private final String name;
-  LanguageClass(String name) {
-    this.name = name;
-  }
-
-  public String getName() {
-    return name;
-  }
-
-  public static LanguageClass fromString(String name) {
-    for (LanguageClass ruleClass : LanguageClass.values()) {
-      if (ruleClass.name.equals(name)) {
-        return ruleClass;
-      }
-    }
-    return null;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/model/primitives/RuleName.java b/blaze-base/src/com/google/idea/blaze/base/model/primitives/RuleName.java
deleted file mode 100644
index aed7e88..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/model/primitives/RuleName.java
+++ /dev/null
@@ -1,188 +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 com.google.common.base.Objects;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.ui.BlazeValidationError;
-
-import javax.annotation.Nullable;
-import java.util.Collection;
-import java.util.List;
-import java.util.regex.Pattern;
-
-public final class RuleName {
-
-  // This is a subset of the allowable target names in Blaze
-  private static final String ALNUM_REGEX_STR = "[a-zA-Z0-9]*";
-  private static final Pattern ALNUM_REGEX = Pattern.compile(ALNUM_REGEX_STR);
-
-  // Rule names must be alpha-numeric or consist of the following allowed chars:
-  // (note, rule names can also contain '/'; we handle that case separately)
-  private static final ImmutableSet<Character> ALLOWED_META = ImmutableSet.of('+', '_', ',', '=', '-', '.', '@', '~');
-
-  private final String name;
-
-  private RuleName(String ruleName) {
-    this.name = ruleName;
-  }
-
-  /**
-   * Silently returns null if the string is not a valid rule name.
-   */
-  @Nullable
-  public static RuleName createIfValid(String ruleName) {
-    if (validate(ruleName, null)) {
-      return new RuleName(ruleName);
-    }
-    return null;
-  }
-
-  public static RuleName create(String ruleName) {
-    List<BlazeValidationError> errors = Lists.newArrayList();
-    if (!validate(ruleName, errors)) {
-      BlazeValidationError.throwError(errors);
-    }
-    return new RuleName(ruleName);
-  }
-
-  /**
-   * Validates a rule name using the same logic as Blaze
-   */
-  public static boolean validate(String ruleName) {
-    return validate(ruleName, null);
-  }
-
-  /**
-   * Validates a rule name using the same logic as Blaze
-   */
-  public static boolean validate(String ruleName, @Nullable Collection<BlazeValidationError> errors) {
-    if (ruleName.isEmpty()) {
-      BlazeValidationError.collect(errors, new BlazeValidationError("target names cannot be empty"));
-      return false;
-    }
-    // Forbidden start chars:
-    if (ruleName.charAt(0) == '/') {
-      BlazeValidationError.collect(errors, new BlazeValidationError(
-        "Invalid target name: " + ruleName + "\n" +
-        "target names may not start with \"/\""
-      ));
-      return false;
-    }
-    else if (ruleName.charAt(0) == '.') {
-      if (ruleName.startsWith("../") || ruleName.equals("..")) {
-        BlazeValidationError.collect(errors, new BlazeValidationError(
-          "Invalid target name: " + ruleName + "\n" +
-          "target names may not contain up-level references \"..\""
-        ));
-        return false;
-      }
-      else if (ruleName.equals(".")) {
-        return true;
-      }
-      else if (ruleName.startsWith("./")) {
-        BlazeValidationError.collect(errors, new BlazeValidationError(
-          "Invalid target name: " + ruleName + "\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);
-      if (ALLOWED_META.contains(c)) {
-        continue;
-      }
-      if (c == '/') {
-        // Forbidden substrings: "/../", "/./", "//"
-        if (ruleName.contains("/../")) {
-          BlazeValidationError.collect(errors, new BlazeValidationError(
-            "Invalid target name: " + ruleName + "\n" +
-            "target names may not contain up-level references \"..\""
-          ));
-          return false;
-        }
-        else if (ruleName.contains("/./")) {
-          BlazeValidationError.collect(errors, new BlazeValidationError(
-            "Invalid target name: " + ruleName + "\n" +
-            "target names may not contain \".\" as a path segment"
-          ));
-          return false;
-        }
-        else if (ruleName.contains("//")) {
-          BlazeValidationError.collect(errors, new BlazeValidationError(
-            "Invalid target name: " + ruleName + "\n" +
-            "target names may not contain \"//\" path separators"
-          ));
-          return false;
-        }
-        continue;
-      }
-      boolean isAlnum = ALNUM_REGEX.matcher(String.valueOf(c)).matches();
-      if (!isAlnum) {
-        BlazeValidationError.collect(errors, new BlazeValidationError(
-          "Invalid target name: " + ruleName + "\n" +
-          "target names may not contain " + c
-        ));
-        return false;
-      }
-    }
-
-    // Forbidden end chars:
-    if (ruleName.endsWith("/..")) {
-      BlazeValidationError.collect(errors, new BlazeValidationError(
-        "Invalid target name: " + ruleName + "\n" +
-        "target names may not contain up-level references \"..\""
-      ));
-      return false;
-    }
-    else if (ruleName.endsWith("/.")) {
-      return true;
-    }
-    else if (ruleName.endsWith("/")) {
-      BlazeValidationError.collect(errors, new BlazeValidationError(
-        "Invalid target name: " + ruleName + "\n" +
-        "target names may not end with \"/\""
-      ));
-      return false;
-    }
-    return true;
-  }
-
-  @Override
-  public String toString() {
-    return name;
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hashCode(name);
-  }
-
-  @Override
-  public boolean equals(Object obj) {
-    if (this == obj) {
-      return true;
-    }
-    if (obj instanceof RuleName) {
-      RuleName that = (RuleName) obj;
-      return Objects.equal(name, that.name);
-    }
-    return false;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/model/primitives/TargetExpression.java b/blaze-base/src/com/google/idea/blaze/base/model/primitives/TargetExpression.java
deleted file mode 100644
index 6093869..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/model/primitives/TargetExpression.java
+++ /dev/null
@@ -1,86 +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 com.google.common.base.Preconditions;
-
-import java.io.Serializable;
-
-/**
- * An interface for objects that represent targets you could pass to Blaze on the command line.
- * See {@link com.google.idea.blaze.base.model.primitives.Label},
- */
-public class TargetExpression implements Serializable, Comparable<TargetExpression> {
-  public static final long serialVersionUID = 1L;
-
-  private final String expression;
-
-  /**
-   * @return A Label instance if the expression is a valid label, or a TargetExpression
-   * instance if it is not.
-   */
-  public static TargetExpression fromString(String expression) {
-    return Label.validate(expression)
-           ? new Label(expression)
-           : new TargetExpression(expression);
-  }
-
-  TargetExpression(String expression) {
-    // TODO(joshgiles): Validation/canonicalization for target expressions.
-    // For reference, handled in Blaze/Bazel in TargetPattern.java.
-    Preconditions.checkArgument(!expression.isEmpty(), "Target should be non-empty.");
-    this.expression = expression;
-  }
-
-  @Override
-  public String toString() {
-    return expression;
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (!(o instanceof TargetExpression)) {
-      return false;
-    }
-    TargetExpression that = (TargetExpression)o;
-    return expression.equals(that.expression);
-  }
-
-  @Override
-  public int hashCode() {
-    return expression.hashCode();
-  }
-
-  /**
-   * All targets in all packages below the given path
-   */
-  public static TargetExpression allFromPackageRecursive(WorkspacePath localPackage) {
-    if (localPackage.relativePath().isEmpty()) {
-      // localPackage is the workspace root
-      return new TargetExpression("//...:all");
-    }
-    return new TargetExpression("//" + localPackage.relativePath() + "/...:all");
-  }
-
-  public static TargetExpression allFromPackageNonRecursive(WorkspacePath localPackage) {
-    return new TargetExpression("//" + localPackage.relativePath() + ":all");
-  }
-
-  @Override
-  public int compareTo(TargetExpression o) {
-    return expression.compareTo(o.expression);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/model/primitives/WorkspacePath.java b/blaze-base/src/com/google/idea/blaze/base/model/primitives/WorkspacePath.java
deleted file mode 100644
index 4a5c60a..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/model/primitives/WorkspacePath.java
+++ /dev/null
@@ -1,121 +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 com.google.idea.blaze.base.ui.BlazeValidationError;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import javax.annotation.concurrent.Immutable;
-import java.io.Serializable;
-import java.util.Collection;
-
-/**
- * Represents a path relative to the workspace root. The path component separator is Blaze specific.
- * <p/>
- * A {@link WorkspacePath} is *not* necessarily a valid package name/path. The primary reason is
- * because it could represent a file and files don't have to follow the same conventions as package
- * names.
- */
-@Immutable
-public class WorkspacePath implements Serializable {
-  public static final long serialVersionUID = 1L;
-
-  /**
-   * Silently returns null if this is not a valid workspace path.
-   */
-  @Nullable
-  public static WorkspacePath createIfValid(String relativePath) {
-    if (validate(relativePath)) {
-      return new WorkspacePath(relativePath);
-    }
-    return null;
-  }
-
-  private static final char BLAZE_COMPONENT_SEPARATOR = '/';
-
-  @NotNull
-  private final String relativePath;
-
-  /**
-   * @param relativePath relative path that must use the Blaze specific separator char to separate
-   *                     path components
-   */
-  public WorkspacePath(@NotNull String relativePath) {
-    if (!validate(relativePath)) {
-      throw new IllegalArgumentException("Invalid workspace path: " + relativePath);
-    }
-    this.relativePath = relativePath;
-  }
-
-  public WorkspacePath(@NotNull WorkspacePath parentPath, @NotNull String childPath) {
-    this(parentPath.relativePath() + BLAZE_COMPONENT_SEPARATOR + childPath);
-  }
-
-  public static boolean validate(@NotNull String relativePath) {
-    return validate(relativePath, null);
-  }
-
-  public static boolean validate(@NotNull String relativePath, @Nullable Collection<BlazeValidationError> errors) {
-    if (relativePath.startsWith("/") ) {
-      BlazeValidationError.collect(errors, new BlazeValidationError("Workspace path may not start with '/': " + relativePath));
-      return false;
-    }
-
-    if (relativePath.endsWith("/") ) {
-      BlazeValidationError.collect(errors, new BlazeValidationError("Workspace path may not end with '/': " + relativePath));
-      return false;
-    }
-
-    if (relativePath.indexOf(':') >= 0) {
-      BlazeValidationError.collect(errors, new BlazeValidationError("Workspace path may not contain ':': " + relativePath));
-      return false;
-    }
-
-    return true;
-  }
-
-  public boolean isWorkspaceRoot() {
-    return relativePath.isEmpty();
-  }
-
-  @Override
-  public String toString() {
-    return relativePath;
-  }
-
-  public String relativePath() {
-    return relativePath;
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) {
-      return true;
-    }
-    if (o == null || this.getClass() != o.getClass()) {
-      return false;
-    }
-
-    WorkspacePath that = (WorkspacePath)o;
-    return relativePath.equals(that.relativePath);
-  }
-
-  @Override
-  public int hashCode() {
-    return relativePath.hashCode();
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/model/primitives/WorkspaceRoot.java b/blaze-base/src/com/google/idea/blaze/base/model/primitives/WorkspaceRoot.java
deleted file mode 100644
index 917603a..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/model/primitives/WorkspaceRoot.java
+++ /dev/null
@@ -1,136 +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 com.google.idea.blaze.base.settings.Blaze;
-import com.google.idea.blaze.base.settings.BlazeImportSettings;
-import com.google.idea.blaze.base.settings.BlazeImportSettingsManager;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.io.FileUtil;
-import com.intellij.openapi.vfs.VirtualFile;
-
-import javax.annotation.Nullable;
-import java.io.File;
-import java.io.Serializable;
-
-/**
- * Represents a workspace root
- */
-public class WorkspaceRoot implements Serializable {
-  public static final long serialVersionUID = 1L;
-
-  private final File directory;
-
-  public WorkspaceRoot(File directory) {
-    this.directory = directory;
-  }
-
-  /**
-   * Get the workspace root for a project
-   *
-   * @param blazeSettings settings for the project in question
-   * @return the path to workspace root that is used for the project
-   */
-  public static WorkspaceRoot fromImportSettings(BlazeImportSettings blazeSettings) {
-    return new WorkspaceRoot(new File(blazeSettings.getWorkspaceRoot()));
-  }
-
-  /**
-   * Tries to load the import settings for the given project and get the workspace root directory.<br>
-   * Unlike {@link #fromProject}, it will silently return null if this is not a blaze project.
-   */
-  @Nullable
-  public static WorkspaceRoot fromProjectSafe(Project project) {
-    if (Blaze.isBlazeProject(project)) {
-      return fromProject(project);
-    }
-    return null;
-  }
-
-  /**
-   * Tries to load the import settings for the given project and get the workspace root directory.
-   */
-  public static WorkspaceRoot fromProject(Project project) {
-    BlazeImportSettings importSettings = BlazeImportSettingsManager.getInstance(project)
-      .getImportSettings();
-    if (importSettings == null) {
-      throw new IllegalStateException("null BlazeImportSettings.");
-    }
-    return fromImportSettings(importSettings);
-  }
-
-  public File fileForPath(WorkspacePath workspacePath) {
-    return new File(directory, workspacePath.relativePath());
-  }
-
-  public File directory() {
-    return directory;
-  }
-
-  public WorkspacePath workspacePathFor(VirtualFile file) {
-    return workspacePathFor(file.getPath());
-  }
-
-  public boolean isInWorkspace(VirtualFile file) {
-    return isInWorkspace(file.getPath());
-  }
-
-  public WorkspacePath workspacePathFor(File file) {
-    return workspacePathFor(file.getPath());
-  }
-
-  public boolean isInWorkspace(File file) {
-    return isInWorkspace(file.getPath());
-  }
-
-  private WorkspacePath workspacePathFor(String path) {
-    if (!isInWorkspace(path)) {
-      throw new IllegalArgumentException("File is not under this workspace");
-    }
-    if (directory.getPath().length() == path.length()) {
-      return new WorkspacePath("");
-    }
-    return new WorkspacePath(path.substring(directory.getPath().length() + 1));
-  }
-
-  private boolean isInWorkspace(String path) {
-    return FileUtil.isAncestor(directory.getPath(), path, false);
-  }
-
-  @Override
-  public String toString() {
-    return directory.toString();
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) {
-      return true;
-    }
-    if (o == null || getClass() != o.getClass()) {
-      return false;
-    }
-
-    WorkspaceRoot that = (WorkspaceRoot)o;
-    return directory.equals(that.directory);
-  }
-
-  @Override
-  public int hashCode() {
-    return directory.hashCode();
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/model/primitives/WorkspaceType.java b/blaze-base/src/com/google/idea/blaze/base/model/primitives/WorkspaceType.java
deleted file mode 100644
index fbd7eb1..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/model/primitives/WorkspaceType.java
+++ /dev/null
@@ -1,60 +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;
-
-/**
- * Workspace types.
- *
- * <p>If the user doesn't specify a workspace, she gets the highest
- * supported workspace type by 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");
-
-  private final String name;
-  private final LanguageClass[] languages;
-  WorkspaceType(String name, LanguageClass... languages) {
-    this.name = name;
-    this.languages = languages;
-  }
-
-  public String getName() {
-    return name;
-  }
-
-  public LanguageClass[] getLanguages() {
-    return languages;
-  }
-
-  public static WorkspaceType fromString(String name) {
-    for (WorkspaceType ruleClass : WorkspaceType.values()) {
-      if (ruleClass.name.equals(name)) {
-        return ruleClass;
-      }
-    }
-    return null;
-  }
-
-  @Override
-  public String toString() {
-    return name;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/plugin/BlazeActionRemover.java b/blaze-base/src/com/google/idea/blaze/base/plugin/BlazeActionRemover.java
deleted file mode 100644
index ebb3fc0..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/plugin/BlazeActionRemover.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.plugin;
-
-import com.google.idea.blaze.base.settings.Blaze;
-import com.intellij.openapi.actionSystem.ActionManager;
-import com.intellij.openapi.actionSystem.AnAction;
-import com.intellij.openapi.actionSystem.AnActionEvent;
-import com.intellij.openapi.actionSystem.Presentation;
-import com.intellij.openapi.project.Project;
-
-/**
- * Wraps an action and makes it invisible for blaze-based projects.
- */
-public class BlazeActionRemover extends AnAction {
-
-  public static void hideAction(String actionId) {
-    AnAction oldAction = ActionManager.getInstance().getAction(actionId);
-    if (oldAction != null) {
-      replaceAction(actionId, new BlazeActionRemover(oldAction));
-    }
-  }
-
-  private static void replaceAction(String actionId, AnAction newAction) {
-    ActionManager actionManager = ActionManager.getInstance();
-    AnAction oldAction = actionManager.getAction(actionId);
-    if (oldAction != null) {
-      newAction.getTemplatePresentation().setIcon(oldAction.getTemplatePresentation().getIcon());
-      actionManager.unregisterAction(actionId);
-    }
-    actionManager.registerAction(actionId, newAction);
-  }
-
-  private final AnAction delegate;
-
-  private BlazeActionRemover(AnAction delegate) {
-    super(delegate.getTemplatePresentation().getTextWithMnemonic(),
-          delegate.getTemplatePresentation().getDescription(),
-          delegate.getTemplatePresentation().getIcon());
-    this.delegate = delegate;
-  }
-
-  @Override
-  public void actionPerformed(AnActionEvent e) {
-    delegate.actionPerformed(e);
-  }
-
-  @Override
-  public void update(AnActionEvent e) {
-    Presentation presentation = e.getPresentation();
-    Project project = e.getProject();
-    if (project != null && Blaze.isBlazeProject(project)) {
-      presentation.setEnabledAndVisible(false);
-      return;
-    }
-    delegate.update(e);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/plugin/BlazeBinaryFileType.java b/blaze-base/src/com/google/idea/blaze/base/plugin/BlazeBinaryFileType.java
deleted file mode 100644
index d032499..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/plugin/BlazeBinaryFileType.java
+++ /dev/null
@@ -1,30 +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.plugin;
-
-import com.intellij.openapi.fileTypes.UserBinaryFileType;
-
-/**
- * Marker type to mark something as binary.
- */
-public class BlazeBinaryFileType extends UserBinaryFileType {
-  public static final BlazeBinaryFileType INSTANCE;
-  static {
-    INSTANCE = new BlazeBinaryFileType();
-    INSTANCE.setName("Binary File");
-    INSTANCE.setDescription("The blaze plugin has guessed this file type as binary for performance.");
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/plugin/BlazeFileTypeFactory.java b/blaze-base/src/com/google/idea/blaze/base/plugin/BlazeFileTypeFactory.java
deleted file mode 100644
index 03d8771..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/plugin/BlazeFileTypeFactory.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.plugin;
-
-import com.intellij.ide.highlighter.ArchiveFileType;
-import com.intellij.openapi.fileTypes.FileTypeConsumer;
-import com.intellij.openapi.fileTypes.FileTypeFactory;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * @author chuckj
- */
-public class BlazeFileTypeFactory extends FileTypeFactory {
-  @Override
-  public void createFileTypes(@NotNull final FileTypeConsumer consumer) {
-    consumer.consume(ArchiveFileType.INSTANCE, "srcjar");
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/plugin/BlazePluginId.java b/blaze-base/src/com/google/idea/blaze/base/plugin/BlazePluginId.java
deleted file mode 100644
index 4ef6f93..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/plugin/BlazePluginId.java
+++ /dev/null
@@ -1,33 +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.plugin;
-
-import com.intellij.openapi.components.ServiceManager;
-
-/**
- * Supplies the ID of the sync plugin.
- */
-public interface BlazePluginId {
-
-  static BlazePluginId getInstance() {
-    return ServiceManager.getService(BlazePluginId.class);
-  }
-
-  /**
-   * @return the plugin ID (same as in the plugin.xml).
-   */
-  String getPluginId();
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/plugin/BlazeSpecificInitializer.java b/blaze-base/src/com/google/idea/blaze/base/plugin/BlazeSpecificInitializer.java
deleted file mode 100644
index 1d1e4f8..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/plugin/BlazeSpecificInitializer.java
+++ /dev/null
@@ -1,53 +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.plugin;
-
-import com.google.idea.blaze.base.lang.buildfile.language.BuildFileLanguage;
-import com.google.idea.blaze.base.lang.buildfile.language.BuildFileTypeFactory;
-import com.intellij.openapi.actionSystem.IdeActions;
-import com.intellij.openapi.components.ApplicationComponent;
-
-/**
- * Runs on startup.
- */
-public class BlazeSpecificInitializer extends ApplicationComponent.Adapter {
-
-  @Override
-  public void initComponent() {
-    hideMakeActions();
-    initializeBuildFileSupportStatus();
-  }
-
-  private static void initializeBuildFileSupportStatus() {
-    BuildFileTypeFactory.updateBuildFileLanguageEnabled(BuildFileLanguage.buildFileSupportEnabled());
-  }
-
-  // The original actions will be visible only on plain IDEA projects.
-  private static void hideMakeActions() {
-    // 'Build' > 'Make Project' action
-    BlazeActionRemover.hideAction("CompileDirty");
-
-    // 'Build' > 'Make Modules' action
-    BlazeActionRemover.hideAction(IdeActions.ACTION_MAKE_MODULE);
-
-    // 'Build' > 'Rebuild' action
-    BlazeActionRemover.hideAction(IdeActions.ACTION_COMPILE_PROJECT);
-
-    // 'Build' > 'Compile Modules' action
-    BlazeActionRemover.hideAction(IdeActions.ACTION_COMPILE);
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/plugin/Version.java b/blaze-base/src/com/google/idea/blaze/base/plugin/Version.java
deleted file mode 100644
index 5806517..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/plugin/Version.java
+++ /dev/null
@@ -1,52 +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.plugin;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.intellij.ide.plugins.IdeaPluginDescriptor;
-import com.intellij.ide.plugins.PluginManager;
-import com.intellij.openapi.extensions.PluginId;
-
-/**
- * Blaze sync plugin ID and version information.
- */
-public class Version {
-
-  public static class PluginInfo {
-    public final String id;
-    public final String version;
-
-    @VisibleForTesting
-    public static final PluginInfo UNKNOWN = new PluginInfo("UNKNOWN_PLUGIN", "UNKNOWN_VERSION");
-
-    public PluginInfo(String id, String version) {
-      this.id = id;
-      this.version = version;
-    }
-  }
-
-  public static PluginInfo getSyncPluginInfo() {
-    BlazePluginId idService = BlazePluginId.getInstance();
-    if (idService != null) {
-      PluginId pluginId = PluginId.getId(idService.getPluginId());
-      IdeaPluginDescriptor pluginInfo = PluginManager.getPlugin(pluginId);
-      if (pluginInfo != null) {
-        return new PluginInfo(pluginId.getIdString(), pluginInfo.getVersion());
-      }
-    }
-    return PluginInfo.UNKNOWN;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/plugin/dependency/PluginDependencyHelper.java b/blaze-base/src/com/google/idea/blaze/base/plugin/dependency/PluginDependencyHelper.java
deleted file mode 100644
index 0e4bac1..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/plugin/dependency/PluginDependencyHelper.java
+++ /dev/null
@@ -1,67 +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.plugin.dependency;
-
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.plugin.BlazePluginId;
-import com.intellij.externalDependencies.DependencyOnPlugin;
-import com.intellij.externalDependencies.ExternalDependenciesManager;
-import com.intellij.externalDependencies.ProjectExternalDependency;
-import com.intellij.openapi.project.Project;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.List;
-
-public class PluginDependencyHelper {
-
-  public static void addDependencyOnSyncPlugin(@NotNull Project blazeProject) {
-    BlazePluginId idService = BlazePluginId.getInstance();
-    if (idService != null) {
-      addDependency(
-        blazeProject,
-        new DependencyOnPlugin(idService.getPluginId(), null, null, null)
-      );
-    }
-  }
-
-  /**
-   * Adds dependency, or replaces existing dependency of same type.
-   * Doesn't trigger any update checking
-   */
-  private static void addDependency(
-    @NotNull Project project,
-    @NotNull DependencyOnPlugin newDep) {
-
-    ExternalDependenciesManager manager = ExternalDependenciesManager.getInstance(project);
-    List<ProjectExternalDependency> deps = Lists.newArrayList(manager.getAllDependencies());
-    boolean added = false;
-    for (int i = 0; i < deps.size(); i++) {
-      ProjectExternalDependency dep = deps.get(i);
-      if (!(dep instanceof DependencyOnPlugin)) {
-        continue;
-      }
-      DependencyOnPlugin pluginDep = (DependencyOnPlugin) dep;
-      if (pluginDep.getPluginId().equals(newDep.getPluginId())) {
-        added = true;
-        deps.set(i, newDep);
-      }
-    }
-    if (!added) {
-      deps.add(newDep);
-    }
-    manager.setAllDependencies(deps);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/plugin/dependency/ProjectDependencyMigration.java b/blaze-base/src/com/google/idea/blaze/base/plugin/dependency/ProjectDependencyMigration.java
deleted file mode 100644
index eafdbf9..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/plugin/dependency/ProjectDependencyMigration.java
+++ /dev/null
@@ -1,43 +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.plugin.dependency;
-
-import com.google.idea.blaze.base.settings.Blaze;
-import com.intellij.openapi.components.ApplicationComponent;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.project.ProjectManager;
-import com.intellij.openapi.project.ProjectManagerAdapter;
-
-/**
- * Temporary migration code. Listens for blaze projects opening and closing,
- * and adds required plugin dependencies
- */
-public class ProjectDependencyMigration extends ApplicationComponent.Adapter {
-
-  @Override
-  public void initComponent() {
-    ProjectManager projectManager = ProjectManager.getInstance();
-    projectManager.addProjectManagerListener(new ProjectManagerAdapter() {
-      @Override
-      public void projectOpened(Project project) {
-        if (Blaze.isBlazeProject(project)) {
-          PluginDependencyHelper.addDependencyOnSyncPlugin(project);
-        }
-      }
-    });
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/prefetch/PrefetchService.java b/blaze-base/src/com/google/idea/blaze/base/prefetch/PrefetchService.java
deleted file mode 100644
index fa7594c..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/prefetch/PrefetchService.java
+++ /dev/null
@@ -1,47 +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.prefetch;
-
-import com.google.common.util.concurrent.ListenableFuture;
-import com.intellij.openapi.components.ServiceManager;
-import com.intellij.openapi.project.Project;
-
-import java.io.File;
-import java.util.List;
-
-/**
- * Interface to request prefetching of files
- */
-public interface PrefetchService {
-  static PrefetchService getInstance() {
-    return ServiceManager.getService(PrefetchService.class);
-  }
-
-  /**
-   * Instructs all prefetchers to prefetch these files.
-   *
-   * @param files The files to prefetch
-   * @param synchronous A hint whether the prefetch should be complete when the future completes.
-   */
-  ListenableFuture<?> prefetchFiles(List<File> files, boolean synchronous);
-
-  /**
-   * Instructs all prefetchers to prefetch any project files they're interested in.
-   *
-   * Should be asynchronous.
-   */
-  void prefetchProjectFiles(Project project);
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/prefetch/PrefetchServiceImpl.java b/blaze-base/src/com/google/idea/blaze/base/prefetch/PrefetchServiceImpl.java
deleted file mode 100644
index a335bb6..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/prefetch/PrefetchServiceImpl.java
+++ /dev/null
@@ -1,53 +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.prefetch;
-
-import com.google.common.collect.Lists;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.common.util.concurrent.MoreExecutors;
-import com.intellij.openapi.project.Project;
-import com.intellij.util.concurrency.BoundedTaskExecutor;
-
-import java.io.File;
-import java.util.List;
-import java.util.concurrent.Executors;
-
-/**
- * Implementation for prefetcher.
- */
-public class PrefetchServiceImpl implements PrefetchService {
-  private static final int THREAD_COUNT = 32;
-  private final ListeningExecutorService executor = MoreExecutors.listeningDecorator(
-    new BoundedTaskExecutor(Executors.newFixedThreadPool(THREAD_COUNT), THREAD_COUNT));
-
-  @Override
-  public ListenableFuture<?> prefetchFiles(List<File> files, boolean synchronous) {
-    List<ListenableFuture<?>> futures = Lists.newArrayList();
-    for (Prefetcher prefetcher : Prefetcher.EP_NAME.getExtensions()) {
-      futures.add(prefetcher.prefetchFiles(files, executor, synchronous));
-    }
-    return Futures.allAsList(futures);
-  }
-
-  @Override
-  public void prefetchProjectFiles(Project project) {
-    for (Prefetcher prefetcher : Prefetcher.EP_NAME.getExtensions()) {
-      prefetcher.prefetchProjectFiles(project, executor);
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/prefetch/PrefetchServiceProjectComponent.java b/blaze-base/src/com/google/idea/blaze/base/prefetch/PrefetchServiceProjectComponent.java
deleted file mode 100644
index 66a404e..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/prefetch/PrefetchServiceProjectComponent.java
+++ /dev/null
@@ -1,73 +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.prefetch;
-
-import com.google.idea.blaze.base.settings.Blaze;
-import com.intellij.ProjectTopics;
-import com.intellij.openapi.components.ProjectComponent;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.roots.ModuleRootAdapter;
-import com.intellij.openapi.roots.ModuleRootEvent;
-import com.intellij.util.messages.MessageBusConnection;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Instructs prefetchers to prefetch all project files when they may have changed / be stale.
- */
-public class PrefetchServiceProjectComponent implements ProjectComponent {
-  private final Project project;
-
-  public PrefetchServiceProjectComponent(Project project) {
-    this.project = project;
-    MessageBusConnection connection = project.getMessageBus().connect(project);
-    connection.subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootAdapter() {
-      @Override
-      public void rootsChanged(ModuleRootEvent event) {
-        prefetch();
-      }
-    });
-  }
-
-  @Override
-  public void projectOpened() {
-    prefetch();
-  }
-
-  private void prefetch() {
-    if (!Blaze.isBlazeProject(project)) {
-      return;
-    }
-    PrefetchService.getInstance().prefetchProjectFiles(project);
-  }
-
-  @Override
-  public void projectClosed() {
-  }
-
-  @Override
-  public void initComponent() {
-  }
-
-  @Override
-  public void disposeComponent() {
-  }
-
-  @NotNull
-  @Override
-  public String getComponentName() {
-    return "PrefetchServiceProjectComponent";
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/prefetch/Prefetcher.java b/blaze-base/src/com/google/idea/blaze/base/prefetch/Prefetcher.java
deleted file mode 100644
index 274b2b5..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/prefetch/Prefetcher.java
+++ /dev/null
@@ -1,48 +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.prefetch;
-
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.intellij.openapi.extensions.ExtensionPointName;
-import com.intellij.openapi.project.Project;
-
-import java.io.File;
-
-/**
- * Prefetches files when a project is opened or roots change.
- */
-public interface Prefetcher {
-  ExtensionPointName<Prefetcher> EP_NAME = ExtensionPointName.create("com.google.idea.blaze.Prefetcher");
-
-  /**
-   * Prefetches the given list of files.
-   *
-   * It is the responsibility of the prefetcher to filter out any files it isn't interested in.
-   *
-   * @param synchronous A hint whether the prefetch should be complete when the returned future completes
-   */
-  ListenableFuture<?> prefetchFiles(Iterable<File> file,
-                                    ListeningExecutorService executor,
-                                    boolean synchrononous);
-
-  /**
-   * Prefetch any project files that this prefetcher is interested in.
-   *
-   * <p>The prefetch should be asynchronous.
-   */
-  void prefetchProjectFiles(Project project, ListeningExecutorService executor);
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/projectview/ProjectView.java b/blaze-base/src/com/google/idea/blaze/base/projectview/ProjectView.java
deleted file mode 100644
index 7366b6b..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/projectview/ProjectView.java
+++ /dev/null
@@ -1,90 +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.projectview;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Maps;
-import com.google.idea.blaze.base.projectview.section.Section;
-import com.google.idea.blaze.base.projectview.section.SectionBuilder;
-import com.google.idea.blaze.base.projectview.section.SectionKey;
-import com.intellij.openapi.diagnostic.Logger;
-
-import javax.annotation.Nullable;
-import java.io.Serializable;
-import java.util.Map;
-
-/**
- * Represents instructions for what should be included in a project.
- */
-public final class ProjectView implements Serializable {
-  private static final long serialVersionUID = 2L;
-
-  private static final Logger LOG = Logger.getInstance(ProjectView.class);
-  private final ImmutableMap<SectionKey, Section> sections;
-
-  private ProjectView(ImmutableMap<SectionKey, Section> sections) {
-    this.sections = sections;
-  }
-
-  @SuppressWarnings("unchecked")
-  @Nullable
-  public <T, SectionType extends Section<T>> SectionType getSectionOfType(SectionKey<T, SectionType> key) {
-    return (SectionType) sections.get(key);
-  }
-
-  public static Builder builder() {
-    return new Builder();
-  }
-
-  public static Builder builder(ProjectView projectView) {
-    return new Builder(projectView);
-  }
-
-  /**
-   * Builder class.
-   */
-  public static class Builder {
-    private final Map<SectionKey, Section> sections;
-
-    Builder() {
-      sections = Maps.newHashMap();
-    }
-
-    Builder(ProjectView projectView) {
-      sections = Maps.newHashMap(projectView.sections);
-    }
-
-    public <T, SectionType extends Section<T>> Builder put(SectionBuilder<T, SectionType> builder) {
-      sections.put(builder.getSectionKey(), builder.build());
-      return this;
-    }
-
-    public <T, SectionType extends Section<T>> Builder put(SectionKey<T, SectionType> key, SectionType section) {
-      sections.put(key, section);
-      return this;
-    }
-
-    @SuppressWarnings("unchecked")
-    @Nullable
-    public <T, SectionType extends Section<T>> SectionType get(SectionKey<T, SectionType> key) {
-      return (SectionType) sections.get(key);
-    }
-
-    public ProjectView build() {
-      return new ProjectView(ImmutableMap.copyOf(sections));
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/projectview/ProjectViewEdit.java b/blaze-base/src/com/google/idea/blaze/base/projectview/ProjectViewEdit.java
deleted file mode 100644
index ea647f8..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/projectview/ProjectViewEdit.java
+++ /dev/null
@@ -1,123 +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.projectview;
-
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.projectview.parser.ProjectViewParser;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.ui.Messages;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
-
-/**
- * Represents a modification to one or more project view files.
- */
-public class ProjectViewEdit {
-
-  private static final Logger LOG = Logger.getInstance(ProjectViewEdit.class);
-  private final Project project;
-  private final List<Modification> modifications;
-
-  ProjectViewEdit(Project project, List<Modification> modifications) {
-    this.project = project;
-    this.modifications = modifications;
-  }
-
-  private static class Modification {
-    ProjectView oldProjectView;
-    ProjectView newProjectView;
-    File projectViewFile;
-  }
-
-  /**
-   * Creates a new edit that modifies all reachable project views in the project view set.
-   */
-  public static ProjectViewEdit editProjectViewSet(Project project, ProjectViewEditor editor) {
-    ProjectViewSet oldProjectViewSet = ProjectViewManager.getInstance(project).getProjectViewSet();
-    List<Modification> modifications = Lists.newArrayList();
-    if (oldProjectViewSet != null) {
-      for (ProjectViewSet.ProjectViewFile projectViewFile : oldProjectViewSet.getProjectViewFiles()) {
-        ProjectView.Builder builder = ProjectView.builder(projectViewFile.projectView);
-        if (editor.editProjectView(builder)) {
-          Modification modification = new Modification();
-          modification.newProjectView = builder.build();
-          modification.oldProjectView = projectViewFile.projectView;
-          modification.projectViewFile = projectViewFile.projectViewFile;
-          modifications.add(modification);
-        }
-      }
-    }
-    return new ProjectViewEdit(project, modifications);
-  }
-
-  /**
-   * Creates a new edit that modifies the local project view only.
-   */
-  public static ProjectViewEdit editLocalProjectView(Project project, ProjectViewEditor editor) {
-    List<Modification> modifications = Lists.newArrayList();
-    ProjectViewSet oldProjectViewSet = ProjectViewManager.getInstance(project).getProjectViewSet();
-    if (oldProjectViewSet != null) {
-      ProjectViewSet.ProjectViewFile projectViewFile = oldProjectViewSet.getTopLevelProjectViewFile();
-      if (projectViewFile != null) {
-        ProjectView.Builder builder = ProjectView.builder(projectViewFile.projectView);
-        if (editor.editProjectView(builder)) {
-          Modification modification = new Modification();
-          modification.newProjectView = builder.build();
-          modification.oldProjectView = projectViewFile.projectView;
-          modification.projectViewFile = projectViewFile.projectViewFile;
-          modifications.add(modification);
-        }
-      }
-    }
-    return new ProjectViewEdit(project, modifications);
-  }
-
-  public void apply() {
-    apply(true);
-  }
-
-  public void undo() {
-    apply(false);
-  }
-
-  private void apply(boolean isApply) {
-    for (Modification modification : modifications) {
-      ProjectView projectView = isApply ? modification.newProjectView : modification.oldProjectView;
-      String projectViewText = ProjectViewParser.projectViewToString(projectView);
-      try {
-        ProjectViewStorageManager.getInstance().writeProjectView(projectViewText, modification.projectViewFile);
-      }
-      catch (IOException e) {
-        LOG.error(e);
-        Messages.showErrorDialog(
-          project,
-          "Could not write updated project view. Is the file write protected?",
-          "Edit Failed");
-      }
-    }
-  }
-
-  public boolean hasModifications() {
-    return !modifications.isEmpty();
-  }
-
-  public interface ProjectViewEditor {
-    boolean editProjectView(ProjectView.Builder builder);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/projectview/ProjectViewManager.java b/blaze-base/src/com/google/idea/blaze/base/projectview/ProjectViewManager.java
deleted file mode 100644
index 7f24cdb..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/projectview/ProjectViewManager.java
+++ /dev/null
@@ -1,48 +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.projectview;
-
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver;
-import com.intellij.openapi.components.ServiceManager;
-import com.intellij.openapi.project.Project;
-
-import javax.annotation.Nullable;
-
-/**
- * Class that manages access to a project's
- * {@link ProjectView}.
- */
-public abstract class ProjectViewManager {
-
-  public static ProjectViewManager getInstance(Project project) {
-    return ServiceManager.getService(project, ProjectViewManager.class);
-  }
-
-  /**
-   * Returns the current project view collection. If there is an error, returns null.
-   */
-  @Nullable
-  public abstract ProjectViewSet getProjectViewSet();
-
-  /**
-   * Reloads the project view, replacing the current one only if there are no errors.
-   *
-   * @return Success.
-   */
-  @Nullable
-  public abstract ProjectViewSet reloadProjectView(BlazeContext context, WorkspacePathResolver workspacePathResolver);
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/projectview/ProjectViewManagerImpl.java b/blaze-base/src/com/google/idea/blaze/base/projectview/ProjectViewManagerImpl.java
deleted file mode 100644
index c497b70..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/projectview/ProjectViewManagerImpl.java
+++ /dev/null
@@ -1,106 +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.projectview;
-
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.projectview.parser.ProjectViewParser;
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.google.idea.blaze.base.settings.BlazeImportSettings;
-import com.google.idea.blaze.base.settings.BlazeImportSettingsManager;
-import com.google.idea.blaze.base.sync.data.BlazeDataStorage;
-import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver;
-import com.google.idea.blaze.base.util.SerializationUtil;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.project.Project;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
-
-/**
- * Project view manager implementation.
- */
-/**
- * Stores mutable per-project user settings.
- */
-final class ProjectViewManagerImpl extends ProjectViewManager {
-
-  private static final Logger LOG = Logger.getInstance(ProjectViewManagerImpl.class);
-  private static final String CACHE_FILE_NAME = "project.view.dat";
-
-  private final Project project;
-  @Nullable private ProjectViewSet projectViewSet;
-  private boolean projectViewSetLoaded = false;
-
-  public ProjectViewManagerImpl(@NotNull Project project) {
-    this.project = project;
-  }
-
-  @Nullable
-  @Override
-  public ProjectViewSet getProjectViewSet() {
-    if (projectViewSet == null && !projectViewSetLoaded) {
-      ProjectViewSet loadedProjectViewSet = null;
-      try {
-        BlazeImportSettings importSettings = BlazeImportSettingsManager.getInstance(project).getImportSettings();
-        if (importSettings == null) {
-          return null;
-        }
-        File file = getCacheFile(project, importSettings);
-
-        List<ClassLoader> classLoaders = Lists.newArrayList();
-        classLoaders.add(getClass().getClassLoader());
-        classLoaders.add(Thread.currentThread().getContextClassLoader());
-        loadedProjectViewSet = (ProjectViewSet) SerializationUtil.loadFromDisk(file, classLoaders);
-      } catch (IOException e) {
-        LOG.info(e);
-      }
-      this.projectViewSet = loadedProjectViewSet;
-      this.projectViewSetLoaded = true;
-    }
-    return projectViewSet;
-  }
-
-  @Override
-  public ProjectViewSet reloadProjectView(BlazeContext context, WorkspacePathResolver workspacePathResolver) {
-    BlazeImportSettings importSettings = BlazeImportSettingsManager.getInstance(project).getImportSettings();
-    assert importSettings != null;
-    assert importSettings.getProjectViewFile() != null;
-    File projectViewFile = new File(importSettings.getProjectViewFile());
-    ProjectViewParser parser = new ProjectViewParser(context, workspacePathResolver);
-    parser.parseProjectView(projectViewFile);
-
-    boolean success = !context.hasErrors();
-    if (success) {
-      ProjectViewSet projectViewSet = parser.getResult();
-      File file = getCacheFile(project, importSettings);
-      try {
-        SerializationUtil.saveToDisk(file, projectViewSet);
-      }
-      catch (IOException e) {
-        LOG.error(e);
-      }
-      this.projectViewSet = projectViewSet;
-    }
-    return success ? projectViewSet : null;
-  }
-
-  private static File getCacheFile(Project project, BlazeImportSettings importSettings) {
-    return new File(BlazeDataStorage.getProjectCacheDir(project, importSettings), CACHE_FILE_NAME);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/projectview/ProjectViewSet.java b/blaze-base/src/com/google/idea/blaze/base/projectview/ProjectViewSet.java
deleted file mode 100644
index adcfaa4..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/projectview/ProjectViewSet.java
+++ /dev/null
@@ -1,126 +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.projectview;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-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.Section;
-import com.google.idea.blaze.base.projectview.section.SectionKey;
-
-import javax.annotation.Nullable;
-import java.io.File;
-import java.io.Serializable;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * A collection of project views and their file names.
- */
-public final class ProjectViewSet implements Serializable {
-  private static final long serialVersionUID = 1L;
-
-  private final ImmutableList<ProjectViewFile> projectViewFiles;
-
-  public ProjectViewSet(ImmutableList<ProjectViewFile> projectViewFiles) {
-    this.projectViewFiles = projectViewFiles;
-  }
-
-  public <T> List<T> listItems(SectionKey<T, ListSection<T>> key) {
-    List<T> result = Lists.newArrayList();
-    for (ListSection<T> section : getSections(key)) {
-      result.addAll(section.items());
-    }
-    return result;
-  }
-
-  @Nullable
-  public <T> T getSectionValue(SectionKey<T, ScalarSection<T>> key) {
-    return getSectionValue(key, null);
-  }
-
-  public <T> T getSectionValue(SectionKey<T, ScalarSection<T>> key, T defaultValue) {
-    Collection<ScalarSection<T>> sections = getSections(key);
-    if (sections.isEmpty()) {
-      return defaultValue;
-    } else {
-      return Iterables.getLast(sections).getValue();
-    }
-  }
-
-  public <T, SectionType extends Section<T>> Collection<SectionType> getSections(SectionKey<T, SectionType> key) {
-    List<SectionType> result = Lists.newArrayList();
-    for (ProjectViewFile projectViewFile : projectViewFiles) {
-      ProjectView projectView = projectViewFile.projectView;
-      SectionType section = projectView.getSectionOfType(key);
-      if (section != null) {
-        result.add(section);
-      }
-    }
-    return result;
-  }
-
-  public Collection<ProjectViewFile> getProjectViewFiles() {
-    return projectViewFiles;
-  }
-
-  @Nullable
-  public ProjectViewFile getTopLevelProjectViewFile() {
-    return !projectViewFiles.isEmpty() ? projectViewFiles.get(projectViewFiles.size() - 1) : null;
-  }
-
-  /**
-   * A project view/file pair
-   */
-  public static class ProjectViewFile implements Serializable {
-    private static final long serialVersionUID = 1L;
-    public final ProjectView projectView;
-    @Nullable public final File projectViewFile;
-
-    public ProjectViewFile(ProjectView projectView, @Nullable File projectViewFile) {
-      this.projectView = projectView;
-      this.projectViewFile = projectViewFile;
-    }
-  }
-
-  public static Builder builder() {
-    return new Builder();
-  }
-
-  public static class Builder {
-    ImmutableList.Builder<ProjectViewFile> projectViewFiles = ImmutableList.builder();
-
-    public Builder add(ProjectView projectView) {
-      return add(null, projectView);
-    }
-
-    public Builder add(@Nullable File projectViewFile, ProjectView projectView) {
-      projectViewFiles.add(new ProjectViewFile(projectView, projectViewFile));
-      return this;
-    }
-
-    public Builder addAll(Collection<ProjectViewFile> projectViewFiles) {
-      this.projectViewFiles.addAll(projectViewFiles);
-      return this;
-    }
-
-    public ProjectViewSet build() {
-      return new ProjectViewSet(projectViewFiles.build());
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/projectview/ProjectViewStorageManager.java b/blaze-base/src/com/google/idea/blaze/base/projectview/ProjectViewStorageManager.java
deleted file mode 100644
index 567db81..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/projectview/ProjectViewStorageManager.java
+++ /dev/null
@@ -1,82 +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.projectview;
-
-import com.google.common.collect.ImmutableList;
-import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
-import com.intellij.openapi.components.ServiceManager;
-import org.jetbrains.annotations.NotNull;
-
-import javax.annotation.Nullable;
-import java.io.File;
-import java.io.IOException;
-
-/**
- * Manages project view storage.
- *
- * For the most part, use ProjectViewManager instead. This is a lower-level API intended for use by
- * ProjectViewManager itself, and during the import process before a project exists.
- */
-public abstract class ProjectViewStorageManager {
-
-  private static final String BLAZE_EXTENSION = "blazeproject";
-  private static final String BAZEL_EXTENSION = "bazelproject";
-  private static final String LEGACY_EXTENSION = "asproject";
-
-  public static final ImmutableList<String> VALID_EXTENSIONS = ImmutableList.of(
-    BLAZE_EXTENSION,
-    BAZEL_EXTENSION,
-    LEGACY_EXTENSION
-  );
-
-  public static boolean isProjectViewFile(@NotNull File file) {
-    return isProjectViewFile(file.getName());
-  }
-
-  public static boolean isProjectViewFile(String fileName) {
-    for (String ext : VALID_EXTENSIONS) {
-      if (fileName.endsWith("." + ext)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  public static String getProjectViewFileName(BuildSystem buildSystem) {
-    switch (buildSystem) {
-      case Blaze:
-        return "." + BLAZE_EXTENSION;
-      case Bazel:
-        return "." + BAZEL_EXTENSION;
-      default:
-        throw new IllegalArgumentException("Unrecognized build system type: " + buildSystem);
-    }
-  }
-
-  public static File getLocalProjectViewFileName(BuildSystem buildSystem, File projectDataDirectory) {
-    return new File(projectDataDirectory, getProjectViewFileName(buildSystem));
-  }
-
-  public static ProjectViewStorageManager getInstance() {
-    return ServiceManager.getService(ProjectViewStorageManager.class);
-  }
-
-  @Nullable
-  public abstract String loadProjectView(File projectViewFile) throws IOException;
-
-  public abstract void writeProjectView(String projectViewText, File projectViewFile) throws IOException;
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/projectview/ProjectViewStorageManagerImpl.java b/blaze-base/src/com/google/idea/blaze/base/projectview/ProjectViewStorageManagerImpl.java
deleted file mode 100644
index 9f4bed2..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/projectview/ProjectViewStorageManagerImpl.java
+++ /dev/null
@@ -1,59 +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.projectview;
-
-import com.google.common.base.Charsets;
-import com.google.common.collect.ImmutableList;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.vfs.LocalFileSystem;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileWriter;
-import java.io.IOException;
-
-/**
- * Project view storage implementation.
- */
-final class ProjectViewStorageManagerImpl extends ProjectViewStorageManager {
-  private static final Logger LOG = Logger.getInstance(ProjectViewManagerImpl.class);
-
-  @Nullable
-  @Override
-  public String loadProjectView(@NotNull File projectViewFile) throws IOException {
-    FileInputStream fis = new FileInputStream(projectViewFile);
-    byte[] data = new byte[(int)projectViewFile.length()];
-    fis.read(data);
-    fis.close();
-    return new String(data, Charsets.UTF_8);
-  }
-
-  @Override
-  public void writeProjectView(
-    @NotNull String projectViewText,
-    @NotNull File projectViewFile) throws IOException {
-    FileWriter fileWriter = new FileWriter(projectViewFile);
-    try {
-      fileWriter.write(projectViewText);
-    } finally {
-      fileWriter.close();
-    }
-
-    LocalFileSystem.getInstance().refreshIoFiles(ImmutableList.of(projectViewFile));
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/projectview/ProjectViewVerifier.java b/blaze-base/src/com/google/idea/blaze/base/projectview/ProjectViewVerifier.java
deleted file mode 100644
index 705d832..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/projectview/ProjectViewVerifier.java
+++ /dev/null
@@ -1,201 +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.projectview;
-
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.io.WorkspaceScanner;
-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;
-import com.google.idea.blaze.base.projectview.section.sections.DirectoryEntry;
-import com.google.idea.blaze.base.projectview.section.sections.DirectorySection;
-import com.google.idea.blaze.base.projectview.section.sections.ExcludedSourceSection;
-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.projectview.WorkspaceLanguageSettings;
-import com.intellij.openapi.util.io.FileUtil;
-
-import java.util.List;
-
-/**
- * Verifies project views.
- */
-public class ProjectViewVerifier {
-
-  public static class MissingDirectoryIssueData extends IssueOutput.IssueData {
-    public final WorkspacePath workspacePath;
-    public MissingDirectoryIssueData(WorkspacePath workspacePath) {
-      this.workspacePath = workspacePath;
-    }
-  }
-
-  /**
-   * Verifies the project view. Any errors are output to the context as issues.
-   */
-  public static boolean verifyProjectView(
-    BlazeContext context,
-    WorkspaceRoot workspaceRoot,
-    ProjectViewSet projectViewSet,
-    WorkspaceLanguageSettings workspaceLanguageSettings) {
-    if (!verifyIncludedPackagesExistOnDisk(context, workspaceRoot, projectViewSet)) {
-      return false;
-    }
-    if (!verifyDirectoriesAreNonOverlapping(context, projectViewSet)) {
-      return false;
-    }
-    if (!verifyIncludedPackagesAreNotExcluded(context, projectViewSet)) {
-      return false;
-    }
-    for (BlazeSyncPlugin syncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) {
-      if (!syncPlugin.validateProjectView(context, projectViewSet, workspaceLanguageSettings)) {
-        return false;
-      }
-    }
-    if (!projectViewSet.listItems(ExcludedSourceSection.KEY).isEmpty()) {
-      IssueOutput
-        .warn("excluded_sources is deprecated and has no effect.")
-        .inFile(projectViewSet.getTopLevelProjectViewFile().projectViewFile)
-        .submit(context);
-    }
-    return true;
-  }
-
-  private static boolean verifyDirectoriesAreNonOverlapping(
-    BlazeContext context,
-    ProjectViewSet projectViewSet) {
-    boolean ok = true;
-
-    List<WorkspacePath> includedDirectories = getIncludedDirectories(projectViewSet);
-
-    for (WorkspacePath includedDirectory : includedDirectories) {
-      for (ProjectViewSet.ProjectViewFile projectViewFile : projectViewSet.getProjectViewFiles()) {
-        ListSection<DirectoryEntry> directorySection = projectViewFile.projectView.getSectionOfType(DirectorySection.KEY);
-        if (directorySection == null) {
-          continue;
-        }
-
-        for (DirectoryEntry entry : directorySection.items()) {
-          if (!entry.included) {
-            continue;
-          }
-
-          if (isAncestor(includedDirectory.relativePath(), entry.directory.relativePath())) {
-            IssueOutput
-              .error(String.format("Overlapping directories: %s already included by %s",
-                                   entry.directory.toString(),
-                                   includedDirectory.toString()))
-              .inFile(projectViewFile.projectViewFile)
-              .submit(context);
-            ok = false;
-          }
-        }
-      }
-    }
-    return ok;
-  }
-
-  /**
-   * Returns true if 'path' is a strict child of 'ancestorPath'.
-   */
-  private static boolean isAncestor(String ancestorPath, String path) {
-    // FileUtil.isAncestor has a bug in its handling of equal, empty paths (it ignores the 'strict' flag in this case).
-    if (ancestorPath.equals(path)) {
-      return false;
-    }
-    return FileUtil.isAncestor(ancestorPath, path, true);
-  }
-
-  private static boolean verifyIncludedPackagesAreNotExcluded(
-    BlazeContext context,
-    ProjectViewSet projectViewSet) {
-    boolean ok = true;
-
-    List<WorkspacePath> includedDirectories = getIncludedDirectories(projectViewSet);
-
-    for (WorkspacePath includedDirectory : includedDirectories) {
-      for (ProjectViewSet.ProjectViewFile projectViewFile : projectViewSet.getProjectViewFiles()) {
-        ListSection<DirectoryEntry> directorySection = projectViewFile.projectView.getSectionOfType(DirectorySection.KEY);
-        if (directorySection == null) {
-          continue;
-        }
-
-        for (DirectoryEntry entry : directorySection.items()) {
-          if (entry.included) {
-            continue;
-          }
-
-          WorkspacePath excludedDirectory = entry.directory;
-          if (FileUtil.isAncestor(
-            excludedDirectory.relativePath(),
-            includedDirectory.relativePath(),
-            false)) {
-            IssueOutput
-              .error(String.format("%s is included, but that contradicts %s which was excluded",
-                                   includedDirectory.toString(),
-                                   excludedDirectory.toString()))
-              .inFile(projectViewFile.projectViewFile)
-              .submit(context);
-            ok = false;
-          }
-        }
-      }
-    }
-    return ok;
-  }
-
-  private static List<WorkspacePath> getIncludedDirectories(ProjectViewSet projectViewSet) {
-    List<WorkspacePath> includedDirectories = Lists.newArrayList();
-    for (DirectoryEntry entry : projectViewSet.listItems(DirectorySection.KEY)) {
-      if (entry.included) {
-        includedDirectories.add(entry.directory);
-      }
-    }
-    return includedDirectories;
-  }
-
-  private static boolean verifyIncludedPackagesExistOnDisk(
-    BlazeContext context,
-    WorkspaceRoot workspaceRoot,
-    ProjectViewSet projectViewSet) {
-    boolean ok = true;
-
-    WorkspaceScanner workspaceScanner = WorkspaceScanner.getInstance();
-
-    for (ProjectViewSet.ProjectViewFile projectViewFile : projectViewSet.getProjectViewFiles()) {
-      ListSection<DirectoryEntry> directorySection = projectViewFile.projectView.getSectionOfType(DirectorySection.KEY);
-      if (directorySection == null) {
-        continue;
-      }
-      for (DirectoryEntry entry : directorySection.items()) {
-        if (!entry.included) {
-          continue;
-        }
-        WorkspacePath workspacePath = entry.directory;
-        if (!workspaceScanner.exists(workspaceRoot, workspacePath)) {
-          IssueOutput
-            .error(String.format("Directory '%s' specified in import roots not found under workspace root '%s'",
-                                 workspacePath, workspaceRoot))
-            .inFile(projectViewFile.projectViewFile)
-            .withData(new MissingDirectoryIssueData(workspacePath))
-            .submit(context);
-          ok = false;
-        }
-      }
-    }
-    return ok;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/projectview/parser/ParseContext.java b/blaze-base/src/com/google/idea/blaze/base/projectview/parser/ParseContext.java
deleted file mode 100644
index 8d20d8b..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/projectview/parser/ParseContext.java
+++ /dev/null
@@ -1,123 +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.projectview.parser;
-
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.projectview.section.SectionParser;
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.google.idea.blaze.base.scope.output.IssueOutput;
-import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver;
-import com.google.idea.blaze.base.ui.BlazeValidationError;
-import org.jetbrains.annotations.Nullable;
-
-import java.io.File;
-import java.util.List;
-
-/**
- * Context for the project view parser.
- */
-public class ParseContext {
-  private final BlazeContext context;
-  private final WorkspacePathResolver workspacePathResolver;
-  @Nullable private final File file;
-  private final List<String> lines;
-
-  @Nullable private Line currentLine;
-  private int currentLineIndex;
-
-  public static class Line {
-    public final String text;
-    public final int indent;
-    public Line(String text, int indent) {
-      this.text = text;
-      this.indent = indent;
-    }
-  }
-
-  public ParseContext(BlazeContext context, WorkspacePathResolver workspacePathResolver, @Nullable File file, String text) {
-    this.context = context;
-    this.workspacePathResolver = workspacePathResolver;
-    this.file = file;
-    this.lines = Lists.newArrayList(text.split("\n"));
-    this.currentLine = null;
-    this.currentLineIndex = -1;
-    consume();
-  }
-
-  public Line current() {
-    assert currentLine != null;
-    return currentLine;
-  }
-
-  public void consume() {
-    while (++currentLineIndex < lines.size()) {
-      String line = lines.get(currentLineIndex);
-      int indent = 0;
-      while (indent < line.length() && line.charAt(indent) == ' ') {
-        ++indent;
-      }
-      if (!indentationCorrect(indent)) {
-        addError(String.format("Invalid indentation. Project view files are indented with %d spaces.", SectionParser.INDENT));
-        continue;
-      }
-
-      line = line.trim();
-      if (!shouldSkipLine(line)) {
-        currentLine = new Line(line, indent);
-        break;
-      }
-    }
-  }
-
-  private boolean indentationCorrect(int indent) {
-    return indent == 0 || indent == 2;
-  }
-
-  boolean shouldSkipLine(String line) {
-    return line.isEmpty() || line.startsWith("#");
-  }
-
-  public boolean atEnd() {
-    return currentLineIndex >= lines.size();
-  }
-
-  public BlazeContext getContext() {
-    return context;
-  }
-
-  public WorkspacePathResolver getWorkspacePathResolver() {
-    return workspacePathResolver;
-  }
-
-  @Nullable
-  public File getProjectViewFile() {
-    return file;
-  }
-
-  public void addErrors(List<BlazeValidationError> errors) {
-    for (BlazeValidationError error : errors) {
-      addError(error.getError());
-    }
-  }
-
-  public void addError(String error) {
-    IssueOutput
-      .error(error)
-      .inFile(file)
-      .onLine(currentLineIndex)
-      .submit(context);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/projectview/parser/ProjectViewParser.java b/blaze-base/src/com/google/idea/blaze/base/projectview/parser/ProjectViewParser.java
deleted file mode 100644
index c9507a5..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/projectview/parser/ProjectViewParser.java
+++ /dev/null
@@ -1,151 +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.projectview.parser;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Maps;
-import com.google.idea.blaze.base.projectview.ProjectView;
-import com.google.idea.blaze.base.projectview.ProjectViewSet;
-import com.google.idea.blaze.base.projectview.ProjectViewStorageManager;
-import com.google.idea.blaze.base.projectview.section.Section;
-import com.google.idea.blaze.base.projectview.section.SectionKey;
-import com.google.idea.blaze.base.projectview.section.SectionParser;
-import com.google.idea.blaze.base.projectview.section.sections.Sections;
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.google.idea.blaze.base.scope.output.IssueOutput;
-import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Map;
-
-/**
- * Parses and writes project views.
- */
-public class ProjectViewParser {
-
-  private final BlazeContext context;
-  private final WorkspacePathResolver workspacePathResolver;
-  private final boolean recursive;
-
-  ImmutableList.Builder<ProjectViewSet.ProjectViewFile> projectViewFiles = ImmutableList.builder();
-
-  public ProjectViewParser(BlazeContext context,
-                           WorkspacePathResolver workspacePathResolver) {
-    this.context = context;
-    this.workspacePathResolver = workspacePathResolver;
-    this.recursive = true;
-  }
-
-  public void parseProjectView(File projectViewFile) {
-    String projectViewText = null;
-    try {
-      projectViewText = ProjectViewStorageManager.getInstance().loadProjectView(projectViewFile);
-    }
-    catch (IOException e) {
-      // Error handled below
-    }
-    if (projectViewText == null) {
-      IssueOutput.error(String.format("Could not load project view file: '%s'", projectViewFile.getPath()))
-        .submit(context);
-      return;
-    }
-    parseProjectView(new ParseContext(context, workspacePathResolver, projectViewFile, projectViewText));
-  }
-
-  public void parseProjectView(String text) {
-    parseProjectView(new ParseContext(context, workspacePathResolver, null, text));
-  }
-
-  private void parseProjectView(ParseContext parseContext) {
-    Map<SectionKey, Section> sectionMap = Maps.newHashMap();
-
-    while (!parseContext.atEnd()) {
-      if (parseContext.current().indent != 0) {
-        parseContext.addError(String.format("Invalid indentation on line: '%s'", parseContext.current().text));
-        skipSection(parseContext);
-        continue;
-      }
-      Section section = null;
-      SectionParser producingParser = null;
-      for (SectionParser sectionParser : Sections.getParsers()) {
-        section = sectionParser.parse(this, parseContext);
-        if (section != null) {
-          producingParser = sectionParser;
-          break;
-        }
-      }
-      if (section != null) {
-        SectionKey key = producingParser.getSectionKey();
-        if (!sectionMap.containsKey(key)) {
-          sectionMap.put(key, section);
-        } else {
-          BlazeContext context = parseContext.getContext();
-          IssueOutput.error(String.format("Duplicate attribute: '%s'", producingParser.getName()))
-            .inFile(parseContext.getProjectViewFile())
-            .submit(context);
-        }
-      } else {
-        parseContext.addError(String.format("Could not parse: '%s'", parseContext.current().text));
-        parseContext.consume();
-
-        // Skip past the entire section
-        skipSection(parseContext);
-      }
-    }
-
-    ProjectView.Builder builder = ProjectView.builder();
-    for (Map.Entry<SectionKey, Section> entry : sectionMap.entrySet()) {
-      builder.put(entry.getKey(), entry.getValue());
-    }
-
-    projectViewFiles.add(new ProjectViewSet.ProjectViewFile(builder.build(), parseContext.getProjectViewFile()));
-  }
-
-  /**
-   * Skips all lines until the next unindented, non-empty line.
-   */
-  private static void skipSection(ParseContext parseContext) {
-    while (!parseContext.atEnd() && parseContext.current().indent != 0) {
-      parseContext.consume();
-    }
-  }
-
-  public boolean isRecursive() {
-    return recursive;
-  }
-
-  public ProjectViewSet getResult() {
-    return new ProjectViewSet(projectViewFiles.build());
-  }
-
-  public static String projectViewToString(ProjectView projectView) {
-    StringBuilder sb = new StringBuilder();
-    int sectionCount = 0;
-    for (SectionParser sectionParser : Sections.getParsers()) {
-      SectionKey sectionKey = sectionParser.getSectionKey();
-      Section section = projectView.getSectionOfType(sectionKey);
-      if (section != null) {
-        if (sectionCount > 0) {
-          sb.append('\n');
-        }
-        sectionParser.print(sb, section);
-        ++sectionCount;
-      }
-    }
-    return sb.toString();
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/projectview/section/Glob.java b/blaze-base/src/com/google/idea/blaze/base/projectview/section/Glob.java
deleted file mode 100644
index 29226cd..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/projectview/section/Glob.java
+++ /dev/null
@@ -1,90 +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.projectview.section;
-
-import com.google.common.base.Objects;
-import com.google.common.collect.Lists;
-import com.intellij.openapi.fileTypes.FileNameMatcher;
-import org.jetbrains.jps.model.fileTypes.FileNameMatcherFactory;
-
-import java.io.Serializable;
-import java.util.Collection;
-
-/**
- * Glob matcher.
- */
-public class Glob implements Serializable {
-  private static final long serialVersionUID = 1L;
-
-  private String pattern;
-  transient private FileNameMatcher matcher;
-
-  public Glob(String pattern) {
-    this.pattern = pattern;
-  }
-
-  public static class GlobSet implements Serializable {
-    private static final long serialVersionUID = 1L;
-
-    private final Collection<Glob> globs = Lists.newArrayList();
-
-    public GlobSet(Collection<Glob> globs) {
-      this.globs.addAll(globs);
-    }
-
-    public boolean isEmpty() {
-      return globs.isEmpty();
-    }
-
-    public void add(Glob glob) {
-      globs.add(glob);
-    }
-
-    public boolean matches(String string) {
-      for (Glob glob : globs) {
-        if (glob.matches(string)) {
-          return true;
-        }
-      }
-      return false;
-    }
-  }
-
-  public boolean matches(String string) {
-    if (matcher == null) {
-      matcher = FileNameMatcherFactory.getInstance().createMatcher(pattern);
-    }
-    return matcher.accept(string);
-  }
-
-  @Override
-  public String toString() {
-    return pattern;
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) return true;
-    if (o == null || getClass() != o.getClass()) return false;
-    Glob glob = (Glob)o;
-    return Objects.equal(pattern, glob.pattern);
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hashCode(pattern);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/projectview/section/GlobSectionParser.java b/blaze-base/src/com/google/idea/blaze/base/projectview/section/GlobSectionParser.java
deleted file mode 100644
index 30e1a41..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/projectview/section/GlobSectionParser.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.projectview.section;
-
-import com.google.common.collect.ImmutableList;
-import com.google.idea.blaze.base.projectview.parser.ParseContext;
-import com.google.idea.blaze.base.projectview.parser.ProjectViewParser;
-
-import java.util.regex.PatternSyntaxException;
-
-/**
- * Parses glob sections.
- */
-public class GlobSectionParser extends ListSectionParser<Glob> {
-
-  public GlobSectionParser(SectionKey<Glob, ListSection<Glob>> key) {
-    super(key);
-  }
-
-  @Override
-  protected final void parseItem(ProjectViewParser parser,
-                                 ParseContext parseContext,
-                                 ImmutableList.Builder<Glob> items) {
-    String text = parseContext.current().text;
-    try {
-      Glob glob = new Glob(text);
-      items.add(glob);
-    }
-    catch (PatternSyntaxException e) {
-      parseContext.addError(e.getMessage());
-    }
-  }
-
-  @Override
-  protected final void printItem(Glob item, StringBuilder sb) {
-    sb.append(item.toString());
-  }
-
-  @Override
-  public ItemType getItemType() {
-    return ItemType.FileSystemItem;
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/projectview/section/LabelSectionParser.java b/blaze-base/src/com/google/idea/blaze/base/projectview/section/LabelSectionParser.java
deleted file mode 100644
index a34fdcc..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/projectview/section/LabelSectionParser.java
+++ /dev/null
@@ -1,58 +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.projectview.section;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.model.primitives.Label;
-import com.google.idea.blaze.base.projectview.parser.ParseContext;
-import com.google.idea.blaze.base.projectview.parser.ProjectViewParser;
-import com.google.idea.blaze.base.ui.BlazeValidationError;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.List;
-
-/**
- * Section of labels
- */
-public final class LabelSectionParser extends ListSectionParser<Label> {
-  public LabelSectionParser(SectionKey<Label, ListSection<Label>> key) {
-    super(key);
-  }
-
-  @Override
-  protected void parseItem(@NotNull ProjectViewParser parser,
-                           @NotNull ParseContext parseContext,
-                           @NotNull ImmutableList.Builder<Label> items) {
-    String text = parseContext.current().text;
-    List<BlazeValidationError> errors = Lists.newArrayList();
-    if (!Label.validate(text, errors)) {
-      parseContext.addErrors(errors);
-      return;
-    }
-    items.add(new Label(text));
-  }
-
-  @Override
-  protected void printItem(@NotNull Label item, @NotNull StringBuilder sb) {
-    sb.append(item.toString());
-  }
-
-  @Override
-  public ItemType getItemType() {
-    return ItemType.Label;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/projectview/section/ListSection.java b/blaze-base/src/com/google/idea/blaze/base/projectview/section/ListSection.java
deleted file mode 100644
index 4bf8eba..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/projectview/section/ListSection.java
+++ /dev/null
@@ -1,73 +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.projectview.section;
-
-import com.google.common.collect.ImmutableList;
-
-import javax.annotation.Nullable;
-import java.util.Collection;
-
-/**
- * List value. Eg.
- *
- * my_attribute:
- *  value0
- *  value1
- *  value2
- *  ...
- */
-public final class ListSection<T> extends Section<T> {
-  private static final long serialVersionUID = 1L;
-
-  private final ImmutableList<T> items;
-
-  ListSection(ImmutableList<T> items) {
-    this.items = items;
-  }
-
-  public Collection<T> items() {
-    return items;
-  }
-
-  public static <T> Builder<T> builder(SectionKey<T, ListSection<T>> sectionKey) {
-    return new Builder<T>(sectionKey, null);
-  }
-
-  public static <T> Builder<T> update(SectionKey<T, ListSection<T>> sectionKey, @Nullable ListSection<T> section) {
-    return new Builder<T>(sectionKey, section);
-  }
-
-  public static class Builder<T> extends SectionBuilder<T, ListSection<T>> {
-    private final ImmutableList.Builder<T> items = ImmutableList.builder();
-
-    public Builder(SectionKey<T, ListSection<T>> sectionKey, @Nullable ListSection<T> section) {
-      super(sectionKey);
-      if (section != null) {
-        items.addAll(section.items);
-      }
-    }
-
-    public final Builder<T> add(T item) {
-      items.add(item);
-      return this;
-    }
-
-    @Override
-    public final ListSection<T> build() {
-      return new ListSection<T>(items.build());
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/projectview/section/ListSectionParser.java b/blaze-base/src/com/google/idea/blaze/base/projectview/section/ListSectionParser.java
deleted file mode 100644
index 713bad2..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/projectview/section/ListSectionParser.java
+++ /dev/null
@@ -1,86 +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.projectview.section;
-
-import com.google.common.collect.ImmutableList;
-import com.google.idea.blaze.base.projectview.parser.ParseContext;
-import com.google.idea.blaze.base.projectview.parser.ProjectViewParser;
-
-import javax.annotation.Nullable;
-
-/**
- * List section parser base class.
- */
-public abstract class ListSectionParser<T> extends SectionParser {
-  protected ListSectionParser(SectionKey<T, ? extends ListSection<T>> key) {
-    super(key);
-  }
-
-  @Nullable
-  @Override
-  public final Section parse(ProjectViewParser parser, ParseContext parseContext) {
-    if (parseContext.atEnd()) {
-      return null;
-    }
-
-    String name = getName();
-
-    if (!parseContext.current().text.equals(name + ':')) {
-      return null;
-    }
-    parseContext.consume();
-
-    ImmutableList.Builder<T> builder = ImmutableList.builder();
-
-    while (!parseContext.atEnd() && parseContext.current().indent >= SectionParser.INDENT) {
-      parseItem(parser, parseContext, builder);
-      parseContext.consume();
-    }
-
-    ImmutableList<T> items = builder.build();
-    if (items.isEmpty()) {
-      parseContext.addError(String.format("Empty section: '%s'", name));
-    }
-
-    return new ListSection<T>(items);
-  }
-
-  @SuppressWarnings("unchecked")
-  @Override
-  public final void print(StringBuilder sb, Section section) {
-    ListSection<T> listSection = (ListSection<T>)section;
-
-    // Omit empty sections completely
-    if (listSection.items().isEmpty()) {
-      return;
-    }
-
-    sb.append(getName()).append(':').append('\n');
-    for (T item : listSection.items()) {
-      for (int i = 0; i < SectionParser.INDENT; ++i) {
-        sb.append(' ');
-      }
-      printItem(item, sb);
-      sb.append('\n');
-    }
-  }
-
-  protected abstract void parseItem(ProjectViewParser parser,
-                                    ParseContext parseContext,
-                                    ImmutableList.Builder<T> items);
-
-  protected abstract void printItem(T item, StringBuilder sb);
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/projectview/section/MetricsProjectSection.java b/blaze-base/src/com/google/idea/blaze/base/projectview/section/MetricsProjectSection.java
deleted file mode 100644
index ea43916..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/projectview/section/MetricsProjectSection.java
+++ /dev/null
@@ -1,50 +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.projectview.section;
-
-import com.google.common.base.CharMatcher;
-import com.google.idea.blaze.base.projectview.parser.ParseContext;
-import com.google.idea.blaze.base.projectview.parser.ProjectViewParser;
-
-import javax.annotation.Nullable;
-
-public class MetricsProjectSection {
-  public static final SectionKey<String, ScalarSection<String>> KEY =
-    SectionKey.of("metrics_project");
-  public static final SectionParser PARSER = new MetricsProjectSectionParser();
-
-  private static class MetricsProjectSectionParser extends ScalarSectionParser<String> {
-    public MetricsProjectSectionParser() {
-      super(KEY, ':');
-    }
-
-    @Nullable
-    @Override
-    protected String parseItem(ProjectViewParser parser, ParseContext parseContext, String rest) {
-      return CharMatcher.is('\"').trimFrom(rest);
-    }
-
-    @Override
-    protected void printItem(StringBuilder sb, String value) {
-      sb.append(value);
-    }
-
-    @Override
-    public ItemType getItemType() {
-      return ItemType.Other;
-    }
-  }
-}
\ No newline at end of file
diff --git a/blaze-base/src/com/google/idea/blaze/base/projectview/section/ScalarSection.java b/blaze-base/src/com/google/idea/blaze/base/projectview/section/ScalarSection.java
deleted file mode 100644
index d43ba63..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/projectview/section/ScalarSection.java
+++ /dev/null
@@ -1,55 +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.projectview.section;
-
-/**
- * Scalar value.
- */
-public final class ScalarSection<T> extends Section<T> {
-  private static final long serialVersionUID = 1L;
-
-  private final T value;
-
-  public ScalarSection(T value) {
-    this.value = value;
-  }
-
-  public T getValue() {
-    return value;
-  }
-
-  public static <T> Builder<T> builder(SectionKey<T, ScalarSection<T>> sectionKey) {
-    return new Builder<T>(sectionKey);
-  }
-
-  public static class Builder<T> extends SectionBuilder<T, ScalarSection<T>> {
-    private T value;
-
-    public Builder(SectionKey<T, ScalarSection<T>> sectionKey) {
-      super(sectionKey);
-    }
-
-    public Builder<T> set(T value) {
-      this.value = value;
-      return this;
-    }
-
-    @Override
-    public ScalarSection<T> build() {
-      return new ScalarSection<T>(value);
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/projectview/section/ScalarSectionParser.java b/blaze-base/src/com/google/idea/blaze/base/projectview/section/ScalarSectionParser.java
deleted file mode 100644
index 7942372..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/projectview/section/ScalarSectionParser.java
+++ /dev/null
@@ -1,77 +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.projectview.section;
-
-import com.google.idea.blaze.base.projectview.parser.ParseContext;
-import com.google.idea.blaze.base.projectview.parser.ProjectViewParser;
-
-import javax.annotation.Nullable;
-
-/**
- * Parses scalar values
- */
-public abstract class ScalarSectionParser<T> extends SectionParser {
-
-  private final char divider;
-
-  protected ScalarSectionParser(SectionKey<T, ? extends ScalarSection<T>> sectionKey,
-                                char divider) {
-    super(sectionKey);
-    this.divider = divider;
-  }
-
-  @Nullable
-  @Override
-  public final ScalarSection<T> parse(ProjectViewParser parser, ParseContext parseContext) {
-    if (parseContext.atEnd()) {
-      return null;
-    }
-
-    String name = getName();
-    ParseContext.Line line = parseContext.current();
-
-    if (!line.text.startsWith(name + divider)) {
-      return null;
-    }
-    String rest = line.text.substring(name.length() + 1).trim();
-    parseContext.consume();
-    T item = parseItem(parser, parseContext, rest);
-    return item != null ? new ScalarSection<T>(item) : null;
-  }
-
-  @SuppressWarnings("unchecked")
-  @Override
-  public final void print(StringBuilder sb, Section section) {
-    sb.append(getName()).append(divider);
-    if (divider != ' ') {
-      sb.append(' ');
-    }
-    printItem(sb, ((ScalarSection<T>)section).getValue());
-    sb.append('\n');
-  }
-
-  /**
-   * Used by psi-parser for validation.
-   */
-  public char getDivider() {
-    return divider;
-  }
-
-  @Nullable
-  protected abstract T parseItem(ProjectViewParser parser, ParseContext parseContext, String rest);
-
-  protected abstract void printItem(StringBuilder sb, T value);
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/projectview/section/Section.java b/blaze-base/src/com/google/idea/blaze/base/projectview/section/Section.java
deleted file mode 100644
index 6fc63d2..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/projectview/section/Section.java
+++ /dev/null
@@ -1,30 +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.projectview.section;
-
-import java.io.Serializable;
-
-/**
- * A section is a part of an project view file. For instance:
- *
- * directories
- *   java/com/a
- *   java/com/b
- *
- * Is a directory section with two items.
- */
-public abstract class Section<T> implements Serializable {
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/projectview/section/SectionBuilder.java b/blaze-base/src/com/google/idea/blaze/base/projectview/section/SectionBuilder.java
deleted file mode 100644
index 74ee9f5..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/projectview/section/SectionBuilder.java
+++ /dev/null
@@ -1,33 +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.projectview.section;
-
-/**
- * Builder base class.
- */
-public abstract class SectionBuilder<T, SectionType extends Section<T>> {
-  private final SectionKey<T, SectionType> sectionKey;
-
-  protected SectionBuilder(SectionKey<T, SectionType> sectionKey) {
-    this.sectionKey = sectionKey;
-  }
-
-  public abstract SectionType build();
-
-  public final SectionKey<T, SectionType> getSectionKey() {
-    return sectionKey;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/projectview/section/SectionKey.java b/blaze-base/src/com/google/idea/blaze/base/projectview/section/SectionKey.java
deleted file mode 100644
index 2c4084b..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/projectview/section/SectionKey.java
+++ /dev/null
@@ -1,53 +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.projectview.section;
-
-import com.google.common.base.Objects;
-
-import java.io.Serializable;
-
-/**
- * Key to a section of type T.
- */
-public final class SectionKey<T, SectionType extends Section<T>> implements Serializable {
-  private static final long serialVersionUID = 1L;
-  private final String name;
-
-  public SectionKey(String name) {
-    this.name = name;
-  }
-
-  public String getName() {
-    return name;
-  }
-
-  public static <T, SectionType extends Section<T>> SectionKey<T, SectionType> of(String name) {
-    return new SectionKey<T, SectionType>(name);
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) return true;
-    if (o == null || getClass() != o.getClass()) return false;
-    SectionKey<?, ?> that = (SectionKey<?, ?>)o;
-    return Objects.equal(name, that.name);
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hashCode(name);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/projectview/section/SectionParser.java b/blaze-base/src/com/google/idea/blaze/base/projectview/section/SectionParser.java
deleted file mode 100644
index d01cad6..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/projectview/section/SectionParser.java
+++ /dev/null
@@ -1,66 +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.projectview.section;
-
-import com.google.idea.blaze.base.projectview.parser.ParseContext;
-import com.google.idea.blaze.base.projectview.parser.ProjectViewParser;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * Parses a section.
- */
-public abstract class SectionParser {
-
-  public static final int INDENT = 2;
-
-  /**
-   * The type of item(s) in this section
-   */
-  public enum ItemType {
-    FileSystemItem, // files, directories, globs
-    Label, // a blaze label
-    Other, // anything else
-  }
-
-  private final SectionKey sectionKey;
-
-  protected SectionParser(SectionKey sectionKey) {
-    this.sectionKey = sectionKey;
-  }
-
-  public String getName() {
-    return sectionKey.getName();
-  }
-
-  public SectionKey getSectionKey() {
-    return sectionKey;
-  }
-
-  @Nullable
-  public abstract Section parse(ProjectViewParser parser, ParseContext parseContext);
-
-  public abstract void print(StringBuilder sb, Section section);
-
-  public boolean isDeprecated() {
-    return false;
-  }
-
-  /**
-   * The type of item(s) in this section.
-   */
-  public abstract ItemType getItemType();
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/projectview/section/sections/AdditionalLanguagesSection.java b/blaze-base/src/com/google/idea/blaze/base/projectview/section/sections/AdditionalLanguagesSection.java
deleted file mode 100644
index 692d385..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/projectview/section/sections/AdditionalLanguagesSection.java
+++ /dev/null
@@ -1,63 +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.projectview.section.sections;
-
-import com.google.common.collect.ImmutableList;
-import com.google.idea.blaze.base.model.primitives.LanguageClass;
-import com.google.idea.blaze.base.projectview.parser.ParseContext;
-import com.google.idea.blaze.base.projectview.parser.ProjectViewParser;
-import com.google.idea.blaze.base.projectview.section.ListSection;
-import com.google.idea.blaze.base.projectview.section.ListSectionParser;
-import com.google.idea.blaze.base.projectview.section.SectionKey;
-import com.google.idea.blaze.base.projectview.section.SectionParser;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Allows users to set the rule classes they want to be imported
- */
-public class AdditionalLanguagesSection {
-  public static final SectionKey<LanguageClass, ListSection<LanguageClass>> KEY = SectionKey.of("additional_languages");
-  public static final SectionParser PARSER = new AdditionalLanguagesSectionParser();
-
-  private static class AdditionalLanguagesSectionParser extends ListSectionParser<LanguageClass> {
-    public AdditionalLanguagesSectionParser() {
-      super(KEY);
-    }
-
-    @Override
-    protected void parseItem(@NotNull ProjectViewParser parser,
-                             @NotNull ParseContext parseContext,
-                             @NotNull ImmutableList.Builder<LanguageClass> items) {
-      String text = parseContext.current().text;
-      LanguageClass language = LanguageClass.fromString(text);
-      if (language == null) {
-        parseContext.addError("Invalid language: " + text);
-        return;
-      }
-      items.add(language);
-    }
-
-    @Override
-    protected void printItem(@NotNull LanguageClass item, @NotNull StringBuilder sb) {
-      sb.append(item.getName());
-    }
-
-    @Override
-    public ItemType getItemType() {
-      return ItemType.Other;
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/projectview/section/sections/BuildFlagsSection.java b/blaze-base/src/com/google/idea/blaze/base/projectview/section/sections/BuildFlagsSection.java
deleted file mode 100644
index d48cc0c..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/projectview/section/sections/BuildFlagsSection.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.projectview.section.sections;
-
-import com.google.common.collect.ImmutableList;
-import com.google.idea.blaze.base.projectview.parser.ParseContext;
-import com.google.idea.blaze.base.projectview.parser.ProjectViewParser;
-import com.google.idea.blaze.base.projectview.section.ListSection;
-import com.google.idea.blaze.base.projectview.section.ListSectionParser;
-import com.google.idea.blaze.base.projectview.section.SectionKey;
-import com.google.idea.blaze.base.projectview.section.SectionParser;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Section for blaze_flags
- */
-public class BuildFlagsSection {
-  public static final SectionKey<String, ListSection<String>> KEY = SectionKey.of("build_flags");
-  public static final SectionParser PARSER = new BuildFlagsSectionParser();
-
-  public static class BuildFlagsSectionParser extends ListSectionParser<String> {
-    protected BuildFlagsSectionParser() {
-      super(KEY);
-    }
-
-    @Override
-    protected void parseItem(@NotNull ProjectViewParser parser,
-                             @NotNull ParseContext parseContext,
-                             @NotNull ImmutableList.Builder<String> items) {
-      String text = parseContext.current().text;
-      items.add(text);
-    }
-
-    @Override
-    protected void printItem(@NotNull String item, @NotNull StringBuilder sb) {
-      sb.append(item);
-    }
-
-    @Override
-    public ItemType getItemType() {
-      return ItemType.Other;
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/projectview/section/sections/DirectoryEntry.java b/blaze-base/src/com/google/idea/blaze/base/projectview/section/sections/DirectoryEntry.java
deleted file mode 100644
index 924c487..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/projectview/section/sections/DirectoryEntry.java
+++ /dev/null
@@ -1,69 +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.projectview.section.sections;
-
-import com.google.common.base.Objects;
-import com.google.idea.blaze.base.model.primitives.WorkspacePath;
-
-import java.io.Serializable;
-
-/**
- * An entry in the directory section.
- */
-public class DirectoryEntry implements Serializable {
-  private static final long serialVersionUID = 1L;
-  public final WorkspacePath directory;
-  public final boolean included;
-
-  public DirectoryEntry(WorkspacePath directory, boolean included) {
-    this.directory = directory;
-    this.included = included;
-  }
-
-  public static DirectoryEntry include(WorkspacePath directory) {
-    return new DirectoryEntry(directory, true);
-  }
-
-  public static DirectoryEntry exclude(WorkspacePath directory) {
-    return new DirectoryEntry(directory, false);
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) return true;
-    if (o == null || getClass() != o.getClass()) return false;
-    DirectoryEntry that = (DirectoryEntry)o;
-    return Objects.equal(included, that.included)
-           && Objects.equal(directory, that.directory);
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hashCode(directory, included);
-  }
-
-  @Override
-  public String toString() {
-    return (included ? "" : "-") + directoryString();
-  }
-
-  private String directoryString() {
-    if (directory.isWorkspaceRoot()) {
-      return ".";
-    }
-    return directory.relativePath();
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/projectview/section/sections/DirectorySection.java b/blaze-base/src/com/google/idea/blaze/base/projectview/section/sections/DirectorySection.java
deleted file mode 100644
index b8e3329..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/projectview/section/sections/DirectorySection.java
+++ /dev/null
@@ -1,73 +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.projectview.section.sections;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.model.primitives.WorkspacePath;
-import com.google.idea.blaze.base.projectview.parser.ParseContext;
-import com.google.idea.blaze.base.projectview.parser.ProjectViewParser;
-import com.google.idea.blaze.base.projectview.section.ListSection;
-import com.google.idea.blaze.base.projectview.section.ListSectionParser;
-import com.google.idea.blaze.base.projectview.section.SectionKey;
-import com.google.idea.blaze.base.projectview.section.SectionParser;
-import com.google.idea.blaze.base.ui.BlazeValidationError;
-import com.intellij.util.PathUtil;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.List;
-
-/**
- * "directories" section.
- */
-public class DirectorySection {
-  public static final SectionKey<DirectoryEntry, ListSection<DirectoryEntry>> KEY = SectionKey.of("directories");
-  public static final SectionParser PARSER = new DirectorySectionParser();
-
-  private static class DirectorySectionParser extends ListSectionParser<DirectoryEntry> {
-    public DirectorySectionParser() {
-      super(KEY);
-    }
-
-    @Override
-    protected void parseItem(@NotNull ProjectViewParser parser,
-                             @NotNull ParseContext parseContext,
-                             @NotNull ImmutableList.Builder<DirectoryEntry> items) {
-      String text = parseContext.current().text;
-      boolean excluded = text.startsWith("-");
-      text = excluded ? text.substring(1) : text;
-
-      text = PathUtil.getCanonicalPath(text);
-
-      List<BlazeValidationError> errors = Lists.newArrayList();
-      if (WorkspacePath.validate(text, errors)) {
-        items.add(new DirectoryEntry(new WorkspacePath(text), !excluded));
-      } else {
-        parseContext.addErrors(errors);
-      }
-    }
-
-    @Override
-    protected void printItem(@NotNull DirectoryEntry item, @NotNull StringBuilder sb) {
-      sb.append(item.toString());
-    }
-
-    @Override
-    public ItemType getItemType() {
-      return ItemType.FileSystemItem;
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/projectview/section/sections/ExcludeTargetSection.java b/blaze-base/src/com/google/idea/blaze/base/projectview/section/sections/ExcludeTargetSection.java
deleted file mode 100644
index 8376747..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/projectview/section/sections/ExcludeTargetSection.java
+++ /dev/null
@@ -1,30 +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.projectview.section.sections;
-
-import com.google.idea.blaze.base.model.primitives.Label;
-import com.google.idea.blaze.base.projectview.section.LabelSectionParser;
-import com.google.idea.blaze.base.projectview.section.ListSection;
-import com.google.idea.blaze.base.projectview.section.SectionKey;
-import com.google.idea.blaze.base.projectview.section.SectionParser;
-
-/**
- * Excludes a target.
- */
-public class ExcludeTargetSection {
-  public static final SectionKey<Label, ListSection<Label>> KEY = SectionKey.of("exclude_target");
-  public static final SectionParser PARSER = new LabelSectionParser(KEY);
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/projectview/section/sections/ExcludedSourceSection.java b/blaze-base/src/com/google/idea/blaze/base/projectview/section/sections/ExcludedSourceSection.java
deleted file mode 100644
index da17d6b..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/projectview/section/sections/ExcludedSourceSection.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.base.projectview.section.sections;
-
-import com.google.idea.blaze.base.projectview.section.*;
-
-/**
- * Section for excluding source files.
- */
-@Deprecated
-public class ExcludedSourceSection {
-  public static final SectionKey<Glob, ListSection<Glob>> KEY = SectionKey.of("excluded_sources");
-  public static final SectionParser PARSER = new GlobSectionParser(KEY) {
-    @Override
-    public boolean isDeprecated() {
-      return true;
-    }
-  };
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/projectview/section/sections/ImportSection.java b/blaze-base/src/com/google/idea/blaze/base/projectview/section/sections/ImportSection.java
deleted file mode 100644
index 53a989d..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/projectview/section/sections/ImportSection.java
+++ /dev/null
@@ -1,75 +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.projectview.section.sections;
-
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.model.primitives.WorkspacePath;
-import com.google.idea.blaze.base.projectview.parser.ParseContext;
-import com.google.idea.blaze.base.projectview.parser.ProjectViewParser;
-import com.google.idea.blaze.base.projectview.section.ScalarSection;
-import com.google.idea.blaze.base.projectview.section.ScalarSectionParser;
-import com.google.idea.blaze.base.projectview.section.SectionKey;
-import com.google.idea.blaze.base.projectview.section.SectionParser;
-import com.google.idea.blaze.base.ui.BlazeValidationError;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.io.File;
-import java.util.List;
-
-/**
- * "import" section.
- */
-public class ImportSection {
-  public static final SectionKey<WorkspacePath, ScalarSection<WorkspacePath>> KEY = SectionKey.of("import");
-  public static final SectionParser PARSER = new ImportSectionParser();
-
-  private static class ImportSectionParser extends ScalarSectionParser<WorkspacePath> {
-    public ImportSectionParser() {
-      super(KEY, ' ');
-    }
-
-    @Override
-    @Nullable
-    protected WorkspacePath parseItem(@NotNull ProjectViewParser parser, @NotNull ParseContext parseContext, @NotNull String text) {
-      List<BlazeValidationError> errors = Lists.newArrayList();
-      if (WorkspacePath.validate(text, errors)) {
-        WorkspacePath workspacePath = new WorkspacePath(text);
-        if (parser.isRecursive()) {
-          File projectViewFile = parseContext.getWorkspacePathResolver().resolveToFile(workspacePath);
-          if (projectViewFile != null) {
-            parser.parseProjectView(projectViewFile);
-          } else {
-            parseContext.addError("Could not resolve import: " + workspacePath);
-          }
-        }
-        return workspacePath;
-      }
-      parseContext.addErrors(errors);
-      return null;
-    }
-
-    @Override
-    protected void printItem(@NotNull StringBuilder sb, @NotNull WorkspacePath section) {
-      sb.append(section.toString());
-    }
-
-    @Override
-    public ItemType getItemType() {
-      return ItemType.FileSystemItem;
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/projectview/section/sections/ImportTargetOutputSection.java b/blaze-base/src/com/google/idea/blaze/base/projectview/section/sections/ImportTargetOutputSection.java
deleted file mode 100644
index 13824e7..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/projectview/section/sections/ImportTargetOutputSection.java
+++ /dev/null
@@ -1,30 +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.projectview.section.sections;
-
-import com.google.idea.blaze.base.model.primitives.Label;
-import com.google.idea.blaze.base.projectview.section.LabelSectionParser;
-import com.google.idea.blaze.base.projectview.section.ListSection;
-import com.google.idea.blaze.base.projectview.section.SectionKey;
-import com.google.idea.blaze.base.projectview.section.SectionParser;
-
-/**
- * Forces target output import of mentioned targets.
- */
-public class ImportTargetOutputSection {
-  public static final SectionKey<Label, ListSection<Label>> KEY = SectionKey.of("import_target_output");
-  public static final SectionParser PARSER = new LabelSectionParser(KEY);
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/projectview/section/sections/Sections.java b/blaze-base/src/com/google/idea/blaze/base/projectview/section/sections/Sections.java
deleted file mode 100644
index 9d3784e..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/projectview/section/sections/Sections.java
+++ /dev/null
@@ -1,59 +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.projectview.section.sections;
-
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.projectview.section.MetricsProjectSection;
-import com.google.idea.blaze.base.projectview.section.SectionParser;
-import com.google.idea.blaze.base.sync.BlazeSyncPlugin;
-
-import java.util.List;
-import java.util.stream.Collectors;
-
-/**
- * List of available sections.
- */
-public class Sections {
-  // Put these in the order that you want them to appear in the output
-  private static final List<SectionParser> PARSERS = Lists.newArrayList(
-    ImportSection.PARSER,
-    DirectorySection.PARSER,
-    TargetSection.PARSER,
-    WorkspaceTypeSection.PARSER,
-    AdditionalLanguagesSection.PARSER,
-    TestSourceSection.PARSER,
-    BuildFlagsSection.PARSER,
-    ImportTargetOutputSection.PARSER,
-    ExcludeTargetSection.PARSER,
-    ExcludedSourceSection.PARSER,
-    MetricsProjectSection.PARSER
-  );
-
-  public static List<SectionParser> getParsers() {
-    List<SectionParser> parsers = Lists.newArrayList(PARSERS);
-    for (BlazeSyncPlugin syncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) {
-      parsers.addAll(syncPlugin.getSections());
-    }
-    return parsers;
-  }
-
-  public static List<SectionParser> getUndeprecatedParsers() {
-    return getParsers().stream()
-      .filter(p -> !p.isDeprecated())
-      .collect(Collectors.toList());
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/projectview/section/sections/TargetSection.java b/blaze-base/src/com/google/idea/blaze/base/projectview/section/sections/TargetSection.java
deleted file mode 100644
index 8a737fb..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/projectview/section/sections/TargetSection.java
+++ /dev/null
@@ -1,58 +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.projectview.section.sections;
-
-import com.google.common.collect.ImmutableList;
-import com.google.idea.blaze.base.model.primitives.TargetExpression;
-import com.google.idea.blaze.base.projectview.parser.ParseContext;
-import com.google.idea.blaze.base.projectview.parser.ProjectViewParser;
-import com.google.idea.blaze.base.projectview.section.ListSection;
-import com.google.idea.blaze.base.projectview.section.ListSectionParser;
-import com.google.idea.blaze.base.projectview.section.SectionKey;
-import com.google.idea.blaze.base.projectview.section.SectionParser;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * "targets" section.
- */
-public class TargetSection {
-  public static final SectionKey<TargetExpression, ListSection<TargetExpression>> KEY = SectionKey.of("targets");
-  public static final SectionParser PARSER = new TargetSectionParser();
-
-  private static class TargetSectionParser extends ListSectionParser<TargetExpression> {
-    public TargetSectionParser() {
-      super(KEY);
-    }
-
-    @Override
-    protected void parseItem(@NotNull ProjectViewParser parser,
-                             @NotNull ParseContext parseContext,
-                             @NotNull ImmutableList.Builder<TargetExpression> items) {
-      String text = parseContext.current().text;
-      items.add(TargetExpression.fromString(text));
-    }
-
-    @Override
-    protected void printItem(@NotNull TargetExpression item, @NotNull StringBuilder sb) {
-      sb.append(item.toString());
-    }
-
-    @Override
-    public ItemType getItemType() {
-      return ItemType.Label;
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/projectview/section/sections/TestSourceSection.java b/blaze-base/src/com/google/idea/blaze/base/projectview/section/sections/TestSourceSection.java
deleted file mode 100644
index 4cbc6bc..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/projectview/section/sections/TestSourceSection.java
+++ /dev/null
@@ -1,26 +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.projectview.section.sections;
-
-import com.google.idea.blaze.base.projectview.section.*;
-
-/**
- * Section for configuring test sources.
- */
-public class TestSourceSection {
-  public static final SectionKey<Glob, ListSection<Glob>> KEY = SectionKey.of("test_sources");
-  public static final SectionParser PARSER = new GlobSectionParser(KEY);
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/projectview/section/sections/WorkspaceTypeSection.java b/blaze-base/src/com/google/idea/blaze/base/projectview/section/sections/WorkspaceTypeSection.java
deleted file mode 100644
index abab2ed..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/projectview/section/sections/WorkspaceTypeSection.java
+++ /dev/null
@@ -1,65 +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.projectview.section.sections;
-
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.model.primitives.WorkspaceType;
-import com.google.idea.blaze.base.projectview.parser.ParseContext;
-import com.google.idea.blaze.base.projectview.parser.ProjectViewParser;
-import com.google.idea.blaze.base.projectview.section.ScalarSection;
-import com.google.idea.blaze.base.projectview.section.ScalarSectionParser;
-import com.google.idea.blaze.base.projectview.section.SectionKey;
-import com.google.idea.blaze.base.projectview.section.SectionParser;
-import com.google.idea.blaze.base.ui.BlazeValidationError;
-
-import javax.annotation.Nullable;
-import java.util.List;
-
-/**
- * The type of your workspace.
- */
-public class WorkspaceTypeSection {
-  public static final SectionKey<WorkspaceType, ScalarSection<WorkspaceType>> KEY = SectionKey.of("workspace_type");
-  public static final SectionParser PARSER = new WorkspaceTypeSectionParser();
-
-  private static class WorkspaceTypeSectionParser extends ScalarSectionParser<WorkspaceType> {
-    public WorkspaceTypeSectionParser() {
-      super(KEY, ':');
-    }
-
-    @Override
-    @Nullable
-    protected WorkspaceType parseItem(ProjectViewParser parser, ParseContext parseContext, String text) {
-      List<BlazeValidationError> errors = Lists.newArrayList();
-      WorkspaceType workspaceType = WorkspaceType.fromString(text);
-      if (workspaceType == null) {
-        parseContext.addError("Invalid workspace type: " + text);
-      }
-      parseContext.addErrors(errors);
-      return workspaceType;
-    }
-
-    @Override
-    protected void printItem(StringBuilder sb, WorkspaceType item) {
-      sb.append(item.toString());
-    }
-
-    @Override
-    public ItemType getItemType() {
-      return ItemType.Other;
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/rulemaps/ReverseDependencyMap.java b/blaze-base/src/com/google/idea/blaze/base/rulemaps/ReverseDependencyMap.java
deleted file mode 100644
index 84e073b..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/rulemaps/ReverseDependencyMap.java
+++ /dev/null
@@ -1,39 +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.model.primitives.Label;
-
-import java.util.Map;
-
-public class ReverseDependencyMap {
-  public static ImmutableMultimap<Label, Label> createRdepsMap(Map<Label, RuleIdeInfo> ruleMap) {
-    ImmutableMultimap.Builder<Label, Label> builder = ImmutableMultimap.builder();
-    for (Map.Entry<Label, RuleIdeInfo> entry : ruleMap.entrySet()) {
-      Label label = entry.getKey();
-      RuleIdeInfo ruleIdeInfo = entry.getValue();
-      for (Label dep : Iterables.concat(ruleIdeInfo.dependencies, ruleIdeInfo.runtimeDeps)) {
-        if (ruleMap.containsKey(dep)) {
-          builder.put(dep, label);
-        }
-      }
-    }
-    return builder.build();
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/rulemaps/SourceToRuleMap.java b/blaze-base/src/com/google/idea/blaze/base/rulemaps/SourceToRuleMap.java
deleted file mode 100644
index 2b10c82..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/rulemaps/SourceToRuleMap.java
+++ /dev/null
@@ -1,35 +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.ImmutableCollection;
-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 {
-
-  static SourceToRuleMap getInstance(Project project) {
-    return ServiceManager.getService(project, SourceToRuleMap.class);
-  }
-
-  ImmutableCollection<Label> getTargetsForSourceFile(File file);
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/rulemaps/SourceToRuleMapImpl.java b/blaze-base/src/com/google/idea/blaze/base/rulemaps/SourceToRuleMapImpl.java
deleted file mode 100644
index c330077..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/rulemaps/SourceToRuleMapImpl.java
+++ /dev/null
@@ -1,93 +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.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.model.BlazeProjectData;
-import com.google.idea.blaze.base.model.primitives.Label;
-import com.google.idea.blaze.base.projectview.ProjectViewSet;
-import com.google.idea.blaze.base.settings.BlazeImportSettings;
-import com.google.idea.blaze.base.sync.SyncListener;
-import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
-import com.intellij.openapi.components.ServiceManager;
-import com.intellij.openapi.project.Project;
-
-import javax.annotation.Nullable;
-import java.io.File;
-
-/**
- * Maps source files to their respective targets
- */
-public class SourceToRuleMapImpl implements SourceToRuleMap {
-  private final Project project;
-  private ImmutableMultimap<File, Label> sourceToTargetMap;
-
-  public static SourceToRuleMapImpl getImpl(Project project) {
-    return (SourceToRuleMapImpl) ServiceManager.getService(project, SourceToRuleMap.class);
-  }
-
-  public SourceToRuleMapImpl(Project project) {
-    this.project = project;
-  }
-
-  @Override
-  public ImmutableCollection<Label> getTargetsForSourceFile(File file) {
-    ImmutableMultimap<File, Label> sourceToTargetMap = getSourceToTargetMap();
-    return sourceToTargetMap != null ? sourceToTargetMap.get(file) : ImmutableList.of();
-  }
-
-  @Nullable
-  private synchronized ImmutableMultimap<File, Label> getSourceToTargetMap() {
-    if (this.sourceToTargetMap == null) {
-      this.sourceToTargetMap = initSourceToTargetMap();
-    }
-    return this.sourceToTargetMap;
-  }
-
-  private synchronized void clearSourceToTargetMap() {
-    this.sourceToTargetMap = null;
-  }
-
-  @Nullable
-  private ImmutableMultimap<File, Label> initSourceToTargetMap() {
-    BlazeProjectData blazeProjectData = BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
-    if (blazeProjectData == null) {
-      return null;
-    }
-    ImmutableMultimap.Builder<File, Label> sourceToTargetMap = ImmutableMultimap.builder();
-    for (RuleIdeInfo rule : blazeProjectData.ruleMap.values()) {
-      Label label = rule.label;
-      for (ArtifactLocation sourceArtifact : rule.sources) {
-        sourceToTargetMap.put(sourceArtifact.getFile(), label);
-      }
-    }
-    return sourceToTargetMap.build();
-  }
-
-  static class ClearSourceToTargetMap extends SyncListener.Adapter {
-    @Override
-    public void onSyncComplete(Project project,
-                               BlazeImportSettings importSettings,
-                               ProjectViewSet projectViewSet,
-                               BlazeProjectData blazeProjectData) {
-      getImpl(project).clearSourceToTargetMap();
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/run/BlazeRuleConfigurationFactory.java b/blaze-base/src/com/google/idea/blaze/base/run/BlazeRuleConfigurationFactory.java
deleted file mode 100644
index 87ab7c7..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/run/BlazeRuleConfigurationFactory.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.run;
-
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
-import com.intellij.execution.RunManager;
-import com.intellij.execution.RunnerAndConfigurationSettings;
-import com.intellij.openapi.extensions.ExtensionPointName;
-
-/**
- * A factory creating run configurations based on Blaze rules.
- */
-public interface BlazeRuleConfigurationFactory {
-  ExtensionPointName<BlazeRuleConfigurationFactory> EP_NAME =
-    ExtensionPointName.create("com.google.idea.blaze.RuleConfigurationFactory");
-
-  /**
-   * Returns whether this factory can handle a rule.
-   */
-  boolean handlesRule(WorkspaceLanguageSettings workspaceLanguageSettings, RuleIdeInfo rule);
-
-  /** Constructs and initializes a configuration for the given rule. */
-  RunnerAndConfigurationSettings createForRule(RunManager runManager, RuleIdeInfo rule);
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/run/BlazeRunConfiguration.java b/blaze-base/src/com/google/idea/blaze/base/run/BlazeRunConfiguration.java
deleted file mode 100644
index f9b6865..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/run/BlazeRunConfiguration.java
+++ /dev/null
@@ -1,27 +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.model.primitives.TargetExpression;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * Marker interface for all run configurations
- */
-public interface BlazeRunConfiguration {
-  @Nullable
-  TargetExpression getTarget();
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/run/BlazeRunConfigurationSyncListener.java b/blaze-base/src/com/google/idea/blaze/base/run/BlazeRunConfigurationSyncListener.java
deleted file mode 100755
index 4f81112..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/run/BlazeRunConfigurationSyncListener.java
+++ /dev/null
@@ -1,119 +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.common.collect.Sets;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.model.primitives.Label;
-import com.google.idea.blaze.base.model.primitives.TargetExpression;
-import com.google.idea.blaze.base.model.BlazeProjectData;
-import com.google.idea.blaze.base.projectview.ProjectViewSet;
-import com.google.idea.blaze.base.projectview.section.sections.TargetSection;
-import com.google.idea.blaze.base.settings.BlazeImportSettings;
-import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
-import com.google.idea.blaze.base.sync.SyncListener;
-import com.intellij.execution.RunManager;
-import com.intellij.execution.RunnerAndConfigurationSettings;
-import com.intellij.execution.configurations.RunConfiguration;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.project.Project;
-import com.intellij.util.ui.UIUtil;
-
-import java.util.List;
-import java.util.Set;
-
-/**
- * Creates run configurations for modules imported from
- * {@link com.android.builder.model.AndroidProject}s.
- */
-public class BlazeRunConfigurationSyncListener implements SyncListener {
-  private static Logger log = Logger.getInstance(BlazeRunConfigurationSyncListener.class);
-
-  @Override
-  public void onSyncStart(Project project) {
-  }
-
-  @Override
-  public void afterSync(Project project,
-                        boolean successful) {
-  }
-
-  @Override
-  public void onSyncComplete(
-    Project project,
-    BlazeImportSettings importSettings,
-    ProjectViewSet projectViewSet,
-    BlazeProjectData blazeProjectData) {
-
-    UIUtil.invokeAndWaitIfNeeded((Runnable)() -> {
-      Set<Label> labelsWithConfigs = labelsWithConfigs(project);
-      Set<TargetExpression> targetExpressions = Sets.newHashSet(projectViewSet.listItems(TargetSection.KEY));
-      for (RuleIdeInfo rule : blazeProjectData.ruleMap.values()) {
-        maybeAddRunConfiguration(project, blazeProjectData.workspaceLanguageSettings, targetExpressions, labelsWithConfigs, rule);
-      }
-    });
-  }
-
-  /** Collects a set of all the Blaze labels that have an associated run configuration. */
-  private static Set<Label> labelsWithConfigs(Project project) {
-    List<RunConfiguration> configurations =
-      RunManager.getInstance(project).getAllConfigurationsList();
-    Set<Label> labelsWithConfigs = Sets.newHashSet();
-    for (RunConfiguration configuration : configurations) {
-      if (configuration instanceof BlazeRunConfiguration) {
-        BlazeRunConfiguration blazeRunConfiguration =
-          (BlazeRunConfiguration) configuration;
-        TargetExpression target = blazeRunConfiguration.getTarget();
-        if (target instanceof Label) {
-          labelsWithConfigs.add((Label)target);
-        }
-      }
-    }
-    return labelsWithConfigs;
-  }
-
-  /**
-   * Adds a run configuration for an android_binary target if there is not already a configuration
-   * for that target.
-   */
-  private static void maybeAddRunConfiguration(
-    Project project,
-    WorkspaceLanguageSettings workspaceLanguageSettings,
-    Set<TargetExpression> importTargets,
-    Set<Label> labelsWithConfigs,
-    RuleIdeInfo rule) {
-    Label label = rule.label;
-    // We only auto-generate configurations for rules listed in the project view.
-    if (!importTargets.contains(label) ||
-        labelsWithConfigs.contains(label)) {
-      return;
-    }
-    labelsWithConfigs.add(label);
-    final RunManager runManager = RunManager.getInstance(project);
-
-    for (BlazeRuleConfigurationFactory configurationFactory : BlazeRuleConfigurationFactory.EP_NAME.getExtensions()) {
-      if (configurationFactory.handlesRule(workspaceLanguageSettings, rule)) {
-        final RunnerAndConfigurationSettings settings = configurationFactory.createForRule(runManager, rule);
-        runManager.addConfiguration(settings, false /* isShared */);
-        if (runManager.getSelectedConfiguration() == null) {
-          // TODO(joshgiles): Better strategy for picking initially selected config.
-          runManager.setSelectedConfiguration(settings);
-        }
-        break;
-      }
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/run/TestRuleFinder.java b/blaze-base/src/com/google/idea/blaze/base/run/TestRuleFinder.java
deleted file mode 100644
index 28e7da2..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/run/TestRuleFinder.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.base.run;
-
-import com.google.idea.blaze.base.ideinfo.TestIdeInfo;
-import com.google.idea.blaze.base.model.primitives.Label;
-import com.intellij.openapi.components.ServiceManager;
-import com.intellij.openapi.project.Project;
-
-import javax.annotation.Nullable;
-import java.io.File;
-import java.util.Collection;
-
-/**
- * Locates test rules for a given file.
- */
-public interface TestRuleFinder {
-  static TestRuleFinder getInstance(Project project) {
-    return ServiceManager.getService(project, TestRuleFinder.class);
-  }
-
-  Collection<Label> testTargetsForSourceFile(File sourceFile, @Nullable TestIdeInfo.TestSize testSize);
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/run/processhandler/LineProcessingProcessAdapter.java b/blaze-base/src/com/google/idea/blaze/base/run/processhandler/LineProcessingProcessAdapter.java
deleted file mode 100644
index 261d38b..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/run/processhandler/LineProcessingProcessAdapter.java
+++ /dev/null
@@ -1,45 +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.processhandler;
-
-import com.google.common.base.Charsets;
-import com.google.idea.blaze.base.async.process.LineProcessingOutputStream;
-import com.intellij.execution.process.ProcessAdapter;
-import com.intellij.execution.process.ProcessEvent;
-import com.intellij.openapi.util.Key;
-
-import java.io.IOException;
-
-public final class LineProcessingProcessAdapter extends ProcessAdapter {
-  private final LineProcessingOutputStream myOutputStream;
-
-  public LineProcessingProcessAdapter(LineProcessingOutputStream outputStream) {
-    myOutputStream = outputStream;
-  }
-
-  @Override
-  public void onTextAvailable(ProcessEvent event, Key outputType) {
-    String text = event.getText();
-    if (text != null) {
-      try {
-        myOutputStream.write(text.getBytes(Charsets.UTF_8));
-      }
-      catch (IOException e) {
-        // Ignore -- cannot happen
-      }
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/run/processhandler/ScopedBlazeProcessHandler.java b/blaze-base/src/com/google/idea/blaze/base/run/processhandler/ScopedBlazeProcessHandler.java
deleted file mode 100644
index abf486e..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/run/processhandler/ScopedBlazeProcessHandler.java
+++ /dev/null
@@ -1,104 +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.processhandler;
-
-import com.google.common.collect.ImmutableList;
-import com.google.idea.blaze.base.command.BlazeCommand;
-import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.intellij.execution.ExecutionException;
-import com.intellij.execution.configurations.GeneralCommandLine;
-import com.intellij.execution.process.*;
-import com.intellij.openapi.util.Key;
-
-/**
- * Scoped process handler.
- *
- * A context is created during construction and is ended when the process is terminated.
- */
-public final class ScopedBlazeProcessHandler extends KillableColoredProcessHandler {
-  /**
-   * Methods to give the caller of {@link ScopedBlazeProcessHandler} hooks after the context is created.
-   */
-  public interface ScopedProcessHandlerDelegate {
-    /**
-     * This method is called when the process starts. Any context setup (like pushing scopes on the context) should be done here.
-     */
-    void onBlazeContextStart(BlazeContext context);
-
-    /**
-     * Get a list of process listeners to add to the process.
-     */
-    ImmutableList<ProcessListener> createProcessListeners(BlazeContext context);
-
-  }
-
-  private final ScopedProcessHandlerDelegate scopedProcessHandlerDelegate;
-  private final BlazeContext context;
-
-  /**
-   * Construct a process handler and a context to be used for the life of the process.
-   *
-   * @param blazeCommand the blaze command to run
-   * @param workspaceRoot workspace root
-   * @param scopedProcessHandlerDelegate delegate methods that will be run with the process's context.
-   * @throws ExecutionException
-   */
-  public ScopedBlazeProcessHandler(
-    BlazeCommand blazeCommand,
-    WorkspaceRoot workspaceRoot,
-    ScopedProcessHandlerDelegate scopedProcessHandlerDelegate) throws ExecutionException {
-    super(new GeneralCommandLine(blazeCommand.toList()).withWorkDirectory(workspaceRoot.directory().getPath()));
-
-    this.scopedProcessHandlerDelegate = scopedProcessHandlerDelegate;
-    this.context = new BlazeContext();
-    // The context is released in the ScopedProcessHandlerListener.
-    this.context.hold();
-
-    for (ProcessListener processListener : scopedProcessHandlerDelegate.createProcessListeners(context)) {
-      addProcessListener(processListener);
-    }
-    addProcessListener(new ScopedProcessHandlerListener());
-  }
-
-  @Override
-  public void coloredTextAvailable(String text, Key attributes) {
-    // Change blaze's stderr output to normal color, otherwise
-    // test output looks red
-    if (attributes == ProcessOutputTypes.STDERR) {
-      attributes = ProcessOutputTypes.STDOUT;
-    }
-
-    super.coloredTextAvailable(text, attributes);
-  }
-
-  /**
-   * Handle the {@link BlazeContext} held in a {@link ScopedBlazeProcessHandler}. This class will take care of calling methods when the
-   * process starts and freeing the context when the process terminates.
-   */
-  private class ScopedProcessHandlerListener extends ProcessAdapter {
-
-    @Override
-    public void startNotified(ProcessEvent event) {
-      scopedProcessHandlerDelegate.onBlazeContextStart(context);
-    }
-
-    @Override
-    public void processWillTerminate(ProcessEvent event, boolean willBeDestroyed) {
-      context.release();
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/run/rulefinder/RuleFinder.java b/blaze-base/src/com/google/idea/blaze/base/run/rulefinder/RuleFinder.java
deleted file mode 100644
index 89441f2..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/run/rulefinder/RuleFinder.java
+++ /dev/null
@@ -1,77 +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 javax.annotation.Nullable;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * 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, input -> input.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, input -> input.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/blaze-base/src/com/google/idea/blaze/base/run/rulefinder/RuleFinderImpl.java b/blaze-base/src/com/google/idea/blaze/base/run/rulefinder/RuleFinderImpl.java
deleted file mode 100644
index b07943c..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/run/rulefinder/RuleFinderImpl.java
+++ /dev/null
@@ -1,47 +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.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.model.BlazeProjectData;
-import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
-import com.intellij.openapi.project.Project;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.List;
-
-/**
- * Implementation of RuleFinder.
- */
-class RuleFinderImpl extends RuleFinder {
-  @Override
-  public List<RuleIdeInfo> findRules(@NotNull Project project, @NotNull Predicate<RuleIdeInfo> predicate) {
-    BlazeProjectData projectData = BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
-    if (projectData == null) {
-      return ImmutableList.of();
-    }
-
-    ImmutableList.Builder<RuleIdeInfo> resultList = ImmutableList.builder();
-    for (RuleIdeInfo rule : projectData.ruleMap.values()) {
-      if (predicate.apply(rule)) {
-        resultList.add(rule);
-      }
-    }
-    return resultList.build();
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/run/testmap/TestRuleFinderImpl.java b/blaze-base/src/com/google/idea/blaze/base/run/testmap/TestRuleFinderImpl.java
deleted file mode 100644
index 91e8f6d..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/run/testmap/TestRuleFinderImpl.java
+++ /dev/null
@@ -1,194 +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.testmap;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.*;
-import com.google.idea.blaze.base.experiments.BoolExperiment;
-import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.ideinfo.TestIdeInfo;
-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.settings.BlazeImportSettings;
-import com.google.idea.blaze.base.sync.SyncListener;
-import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
-import com.intellij.openapi.project.Project;
-
-import javax.annotation.Nullable;
-import java.io.File;
-import java.util.Collection;
-import java.util.List;
-import java.util.Queue;
-import java.util.Set;
-
-/**
- * Used to locate tests from source files for things like right-clicks.
- *
- * It's essentially a map from source file -> reachable test rules.
- */
-public class TestRuleFinderImpl implements TestRuleFinder {
-
-  // Safety experiment to allow us to turn this off. Deployed in ijwb 1.2.
-  private static final BoolExperiment USE_TEST_SIZE = new BoolExperiment("use.test.sizes", true);
-
-  private final Project project;
-  @Nullable
-  private TestMap testMap;
-
-  static class TestMap {
-    private final Project project;
-    private final Multimap<File, Label> rootsMap;
-    private final ImmutableMap<Label, RuleIdeInfo> ruleMap;
-
-    TestMap(Project project, ImmutableMap<Label, RuleIdeInfo> ruleMap) {
-      this.project = project;
-      this.rootsMap = createRootsMap(ruleMap.values());
-      this.ruleMap = ruleMap;
-    }
-
-    public Collection<Label> testTargetsForSourceFile(File sourceFile,
-                                                      @Nullable TestIdeInfo.TestSize testSize) {
-      BlazeProjectData blazeProjectData = BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
-      if (blazeProjectData != null) {
-        if (!USE_TEST_SIZE.getValue()) {
-          testSize = null;
-        }
-        // If testSize == null then do a pass preferring small
-        // Some test runners will assume no size annotation == small and filter on that, others will not
-        else if (testSize == null) {
-          Collection<Label> smallResults = testTargetsForSourceFile(
-            blazeProjectData.reverseDependencies,
-            sourceFile,
-            TestIdeInfo.DEFAULT_NON_ANNOTATED_TEST_SIZE);
-
-          if (!smallResults.isEmpty()) {
-            return smallResults;
-          }
-        }
-
-        return testTargetsForSourceFile(blazeProjectData.reverseDependencies, sourceFile, testSize);
-      }
-      return ImmutableList.of();
-    }
-
-    @VisibleForTesting
-    Collection<Label> testTargetsForSourceFile(ImmutableMultimap<Label, Label> rdepsMap,
-                                               File sourceFile,
-                                               @Nullable TestIdeInfo.TestSize testSize) {
-      List<Label> result = Lists.newArrayList();
-      Collection<Label> roots = rootsMap.get(sourceFile);
-
-      Queue<Label> todo = Queues.newArrayDeque();
-      for (Label label : roots) {
-        todo.add(label);
-      }
-      Set<Label> seen = Sets.newHashSet();
-      while (!todo.isEmpty()) {
-        Label label = todo.remove();
-        if (!seen.add(label)) {
-          continue;
-        }
-
-        RuleIdeInfo rule = ruleMap.get(label);
-        if (isTestRule(rule) && matchesTestSize(rule, testSize)) {
-          result.add(label);
-        }
-        for (Label rdep : rdepsMap.get(label)) {
-          todo.add(rdep);
-        }
-      }
-      return result;
-    }
-
-    static Multimap<File, Label> createRootsMap(Collection<RuleIdeInfo> rules) {
-      Multimap<File, Label> result = ArrayListMultimap.create();
-      for (RuleIdeInfo ruleIdeInfo : rules) {
-        for (ArtifactLocation source : ruleIdeInfo.sources) {
-          result.put(source.getFile(), ruleIdeInfo.label);
-        }
-      }
-      return result;
-    }
-
-    private static boolean isTestRule(@Nullable RuleIdeInfo rule) {
-      return rule != null && rule.kind != null && rule.kind.isOneOf(
-        Kind.ANDROID_ROBOLECTRIC_TEST,
-        Kind.ANDROID_TEST,
-        Kind.JAVA_TEST,
-        Kind.GWT_TEST
-      );
-    }
-  }
-
-  private static boolean matchesTestSize(RuleIdeInfo rule, @Nullable TestIdeInfo.TestSize testSize) {
-    if (testSize == null) {
-      return true;
-    }
-    TestIdeInfo.TestSize ruleTestSize = TestIdeInfo.getTestSize(rule);
-    if (ruleTestSize == null) {
-      return true;
-    }
-    return ruleTestSize == testSize;
-  }
-
-  public TestRuleFinderImpl(Project project) {
-    this.project = project;
-  }
-
-  @Override
-  public Collection<Label> testTargetsForSourceFile(File sourceFile, @Nullable TestIdeInfo.TestSize testSize) {
-    TestMap testMap = getTestMap();
-    if (testMap == null) {
-      return ImmutableList.of();
-    }
-    return testMap.testTargetsForSourceFile(sourceFile, testSize);
-  }
-
-  private synchronized TestMap getTestMap() {
-    if (testMap == null) {
-      testMap = initTestMap();
-    }
-    return testMap;
-  }
-
-  @Nullable
-  private TestMap initTestMap() {
-    BlazeProjectData blazeProjectData = BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
-    if (blazeProjectData == null) {
-      return null;
-    }
-    return new TestMap(project, blazeProjectData.ruleMap);
-  }
-
-  private synchronized void clearMapData() {
-    this.testMap = null;
-  }
-
-  static class ClearTestMap extends SyncListener.Adapter {
-    @Override
-    public void onSyncComplete(Project project,
-                               BlazeImportSettings importSettings,
-                               ProjectViewSet projectViewSet,
-                               BlazeProjectData blazeProjectData) {
-      TestRuleFinder testRuleFinder = TestRuleFinder.getInstance(project);
-      ((TestRuleFinderImpl) testRuleFinder).clearMapData();
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/scope/BlazeContext.java b/blaze-base/src/com/google/idea/blaze/base/scope/BlazeContext.java
deleted file mode 100644
index 83f1d0f..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/scope/BlazeContext.java
+++ /dev/null
@@ -1,268 +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.scope;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.Lists;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.util.List;
-
-/**
- * Scoped operation context.
- */
-public class BlazeContext {
-  @Nullable
-  private BlazeContext parentContext;
-
-  @NotNull
-  private final List<BlazeScope> scopes = Lists.newArrayList();
-
-  @NotNull
-  private final ArrayListMultimap<Class<? extends Output>, OutputSink<?>> outputSinks = ArrayListMultimap.create();
-
-  boolean isEnding;
-
-  boolean isCancelled;
-
-  private int holdCount;
-
-  private boolean hasErrors;
-
-  private boolean propagatesErrors = true;
-
-  public BlazeContext() {
-    this(null);
-  }
-
-  public BlazeContext(@Nullable BlazeContext parentContext) {
-    this.parentContext = parentContext;
-  }
-
-  public BlazeContext push(@NotNull BlazeScope scope) {
-    scopes.add(scope);
-    scope.onScopeBegin(this);
-    return this;
-  }
-
-  /**
-   * Ends the context scope.
-   */
-  public void endScope() {
-    if (isEnding || holdCount > 0) {
-      return;
-    }
-    isEnding = true;
-    for (int i = scopes.size() - 1; i >= 0; i--) {
-      scopes.get(i).onScopeEnd(this);
-    }
-
-    if (parentContext != null && hasErrors && propagatesErrors) {
-      parentContext.setHasError();
-    }
-  }
-
-  /**
-   * Requests cancellation of the operation.
-   * <p/>
-   * <p>Each context holder must handle cancellation individually.
-   */
-  public void setCancelled() {
-    if (isEnding || isCancelled) {
-      return;
-    }
-
-    isCancelled = true;
-
-    if (parentContext != null) {
-      parentContext.setCancelled();
-    }
-  }
-
-  public void hold() {
-    ++holdCount;
-  }
-
-  public void release() {
-    if (--holdCount == 0) {
-      endScope();
-    }
-  }
-
-  public boolean isEnding() {
-    return isEnding;
-  }
-
-  public boolean isCancelled() {
-    return isCancelled;
-  }
-
-  @Nullable
-  public <T extends BlazeScope> T getScope(@NotNull Class<T> scopeClass) {
-    return getScope(scopeClass, scopes.size());
-  }
-
-  @Nullable
-  private <T extends BlazeScope> T getScope(@NotNull Class<T> scopeClass, int endIndex) {
-    for (int i = endIndex - 1; i >= 0; i--) {
-      if (scopes.get(i).getClass() == scopeClass) {
-        return scopeClass.cast(scopes.get(i));
-      }
-    }
-    if (parentContext != null) {
-      return parentContext.getScope(scopeClass);
-    }
-    return null;
-  }
-
-  @Nullable
-  public <T extends BlazeScope> T getParentScope(@NotNull T scope) {
-    int index = scopes.indexOf(scope);
-    if (index == -1) {
-      throw new IllegalArgumentException("Scope does not belong to this context.");
-    }
-    @SuppressWarnings("unchecked")
-    Class<T> scopeClass = (Class<T>)scope.getClass();
-    return getScope(scopeClass, index);
-  }
-
-  /**
-   * Find all instances of {@param scopeClass} that are on the stack starting with this context.
-   * That includes this context and all parent contexts recursively.
-   *
-   * @param scopeClass type of scopes to locate
-   * @return The ordered list of all scopes of type {@param scopeClass}, ordered from
-   * {@param startingScope} to the root.
-   */
-  @NotNull
-  public <T extends BlazeScope> List<T> getScopes(@NotNull Class<T> scopeClass) {
-    List<T> scopesCollector = Lists.newArrayList();
-    getScopes(scopesCollector, scopeClass, scopes.size());
-    return scopesCollector;
-  }
-
-  /**
-   * Find all instances of {@param scopeClass} that are above {@param startingScope} on the stack.
-   * That includes this context and all parent contexts recursively. {@param startingScope} must be
-   * in the this {@link BlazeContext}.
-   *
-   * @param scopeClass    type of scopes to locate
-   * @param startingScope scope to start our search from
-   * @return If {@param startingScope} is in this context, the ordered list of all scopes of type
-   * {@param scopeClass}, ordered from {@param startingScope} to the root. Otherwise, an empty
-   * list.
-   */
-  @NotNull
-  public <T extends BlazeScope> List<T> getScopes(
-    @NotNull Class<T> scopeClass,
-    @NotNull BlazeScope startingScope) {
-    List<T> scopesCollector = Lists.newArrayList();
-    int index = scopes.indexOf(startingScope);
-    if (index == -1) {
-      return scopesCollector;
-    }
-
-    // index + 1 so we include startingScope
-    getScopes(scopesCollector, scopeClass, index + 1);
-    return scopesCollector;
-  }
-
-  /**
-   * Add matching scopes to {@param scopesCollector}. Search from {@param maxIndex} - 1 to 0.
-   */
-  @VisibleForTesting
-  <T extends BlazeScope> void getScopes(
-    @NotNull List<T> scopesCollector,
-    @NotNull Class<T> scopeClass,
-    int maxIndex) {
-    for (int i = maxIndex - 1; i >= 0; --i) {
-      BlazeScope scope = scopes.get(i);
-      if (scope.getClass() == scopeClass) {
-        scopesCollector.add((T)scope);
-      }
-    }
-    if (parentContext != null) {
-      parentContext.getScopes(
-        scopesCollector,
-        scopeClass,
-        parentContext.scopes.size());
-    }
-  }
-
-  public <T extends Output> BlazeContext addOutputSink(@NotNull Class<T> outputClass,
-                                               @NotNull OutputSink<T> outputSink) {
-    outputSinks.put(outputClass, outputSink);
-    return this;
-  }
-
-  /**
-   * Produces output by sending it to any registered sinks.
-   */
-  @SuppressWarnings("unchecked")
-  public synchronized <T extends Output> void output(@NotNull T output) {
-    Class<? extends Output> outputClass = output.getClass();
-    List<OutputSink<?>> outputSinks = this.outputSinks.get(outputClass);
-
-    boolean continuePropagation = true;
-    for (int i = outputSinks.size() - 1; i >= 0; --i) {
-      OutputSink<?> outputSink = outputSinks.get(i);
-      OutputSink.Propagation propagation = ((OutputSink<T>)outputSink).onOutput(output);
-      continuePropagation = propagation == OutputSink.Propagation.Continue;
-      if (!continuePropagation) {
-        break;
-      }
-    }
-    if (continuePropagation && parentContext != null) {
-      parentContext.output(output);
-    }
-  }
-
-  /**
-   * Sets the error state.
-   * <p/>
-   * <p>The error state will be propagated to any parents.
-   */
-  public void setHasError() {
-    this.hasErrors = true;
-  }
-
-  /**
-   * Returns true if there were errors
-   */
-  public boolean hasErrors() {
-    return hasErrors;
-  }
-
-  public boolean isRoot() {
-    return parentContext == null;
-  }
-
-  /**
-   * Returns true if no errors and isn't cancelled.
-   */
-  public boolean shouldContinue() {
-    return !hasErrors() && !isCancelled();
-  }
-
-  /**
-   * Sets whether errors are propagated to the parent context.
-   */
-  public void setPropagatesErrors(boolean propagatesErrors) {
-    this.propagatesErrors = propagatesErrors;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/scope/BlazeScope.java b/blaze-base/src/com/google/idea/blaze/base/scope/BlazeScope.java
deleted file mode 100644
index 56a48cd..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/scope/BlazeScope.java
+++ /dev/null
@@ -1,35 +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.scope;
-
-import org.jetbrains.annotations.NotNull;
-
-/**
- * A scoped facet of a scoped operation.
- * <p/>
- * <p>Attaches to a blaze context and starts and ends with it.
- */
-public interface BlazeScope {
-  /**
-   * Called when the scope is added to the context.
-   */
-  void onScopeBegin(@NotNull BlazeContext context);
-
-  /**
-   * Called when the context scope is ending.
-   */
-  void onScopeEnd(@NotNull BlazeContext context);
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/scope/Output.java b/blaze-base/src/com/google/idea/blaze/base/scope/Output.java
deleted file mode 100644
index c5a7759..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/scope/Output.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.scope;
-
-/**
- * A base interface for contextual output operations.
- */
-public interface Output {
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/scope/OutputSink.java b/blaze-base/src/com/google/idea/blaze/base/scope/OutputSink.java
deleted file mode 100644
index df8e98e..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/scope/OutputSink.java
+++ /dev/null
@@ -1,37 +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.scope;
-
-import org.jetbrains.annotations.NotNull;
-
-/**
- * An output sink registered with a context.
- * <p/>
- * <p>Register these via a ScopeExtension.
- */
-public interface OutputSink<T extends Output> {
-  enum Propagation {
-    Continue,
-    Stop
-  }
-
-  /**
-   * Called when an Output of the correct type goes through the scope.
-   *
-   * @return Whether to continue propagation of this input.
-   */
-  Propagation onOutput(@NotNull T output);
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/scope/Result.java b/blaze-base/src/com/google/idea/blaze/base/scope/Result.java
deleted file mode 100644
index 7285a11..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/scope/Result.java
+++ /dev/null
@@ -1,42 +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.scope;
-
-/**
- * Helper class to be used when you want to return a result or error in a scoped function.
- */
-public class Result<T> {
-  public final T result;
-  public final Throwable error;
-
-  public Result(T result) {
-    this.result = result;
-    this.error = null;
-  }
-
-  public Result(Throwable error) {
-    this.result = null;
-    this.error = error;
-  }
-
-  public static <T> Result<T> of(T result) {
-    return new Result<T>(result);
-  }
-
-  public static <T> Result<T> error(Throwable t) {
-    return new Result<T>(t);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/scope/Scope.java b/blaze-base/src/com/google/idea/blaze/base/scope/Scope.java
deleted file mode 100644
index 6ddd243..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/scope/Scope.java
+++ /dev/null
@@ -1,83 +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.scope;
-
-import com.intellij.openapi.diagnostic.Logger;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * Helper methods to run scoped functions and operations in a scoped context.
- */
-public final class Scope {
-  private static final Logger LOG = Logger.getInstance(Scope.class);
-
-  /**
-   * Runs a scoped function in a new root scope.
-   */
-  public static <T> T root(
-    @NotNull ScopedFunction<T> scopedFunction) {
-    return push(null, scopedFunction);
-  }
-
-  /**
-   * Runs a scoped function in a new nested scope.
-   */
-  public static <T> T push(
-    @Nullable BlazeContext parentContext,
-    @NotNull ScopedFunction<T> scopedFunction) {
-    BlazeContext context = new BlazeContext(parentContext);
-    try {
-      return scopedFunction.execute(context);
-    }
-    catch (RuntimeException e) {
-      context.setHasError();
-      LOG.error(e);
-      throw e;
-    }
-    finally {
-      context.endScope();
-    }
-  }
-
-  /**
-   * Runs a scoped operation in a new root scope.
-   */
-  public static void root(
-    @NotNull ScopedOperation scopedOperation) {
-    push(null, scopedOperation);
-  }
-
-  /**
-   * Runs a scoped operation in a new nested scope.
-   */
-  public static void push(
-    @Nullable BlazeContext parentContext,
-    @NotNull ScopedOperation scopedOperation) {
-    BlazeContext context = new BlazeContext(parentContext);
-    try {
-      scopedOperation.execute(context);
-    }
-    catch (RuntimeException e) {
-      context.setHasError();
-      LOG.error(e);
-      throw e;
-    }
-    finally {
-      context.endScope();
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/scope/ScopedFunction.java b/blaze-base/src/com/google/idea/blaze/base/scope/ScopedFunction.java
deleted file mode 100644
index 8a780cd..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/scope/ScopedFunction.java
+++ /dev/null
@@ -1,25 +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.scope;
-
-import org.jetbrains.annotations.NotNull;
-
-/**
- * A scoped operation that can return a result to its caller.
- */
-public interface ScopedFunction<T> {
-  T execute(@NotNull BlazeContext context);
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/scope/ScopedOperation.java b/blaze-base/src/com/google/idea/blaze/base/scope/ScopedOperation.java
deleted file mode 100644
index 29f3610..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/scope/ScopedOperation.java
+++ /dev/null
@@ -1,25 +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.scope;
-
-import org.jetbrains.annotations.NotNull;
-
-/**
- * A scoped operation.
- */
-public interface ScopedOperation {
-  void execute(@NotNull BlazeContext context);
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/scope/ScopedTask.java b/blaze-base/src/com/google/idea/blaze/base/scope/ScopedTask.java
deleted file mode 100644
index 341cae4..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/scope/ScopedTask.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.scope;
-
-import com.google.idea.blaze.base.scope.scopes.ProgressIndicatorScope;
-import com.intellij.openapi.progress.ProgressIndicator;
-import com.intellij.openapi.progress.Progressive;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * Wrapper between an IntelliJ Task and a BlazeContext
- */
-public abstract class ScopedTask implements Progressive {
-  @Nullable
-  final BlazeContext parentContext;
-
-  public ScopedTask() {
-    this(null /* parentContext */);
-  }
-
-  public ScopedTask(@Nullable BlazeContext parentContext) {
-    this.parentContext = parentContext;
-  }
-
-  @Override
-  public void run(@NotNull final ProgressIndicator indicator) {
-    Scope.push(parentContext, new ScopedOperation() {
-      @Override
-      public void execute(@NotNull BlazeContext context) {
-        context.push(new ProgressIndicatorScope(indicator));
-        ScopedTask.this.execute(context);
-      }
-    });
-  }
-
-  protected abstract void execute(@NotNull BlazeContext context);
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/scope/output/IssueOutput.java b/blaze-base/src/com/google/idea/blaze/base/scope/output/IssueOutput.java
deleted file mode 100644
index e3b28d7..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/scope/output/IssueOutput.java
+++ /dev/null
@@ -1,176 +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.scope.output;
-
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.google.idea.blaze.base.scope.Output;
-import com.intellij.pom.Navigatable;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.io.File;
-
-/**
- * An issue in a blaze operation.
- */
-public class IssueOutput implements Output {
-
-  public static final int NO_LINE = -1;
-  public static final int NO_COLUMN = -1;
-
-  @Nullable private final File file;
-  private final int line;
-  private final int column;
-  @NotNull private final Category category;
-  @NotNull private final String message;
-  @Nullable Navigatable navigatable;
-  @Nullable IssueData issueData;
-
-  public static class IssueData {}
-
-  public enum Category {
-    ERROR,
-    WARNING,
-    STATISTICS,
-    INFORMATION
-  }
-
-  @NotNull
-  public static Builder issue(@NotNull Category category, @NotNull String message) {
-    return new Builder(category, message);
-  }
-
-  @NotNull
-  public static Builder error(@NotNull String message) {
-    return new Builder(Category.ERROR, message);
-  }
-
-  @NotNull
-  public static Builder warn(@NotNull String message) {
-    return new Builder(Category.WARNING, message);
-  }
-
-  public static class Builder {
-    @NotNull private final Category category;
-    @NotNull private final String message;
-    @Nullable private File file;
-    private int line = NO_LINE;
-    private int column = NO_COLUMN;
-    @Nullable Navigatable navigatable;
-    @Nullable IssueData issueData;
-
-    public Builder(@NotNull Category category, @NotNull String message) {
-      this.category = category;
-      this.message = message;
-    }
-
-    @NotNull
-    public Builder inFile(@Nullable File file) {
-      this.file = file;
-      return this;
-    }
-
-    @NotNull
-    public Builder onLine(int line) {
-      this.line = line;
-      return this;
-    }
-
-    @NotNull
-    public Builder inColumn(int column) {
-      this.column = column;
-      return this;
-    }
-
-    @NotNull
-    public Builder withData(@Nullable IssueData issueData) {
-      this.issueData = issueData;
-      return this;
-    }
-
-    @NotNull
-    public Builder navigatable(@Nullable Navigatable navigatable) {
-      this.navigatable = navigatable;
-      return this;
-    }
-
-    public IssueOutput build() {
-      return new IssueOutput(file, line, column, navigatable, category, message, issueData);
-    }
-
-    public void submit(@NotNull BlazeContext context) {
-      context.output(build());
-      if (category == Category.ERROR) {
-        context.setHasError();
-      }
-    }
-  }
-
-  private IssueOutput(
-    @Nullable File file,
-    int line,
-    int column,
-    @Nullable Navigatable navigatable,
-    @NotNull Category category,
-    @NotNull String message,
-    @Nullable IssueData issueData) {
-    this.file = file;
-    this.line = line;
-    this.column = column;
-    this.navigatable = navigatable;
-    this.category = category;
-    this.message = message;
-    this.issueData = issueData;
-  }
-
-  @Nullable
-  public File getFile() {
-    return file;
-  }
-
-  public int getLine() {
-    return line;
-  }
-
-  public int getColumn() {
-    return column;
-  }
-
-  @Nullable
-  public Navigatable getNavigatable() {
-    return navigatable;
-  }
-
-  @NotNull
-  public Category getCategory() {
-    return category;
-  }
-
-  @NotNull
-  public String getMessage() {
-    return message;
-  }
-
-  @Override
-  public String toString() {
-    return message;
-  }
-
-  @Nullable
-  public IssueData getIssueData() {
-    return issueData;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/scope/output/PerformanceWarning.java b/blaze-base/src/com/google/idea/blaze/base/scope/output/PerformanceWarning.java
deleted file mode 100644
index 1cdbf27..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/scope/output/PerformanceWarning.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.scope.output;
-
-import com.google.idea.blaze.base.scope.Output;
-
-/**
- * Output that is collected when running in performance collection mode.
- */
-public class PerformanceWarning implements Output {
-  public final String text;
-
-  public PerformanceWarning(String text) {
-    this.text = text;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/scope/output/PrintOutput.java b/blaze-base/src/com/google/idea/blaze/base/scope/output/PrintOutput.java
deleted file mode 100644
index dfe0203..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/scope/output/PrintOutput.java
+++ /dev/null
@@ -1,69 +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.scope.output;
-
-import com.google.idea.blaze.base.scope.Output;
-import com.intellij.execution.process.ProcessOutputTypes;
-import com.intellij.openapi.util.Key;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Output that can be printed to a log.
- */
-public class PrintOutput implements Output {
-
-  @NotNull
-  private final String text;
-
-  @NotNull
-  private final OutputType outputType;
-
-  public enum OutputType {
-    NORMAL,
-    ERROR;
-
-    public static OutputType fromProcessOutputKey(Key outputKey) {
-      return outputKey == ProcessOutputTypes.STDERR ? ERROR : NORMAL;
-    }
-  }
-
-  public PrintOutput(@NotNull String text, @NotNull OutputType outputType) {
-    this.text = text;
-    this.outputType = outputType;
-  }
-
-  public PrintOutput(@NotNull String text) {
-    this(text, OutputType.NORMAL);
-  }
-
-  @NotNull
-  public String getText() {
-    return text;
-  }
-
-  @NotNull
-  public OutputType getOutputType() {
-    return outputType;
-  }
-
-  public static PrintOutput output(String text) {
-    return new PrintOutput(text);
-  }
-
-  public static PrintOutput error(String text) {
-    return new PrintOutput(text, OutputType.ERROR);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/scope/output/StatusOutput.java b/blaze-base/src/com/google/idea/blaze/base/scope/output/StatusOutput.java
deleted file mode 100644
index 92ab9f3..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/scope/output/StatusOutput.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.base.scope.output;
-
-import com.google.idea.blaze.base.scope.Output;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Status message output.
- */
-public class StatusOutput implements Output {
-  @NotNull
-  String status;
-
-  public StatusOutput(@NotNull String status) {
-    this.status = status;
-  }
-
-  @NotNull
-  public String getStatus() {
-    return status;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/scope/scopes/BlazeConsoleScope.java b/blaze-base/src/com/google/idea/blaze/base/scope/scopes/BlazeConsoleScope.java
deleted file mode 100644
index 646afbc..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/scope/scopes/BlazeConsoleScope.java
+++ /dev/null
@@ -1,126 +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.scope.scopes;
-
-import com.google.idea.blaze.base.console.BlazeConsoleService;
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.google.idea.blaze.base.scope.BlazeScope;
-import com.google.idea.blaze.base.scope.OutputSink;
-import com.google.idea.blaze.base.scope.output.PrintOutput;
-import com.google.idea.blaze.base.scope.output.PrintOutput.OutputType;
-import com.google.idea.blaze.base.scope.output.StatusOutput;
-import com.intellij.execution.ui.ConsoleViewContentType;
-import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.progress.ProgressIndicator;
-import com.intellij.openapi.project.Project;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * Moves print output to the blaze console.
- */
-public class BlazeConsoleScope implements BlazeScope {
-
-  public static class Builder {
-    private Project project;
-    private ProgressIndicator progressIndicator;
-    private boolean suppressConsole = false;
-
-    public Builder(@NotNull Project project) {
-      this(project, null);
-    }
-
-    public Builder(@NotNull Project project,
-                   ProgressIndicator progressIndicator) {
-      this.project = project;
-      this.progressIndicator = progressIndicator;
-    }
-
-    public Builder setSuppressConsole(boolean suppressConsole) {
-      this.suppressConsole = suppressConsole;
-      return this;
-    }
-
-    public BlazeConsoleScope build() {
-      return new BlazeConsoleScope(project, progressIndicator, suppressConsole);
-    }
-
-  }
-
-  @NotNull
-  private final Project project;
-
-  @NotNull
-  private final BlazeConsoleService blazeConsoleService;
-
-  @Nullable
-  private final ProgressIndicator progressIndicator;
-
-  private final boolean showDialogOnChange;
-  private boolean activated;
-
-  private OutputSink<PrintOutput> printSink = (output) -> {
-      @NotNull String text = output.getText();
-      @NotNull ConsoleViewContentType contentType = output.getOutputType() == OutputType.NORMAL
-                                                    ? ConsoleViewContentType.NORMAL_OUTPUT
-                                                    : ConsoleViewContentType.ERROR_OUTPUT;
-      print(text, contentType);
-      return OutputSink.Propagation.Continue;
-  };
-
-  private OutputSink<StatusOutput> statusSink = (output) -> {
-      @NotNull String text = output.getStatus();
-      @NotNull ConsoleViewContentType contentType = ConsoleViewContentType.NORMAL_OUTPUT;
-      print(text, contentType);
-      return OutputSink.Propagation.Continue;
-  };
-
-  private BlazeConsoleScope(@NotNull Project project,
-                           @Nullable ProgressIndicator progressIndicator,
-                           boolean suppressConsole) {
-    this.project = project;
-    this.blazeConsoleService = BlazeConsoleService.getInstance(project);
-    this.progressIndicator = progressIndicator;
-    this.showDialogOnChange = !suppressConsole;
-  }
-
-  private void print(String text, ConsoleViewContentType contentType) {
-    blazeConsoleService.print(text + "\n", contentType);
-
-    if (showDialogOnChange && !activated) {
-      activated = true;
-      ApplicationManager.getApplication().invokeLater(() -> blazeConsoleService.activateConsoleWindow());
-    }
-  }
-
-  @Override
-  public void onScopeBegin(@NotNull final BlazeContext context) {
-    context.addOutputSink(PrintOutput.class, printSink);
-    context.addOutputSink(StatusOutput.class, statusSink);
-    blazeConsoleService.clear();
-    blazeConsoleService.setStopHandler(() -> {
-      if (progressIndicator != null) {
-        progressIndicator.cancel();
-      }
-      context.setCancelled();
-    });
-  }
-
-  @Override
-  public void onScopeEnd(@NotNull BlazeContext context) {
-    blazeConsoleService.setStopHandler(null);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/scope/scopes/IssuesScope.java b/blaze-base/src/com/google/idea/blaze/base/scope/scopes/IssuesScope.java
deleted file mode 100644
index 5241fff..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/scope/scopes/IssuesScope.java
+++ /dev/null
@@ -1,83 +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.scope.scopes;
-
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.google.idea.blaze.base.scope.BlazeScope;
-import com.google.idea.blaze.base.scope.OutputSink;
-import com.google.idea.blaze.base.scope.output.IssueOutput;
-import com.google.idea.blaze.base.ui.BlazeProblemsView;
-import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.wm.ToolWindow;
-import com.intellij.openapi.wm.ToolWindowManager;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.UUID;
-
-/**
- * Shows the compiler output.
- */
-public class IssuesScope implements BlazeScope, OutputSink<IssueOutput> {
-
-  private final Project project;
-  private final UUID sessionId;
-  private int issuesCount;
-
-  public IssuesScope(@NotNull Project project) {
-    this.project = project;
-    this.sessionId = UUID.randomUUID();
-  }
-
-  @Override
-  public void onScopeBegin(@NotNull BlazeContext context) {
-    context.addOutputSink(IssueOutput.class, this);
-    BlazeProblemsView blazeProblemsView = BlazeProblemsView.getInstance(project);
-    if (blazeProblemsView != null) {
-      blazeProblemsView.clearOldMessages(sessionId);
-    }
-  }
-
-  @Override
-  public void onScopeEnd(@NotNull BlazeContext context) {
-    if (issuesCount > 0) {
-      ApplicationManager.getApplication().invokeLater(new Runnable() {
-        @Override
-        public void run() {
-          focusProblemsView();
-        }
-      });
-    }
-  }
-
-  private void focusProblemsView() {
-    ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(project);
-    ToolWindow toolWindow = toolWindowManager.getToolWindow("Problems");
-    if (toolWindow != null) {
-      toolWindow.activate(null, false, false);
-    }
-  }
-
-  @Override
-  public Propagation onOutput(@NotNull IssueOutput output) {
-    BlazeProblemsView blazeProblemsView = BlazeProblemsView.getInstance(project);
-    if (blazeProblemsView != null) {
-      blazeProblemsView.addMessage(output, sessionId);
-    }
-    ++issuesCount;
-    return Propagation.Continue;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/scope/scopes/LoggedTimingScope.java b/blaze-base/src/com/google/idea/blaze/base/scope/scopes/LoggedTimingScope.java
deleted file mode 100644
index 64fca5a..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/scope/scopes/LoggedTimingScope.java
+++ /dev/null
@@ -1,59 +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.scope.scopes;
-
-import com.google.common.base.Stopwatch;
-import com.google.idea.blaze.base.metrics.Action;
-import com.google.idea.blaze.base.metrics.LoggingService;
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.google.idea.blaze.base.scope.BlazeScope;
-import com.intellij.openapi.project.Project;
-
-import java.util.concurrent.TimeUnit;
-
-/**
- * Timing scope where the results are sent to a logging service
- */
-public class LoggedTimingScope implements BlazeScope {
-  // It is not guaranteed that the threading model will be sane during the entirety of this scope,
-  // so we use wall clock time and not ThreadMXBean where we could get user/system time.
-
-  Project project;
-  private final Action action;
-  private Stopwatch timer;
-
-  /**
-   * @param action The action we will be reporting a time for to the logging service
-   */
-  public LoggedTimingScope(Project project, Action action) {
-    this.project = project;
-    this.action = action;
-    this.timer = Stopwatch.createUnstarted();
-  }
-
-  @Override
-  public void onScopeBegin(BlazeContext context) {
-    timer.start();
-  }
-
-  @Override
-  public void onScopeEnd(BlazeContext context) {
-    if (!context.isCancelled()) {
-      long totalMS = timer.elapsed(TimeUnit.MILLISECONDS);
-      LoggingService.reportEvent(project, action, totalMS);
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/scope/scopes/NotificationScope.java b/blaze-base/src/com/google/idea/blaze/base/scope/scopes/NotificationScope.java
deleted file mode 100644
index 96d67c7..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/scope/scopes/NotificationScope.java
+++ /dev/null
@@ -1,95 +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.scope.scopes;
-
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.google.idea.blaze.base.scope.BlazeScope;
-import com.google.idea.blaze.base.scope.output.PrintOutput;
-import com.google.idea.blaze.base.scope.output.PrintOutput.OutputType;
-import com.intellij.openapi.project.Project;
-import com.intellij.ui.SystemNotifications;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Notifies the user with a system notification when the scope ends.
- */
-public class NotificationScope implements BlazeScope {
-
-  private static final long NOTIFICATION_THRESHOLD_MS = 0;
-
-  @NotNull
-  private Project project;
-
-  @NotNull
-  private final String notificationName;
-
-  @NotNull
-  private final String notificationTitle;
-
-  @NotNull
-  private final String notificationText;
-
-  @NotNull
-  private final String notificationErrorText;
-
-  private long startTime;
-
-  public NotificationScope(
-    @NotNull Project project,
-    @NotNull String notificationName,
-    @NotNull String notificationTitle,
-    @NotNull String notificationText,
-    @NotNull String notificationErrorText) {
-    this.project = project;
-    this.notificationName = notificationName;
-    this.notificationTitle = notificationTitle;
-    this.notificationText = notificationText;
-    this.notificationErrorText = notificationErrorText;
-  }
-
-  @Override
-  public void onScopeBegin(@NotNull BlazeContext context) {
-    startTime = System.currentTimeMillis();
-  }
-
-  @Override
-  public void onScopeEnd(@NotNull BlazeContext context) {
-    if (project.isDisposed()) {
-      return;
-    }
-    if (context.isCancelled()) {
-      context.output(new PrintOutput(notificationName + " cancelled"));
-      return;
-    }
-    long duration = System.currentTimeMillis() - startTime;
-    if (duration < NOTIFICATION_THRESHOLD_MS) {
-      return;
-    }
-
-    String notificationText = !context.hasErrors()
-                              ? this.notificationText
-                              : this.notificationErrorText;
-
-    SystemNotifications.getInstance().notify(
-      notificationName,
-      notificationTitle,
-      notificationText);
-
-    if (context.hasErrors()) {
-      context.output(new PrintOutput(notificationName + " failed", OutputType.ERROR));
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/scope/scopes/PerformanceWarningScope.java b/blaze-base/src/com/google/idea/blaze/base/scope/scopes/PerformanceWarningScope.java
deleted file mode 100644
index e36d310..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/scope/scopes/PerformanceWarningScope.java
+++ /dev/null
@@ -1,58 +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.scope.scopes;
-
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.google.idea.blaze.base.scope.BlazeScope;
-import com.google.idea.blaze.base.scope.OutputSink;
-import com.google.idea.blaze.base.scope.output.PerformanceWarning;
-import com.google.idea.blaze.base.scope.output.PrintOutput;
-
-import java.util.List;
-
-/**
- * Shows performance warnings.
- */
-public class PerformanceWarningScope implements BlazeScope, OutputSink<PerformanceWarning> {
-
-  private final List<PerformanceWarning> outputs = Lists.newArrayList();
-
-  @Override
-  public void onScopeBegin(BlazeContext context) {
-    context.addOutputSink(PerformanceWarning.class, this);
-  }
-
-  @Override
-  public void onScopeEnd(BlazeContext context) {
-    if (outputs.isEmpty()) {
-      return;
-    }
-    context.output(new PrintOutput("\n===== PERFORMANCE WARNINGS =====\n"));
-    context.output(new PrintOutput("Your IDE isn't as fast as it could be."));
-    context.output(new PrintOutput("You can turn these off via Blaze > Show Performance Warnings."));
-    context.output(new PrintOutput(""));
-    for (PerformanceWarning output : outputs) {
-      context.output(new PrintOutput(output.text));
-    }
-  }
-
-  @Override
-  public Propagation onOutput(PerformanceWarning output) {
-    outputs.add(output);
-    return Propagation.Continue;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/scope/scopes/ProgressIndicatorScope.java b/blaze-base/src/com/google/idea/blaze/base/scope/scopes/ProgressIndicatorScope.java
deleted file mode 100644
index 3d1f96b..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/scope/scopes/ProgressIndicatorScope.java
+++ /dev/null
@@ -1,67 +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.scope.scopes;
-
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.google.idea.blaze.base.scope.BlazeScope;
-import com.google.idea.blaze.base.scope.OutputSink;
-import com.google.idea.blaze.base.scope.output.StatusOutput;
-import com.intellij.openapi.progress.ProgressIndicator;
-import com.intellij.openapi.progress.util.AbstractProgressIndicatorExBase;
-import com.intellij.openapi.wm.ex.ProgressIndicatorEx;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Progress indicator scope.
- * <p/>
- * <p>Channels status outputs to the progress indicator text.
- * <p>Cancels the scope if the user presses cancel on the progress indicator.
- */
-public class ProgressIndicatorScope extends AbstractProgressIndicatorExBase
-  implements BlazeScope, OutputSink<StatusOutput> {
-
-  private final ProgressIndicator progressIndicator;
-  private BlazeContext context;
-
-  public ProgressIndicatorScope(@NotNull ProgressIndicator progressIndicator) {
-    this.progressIndicator = progressIndicator;
-
-    if (progressIndicator instanceof ProgressIndicatorEx) {
-      ((ProgressIndicatorEx)progressIndicator).addStateDelegate(this);
-    }
-  }
-
-  @Override
-  public void onScopeBegin(@NotNull BlazeContext context) {
-    this.context = context;
-    context.addOutputSink(StatusOutput.class, this);
-  }
-
-  @Override
-  public void onScopeEnd(@NotNull BlazeContext context) {
-  }
-
-  @Override
-  public void cancel() {
-    context.setCancelled();
-  }
-
-  @Override
-  public Propagation onOutput(@NotNull StatusOutput output) {
-    progressIndicator.setText(output.getStatus());
-    return Propagation.Continue;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/scope/scopes/ProjectCloseScope.java b/blaze-base/src/com/google/idea/blaze/base/scope/scopes/ProjectCloseScope.java
deleted file mode 100644
index d4e8369..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/scope/scopes/ProjectCloseScope.java
+++ /dev/null
@@ -1,91 +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.scope.scopes;
-
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.google.idea.blaze.base.scope.BlazeScope;
-import com.google.idea.blaze.base.settings.Blaze;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.project.ProjectManager;
-import com.intellij.openapi.project.ProjectManagerListener;
-import com.intellij.openapi.ui.Messages;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Prevents the user from closing the project while the scope is open.
- */
-public class ProjectCloseScope implements ProjectManagerListener, BlazeScope {
-
-  @NotNull
-  private final Project project;
-
-  private boolean isApplicationExitingOrProjectClosing;
-
-  public ProjectCloseScope(@NotNull Project project) {
-    this.project = project;
-  }
-
-  @Override
-  public void onScopeBegin(@NotNull BlazeContext context) {
-    ProjectManager projectManager = ProjectManager.getInstance();
-    projectManager.addProjectManagerListener(project, this);
-  }
-
-  @Override
-  public void onScopeEnd(@NotNull BlazeContext context) {
-    ProjectManager projectManager = ProjectManager.getInstance();
-    projectManager.removeProjectManagerListener(project, this);
-  }
-
-  @Override
-  public void projectOpened(Project project) {
-  }
-
-  @Override
-  public boolean canCloseProject(Project project) {
-    if (!project.equals(this.project)) {
-      return true;
-    }
-    if (shouldPromptUser()) {
-      askUserToWait();
-      return false;
-    }
-    return false;
-  }
-
-  @Override
-  public void projectClosed(Project project) {
-  }
-
-  @Override
-  public void projectClosing(Project project) {
-    if (project.equals(this.project)) {
-      isApplicationExitingOrProjectClosing = true;
-    }
-  }
-
-  private boolean shouldPromptUser() {
-    return !isApplicationExitingOrProjectClosing;
-  }
-
-  private void askUserToWait() {
-    String buildSystem = Blaze.buildSystemName(project);
-    Messages.showMessageDialog(project,
-                               String.format("Please wait until %s command execution finishes", buildSystem),
-                               buildSystem + " Running",
-                               null);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/scope/scopes/TimingScope.java b/blaze-base/src/com/google/idea/blaze/base/scope/scopes/TimingScope.java
deleted file mode 100644
index 49f0d24..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/scope/scopes/TimingScope.java
+++ /dev/null
@@ -1,123 +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.scope.scopes;
-
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.google.idea.blaze.base.scope.BlazeScope;
-import com.google.idea.blaze.base.scope.output.PrintOutput;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.util.List;
-
-/**
- * Prints timing information as output.
- */
-public class TimingScope implements BlazeScope {
-
-  @NotNull
-  private final String name;
-
-  private long startTime;
-
-  private double duration;
-
-  @Nullable
-  private TimingScope parentScope;
-
-  @NotNull
-  private List<TimingScope> children = Lists.newArrayList();
-
-  public TimingScope(@NotNull String name) {
-    this.name = name;
-  }
-
-  @Override
-  public void onScopeBegin(@NotNull BlazeContext context) {
-    startTime = System.currentTimeMillis();
-    parentScope = context.getParentScope(this);
-
-    if (parentScope != null) {
-      parentScope.children.add(this);
-    }
-  }
-
-  @Override
-  public void onScopeEnd(@NotNull BlazeContext context) {
-    if (context.isCancelled()) {
-      return;
-    }
-
-    long elapsedTime = System.currentTimeMillis() - startTime;
-    duration = (double)elapsedTime / 1000.0;
-
-    if (parentScope == null) {
-      outputReport(context);
-    }
-  }
-
-  private void outputReport(@NotNull BlazeContext context) {
-    context.output(new PrintOutput("\n==== TIMING REPORT ====\n"));
-    outputReport(context, this, 0);
-  }
-
-  private static void outputReport(
-    @NotNull BlazeContext context,
-    @NotNull TimingScope timingScope,
-    int depth) {
-    String selfString = "";
-
-    // Self time trivially 100% if no children
-    if (timingScope.children.size() > 0) {
-      // Calculate self time as <my duration> - <sum child duration>
-      double selfTime = timingScope.duration;
-      for (TimingScope child : timingScope.children) {
-        selfTime -= child.duration;
-      }
-
-      selfString = selfTime > 0.1
-                   ? String.format(" (%s)", durationStr(selfTime))
-                   : "";
-    }
-
-    context.output(new PrintOutput(
-      String.format("%s%s: %s%s",
-                    getIndentation(depth),
-                    timingScope.name,
-                    durationStr(timingScope.duration),
-                    selfString)
-    ));
-
-    for (TimingScope child : timingScope.children) {
-      outputReport(context, child, depth + 1);
-    }
-  }
-
-  private static String durationStr(double time) {
-    return time >= 1.0
-           ? String.format("%.1fs", time)
-           : String.format("%dms", (int)(time * 1000));
-  }
-
-  private static String getIndentation(int depth) {
-    StringBuilder sb = new StringBuilder();
-    for (int i = 0; i < depth; ++i) {
-      sb.append("    ");
-    }
-    return sb.toString();
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/settings/Blaze.java b/blaze-base/src/com/google/idea/blaze/base/settings/Blaze.java
deleted file mode 100644
index 2b85c24..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/settings/Blaze.java
+++ /dev/null
@@ -1,121 +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.settings;
-
-import com.google.idea.blaze.base.bazel.BuildSystemProvider;
-import com.intellij.ide.DataManager;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.project.ProjectManager;
-
-import javax.annotation.Nullable;
-import javax.swing.*;
-
-/**
- * Blaze project utilities.
- */
-public class Blaze {
-
-  public enum BuildSystem {
-    Blaze,
-    Bazel;
-
-    /**
-     * The build system name, capitalized.
-     */
-    public String getName() {
-      return name();
-    }
-
-    /**
-     * The build system name, capitalized.
-     */
-    public String getLowerCaseName() {
-      return name().toLowerCase();
-    }
-  }
-
-  private Blaze() {
-  }
-
-  public static boolean isBlazeProjectOpen() {
-    for (Project project : ProjectManager.getInstance().getOpenProjects()) {
-      if (isBlazeProject(project)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  /**
-   * Returns whether this project was imported from blaze.
-   */
-  public static boolean isBlazeProject(Project project) {
-    return BlazeImportSettingsManager.getInstance(project).getImportSettings() != null;
-  }
-
-  /**
-   * Returns the build system associated with this project,
-   * or falls back to the default blaze build system if the project is null or
-   * not a blaze project.
-   */
-  public static BuildSystem getBuildSystem(@Nullable Project project) {
-    BlazeImportSettings importSettings =
-      project == null ? null: BlazeImportSettingsManager.getInstance(project).getImportSettings();
-    if (importSettings == null) {
-      return BuildSystemProvider.defaultBuildSystem().buildSystem();
-    }
-    return importSettings.getBuildSystem();
-  }
-
-  /**
-   * The name of the build system associated with the given project,
-   * or falls back to the default blaze build system if the project is null or
-   * not a blaze project.
-   */
-  public static String buildSystemName(@Nullable Project project) {
-    return getBuildSystem(project).getName();
-  }
-
-  /**
-   * The name of the application-wide build system default. This should
-   * only be used in situations where it doesn't make sense to use the build system
-   * associated with the current project (e.g. the import project action).
-   */
-  public static String defaultBuildSystemName() {
-    return BuildSystemProvider.defaultBuildSystem().buildSystem().getName();
-  }
-
-  /**
-   * Tries to guess the current project, and uses that to determine the build system name.<br>
-   * Should only be used in situations where the current project is not accessible.
-   */
-  public static String guessBuildSystemName() {
-    Project project = guessCurrentProject();
-    return buildSystemName(project);
-  }
-
-  private static Project guessCurrentProject() {
-    Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
-    if (openProjects.length == 1) {
-      return openProjects[0];
-    }
-    if (SwingUtilities.isEventDispatchThread()) {
-      return (Project) DataManager.getInstance().getDataContext().getData("project");
-    }
-    return null;
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/settings/BlazeImportSettings.java b/blaze-base/src/com/google/idea/blaze/base/settings/BlazeImportSettings.java
deleted file mode 100644
index ac4db38..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/settings/BlazeImportSettings.java
+++ /dev/null
@@ -1,141 +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.settings;
-
-import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
-import com.intellij.util.xmlb.annotations.Tag;
-
-import javax.annotation.Nullable;
-
-/**
- * Project settings that are set at import time.
- */
-@Tag("BlazeProjectSettings") // Legacy migration support
-public final class BlazeImportSettings {
-
-  private String workspaceRoot = "";
-
-  private String projectName = "";
-
-  private String projectDataDirectory = "";
-
-  private String locationHash = "";
-
-  private String projectViewFile;
-
-  private BuildSystem buildSystem = BuildSystem.Blaze; // default for backwards compatibility with existing projects.
-
-  // Used by bean serialization
-  @SuppressWarnings("unused")
-  BlazeImportSettings() {
-  }
-
-  public BlazeImportSettings(
-    String workspaceRoot,
-    String projectName,
-    String projectDataDirectory,
-    String locationHash,
-    String projectViewFile,
-    BuildSystem buildSystem) {
-    this.workspaceRoot = workspaceRoot;
-    this.projectName = projectName;
-    this.projectDataDirectory = projectDataDirectory;
-    this.locationHash = locationHash;
-    this.projectViewFile = projectViewFile;
-    this.buildSystem = buildSystem;
-  }
-
-  @SuppressWarnings("unused")
-  public String getWorkspaceRoot() {
-    return workspaceRoot;
-  }
-
-  @SuppressWarnings("unused")
-  public String getProjectName() {
-    return projectName;
-  }
-
-  @SuppressWarnings("unused")
-  public String getProjectDataDirectory() {
-    return projectDataDirectory;
-  }
-
-  /**
-   * Hash used to give the project a unique directory in the system directory.
-   */
-  @SuppressWarnings("unused")
-  public String getLocationHash() {
-    return locationHash;
-  }
-
-  /**
-   * The user's local project view file
-   */
-  @SuppressWarnings("unused")
-  public String getProjectViewFile() {
-    return projectViewFile;
-  }
-
-  /**
-   * The build system used for the project.
-   */
-  @SuppressWarnings("unused")
-  public BuildSystem getBuildSystem() {
-    return buildSystem;
-  }
-
-  // Used by bean serialization
-  @SuppressWarnings("unused")
-  public void setWorkspaceRoot(String workspaceRoot) {
-    this.workspaceRoot = workspaceRoot;
-  }
-
-  // Used by bean serialization
-  @SuppressWarnings("unused")
-  public void setProjectName(String projectName) {
-    this.projectName = projectName;
-  }
-
-  // Used by bean serialization
-  @SuppressWarnings("unused")
-  public void setProjectDataDirectory(String projectDataDirectory) {
-    this.projectDataDirectory = projectDataDirectory;
-  }
-
-  // Used by bean serialization
-  @SuppressWarnings("unused")
-  public void setLocationHash(String locationHash) {
-    this.locationHash = locationHash;
-  }
-
-  // Used by bean serialization
-  @SuppressWarnings("unused")
-  public void setProjectViewFile(@Nullable String projectViewFile) {
-    this.projectViewFile = projectViewFile;
-  }
-
-  // Used by bean serialization -- legacy import support
-  @SuppressWarnings("unused")
-  public void setAsProjectFile(@Nullable String projectViewFile) {
-    this.projectViewFile = projectViewFile;
-  }
-
-  // Used by bean serialization
-  @SuppressWarnings("unused")
-  public void setBuildSystem(BuildSystem buildSystem) {
-    this.buildSystem = buildSystem;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/settings/BlazeImportSettingsManager.java b/blaze-base/src/com/google/idea/blaze/base/settings/BlazeImportSettingsManager.java
deleted file mode 100644
index 3e40500..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/settings/BlazeImportSettingsManager.java
+++ /dev/null
@@ -1,104 +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.settings;
-
-import com.google.common.collect.Lists;
-import com.intellij.openapi.components.*;
-import com.intellij.openapi.project.Project;
-import com.intellij.util.xmlb.annotations.AbstractCollection;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.util.Collection;
-import java.util.List;
-
-/**
- * Manages storage for the project's {@link BlazeImportSettings}.
- */
-@State(
-  name = "BlazeSettings",
-  storages = {@Storage(file = StoragePathMacros.PROJECT_FILE),
-    @Storage(file = StoragePathMacros.PROJECT_CONFIG_DIR + "/blaze.xml",
-      scheme = StorageScheme.DIRECTORY_BASED)}
-)
-public class BlazeImportSettingsManager implements
-                                        PersistentStateComponent<BlazeImportSettingsManager.State> {
-
-  @Nullable
-  private BlazeImportSettings importSettings;
-
-  @NotNull
-  private Project project;
-
-  public BlazeImportSettingsManager(@NotNull Project project) {
-    this.project = project;
-  }
-
-  @NotNull
-  public static BlazeImportSettingsManager getInstance(@NotNull Project project) {
-    return ServiceManager.getService(project, BlazeImportSettingsManager.class);
-  }
-
-  @SuppressWarnings("unchecked")
-  @Nullable
-  @Override
-  public State getState() {
-    State state = new State();
-    List<BlazeImportSettings> value = Lists.newArrayList();
-    if (importSettings != null) {
-      value.add(importSettings);
-    }
-    state.setLinkedExternalProjectsSettings(value);
-    return state;
-  }
-
-  @Override
-  public void loadState(State state) {
-    Collection<BlazeImportSettings> settings = state.getLinkedExternalProjectsSettings();
-    if (settings != null && !settings.isEmpty()) {
-      importSettings = settings.iterator().next();
-    }
-    else {
-      importSettings = null;
-    }
-  }
-
-  @Nullable
-  public BlazeImportSettings getImportSettings() {
-    return importSettings;
-  }
-
-  public void setImportSettings(@NotNull BlazeImportSettings importSettings) {
-    this.importSettings = importSettings;
-  }
-
-  /**
-   * State class for the Blaze settings.
-   */
-  static class State {
-
-    private List<BlazeImportSettings> importSettings = Lists.newArrayList();
-
-    @AbstractCollection(surroundWithTag = false, elementTypes = {BlazeImportSettings.class})
-    public List<BlazeImportSettings> getLinkedExternalProjectsSettings() {
-      return importSettings;
-    }
-
-    public void setLinkedExternalProjectsSettings(List<BlazeImportSettings> settings) {
-      importSettings = settings;
-    }
-  }
-}
\ No newline at end of file
diff --git a/blaze-base/src/com/google/idea/blaze/base/settings/BlazeUserSettings.java b/blaze-base/src/com/google/idea/blaze/base/settings/BlazeUserSettings.java
deleted file mode 100644
index dc3b699..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/settings/BlazeUserSettings.java
+++ /dev/null
@@ -1,178 +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.settings;
-
-import com.google.idea.blaze.base.lang.buildfile.language.BuildFileLanguage;
-import com.google.idea.blaze.base.lang.buildfile.language.BuildFileTypeFactory;
-import com.google.idea.blaze.base.sync.status.BlazeSyncStatus;
-import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.components.PersistentStateComponent;
-import com.intellij.openapi.components.ServiceManager;
-import com.intellij.openapi.components.State;
-import com.intellij.openapi.components.Storage;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.project.ProjectManager;
-import com.intellij.util.xmlb.XmlSerializerUtil;
-import org.jetbrains.annotations.NotNull;
-
-import javax.annotation.Nullable;
-
-/**
- * Stores blaze view settings.
- */
-@State(
-  name = "BlazeUserSettings",
-  storages = @Storage(id = "other", value = "blaze.view.xml")
-)
-public class BlazeUserSettings implements PersistentStateComponent<BlazeUserSettings> {
-
-  public boolean suppressConsoleForRunAction = false;
-  private boolean resyncAutomatically = false;
-  private boolean buildFileSupportEnabled = true;
-  private boolean syncStatusPopupShown = false;
-  private boolean expandSyncToWorkingSet = true;
-  private boolean showPerformanceWarnings = false;
-  private boolean attachSourcesByDefault = false;
-  private boolean attachSourcesOnDemand = false;
-  private boolean collapseProjectView = true;
-  private String blazeBinaryPath = "/usr/bin/blaze";
-  @Nullable private String bazelBinaryPath;
-
-  @NotNull
-  public static BlazeUserSettings getInstance() {
-    return ServiceManager.getService(BlazeUserSettings.class);
-  }
-
-  @Override
-  @NotNull
-  public BlazeUserSettings getState() {
-    return this;
-  }
-
-  @Override
-  public void loadState(BlazeUserSettings state) {
-    XmlSerializerUtil.copyBean(state, this);
-  }
-
-  /**
-   * Also kicks off an incremental sync if we're now syncing automatically,
-   * and the project is currently dirty.
-   */
-  public void setResyncAutomatically(boolean resyncAutomatically) {
-    if (this.resyncAutomatically == resyncAutomatically) {
-      return;
-    }
-    this.resyncAutomatically = resyncAutomatically;
-    ProjectManager projectManager = ApplicationManager.getApplication().getComponent(ProjectManager.class);
-    Project[] openProjects = projectManager.getOpenProjects();
-    for (Project project : openProjects) {
-      if (Blaze.isBlazeProject(project)) {
-        BlazeSyncStatus.getInstance(project).queueAutomaticSyncIfDirty();
-      }
-    }
-  }
-
-  public boolean getResyncAutomatically() {
-    return resyncAutomatically;
-  }
-
-  /**
-   * Triggers an update to the list of registered file types.
-   */
-  public void setBuildFileSupportEnabled(boolean supportEnabled) {
-    if (supportEnabled != buildFileSupportEnabled) {
-      buildFileSupportEnabled = supportEnabled;
-      BuildFileTypeFactory.updateBuildFileLanguageEnabled(BuildFileLanguage.BUILD_FILE_SUPPORT_ENABLED.getValue() && supportEnabled);
-    }
-  }
-
-  public boolean getBuildFileSupportEnabled() {
-    return buildFileSupportEnabled;
-  }
-
-  public boolean getSuppressConsoleForRunAction() {
-    return suppressConsoleForRunAction;
-  }
-
-  public void setSuppressConsoleForRunAction(boolean suppressConsoleForRunAction) {
-    this.suppressConsoleForRunAction = suppressConsoleForRunAction;
-  }
-
-  public boolean getSyncStatusPopupShown() {
-    return syncStatusPopupShown;
-  }
-
-  public void setSyncStatusPopupShown(boolean syncStatusPopupShown) {
-    this.syncStatusPopupShown = syncStatusPopupShown;
-  }
-
-  public boolean getExpandSyncToWorkingSet() {
-    return expandSyncToWorkingSet;
-  }
-
-  public void setExpandSyncToWorkingSet(boolean expandSyncToWorkingSet) {
-    this.expandSyncToWorkingSet = expandSyncToWorkingSet;
-  }
-
-  public boolean getShowPerformanceWarnings() {
-    return showPerformanceWarnings;
-  }
-
-  public void setShowPerformanceWarnings(boolean showPerformanceWarnings) {
-    this.showPerformanceWarnings = showPerformanceWarnings;
-  }
-
-  public String getBlazeBinaryPath() {
-    return blazeBinaryPath;
-  }
-
-  public void setBlazeBinaryPath(String blazeBinaryPath) {
-    this.blazeBinaryPath = blazeBinaryPath;
-  }
-
-  @Nullable
-  public String getBazelBinaryPath() {
-    return bazelBinaryPath;
-  }
-
-  public void setBazelBinaryPath(String bazelBinaryPath) {
-    this.bazelBinaryPath = bazelBinaryPath;
-  }
-
-  public boolean getAttachSourcesByDefault() {
-    return attachSourcesByDefault;
-  }
-
-  public void setAttachSourcesByDefault(boolean attachSourcesByDefault) {
-    this.attachSourcesByDefault = attachSourcesByDefault;
-  }
-
-  public boolean getAttachSourcesOnDemand() {
-    return attachSourcesOnDemand;
-  }
-
-  public void setAttachSourcesOnDemand(boolean attachSourcesOnDemand) {
-    this.attachSourcesOnDemand = attachSourcesOnDemand;
-  }
-
-  public boolean getCollapseProjectView() {
-    return collapseProjectView;
-  }
-
-  public void setCollapseProjectView(boolean collapseProjectView) {
-    this.collapseProjectView = collapseProjectView;
-  }
-}
\ No newline at end of file
diff --git a/blaze-base/src/com/google/idea/blaze/base/settings/IsBlazeProjectCondition.java b/blaze-base/src/com/google/idea/blaze/base/settings/IsBlazeProjectCondition.java
deleted file mode 100644
index ebfc6a7..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/settings/IsBlazeProjectCondition.java
+++ /dev/null
@@ -1,30 +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.settings;
-
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.Condition;
-
-/**
- * Condition for enabling features (e.g. Blaze and Crow consoles) only in Blaze projects.
- */
-public class IsBlazeProjectCondition implements Condition<Project> {
-
-  @Override
-  public boolean value(Project project) {
-    return project != null && Blaze.isBlazeProject(project);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/settings/ui/BlazeUserSettingsConfigurable.java b/blaze-base/src/com/google/idea/blaze/base/settings/ui/BlazeUserSettingsConfigurable.java
deleted file mode 100644
index d63c2cf..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/settings/ui/BlazeUserSettingsConfigurable.java
+++ /dev/null
@@ -1,225 +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.settings.ui;
-
-import com.google.common.base.Objects;
-import com.google.idea.blaze.base.lang.buildfile.language.BuildFileLanguage;
-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;
-import com.google.idea.blaze.base.sync.status.BlazeSyncStatusImpl;
-import com.google.idea.blaze.base.ui.FileSelectorWithStoredHistory;
-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.openapi.ui.Messages;
-import com.intellij.uiDesigner.core.GridConstraints;
-import com.intellij.uiDesigner.core.GridLayoutManager;
-import com.intellij.uiDesigner.core.Spacer;
-
-import javax.annotation.Nullable;
-import javax.swing.*;
-import java.awt.*;
-
-/**
- * Blaze console view settings
- */
-public class BlazeUserSettingsConfigurable extends BaseConfigurable implements SearchableConfigurable {
-
-  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 JPanel myMainPanel;
-  private JCheckBox suppressConsoleForRunAction;
-  private JCheckBox resyncAutomatically;
-  private JCheckBox buildFileSupportEnabled;
-  private JCheckBox attachSourcesByDefault;
-  private JCheckBox attachSourcesOnDemand;
-  private JCheckBox collapseProjectView;
-  private FileSelectorWithStoredHistory blazeBinaryPathField;
-  private FileSelectorWithStoredHistory bazelBinaryPathField;
-
-  public BlazeUserSettingsConfigurable(Project project) {
-    this.buildSystem = Blaze.getBuildSystem(project);
-    setupUI();
-  }
-
-  @Override
-  public String getDisplayName() {
-    return buildSystem.getName() + " View Settings";
-  }
-
-  @Nullable
-  @Override
-  public String getHelpTopic() {
-    return null;
-  }
-
-  @Override
-  public void apply() throws ConfigurationException {
-    BlazeUserSettings settings = BlazeUserSettings.getInstance();
-    settings.setSuppressConsoleForRunAction(suppressConsoleForRunAction.isSelected());
-    settings.setResyncAutomatically(resyncAutomatically.isSelected());
-    settings.setBuildFileSupportEnabled(buildFileSupportEnabled.isSelected());
-    settings.setAttachSourcesByDefault(attachSourcesByDefault.isSelected());
-    settings.setAttachSourcesOnDemand(attachSourcesOnDemand.isSelected());
-    settings.setCollapseProjectView(collapseProjectView.isSelected());
-    if (blazeBinaryPathField.getText() != null) {
-      settings.setBlazeBinaryPath(blazeBinaryPathField.getText());
-    }
-    if (bazelBinaryPathField.getText() != null) {
-      settings.setBazelBinaryPath(bazelBinaryPathField.getText());
-    }
-  }
-
-  @Override
-  public void reset() {
-    BlazeUserSettings settings = BlazeUserSettings.getInstance();
-    suppressConsoleForRunAction.setSelected(settings.getSuppressConsoleForRunAction());
-    resyncAutomatically.setSelected(settings.getResyncAutomatically());
-    buildFileSupportEnabled.setSelected(settings.getBuildFileSupportEnabled());
-    attachSourcesByDefault.setSelected(settings.getAttachSourcesByDefault());
-    attachSourcesOnDemand.setSelected(settings.getAttachSourcesOnDemand());
-    collapseProjectView.setSelected(settings.getCollapseProjectView());
-    blazeBinaryPathField.setTextWithHistory(settings.getBlazeBinaryPath());
-    bazelBinaryPathField.setTextWithHistory(settings.getBazelBinaryPath());
-  }
-
-  @Nullable
-  @Override
-  public JComponent createComponent() {
-    resyncAutomatically.setVisible(BlazeSyncStatusImpl.AUTOMATIC_INCREMENTAL_SYNC.getValue());
-    buildFileSupportEnabled.setVisible(BuildFileLanguage.BUILD_FILE_SUPPORT_ENABLED.getValue());
-    return myMainPanel;
-  }
-
-  @Override
-  public boolean isModified() {
-    BlazeUserSettings settings = BlazeUserSettings.getInstance();
-    return !Objects.equal(suppressConsoleForRunAction.isSelected(), settings.getSuppressConsoleForRunAction()) ||
-           !Objects.equal(resyncAutomatically.isSelected(), settings.getResyncAutomatically()) ||
-           !Objects.equal(buildFileSupportEnabled.isSelected(), settings.getBuildFileSupportEnabled()) ||
-           !Objects.equal(attachSourcesByDefault.isSelected(), settings.getAttachSourcesByDefault()) ||
-           !Objects.equal(attachSourcesOnDemand.isSelected(), settings.getAttachSourcesOnDemand()) ||
-           !Objects.equal(collapseProjectView.isSelected(), settings.getCollapseProjectView()) ||
-           !Objects.equal(blazeBinaryPathField.getText(), settings.getBlazeBinaryPath()) ||
-           !Objects.equal(bazelBinaryPathField.getText(), settings.getBazelBinaryPath());
-  }
-
-  @Override
-  public void disposeUIResources() {
-  }
-
-  @Override
-  public String getId() {
-    return "blaze.view.settings";
-  }
-
-  @Nullable
-  @Override
-  public Runnable enableSearch(String option) {
-    return null;
-  }
-
-
-  /**
-   * Initially generated by IntelliJ from a .form file.
-   */
-  private void setupUI() {
-    myMainPanel = new JPanel();
-    myMainPanel.setLayout(new GridLayoutManager(8, 2, new Insets(0, 0, 0, 0), -1, -1));
-    suppressConsoleForRunAction = new JCheckBox();
-    suppressConsoleForRunAction.setText(String.format("Suppress %s console for Run/Debug actions", buildSystem));
-    suppressConsoleForRunAction.setVerticalAlignment(0);
-    myMainPanel.add(suppressConsoleForRunAction,
-                    new GridConstraints(0, 0, 1, 2, GridConstraints.ANCHOR_NORTHWEST, GridConstraints.FILL_NONE,
-                                        GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
-                                        GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
-    resyncAutomatically = new JCheckBox();
-    resyncAutomatically.setSelected(false);
-    resyncAutomatically.setText("Automatically re-sync project when BUILD files change");
-    myMainPanel.add(resyncAutomatically, new GridConstraints(1, 0, 1, 2, GridConstraints.ANCHOR_NORTHWEST, GridConstraints.FILL_NONE,
-                                                             GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
-                                                             GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
-    buildFileSupportEnabled = new JCheckBox();
-    buildFileSupportEnabled.setSelected(true);
-    buildFileSupportEnabled.setText("BUILD file language support enabled");
-    myMainPanel.add(buildFileSupportEnabled, new GridConstraints(2, 0, 1, 2, GridConstraints.ANCHOR_NORTHWEST, GridConstraints.FILL_NONE,
-                                                             GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
-                                                             GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
-    attachSourcesByDefault = new JCheckBox();
-    attachSourcesByDefault.setSelected(false);
-    attachSourcesByDefault.setText("Automatically attach sources on project sync (WARNING: increases index time by 100%+)");
-    myMainPanel.add(attachSourcesByDefault, new GridConstraints(3, 0, 1, 2, GridConstraints.ANCHOR_NORTHWEST, GridConstraints.FILL_NONE,
-                                                                GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
-                                                                GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
-    attachSourcesByDefault.addActionListener((event) -> {
-      BlazeUserSettings settings = BlazeUserSettings.getInstance();
-      if (attachSourcesByDefault.isSelected() && !settings.getAttachSourcesByDefault()) {
-        int result = Messages.showOkCancelDialog(
-          "You are turning on source jars by default. This setting increases indexing time by "
-          + ">100%, can cost ~1GB RAM, and will increase project reopen time significantly. "
-          + "Are you sure you want to proceed?",
-          "Turn On Sources By Default?",
-          null
-        );
-        if (result != Messages.OK) {
-          attachSourcesByDefault.setSelected(false);
-        }
-      }
-    });
-
-    attachSourcesOnDemand = new JCheckBox();
-    attachSourcesOnDemand.setSelected(false);
-    attachSourcesOnDemand.setText("Automatically attach sources when you open decompiled source");
-    myMainPanel.add(attachSourcesOnDemand, new GridConstraints(4, 0, 1, 2, GridConstraints.ANCHOR_NORTHWEST, GridConstraints.FILL_NONE,
-                                                                GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
-                                                                GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
-    collapseProjectView = new JCheckBox();
-    collapseProjectView.setSelected(false);
-    collapseProjectView.setText("Collapse project view directory roots");
-    myMainPanel.add(collapseProjectView, new GridConstraints(5, 0, 1, 2, GridConstraints.ANCHOR_NORTHWEST, GridConstraints.FILL_NONE,
-                                                               GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
-                                                               GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
-
-    blazeBinaryPathField = FileSelectorWithStoredHistory.create(BLAZE_BINARY_PATH_KEY, "Specify the blaze binary path");
-    bazelBinaryPathField = 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");
-    }
-    pathLabel.setLabelFor(pathPanel);
-    myMainPanel.add(pathLabel, new GridConstraints(6, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_NONE,
-                                                   GridConstraints.SIZEPOLICY_FIXED,
-                                                   GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
-    myMainPanel.add(pathPanel, new GridConstraints(6, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL,
-                                                   GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
-                                                   GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
-
-    myMainPanel.add(new Spacer(), new GridConstraints(7, 0, 1, 2, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_VERTICAL, 1,
-                                                 GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false));
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/settings/ui/EditProjectViewAction.java b/blaze-base/src/com/google/idea/blaze/base/settings/ui/EditProjectViewAction.java
deleted file mode 100644
index 73259f2..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/settings/ui/EditProjectViewAction.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.settings.ui;
-
-import com.google.idea.blaze.base.actions.BlazeAction;
-import com.google.idea.blaze.base.projectview.ProjectViewManager;
-import com.google.idea.blaze.base.projectview.ProjectViewSet;
-import com.intellij.openapi.actionSystem.AnActionEvent;
-import com.intellij.openapi.fileEditor.FileEditorManager;
-import com.intellij.openapi.fileEditor.OpenFileDescriptor;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.vfs.VfsUtil;
-import com.intellij.openapi.vfs.VirtualFile;
-
-import java.io.File;
-
-/**
- * Opens all the user's project views.
- */
-public class EditProjectViewAction extends BlazeAction {
-
-  @Override
-  public void actionPerformed(AnActionEvent e) {
-    Project project = e.getProject();
-    if (project == null) {
-      return;
-    }
-    ProjectViewSet projectViewSet = ProjectViewManager.getInstance(project).getProjectViewSet();
-    if (projectViewSet == null) {
-      return;
-    }
-    for (ProjectViewSet.ProjectViewFile projectViewFile : projectViewSet.getProjectViewFiles()) {
-      File file = projectViewFile.projectViewFile;
-      if (file != null) {
-        VirtualFile virtualFile = VfsUtil.findFileByIoFile(file, true);
-        if (virtualFile != null) {
-          OpenFileDescriptor descriptor = new OpenFileDescriptor(project, virtualFile);
-          FileEditorManager.getInstance(project).openTextEditor(descriptor, true);
-        }
-      }
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/settings/ui/JPanelProvidingProject.java b/blaze-base/src/com/google/idea/blaze/base/settings/ui/JPanelProvidingProject.java
deleted file mode 100644
index f29c7db..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/settings/ui/JPanelProvidingProject.java
+++ /dev/null
@@ -1,45 +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.settings.ui;
-
-import com.intellij.openapi.actionSystem.CommonDataKeys;
-import com.intellij.openapi.actionSystem.DataProvider;
-import com.intellij.openapi.project.Project;
-
-import javax.annotation.Nullable;
-import javax.swing.*;
-import java.awt.*;
-
-/**
- * A normal JPanel which implements DataProvider, providing the specified IntelliJ Project.<p>
- * This is used by IntelliJ's action system to determine the relevant project associated with
- * a UI component.
- */
-public class JPanelProvidingProject extends JPanel implements DataProvider {
-
-  private final Project project;
-
-  public JPanelProvidingProject(Project project, LayoutManager layoutManager) {
-    super(layoutManager);
-    this.project = project;
-  }
-
-  @Nullable
-  @Override
-  public Object getData(String dataId) {
-    return CommonDataKeys.PROJECT.is(dataId) ? project : null;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/settings/ui/ProjectViewUi.java b/blaze-base/src/com/google/idea/blaze/base/settings/ui/ProjectViewUi.java
deleted file mode 100644
index 4b78ff9..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/settings/ui/ProjectViewUi.java
+++ /dev/null
@@ -1,236 +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.settings.ui;
-
-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;
-import com.google.idea.blaze.base.scope.Scope;
-import com.google.idea.blaze.base.scope.output.IssueOutput;
-import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolverImpl;
-import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolverProvider;
-import com.google.idea.blaze.base.ui.UiUtil;
-import com.intellij.ide.DataManager;
-import com.intellij.openapi.Disposable;
-import com.intellij.openapi.application.Result;
-import com.intellij.openapi.application.WriteAction;
-import com.intellij.openapi.command.undo.UndoUtil;
-import com.intellij.openapi.editor.Document;
-import com.intellij.openapi.editor.EditorFactory;
-import com.intellij.openapi.editor.EditorSettings;
-import com.intellij.openapi.editor.colors.EditorColors;
-import com.intellij.openapi.editor.ex.EditorEx;
-import com.intellij.openapi.editor.impl.DocumentImpl;
-import com.intellij.openapi.editor.impl.EditorFactoryImpl;
-import com.intellij.openapi.fileEditor.impl.FileDocumentManagerImpl;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.project.ProjectManager;
-import com.intellij.openapi.project.impl.ProjectImpl;
-import com.intellij.openapi.util.Disposer;
-import com.intellij.psi.PsiManager;
-import com.intellij.psi.impl.PsiManagerEx;
-import com.intellij.psi.impl.file.impl.FileManager;
-import com.intellij.testFramework.LightVirtualFile;
-import com.intellij.ui.components.JBLabel;
-import org.jetbrains.annotations.NotNull;
-import org.picocontainer.MutablePicoContainer;
-
-import javax.annotation.Nullable;
-import javax.swing.*;
-import java.awt.*;
-import java.io.File;
-import java.util.List;
-
-/**
- * UI for changing the ProjectView.
- */
-public class ProjectViewUi {
-
-  private static final String USE_SHARED_PROJECT_VIEW = "Use shared project view file";
-
-  private final Disposable parentDisposable;
-  private EditorEx projectViewEditor;
-  private JCheckBox useShared;
-
-  private WorkspaceRoot workspaceRoot;
-  private boolean useSharedProjectView;
-  private boolean allowEditShared;
-  private String sharedProjectViewText;
-  private boolean settingsInitialized;
-
-  public ProjectViewUi(Disposable parentDisposable) {
-    this.parentDisposable = parentDisposable;
-  }
-
-  /**
-   * To support the custom language features, we need a ProjectImpl, and it's not desirable to create one from scratch.<br>
-   * @return the current, non-default project, if one exists, else the default project.
-   */
-  public static Project getProject() {
-    Project project = (Project) DataManager.getInstance().getDataContext().getData("project");
-    if (project != null && project instanceof ProjectImpl) {
-      return project;
-    }
-    return ProjectManager.getInstance().getDefaultProject();
-  }
-
-  public static Dimension getMinimumSize() {
-    return new Dimension(1000, 550);
-  }
-
-  private static EditorEx createEditor(String tooltip) {
-    Project project = getProject();
-    LightVirtualFile virtualFile = new LightVirtualFile("mockProjectViewFile", ProjectViewLanguage.INSTANCE, "");
-    final Document document = ((EditorFactoryImpl) EditorFactory.getInstance()).createDocument(true);
-    ((DocumentImpl) document).setAcceptSlashR(true);
-    FileDocumentManagerImpl.registerDocument(document, virtualFile);
-
-    FileManager fileManager = ((PsiManagerEx) PsiManager.getInstance(project)).getFileManager();
-    fileManager.setViewProvider(virtualFile, fileManager.createFileViewProvider(virtualFile, true));
-
-    if (project.isDefault()) {
-      // Undo-redo doesn't work with the default project. Explicitly turn it off to avoid error dialogs.
-      UndoUtil.disableUndoFor(document);
-    }
-
-    EditorEx editor = (EditorEx) EditorFactory.getInstance().createEditor(document, project, ProjectViewFileType.INSTANCE, false);
-    final EditorSettings settings = editor.getSettings();
-    settings.setLineNumbersShown(false);
-    settings.setLineMarkerAreaShown(false);
-    settings.setFoldingOutlineShown(false);
-    settings.setRightMarginShown(false);
-    settings.setAdditionalPageAtBottom(false);
-    editor.getComponent().setMinimumSize(getMinimumSize());
-    editor.getComponent().setPreferredSize(getMinimumSize());
-    editor.getComponent().setToolTipText(tooltip);
-    editor.getComponent().setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, null);
-    editor.getComponent().setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, null);
-    return editor;
-  }
-
-  public void fillUi(JPanel canvas, int indentLevel) {
-    String tooltip = "Enter a project view descriptor file. See 'go/intellij/docs/project-views.md' for more information.";
-
-    projectViewEditor = createEditor(tooltip);
-    projectViewEditor.getColorsScheme().setColor(EditorColors.READONLY_BACKGROUND_COLOR, UIManager.getColor("Label.background"));
-    Disposer.register(parentDisposable, () -> EditorFactory.getInstance().releaseEditor(projectViewEditor));
-
-    JBLabel labelsLabel = new JBLabel("Project View");
-    labelsLabel.setToolTipText(tooltip);
-    canvas.add(labelsLabel, UiUtil.getFillLineConstraints(indentLevel));
-
-    canvas.add(projectViewEditor.getComponent(), UiUtil.getFillLineConstraints(indentLevel));
-
-    useShared = new JCheckBox(USE_SHARED_PROJECT_VIEW);
-    useShared.addActionListener(e -> {
-      useSharedProjectView = useShared.isSelected();
-      if (useSharedProjectView) {
-        setProjectViewText(sharedProjectViewText);
-      }
-      updateTextAreasEnabled();
-    });
-    canvas.add(useShared, UiUtil.getFillLineConstraints(indentLevel));
-  }
-
-  public void init(
-    WorkspaceRoot workspaceRoot,
-    String projectViewText,
-    @Nullable String sharedProjectViewText,
-    @Nullable File sharedProjectViewFile,
-    boolean useSharedProjectView,
-    boolean allowEditShared
-  ) {
-    this.workspaceRoot = workspaceRoot;
-    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());
-    }
-
-    useShared.setSelected(useSharedProjectView);
-
-    if (sharedProjectViewText == null) {
-      useShared.setEnabled(false);
-    }
-
-    setDummyWorkspacePathResolverProvider(workspaceRoot);
-    setProjectViewText(projectViewText);
-    settingsInitialized = true;
-  }
-
-  private void setDummyWorkspacePathResolverProvider(WorkspaceRoot workspaceRoot) {
-    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));
-    if (!settingsInitialized) {
-      Disposer.register(parentDisposable, () -> {
-        container.unregisterComponent(key.getName());
-        if (oldProvider != null) {
-          container.registerComponentInstance(key.getName(), oldProvider);
-        }
-      });
-    }
-  }
-
-  private void setProjectViewText(String projectViewText) {
-    new WriteAction() {
-      @Override
-      protected void run(@NotNull Result result) throws Throwable {
-        projectViewEditor.getDocument().setReadOnly(false);
-        projectViewEditor.getDocument().setText(projectViewText);
-      }
-    }.execute();
-    updateTextAreasEnabled();
-  }
-
-  private void updateTextAreasEnabled() {
-    boolean editEnabled = allowEditShared || !useSharedProjectView;
-    projectViewEditor.setViewer(!editEnabled);
-    projectViewEditor.getDocument().setReadOnly(!editEnabled);
-    projectViewEditor.reinitSettings();
-  }
-
-  public ProjectViewSet parseProjectView(final List<IssueOutput> issues) {
-    final String projectViewText = projectViewEditor.getDocument().getText();
-    final OutputSink<IssueOutput> issueCollector = output -> {
-      issues.add(output);
-      return OutputSink.Propagation.Continue;
-    };
-    return Scope.root(context -> {
-      context.addOutputSink(IssueOutput.class, issueCollector);
-      ProjectViewParser projectViewParser = new ProjectViewParser(context, new WorkspacePathResolverImpl(workspaceRoot));
-      projectViewParser.parseProjectView(projectViewText);
-      return projectViewParser.getResult();
-    });
-  }
-
-  public boolean getUseSharedProjectView() {
-    return this.useSharedProjectView;
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/BlazeSyncManager.java b/blaze-base/src/com/google/idea/blaze/base/sync/BlazeSyncManager.java
deleted file mode 100644
index c99f9db..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/BlazeSyncManager.java
+++ /dev/null
@@ -1,66 +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;
-
-import com.google.idea.blaze.base.async.executor.BlazeExecutor;
-import com.google.idea.blaze.base.settings.Blaze;
-import com.google.idea.blaze.base.settings.BlazeImportSettings;
-import com.google.idea.blaze.base.settings.BlazeImportSettingsManager;
-import com.intellij.openapi.components.ServiceManager;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.startup.StartupManager;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Manages syncing and its listeners.
- */
-public class BlazeSyncManager {
-
-  @NotNull
-  private final Project project;
-
-  public BlazeSyncManager(@NotNull Project project) {
-    this.project = project;
-  }
-
-  public static BlazeSyncManager getInstance(@NotNull Project project) {
-    return ServiceManager.getService(project, BlazeSyncManager.class);
-  }
-
-  /**
-   * Requests a project sync with Blaze.
-   */
-  public void requestProjectSync(@NotNull final BlazeSyncParams syncParams) {
-    StartupManager.getInstance(project).runWhenProjectIsInitialized(new Runnable() {
-      @Override
-      public void run() {
-        final BlazeImportSettings importSettings =
-          BlazeImportSettingsManager.getInstance(project).getImportSettings();
-        if (importSettings == null) {
-          throw new IllegalStateException(String.format("Attempt to sync non-%s project.", Blaze.buildSystemName(project)));
-        }
-
-        final BlazeSyncTask syncTask = new BlazeSyncTask(
-          project,
-          importSettings,
-          syncParams
-        );
-
-        BlazeExecutor.submitTask(project, syncTask);
-      }
-    });
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/BlazeSyncParams.java b/blaze-base/src/com/google/idea/blaze/base/sync/BlazeSyncParams.java
deleted file mode 100644
index 9137fd5..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/BlazeSyncParams.java
+++ /dev/null
@@ -1,92 +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;
-
-import com.google.common.collect.ImmutableList;
-import com.google.idea.blaze.base.model.primitives.TargetExpression;
-
-import javax.annotation.concurrent.Immutable;
-import java.util.Collection;
-
-/**
- * Parameters that control the sync.
- */
-@Immutable
-public final class BlazeSyncParams {
-
-  public enum SyncMode {
-    RESTORE_EPHEMERAL_STATE,
-    INCREMENTAL,
-    FULL
-  }
-
-  public static final class Builder {
-    private String title;
-    private SyncMode syncMode;
-    private boolean backgroundSync = false;
-    private boolean doBuild = true;
-    private ImmutableList.Builder<TargetExpression> targetExpressions = ImmutableList.builder();
-
-    public Builder(String title,
-                   SyncMode syncMode) {
-      this.title = title;
-      this.syncMode = syncMode;
-    }
-
-    public Builder setDoBuild(boolean doBuild) {
-      this.doBuild = doBuild;
-      return this;
-    }
-
-    public Builder setBackgroundSync(boolean backgroundSync) {
-      this.backgroundSync = backgroundSync;
-      return this;
-    }
-
-    public Builder addTargetExpression(TargetExpression targetExpression) {
-      this.targetExpressions.add(targetExpression);
-      return this;
-    }
-
-    public Builder addTargetExpressions(Collection<TargetExpression> targets) {
-      this.targetExpressions.addAll(targets);
-      return this;
-    }
-
-    public BlazeSyncParams build() {
-      return new BlazeSyncParams(title, syncMode, backgroundSync, doBuild, targetExpressions.build());
-    }
-  }
-
-  public final String title;
-  public final SyncMode syncMode;
-  public final boolean backgroundSync;
-  public final boolean doBuild;
-  public final ImmutableList<TargetExpression> targetExpressions;
-
-  private BlazeSyncParams(
-    String title,
-    SyncMode syncMode,
-    boolean backgroundSync,
-    boolean doBuild,
-    ImmutableList<TargetExpression> targetExpressions) {
-    this.title = title;
-    this.syncMode = syncMode;
-    this.backgroundSync = backgroundSync;
-    this.doBuild = doBuild;
-    this.targetExpressions = targetExpressions;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/BlazeSyncPlugin.java b/blaze-base/src/com/google/idea/blaze/base/sync/BlazeSyncPlugin.java
deleted file mode 100644
index 2f65e17..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/BlazeSyncPlugin.java
+++ /dev/null
@@ -1,290 +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;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.model.BlazeProjectData;
-import com.google.idea.blaze.base.model.SyncState;
-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.WorkspaceRoot;
-import com.google.idea.blaze.base.model.primitives.WorkspaceType;
-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.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.projectview.WorkspaceLanguageSettings;
-import com.intellij.openapi.extensions.ExtensionPointName;
-import com.intellij.openapi.module.Module;
-import com.intellij.openapi.module.ModuleType;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.roots.ContentEntry;
-import com.intellij.openapi.roots.ModifiableRootModel;
-
-import javax.annotation.Nullable;
-import java.io.File;
-import java.util.Collection;
-import java.util.Set;
-
-/**
- * Can plug into the blaze sync system.
- */
-public interface BlazeSyncPlugin {
-  ExtensionPointName<BlazeSyncPlugin> EP_NAME = ExtensionPointName.create("com.google.idea.blaze.SyncPlugin");
-
-  /**
-   * May be used by the plugin to create/edit modules.
-   *
-   * Using this ensures that the blaze plugin is aware of the modules,
-   * won't garbage collect them, and that all module modifications
-   * happen in a single transaction.
-   */
-  interface ModuleEditor {
-    /**
-     * Creates a new module and registers it with the module editor.
-     */
-    Module createModule(String moduleName, ModuleType moduleType);
-
-    /**
-     * Edits a module. It will be committed when commit is called.
-     *
-     * <p>The module will be returned in a cleared state. You should
-     * not call this method multiple times.
-     */
-    ModifiableRootModel editModule(Module module);
-
-    /**
-     * Registers a module. This prevents garbage collection of
-     * the module upon commit.
-     *
-     * @return True if the module exists and was registered.
-     */
-    boolean registerModule(String moduleName);
-
-    /**
-     * Finds a module by name. This doesn't register the module.
-     */
-    @Nullable
-    Module findModule(String moduleName);
-
-    /**
-     * Commits the module editor without garbage collection.
-     */
-    void commit();
-  }
-
-  /**
-   * @return The default workspace type recommended by this plugin.
-   */
-  @Nullable
-  WorkspaceType getDefaultWorkspaceType();
-
-  /**
-   * @return The module type for the workspace given the workspace type.
-   */
-  @Nullable
-  ModuleType getWorkspaceModuleType(WorkspaceType workspaceType);
-
-  /**
-   * @return The set of supported languages under this workspace type.
-   */
-  Set<LanguageClass> getSupportedLanguagesInWorkspace(WorkspaceType workspaceType);
-
-  void updateSyncState(
-    Project project,
-    BlazeContext context,
-    WorkspaceRoot workspaceRoot,
-    ProjectViewSet projectViewSet,
-    WorkspaceLanguageSettings workspaceLanguageSettings,
-    BlazeRoots blazeRoots,
-    @Nullable WorkingSet workingSet,
-    WorkspacePathResolver workspacePathResolver,
-    ImmutableMap<Label, RuleIdeInfo> ruleMap,
-    @Deprecated @Nullable File androidPlatformDirectory,
-    SyncState.Builder syncStateBuilder,
-    @Nullable SyncState previousSyncState
-  );
-
-  /**
-   * Updates the sdk.
-   */
-  void updateSdk(Project project,
-                 BlazeContext context,
-                 ProjectViewSet projectViewSet,
-                 BlazeProjectData blazeProjectData);
-
-  /**
-   * Modify the project content entries. There will be one content entry
-   * per project directory from the project view set.
-   */
-  void updateContentEntries(Project project,
-                            BlazeContext context,
-                            WorkspaceRoot workspaceRoot,
-                            ProjectViewSet projectViewSet,
-                            BlazeProjectData blazeProjectData,
-                            Collection<ContentEntry> contentEntries);
-
-  void updateProjectStructure(
-    Project project,
-    BlazeContext context,
-    WorkspaceRoot workspaceRoot,
-    ProjectViewSet projectViewSet,
-    BlazeProjectData blazeProjectData,
-    @Nullable BlazeProjectData oldBlazeProjectData,
-    ModuleEditor moduleEditor,
-    Module workspaceModule,
-    ModifiableRootModel workspaceModifiableModel
-  );
-
-  /**
-   * Validates the project.
-   */
-  boolean validate(Project project,
-                   BlazeContext context,
-                   BlazeProjectData blazeProjectData);
-
-  /**
-   * Validates the project view.
-   * @return True for success, false for fatal error.
-   */
-  boolean validateProjectView(
-    BlazeContext context,
-    ProjectViewSet projectViewSet,
-    WorkspaceLanguageSettings workspaceLanguageSettings
-  );
-
-  /**
-   * Returns any custom sections that this plugin supports.
-   */
-  Collection<SectionParser> getSections();
-
-  /**
-   * Returns whether this plugin requires resolving ide artifacts to function.
-   */
-  boolean requiresResolveIdeArtifacts();
-
-  /**
-   * Whether this plugin requires an Android SDK to function.
-   */
-  boolean requiresAndroidSdk(WorkspaceLanguageSettings workspaceLanguageSettings);
-
-  /**
-   * Returns any source file extensions that are a good candidate for the {@link com.google.idea.blaze.base.prefetch.Prefetcher}.
-   */
-  Set<String> prefetchSrcFileExtensions();
-
-  class Adapter implements BlazeSyncPlugin {
-
-    @Nullable
-    @Override
-    public WorkspaceType getDefaultWorkspaceType() {
-      return null;
-    }
-
-    @Nullable
-    @Override
-    public ModuleType getWorkspaceModuleType(WorkspaceType workspaceType) {
-      return null;
-    }
-
-    @Override
-    public Set<LanguageClass> getSupportedLanguagesInWorkspace(WorkspaceType workspaceType) {
-      return ImmutableSet.of();
-    }
-
-    @Override
-    public void updateSyncState(Project project,
-                                BlazeContext context,
-                                WorkspaceRoot workspaceRoot,
-                                ProjectViewSet projectViewSet,
-                                WorkspaceLanguageSettings workspaceLanguageSettings,
-                                BlazeRoots blazeRoots,
-                                @Nullable WorkingSet workingSet,
-                                WorkspacePathResolver workspacePathResolver,
-                                ImmutableMap<Label, RuleIdeInfo> ruleMap,
-                                @Deprecated @Nullable File androidPlatformDirectory,
-                                SyncState.Builder syncStateBuilder,
-                                @Nullable SyncState previousSyncState) {
-    }
-
-    @Override
-    public void updateSdk(Project project,
-                          BlazeContext context,
-                          ProjectViewSet projectViewSet,
-                          BlazeProjectData blazeProjectData) {
-    }
-
-    @Override
-    public void updateContentEntries(Project project,
-                                     BlazeContext context,
-                                     WorkspaceRoot workspaceRoot,
-                                     ProjectViewSet projectViewSet,
-                                     BlazeProjectData blazeProjectData,
-                                     Collection<ContentEntry> contentEntries) {
-    }
-
-    @Override
-    public void updateProjectStructure(Project project,
-                                       BlazeContext context,
-                                       WorkspaceRoot workspaceRoot,
-                                       ProjectViewSet projectViewSet,
-                                       BlazeProjectData blazeProjectData,
-                                       @Nullable BlazeProjectData oldBlazeProjectData,
-                                       ModuleEditor moduleEditor,
-                                       Module workspaceModule,
-                                       ModifiableRootModel workspaceModifiableModel) {
-    }
-
-    @Override
-    public boolean validate(Project project,
-                            BlazeContext context,
-                            BlazeProjectData blazeProjectData) {
-      return true;
-    }
-
-    @Override
-    public boolean validateProjectView(BlazeContext context,
-                                       ProjectViewSet projectViewSet,
-                                       WorkspaceLanguageSettings workspaceLanguageSettings) {
-      return true;
-    }
-
-    @Override
-    public Collection<SectionParser> getSections() {
-      return ImmutableList.of();
-    }
-
-    @Override
-    public boolean requiresResolveIdeArtifacts() {
-      return false;
-    }
-
-    @Override
-    public boolean requiresAndroidSdk(WorkspaceLanguageSettings workspaceLanguageSettings) {
-      return false;
-    }
-
-    @Override
-    public Set<String> prefetchSrcFileExtensions() {
-      return ImmutableSet.of();
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/BlazeSyncStartupActivity.java b/blaze-base/src/com/google/idea/blaze/base/sync/BlazeSyncStartupActivity.java
deleted file mode 100644
index 726a5ae..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/BlazeSyncStartupActivity.java
+++ /dev/null
@@ -1,50 +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;
-
-import com.google.idea.blaze.base.settings.BlazeImportSettings;
-import com.google.idea.blaze.base.settings.BlazeImportSettingsManager;
-import com.google.idea.blaze.base.sync.actions.IncrementalSyncProjectAction;
-import com.google.idea.blaze.base.sync.status.BlazeSyncStatusImpl;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.startup.StartupActivity;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Syncs the project upon startup.
- */
-public class BlazeSyncStartupActivity implements StartupActivity {
-
-  @Override
-  public void runActivity(@NotNull final Project project) {
-    BlazeImportSettings importSettings = BlazeImportSettingsManager.getInstance(project)
-      .getImportSettings();
-
-    if (importSettings != null) {
-      BlazeSyncManager.getInstance(project).requestProjectSync(getSyncParams());
-    }
-  }
-
-  private static BlazeSyncParams getSyncParams() {
-    if (BlazeSyncStatusImpl.AUTOMATIC_INCREMENTAL_SYNC.getValue()) {
-      return IncrementalSyncProjectAction.startupSyncParams;
-    }
-    return new BlazeSyncParams.Builder(
-      "Sync Project",
-      BlazeSyncParams.SyncMode.RESTORE_EPHEMERAL_STATE)
-      .build();
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/BlazeSyncTask.java b/blaze-base/src/com/google/idea/blaze/base/sync/BlazeSyncTask.java
deleted file mode 100755
index 788906c..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/BlazeSyncTask.java
+++ /dev/null
@@ -1,679 +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;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableMultimap;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListeningExecutorService;
-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.experiments.ExperimentScope;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.metrics.Action;
-import com.google.idea.blaze.base.model.BlazeProjectData;
-import com.google.idea.blaze.base.model.SyncState;
-import com.google.idea.blaze.base.model.primitives.Label;
-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.ProjectViewManager;
-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;
-import com.google.idea.blaze.base.scope.output.PrintOutput;
-import com.google.idea.blaze.base.scope.output.StatusOutput;
-import com.google.idea.blaze.base.scope.scopes.*;
-import com.google.idea.blaze.base.settings.Blaze;
-import com.google.idea.blaze.base.settings.BlazeImportSettings;
-import com.google.idea.blaze.base.settings.BlazeUserSettings;
-import com.google.idea.blaze.base.sync.BlazeSyncParams.SyncMode;
-import com.google.idea.blaze.base.sync.BlazeSyncPlugin.ModuleEditor;
-import com.google.idea.blaze.base.sync.aspects.BlazeIdeInterface;
-import com.google.idea.blaze.base.sync.data.BlazeProjectDataManagerImpl;
-import com.google.idea.blaze.base.sync.projectstructure.ContentEntryEditor;
-import com.google.idea.blaze.base.sync.projectstructure.ModuleDataStorage;
-import com.google.idea.blaze.base.sync.projectstructure.ModuleEditorImpl;
-import com.google.idea.blaze.base.sync.projectstructure.ModuleEditorProvider;
-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.*;
-import com.google.idea.blaze.base.util.SaveUtil;
-import com.google.idea.blaze.base.vcs.BlazeVcsHandler;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.module.Module;
-import com.intellij.openapi.module.ModuleType;
-import com.intellij.openapi.progress.ProgressIndicator;
-import com.intellij.openapi.progress.Progressive;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.roots.ContentEntry;
-import com.intellij.openapi.roots.ModifiableRootModel;
-import com.intellij.openapi.roots.ex.ProjectRootManagerEx;
-import com.intellij.openapi.util.io.FileUtil;
-import com.intellij.openapi.vfs.LocalFileSystem;
-import com.intellij.openapi.vfs.StandardFileSystems;
-import com.intellij.openapi.vfs.VirtualFileManager;
-
-import javax.annotation.Nullable;
-import java.io.File;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.stream.Collectors;
-
-
-/**
- * Syncs the project with blaze.
- */
-final class BlazeSyncTask implements Progressive {
-
-  private final static Logger LOG = Logger.getInstance(BlazeSyncTask.class);
-
-  private final Project project;
-  private final BlazeImportSettings importSettings;
-  private final WorkspaceRoot workspaceRoot;
-  private final BlazeSyncParams syncParams;
-  private final boolean expandSyncToWorkingSet;
-  private final boolean showPerformanceWarnings;
-  private long syncStartTime;
-
-  BlazeSyncTask(
-    Project project,
-    BlazeImportSettings importSettings,
-    final BlazeSyncParams syncParams) {
-    this.project = project;
-    this.importSettings = importSettings;
-    this.workspaceRoot = WorkspaceRoot.fromImportSettings(importSettings);
-    this.syncParams = syncParams;
-    this.expandSyncToWorkingSet = BlazeUserSettings.getInstance().getExpandSyncToWorkingSet()
-                                  && ExpandWorkingSetTargetsExperiment.ENABLE_EXPAND_WORKING_SET_TARGETS.getValue();
-    this.showPerformanceWarnings = BlazeUserSettings.getInstance().getShowPerformanceWarnings();
-  }
-
-
-  @Override
-  public void run(final ProgressIndicator indicator) {
-    Scope.root((BlazeContext context) -> {
-      context.push(new ExperimentScope());
-      if (showPerformanceWarnings) {
-        context.push(new PerformanceWarningScope());
-      }
-      context
-        .push(new ProgressIndicatorScope(indicator))
-        .push(new TimingScope("Sync"))
-        .push(new LoggedTimingScope(project, Action.SYNC_TOTAL_TIME))
-      ;
-
-      if (!syncParams.backgroundSync) {
-        context
-          .push(new BlazeConsoleScope.Builder(project, indicator).build())
-          .push(new IssuesScope(project))
-          .push(new NotificationScope(
-            project,
-            "Sync",
-            "Sync project",
-            "Sync successful",
-            "Sync failed"
-          ))
-        ;
-      }
-
-      context.output(new StatusOutput("Syncing project..."));
-      syncProject(context);
-    });
-  }
-
-  /**
-   * Returns true if sync successfully completed
-   */
-  @VisibleForTesting
-  boolean syncProject(BlazeContext context) {
-    boolean success = false;
-    try {
-      SaveUtil.saveAllFiles();
-      onSyncStart(project);
-      success = doSyncProject(context);
-    } catch (AssertionError|Exception e) {
-      LOG.error(e);
-      IssueOutput.error("Internal error: " + e.getMessage()).submit(context);
-    } finally {
-      afterSync(project, success);
-    }
-    return success;
-  }
-
-  /**
-   * @return true if sync successfully completed
-   */
-  private boolean doSyncProject(final BlazeContext context) {
-    this.syncStartTime = System.currentTimeMillis();
-
-    if (importSettings.getProjectViewFile() == null) {
-      IssueOutput.error(
-        "This project looks like it's been opened from an old version of ASwB. "
-        + "That is unfortunately not supported. Please reimport your project."
-      ).submit(context);
-      return false;
-    }
-
-    @Nullable BlazeProjectData oldBlazeProjectData = null;
-    if (syncParams.syncMode != SyncMode.FULL) {
-      oldBlazeProjectData = BlazeProjectDataManagerImpl.getImpl(project).loadProjectRoot(context, importSettings);
-    }
-
-    BlazeVcsHandler vcsHandler = null;
-    for (BlazeVcsHandler candidate : BlazeVcsHandler.EP_NAME.getExtensions()) {
-      if (candidate.handlesProject(project, workspaceRoot)) {
-        vcsHandler = candidate;
-        break;
-      }
-    }
-    if (vcsHandler == null) {
-      IssueOutput.error("Could not find a VCS handler").submit(context);
-      return false;
-    }
-
-    ListeningExecutorService executor = BlazeExecutor.getInstance().getExecutor();
-    ListenableFuture<BlazeRoots> blazeRootsFuture = BlazeRoots.compute(project, workspaceRoot, context);
-    ListenableFuture<WorkingSet> workingSetFuture = vcsHandler.getWorkingSet(project, workspaceRoot, executor);
-
-    BlazeRoots blazeRoots = FutureUtil.waitForFuture(context, blazeRootsFuture)
-      .timed(Blaze.buildSystemName(project) + "Roots")
-      .withProgressMessage(String.format("Running %s info...", Blaze.buildSystemName(project)))
-      .onError(String.format("Could not get %s roots", Blaze.buildSystemName(project)))
-      .run()
-      .result();
-    if (blazeRoots == null) {
-      return false;
-    }
-
-    WorkspacePathResolverAndProjectView workspacePathResolverAndProjectView = computeWorkspacePathResolverAndProjectView(
-      context,
-      blazeRoots,
-      vcsHandler,
-      executor
-    );
-    if (workspacePathResolverAndProjectView == null) {
-      return false;
-    }
-    WorkspacePathResolver workspacePathResolver = workspacePathResolverAndProjectView.workspacePathResolver;
-    ProjectViewSet projectViewSet = workspacePathResolverAndProjectView.projectViewSet;
-
-    WorkspaceLanguageSettings workspaceLanguageSettings = LanguageSupport.createWorkspaceLanguageSettings(context, projectViewSet);
-    if (workspaceLanguageSettings == null) {
-      return false;
-    }
-
-    if (!ProjectViewVerifier.verifyProjectView(context, workspaceRoot, projectViewSet, workspaceLanguageSettings)) {
-      return false;
-    }
-
-    final BlazeProjectData newBlazeProjectData;
-
-    WorkingSet workingSet = FutureUtil.waitForFuture(context, workingSetFuture)
-      .timed("WorkingSet")
-      .withProgressMessage("Computing VCS working set...")
-      .onError("Could not compute working set")
-      .run()
-      .result();
-    if (!context.shouldContinue()) {
-      return false;
-    }
-
-    if (workingSet != null) {
-      printWorkingSet(context, workingSet);
-    }
-
-    boolean ideResolveErrors = false;
-    if (syncParams.syncMode != SyncMode.RESTORE_EPHEMERAL_STATE || oldBlazeProjectData == null) {
-      SyncState.Builder syncStateBuilder = new SyncState.Builder();
-      SyncState previousSyncState = oldBlazeProjectData != null ? oldBlazeProjectData.syncState : null;
-      List<TargetExpression> allTargets = projectViewSet.listItems(TargetSection.KEY);
-      if (expandSyncToWorkingSet && workingSet != null) {
-        allTargets.addAll(getWorkingSetTargets(workingSet));
-      }
-
-      boolean syncPluginRequiresBuild = false;
-      boolean requiresAndroidSdk = false;
-      for (BlazeSyncPlugin syncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) {
-        syncPluginRequiresBuild |= syncPlugin.requiresResolveIdeArtifacts();
-        requiresAndroidSdk |= syncPlugin.requiresAndroidSdk(workspaceLanguageSettings);
-      }
-
-      final BlazeIdeInterface.IdeResult ideQueryResult = getIdeQueryResult(
-        project,
-        context,
-        projectViewSet,
-        allTargets,
-        workspaceLanguageSettings,
-        new ArtifactLocationDecoder(blazeRoots, workspacePathResolver),
-        syncStateBuilder,
-        previousSyncState,
-        requiresAndroidSdk
-      );
-      if (ideQueryResult == null) {
-        if (workingSet != null && !workingSet.isEmpty() && expandSyncToWorkingSet && !context.isCancelled()) {
-          String msg = String.format("If you have broken targets in your VCS working set, uncheck '%s > Expand Sync to Working Set'" +
-                                     " and try again.", Blaze.buildSystemName(project));
-          context.output(new PrintOutput(msg, PrintOutput.OutputType.ERROR));
-        }
-        return false;
-      }
-      ImmutableMap<Label, RuleIdeInfo> ruleMap = ideQueryResult.ruleMap;
-
-      ListenableFuture<ImmutableMultimap<Label, Label>> reverseDependenciesFuture =
-        BlazeExecutor.getInstance().submit(() -> ReverseDependencyMap.createRdepsMap(ruleMap));
-
-      boolean doBuild = syncPluginRequiresBuild || (syncParams.doBuild || oldBlazeProjectData == null);
-      if (doBuild) {
-        List<TargetExpression> targetExpressions = Lists.newArrayList(syncParams.targetExpressions);
-        if (targetExpressions.isEmpty()) {
-          targetExpressions.addAll(allTargets);
-        }
-        ideResolveErrors = !resolveIdeArtifacts(project, context, workspaceRoot, projectViewSet, targetExpressions);
-        LocalFileSystem.getInstance().refresh(true);
-        if (context.isCancelled()) {
-          return false;
-        }
-      }
-
-      Scope.push(context, (childContext) -> {
-        childContext.push(new TimingScope("UpdateSyncState"));
-        for (BlazeSyncPlugin syncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) {
-          syncPlugin.updateSyncState(
-            project,
-            childContext,
-            workspaceRoot,
-            projectViewSet,
-            workspaceLanguageSettings,
-            blazeRoots,
-            workingSet,
-            workspacePathResolver,
-            ruleMap,
-            ideQueryResult.androidPlatformDirectory,
-            syncStateBuilder,
-            previousSyncState);
-        }
-      });
-
-      ImmutableMultimap<Label, Label> reverseDependencies = FutureUtil.waitForFuture(context, reverseDependenciesFuture)
-        .timed("ReverseDependencies")
-        .onError("Failed to compute reverse dependency map")
-        .run()
-        .result();
-      if (reverseDependencies == null) {
-        return false;
-      }
-
-      newBlazeProjectData = new BlazeProjectData(
-        syncStartTime,
-        ruleMap,
-        blazeRoots,
-        workingSet,
-        workspacePathResolver,
-        workspaceLanguageSettings,
-        syncStateBuilder.build(),
-        reverseDependencies
-      );
-    } else {
-      // Restore project based on old blaze project data
-      newBlazeProjectData = oldBlazeProjectData;
-    }
-
-    boolean success = updateProject(project, context, projectViewSet, oldBlazeProjectData, newBlazeProjectData);
-    if (!success) {
-      return false;
-    }
-
-    if (ideResolveErrors) {
-      context.output(new PrintOutput(
-        "Sync was successful, but there were compilation errors. The project may not fully resolve until fixed.",
-        PrintOutput.OutputType.ERROR
-      ));
-    }
-
-    onSyncComplete(project, context, projectViewSet, newBlazeProjectData);
-    return true;
-  }
-
-  static class WorkspacePathResolverAndProjectView {
-    final WorkspacePathResolver workspacePathResolver;
-    final ProjectViewSet projectViewSet;
-    public WorkspacePathResolverAndProjectView(WorkspacePathResolver workspacePathResolver,
-                                               ProjectViewSet projectViewSet) {
-      this.workspacePathResolver = workspacePathResolver;
-      this.projectViewSet = projectViewSet;
-    }
-  }
-  private WorkspacePathResolverAndProjectView computeWorkspacePathResolverAndProjectView(BlazeContext context,
-                                                                                         BlazeRoots blazeRoots,
-                                                                                         BlazeVcsHandler vcsHandler,
-                                                                                         ListeningExecutorService executor) {
-    for (int i = 0; i < 3; ++i) {
-      WorkspacePathResolver vcsWorkspacePathResolver = null;
-      BlazeVcsHandler.BlazeVcsSyncHandler vcsSyncHandler = vcsHandler.createSyncHandler(project, workspaceRoot);
-      if (vcsSyncHandler != null) {
-        boolean ok = Scope.push(context, (childContext) -> {
-          childContext.push(new TimingScope("UpdateVcs"));
-          return vcsSyncHandler.update(context, blazeRoots, executor);
-        });
-        if (!ok) {
-          return null;
-        }
-        vcsWorkspacePathResolver = vcsSyncHandler.getWorkspacePathResolver();
-      }
-
-      WorkspacePathResolver workspacePathResolver = vcsWorkspacePathResolver != null
-                                                    ? vcsWorkspacePathResolver
-                                                    : new WorkspacePathResolverImpl(workspaceRoot, blazeRoots);
-
-      ProjectViewSet projectViewSet = ProjectViewManager.getInstance(project).reloadProjectView(context, workspacePathResolver);
-      if (projectViewSet == null) {
-        return null;
-      }
-
-      if (vcsSyncHandler != null) {
-        BlazeVcsHandler.BlazeVcsSyncHandler.ValidationResult validationResult =
-          vcsSyncHandler.validateProjectView(context, projectViewSet);
-        switch (validationResult) {
-          case OK:
-            // Fall-through and return
-            break;
-          case Error:
-            return null;
-          case RestartSync:
-            continue;
-          default:
-            // Cannot happen
-            return null;
-        }
-      }
-
-      return new WorkspacePathResolverAndProjectView(workspacePathResolver, projectViewSet);
-    }
-    return null;
-  }
-
-  private void printWorkingSet(BlazeContext context, WorkingSet workingSet) {
-    List<String> messages = Lists.newArrayList();
-    messages.addAll(workingSet.addedFiles.stream().map(file -> file.relativePath() + " (added)").collect(Collectors.toList()));
-    messages.addAll(workingSet.modifiedFiles.stream().map(file -> file.relativePath() + " (modified)").collect(Collectors.toList()));
-    Collections.sort(messages);
-
-    if (messages.isEmpty()) {
-      context.output(new PrintOutput("Your working set is empty"));
-      return;
-    }
-    int maxFiles = 20;
-    for (String message : Iterables.limit(messages, maxFiles)) {
-      context.output(new PrintOutput("  " + message));
-    }
-    if (messages.size() > maxFiles) {
-      context.output(new PrintOutput(String.format("  (and %d more)", messages.size() - maxFiles)));
-    }
-  }
-
-  private Collection<? extends TargetExpression> getWorkingSetTargets(WorkingSet workingSet) {
-    List<TargetExpression> result = Lists.newArrayList();
-    for (WorkspacePath workspacePath : Iterables.concat(workingSet.addedFiles, workingSet.modifiedFiles)) {
-      File buildFile = workspaceRoot.fileForPath(workspacePath);
-      if (buildFile.getName().equals("BUILD")) {
-        result.add(TargetExpression.allFromPackageNonRecursive(workspaceRoot.workspacePathFor(buildFile.getParentFile())));
-      }
-    }
-    return result;
-  }
-
-
-  private boolean updateProject(Project project,
-                                BlazeContext parentContext,
-                                ProjectViewSet projectViewSet,
-                                @Nullable BlazeProjectData oldBlazeProjectData,
-                                BlazeProjectData newBlazeProjectData) {
-    return Scope.push(parentContext, context -> {
-      context
-        .push(new LoggedTimingScope(project, Action.SYNC_IMPORT_DATA_TIME))
-        .push(new TimingScope("UpdateProjectStructure"));
-      context.output(new StatusOutput("Committing project structure..."));
-
-      return updateProject(
-        context,
-        importSettings,
-        projectViewSet,
-        oldBlazeProjectData,
-        newBlazeProjectData
-      );
-    });
-  }
-
-  @Nullable
-  private BlazeIdeInterface.IdeResult getIdeQueryResult(
-    Project project,
-    BlazeContext parentContext,
-    ProjectViewSet projectViewSet,
-    List<TargetExpression> targets,
-    WorkspaceLanguageSettings workspaceLanguageSettings,
-    ArtifactLocationDecoder artifactLocationDecoder,
-    SyncState.Builder syncStateBuilder,
-    @Nullable SyncState previousSyncState,
-    boolean requiresAndroidSdk) {
-
-    return Scope.push(parentContext, context -> {
-      context.push(new TimingScope("IdeQuery"));
-
-      BlazeIdeInterface blazeIdeInterface = BlazeIdeInterface.getInstance();
-      return blazeIdeInterface.updateBlazeIdeState(
-        project,
-        context,
-        workspaceRoot,
-        projectViewSet,
-        targets,
-        workspaceLanguageSettings,
-        artifactLocationDecoder,
-        syncStateBuilder,
-        previousSyncState,
-        requiresAndroidSdk
-      );
-    });
-  }
-
-  private static boolean resolveIdeArtifacts(
-    Project project,
-    BlazeContext parentContext,
-    WorkspaceRoot workspaceRoot,
-    ProjectViewSet projectViewSet,
-    List<TargetExpression> targetExpressions) {
-    return Scope.push(parentContext, context -> {
-      context
-        .push(new LoggedTimingScope(project, Action.BLAZE_BUILD_DURING_SYNC))
-        .push(new TimingScope("BlazeBuild"))
-      ;
-      context.output(new StatusOutput("Building project dependencies..."));
-
-      // We don't want errors propagated for the build step - compilation errors shouldn't be interpreted
-      // as "Sync failed"
-      context.setPropagatesErrors(false);
-
-      if (!targetExpressions.isEmpty()) {
-        BlazeIdeInterface blazeIdeInterface = BlazeIdeInterface.getInstance();
-        blazeIdeInterface.resolveIdeArtifacts(project, context, workspaceRoot, projectViewSet, targetExpressions);
-      }
-
-      return !context.hasErrors();
-    });
-  }
-
-  private boolean updateProject(
-    BlazeContext context,
-    BlazeImportSettings importSettings,
-    ProjectViewSet projectViewSet,
-    @Nullable BlazeProjectData oldBlazeProjectData,
-    BlazeProjectData newBlazeProjectData) {
-
-    try {
-      AsyncUtil.executeProjectChangeAction(() -> ProjectRootManagerEx.getInstanceEx(project).mergeRootsChangesDuring(() -> {
-        updateSdk(
-          context,
-          projectViewSet,
-          newBlazeProjectData
-        );
-        updateProjectStructure(
-          context,
-          importSettings,
-          projectViewSet,
-          oldBlazeProjectData,
-          newBlazeProjectData
-        );
-      }));
-    } catch (Throwable t) {
-      IssueOutput.error("Internal error. Please issue a bug at go/aswbbug. Error: " + t)
-        .submit(context);
-      LOG.error(t);
-      return false;
-    }
-
-    BlazeProjectDataManagerImpl.getImpl(project).saveProject(importSettings, newBlazeProjectData);
-    return true;
-  }
-
-  private void updateSdk(BlazeContext context,
-                         ProjectViewSet projectViewSet,
-                         BlazeProjectData newBlazeProjectData) {
-    for (BlazeSyncPlugin syncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) {
-      syncPlugin.updateSdk(project, context, projectViewSet, newBlazeProjectData);
-    }
-  }
-
-  private void updateProjectStructure(
-    BlazeContext context,
-    BlazeImportSettings importSettings,
-    ProjectViewSet projectViewSet,
-    @Nullable BlazeProjectData oldBlazeProjectData,
-    BlazeProjectData newBlazeProjectData) {
-
-    ModuleEditorImpl moduleEditor = ModuleEditorProvider.getInstance().getModuleEditor(project, importSettings);
-
-    ModuleType workspaceModuleType = null;
-    for (BlazeSyncPlugin syncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) {
-      workspaceModuleType = syncPlugin.getWorkspaceModuleType(newBlazeProjectData.workspaceLanguageSettings.getWorkspaceType());
-      if (workspaceModuleType != null) {
-        break;
-      }
-    }
-    if (workspaceModuleType == null) {
-      workspaceModuleType = ModuleType.EMPTY;
-      IssueOutput.warn("Could not set module type for workspace module.").submit(context);
-    }
-
-    Module workspaceModule = moduleEditor.createModule(ModuleDataStorage.WORKSPACE_MODULE_NAME, workspaceModuleType);
-    ModifiableRootModel workspaceModifiableModel = moduleEditor.editModule(workspaceModule);
-
-    ContentEntryEditor.createContentEntries(
-      project,
-      context,
-      workspaceRoot,
-      projectViewSet,
-      newBlazeProjectData,
-      workspaceModifiableModel
-    );
-
-    for (BlazeSyncPlugin blazeSyncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) {
-      blazeSyncPlugin.updateProjectStructure(
-        project,
-        context,
-        workspaceRoot,
-        projectViewSet,
-        newBlazeProjectData,
-        oldBlazeProjectData,
-        moduleEditor,
-        workspaceModule,
-        workspaceModifiableModel);
-    }
-
-    createProjectDataDirectoryModule(moduleEditor, new File(importSettings.getProjectDataDirectory()), workspaceModuleType);
-
-    moduleEditor.commitWithGc(context);
-  }
-
-  /**
-   * Creates a module that includes the user's data directory.
-   *
-   * This is useful to be able to edit the project view without IntelliJ complaining it's outside the project.
-   */
-  private void createProjectDataDirectoryModule(ModuleEditor moduleEditor,
-                                                File projectDataDirectory,
-                                                ModuleType moduleType) {
-    Module module = moduleEditor.createModule(ModuleDataStorage.PROJECT_DATA_DIR_MODULE_NAME, moduleType);
-    ModifiableRootModel modifiableModel = moduleEditor.editModule(module);
-    ContentEntry rootContentEntry = modifiableModel.addContentEntry(pathToUrl(projectDataDirectory));
-    rootContentEntry.addExcludeFolder(pathToUrl(new File(projectDataDirectory, ".idea")));
-    rootContentEntry.addExcludeFolder(pathToUrl(new File(projectDataDirectory, ModuleDataStorage.DATA_SUBDIRECTORY)));
-  }
-
-  private static String pathToUrl(File path) {
-    String filePath = FileUtil.toSystemIndependentName(path.getPath());
-    return VirtualFileManager.constructUrl(StandardFileSystems.FILE_PROTOCOL, filePath);
-  }
-
-  private static void onSyncStart(Project project) {
-    final SyncListener[] syncListeners = SyncListener.EP_NAME.getExtensions();
-    for (SyncListener syncListener : syncListeners) {
-      syncListener.onSyncStart(project);
-    }
-  }
-
-  private static void afterSync(Project project,
-                                boolean successful) {
-    final SyncListener[] syncListeners = SyncListener.EP_NAME.getExtensions();
-    for (SyncListener syncListener : syncListeners) {
-      syncListener.afterSync(project, successful);
-    }
-  }
-
-  private void onSyncComplete(Project project,
-                              BlazeContext context,
-                              ProjectViewSet projectViewSet,
-                              BlazeProjectData blazeProjectData) {
-    validate(project, context, blazeProjectData);
-
-    final SyncListener[] syncListeners = SyncListener.EP_NAME.getExtensions();
-    for (SyncListener syncListener : syncListeners) {
-      syncListener.onSyncComplete(
-        project,
-        importSettings,
-        projectViewSet,
-        blazeProjectData
-      );
-    }
-  }
-
-  private static void validate(
-    Project project,
-    BlazeContext context,
-    BlazeProjectData blazeProjectData) {
-    for (BlazeSyncPlugin syncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) {
-      syncPlugin.validate(project, context, blazeProjectData);
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/ExpandWorkingSetTargetsExperiment.java b/blaze-base/src/com/google/idea/blaze/base/sync/ExpandWorkingSetTargetsExperiment.java
deleted file mode 100644
index 29e189e..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/ExpandWorkingSetTargetsExperiment.java
+++ /dev/null
@@ -1,25 +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;
-
-import com.google.idea.blaze.base.experiments.BoolExperiment;
-
-/**
- * Experiment holder for the working set experiment.
- */
-public class ExpandWorkingSetTargetsExperiment {
-  public static final BoolExperiment ENABLE_EXPAND_WORKING_SET_TARGETS = new BoolExperiment("enable.expand.working.set.targets", true);
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/SyncListener.java b/blaze-base/src/com/google/idea/blaze/base/sync/SyncListener.java
deleted file mode 100644
index 4e7fd71..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/SyncListener.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.sync;
-
-import com.google.idea.blaze.base.model.BlazeProjectData;
-import com.google.idea.blaze.base.projectview.ProjectViewSet;
-import com.google.idea.blaze.base.settings.BlazeImportSettings;
-import com.intellij.openapi.extensions.ExtensionPointName;
-import com.intellij.openapi.project.Project;
-
-/**
- * Extension interface for listening to syncs.
- */
-public interface SyncListener {
-  ExtensionPointName<SyncListener> EP_NAME = ExtensionPointName
-    .create("com.google.idea.blaze.SyncListener");
-
-  /**
-   * Called after open documents have been saved, prior to starting the blaze sync.
-   */
-  void onSyncStart(Project project);
-
-  /**
-   * Called on successful completion of a sync
-   */
-  void onSyncComplete(
-    Project project,
-    BlazeImportSettings importSettings,
-    ProjectViewSet projectViewSet,
-    BlazeProjectData blazeProjectData);
-
-  /**
-   * Guaranteed to be called once per sync,
-   * regardless of whether it successfully completed
-   */
-  void afterSync(Project project,
-                 boolean successful);
-
-  /**
-   * Convenience adapter class.
-   */
-  abstract class Adapter implements SyncListener {
-
-    @Override
-    public void onSyncStart(Project project) {
-    }
-
-    @Override
-    public void onSyncComplete(Project project,
-                               BlazeImportSettings importSettings,
-                               ProjectViewSet projectViewSet,
-                               BlazeProjectData blazeProjectData) {
-    }
-
-    @Override
-    public void afterSync(Project project, boolean successful) {
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/actions/ExpandSyncToWorkingSetAction.java b/blaze-base/src/com/google/idea/blaze/base/sync/actions/ExpandSyncToWorkingSetAction.java
deleted file mode 100644
index 5b577a9..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/actions/ExpandSyncToWorkingSetAction.java
+++ /dev/null
@@ -1,45 +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.actions;
-
-import com.google.idea.blaze.base.actions.BlazeToggleAction;
-import com.google.idea.blaze.base.settings.BlazeUserSettings;
-import com.google.idea.blaze.base.sync.ExpandWorkingSetTargetsExperiment;
-import com.intellij.openapi.actionSystem.AnActionEvent;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Manages a tick box of whether to expand the sync targets to the working set.
- */
-public class ExpandSyncToWorkingSetAction extends BlazeToggleAction {
-  @Override
-  public boolean isSelected(AnActionEvent e) {
-    return BlazeUserSettings.getInstance().getExpandSyncToWorkingSet();
-  }
-
-  @Override
-  public void setSelected(AnActionEvent e, boolean state) {
-    BlazeUserSettings.getInstance().setExpandSyncToWorkingSet(state);
-  }
-
-  @Override
-  protected void doUpdate(@NotNull AnActionEvent e) {
-    super.doUpdate(e);
-
-    boolean enabled = ExpandWorkingSetTargetsExperiment.ENABLE_EXPAND_WORKING_SET_TARGETS.getValue();
-    e.getPresentation().setVisible(enabled);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/actions/FullSyncProjectAction.java b/blaze-base/src/com/google/idea/blaze/base/sync/actions/FullSyncProjectAction.java
deleted file mode 100644
index 853181b..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/actions/FullSyncProjectAction.java
+++ /dev/null
@@ -1,53 +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.actions;
-
-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.actionSystem.Presentation;
-import com.intellij.openapi.project.Project;
-
-/**
- * Re-imports (syncs) an Android-Blaze project, without showing the "Import Project" wizard.
- */
-public class FullSyncProjectAction extends BlazeAction {
-
-  public FullSyncProjectAction() {
-    super("Non-Incrementally Sync Project with BUILD Files");
-  }
-
-  @Override
-  public void actionPerformed(final AnActionEvent e) {
-    Project project = e.getProject();
-    if (project != null) {
-      Presentation presentation = e.getPresentation();
-      presentation.setEnabled(false);
-      try {
-        BlazeSyncParams syncParams = new BlazeSyncParams.Builder(
-          "Full Sync",
-          SyncMode.FULL
-        ).build();
-        BlazeSyncManager.getInstance(project).requestProjectSync(syncParams);
-      }
-      finally {
-        presentation.setEnabled(true);
-      }
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/actions/IncrementalSyncProjectAction.java b/blaze-base/src/com/google/idea/blaze/base/sync/actions/IncrementalSyncProjectAction.java
deleted file mode 100644
index c82265f..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/actions/IncrementalSyncProjectAction.java
+++ /dev/null
@@ -1,120 +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.actions;
-
-import com.google.idea.blaze.base.actions.BlazeAction;
-import com.google.idea.blaze.base.settings.Blaze;
-import com.google.idea.blaze.base.settings.BlazeUserSettings;
-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.google.idea.blaze.base.sync.status.BlazeSyncStatus;
-import com.google.idea.blaze.base.sync.status.BlazeSyncStatusImpl;
-import com.intellij.notification.*;
-import com.intellij.openapi.actionSystem.AnActionEvent;
-import com.intellij.openapi.actionSystem.Presentation;
-import com.intellij.openapi.project.Project;
-import icons.BlazeIcons;
-
-import javax.swing.*;
-
-
-/**
- * Re-imports (syncs) an Android-Blaze project, without showing the "Import Project" wizard.
- */
-public class IncrementalSyncProjectAction extends BlazeAction {
-
-  public static final String ACTION_ID = "Blaze.IncrementalSyncProject";
-
-  public static final BlazeSyncParams manualSyncParams =
-    new BlazeSyncParams.Builder("Sync", SyncMode.INCREMENTAL).build();
-
-  public static final BlazeSyncParams autoSyncParams =
-    new BlazeSyncParams.Builder("Sync", SyncMode.INCREMENTAL)
-    .setBackgroundSync(true)
-    .setDoBuild(false)
-    .build();
-
-  public static final BlazeSyncParams startupSyncParams =
-    new BlazeSyncParams.Builder("Sync", SyncMode.INCREMENTAL)
-      .setDoBuild(false)
-      .build();
-
-  public IncrementalSyncProjectAction() {
-    super("Sync Project with BUILD Files");
-  }
-
-  @Override
-  public void actionPerformed(final AnActionEvent e) {
-    Project project = e.getProject();
-    if (project != null) {
-      BlazeSyncManager.getInstance(project).requestProjectSync(manualSyncParams);
-      updateIcon(e);
-    }
-  }
-
-  @Override
-  protected void doUpdate(AnActionEvent e) {
-    super.doUpdate(e);
-    updateIcon(e);
-  }
-
-  private static void updateIcon(AnActionEvent e) {
-    Project project = e.getProject();
-    Presentation presentation = e.getPresentation();
-    if (project == null) {
-      presentation.setIcon(BlazeIcons.Blaze);
-      presentation.setEnabled(true);
-      return;
-    }
-    BlazeSyncStatusImpl statusHelper = BlazeSyncStatusImpl.getImpl(project);
-    BlazeSyncStatus.SyncStatus status = statusHelper.getStatus();
-    presentation.setIcon(getIcon(status));
-    presentation.setEnabled(!statusHelper.syncInProgress.get());
-
-    if (status == BlazeSyncStatus.SyncStatus.DIRTY && !BlazeUserSettings.getInstance().getSyncStatusPopupShown()) {
-      BlazeUserSettings.getInstance().setSyncStatusPopupShown(true);
-      showPopupNotification(project);
-    }
-  }
-
-  private static Icon getIcon(BlazeSyncStatus.SyncStatus status) {
-    switch (status) {
-      case FAILED: return BlazeIcons.BlazeFailed;
-      case DIRTY: return BlazeIcons.BlazeDirty;
-      case CLEAN: return BlazeIcons.BlazeClean;
-      default: return BlazeIcons.Blaze;
-    }
-  }
-
-  private static final NotificationGroup NOTIFICATION_GROUP =
-    new NotificationGroup("Changes since last blaze sync", NotificationDisplayType.BALLOON, true);
-
-  private static void showPopupNotification(Project project) {
-    String msg = "Some relevant files (e.g. BUILD files, .blazeproject file) have changed " +
-                 "since the last sync. Please press the 'Sync' button in the toolbar to " +
-                 "re-sync your IntelliJ project.";
-    Notification notification = new Notification(
-      NOTIFICATION_GROUP.getDisplayId(),
-      String.format("Changes since last %s sync", Blaze.buildSystemName(project)),
-      msg,
-      NotificationType.INFORMATION);
-    notification.setImportant(true);
-    Notifications.Bus.notify(notification, project);
-  }
-
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/actions/PartialSyncAction.java b/blaze-base/src/com/google/idea/blaze/base/sync/actions/PartialSyncAction.java
deleted file mode 100644
index 43eeab6..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/actions/PartialSyncAction.java
+++ /dev/null
@@ -1,114 +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.actions;
-
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.actions.BlazeAction;
-import com.google.idea.blaze.base.model.primitives.TargetExpression;
-import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
-import com.google.idea.blaze.base.rulemaps.SourceToRuleMap;
-import com.google.idea.blaze.base.settings.Blaze;
-import com.google.idea.blaze.base.sync.BlazeSyncManager;
-import com.google.idea.blaze.base.sync.BlazeSyncParams;
-import com.intellij.openapi.actionSystem.AnActionEvent;
-import com.intellij.openapi.actionSystem.CommonDataKeys;
-import com.intellij.openapi.actionSystem.Presentation;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.vfs.VirtualFile;
-
-import javax.annotation.Nullable;
-import java.io.File;
-import java.util.List;
-
-/**
- * Allows a partial sync of the project depending on what's been selected.
- */
-public class PartialSyncAction extends BlazeAction {
-  @Override
-  public void actionPerformed(AnActionEvent e) {
-    Project project = e.getProject();
-    if (project != null) {
-      List<TargetExpression> targetExpressions = Lists.newArrayList();
-      getTargets(e, targetExpressions);
-
-      BlazeSyncParams syncParams = new BlazeSyncParams.Builder("Partial Sync", BlazeSyncParams.SyncMode.INCREMENTAL)
-        .setDoBuild(true)
-        .setBackgroundSync(false)
-        .addTargetExpressions(targetExpressions)
-        .build();
-
-      BlazeSyncManager.getInstance(project).requestProjectSync(syncParams);
-    }
-  }
-
-  @Override
-  protected void doUpdate(AnActionEvent e) {
-    super.doUpdate(e);
-    List<TargetExpression> targets = Lists.newArrayList();
-    String objectName = getTargets(e, targets);
-
-    boolean enabled = objectName != null && !targets.isEmpty();
-    Presentation presentation = e.getPresentation();
-    presentation.setEnabled(enabled);
-
-
-    if (enabled) {
-      presentation.setText(String.format(
-        "Partially Sync %s with %s", objectName, buildSystemName(e.getProject())
-      ));
-    } else {
-      presentation.setText(String.format("Partial %s Sync", buildSystemName(e.getProject())));
-    }
-  }
-
-  private static String buildSystemName(@Nullable Project project) {
-    return Blaze.buildSystemName(project);
-  }
-
-  @Nullable
-  private String getTargets(AnActionEvent e, List<TargetExpression> targets) {
-    Project project = e.getProject();
-    VirtualFile virtualFile = e.getData(CommonDataKeys.VIRTUAL_FILE);
-    if (project == null || virtualFile == null || !virtualFile.isInLocalFileSystem()) {
-      return null;
-    }
-
-    String objectName = null;
-    WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProject(project);
-
-    if (virtualFile.isDirectory()) {
-      if (workspaceRoot.isInWorkspace(virtualFile)) {
-        targets.add(TargetExpression.allFromPackageRecursive(workspaceRoot.workspacePathFor(virtualFile)));
-      }
-      objectName = "Package";
-    } else {
-      targets.addAll(SourceToRuleMap.getInstance(project).getTargetsForSourceFile(new File(virtualFile.getPath())));
-
-      // If empty, try to build parent package
-      if (targets.isEmpty()) {
-        VirtualFile parent = virtualFile.getParent();
-        if (parent.isDirectory()) {
-          if (workspaceRoot.isInWorkspace(parent)) {
-            targets.add(TargetExpression.allFromPackageNonRecursive(workspaceRoot.workspacePathFor(parent)));
-          }
-        }
-      }
-      objectName = "File";
-    }
-
-    return objectName;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/actions/ShowPerformanceWarningsToggleAction.java b/blaze-base/src/com/google/idea/blaze/base/sync/actions/ShowPerformanceWarningsToggleAction.java
deleted file mode 100644
index b5306ad..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/actions/ShowPerformanceWarningsToggleAction.java
+++ /dev/null
@@ -1,35 +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.actions;
-
-import com.google.idea.blaze.base.actions.BlazeToggleAction;
-import com.google.idea.blaze.base.settings.BlazeUserSettings;
-import com.intellij.openapi.actionSystem.AnActionEvent;
-
-/**
- * Manages a tick box of whether to show performance warnings.
- */
-public class ShowPerformanceWarningsToggleAction extends BlazeToggleAction {
-  @Override
-  public boolean isSelected(AnActionEvent e) {
-    return BlazeUserSettings.getInstance().getShowPerformanceWarnings();
-  }
-
-  @Override
-  public void setSelected(AnActionEvent e, boolean state) {
-    BlazeUserSettings.getInstance().setShowPerformanceWarnings(state);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/aspects/AspectStrategy.java b/blaze-base/src/com/google/idea/blaze/base/sync/aspects/AspectStrategy.java
deleted file mode 100644
index 0d8e3e4..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/aspects/AspectStrategy.java
+++ /dev/null
@@ -1,114 +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.*;
-
-/**
- * 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/blaze-base/src/com/google/idea/blaze/base/sync/aspects/BlazeIdeInterface.java b/blaze-base/src/com/google/idea/blaze/base/sync/aspects/BlazeIdeInterface.java
deleted file mode 100644
index d988982..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/aspects/BlazeIdeInterface.java
+++ /dev/null
@@ -1,77 +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.common.collect.ImmutableMap;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.model.SyncState;
-import com.google.idea.blaze.base.model.primitives.Label;
-import com.google.idea.blaze.base.model.primitives.TargetExpression;
-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.sync.workspace.ArtifactLocationDecoder;
-import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
-import com.intellij.openapi.components.ServiceManager;
-import com.intellij.openapi.project.Project;
-
-import javax.annotation.Nullable;
-import java.io.File;
-import java.util.List;
-
-/**
- * Indirection between ide_build_info and aspect style IDE info.
- */
-public abstract class BlazeIdeInterface {
-
-  public static BlazeIdeInterface getInstance() {
-    return ServiceManager.getService(BlazeIdeInterface.class);
-  }
-
-  public static class IdeResult {
-    public final ImmutableMap<Label, RuleIdeInfo> ruleMap;
-    @Deprecated
-    @Nullable
-    public final File androidPlatformDirectory;
-    public IdeResult(
-      ImmutableMap<Label, RuleIdeInfo> ruleMap,
-      @Nullable File androidPlatformDirectory) {
-
-      this.ruleMap = ruleMap;
-      this.androidPlatformDirectory = androidPlatformDirectory;
-    }
-  }
-
-  @Nullable
-  public abstract IdeResult updateBlazeIdeState(
-    Project project,
-    BlazeContext context,
-    WorkspaceRoot workspaceRoot,
-    ProjectViewSet projectViewSet,
-    List<TargetExpression> targets,
-    WorkspaceLanguageSettings workspaceLanguageSettings,
-    ArtifactLocationDecoder artifactLocationDecoder,
-    SyncState.Builder syncStateBuilder,
-    @Nullable SyncState previousSyncState,
-    boolean requiresAndroidSdk);
-
-  public abstract void resolveIdeArtifacts(
-    Project project,
-    BlazeContext context,
-    WorkspaceRoot workspaceRoot,
-    ProjectViewSet projectViewSet,
-    List<TargetExpression> targets);
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/aspects/BlazeIdeInterfaceAspectsImpl.java b/blaze-base/src/com/google/idea/blaze/base/sync/aspects/BlazeIdeInterfaceAspectsImpl.java
deleted file mode 100644
index 367a1ed..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/aspects/BlazeIdeInterfaceAspectsImpl.java
+++ /dev/null
@@ -1,382 +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.common.base.Objects;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.idea.blaze.base.async.FutureUtil;
-import com.google.idea.blaze.base.async.executor.BlazeExecutor;
-import com.google.idea.blaze.base.async.process.ExternalTask;
-import com.google.idea.blaze.base.async.process.LineProcessingOutputStream;
-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.command.ExperimentalShowArtifactsLineProcessor;
-import com.google.idea.blaze.base.experiments.BoolExperiment;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.issueparser.IssueOutputLineProcessor;
-import com.google.idea.blaze.base.metrics.Action;
-import com.google.idea.blaze.base.model.SyncState;
-import com.google.idea.blaze.base.model.primitives.Label;
-import com.google.idea.blaze.base.model.primitives.TargetExpression;
-import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
-import com.google.idea.blaze.base.prefetch.PrefetchService;
-import com.google.idea.blaze.base.projectview.ProjectViewSet;
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.google.idea.blaze.base.scope.Result;
-import com.google.idea.blaze.base.scope.Scope;
-import com.google.idea.blaze.base.scope.ScopedFunction;
-import com.google.idea.blaze.base.scope.output.PerformanceWarning;
-import com.google.idea.blaze.base.scope.output.PrintOutput;
-import com.google.idea.blaze.base.scope.scopes.LoggedTimingScope;
-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.filediff.FileDiffService;
-import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
-import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
-import com.google.repackaged.devtools.build.lib.ideinfo.androidstudio.AndroidStudioIdeInfo;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.project.Project;
-
-import javax.annotation.Nullable;
-import java.io.File;
-import java.io.Serializable;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.atomic.AtomicLong;
-
-/**
- * Implementation of BlazeIdeInterface based on aspects.
- */
-public class BlazeIdeInterfaceAspectsImpl extends BlazeIdeInterface {
-
-  private static final Logger LOG = Logger.getInstance(BlazeIdeInterfaceAspectsImpl.class);
-  private static final Label ANDROID_SDK_TARGET = new Label("//third_party/java/android/android_sdk_linux:android");
-  private static final FileDiffService fileDiffService = new FileDiffService();
-  private static final BoolExperiment USE_SKYLARK_ASPECT = new BoolExperiment("use.skylark.aspect", false);
-
-  static class State implements Serializable {
-    private static final long serialVersionUID = 10L;
-    ImmutableMap<Label, RuleIdeInfo> ruleMap;
-    File androidPlatformDirectory;
-    FileDiffService.State fileState = null;
-    Map<File, Label> fileToLabel = Maps.newHashMap();
-    WorkspaceLanguageSettings workspaceLanguageSettings;
-    String aspectStrategyName;
-  }
-
-  @Nullable
-  @Override
-  public IdeResult updateBlazeIdeState(Project project,
-                                       BlazeContext context,
-                                       WorkspaceRoot workspaceRoot,
-                                       ProjectViewSet projectViewSet,
-                                       List<TargetExpression> targets,
-                                       WorkspaceLanguageSettings workspaceLanguageSettings,
-                                       ArtifactLocationDecoder artifactLocationDecoder,
-                                       SyncState.Builder syncStateBuilder,
-                                       @Nullable SyncState previousSyncState,
-                                       boolean requiresAndroidSdk) {
-    State prevState = previousSyncState != null ? previousSyncState.get(State.class) : null;
-
-    // If the language filter has changed, redo everything from scratch
-    if (prevState != null && !prevState.workspaceLanguageSettings.equals(workspaceLanguageSettings)) {
-      prevState = null;
-    }
-
-    // If the aspect strategy has changed, redo everything from scratch
-    final AspectStrategy aspectStrategy = getAspectStrategy();
-    if (prevState != null && !Objects.equal(prevState.aspectStrategyName, aspectStrategy.getName())) {
-      prevState = null;
-    }
-
-    List<File> fileList = getIdeInfo(project, context, workspaceRoot, projectViewSet, targets, aspectStrategy, requiresAndroidSdk);
-    if (!context.shouldContinue()) {
-      return null;
-    }
-
-    List<File> updatedFiles = Lists.newArrayList();
-    List<File> removedFiles = Lists.newArrayList();
-    FileDiffService.State fileState = fileDiffService.updateFiles(
-      prevState != null ? prevState.fileState : null,
-      fileList,
-      updatedFiles,
-      removedFiles
-    );
-    if (fileState == null) {
-      return null;
-    }
-
-    context.output(new PrintOutput(String.format(
-      "Total rules: %d, new/changed: %d, removed: %d",
-      fileList.size(),
-      updatedFiles.size(),
-      removedFiles.size()
-    )));
-
-    ListenableFuture<?> prefetchFuture = PrefetchService.getInstance().prefetchFiles(updatedFiles, true);
-    if (!FutureUtil.waitForFuture(context, prefetchFuture)
-      .timed("FetchAspectOutput")
-      .run()
-      .success()) {
-      return null;
-    }
-
-    State state = updateState(
-      context,
-      prevState,
-      fileState,
-      workspaceLanguageSettings,
-      artifactLocationDecoder,
-      aspectStrategy,
-      updatedFiles,
-      removedFiles
-    );
-    if (state == null) {
-      return null;
-    }
-    if (state.androidPlatformDirectory == null && requiresAndroidSdk) {
-      LOG.error("Android platform directory not found.");
-      return null;
-    }
-    syncStateBuilder.put(State.class, state);
-    return new IdeResult(state.ruleMap, state.androidPlatformDirectory);
-  }
-
-  private static List<File> getIdeInfo(Project project,
-                                       BlazeContext parentContext,
-                                       WorkspaceRoot workspaceRoot,
-                                       ProjectViewSet projectViewSet,
-                                       List<TargetExpression> targets,
-                                       AspectStrategy aspectStrategy,
-                                       boolean addAndroidSdkTarget) {
-    return Scope.push(parentContext, context -> {
-      context.push(new TimingScope("ExecuteBlazeCommand"));
-
-      List<File> result = Lists.newArrayList();
-
-      BuildSystem buildSystem = Blaze.getBuildSystem(project);
-      BlazeCommand.Builder blazeCommandBuilder = BlazeCommand.builder(buildSystem, BlazeCommandName.BUILD);
-      if (addAndroidSdkTarget) {
-        blazeCommandBuilder.addTargets(ANDROID_SDK_TARGET);
-      }
-      blazeCommandBuilder
-        .addTargets(targets)
-        .addBlazeFlags(BlazeFlags.EXPERIMENTAL_SHOW_ARTIFACTS)
-        .addBlazeFlags(BlazeFlags.buildFlags(project, projectViewSet));
-
-      aspectStrategy.modifyIdeInfoCommand(blazeCommandBuilder);
-
-      int retVal = ExternalTask.builder(workspaceRoot, blazeCommandBuilder.build())
-        .context(context)
-        .stderr(LineProcessingOutputStream.of(
-          new ExperimentalShowArtifactsLineProcessor(result, aspectStrategy.getAspectOutputFileExtension()),
-          new IssueOutputLineProcessor(project, context, workspaceRoot)
-        ))
-        .build()
-        .run(new LoggedTimingScope(project, Action.BLAZE_BUILD));
-
-      if (retVal != 0) {
-        context.setHasError();
-      }
-
-      return result;
-    });
-  }
-
-  private static class RuleIdeInfoOrSdkInfo {
-    public File file;
-    public RuleIdeInfo ruleIdeInfo;
-    public File androidPlatformDirectory;
-  }
-
-  @Nullable
-  static State updateState(BlazeContext parentContext,
-                           @Nullable State prevState,
-                           FileDiffService.State fileState,
-                           WorkspaceLanguageSettings workspaceLanguageSettings,
-                           ArtifactLocationDecoder artifactLocationDecoder,
-                           AspectStrategy aspectStrategy,
-                           List<File> newFiles,
-                           List<File> removedFiles) {
-    Result<State> result = Scope.push(parentContext, (ScopedFunction<Result<State>>)context -> {
-      context.push(new TimingScope("UpdateRuleMap"));
-
-      State state = new State();
-      state.fileState = fileState;
-      state.workspaceLanguageSettings = workspaceLanguageSettings;
-      state.aspectStrategyName = aspectStrategy.getName();
-
-      Map<Label, RuleIdeInfo> ruleMap = Maps.newHashMap();
-      Map<Label, RuleIdeInfo> updatedRules = Maps.newHashMap();
-      if (prevState != null) {
-        ruleMap.putAll(prevState.ruleMap);
-        state.androidPlatformDirectory = prevState.androidPlatformDirectory;
-        state.fileToLabel.putAll(prevState.fileToLabel);
-      }
-
-      // Update removed
-      for (File removedFile : removedFiles) {
-        Label label = state.fileToLabel.remove(removedFile);
-        if (label != null) {
-          ruleMap.remove(label);
-        }
-      }
-
-      AtomicLong totalSizeLoaded = new AtomicLong(0);
-
-      // Read protos from any new files
-      List<ListenableFuture<RuleIdeInfoOrSdkInfo>> futures = Lists.newArrayList();
-      for (File file : newFiles) {
-        futures.add(submit(() -> {
-          RuleIdeInfoOrSdkInfo ruleIdeInfoOrSdkInfo = new RuleIdeInfoOrSdkInfo();
-          ruleIdeInfoOrSdkInfo.file = file;
-
-          totalSizeLoaded.addAndGet(file.length());
-
-          AndroidStudioIdeInfo.RuleIdeInfo ruleProto = aspectStrategy.readAspectFile(file);
-          if (ruleProto.getLabel().equals(ANDROID_SDK_TARGET.toString())) {
-            ruleIdeInfoOrSdkInfo.androidPlatformDirectory = getAndroidPlatformDirectoryFromAndroidTarget(
-              ruleProto,
-              artifactLocationDecoder
-            );
-          }
-          else {
-            ruleIdeInfoOrSdkInfo.ruleIdeInfo = IdeInfoFromProtobuf.makeRuleIdeInfo(
-              workspaceLanguageSettings,
-              artifactLocationDecoder,
-              ruleProto
-            );
-          }
-          return ruleIdeInfoOrSdkInfo;
-        }
-        ));
-      }
-
-      // Update state with result from proto files
-      int duplicateRuleLabels = 0;
-      try {
-        for (RuleIdeInfoOrSdkInfo ruleIdeInfoOrSdkInfo : Futures.allAsList(futures).get()) {
-          if (ruleIdeInfoOrSdkInfo.androidPlatformDirectory != null) {
-            state.androidPlatformDirectory = ruleIdeInfoOrSdkInfo.androidPlatformDirectory;
-          } else if (ruleIdeInfoOrSdkInfo.ruleIdeInfo != null) {
-            File file = ruleIdeInfoOrSdkInfo.file;
-            Label label = ruleIdeInfoOrSdkInfo.ruleIdeInfo.label;
-
-            RuleIdeInfo previousRule = updatedRules.putIfAbsent(label, ruleIdeInfoOrSdkInfo.ruleIdeInfo);
-            if (previousRule == null) {
-              state.fileToLabel.put(file, label);
-            } else {
-              duplicateRuleLabels++;
-            }
-          }
-        }
-      }
-      catch (InterruptedException e) {
-        Thread.currentThread().interrupt();
-        return Result.error(null);
-      }
-      catch (ExecutionException e) {
-        return Result.error(e);
-      }
-      ruleMap.putAll(updatedRules);
-
-      context.output(new PrintOutput(String.format(
-        "Loaded %d aspect files, total size %dkB", newFiles.size(), totalSizeLoaded.get() / 1024
-      )));
-      if (duplicateRuleLabels > 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())
-        )));
-      }
-
-      state.ruleMap = ImmutableMap.copyOf(ruleMap);
-      return Result.of(state);
-    });
-
-    if (result.error != null) {
-      LOG.error(result.error);
-      return null;
-    }
-    return result.result;
-  }
-
-  @Nullable
-  private static File getAndroidPlatformDirectoryFromAndroidTarget(AndroidStudioIdeInfo.RuleIdeInfo ruleProto,
-                                                                   ArtifactLocationDecoder artifactLocationDecoder) {
-    if (!ruleProto.hasJavaRuleIdeInfo()) {
-      return null;
-    }
-    AndroidStudioIdeInfo.JavaRuleIdeInfo javaRuleIdeInfo = ruleProto.getJavaRuleIdeInfo();
-    if (javaRuleIdeInfo.getJarsCount() == 0) {
-      return null;
-    }
-    AndroidStudioIdeInfo.LibraryArtifact libraryArtifact = javaRuleIdeInfo.getJars(0);
-    AndroidStudioIdeInfo.ArtifactLocation artifactLocation = libraryArtifact.getJar();
-    if (artifactLocation == null) {
-      return null;
-    }
-    File androidJar = artifactLocationDecoder.decode(artifactLocation).getFile();
-    return androidJar.getParentFile();
-  }
-
-  private static <T> ListenableFuture<T> submit(Callable<T> callable) {
-    return BlazeExecutor.getInstance().submit(callable);
-  }
-
-  @Override
-  public void resolveIdeArtifacts(Project project,
-                                  BlazeContext context,
-                                  WorkspaceRoot workspaceRoot,
-                                  ProjectViewSet projectViewSet,
-                                  List<TargetExpression> targets) {
-    AspectStrategy aspectStrategy = getAspectStrategy();
-
-    BlazeCommand.Builder blazeCommandBuilder = BlazeCommand.builder(Blaze.getBuildSystem(project), BlazeCommandName.BUILD)
-      .addTargets(targets)
-      .addBlazeFlags()
-      .addBlazeFlags(BlazeFlags.KEEP_GOING)
-      .addBlazeFlags(BlazeFlags.buildFlags(project, projectViewSet));
-
-    aspectStrategy.modifyIdeResolveCommand(blazeCommandBuilder);
-
-    BlazeCommand blazeCommand = blazeCommandBuilder.build();
-
-    int retVal = ExternalTask.builder(workspaceRoot, blazeCommand)
-      .context(context)
-      .stderr(LineProcessingOutputStream.of(new IssueOutputLineProcessor(project, context, workspaceRoot)))
-      .build()
-      .run(new LoggedTimingScope(project, Action.BLAZE_BUILD));
-
-    if (retVal != 0) {
-      context.setHasError();
-    }
-  }
-
-  private AspectStrategy getAspectStrategy() {
-    return USE_SKYLARK_ASPECT.getValue() ? AspectStrategy.SKYLARK_ASPECT : AspectStrategy.NATIVE_ASPECT;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/aspects/IdeInfoFromProtobuf.java b/blaze-base/src/com/google/idea/blaze/base/sync/aspects/IdeInfoFromProtobuf.java
deleted file mode 100644
index e723505..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/aspects/IdeInfoFromProtobuf.java
+++ /dev/null
@@ -1,325 +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.common.base.Strings;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.ideinfo.*;
-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.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
-import com.google.repackaged.devtools.build.lib.ideinfo.androidstudio.AndroidStudioIdeInfo;
-import com.google.repackaged.protobuf.ProtocolStringList;
-
-import javax.annotation.Nullable;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * Conversion functions from new aspect-style Bazel IDE info to ASWB internal classes.
- */
-public class IdeInfoFromProtobuf {
-
-  @Nullable
-  public static RuleIdeInfo makeRuleIdeInfo(WorkspaceLanguageSettings workspaceLanguageSettings,
-                                            ArtifactLocationDecoder decoder,
-                                            AndroidStudioIdeInfo.RuleIdeInfo message) {
-    Kind kind = getKind(message);
-    if (kind == null) {
-      return null;
-    }
-    if (!workspaceLanguageSettings.isLanguageActive(kind.getLanguageClass())) {
-      return null;
-    }
-
-    Label label = new Label(message.getLabel());
-    ArtifactLocation buildFile = getBuildFile(decoder, message);
-
-    Collection<Label> dependencies = makeLabelListFromProtobuf(message.getDependenciesList());
-    Collection<Label> runtimeDeps = makeLabelListFromProtobuf(message.getRuntimeDepsList());
-    Collection<String> tags = ImmutableList.copyOf(message.getTagsList());
-
-    Collection<ArtifactLocation> sources = Lists.newArrayList();
-    CRuleIdeInfo cRuleIdeInfo = null;
-    if (message.hasCRuleIdeInfo()) {
-      cRuleIdeInfo = makeCRuleIdeInfo(decoder, message.getCRuleIdeInfo());
-      sources.addAll(cRuleIdeInfo.sources);
-    }
-    CToolchainIdeInfo cToolchainIdeInfo = null;
-    if (message.hasCToolchainIdeInfo()) {
-      cToolchainIdeInfo = makeCToolchainIdeInfo(message.getCToolchainIdeInfo());
-    }
-    JavaRuleIdeInfo javaRuleIdeInfo = null;
-    if (message.hasJavaRuleIdeInfo()) {
-      javaRuleIdeInfo = makeJavaRuleIdeInfo(decoder, message.getJavaRuleIdeInfo());
-      Collection<ArtifactLocation> javaSources = makeArtifactLocationList(decoder, message.getJavaRuleIdeInfo().getSourcesList());
-      sources.addAll(javaSources);
-    }
-    AndroidRuleIdeInfo androidRuleIdeInfo = null;
-    if (message.hasAndroidRuleIdeInfo()) {
-      androidRuleIdeInfo = makeAndroidRuleIdeInfo(decoder, message.getAndroidRuleIdeInfo());
-    }
-    TestIdeInfo testIdeInfo = null;
-    if (message.hasTestInfo()) {
-      testIdeInfo = makeTestIdeInfo(message.getTestInfo());
-    }
-    ProtoLibraryLegacyInfo protoLibraryLegacyInfo = null;
-    if (message.hasProtoLibraryLegacyJavaIdeInfo()) {
-      protoLibraryLegacyInfo = makeProtoLibraryLegacyInfo(decoder, message.getProtoLibraryLegacyJavaIdeInfo());
-    }
-    JavaToolchainIdeInfo javaToolchainIdeInfo = null;
-    if (message.hasJavaToolchainIdeInfo()) {
-      javaToolchainIdeInfo = makeJavaToolchainIdeInfo(message.getJavaToolchainIdeInfo());
-    }
-
-    return new RuleIdeInfo(
-      label,
-      kind,
-      buildFile,
-      dependencies,
-      runtimeDeps,
-      tags,
-      sources,
-      cRuleIdeInfo,
-      cToolchainIdeInfo,
-      javaRuleIdeInfo,
-      androidRuleIdeInfo,
-      testIdeInfo,
-      protoLibraryLegacyInfo,
-      javaToolchainIdeInfo
-    );
-  }
-
-  @Nullable
-  private static ArtifactLocation getBuildFile(ArtifactLocationDecoder decoder,
-                                               AndroidStudioIdeInfo.RuleIdeInfo message) {
-    if (message.hasBuildFileArtifactLocation()) {
-      return makeArtifactLocation(decoder, message.getBuildFileArtifactLocation());
-    }
-    return null;
-  }
-
-  private static CRuleIdeInfo makeCRuleIdeInfo(
-    ArtifactLocationDecoder decoder,
-    AndroidStudioIdeInfo.CRuleIdeInfo cRuleIdeInfo
-  ) {
-    List<ArtifactLocation> sources = makeArtifactLocationList(decoder, cRuleIdeInfo.getSourceList());
-    List<ExecutionRootPath> transitiveIncludeDirectories = makeExecutionRootPathList(cRuleIdeInfo.getTransitiveIncludeDirectoryList());
-    List<ExecutionRootPath> transitiveQuoteIncludeDirectories =
-      makeExecutionRootPathList(cRuleIdeInfo.getTransitiveQuoteIncludeDirectoryList());
-    List<ExecutionRootPath> transitiveSystemIncludeDirectories =
-      makeExecutionRootPathList(cRuleIdeInfo.getTransitiveSystemIncludeDirectoryList());
-
-    CRuleIdeInfo.Builder builder = CRuleIdeInfo.builder()
-      .addSources(sources)
-      .addTransitiveIncludeDirectories(transitiveIncludeDirectories)
-      .addTransitiveQuoteIncludeDirectories(transitiveQuoteIncludeDirectories)
-      .addTransitiveDefines(cRuleIdeInfo.getTransitiveDefineList())
-      .addTransitiveSystemIncludeDirectories(transitiveSystemIncludeDirectories)
-    ;
-
-    return builder.build();
-  }
-
-  private static List<ExecutionRootPath> makeExecutionRootPathList(Iterable<String> relativePaths) {
-    List<ExecutionRootPath> workspacePaths = Lists.newArrayList();
-    for (String relativePath : relativePaths) {
-      workspacePaths.add(new ExecutionRootPath(relativePath));
-    }
-    return workspacePaths;
-  }
-
-  private static CToolchainIdeInfo makeCToolchainIdeInfo(AndroidStudioIdeInfo.CToolchainIdeInfo cToolchainIdeInfo) {
-    Collection<ExecutionRootPath> builtInIncludeDirectories = makeExecutionRootPathList(cToolchainIdeInfo.getBuiltInIncludeDirectoryList());
-    ExecutionRootPath cppExecutable = new ExecutionRootPath(cToolchainIdeInfo.getCppExecutable());
-    ExecutionRootPath preprocessorExecutable = new ExecutionRootPath(cToolchainIdeInfo.getPreprocessorExecutable());
-
-    UnfilteredCompilerOptions unfilteredCompilerOptions =
-      new UnfilteredCompilerOptions(cToolchainIdeInfo.getUnfilteredCompilerOptionList());
-
-    CToolchainIdeInfo.Builder builder = CToolchainIdeInfo.builder()
-      .addBaseCompilerOptions(cToolchainIdeInfo.getBaseCompilerOptionList())
-      .addCCompilerOptions(cToolchainIdeInfo.getCOptionList())
-      .addCppCompilerOptions(cToolchainIdeInfo.getCppOptionList())
-      .addLinkOptions(cToolchainIdeInfo.getLinkOptionList())
-      .addBuiltInIncludeDirectories(builtInIncludeDirectories)
-      .setCppExecutable(cppExecutable)
-      .setPreprocessorExecutable(preprocessorExecutable)
-      .setTargetName(cToolchainIdeInfo.getTargetName())
-      .addUnfilteredCompilerOptions(unfilteredCompilerOptions.getToolchainFlags())
-      .addUnfilteredToolchainSystemIncludes(unfilteredCompilerOptions.getToolchainSysIncludes())
-      ;
-
-    return builder.build();
-  }
-
-  private static JavaRuleIdeInfo makeJavaRuleIdeInfo(ArtifactLocationDecoder decoder,
-                                                     AndroidStudioIdeInfo.JavaRuleIdeInfo javaRuleIdeInfo) {
-    return new JavaRuleIdeInfo(
-      makeLibraryArtifactList(decoder, javaRuleIdeInfo.getJarsList()),
-      makeLibraryArtifactList(decoder, javaRuleIdeInfo.getGeneratedJarsList()),
-      javaRuleIdeInfo.hasPackageManifest() ? makeArtifactLocation(decoder, javaRuleIdeInfo.getPackageManifest()) : null,
-      javaRuleIdeInfo.hasJdeps() ? makeArtifactLocation(decoder, javaRuleIdeInfo.getJdeps()) : null
-    );
-  }
-
-  private static AndroidRuleIdeInfo makeAndroidRuleIdeInfo(ArtifactLocationDecoder decoder,
-                                                           AndroidStudioIdeInfo.AndroidRuleIdeInfo androidRuleIdeInfo) {
-    return new AndroidRuleIdeInfo(
-      makeArtifactLocationList(decoder, androidRuleIdeInfo.getResourcesList()),
-      androidRuleIdeInfo.getJavaPackage(),
-      androidRuleIdeInfo.getGenerateResourceClass(),
-      androidRuleIdeInfo.hasManifest() ? makeArtifactLocation(decoder, androidRuleIdeInfo.getManifest()) : null,
-      androidRuleIdeInfo.hasIdlJar() ? makeLibraryArtifact(decoder, androidRuleIdeInfo.getIdlJar()) : null,
-      androidRuleIdeInfo.hasResourceJar() ? makeLibraryArtifact(decoder, androidRuleIdeInfo.getResourceJar()) : null,
-      androidRuleIdeInfo.getHasIdlSources(),
-      !Strings.isNullOrEmpty(androidRuleIdeInfo.getLegacyResources()) ? new Label(androidRuleIdeInfo.getLegacyResources()) : null
-    );
-  }
-
-  private static TestIdeInfo makeTestIdeInfo(AndroidStudioIdeInfo.TestInfo testInfo) {
-    String size = testInfo.getSize();
-    TestIdeInfo.TestSize testSize = TestIdeInfo.DEFAULT_RULE_TEST_SIZE;
-    if (!Strings.isNullOrEmpty(size)) {
-      switch (size) {
-        case "small":
-          testSize = TestIdeInfo.TestSize.SMALL;
-          break;
-        case "medium":
-          testSize = TestIdeInfo.TestSize.MEDIUM;
-          break;
-        case "large":
-          testSize = TestIdeInfo.TestSize.LARGE;
-          break;
-        case "enormous":
-          testSize = TestIdeInfo.TestSize.ENORMOUS;
-          break;
-        default:
-          break;
-      }
-    }
-    return new TestIdeInfo(testSize);
-  }
-
-  private static ProtoLibraryLegacyInfo makeProtoLibraryLegacyInfo(ArtifactLocationDecoder decoder,
-                                                                   AndroidStudioIdeInfo.ProtoLibraryLegacyJavaIdeInfo protoLibraryLegacyJavaIdeInfo) {
-    final ProtoLibraryLegacyInfo.ApiFlavor apiFlavor;
-    if (protoLibraryLegacyJavaIdeInfo.getApiVersion() == 1) {
-      apiFlavor = ProtoLibraryLegacyInfo.ApiFlavor.VERSION_1;
-    } else {
-      switch (protoLibraryLegacyJavaIdeInfo.getApiFlavor()) {
-        case MUTABLE:
-          apiFlavor = ProtoLibraryLegacyInfo.ApiFlavor.MUTABLE;
-          break;
-        case IMMUTABLE:
-          apiFlavor = ProtoLibraryLegacyInfo.ApiFlavor.IMMUTABLE;
-          break;
-        case BOTH:
-          apiFlavor = ProtoLibraryLegacyInfo.ApiFlavor.BOTH;
-          break;
-        default:
-          apiFlavor = ProtoLibraryLegacyInfo.ApiFlavor.NONE;
-          break;
-      }
-    }
-    return new ProtoLibraryLegacyInfo(
-      apiFlavor,
-      makeLibraryArtifactList(decoder, protoLibraryLegacyJavaIdeInfo.getJars1List()),
-      makeLibraryArtifactList(decoder, protoLibraryLegacyJavaIdeInfo.getJarsMutableList()),
-      makeLibraryArtifactList(decoder, protoLibraryLegacyJavaIdeInfo.getJarsImmutableList())
-    );
-  }
-
-  private static JavaToolchainIdeInfo makeJavaToolchainIdeInfo(AndroidStudioIdeInfo.JavaToolchainIdeInfo javaToolchainIdeInfo) {
-    return new JavaToolchainIdeInfo(javaToolchainIdeInfo.getSourceVersion(), javaToolchainIdeInfo.getTargetVersion());
-  }
-
-  private static Collection<LibraryArtifact> makeLibraryArtifactList(
-    ArtifactLocationDecoder decoder,
-    List<AndroidStudioIdeInfo.LibraryArtifact> jarsList) {
-    ImmutableList.Builder<LibraryArtifact> builder = ImmutableList.builder();
-    for (AndroidStudioIdeInfo.LibraryArtifact libraryArtifact : jarsList) {
-      LibraryArtifact lib = makeLibraryArtifact(decoder, libraryArtifact);
-      if (lib != null) {
-        builder.add(lib);
-      }
-    }
-    return builder.build();
-  }
-
-  @Nullable
-  private static LibraryArtifact makeLibraryArtifact(ArtifactLocationDecoder decoder,
-                                                     AndroidStudioIdeInfo.LibraryArtifact libraryArtifact) {
-    ArtifactLocation runtimeJar = libraryArtifact.hasJar()
-                                  ? makeArtifactLocation(decoder, libraryArtifact.getJar()) : null;
-    ArtifactLocation iJar = libraryArtifact.hasInterfaceJar()
-                            ? makeArtifactLocation(decoder, libraryArtifact.getInterfaceJar()) : runtimeJar;
-    ArtifactLocation sourceJar = libraryArtifact.hasSourceJar()
-                                 ? makeArtifactLocation(decoder, libraryArtifact.getSourceJar()) : null;
-    if (iJar == null) {
-      // Failed to find ArtifactLocation file -- presumably because it was removed from file system since blaze build
-      return null;
-    }
-    return new LibraryArtifact(
-      iJar,
-      runtimeJar,
-      sourceJar
-    );
-  }
-
-  private static List<ArtifactLocation> makeArtifactLocationList(
-    ArtifactLocationDecoder decoder,
-    List<AndroidStudioIdeInfo.ArtifactLocation> sourcesList) {
-    ImmutableList.Builder<ArtifactLocation> builder = ImmutableList.builder();
-    for (AndroidStudioIdeInfo.ArtifactLocation pbArtifactLocation : sourcesList) {
-      ArtifactLocation loc = makeArtifactLocation(decoder, pbArtifactLocation);
-      if (loc != null) {
-        builder.add(loc);
-      }
-    }
-    return builder.build();
-  }
-
-  @Nullable
-  private static ArtifactLocation makeArtifactLocation(ArtifactLocationDecoder decoder,
-                                                       AndroidStudioIdeInfo.ArtifactLocation pbArtifactLocation) {
-    if (pbArtifactLocation == null) {
-      return null;
-    }
-    return decoder.decode(pbArtifactLocation);
-  }
-
-  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();
-    if (!Strings.isNullOrEmpty(kindString)) {
-      return Kind.fromString(kindString);
-    }
-    return null;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/aspects/UnfilteredCompilerOptions.java b/blaze-base/src/com/google/idea/blaze/base/sync/aspects/UnfilteredCompilerOptions.java
deleted file mode 100644
index 4345b6f..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/aspects/UnfilteredCompilerOptions.java
+++ /dev/null
@@ -1,83 +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.common.annotations.VisibleForTesting;
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.model.primitives.ExecutionRootPath;
-
-import java.util.List;
-
-/**
- * unfilteredCompilerOptions is a grab bag of options passed to the compiler. Do minimal parsing to extract what we need.
- */
-final class UnfilteredCompilerOptions {
-  private enum NextOption {ISYSTEM, FLAG}
-
-  private final List<ExecutionRootPath> toolchainSysIncludes;
-  private final List<String> toolchainFlags;
-
-  public UnfilteredCompilerOptions(Iterable<String> unfilteredOptions) {
-    List<String> toolchainSystemIncludePaths = Lists.newArrayList();
-    toolchainFlags = Lists.newArrayList();
-    splitUnfilteredCompilerOptions(unfilteredOptions, toolchainSystemIncludePaths, toolchainFlags);
-
-    toolchainSysIncludes = Lists.newArrayList();
-    for (String systemInclude : toolchainSystemIncludePaths) {
-      toolchainSysIncludes.add(new ExecutionRootPath(systemInclude));
-    }
-  }
-
-  public List<String> getToolchainFlags() {
-    return toolchainFlags;
-  }
-
-  public List<ExecutionRootPath> getToolchainSysIncludes() {
-    return toolchainSysIncludes;
-  }
-
-  @VisibleForTesting
-  static void splitUnfilteredCompilerOptions(
-    Iterable<String> unfilteredOptions,
-    List<String> toolchainSysIncludes,
-    List<String> toolchainFlags
-  ) {
-    NextOption nextOption = NextOption.FLAG;
-    for (String unfilteredOption : unfilteredOptions) {
-      // We are looking for either the flag pair "-isystem /path/to/dir" or the flag "-isystem/path/to/dir"
-      //
-      // blaze emits isystem flags in both formats. The latter isn't ideal but apparently it is accepted by GCC and will be emitted by
-      // blaze under certain circumstances.
-      if (nextOption == NextOption.ISYSTEM) {
-        toolchainSysIncludes.add(unfilteredOption);
-        nextOption = NextOption.FLAG;
-      }
-      else {
-        if (unfilteredOption.equals("-isystem")) {
-          nextOption = NextOption.ISYSTEM;
-        }
-        else if (unfilteredOption.startsWith("-isystem")) {
-          String iSystemIncludePath = unfilteredOption.substring("-isystem".length());
-          toolchainSysIncludes.add(iSystemIncludePath);
-        }
-        else {
-          toolchainFlags.add(unfilteredOption);
-          nextOption = NextOption.FLAG;
-        }
-      }
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/data/BlazeDataStorage.java b/blaze-base/src/com/google/idea/blaze/base/sync/data/BlazeDataStorage.java
deleted file mode 100644
index edb49ea..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/data/BlazeDataStorage.java
+++ /dev/null
@@ -1,49 +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.data;
-
-import com.google.common.base.Strings;
-import com.google.idea.blaze.base.settings.BlazeImportSettings;
-import com.intellij.openapi.application.PathManager;
-import com.intellij.openapi.project.Project;
-import org.jetbrains.annotations.NotNull;
-
-import java.io.File;
-
-/**
- * Defines where we store our blaze project data.
- */
-public class BlazeDataStorage {
-  public static final String CACHE_FILE_NAME = "cache.dat";
-
-  @NotNull
-  public static File getProjectCacheDir(
-    @NotNull Project project,
-    @NotNull BlazeImportSettings importSettings) {
-    String locationHash = importSettings.getLocationHash();
-
-    // Legacy support: The location hash used to be just the project hash
-    if (Strings.isNullOrEmpty(locationHash)) {
-      locationHash = project.getLocationHash();
-    }
-
-    return new File(getProjectConfigurationDir(), locationHash);
-  }
-
-  private static File getProjectConfigurationDir() {
-    return new File(PathManager.getSystemPath(), "blaze/projects").getAbsoluteFile();
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/data/BlazeProjectDataManager.java b/blaze-base/src/com/google/idea/blaze/base/sync/data/BlazeProjectDataManager.java
deleted file mode 100644
index fa0f881..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/data/BlazeProjectDataManager.java
+++ /dev/null
@@ -1,37 +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.data;
-
-import com.google.idea.blaze.base.model.BlazeProjectData;
-import com.google.idea.blaze.base.sync.BlazeSyncPlugin;
-import com.intellij.openapi.components.ServiceManager;
-import com.intellij.openapi.project.Project;
-
-import javax.annotation.Nullable;
-
-/**
- * Stores a cache of blaze project data.
- */
-public interface BlazeProjectDataManager {
-  static BlazeProjectDataManager getInstance(Project project) {
-    return ServiceManager.getService(project, BlazeProjectDataManager.class);
-  }
-
-  @Nullable
-  BlazeProjectData getBlazeProjectData();
-
-  BlazeSyncPlugin.ModuleEditor editModules();
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/data/BlazeProjectDataManagerImpl.java b/blaze-base/src/com/google/idea/blaze/base/sync/data/BlazeProjectDataManagerImpl.java
deleted file mode 100644
index c10640a..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/data/BlazeProjectDataManagerImpl.java
+++ /dev/null
@@ -1,137 +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.data;
-
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.async.executor.BlazeExecutor;
-import com.google.idea.blaze.base.model.BlazeProjectData;
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.google.idea.blaze.base.scope.output.StatusOutput;
-import com.google.idea.blaze.base.settings.BlazeImportSettings;
-import com.google.idea.blaze.base.settings.BlazeImportSettingsManager;
-import com.google.idea.blaze.base.sync.BlazeSyncPlugin;
-import com.google.idea.blaze.base.sync.projectstructure.ModuleEditorProvider;
-import com.google.idea.blaze.base.util.SerializationUtil;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.progress.ProgressIndicator;
-import com.intellij.openapi.project.Project;
-
-import javax.annotation.Nullable;
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
-
-/**
- * Stores a cache of blaze project data and issues any side effects when that data is updated.
- */
-public class BlazeProjectDataManagerImpl implements BlazeProjectDataManager {
-
-  private static final Logger LOG = Logger.getInstance(BlazeProjectDataManagerImpl.class.getName());
-
-  private final Project project;
-
-  @Nullable
-  private volatile BlazeProjectData blazeProjectData;
-
-  private final Object saveLock = new Object();
-
-  public static BlazeProjectDataManagerImpl getImpl(Project project) {
-    return (BlazeProjectDataManagerImpl) BlazeProjectDataManager.getInstance(project);
-  }
-
-  public BlazeProjectDataManagerImpl(Project project) {
-    this.project = project;
-  }
-
-  @Nullable
-  public BlazeProjectData loadProjectRoot(
-    BlazeContext context,
-    BlazeImportSettings importSettings) {
-    BlazeProjectData projectData = blazeProjectData;
-    if (projectData != null) {
-      return projectData;
-    }
-    synchronized (this) {
-      projectData = blazeProjectData;
-      return projectData != null ? projectData : loadProject(context, importSettings);
-    }
-  }
-
-  @Override
-  @Nullable
-  public BlazeProjectData getBlazeProjectData() {
-    return blazeProjectData;
-  }
-
-  @Override
-  public BlazeSyncPlugin.ModuleEditor editModules() {
-    return ModuleEditorProvider.getInstance().getModuleEditor(
-      project,
-      BlazeImportSettingsManager.getInstance(project).getImportSettings()
-    );
-  }
-
-  @Nullable
-  private synchronized BlazeProjectData loadProject(
-    BlazeContext context,
-    BlazeImportSettings importSettings) {
-    BlazeProjectData blazeProjectData = null;
-    try {
-      File file = getCacheFile(project, importSettings);
-
-      List<ClassLoader> classLoaders = Lists.newArrayList();
-      for (BlazeSyncPlugin syncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) {
-        classLoaders.add(syncPlugin.getClass().getClassLoader());
-      }
-      classLoaders.add(getClass().getClassLoader());
-      classLoaders.add(Thread.currentThread().getContextClassLoader());
-
-      blazeProjectData = (BlazeProjectData)SerializationUtil.loadFromDisk(file, classLoaders);
-    }
-    catch (IOException e) {
-      String buildSystemName = importSettings.getBuildSystem().getLowerCaseName();
-      context.output(new StatusOutput(String.format("Stale %s project cache, sync will be needed", buildSystemName)));
-      LOG.info(e);
-    }
-
-    this.blazeProjectData = blazeProjectData;
-    return blazeProjectData;
-  }
-
-  public void saveProject(
-    final BlazeImportSettings importSettings,
-    final BlazeProjectData blazeProjectData) {
-    this.blazeProjectData = blazeProjectData;
-
-    // Can only run one save operation per project at a time
-    synchronized (saveLock) {
-      BlazeExecutor.submitTask(project, "Saving sync data...", (ProgressIndicator indicator) -> {
-        try {
-          File file = getCacheFile(project, importSettings);
-          SerializationUtil.saveToDisk(file, blazeProjectData);
-        }
-        catch (IOException e) {
-          LOG.error("Could not save cache data file to disk. Please resync project. Error: " + e.getMessage());
-        }
-      });
-    }
-  }
-
-  private static File getCacheFile(Project project, BlazeImportSettings importSettings) {
-    return new File(BlazeDataStorage.getProjectCacheDir(project, importSettings), BlazeDataStorage.CACHE_FILE_NAME);
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/filediff/FileDiffService.java b/blaze-base/src/com/google/idea/blaze/base/sync/filediff/FileDiffService.java
deleted file mode 100644
index 9cb4854..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/filediff/FileDiffService.java
+++ /dev/null
@@ -1,132 +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.filediff;
-
-import com.google.common.base.Objects;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Lists;
-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.io.FileAttributeProvider;
-import com.intellij.openapi.diagnostic.Logger;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.io.File;
-import java.io.Serializable;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.Callable;
-
-/**
- * Provides a diffing service for a collection of files.
- */
-public class FileDiffService {
-  private static Logger LOG = Logger.getInstance(FileDiffService.class);
-
-  public static class State implements Serializable {
-    private static final long serialVersionUID = 2L;
-    Map<File, FileEntry> fileEntryMap;
-  }
-
-  static class FileEntry implements Serializable {
-    private static final long serialVersionUID = 2L;
-
-    public File file;
-    public long timestamp;
-
-    @Override
-    public boolean equals(Object o) {
-      if (this == o) return true;
-      if (o == null || getClass() != o.getClass()) return false;
-      FileEntry fileEntry = (FileEntry)o;
-      return Objects.equal(timestamp, fileEntry.timestamp) &&
-             Objects.equal(file, fileEntry.file);
-    }
-
-    @Override
-    public int hashCode() {
-      return Objects.hashCode(file, timestamp);
-    }
-  }
-
-  @Nullable
-  public State updateFiles(@Nullable State oldState,
-                           @NotNull Iterable<File> files,
-                           @NotNull List<File> updatedFiles,
-                           @NotNull List<File> removedFiles) {
-    Map<File, FileEntry> oldFiles = oldState != null
-                                    ? oldState.fileEntryMap
-                                    : ImmutableMap.of();
-
-    List<FileEntry> fileEntryList = null;
-    try {
-      fileEntryList = updateTimeStamps(files);
-    } catch (Exception e) {
-      LOG.error(e);
-      return null;
-    }
-
-    // Find changed/new
-    for (FileEntry newFile : fileEntryList) {
-      FileEntry oldFile = oldFiles.get(newFile.file);
-      final boolean isNew = oldFile == null || newFile.timestamp != oldFile.timestamp;
-      if (isNew) {
-        updatedFiles.add(newFile.file);
-      }
-    }
-
-    // Find removed
-    Set<File> newFiles = Sets.newHashSet();
-    for (File file : files) {
-      newFiles.add(file);
-    }
-    for (File file : oldFiles.keySet()) {
-      if (!newFiles.contains(file)) {
-        removedFiles.add(file);
-      }
-    }
-    ImmutableMap.Builder<File, FileEntry> fileMap = ImmutableMap.builder();
-    for (FileEntry fileEntry : fileEntryList) {
-      fileMap.put(fileEntry.file, fileEntry);
-    }
-    State newState = new State();
-    newState.fileEntryMap = fileMap.build();
-    return newState;
-  }
-
-  private static List<FileEntry> updateTimeStamps(@NotNull Iterable<File> fileList) throws Exception {
-    final FileAttributeProvider fileAttributeProvider = FileAttributeProvider.getInstance();
-    List<ListenableFuture<FileEntry>> futures = Lists.newArrayList();
-    for (File file : fileList) {
-      futures.add(submit(() -> {
-                           FileEntry fileEntry = new FileEntry();
-                           fileEntry.file = file;
-                           fileEntry.timestamp = fileAttributeProvider.getFileModifiedTime(fileEntry.file);
-                           return fileEntry;
-                         }
-      ));
-    }
-    return Futures.allAsList(futures).get();
-  }
-
-  private static <T> ListenableFuture<T> submit(Callable<T> callable) {
-    return BlazeExecutor.getInstance().submit(callable);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/projectstructure/ContentEntryEditor.java b/blaze-base/src/com/google/idea/blaze/base/sync/projectstructure/ContentEntryEditor.java
deleted file mode 100644
index 5fe25e7..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/projectstructure/ContentEntryEditor.java
+++ /dev/null
@@ -1,135 +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.projectstructure;
-
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Multimap;
-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.projectview.ProjectViewSet;
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.google.idea.blaze.base.settings.Blaze;
-import com.google.idea.blaze.base.sync.BlazeSyncPlugin;
-import com.google.idea.blaze.base.sync.projectview.ImportRoots;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.roots.ContentEntry;
-import com.intellij.openapi.roots.ModifiableRootModel;
-import com.intellij.openapi.util.io.FileUtil;
-import com.intellij.openapi.util.io.FileUtilRt;
-import com.intellij.openapi.vfs.VfsUtilCore;
-import com.intellij.util.io.URLUtil;
-import org.jetbrains.annotations.NonNls;
-import org.jetbrains.annotations.NotNull;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.List;
-
-public class ContentEntryEditor {
-
-  public static void createContentEntries(Project project,
-                                          BlazeContext context,
-                                          WorkspaceRoot workspaceRoot,
-                                          ProjectViewSet projectViewSet,
-                                          BlazeProjectData blazeProjectData,
-                                          ModifiableRootModel modifiableRootModel) {
-    ImportRoots importRoots = ImportRoots.builder(workspaceRoot, Blaze.getBuildSystem(project))
-      .add(projectViewSet)
-      .build();
-    Collection<WorkspacePath> rootDirectories = importRoots.rootDirectories();
-    Collection<WorkspacePath> excludeDirectories = importRoots.excludeDirectories();
-    Multimap<WorkspacePath, WorkspacePath> excludesByRootDirectory = sortExcludesByRootDirectory(rootDirectories, excludeDirectories);
-
-
-    List<ContentEntry> contentEntries = Lists.newArrayList();
-    for (WorkspacePath rootDirectory : rootDirectories) {
-      File root = workspaceRoot.fileForPath(rootDirectory);
-      ContentEntry contentEntry = modifiableRootModel.addContentEntry(pathToUrl(root.getPath()));
-      contentEntries.add(contentEntry);
-
-      for (WorkspacePath exclude : excludesByRootDirectory.get(rootDirectory)) {
-        File excludeFolder = workspaceRoot.fileForPath(exclude);
-        contentEntry.addExcludeFolder(pathToIdeaUrl(excludeFolder));
-      }
-    }
-
-    for (BlazeSyncPlugin syncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) {
-      syncPlugin.updateContentEntries(
-        project,
-        context,
-        workspaceRoot,
-        projectViewSet,
-        blazeProjectData,
-        contentEntries
-      );
-    }
-  }
-
-  private static Multimap<WorkspacePath, WorkspacePath> sortExcludesByRootDirectory(
-    Collection<WorkspacePath> rootDirectories,
-    Collection<WorkspacePath> excludedDirectories) {
-
-    Multimap<WorkspacePath, WorkspacePath> result = ArrayListMultimap.create();
-    for (WorkspacePath exclude : excludedDirectories) {
-      WorkspacePath foundWorkspacePath = rootDirectories
-        .stream()
-        .filter(rootDirectory -> isUnderRootDirectory(rootDirectory, exclude.relativePath()))
-        .findFirst()
-        .orElse(null);
-      if (foundWorkspacePath != null) {
-        result.put(foundWorkspacePath, exclude);
-      }
-    }
-    return result;
-  }
-
-  private static boolean isUnderRootDirectory(WorkspacePath rootDirectory, String relativePath) {
-    if (rootDirectory.isWorkspaceRoot()) {
-      return true;
-    }
-    String rootDirectoryString = rootDirectory.toString();
-    return relativePath.startsWith(rootDirectoryString)
-           && (relativePath.length() == rootDirectoryString.length()
-               || (relativePath.charAt(rootDirectoryString.length()) == '/'));
-  }
-
-  @NotNull
-  private static String pathToUrl(@NotNull String filePath) {
-    filePath = FileUtil.toSystemIndependentName(filePath);
-    if (filePath.endsWith(".srcjar") || filePath.endsWith(".jar")) {
-      return URLUtil.JAR_PROTOCOL + URLUtil.SCHEME_SEPARATOR +
-             filePath + URLUtil.JAR_SEPARATOR;
-    }
-    else if (filePath.contains("src.jar!")) {
-      return URLUtil.JAR_PROTOCOL + URLUtil.SCHEME_SEPARATOR +
-             filePath;
-    }
-    else {
-      return VfsUtilCore.pathToUrl(filePath);
-    }
-  }
-
-  @NotNull
-  private static String pathToIdeaUrl(@NotNull File path) {
-    return pathToUrl(toSystemIndependentName(path.getPath()));
-  }
-  @NotNull
-  private static String toSystemIndependentName(@NonNls @NotNull String aFileName) {
-    return FileUtilRt.toSystemIndependentName(aFileName);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/projectstructure/ModuleDataStorage.java b/blaze-base/src/com/google/idea/blaze/base/sync/projectstructure/ModuleDataStorage.java
deleted file mode 100644
index 9371728..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/projectstructure/ModuleDataStorage.java
+++ /dev/null
@@ -1,26 +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.projectstructure;
-
-/**
- * Constants about where we store module data.
- */
-public class ModuleDataStorage {
-  public static final String MODULE_DATA_SUBDIRECTORY = "modules";
-  public static final String DATA_SUBDIRECTORY = ".blaze";
-  public static final String WORKSPACE_MODULE_NAME = ".workspace";
-  public static final String PROJECT_DATA_DIR_MODULE_NAME = ".project-data-dir";
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/projectstructure/ModuleEditorImpl.java b/blaze-base/src/com/google/idea/blaze/base/sync/projectstructure/ModuleEditorImpl.java
deleted file mode 100644
index 2ae1167..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/projectstructure/ModuleEditorImpl.java
+++ /dev/null
@@ -1,177 +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.projectstructure;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-import com.google.idea.blaze.base.io.FileAttributeProvider;
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.google.idea.blaze.base.scope.output.PrintOutput;
-import com.google.idea.blaze.base.settings.BlazeImportSettings;
-import com.google.idea.blaze.base.sync.BlazeSyncPlugin;
-import com.intellij.ide.highlighter.ModuleFileType;
-import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.module.ModifiableModuleModel;
-import com.intellij.openapi.module.Module;
-import com.intellij.openapi.module.ModuleManager;
-import com.intellij.openapi.module.ModuleType;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.roots.CompilerModuleExtension;
-import com.intellij.openapi.roots.ModifiableRootModel;
-import com.intellij.openapi.roots.ModuleRootManager;
-import com.intellij.openapi.roots.impl.ModifiableModelCommitter;
-import com.intellij.openapi.vfs.VfsUtil;
-import com.intellij.openapi.vfs.VirtualFile;
-
-import javax.annotation.Nullable;
-import java.io.File;
-import java.io.IOException;
-import java.util.Collection;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Module editor implementation.
- */
-public class ModuleEditorImpl implements BlazeSyncPlugin.ModuleEditor {
-  private static final Logger LOG = Logger.getInstance(ModuleEditorImpl.class.getName());
-  private static final String EXTERNAL_SYSTEM_ID_KEY = "external.system.id";
-  private static final String EXTERNAL_SYSTEM_ID_VALUE = "Blaze";
-
-  private final Project project;
-  private final ModifiableModuleModel moduleModel;
-  private final File imlDirectory;
-  private final Set<String> moduleNames = Sets.newHashSet();
-  @VisibleForTesting
-  public Collection<ModifiableRootModel> modifiableModels = Lists.newArrayList();
-
-  public ModuleEditorImpl(Project project, BlazeImportSettings importSettings) {
-    this.project = project;
-    this.moduleModel = ModuleManager.getInstance(project).getModifiableModel();
-
-    this.imlDirectory = getImlDirectory(importSettings);
-    if (!FileAttributeProvider.getInstance().exists(imlDirectory)) {
-      if (!imlDirectory.mkdirs()) {
-        LOG.error("Could not make directory: " + imlDirectory.getPath());
-      }
-    }
-  }
-
-  @Override
-  public boolean registerModule(String moduleName) {
-    boolean hasModule = moduleModel.findModuleByName(moduleName) != null;
-    if (hasModule) {
-      moduleNames.add(moduleName);
-    }
-    return hasModule;
-  }
-
-  @Override
-  public Module createModule(String moduleName, ModuleType moduleType) {
-    Module module = moduleModel.findModuleByName(moduleName);
-    if (module == null) {
-      File imlFile = new File(imlDirectory, moduleName + ModuleFileType.DOT_DEFAULT_EXTENSION);
-      removeImlFile(imlFile);
-      module = moduleModel.newModule(imlFile.getPath(), moduleType.getId());
-      module.setOption(EXTERNAL_SYSTEM_ID_KEY, EXTERNAL_SYSTEM_ID_VALUE);
-    }
-    module.setOption(Module.ELEMENT_TYPE, moduleType.getId());
-    moduleNames.add(moduleName);
-    return module;
-  }
-
-  @Override
-  public ModifiableRootModel editModule(Module module) {
-    ModifiableRootModel modifiableModel = ModuleRootManager.getInstance(module).getModifiableModel();
-    modifiableModels.add(modifiableModel);
-
-    modifiableModel.clear();
-    modifiableModel.inheritSdk();
-    CompilerModuleExtension compilerSettings = modifiableModel.getModuleExtension(CompilerModuleExtension.class);
-    if (compilerSettings != null) {
-      compilerSettings.inheritCompilerOutputPath(false);
-    }
-
-    return modifiableModel;
-  }
-
-  @Override
-  @Nullable
-  public Module findModule(String moduleName) {
-    return moduleModel.findModuleByName(moduleName);
-  }
-
-  public void commitWithGc(BlazeContext context) {
-    List<Module> orphanModules = Lists.newArrayList();
-    for (Module module : ModuleManager.getInstance(project).getModules()) {
-      if (!moduleNames.contains(module.getName())) {
-        orphanModules.add(module);
-      }
-    }
-    if (orphanModules.size() > 0) {
-      context.output(new PrintOutput(
-        String.format("Removing %d dead modules", orphanModules.size()))
-      );
-      for (Module module : orphanModules) {
-        if (module.isDisposed()) {
-          continue;
-        }
-        moduleModel.disposeModule(module);
-        File imlFile = new File(module.getModuleFilePath());
-        removeImlFile(imlFile);
-      }
-    }
-
-    context.output(new PrintOutput(
-      String.format("Workspace has %s modules", modifiableModels.size())
-    ));
-
-    commit();
-  }
-
-  @Override
-  public void commit() {
-    ModifiableModelCommitter.multiCommit(modifiableModels, moduleModel);
-  }
-
-  private File getImlDirectory(BlazeImportSettings importSettings) {
-    return new File(
-      new File(importSettings.getProjectDataDirectory(), ModuleDataStorage.DATA_SUBDIRECTORY),
-      ModuleDataStorage.MODULE_DATA_SUBDIRECTORY
-    );
-  }
-
-  // Delete using the virtual file to ensure that IntelliJ properly updates its index. Otherwise, it is possible for IntelliJ to read the
-  // old IML file from its index and behave unpredictably (like failing to save the new IML files to disk).
-  private static void removeImlFile(final File imlFile) {
-    final VirtualFile imlVirtualFile = VfsUtil.findFileByIoFile(imlFile, true);
-    if (imlVirtualFile != null && imlVirtualFile.exists()) {
-      ApplicationManager.getApplication().runWriteAction(new Runnable() {
-        @Override
-        public void run() {
-          try {
-            imlVirtualFile.delete(this);
-          }
-          catch (IOException e) {
-            LOG.warn(String.format("Could not delete file: %s, will try to continue anyway.", imlVirtualFile.getPath()), e);
-          }
-        }
-      });
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/projectstructure/ModuleEditorProvider.java b/blaze-base/src/com/google/idea/blaze/base/sync/projectstructure/ModuleEditorProvider.java
deleted file mode 100644
index 3c00fe4..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/projectstructure/ModuleEditorProvider.java
+++ /dev/null
@@ -1,33 +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.projectstructure;
-
-import com.google.idea.blaze.base.settings.BlazeImportSettings;
-import com.intellij.openapi.components.ServiceManager;
-import com.intellij.openapi.project.Project;
-
-/**
- * Provides a ModuleEditor. This indirection is required to avoid committing modules
- * during integration tests of the sync process, as this is not allowed by LightPlatformTestCase.
- */
-public interface ModuleEditorProvider {
-
-  static ModuleEditorProvider getInstance() {
-    return ServiceManager.getService(ModuleEditorProvider.class);
-  }
-
-  ModuleEditorImpl getModuleEditor(Project project, BlazeImportSettings importSettings);
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/projectstructure/ModuleEditorProviderImpl.java b/blaze-base/src/com/google/idea/blaze/base/sync/projectstructure/ModuleEditorProviderImpl.java
deleted file mode 100644
index dfc5fe7..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/projectstructure/ModuleEditorProviderImpl.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.sync.projectstructure;
-
-import com.google.idea.blaze.base.settings.BlazeImportSettings;
-import com.intellij.openapi.project.Project;
-
-/**
- * Provides a ModuleEditor. This indirection is required to avoid committing modules
- * during integration tests of the sync process, as this is not allowed by LightPlatformTestCase.
- */
-public class ModuleEditorProviderImpl implements ModuleEditorProvider {
-
-  @Override
-  public ModuleEditorImpl getModuleEditor(Project project, BlazeImportSettings importSettings) {
-    return new ModuleEditorImpl(project, importSettings);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/projectview/ImportRoots.java b/blaze-base/src/com/google/idea/blaze/base/sync/projectview/ImportRoots.java
deleted file mode 100644
index 4e7ef66..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/projectview/ImportRoots.java
+++ /dev/null
@@ -1,145 +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.projectview;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.ImmutableCollection;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.idea.blaze.base.bazel.BuildSystemProvider;
-import com.google.idea.blaze.base.model.primitives.Label;
-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.section.sections.DirectoryEntry;
-import com.google.idea.blaze.base.projectview.section.sections.DirectorySection;
-import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
-
-import java.util.Collection;
-import java.util.Set;
-
-/**
- * The roots to import. Derived from project view.
- */
-public final class ImportRoots {
-  public static class Builder {
-    private final ImmutableCollection.Builder<WorkspacePath> rootDirectoriesBuilder = ImmutableList.builder();
-    private final ImmutableSet.Builder<WorkspacePath> excludeDirectoriesBuilder = ImmutableSet.builder();
-
-    private final WorkspaceRoot workspaceRoot;
-    private final BuildSystem buildSystem;
-
-    private Builder(WorkspaceRoot workspaceRoot, BuildSystem buildSystem) {
-      this.workspaceRoot = workspaceRoot;
-      this.buildSystem = buildSystem;
-    }
-
-    public Builder add(ProjectViewSet projectViewSet) {
-      for (DirectoryEntry entry : projectViewSet.listItems(DirectorySection.KEY)) {
-        add(entry);
-      }
-      return this;
-    }
-
-    @VisibleForTesting
-    public Builder add(DirectoryEntry entry) {
-      if (entry.included) {
-        rootDirectoriesBuilder.add(entry.directory);
-      } else {
-        excludeDirectoriesBuilder.add(entry.directory);
-      }
-      return this;
-    }
-
-    public ImportRoots build() {
-      ImmutableCollection<WorkspacePath> rootDirectories = rootDirectoriesBuilder.build();
-      // for bazel projects, if we're including the workspace root, we force-exclude the bazel artifact directories
-      // (e.g. bazel-bin, bazel-genfiles).
-      if (buildSystem == BuildSystem.Bazel && hasWorkspaceRoot(rootDirectories)) {
-        excludeBuildSystemArtifacts();
-      }
-      return new ImportRoots(rootDirectories, excludeDirectoriesBuilder.build());
-    }
-
-    private void excludeBuildSystemArtifacts() {
-      for (String dir : BuildSystemProvider.getBuildSystemProvider(buildSystem).buildArtifactDirectories(workspaceRoot)) {
-        excludeDirectoriesBuilder.add(new WorkspacePath(dir));
-      }
-    }
-
-    private static boolean hasWorkspaceRoot(ImmutableCollection<WorkspacePath> rootDirectories) {
-      return rootDirectories.stream().anyMatch(WorkspacePath::isWorkspaceRoot);
-    }
-
-  }
-
-  private final ImmutableCollection<WorkspacePath> rootDirectories;
-  private final ImmutableSet<WorkspacePath> excludeDirectories;
-
-  public static Builder builder(WorkspaceRoot workspaceRoot, BuildSystem buildSystem) {
-    return new Builder(workspaceRoot, buildSystem);
-  }
-
-  private ImportRoots(
-    ImmutableCollection<WorkspacePath> rootDirectories,
-    ImmutableSet<WorkspacePath> excludeDirectories) {
-    this.rootDirectories = rootDirectories;
-    this.excludeDirectories = excludeDirectories;
-  }
-
-  public Collection<WorkspacePath> rootDirectories() {
-    return rootDirectories;
-  }
-
-  public Set<WorkspacePath> excludeDirectories() {
-    return excludeDirectories;
-  }
-
-  /**
-   * Returns true if this rule should be imported as source.
-   */
-  public boolean importAsSource(Label label) {
-    return containsLabel(label);
-  }
-
-  private boolean containsLabel(Label label) {
-    boolean included = false;
-    boolean excluded = false;
-    for (WorkspacePath workspacePath : rootDirectories()) {
-      included = included || matchesLabel(workspacePath, label);
-    }
-    for (WorkspacePath workspacePath : excludeDirectories()) {
-      excluded = excluded || matchesLabel(workspacePath, label);
-    }
-    return included && !excluded;
-  }
-
-  private static boolean matchesLabel(WorkspacePath workspacePath, Label label) {
-    if (workspacePath.isWorkspaceRoot()) {
-      return true;
-    }
-    String moduleLabelStr = label.toString();
-    int packagePrefixLength = "//".length();
-    int nextCharIndex = workspacePath.relativePath().length() + packagePrefixLength;
-    if (moduleLabelStr.startsWith(workspacePath.relativePath(), packagePrefixLength)
-        && moduleLabelStr.length() >= nextCharIndex) {
-      char c = moduleLabelStr.charAt(nextCharIndex);
-      return c == '/' || c == ':';
-    }
-    return false;
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/projectview/LanguageSupport.java b/blaze-base/src/com/google/idea/blaze/base/sync/projectview/LanguageSupport.java
deleted file mode 100644
index e3a8d97..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/projectview/LanguageSupport.java
+++ /dev/null
@@ -1,83 +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.projectview;
-
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Sets;
-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.ProjectViewSet;
-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.output.IssueOutput;
-import com.google.idea.blaze.base.sync.BlazeSyncPlugin;
-import com.intellij.openapi.diagnostic.Logger;
-
-import java.util.Collection;
-import java.util.Set;
-
-/**
- * Reads the user's language preferences from the project view.
- */
-public class LanguageSupport {
-
-  private static final Logger LOG = Logger.getInstance(LanguageSupport.class);
-
-  public static WorkspaceLanguageSettings createWorkspaceLanguageSettings(BlazeContext context, ProjectViewSet projectViewSet) {
-    WorkspaceType workspaceType = projectViewSet.getSectionValue(WorkspaceTypeSection.KEY);
-    if (workspaceType == null) {
-      for (BlazeSyncPlugin syncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) {
-        WorkspaceType pluginWorkspaceType = syncPlugin.getDefaultWorkspaceType();
-        if (pluginWorkspaceType != null) {
-          if (workspaceType == null || workspaceType.ordinal() < pluginWorkspaceType.ordinal()) {
-            workspaceType = pluginWorkspaceType;
-          }
-        }
-      }
-    }
-
-    if (workspaceType == null) {
-      LOG.error("Could not find workspace type."); // Should never happen
-      return null;
-    }
-
-    Set<LanguageClass> activeLanguages = Sets.newHashSet();
-    for (LanguageClass languageClass : workspaceType.getLanguages()) {
-      activeLanguages.add(languageClass);
-    }
-    activeLanguages.addAll(projectViewSet.listItems(AdditionalLanguagesSection.KEY));
-
-    Set<LanguageClass> supportedLanguages = Sets.newHashSet();
-    for (BlazeSyncPlugin syncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) {
-      supportedLanguages.addAll(syncPlugin.getSupportedLanguagesInWorkspace(workspaceType));
-    }
-
-    for (LanguageClass languageClass : activeLanguages) {
-      if (!supportedLanguages.contains(languageClass)) {
-        IssueOutput
-          .error(String.format(
-            "Language '%s' is not supported for this plugin with workspace type: '%s'",
-            languageClass.getName(), workspaceType.getName()))
-          .submit(context);
-        return null;
-      }
-    }
-
-    return new WorkspaceLanguageSettings(workspaceType, activeLanguages);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/projectview/ProjectViewRuleImportFilter.java b/blaze-base/src/com/google/idea/blaze/base/sync/projectview/ProjectViewRuleImportFilter.java
deleted file mode 100644
index 1585de9..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/projectview/ProjectViewRuleImportFilter.java
+++ /dev/null
@@ -1,58 +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.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.model.primitives.Label;
-import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
-import com.google.idea.blaze.base.projectview.ProjectViewSet;
-import com.google.idea.blaze.base.projectview.section.sections.ExcludeTargetSection;
-import com.google.idea.blaze.base.projectview.section.sections.ImportTargetOutputSection;
-import com.google.idea.blaze.base.settings.Blaze;
-import com.intellij.openapi.project.Project;
-
-import java.util.Set;
-
-/**
- * Filters rules into source/library depending on the project view.
- */
-public class ProjectViewRuleImportFilter {
-  private final ImportRoots importRoots;
-  private final Set<Label> importTargetOutputs;
-  private final Set<Label> excludedTargets;
-
-  public ProjectViewRuleImportFilter(Project project, WorkspaceRoot workspaceRoot, ProjectViewSet projectViewSet) {
-    this.importRoots = ImportRoots.builder(workspaceRoot, Blaze.getBuildSystem(project)).add(projectViewSet).build();
-    this.importTargetOutputs = Sets.newHashSet(projectViewSet.listItems(ImportTargetOutputSection.KEY));
-    this.excludedTargets = Sets.newHashSet(projectViewSet.listItems(ExcludeTargetSection.KEY));
-  }
-
-  public boolean isSourceRule(RuleIdeInfo rule) {
-    return importRoots.importAsSource(rule.label) && !importTargetOutput(rule);
-  }
-
-  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);
-  }
-
-  public boolean excludeTarget(RuleIdeInfo rule) {
-    return excludedTargets.contains(rule.label) || rule.tags.contains(Tags.RULE_TAG_PROVIDED_BY_SDK);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/projectview/SourceTestConfig.java b/blaze-base/src/com/google/idea/blaze/base/sync/projectview/SourceTestConfig.java
deleted file mode 100644
index 04ab82c..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/projectview/SourceTestConfig.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.sync.projectview;
-
-import com.google.idea.blaze.base.projectview.ProjectViewSet;
-import com.google.idea.blaze.base.projectview.section.Glob;
-import com.google.idea.blaze.base.projectview.section.sections.TestSourceSection;
-
-/**
- * Affects the way sources are imported.
- */
-public class SourceTestConfig {
-  private final Glob.GlobSet testSources;
-
-  public SourceTestConfig(ProjectViewSet projectViewSet) {
-    this.testSources = new Glob.GlobSet(projectViewSet.listItems(TestSourceSection.KEY));
-  }
-
-  /**
-   * Returns true if this artifact is a test artifact.
-   */
-  public boolean isTestSource(String relativePath) {
-    return testSources.matches(relativePath);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/projectview/WorkspaceLanguageSettings.java b/blaze-base/src/com/google/idea/blaze/base/sync/projectview/WorkspaceLanguageSettings.java
deleted file mode 100644
index 3c9e5f2..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/projectview/WorkspaceLanguageSettings.java
+++ /dev/null
@@ -1,84 +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.projectview;
-
-import com.google.common.base.Objects;
-import com.google.idea.blaze.base.model.primitives.LanguageClass;
-import com.google.idea.blaze.base.model.primitives.WorkspaceType;
-
-import javax.annotation.concurrent.Immutable;
-import java.io.Serializable;
-import java.util.Set;
-
-/**
- * Contains the user's language preferences from the project view.
- */
-@Immutable
-public class WorkspaceLanguageSettings implements Serializable {
-  private static final long serialVersionUID = 1L;
-
-  private final WorkspaceType workspaceType;
-  private final Set<LanguageClass> activeLanguages;
-
-  public WorkspaceLanguageSettings(WorkspaceType workspaceType,
-                                   Set<LanguageClass> activeLanguages) {
-    this.workspaceType = workspaceType;
-    this.activeLanguages = activeLanguages;
-  }
-
-  public WorkspaceType getWorkspaceType() {
-    return workspaceType;
-  }
-
-  public boolean isWorkspaceType(WorkspaceType workspaceType) {
-    return this.workspaceType == workspaceType;
-  }
-
-  public boolean isWorkspaceType(WorkspaceType... workspaceTypes) {
-    for (WorkspaceType workspaceType : workspaceTypes) {
-      if (this.workspaceType == workspaceType) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  public boolean isLanguageActive(LanguageClass languageClass) {
-    return activeLanguages.contains(languageClass);
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) return true;
-    if (o == null || getClass() != o.getClass()) return false;
-    WorkspaceLanguageSettings that = (WorkspaceLanguageSettings)o;
-    return workspaceType == that.workspaceType
-           && Objects.equal(activeLanguages, that.activeLanguages);
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hashCode(workspaceType, activeLanguages);
-  }
-
-  @Override
-  public String toString() {
-    return "WorkspaceLanguageSettings {" + "\n"
-           + "  workspaceType: " + workspaceType + "\n"
-           + "  activeLanguages: " + activeLanguages + "\n"
-           + '}';
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/sdk/DefaultSdkProvider.java b/blaze-base/src/com/google/idea/blaze/base/sync/sdk/DefaultSdkProvider.java
deleted file mode 100644
index e444984..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/sdk/DefaultSdkProvider.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.base.sync.sdk;
-
-import com.google.idea.blaze.base.model.primitives.LanguageClass;
-import com.intellij.openapi.extensions.ExtensionPointName;
-
-import javax.annotation.Nullable;
-import java.io.File;
-
-/**
- * May download or otherwise provide default sdk locations for languages.
- */
-public interface DefaultSdkProvider {
-  ExtensionPointName<DefaultSdkProvider> EP_NAME = ExtensionPointName.create("com.google.idea.blaze.DefaultSdkProvider");
-
-  @Nullable
-  File provideSdkForLanguage(LanguageClass language);
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/status/BlazeSyncStatus.java b/blaze-base/src/com/google/idea/blaze/base/sync/status/BlazeSyncStatus.java
deleted file mode 100644
index 1bc7381..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/status/BlazeSyncStatus.java
+++ /dev/null
@@ -1,42 +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.status;
-
-import com.intellij.openapi.components.ServiceManager;
-import com.intellij.openapi.project.Project;
-
-/**
- * Interface to tell blaze it might need to resync.
- */
-public interface BlazeSyncStatus {
-
-  enum SyncStatus {
-    FAILED,
-    DIRTY,
-    CLEAN,
-  }
-
-  SyncStatus getStatus();
-
-  static BlazeSyncStatus getInstance(Project project) {
-    return ServiceManager.getService(project, BlazeSyncStatus.class);
-  }
-
-  void setDirty();
-
-  void queueAutomaticSyncIfDirty();
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/status/BlazeSyncStatusImpl.java b/blaze-base/src/com/google/idea/blaze/base/sync/status/BlazeSyncStatusImpl.java
deleted file mode 100644
index dba518e..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/status/BlazeSyncStatusImpl.java
+++ /dev/null
@@ -1,199 +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.status;
-
-import com.google.idea.blaze.base.experiments.BoolExperiment;
-import com.google.idea.blaze.base.projectview.ProjectViewStorageManager;
-import com.google.idea.blaze.base.settings.BlazeUserSettings;
-import com.google.idea.blaze.base.sync.BlazeSyncManager;
-import com.google.idea.blaze.base.sync.actions.IncrementalSyncProjectAction;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.editor.Document;
-import com.intellij.openapi.fileEditor.*;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.vfs.*;
-import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.util.Collection;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * Per-project listener for changes to BUILD files, and other changes requiring an incremental sync.
- */
-public class BlazeSyncStatusImpl implements BlazeSyncStatus {
-
-  public static final BoolExperiment AUTOMATIC_INCREMENTAL_SYNC =
-    new BoolExperiment("automatic.incremental.sync", true);
-
-  public static BlazeSyncStatusImpl getImpl(@NotNull Project project) {
-    return (BlazeSyncStatusImpl) BlazeSyncStatus.getInstance(project);
-  }
-
-  private static Logger log = Logger.getInstance(BlazeSyncStatusImpl.class);
-
-  private final Project project;
-
-  public final AtomicBoolean syncInProgress = new AtomicBoolean(false);
-  private final AtomicBoolean syncPending = new AtomicBoolean(false);
-
-  /**
-   * has a BUILD file changed since the last sync started
-   */
-  private volatile boolean dirty = false;
-
-  private volatile boolean failedSync = false;
-
-  public BlazeSyncStatusImpl(Project project) {
-    this.project = project;
-    // listen for changes to the VFS
-    VirtualFileManager.getInstance().addVirtualFileListener(new FileListener(), project);
-
-    // trigger VFS updates whenever navigating away from an unsaved BUILD file
-    project.getMessageBus().connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER,
-                                                new FileFocusListener());
-  }
-
-  private static boolean automaticSyncEnabled() {
-    return AUTOMATIC_INCREMENTAL_SYNC.getValue()
-        && BlazeUserSettings.getInstance().getResyncAutomatically();
-  }
-
-  @Override
-  public SyncStatus getStatus() {
-    if (failedSync) {
-      return SyncStatus.FAILED;
-    }
-    return dirty ? SyncStatus.DIRTY : SyncStatus.CLEAN;
-  }
-
-  public void syncStarted() {
-    syncPending.set(false);
-    syncInProgress.set(true);
-  }
-
-  public void syncEnded(boolean successful) {
-    syncInProgress.set(false);
-    failedSync = !successful;
-    if (successful && !syncPending.get()) {
-      dirty = false;
-    }
-  }
-
-  @Override
-  public void setDirty() {
-    dirty = true;
-    queueIncrementalSync();
-  }
-
-  @Override
-  public void queueAutomaticSyncIfDirty() {
-    if (dirty) {
-      queueIncrementalSync();
-    }
-  }
-
-  private void queueIncrementalSync() {
-    if (automaticSyncEnabled() && syncPending.compareAndSet(false, true)) {
-      log.info("Automatic sync started");
-      BlazeSyncManager.getInstance(project).requestProjectSync(IncrementalSyncProjectAction.autoSyncParams);
-    }
-  }
-
-  /**
-   * Listens for changes to files which impact the sync process
-   * (BUILD files and project view files)
-   */
-  private class FileListener extends VirtualFileAdapter {
-    @Override
-    public void fileCreated(@NotNull VirtualFileEvent event) {
-      processEvent(event);
-    }
-
-    @Override
-    public void fileDeleted(@NotNull VirtualFileEvent event){
-      processEvent(event);
-      // we (sometimes) only get one event when a directory is deleted, so check the children too.
-      checkChildren(event.getFile());
-    }
-
-    @Override
-    public void fileMoved(@NotNull VirtualFileMoveEvent event){
-      processEvent(event);
-    }
-
-    @Override
-    public void contentsChanged(@NotNull VirtualFileEvent event){
-      processEvent(event);
-    }
-
-    private void processEvent(@NotNull VirtualFileEvent event) {
-      if (isSyncSensitiveFile(event.getFile())) {
-        setDirty();
-      }
-    }
-
-    private void checkChildren(VirtualFile file) {
-      if (!(file instanceof NewVirtualFile)) {
-        return;
-      }
-      Collection<VirtualFile> children = ((NewVirtualFile) file).getCachedChildren();
-      for (VirtualFile child : children) {
-        if (isSyncSensitiveFile(child)) {
-          setDirty();
-          return;
-        }
-      }
-    }
-  }
-
-  /**
-   * Listens for changes to files which impact the sync process
-   * (BUILD files and project view files)
-   */
-  private static class FileFocusListener extends FileEditorManagerAdapter {
-    @Override
-    public void fileClosed(@NotNull FileEditorManager source, @NotNull VirtualFile file) {
-      processEvent(file);
-    }
-
-    @Override
-    public void selectionChanged(@NotNull FileEditorManagerEvent event) {
-      processEvent(event.getOldFile());
-    }
-
-    private void processEvent(@Nullable VirtualFile file) {
-      if (isSyncSensitiveFile(file)) {
-        FileDocumentManager manager = FileDocumentManager.getInstance();
-        Document doc = manager.getCachedDocument(file);
-        if (doc != null) {
-          manager.saveDocument(doc);
-        }
-      }
-    }
-  }
-
-  private static boolean isSyncSensitiveFile(@Nullable VirtualFile file) {
-    return file != null && (isBuildFile(file) || ProjectViewStorageManager.isProjectViewFile(file.getPath()));
-  }
-
-
-  private static boolean isBuildFile(VirtualFile file) {
-    return file.getName().equals("BUILD");
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/status/BlazeSyncStatusListener.java b/blaze-base/src/com/google/idea/blaze/base/sync/status/BlazeSyncStatusListener.java
deleted file mode 100644
index 382e022..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/status/BlazeSyncStatusListener.java
+++ /dev/null
@@ -1,49 +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.status;
-
-import com.google.idea.blaze.base.model.BlazeProjectData;
-import com.google.idea.blaze.base.projectview.ProjectViewSet;
-import com.google.idea.blaze.base.settings.BlazeImportSettings;
-import com.google.idea.blaze.base.sync.SyncListener;
-import com.intellij.openapi.project.Project;
-
-/**
- * Application-wide listener for blaze syncs. Notifies per-project status listener when
- * they start and finish.
- */
-public class BlazeSyncStatusListener implements SyncListener {
-
-  @Override
-  public void onSyncStart(Project project) {
-    BlazeSyncStatusImpl.getImpl(project).syncStarted();
-  }
-
-  @Override
-  public void afterSync(Project project,
-                        boolean successful) {
-    BlazeSyncStatusImpl.getImpl(project).syncEnded(successful);
-  }
-
-  @Override
-  public void onSyncComplete(
-    Project project,
-    BlazeImportSettings importSettings,
-    ProjectViewSet projectViewSet,
-    BlazeProjectData blazeProjectData) {
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/workspace/ArtifactLocationDecoder.java b/blaze-base/src/com/google/idea/blaze/base/sync/workspace/ArtifactLocationDecoder.java
deleted file mode 100644
index 19223da..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/workspace/ArtifactLocationDecoder.java
+++ /dev/null
@@ -1,102 +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.workspace;
-
-import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
-import com.google.repackaged.devtools.build.lib.ideinfo.androidstudio.AndroidStudioIdeInfo;
-import com.google.repackaged.devtools.build.lib.ideinfo.androidstudio.PackageManifestOuterClass;
-
-import javax.annotation.Nullable;
-import java.io.File;
-
-/**
- * Decodes android_studio_ide_info.proto ArtifactLocation file paths
- */
-public class ArtifactLocationDecoder {
-
-  private final BlazeRoots blazeRoots;
-  private final WorkspacePathResolver pathResolver;
-
-  public ArtifactLocationDecoder(BlazeRoots blazeRoots, WorkspacePathResolver pathResolver) {
-    this.blazeRoots = blazeRoots;
-    this.pathResolver = pathResolver;
-  }
-
-  /**
-   * Decodes the ArtifactLocation proto, locates the absolute artifact file path.
-   * Returns null if the file can't be found (presumably because it was removed
-   * since the blaze build)
-   */
-  @Nullable
-  public ArtifactLocation decode(AndroidStudioIdeInfo.ArtifactLocation loc) {
-    return decode(loc.getRootPath(),
-                  loc.getRootExecutionPathFragment(),
-                  loc.getRelativePath(),
-                  loc.getIsSource());
-  }
-
-  /**
-   * Decodes the ArtifactLocation proto, locates the absolute artifact file path.
-   * Returns null if the file can't be found (presumably because it was removed
-   * since the blaze build)
-   */
-  @Nullable
-  public ArtifactLocation decode(PackageManifestOuterClass.ArtifactLocation loc) {
-    return decode(loc.getRootPath(),
-                  loc.getRootExecutionPathFragment(),
-                  loc.getRelativePath(),
-                  loc.getIsSource());
-  }
-
-  @Nullable
-  private ArtifactLocation decode(
-    String rootPath,
-    String rootExecutionPathFragment,
-    String relativePath,
-    boolean isSource) {
-    File root;
-    if (isSource) {
-      root = pathResolver.findPackageRoot(relativePath);
-    } else {
-      if (rootExecutionPathFragment.isEmpty()) {
-        // old format -- derive execution path fragment from the root path.
-        // it's a backwards way of doing it -- but we want to test the new code,
-        // and this will soon be removed
-        rootExecutionPathFragment = deriveRootExecutionPathFragmentFromRoot(rootPath);
-      }
-      root = new File(blazeRoots.executionRoot, rootExecutionPathFragment);
-    }
-    if (root == null) {
-      return null;
-    }
-    return ArtifactLocation.builder()
-      .setRootPath(root.toString())
-      .setRootExecutionPathFragment(rootExecutionPathFragment)
-      .setRelativePath(relativePath)
-      .setIsSource(isSource)
-      .build();
-  }
-
-  @Deprecated
-  private String deriveRootExecutionPathFragmentFromRoot(String rootPath) {
-    String execRoot = blazeRoots.executionRoot.toString();
-    if (rootPath.startsWith(execRoot)) {
-      return rootPath.substring(execRoot.length());
-    }
-    return "";
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/workspace/BlazeRoots.java b/blaze-base/src/com/google/idea/blaze/base/sync/workspace/BlazeRoots.java
deleted file mode 100644
index 65b6145..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/workspace/BlazeRoots.java
+++ /dev/null
@@ -1,134 +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.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 javax.annotation.Nullable;
-import java.io.File;
-import java.io.Serializable;
-import java.util.List;
-
-/**
- * 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))
-          );
-        }
-      }
-    );
-  }
-
-  private static String getOrThrow(BuildSystem buildSystem, ImmutableMap<String, String> map, String key) {
-    String value = map.get(key);
-    if (value == null) {
-      throw new RuntimeException(String.format("Could not locate %s in %s info", key, buildSystem.getLowerCaseName()));
-    }
-    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);
-    FileAttributeProvider fileAttributeProvider = FileAttributeProvider.getInstance();
-    for (String path : paths) {
-      File packagePath = new File(path.replace("%workspace%", workspaceRoot));
-      if (fileAttributeProvider.exists(packagePath)) {
-        packagePaths.add(packagePath);
-      }
-    }
-    return packagePaths;
-  }
-
-  public final File executionRoot;
-  public final List<File> packagePaths;
-  public final ExecutionRootPath blazeBinExecutionRootPath;
-  public final ExecutionRootPath blazeGenfilesExecutionRootPath;
-
-  @VisibleForTesting
-  public BlazeRoots(
-    File executionRoot,
-    List<File> packagePaths,
-    ExecutionRootPath blazeBinExecutionRootPath,
-    ExecutionRootPath blazeGenfilesExecutionRootPath
-  ) {
-    this.executionRoot = executionRoot;
-    this.packagePaths = packagePaths;
-    this.blazeBinExecutionRootPath = blazeBinExecutionRootPath;
-    this.blazeGenfilesExecutionRootPath = blazeGenfilesExecutionRootPath;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/workspace/WorkingSet.java b/blaze-base/src/com/google/idea/blaze/base/sync/workspace/WorkingSet.java
deleted file mode 100644
index 39b5373..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/workspace/WorkingSet.java
+++ /dev/null
@@ -1,44 +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.workspace;
-
-import com.google.common.collect.ImmutableList;
-import com.google.idea.blaze.base.model.primitives.WorkspacePath;
-
-import java.io.Serializable;
-
-/**
- * Computes the working set of files of directories from source control.
- */
-public class WorkingSet implements Serializable {
-  private static final long serialVersionUID = 2L;
-
-  public final ImmutableList<WorkspacePath> addedFiles;
-  public final ImmutableList<WorkspacePath> modifiedFiles;
-  public final ImmutableList<WorkspacePath> deletedFiles;
-
-  public WorkingSet(ImmutableList<WorkspacePath> addedFiles,
-                    ImmutableList<WorkspacePath> modifiedFiles,
-                    ImmutableList<WorkspacePath> deletedFiles) {
-    this.addedFiles = addedFiles;
-    this.modifiedFiles = modifiedFiles;
-    this.deletedFiles = deletedFiles;
-  }
-
-  public boolean isEmpty() {
-    return addedFiles.isEmpty() && modifiedFiles.isEmpty() && deletedFiles.isEmpty();
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/workspace/WorkspacePathResolver.java b/blaze-base/src/com/google/idea/blaze/base/sync/workspace/WorkspacePathResolver.java
deleted file mode 100644
index acc5ea1..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/workspace/WorkspacePathResolver.java
+++ /dev/null
@@ -1,64 +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.workspace;
-
-import com.google.common.collect.ImmutableList;
-import com.google.idea.blaze.base.model.primitives.ExecutionRootPath;
-import com.google.idea.blaze.base.model.primitives.WorkspacePath;
-import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
-
-import javax.annotation.Nullable;
-import java.io.File;
-import java.io.Serializable;
-
-/**
- * Uses workspace root, blaze roots and git5 tracked directory information to
- * convert workspace-relative paths to absolute files with a minimum of file system calls (typically none).
- */
-public interface WorkspacePathResolver extends Serializable {
-  /**
-   * Resolves a workspace path to an absolute file.
-   */
-  @Nullable
-  default File resolveToFile(WorkspacePath workspacepath) {
-    return resolveToFile(workspacepath.relativePath());
-  }
-
-  /**
-   * Resolves a workspace relative path to an absolute file.
-   */
-  @Nullable
-  default File resolveToFile(String workspaceRelativePath) {
-    File packageRoot = findPackageRoot(workspaceRelativePath);
-    return packageRoot != null ? new File(packageRoot, workspaceRelativePath) : null;
-  }
-
-  /**
-   * This method should be used for directories. In the case that the directory is tracked, it returns the directory under the workspace
-   * root. If the directory is partially tracked (a sub directory is tracked), then the directory in the workspace and the directory under
-   * READONLY are returned in that order in a list. If the directory is untracked, the path is examined to see if this method should return
-   * a file under the execution root or a file under READONLY.
-   */
-  ImmutableList<File> resolveToIncludeDirectories(ExecutionRootPath executionRootPath);
-
-  /**
-   * Finds the package root directory that a workspace relative path is in.
-   */
-  @Nullable
-  File findPackageRoot(String relativePath);
-
-  WorkspaceRoot getWorkspaceRoot();
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/workspace/WorkspacePathResolverImpl.java b/blaze-base/src/com/google/idea/blaze/base/sync/workspace/WorkspacePathResolverImpl.java
deleted file mode 100644
index 119b3aa..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/workspace/WorkspacePathResolverImpl.java
+++ /dev/null
@@ -1,75 +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.workspace;
-
-import com.google.common.collect.ImmutableList;
-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 javax.annotation.Nullable;
-import java.io.File;
-import java.util.List;
-
-/**
- * Uses the package path locations to resolve a workspace path.
- */
-public class WorkspacePathResolverImpl implements WorkspacePathResolver {
-  private static final long serialVersionUID = 2L;
-
-  private final WorkspaceRoot workspaceRoot;
-  private final List<File> packagePaths;
-
-  public WorkspacePathResolverImpl(WorkspaceRoot workspaceRoot, BlazeRoots blazeRoots) {
-    this(workspaceRoot, blazeRoots.packagePaths);
-  }
-
-  public WorkspacePathResolverImpl(WorkspaceRoot workspaceRoot) {
-    this(workspaceRoot, ImmutableList.of(workspaceRoot.directory()));
-  }
-
-  public WorkspacePathResolverImpl(WorkspaceRoot workspaceRoot, List<File> packagePaths) {
-    this.workspaceRoot = workspaceRoot;
-    this.packagePaths = packagePaths;
-  }
-
-  @Override
-  public ImmutableList<File> resolveToIncludeDirectories(ExecutionRootPath executionRootPath) {
-    File trackedLocation = executionRootPath.getFileRootedAt(workspaceRoot.directory());
-    return ImmutableList.of(trackedLocation);
-  }
-
-  @Override
-  @Nullable
-  public File findPackageRoot(String relativePath) {
-    if (packagePaths.size() == 1) {
-      return packagePaths.get(0);
-    }
-    // fall back to manually checking each one
-    FileAttributeProvider existenceChecker = FileAttributeProvider.getInstance();
-    for (File pkg : packagePaths) {
-      if (existenceChecker.exists(new File(pkg, relativePath))) {
-        return pkg;
-      }
-    }
-    return null;
-  }
-
-  @Override
-  public WorkspaceRoot getWorkspaceRoot() {
-    return workspaceRoot;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/workspace/WorkspacePathResolverProvider.java b/blaze-base/src/com/google/idea/blaze/base/sync/workspace/WorkspacePathResolverProvider.java
deleted file mode 100644
index 1d9d08f..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/workspace/WorkspacePathResolverProvider.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.sync.workspace;
-
-import com.intellij.openapi.components.ServiceManager;
-import com.intellij.openapi.project.Project;
-
-import javax.annotation.Nullable;
-
-/**
- * Provides a WorkspacePathResolver.
- */
-public interface WorkspacePathResolverProvider {
-
-  static WorkspacePathResolverProvider getInstance(Project project) {
-    return ServiceManager.getService(project, WorkspacePathResolverProvider.class);
-  }
-
-  /**
-   * Returns a WorkspacePathResolver for this project, or null if it's not a blaze/bazel project.
-   */
-  @Nullable
-  WorkspacePathResolver getPathResolver();
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/sync/workspace/WorkspacePathResolverProviderImpl.java b/blaze-base/src/com/google/idea/blaze/base/sync/workspace/WorkspacePathResolverProviderImpl.java
deleted file mode 100644
index 6563ed8..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/sync/workspace/WorkspacePathResolverProviderImpl.java
+++ /dev/null
@@ -1,41 +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.workspace;
-
-import com.google.idea.blaze.base.model.BlazeProjectData;
-import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
-import com.intellij.openapi.project.Project;
-
-import javax.annotation.Nullable;
-
-/**
- * Provides a WorkspacePathResolver.
- */
-public class WorkspacePathResolverProviderImpl implements WorkspacePathResolverProvider {
-
-  private final Project project;
-
-  public WorkspacePathResolverProviderImpl(Project project) {
-    this.project = project;
-  }
-
-  @Nullable
-  @Override
-  public WorkspacePathResolver getPathResolver() {
-    BlazeProjectData projectData = BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
-    return projectData != null ? projectData.workspacePathResolver : null;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/trace/Trace.java b/blaze-base/src/com/google/idea/blaze/base/trace/Trace.java
deleted file mode 100644
index 07e4b0b..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/trace/Trace.java
+++ /dev/null
@@ -1,73 +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.trace;
-
-import com.google.idea.blaze.base.trickle.*;
-import com.google.idea.blaze.base.scope.BlazeContext;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Helper methods for tracing.
- */
-public class Trace {
-  public static <R> Function0<R> trace(@NotNull BlazeContext context, @NotNull String name, @NotNull Function0<R> func) {
-    return () -> {
-      try (TraceContext traceContext = new TraceContext(context, name)) {
-        return func.run();
-      }
-    };
-  }
-
-  public static <A, R> Function1<A, R> trace(@NotNull BlazeContext context, @NotNull String name, @NotNull Function1<A, R> func) {
-    return (a) -> {
-      try (TraceContext traceContext = new TraceContext(context, name)) {
-        return func.run(a);
-      }
-    };
-  }
-
-  public static <A, B, R> Function2<A, B, R> trace(@NotNull BlazeContext context, @NotNull String name, @NotNull Function2<A, B, R> func) {
-    return (a, b) -> {
-      try (TraceContext traceContext = new TraceContext(context, name)) {
-        return func.run(a, b);
-      }
-    };
-  }
-
-  public static <A, B, C, R> Function3<A, B, C, R> trace(@NotNull BlazeContext context, @NotNull String name, @NotNull Function3<A, B, C, R> func) {
-    return (a, b, c) -> {
-      try (TraceContext traceContext = new TraceContext(context, name)) {
-        return func.run(a, b, c);
-      }
-    };
-  }
-
-  public static <A, B, C, D, R> Function4<A, B, C, D, R> trace(@NotNull BlazeContext context, @NotNull String name, @NotNull Function4<A, B, C, D, R> func) {
-    return (a, b, c, d) -> {
-      try (TraceContext traceContext = new TraceContext(context, name)) {
-        return func.run(a, b, c, d);
-      }
-    };
-  }
-
-  public static <A, B, C, D, E, R> Function5<A, B, C, D, E, R> trace(@NotNull BlazeContext context, @NotNull String name, @NotNull Function5<A, B, C, D, E, R> func) {
-    return (a, b, c, d, e) -> {
-      try (TraceContext traceContext = new TraceContext(context, name)) {
-        return func.run(a, b, c, d, e);
-      }
-    };
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/trace/TraceContext.java b/blaze-base/src/com/google/idea/blaze/base/trace/TraceContext.java
deleted file mode 100644
index 473f871..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/trace/TraceContext.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.trace;
-
-import com.google.idea.blaze.base.scope.BlazeContext;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Trace context utility class that can be used with try-with-resource.
- */
-public class TraceContext implements AutoCloseable {
-  @NotNull BlazeContext context;
-  @NotNull String name;
-
-  public TraceContext(@NotNull BlazeContext context, @NotNull String name) {
-    this.context = context;
-    this.name = name;
-    context.output(new TraceEvent(name, TraceEvent.Type.Begin));
-  }
-
-  @Override
-  public void close() {
-    context.output(new TraceEvent(name, TraceEvent.Type.End));
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/trace/TraceEvent.java b/blaze-base/src/com/google/idea/blaze/base/trace/TraceEvent.java
deleted file mode 100644
index 08e3395..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/trace/TraceEvent.java
+++ /dev/null
@@ -1,41 +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.trace;
-
-import com.google.idea.blaze.base.scope.Output;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Trace event.
- */
-public class TraceEvent implements Output {
-  public final String name;
-  public final Type type;
-  public final long nanoTime;
-  public long threadId;
-
-  public enum Type {
-    Begin,
-    End,
-  }
-
-  public TraceEvent(@NotNull String name, @NotNull Type type) {
-    this.name = name;
-    this.type = type;
-    this.nanoTime = System.nanoTime();
-    this.threadId = Thread.currentThread().getId();
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/trace/TraceScope.java b/blaze-base/src/com/google/idea/blaze/base/trace/TraceScope.java
deleted file mode 100644
index 488f3dd..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/trace/TraceScope.java
+++ /dev/null
@@ -1,90 +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.trace;
-
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.google.idea.blaze.base.scope.BlazeScope;
-import com.google.idea.blaze.base.scope.OutputSink;
-import com.google.idea.blaze.base.scope.output.PrintOutput;
-import com.intellij.openapi.diagnostic.Logger;
-import org.jetbrains.annotations.NotNull;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.PrintWriter;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Tracks trace events and writes trace to ~/blaze-trace.json at end of scope.
- *
- * The results can be imported into Chrome using chrome://tracing.
- */
-public class TraceScope implements BlazeScope, OutputSink<TraceEvent> {
-  private static final Logger LOG = Logger.getInstance(TraceScope.class);
-  private final List<TraceEvent> traceEvents = Collections.synchronizedList(Lists.newArrayList());
-  private long traceStartNanos;
-
-  @Override
-  public void onScopeBegin(@NotNull BlazeContext context) {
-    traceStartNanos = System.nanoTime();
-    context.addOutputSink(TraceEvent.class, this);
-  }
-
-  @Override
-  public void onScopeEnd(@NotNull BlazeContext context) {
-    Collections.sort(traceEvents, (a, b) -> Long.compare(a.nanoTime, b.nanoTime));
-
-    String home = System.getProperty("user.home");
-    File file = new File(home, "blaze-trace.json");
-    try (PrintWriter printWriter = new PrintWriter(file)) {
-      printWriter.println("[");
-
-      for (int i = 0; i < traceEvents.size(); ++i) {
-        TraceEvent traceEvent = traceEvents.get(i);
-        long startTimeNanos = traceEvent.nanoTime - traceStartNanos;
-        long startTimeMicros = startTimeNanos / 1000;
-        printWriter.print(String.format(
-          "{\"name\": \"%s\", \"ts\": %d, \"ph\": \"%s\", \"pid\": %d}",
-          traceEvent.name,
-          startTimeMicros,
-          traceEvent.type == TraceEvent.Type.Begin ? "B" : "E",
-          traceEvent.threadId
-        ));
-
-        // No trailing commas in JSON :(
-        if (i != traceEvents.size() - 1) {
-          printWriter.append(',');
-        }
-        printWriter.append('\n');
-      }
-
-      printWriter.println("]");
-    }
-    catch (FileNotFoundException e) {
-      LOG.error(e);
-    }
-
-    context.output(new PrintOutput("Wrote trace output to: " + file));
-  }
-
-  @Override
-  public Propagation onOutput(@NotNull TraceEvent output) {
-    traceEvents.add(output);
-    return Propagation.Continue;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/treeview/BlazePsiDirectoryNode.java b/blaze-base/src/com/google/idea/blaze/base/treeview/BlazePsiDirectoryNode.java
deleted file mode 100644
index 5d94e82..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/treeview/BlazePsiDirectoryNode.java
+++ /dev/null
@@ -1,60 +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.treeview;
-
-import com.intellij.ide.projectView.PresentationData;
-import com.intellij.ide.projectView.ViewSettings;
-import com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode;
-import com.intellij.openapi.project.Project;
-import com.intellij.psi.PsiDirectory;
-import com.intellij.ui.SimpleTextAttributes;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * A PsiDirectoryNode that doesn't render module names or source roots.
- */
-public class BlazePsiDirectoryNode extends PsiDirectoryNode {
-  public BlazePsiDirectoryNode(@NotNull PsiDirectoryNode original) {
-    this(original.getProject(), original.getValue(), original.getSettings());
-  }
-
-  public BlazePsiDirectoryNode(Project project, PsiDirectory directory, ViewSettings settings) {
-    super(project, directory, settings);
-  }
-
-  @Override
-  protected boolean shouldShowModuleName() {
-    return false;
-  }
-
-  @Override
-  protected boolean shouldShowSourcesRoot() {
-    return false;
-  }
-
-  @Override
-  protected void updateImpl(PresentationData data) {
-    super.updateImpl(data);
-    PsiDirectory psiDirectory = getValue();
-    assert psiDirectory != null;
-    String text = psiDirectory.getName();
-
-    data.setPresentableText(text);
-    data.clearText();
-    data.addText(text, SimpleTextAttributes.REGULAR_ATTRIBUTES);
-    data.setLocationString("");
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/treeview/BlazePsiDirectoryRootNode.java b/blaze-base/src/com/google/idea/blaze/base/treeview/BlazePsiDirectoryRootNode.java
deleted file mode 100644
index 4a71968..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/treeview/BlazePsiDirectoryRootNode.java
+++ /dev/null
@@ -1,49 +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.treeview;
-
-import com.google.idea.blaze.base.model.primitives.WorkspacePath;
-import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
-import com.intellij.ide.projectView.PresentationData;
-import com.intellij.ide.projectView.ViewSettings;
-import com.intellij.openapi.project.Project;
-import com.intellij.psi.PsiDirectory;
-import com.intellij.ui.SimpleTextAttributes;
-
-/**
- * A PsiDirectoryNode that represents a directory root, rendering the
- * whole directory name from the workspace root.
- */
-public class BlazePsiDirectoryRootNode extends BlazePsiDirectoryNode {
-  public BlazePsiDirectoryRootNode(Project project, PsiDirectory directory, ViewSettings settings) {
-    super(project, directory, settings);
-  }
-
-  @Override
-  protected void updateImpl(PresentationData data) {
-    super.updateImpl(data);
-    WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProject(getProject());
-    PsiDirectory psiDirectory = getValue();
-    assert psiDirectory != null;
-    WorkspacePath workspacePath = workspaceRoot.workspacePathFor(psiDirectory.getVirtualFile());
-    String text = workspacePath.relativePath();
-
-    data.setPresentableText(text);
-    data.clearText();
-    data.addText(text, SimpleTextAttributes.REGULAR_ATTRIBUTES);
-    data.setLocationString("");
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/treeview/BlazeTreeStructureProvider.java b/blaze-base/src/com/google/idea/blaze/base/treeview/BlazeTreeStructureProvider.java
deleted file mode 100644
index 85f8aad..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/treeview/BlazeTreeStructureProvider.java
+++ /dev/null
@@ -1,154 +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.treeview;
-
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
-import com.google.idea.blaze.base.settings.Blaze;
-import com.google.idea.blaze.base.settings.BlazeImportSettings;
-import com.google.idea.blaze.base.settings.BlazeImportSettingsManager;
-import com.intellij.ide.projectView.ProjectViewSettings;
-import com.intellij.ide.projectView.TreeStructureProvider;
-import com.intellij.ide.projectView.ViewSettings;
-import com.intellij.ide.projectView.impl.nodes.ExternalLibrariesNode;
-import com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode;
-import com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode;
-import com.intellij.ide.util.treeView.AbstractTreeNode;
-import com.intellij.openapi.project.DumbAware;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.vfs.LocalFileSystem;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.psi.PsiDirectory;
-import com.intellij.psi.PsiManager;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * Modifies the project view:
- *
- * - Replaces the root with a single workspace root
- * - Removes rendering of module names and source roots
- */
-public class BlazeTreeStructureProvider implements TreeStructureProvider, DumbAware {
-  @NotNull
-  @Override
-  public Collection<AbstractTreeNode> modify(@NotNull AbstractTreeNode parent,
-                                             @NotNull Collection<AbstractTreeNode> children,
-                                             ViewSettings settings) {
-    Project project = parent.getProject();
-    if (project == null || !Blaze.isBlazeProject(project)) {
-      return children;
-    }
-
-    if (parent instanceof ProjectViewProjectNode) {
-      WorkspaceRootNode rootNode = createRootNode(project, settings);
-      if (rootNode == null) {
-        return children;
-      }
-      
-      Collection<AbstractTreeNode> result = Lists.newArrayList();
-      result.add(rootNode);
-      for (AbstractTreeNode treeNode : children) {
-        if (treeNode instanceof ExternalLibrariesNode) {
-          result.add(treeNode);
-        }
-      }
-      return result;
-    }
-    else {
-      List<AbstractTreeNode> result = Lists.newArrayList();
-      for (AbstractTreeNode treeNode : children) {
-        if (treeNode.getClass().equals(PsiDirectoryNode.class)) {
-          result.add(new BlazePsiDirectoryNode((PsiDirectoryNode)treeNode));
-        } else {
-          result.add(treeNode);
-        }
-      }
-      return result;
-    }
-  }
-
-  @Nullable
-  private WorkspaceRootNode createRootNode(@NotNull Project project, @NotNull ViewSettings settings) {
-    BlazeImportSettings importSettings = BlazeImportSettingsManager.getInstance(project)
-      .getImportSettings();
-    if (importSettings != null) {
-      WorkspaceRoot workspaceRoot = WorkspaceRoot.fromImportSettings(importSettings);
-      File fdir = workspaceRoot.directory();
-      VirtualFile vdir = LocalFileSystem.getInstance().findFileByIoFile(fdir);
-      if (vdir != null) {
-        final PsiManager psiManager = PsiManager.getInstance(project);
-        PsiDirectory directory = psiManager.findDirectory(vdir);
-        return new WorkspaceRootNode(project, workspaceRoot, directory, wrapViewSettings(settings));
-      }
-    }
-    return null;
-  }
-
-  @Nullable
-  @Override
-  public Object getData(Collection<AbstractTreeNode> selected, String dataName) {
-    return null;
-  }
-
-  private ViewSettings wrapViewSettings(@NotNull final ViewSettings original) {
-    return new ProjectViewSettings() {
-      @Override
-      public boolean isShowMembers() {
-        return original.isShowMembers();
-      }
-
-      @Override
-      public boolean isStructureView() {
-        return original.isStructureView();
-      }
-
-      @Override
-      public boolean isShowModules() {
-        return original.isShowModules();
-      }
-
-      @Override
-      public boolean isFlattenPackages() {
-        return false;
-      }
-
-      @Override
-      public boolean isAbbreviatePackageNames() {
-        return original.isAbbreviatePackageNames();
-      }
-
-      @Override
-      public boolean isHideEmptyMiddlePackages() {
-        return false;
-      }
-
-      @Override
-      public boolean isShowLibraryContents() {
-        return original.isShowLibraryContents();
-      }
-
-      @Override
-      public boolean isShowExcludedFiles() {
-        return true;
-      }
-    };
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/treeview/WorkspaceRootNode.java b/blaze-base/src/com/google/idea/blaze/base/treeview/WorkspaceRootNode.java
deleted file mode 100644
index e8ee00f..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/treeview/WorkspaceRootNode.java
+++ /dev/null
@@ -1,113 +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.treeview;
-
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.experiments.BoolExperiment;
-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.ProjectViewManager;
-import com.google.idea.blaze.base.projectview.ProjectViewSet;
-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.intellij.ide.projectView.PresentationData;
-import com.intellij.ide.projectView.ViewSettings;
-import com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode;
-import com.intellij.ide.util.treeView.AbstractTreeNode;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.vfs.VfsUtil;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.psi.PsiDirectory;
-import com.intellij.psi.PsiManager;
-import com.intellij.ui.SimpleTextAttributes;
-
-import java.util.Collection;
-import java.util.List;
-
-/**
- * Workspace root node.
- * <p/>
- * <p>Customizes rendering of the workspace root node to cut out
- * the full absolute path of the 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(Project project,
-                           WorkspaceRoot workspaceRoot,
-                           PsiDirectory value,
-                           ViewSettings viewSettings) {
-    super(project, value, viewSettings);
-    this.workspaceRoot = workspaceRoot;
-  }
-
-  @Override
-  public Collection<AbstractTreeNode> getChildrenImpl() {
-    if (!COLLAPSE_PROJECT_VIEW.getValue()) {
-      return super.getChildrenImpl();
-    }
-    if (!BlazeUserSettings.getInstance().getCollapseProjectView()) {
-      return super.getChildrenImpl();
-    }
-    Project project = getProject();
-    if (project == null) {
-      return super.getChildrenImpl();
-    }
-    List<AbstractTreeNode> children = Lists.newArrayList();
-    ProjectViewSet projectViewSet = ProjectViewManager.getInstance(project).getProjectViewSet();
-    if (projectViewSet == null) {
-      return super.getChildrenImpl();
-    }
-
-    WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProject(project);
-    ImportRoots importRoots = ImportRoots.builder(workspaceRoot, Blaze.getBuildSystem(project)).add(projectViewSet).build();
-    if (importRoots.rootDirectories().stream().anyMatch(WorkspacePath::isWorkspaceRoot)) {
-      return super.getChildrenImpl();
-    }
-    for (WorkspacePath workspacePath : importRoots.rootDirectories()) {
-      VirtualFile virtualFile = VfsUtil.findFileByIoFile(workspaceRoot.fileForPath(workspacePath), false);
-      if (virtualFile == null) {
-        continue;
-      }
-      PsiDirectory psiDirectory = PsiManager.getInstance(project).findDirectory(virtualFile);
-      if (psiDirectory == null) {
-        continue;
-      }
-      children.add(new BlazePsiDirectoryRootNode(project, psiDirectory, getSettings()));
-    }
-    if (children.isEmpty()) {
-      return super.getChildrenImpl();
-    }
-    return children;
-  }
-
-  @Override
-  protected void updateImpl(PresentationData data) {
-    super.updateImpl(data);
-    PsiDirectory psiDirectory = getValue();
-    assert psiDirectory != null;
-    String text = psiDirectory.getName();
-
-    data.setPresentableText(text);
-    data.clearText();
-    data.addText(text, SimpleTextAttributes.REGULAR_ATTRIBUTES);
-    data.setLocationString("");
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/trickle/Function0.java b/blaze-base/src/com/google/idea/blaze/base/trickle/Function0.java
deleted file mode 100644
index a45bc3d..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/trickle/Function0.java
+++ /dev/null
@@ -1,24 +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.trickle;
-
-/**
- * Function of 0 parameters.
- */
-public interface Function0<R> {
-  R run();
-}
-
diff --git a/blaze-base/src/com/google/idea/blaze/base/trickle/Function1.java b/blaze-base/src/com/google/idea/blaze/base/trickle/Function1.java
deleted file mode 100644
index 67893fd..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/trickle/Function1.java
+++ /dev/null
@@ -1,26 +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.trickle;
-
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Function of 1 parameters.
- */
-public interface Function1<A, R> {
-  R run(@NotNull A a);
-}
-
diff --git a/blaze-base/src/com/google/idea/blaze/base/trickle/Function2.java b/blaze-base/src/com/google/idea/blaze/base/trickle/Function2.java
deleted file mode 100644
index bc704a1..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/trickle/Function2.java
+++ /dev/null
@@ -1,26 +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.trickle;
-
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Function of 2 parameters.
- */
-public interface Function2<A, B, R> {
-  R run(@NotNull A a, @NotNull B b);
-}
-
diff --git a/blaze-base/src/com/google/idea/blaze/base/trickle/Function3.java b/blaze-base/src/com/google/idea/blaze/base/trickle/Function3.java
deleted file mode 100644
index faaecd6..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/trickle/Function3.java
+++ /dev/null
@@ -1,26 +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.trickle;
-
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Function of 3 parameters.
- */
-public interface Function3<A, B, C, R> {
-  R run(@NotNull A a, @NotNull B b, @NotNull C c);
-}
-
diff --git a/blaze-base/src/com/google/idea/blaze/base/trickle/Function4.java b/blaze-base/src/com/google/idea/blaze/base/trickle/Function4.java
deleted file mode 100644
index e75b134..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/trickle/Function4.java
+++ /dev/null
@@ -1,26 +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.trickle;
-
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Function of 4 parameters.
- */
-public interface Function4<A, B, C, D, R> {
-  R run(@NotNull A a, @NotNull B b, @NotNull C c, @NotNull D d);
-}
-
diff --git a/blaze-base/src/com/google/idea/blaze/base/trickle/Function5.java b/blaze-base/src/com/google/idea/blaze/base/trickle/Function5.java
deleted file mode 100644
index 1573832..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/trickle/Function5.java
+++ /dev/null
@@ -1,26 +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.trickle;
-
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Function of 5 parameters.
- */
-public interface Function5<A, B, C, D, E, R> {
-  R run(@NotNull A a, @NotNull B b, @NotNull C c, @NotNull D d, @NotNull E e);
-}
-
diff --git a/blaze-base/src/com/google/idea/blaze/base/trickle/Tricklex.java b/blaze-base/src/com/google/idea/blaze/base/trickle/Tricklex.java
deleted file mode 100644
index df7dbf9..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/trickle/Tricklex.java
+++ /dev/null
@@ -1,73 +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.trickle;
-
-import com.google.idea.blaze.base.async.executor.BlazeExecutor;
-import com.spotify.trickle.*;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Ties Trickle to an executor to cut down on boilerplate.
- */
-public class Tricklex {
-  public static <R> ConfigurableGraph<R> call(@NotNull Function0<R> func) {
-    return Trickle.call(func0(func));
-  }
-
-  public static <A, R> Trickle.NeedsParameters1<A, R> call(@NotNull Function1<A, R> func) {
-    return Trickle.call(func1(func));
-  }
-
-  public static <A, B, R> Trickle.NeedsParameters2<A, B, R> call(@NotNull Function2<A, B, R> func) {
-    return Trickle.call(func2(func));
-  }
-
-  public static <A, B, C, R> Trickle.NeedsParameters3<A, B, C, R> call(@NotNull Function3<A, B, C, R> func) {
-    return Trickle.call(func3(func));
-  }
-
-  public static <A, B, C, D, R> Trickle.NeedsParameters4<A, B, C, D, R> call(@NotNull Function4<A, B, C, D, R> func) {
-    return Trickle.call(func4(func));
-  }
-
-  public static <A, B, C, D, E, R> Trickle.NeedsParameters5<A, B, C, D, E, R> call(@NotNull Function5<A, B, C, D, E, R> func) {
-    return Trickle.call(func5(func));
-  }
-
-  private static <R> Func0<R> func0(@NotNull final Function0<R> func) {
-    return () -> BlazeExecutor.getInstance().submit(func::run);
-  }
-
-  private static <A, R> Func1<A, R> func1(@NotNull final Function1<A, R> func) {
-    return a -> BlazeExecutor.getInstance().submit(() -> func.run(a));
-  }
-
-  private static <A, B, R> Func2<A, B, R> func2(@NotNull final Function2<A, B, R> func) {
-    return (a, b) -> BlazeExecutor.getInstance().submit(() -> func.run(a, b));
-  }
-
-  private static <A, B, C, R> Func3<A, B, C, R> func3(@NotNull final Function3<A, B, C, R> func) {
-    return (a, b, c) -> BlazeExecutor.getInstance().submit(() -> func.run(a, b, c));
-  }
-
-  private static <A, B, C, D, R> Func4<A, B, C, D, R> func4(@NotNull final Function4<A, B, C, D, R> func) {
-    return (a, b, c, d) -> BlazeExecutor.getInstance().submit(() -> func.run(a, b, c, d));
-  }
-
-  private static <A, B, C, D, E, R> Func5<A, B, C, D, E, R> func5(@NotNull final Function5<A, B, C, D, E, R> func) {
-    return (a, b, c, d, e) -> BlazeExecutor.getInstance().submit(() -> func.run(a, b, c, d, e));
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/ui/BlazeProblemsView.java b/blaze-base/src/com/google/idea/blaze/base/ui/BlazeProblemsView.java
deleted file mode 100644
index 0c65b61..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/ui/BlazeProblemsView.java
+++ /dev/null
@@ -1,33 +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.ui;
-
-import com.google.idea.blaze.base.scope.output.IssueOutput;
-import com.intellij.openapi.components.ServiceManager;
-import com.intellij.openapi.project.Project;
-
-import javax.annotation.Nullable;
-import java.util.UUID;
-
-public interface BlazeProblemsView {
-  @Nullable
-  static BlazeProblemsView getInstance(Project project) {
-    return ServiceManager.getService(project, BlazeProblemsView.class);
-  }
-
-  void clearOldMessages(UUID sessionId);
-  void addMessage(IssueOutput issue, UUID sessionId);
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/ui/BlazeValidationError.java b/blaze-base/src/com/google/idea/blaze/base/ui/BlazeValidationError.java
deleted file mode 100644
index bde6a7c..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/ui/BlazeValidationError.java
+++ /dev/null
@@ -1,66 +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.ui;
-
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.ui.Messages;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import javax.annotation.concurrent.Immutable;
-import java.util.Collection;
-
-@Immutable
-public final class BlazeValidationError {
-
-  @NotNull
-  private final String error;
-
-  public BlazeValidationError(@NotNull String validationFailure) {
-    this.error = validationFailure;
-  }
-
-  @NotNull
-  public String getError() {
-    return error;
-  }
-
-  public static void collect(@Nullable Collection<BlazeValidationError> errors, @NotNull BlazeValidationError error) {
-    if (errors != null) {
-      errors.add(error);
-    }
-  }
-
-  public static void throwError(@NotNull Collection<BlazeValidationError> errors) throws IllegalArgumentException {
-    BlazeValidationError error = !errors.isEmpty() ? errors.iterator().next() : null;
-    String errorMessage = error != null ? error.getError() : "Unknown validation error";
-    throw new IllegalArgumentException(errorMessage);
-  }
-
-  /**
-   * Shows an error dialog.
-   *
-   * @return true if there are no errors
-   */
-  public static boolean verify(@NotNull Project project, @NotNull String title, @NotNull Collection<BlazeValidationError> errors) {
-    if (!errors.isEmpty()) {
-      BlazeValidationError error = errors.iterator().next();
-      Messages.showErrorDialog(project, error.getError(), title);
-      return false;
-    }
-    return true;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/ui/BlazeValidationResult.java b/blaze-base/src/com/google/idea/blaze/base/ui/BlazeValidationResult.java
deleted file mode 100644
index 8965526..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/ui/BlazeValidationResult.java
+++ /dev/null
@@ -1,45 +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.ui;
-
-import org.jetbrains.annotations.Nullable;
-
-/**
- * Pair of (success, validation error)
- */
-public class BlazeValidationResult {
-  public final boolean success;
-  @Nullable public final BlazeValidationError error;
-
-  private static final BlazeValidationResult SUCCESS = new BlazeValidationResult(true, null);
-
-  private BlazeValidationResult(boolean success, @Nullable BlazeValidationError error) {
-    this.success = success;
-    this.error = error;
-  }
-
-  public static BlazeValidationResult success() {
-    return SUCCESS;
-  }
-
-  public static BlazeValidationResult failure(BlazeValidationError error) {
-    return new BlazeValidationResult(false, error);
-  }
-
-  public static BlazeValidationResult failure(String error) {
-    return failure(new BlazeValidationError(error));
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/ui/ComboWrapper.java b/blaze-base/src/com/google/idea/blaze/base/ui/ComboWrapper.java
deleted file mode 100644
index 363660b..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/ui/ComboWrapper.java
+++ /dev/null
@@ -1,66 +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.ui;
-
-import com.intellij.openapi.ui.ComboBox;
-import com.intellij.ui.ListCellRendererWrapper;
-import org.jetbrains.annotations.NotNull;
-
-import javax.swing.*;
-import java.awt.event.ActionListener;
-import java.util.Collection;
-
-/**
- * A simple wrapper for IDEA's {@link ComboBox} class adding type safety for the methods we commonly
- * use.
- */
-public final class ComboWrapper<T> {
-  @NotNull
-  private final ComboBox combo;
-
-  public static <T> ComboWrapper<T> create() {
-    return new ComboWrapper<T>();
-  }
-
-  private ComboWrapper() {
-    combo = new ComboBox();
-  }
-
-  public void setItems(@NotNull Collection<T> values) {
-    combo.setModel(new DefaultComboBoxModel(values.toArray()));
-  }
-
-  public void setSelectedItem(T value) {
-    combo.setSelectedItem(value);
-  }
-
-  public T getSelectedItem() {
-    return (T)combo.getSelectedItem();
-  }
-
-  public void addActionListener(@NotNull ActionListener listener) {
-    combo.addActionListener(listener);
-  }
-
-  public void setRenderer(ListCellRendererWrapper<T> renderer) {
-    combo.setRenderer(renderer);
-  }
-
-  @NotNull
-  public ComboBox getCombo() {
-    return combo;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/ui/FileSelectorWithStoredHistory.java b/blaze-base/src/com/google/idea/blaze/base/ui/FileSelectorWithStoredHistory.java
deleted file mode 100644
index 5a1ac95..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/ui/FileSelectorWithStoredHistory.java
+++ /dev/null
@@ -1,70 +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.ui;
-
-import com.intellij.ide.util.BrowseFilesListener;
-import com.intellij.openapi.ui.ComponentWithBrowseButton;
-import com.intellij.openapi.ui.TextComponentAccessor;
-import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.ui.TextFieldWithStoredHistory;
-
-import javax.annotation.Nullable;
-
-/**
- * A file selector panel with text field, browse button and stored history.
- */
-public class FileSelectorWithStoredHistory extends ComponentWithBrowseButton<TextFieldWithStoredHistory> {
-
-  public static FileSelectorWithStoredHistory create(String historyKey, String title) {
-    TextFieldWithStoredHistory textField = new TextFieldWithStoredHistory(historyKey);
-    return new FileSelectorWithStoredHistory(textField, title);
-  }
-
-  private FileSelectorWithStoredHistory(TextFieldWithStoredHistory textField, String title) {
-    super(textField, null);
-
-    addBrowseFolderListener(
-      title,
-      "",
-      null,
-      BrowseFilesListener.SINGLE_FILE_DESCRIPTOR,
-      TextComponentAccessor.TEXT_FIELD_WITH_STORED_HISTORY_WHOLE_TEXT);
-  }
-
-  /**
-   * Set the text without altering the history.
-   */
-  public void setText(@Nullable String text) {
-    if (text == null) {
-      getChildComponent().reset();
-    } else {
-      getChildComponent().setText(text);
-    }
-  }
-
-  public void setTextWithHistory(@Nullable String text) {
-    setText(text);
-    if (text != null) {
-      getChildComponent().addCurrentTextToHistory();
-    }
-  }
-
-  @Nullable
-  public String getText() {
-    String text = getChildComponent().getText();
-    return StringUtil.nullize(text);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/ui/IntegerTextField.java b/blaze-base/src/com/google/idea/blaze/base/ui/IntegerTextField.java
deleted file mode 100644
index 76f5245..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/ui/IntegerTextField.java
+++ /dev/null
@@ -1,96 +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.ui;
-
-import javax.swing.*;
-import java.text.FieldPosition;
-import java.text.NumberFormat;
-import java.text.ParseException;
-import java.text.ParsePosition;
-
-/**
- * Naive extension of JTextField, accepting integers or null.
- */
-public class IntegerTextField extends JFormattedTextField {
-
-  private static final NumberFormat integerFormatter = new NullableNumberFormat(NumberFormat.getIntegerInstance());
-
-  private static class NullableNumberFormat extends NumberFormat {
-
-    private final NumberFormat base;
-
-    private NullableNumberFormat(NumberFormat base) {
-      this.base = base;
-    }
-
-    @Override
-    public Object parseObject(String source) throws ParseException {
-      if (source == null || source.trim().isEmpty()) {
-        return null;
-      }
-      return super.parseObject(source);
-    }
-
-    @Override
-    public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos) {
-      return base.format(number, toAppendTo, pos);
-    }
-
-    @Override
-    public StringBuffer format(long number, StringBuffer toAppendTo, FieldPosition pos) {
-      return base.format(number, toAppendTo, pos);
-    }
-
-    @Override
-    public Number parse(String source, ParsePosition parsePosition) {
-      return base.parse(source, parsePosition);
-    }
-  }
-
-  private int minValue = Integer.MIN_VALUE;
-  private int maxValue = Integer.MAX_VALUE;
-
-  public IntegerTextField() {
-    super(integerFormatter);
-  }
-
-  @Override
-  public void setValue(Object value) {
-    if (value == null) {
-      super.setValue(value);
-      return;
-    }
-    Integer integer;
-    try {
-      integer = Integer.parseInt(getFormatter().valueToString(value));
-
-    } catch (ParseException | NumberFormatException e) {
-      return; // retain existing value if invalid
-    }
-    super.setValue(integer < minValue ? minValue : integer > maxValue ? maxValue : integer);
-  }
-
-  public IntegerTextField setMinValue(int minValue) {
-    this.minValue = minValue;
-    return this;
-  }
-
-  public IntegerTextField setMaxValue(int maxValue) {
-    this.maxValue = maxValue;
-    return this;
-  }
-
-}
\ No newline at end of file
diff --git a/blaze-base/src/com/google/idea/blaze/base/ui/UiUtil.java b/blaze-base/src/com/google/idea/blaze/base/ui/UiUtil.java
deleted file mode 100644
index 05530bd..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/ui/UiUtil.java
+++ /dev/null
@@ -1,83 +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.ui;
-
-import com.google.common.collect.Lists;
-import com.intellij.util.ui.GridBag;
-import org.jetbrains.annotations.NotNull;
-
-import javax.swing.*;
-import java.awt.*;
-
-/**
- * A collection of UI utility methods.
- */
-public final class UiUtil {
-
-  public static final int INSETS = 7;
-
-  private UiUtil() {
-  }
-
-  public static Box createBox(@NotNull Component... components) {
-    return createBox(Lists.newArrayList(components));
-  }
-
-  /**
-   * Puts all the given components in order in a box, aligned left.
-   */
-  public static Box createBox(@NotNull Iterable<Component> components) {
-    Box box = Box.createVerticalBox();
-    box.setAlignmentX(0);
-    for (Component component : components) {
-      if (component instanceof JComponent) {
-        ((JComponent)component).setAlignmentX(0);
-      }
-      box.add(component);
-    }
-    return box;
-  }
-
-  @NotNull
-  public static GridBag getLabelConstraints(int indentLevel) {
-    Insets insets = new Insets(INSETS, INSETS + INSETS * indentLevel, 0, INSETS);
-    return new GridBag().anchor(GridBagConstraints.WEST).weightx(0).insets(insets);
-  }
-
-  @NotNull
-  public static GridBag getFillLineConstraints(int indentLevel) {
-    Insets insets = new Insets(INSETS, INSETS + INSETS * indentLevel, 0, INSETS);
-    return new GridBag().weightx(1).coverLine().fillCellHorizontally().anchor(GridBagConstraints.WEST).insets(insets);
-  }
-
-  public static void fillBottom(@NotNull JComponent component) {
-    component.add(Box.createVerticalGlue(), new GridBag().weightx(1).weighty(1).fillCell().coverLine());
-  }
-
-  public static void setEnabledRecursive(Component component, boolean enabled) {
-    component.setEnabled(enabled);
-    if (component instanceof Container) {
-      for (Component child : ((Container) component).getComponents()) {
-        setEnabledRecursive(child, enabled);
-      }
-    }
-  }
-
-  public static void setPreferredWidth(JComponent component, int width) {
-    int height = component.getPreferredSize().height;
-    component.setPreferredSize(new Dimension(width, height));
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/util/BlazeHelperBinaryUtil.java b/blaze-base/src/com/google/idea/blaze/base/util/BlazeHelperBinaryUtil.java
deleted file mode 100644
index f9028bd..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/util/BlazeHelperBinaryUtil.java
+++ /dev/null
@@ -1,69 +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.util;
-
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.util.io.URLUtil;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.nio.file.Files;
-import java.nio.file.StandardCopyOption;
-import java.util.HashMap;
-import java.util.Map;
-
-public final class BlazeHelperBinaryUtil {
-
-  private static final Logger LOG = Logger.getInstance(BlazeHelperBinaryUtil.class);
-
-  private static final File tempDirectory = com.google.common.io.Files.createTempDir();
-  private static final Map<String, File> cachedFiles = new HashMap<>();
-
-  @Nullable
-  public static synchronized File getBlazeHelperBinary(@NotNull String binaryName) {
-    File file = cachedFiles.get(binaryName);
-    if (file != null) {
-      return file;
-    }
-    file = new File(tempDirectory, binaryName);
-    File directory = file.getParentFile();
-
-    if (!directory.mkdirs()) {
-      LOG.error("Could not create temporary dir: " + directory);
-      return null;
-    }
-
-    URL url = BlazeHelperBinaryUtil.class.getResource(binaryName);
-    if (url == null) {
-      LOG.error(String.format("Blaze binary '%s' was not found", binaryName));
-      return null;
-    }
-    try (InputStream inputStream = URLUtil.openResourceStream(url)) {
-      Files.copy(inputStream, file.toPath(), StandardCopyOption.REPLACE_EXISTING);
-      file.setExecutable(true);
-      cachedFiles.put(binaryName, file);
-      return file;
-    } catch (IOException e) {
-      LOG.error(String.format("Error loading blaze binary '%s'", binaryName));
-      return null;
-    }
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/util/PackagePrefixCalculator.java b/blaze-base/src/com/google/idea/blaze/base/util/PackagePrefixCalculator.java
deleted file mode 100644
index 5ca01c7..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/util/PackagePrefixCalculator.java
+++ /dev/null
@@ -1,41 +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.util;
-
-import com.google.idea.blaze.base.model.primitives.WorkspacePath;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Calculates package prefix from workspace paths.
- */
-public final class PackagePrefixCalculator {
-
-  public static String packagePrefixOf(@NotNull WorkspacePath workspacePath) {
-    int skipIndex = 0;
-
-    skipIndex = skipIndex == 0 ? skip(workspacePath, "java/") : skipIndex;
-    skipIndex = skipIndex == 0 ? skip(workspacePath, "javatests/") : skipIndex;
-
-    return workspacePath.relativePath().substring(skipIndex).replace('/', '.');
-  }
-
-  private static int skip(@NotNull WorkspacePath workspacePath, @NotNull String skipString) {
-    if (workspacePath.relativePath().startsWith(skipString)) {
-      return skipString.length();
-    }
-    return 0;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/util/SaveUtil.java b/blaze-base/src/com/google/idea/blaze/base/util/SaveUtil.java
deleted file mode 100644
index b036308..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/util/SaveUtil.java
+++ /dev/null
@@ -1,33 +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.util;
-
-import com.intellij.openapi.fileEditor.FileDocumentManager;
-import com.intellij.util.ui.UIUtil;
-
-/**
- * Utility for saving all files.
- */
-public class SaveUtil {
-  public static void saveAllFiles() {
-    UIUtil.invokeAndWaitIfNeeded(new Runnable() {
-      @Override
-      public void run() {
-        FileDocumentManager.getInstance().saveAllDocuments();
-      }
-    });
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/util/SerializationUtil.java b/blaze-base/src/com/google/idea/blaze/base/util/SerializationUtil.java
deleted file mode 100644
index 4e64cfe..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/util/SerializationUtil.java
+++ /dev/null
@@ -1,102 +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.util;
-
-import com.google.common.io.Closeables;
-import com.intellij.CommonBundle;
-import com.intellij.openapi.diagnostic.Logger;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.io.*;
-
-/**
- * Utils for serialization.
- */
-public class SerializationUtil {
-  private static final Logger LOG = Logger.getInstance(SerializationUtil.class.getName());
-
-  public static void saveToDisk(@NotNull File file, @NotNull Serializable serializable) throws IOException {
-    ensureExists(file.getParentFile());
-    FileOutputStream fos = null;
-    try {
-      fos = new FileOutputStream(file);
-      ObjectOutputStream oos = new ObjectOutputStream(fos);
-      try {
-        oos.writeObject(serializable);
-      }
-      finally {
-        Closeables.close(oos, false);
-      }
-    }
-    finally {
-      Closeables.close(fos, false);
-    }
-  }
-
-  @Nullable
-  public static Object loadFromDisk(
-    @NotNull File file,
-    @NotNull final Iterable<ClassLoader> classLoaders) throws IOException {
-    try {
-      FileInputStream fin = null;
-      try {
-        if (!file.exists()) {
-          return null;
-        }
-        fin = new FileInputStream(file);
-        ObjectInputStream ois = new ObjectInputStream(fin) {
-          @Override
-          protected Class<?> resolveClass(ObjectStreamClass desc)
-            throws IOException, ClassNotFoundException {
-            String name = desc.getName();
-            for (ClassLoader loader : classLoaders) {
-              try {
-                return Class.forName(name, false, loader);
-              }
-              catch (ClassNotFoundException e) {
-                // Ignore - will throw eventually in super
-              }
-            }
-            return super.resolveClass(desc);
-          }
-        };
-        try {
-          return (Object) ois.readObject();
-        }
-        finally {
-          Closeables.close(ois, false);
-        }
-      }
-      finally {
-        Closeables.close(fin, false);
-      }
-    }
-    catch (ClassNotFoundException e) {
-      throw new IOException(e);
-    }
-    catch (ClassCastException e) {
-      throw new IOException(e);
-    }
-  }
-
-  private static void ensureExists(@NotNull File dir) throws IOException {
-    if (!dir.exists() && !dir.mkdirs()) {
-      throw new IOException(
-        CommonBundle.message("exception.directory.can.not.create", dir.getPath()));
-    }
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/vcs/BlazeDefaultVcsRootPolicy.java b/blaze-base/src/com/google/idea/blaze/base/vcs/BlazeDefaultVcsRootPolicy.java
deleted file mode 100644
index 527321c..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/vcs/BlazeDefaultVcsRootPolicy.java
+++ /dev/null
@@ -1,118 +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.vcs;
-
-import com.google.common.collect.Lists;
-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.projectview.section.sections.DirectoryEntry;
-import com.google.idea.blaze.base.projectview.section.sections.DirectorySection;
-import com.google.idea.blaze.base.settings.BlazeImportSettings;
-import com.google.idea.blaze.base.settings.BlazeImportSettingsManager;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.vcs.impl.DefaultVcsRootPolicy;
-import com.intellij.openapi.vcs.impl.projectlevelman.NewMappings;
-import com.intellij.openapi.vfs.LocalFileSystem;
-import com.intellij.openapi.vfs.VfsUtilCore;
-import com.intellij.openapi.vfs.VirtualFile;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * Converts project-level mappings to actual VCS roots.
- */
-public class BlazeDefaultVcsRootPolicy extends DefaultVcsRootPolicy {
-
-  @NotNull
-  private final Project project;
-
-  public BlazeDefaultVcsRootPolicy(@NotNull Project project) {
-    this.project = project;
-  }
-
-  @Override
-  public void addDefaultVcsRoots(
-    NewMappings mappingList,
-    @NotNull String vcsName,
-    List<VirtualFile> result) {
-    result.addAll(getVcsRoots());
-  }
-
-  @NotNull
-  @Override
-  public Collection<VirtualFile> getDirtyRoots() {
-    return getVcsRoots();
-  }
-
-  private List<VirtualFile> getVcsRoots() {
-    List<VirtualFile> result = Lists.newArrayList();
-    BlazeImportSettings importSettings = BlazeImportSettingsManager.getInstance(project)
-      .getImportSettings();
-    if (importSettings == null) {
-      return result;
-    }
-    ProjectViewSet projectViewSet = ProjectViewManager.getInstance(project).getProjectViewSet();
-    if (projectViewSet == null) {
-      return result;
-    }
-    WorkspaceRoot workspaceRoot = WorkspaceRoot.fromImportSettings(importSettings);
-
-    for (DirectoryEntry entry : projectViewSet.listItems(DirectorySection.KEY)) {
-      if (!entry.included) {
-        continue;
-      }
-      File packageDir = workspaceRoot.fileForPath(entry.directory);
-
-      VirtualFile virtualFile = LocalFileSystem.getInstance().findFileByIoFile(packageDir);
-      if (virtualFile != null) {
-        result.add(virtualFile);
-      }
-    }
-    return result;
-  }
-
-  @Override
-  public boolean matchesDefaultMapping(final VirtualFile file, final Object matchContext) {
-    for (VirtualFile directory : getVcsRoots()) {
-      if (VfsUtilCore.isAncestor(directory, file, false)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  @Override
-  @Nullable
-  public Object getMatchContext(final VirtualFile file) {
-    return null;
-  }
-
-  @Override
-  @Nullable
-  public VirtualFile getVcsRootFor(final VirtualFile file) {
-    for (VirtualFile directory : getVcsRoots()) {
-      if (VfsUtilCore.isAncestor(directory, file, false)) {
-        return directory;
-      }
-    }
-    return null;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/vcs/BlazeVcsHandler.java b/blaze-base/src/com/google/idea/blaze/base/vcs/BlazeVcsHandler.java
deleted file mode 100644
index 973ce16..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/vcs/BlazeVcsHandler.java
+++ /dev/null
@@ -1,86 +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.vcs;
-
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListeningExecutorService;
-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.sync.workspace.BlazeRoots;
-import com.google.idea.blaze.base.sync.workspace.WorkingSet;
-import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver;
-import com.intellij.openapi.extensions.ExtensionPointName;
-import com.intellij.openapi.project.Project;
-
-import javax.annotation.Nullable;
-
-/**
- * Provides a diff against the version control system.
- */
-public interface BlazeVcsHandler {
-  ExtensionPointName<BlazeVcsHandler> EP_NAME = ExtensionPointName.create("com.google.idea.blaze.VcsHandler");
-
-  /**
-   * Optionally returns a client name if the supplied workspace root corresponds to this VCS type.
-   */
-  @Nullable
-  String getClientName(WorkspaceRoot workspaceRoot);
-
-  /**
-   * Returns whether this vcs handler can manage this project
-   */
-  boolean handlesProject(Project project, WorkspaceRoot workspaceRoot);
-
-  /**
-   * Returns the working set of modified files compared to some "upstream".
-   */
-  ListenableFuture<WorkingSet> getWorkingSet(Project project,
-                                             WorkspaceRoot workspaceRoot,
-                                             ListeningExecutorService executor);
-
-  /**
-   * Optionally creates a sync handler to perform vcs-specific computation during sync.
-   */
-  @Nullable
-  BlazeVcsSyncHandler createSyncHandler(Project project, WorkspaceRoot workspaceRoot);
-
-  interface BlazeVcsSyncHandler {
-    enum ValidationResult {
-      OK,
-      Error,
-      RestartSync, /** The sync process needs restarting **/
-    }
-
-    /**
-     * Updates the vcs state of the project.
-     *
-     * @return True for OK, false to abort the sync process.
-     */
-    boolean update(BlazeContext context, BlazeRoots blazeRoots, ListeningExecutorService executor);
-
-    /**
-     * Returns a custom workspace path resolver for this vcs.
-     */
-    @Nullable
-    WorkspacePathResolver getWorkspacePathResolver();
-
-    /**
-     * Validates the project view. Can cause sync to fail or restart.
-     */
-    ValidationResult validateProjectView(BlazeContext context, ProjectViewSet projectViewSet);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/vcs/BlazeVcsHelper.java b/blaze-base/src/com/google/idea/blaze/base/vcs/BlazeVcsHelper.java
deleted file mode 100644
index 809f88a..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/vcs/BlazeVcsHelper.java
+++ /dev/null
@@ -1,37 +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.vcs;
-
-import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
-
-import javax.annotation.Nullable;
-
-/**
- * Utility methods for VCS-specific functionality.
- */
-public class BlazeVcsHelper {
-
-  @Nullable
-  public static String getClientName(WorkspaceRoot workspaceRoot) {
-    for (BlazeVcsHandler candidate : BlazeVcsHandler.EP_NAME.getExtensions()) {
-      String name = candidate.getClientName(workspaceRoot);
-      if (name != null) {
-        return name;
-      }
-    }
-    return null;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/vcs/FallbackBlazeVcsHandler.java b/blaze-base/src/com/google/idea/blaze/base/vcs/FallbackBlazeVcsHandler.java
deleted file mode 100644
index 5a9ea64..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/vcs/FallbackBlazeVcsHandler.java
+++ /dev/null
@@ -1,54 +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.vcs;
-
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
-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.WorkingSet;
-import com.intellij.openapi.project.Project;
-
-import javax.annotation.Nullable;
-
-/**
- * Used for bazel projects, when no other vcs handler can be found. Fallback to returning a null working set.
- */
-public class FallbackBlazeVcsHandler implements BlazeVcsHandler {
-  @Nullable
-  @Override
-  public String getClientName(WorkspaceRoot workspaceRoot) {
-    return null;
-  }
-
-  @Override
-  public boolean handlesProject(Project project, WorkspaceRoot workspaceRoot) {
-    return Blaze.getBuildSystem(project) == BuildSystem.Bazel;
-  }
-
-  @Override
-  public ListenableFuture<WorkingSet> getWorkingSet(Project project, WorkspaceRoot workspaceRoot, ListeningExecutorService executor) {
-    return Futures.immediateFuture(null);
-  }
-
-  @Nullable
-  @Override
-  public BlazeVcsSyncHandler createSyncHandler(Project project, WorkspaceRoot workspaceRoot) {
-    return null;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/vcs/VcsWorkspacePathResolver.java b/blaze-base/src/com/google/idea/blaze/base/vcs/VcsWorkspacePathResolver.java
deleted file mode 100644
index b6cf25b..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/vcs/VcsWorkspacePathResolver.java
+++ /dev/null
@@ -1,28 +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.vcs;
-
-import javax.annotation.Nullable;
-import java.io.File;
-
-/**
- * Created by tomlu on 5/13/16.
- */
-public interface VcsWorkspacePathResolver {
-
-  @Nullable
-  File findPackageRoot(String relativePath);
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/vcs/git/GitBlazeVcsHandler.java b/blaze-base/src/com/google/idea/blaze/base/vcs/git/GitBlazeVcsHandler.java
deleted file mode 100644
index 322e91f..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/vcs/git/GitBlazeVcsHandler.java
+++ /dev/null
@@ -1,121 +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.vcs.git;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.idea.blaze.base.async.process.ExternalTask;
-import com.google.idea.blaze.base.io.FileAttributeProvider;
-import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
-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.WorkingSet;
-import com.google.idea.blaze.base.vcs.BlazeVcsHandler;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.text.StringUtil;
-
-import javax.annotation.Nullable;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-
-/**
- * Vcs diff provider for git
- */
-public class GitBlazeVcsHandler implements BlazeVcsHandler {
-
-  private static final Logger LOG = Logger.getInstance(GitBlazeVcsHandler.class);
-
-  @Nullable
-  @Override
-  public String getClientName(WorkspaceRoot workspaceRoot) {
-    return null;
-  }
-
-  @Override
-  public boolean handlesProject(Project project, WorkspaceRoot workspaceRoot) {
-    return Blaze.getBuildSystem(project) == BuildSystem.Bazel
-      && isGitRepository(workspaceRoot)
-      && tracksRemote(workspaceRoot);
-  }
-
-  @Override
-  public ListenableFuture<WorkingSet> getWorkingSet(Project project,
-                                                    WorkspaceRoot workspaceRoot,
-                                                    ListeningExecutorService executor) {
-    return executor.submit(() -> {
-      String upstreamSha = getUpstreamSha(workspaceRoot, false);
-      if (upstreamSha == null) {
-        return null;
-      }
-      return GitDiffProvider.calculateDiff(workspaceRoot, upstreamSha);
-    });
-  }
-
-  @Nullable
-  @Override
-  public BlazeVcsSyncHandler createSyncHandler(Project project,
-                                               WorkspaceRoot workspaceRoot) {
-    return null;
-  }
-
-  private static boolean isGitRepository(WorkspaceRoot workspaceRoot) {
-    // TODO: What if the git repo root is a parent directory of the workspace root?
-    // Just call 'git rev-parse --is-inside-work-tree' or similar instead?
-    File gitDir = new File(workspaceRoot.directory(), ".git");
-    return FileAttributeProvider.getInstance().isDirectory(gitDir);
-  }
-
-  /**
-   * If we're not on a git branch which tracks a remote, we have no way of determining a WorkingSet.
-   */
-  private static boolean tracksRemote(WorkspaceRoot workspaceRoot) {
-    return getUpstreamSha(workspaceRoot, true) != null;
-  }
-
-  /**
-   * Returns the git commit SHA corresponding to the most recent commit
-   * in the current branch which matches a commit in the currently-tracked remote branch.
-   */
-  @Nullable
-  public static String getUpstreamSha(WorkspaceRoot workspaceRoot, boolean suppressErrors) {
-    return getConsoleOutput(workspaceRoot, ImmutableList.of("git", "rev-parse", "@{u}"), suppressErrors);
-  }
-
-  /**
-   * @return the console output, in string form, or null if there was a non-zero exit code.
-   */
-  @Nullable
-  private static String getConsoleOutput(WorkspaceRoot workspaceRoot, ImmutableList<String> command, boolean suppressErrors) {
-    ByteArrayOutputStream stdout = new ByteArrayOutputStream();
-    ByteArrayOutputStream stderr = new ByteArrayOutputStream();
-
-    int retVal = ExternalTask.builder(workspaceRoot, command)
-      .stdout(stdout)
-      .stderr(stderr)
-      .build()
-      .run();
-    if (retVal != 0) {
-      if (!suppressErrors) {
-        LOG.error(stderr);
-      }
-      return null;
-    }
-    return StringUtil.trimEnd(stdout.toString(), "\n");
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/vcs/git/GitDiffProvider.java b/blaze-base/src/com/google/idea/blaze/base/vcs/git/GitDiffProvider.java
deleted file mode 100644
index 1981ae5..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/vcs/git/GitDiffProvider.java
+++ /dev/null
@@ -1,109 +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.vcs.git;
-
-import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableList;
-import com.google.idea.blaze.base.async.process.ExternalTask;
-import com.google.idea.blaze.base.async.process.LineProcessingOutputStream;
-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;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.util.text.StringUtil;
-
-import javax.annotation.Nullable;
-import java.io.ByteArrayOutputStream;
-import java.util.Arrays;
-import java.util.List;
-import java.util.stream.Collectors;
-
-/**
- * Vcs diff provider for git.
- */
-public class GitDiffProvider {
-
-  private static final Logger LOG = Logger.getInstance(GitDiffProvider.class);
-
-  /**
-   * Finds all changes between HEAD and the git commit specified by
-   * the provided SHA.<br>
-   * Returns null if an error occurred.
-   */
-  @Nullable
-  public static WorkingSet calculateDiff(
-    WorkspaceRoot workspaceRoot,
-    String upstreamSha) {
-
-    String gitRoot = getConsoleOutput(workspaceRoot, "git", "rev-parse", "--show-toplevel");
-    if (gitRoot == null) {
-      return null;
-    }
-    GitStatusLineProcessor processor = new GitStatusLineProcessor(workspaceRoot, gitRoot);
-    ByteArrayOutputStream stderr = new ByteArrayOutputStream();
-
-    // Do a git diff to find all modified files we know about
-    int retVal = ExternalTask.builder(workspaceRoot, ImmutableList.of("git", "diff", "--name-status", "--no-renames", upstreamSha))
-      .stdout(LineProcessingOutputStream.of(processor))
-      .stderr(stderr)
-      .build()
-      .run();
-    if (retVal != 0) {
-      LOG.error(stderr);
-      return null;
-    }
-
-    // Finally list all untracked files, as they're not caught by the git diff step above
-    String untrackedFilesOutput = getConsoleOutput(workspaceRoot, "git", "ls-files", "--others", "--exclude-standard");
-    if (untrackedFilesOutput == null) {
-      return null;
-    }
-
-    List<WorkspacePath> untrackedFiles = Arrays.asList(untrackedFilesOutput.split("\n"))
-      .stream()
-      .filter(s -> !Strings.isNullOrEmpty(s))
-      .filter(WorkspacePath::validate)
-      .map(WorkspacePath::new)
-      .collect(Collectors.toList());
-
-    return new WorkingSet(
-      ImmutableList.<WorkspacePath>builder().addAll(processor.addedFiles).addAll(untrackedFiles).build(),
-      ImmutableList.copyOf(processor.modifiedFiles),
-      ImmutableList.copyOf(processor.deletedFiles)
-    );
-  }
-
-  /**
-   * @return the console output, in string form, or null if there was a non-zero exit code.
-   */
-  @Nullable
-  private static String getConsoleOutput(WorkspaceRoot workspaceRoot, String... commands) {
-    ByteArrayOutputStream stdout = new ByteArrayOutputStream();
-    ByteArrayOutputStream stderr = new ByteArrayOutputStream();
-
-    int retVal = ExternalTask.builder(workspaceRoot, ImmutableList.copyOf(commands))
-      .stdout(stdout)
-      .stderr(stderr)
-      .build()
-      .run();
-    if (retVal != 0) {
-      LOG.error(stderr);
-      return null;
-    }
-    return StringUtil.trimEnd(stdout.toString(), "\n");
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/vcs/git/GitStatusLineProcessor.java b/blaze-base/src/com/google/idea/blaze/base/vcs/git/GitStatusLineProcessor.java
deleted file mode 100644
index aaf225e..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/vcs/git/GitStatusLineProcessor.java
+++ /dev/null
@@ -1,82 +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.vcs.git;
-
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.async.process.LineProcessingOutputStream;
-import com.google.idea.blaze.base.model.primitives.WorkspacePath;
-import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
-import com.intellij.openapi.util.text.StringUtil;
-
-import javax.annotation.Nullable;
-import java.io.File;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class GitStatusLineProcessor implements LineProcessingOutputStream.LineProcessor {
-
-  private static final Pattern REGEX = Pattern.compile("^(A|M|D)\\s*(.*?)$");
-
-  private final WorkspaceRoot workspaceRoot;
-  private final String gitRoot;
-
-  public final List<WorkspacePath> addedFiles = Lists.newArrayList();
-  public final List<WorkspacePath> modifiedFiles = Lists.newArrayList();
-  public final List<WorkspacePath> deletedFiles = Lists.newArrayList();
-
-  public GitStatusLineProcessor(WorkspaceRoot workspaceRoot, String gitRoot) {
-    this.workspaceRoot = workspaceRoot;
-    this.gitRoot = gitRoot;
-  }
-
-  @Override
-  public boolean processLine(String line) {
-    Matcher matcher = REGEX.matcher(line);
-    if (matcher.find()) {
-      String type = matcher.group(1);
-      String file = matcher.group(2);
-      file = StringUtil.trimEnd(file, '/');
-
-      WorkspacePath workspacePath = getWorkspacePath(file);
-      if (workspacePath == null) {
-        return true;
-      }
-      switch (type) {
-        case "A":
-          addedFiles.add(workspacePath);
-          break;
-        case "M":
-          modifiedFiles.add(workspacePath);
-          break;
-        case "D":
-          deletedFiles.add(workspacePath);
-          break;
-      }
-    }
-    return true;
-  }
-
-  @Nullable
-  private WorkspacePath getWorkspacePath(String gitPath) {
-    File absoluteFile = new File(gitRoot, gitPath);
-    if (workspaceRoot.isInWorkspace(absoluteFile)) {
-      return workspaceRoot.workspacePathFor(absoluteFile);
-    }
-    return null;
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/wizard/BlazeImportFileChooser.java b/blaze-base/src/com/google/idea/blaze/base/wizard/BlazeImportFileChooser.java
deleted file mode 100644
index f9ef3f3..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/wizard/BlazeImportFileChooser.java
+++ /dev/null
@@ -1,73 +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.wizard;
-
-import com.intellij.ide.util.PropertiesComponent;
-import com.intellij.openapi.fileChooser.FileChooserDescriptor;
-import com.intellij.openapi.fileChooser.FileChooserDialog;
-import com.intellij.openapi.fileChooser.FileChooserFactory;
-import com.intellij.openapi.vfs.LocalFileSystem;
-import com.intellij.openapi.vfs.VirtualFile;
-import org.jetbrains.annotations.NonNls;
-
-import javax.annotation.Nullable;
-
-public final class BlazeImportFileChooser {
-  private static final String WIZARD_TITLE = "Import Blaze Project";
-  private static final String WIZARD_DESCRIPTION = "Select a workspace, a .blazeproject file, or a BUILD file to import";
-  @NonNls
-  private static final String LAST_IMPORTED_LOCATION = "last.imported.location";
-
-
-  private static final class BlazeFileChooser extends FileChooserDescriptor {
-    BlazeFileChooser() {
-      super(true, true, false, false, false, false);
-    }
-
-    @Override
-    public boolean isFileSelectable(VirtualFile file) {
-      // Default implementation doesn't filter directories, we want to make sure only workspace roots are selectable
-      return super.isFileSelectable(file) && ImportSource.canImport(file);
-    }
-  }
-
-  private static FileChooserDescriptor createFileChooserDescriptor() {
-    return new BlazeFileChooser()
-      .withShowHiddenFiles(true) // Show root project view file
-      .withHideIgnored(false)
-      .withTitle(WIZARD_TITLE)
-      .withDescription(WIZARD_DESCRIPTION)
-      .withFileFilter(ImportSource::canImport);
-  }
-
-  @Nullable
-  public static VirtualFile getFileToImport() {
-    FileChooserDescriptor descriptor = createFileChooserDescriptor();
-    FileChooserDialog chooser = FileChooserFactory.getInstance().createFileChooser(descriptor, null, null);
-    VirtualFile toSelect = null;
-    String lastLocation = PropertiesComponent.getInstance().getValue(LAST_IMPORTED_LOCATION);
-    if (lastLocation != null) {
-      toSelect = LocalFileSystem.getInstance().refreshAndFindFileByPath(lastLocation);
-    }
-    VirtualFile[] files = chooser.choose(null, toSelect);
-    if (files.length == 0) {
-      return null;
-    }
-    VirtualFile file = files[0];
-    PropertiesComponent.getInstance().setValue(LAST_IMPORTED_LOCATION, file.getPath());
-    return file;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/wizard/BlazeNewProjectBuilder.java b/blaze-base/src/com/google/idea/blaze/base/wizard/BlazeNewProjectBuilder.java
deleted file mode 100644
index 3559ffd..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/wizard/BlazeNewProjectBuilder.java
+++ /dev/null
@@ -1,69 +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.wizard;
-
-import com.google.idea.blaze.base.plugin.dependency.PluginDependencyHelper;
-import com.google.idea.blaze.base.projectview.ProjectView;
-import com.google.idea.blaze.base.projectview.ProjectViewStorageManager;
-import com.google.idea.blaze.base.projectview.parser.ProjectViewParser;
-import com.google.idea.blaze.base.settings.BlazeImportSettings;
-import com.google.idea.blaze.base.settings.BlazeImportSettingsManager;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.module.Module;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.text.StringUtil;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
-
-public final class BlazeNewProjectBuilder {
-  private static final Logger LOG = Logger.getInstance(BlazeNewProjectBuilder.class);
-
-  public static List<Module> commit(final Project project, BlazeImportSettings importSettings, ProjectView projectView) {
-    String projectDataDirectory = importSettings.getProjectDataDirectory();
-
-    if (!StringUtil.isEmpty(projectDataDirectory)) {
-      File projectDataDir = new File(projectDataDirectory);
-      if (!projectDataDir.exists()) {
-        if (!projectDataDir.mkdirs()) {
-          LOG.error("Unable to create the project directory: " + projectDataDirectory);
-        }
-      }
-    }
-
-    BlazeImportSettingsManager.getInstance(project).setImportSettings(importSettings);
-
-    try {
-      String projectViewFile = importSettings.getProjectViewFile();
-      LOG.assertTrue(projectViewFile != null);
-      ProjectViewStorageManager.getInstance().writeProjectView(
-          ProjectViewParser.projectViewToString(projectView),
-          new File(projectViewFile)
-      );
-    } catch (IOException e) {
-      LOG.error(e);
-    }
-
-    PluginDependencyHelper.addDependencyOnSyncPlugin(project);
-
-    // Initial sync of the project happens in BlazeSyncStartupActivity
-
-    return Collections.emptyList();
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/wizard/BlazeProjectSettingsControl.java b/blaze-base/src/com/google/idea/blaze/base/wizard/BlazeProjectSettingsControl.java
deleted file mode 100644
index 9ebc727..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/wizard/BlazeProjectSettingsControl.java
+++ /dev/null
@@ -1,402 +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.wizard;
-
-import com.google.common.collect.Lists;
-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.ProjectViewSet;
-import com.google.idea.blaze.base.projectview.ProjectViewStorageManager;
-import com.google.idea.blaze.base.projectview.parser.ProjectViewParser;
-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.DirectoryEntry;
-import com.google.idea.blaze.base.projectview.section.sections.DirectorySection;
-import com.google.idea.blaze.base.projectview.section.sections.ImportSection;
-import com.google.idea.blaze.base.projectview.section.sections.TargetSection;
-import com.google.idea.blaze.base.scope.output.IssueOutput;
-import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
-import com.google.idea.blaze.base.settings.BlazeImportSettings;
-import com.google.idea.blaze.base.settings.ui.JPanelProvidingProject;
-import com.google.idea.blaze.base.settings.ui.ProjectViewUi;
-import com.google.idea.blaze.base.ui.BlazeValidationError;
-import com.google.idea.blaze.base.ui.BlazeValidationResult;
-import com.google.idea.blaze.base.ui.UiUtil;
-import com.google.idea.blaze.base.vcs.BlazeVcsHelper;
-import com.intellij.ide.RecentProjectsManager;
-import com.intellij.openapi.Disposable;
-import com.intellij.openapi.application.ApplicationNamesInfo;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.fileChooser.FileChooserDescriptor;
-import com.intellij.openapi.ui.TextComponentAccessor;
-import com.intellij.openapi.ui.TextFieldWithBrowseButton;
-import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.ui.components.JBLabel;
-import com.intellij.util.SystemProperties;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import javax.swing.*;
-import java.awt.*;
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
-import java.util.UUID;
-import java.util.stream.Collectors;
-
-/**
- * The UI control to collect project settings when importing a Blaze project.
- */
-public final class BlazeProjectSettingsControl {
-
-  private static final FileChooserDescriptor STUDIO_PROJECT_FOLDER_DESCRIPTOR =
-    new FileChooserDescriptor(false, true, false, false, false, false);
-  private static final Logger LOG = Logger.getInstance(BlazeProjectSettingsControl.class);
-
-  private WorkspaceRoot workspaceRoot;
-  @Nullable private File sharedProjectViewFile;
-  @Nullable private String vcsClientName;
-
-  private TextFieldWithBrowseButton projectDataDirField;
-  private JTextField projectNameField;
-  private ProjectViewUi projectViewUi;
-
-  public BlazeProjectSettingsControl(Disposable parentDisposable) {
-    this.projectViewUi = new ProjectViewUi(parentDisposable);
-  }
-
-  public JPanel createComponent(File fileToImport) {
-    JPanel component = new JPanelProvidingProject(ProjectViewUi.getProject(), new GridBagLayout());
-    fillUi(component, 0);
-    init(fileToImport);
-    UiUtil.fillBottom(component);
-    return component;
-  }
-
-  private void fillUi(@NotNull JPanel canvas, int indentLevel) {
-    JLabel projectDataDirLabel = new JBLabel("Project data directory:");
-
-    Dimension minSize = ProjectViewUi.getMinimumSize();
-    // Add 120 pixels so we have room for our extra fields
-    minSize.setSize(minSize.width, minSize.height + 120);
-    canvas.setMinimumSize(minSize);
-    canvas.setPreferredSize(minSize);
-
-    projectDataDirField = new TextFieldWithBrowseButton();
-    projectDataDirField
-      .addBrowseFolderListener("", "Blaze Android Studio project data directory", null,
-                               STUDIO_PROJECT_FOLDER_DESCRIPTOR,
-                               TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT, false);
-    final String dataDirToolTipText =
-      "Directory in which to store the project's metadata. Choose a directory outside of"
-      + " the Piper/CitC client directories or Git5 directory.";
-    projectDataDirField.setToolTipText(dataDirToolTipText);
-    projectDataDirLabel.setToolTipText(dataDirToolTipText);
-
-    canvas.add(projectDataDirLabel, UiUtil.getLabelConstraints(indentLevel));
-    canvas.add(projectDataDirField, UiUtil.getFillLineConstraints(0));
-
-    JLabel projectNameLabel = new JLabel("Project name:");
-    projectNameField = new JTextField();
-    final String projectNameToolTipText =
-      "Project display name.";
-    projectNameField.setToolTipText(projectNameToolTipText);
-    projectNameLabel.setToolTipText(projectNameToolTipText);
-    canvas.add(projectNameLabel, UiUtil.getLabelConstraints(indentLevel));
-    canvas.add(projectNameField, UiUtil.getFillLineConstraints(0));
-
-    projectViewUi.fillUi(canvas, indentLevel);
-  }
-
-  private void init(File fileToImport) {
-    workspaceRoot = BuildSystemProvider.getWorkspaceRootProvider(BuildSystem.Blaze).findWorkspaceRoot(fileToImport);
-    if (workspaceRoot == null) {
-      throw new IllegalArgumentException("Invalid workspace root: " + fileToImport);
-    }
-    vcsClientName = BlazeVcsHelper.getClientName(workspaceRoot);
-
-    File importDirectory = null;
-    if (ProjectViewStorageManager.isProjectViewFile(fileToImport.getPath())) {
-      importDirectory = fileToImport.getParentFile();
-      sharedProjectViewFile = new File(fileToImport.getPath());
-    }
-    else if (ImportSource.isBuildFile(fileToImport)) {
-      importDirectory = fileToImport.getParentFile();
-      for (String extension : ProjectViewStorageManager.VALID_EXTENSIONS) {
-        File defaultProjectViewFile = new File(fileToImport.getParentFile(), "." + extension);
-        if (defaultProjectViewFile.exists()) {
-          sharedProjectViewFile = defaultProjectViewFile;
-          break;
-        }
-      }
-    }
-
-    String defaultProjectName = importDirectory != null
-                                ? importDirectory.getName()
-                                : workspaceRoot.directory().getParentFile().getName();
-    projectNameField.setText(defaultProjectName);
-
-    String defaultDataDir = getDefaultProjectDataDirectory(defaultProjectName, vcsClientName);
-    projectDataDirField.setText(defaultDataDir);
-
-    String projectViewText = "";
-    if (sharedProjectViewFile != null) {
-      try {
-        projectViewText = ProjectViewStorageManager.getInstance().loadProjectView(sharedProjectViewFile);
-        if (projectViewText == null) {
-          LOG.error("Could not load project view: " + sharedProjectViewFile);
-          projectViewText = "";
-        }
-      }
-      catch (IOException e) {
-        LOG.error(e);
-      }
-    }
-    if (projectViewText.isEmpty() && importDirectory != null) {
-      projectViewText = guessProjectViewFromLocation(workspaceRoot, importDirectory);
-    }
-
-    projectViewUi.init(
-      workspaceRoot,
-      projectViewText,
-      sharedProjectViewFile != null ? projectViewText : null,
-      sharedProjectViewFile,
-      sharedProjectViewFile != null,
-      false /* allowEditShared - not allowed during import */
-    );
-  }
-
-
-  @NotNull
-  private static String getDefaultProjectDataDirectory(@NotNull String projectName, @Nullable String vcsClientName) {
-    File defaultDataDirectory = new File(getDefaultProjectsDirectory());
-    if (vcsClientName != null) {
-      // Ensure that each client gets its own data directory.
-      projectName = vcsClientName + "-" + projectName;
-    }
-    File desiredLocation = new File(defaultDataDirectory, projectName);
-    return newUniquePath(desiredLocation);
-  }
-
-  @NotNull
-  private static String getDefaultProjectsDirectory() {
-    final String lastProjectLocation = RecentProjectsManager.getInstance().getLastProjectCreationLocation();
-    if (lastProjectLocation != null) {
-      return lastProjectLocation.replace('/', File.separatorChar);
-    }
-    final String userHome = SystemProperties.getUserHome();
-    String productName = ApplicationNamesInfo.getInstance().getLowercaseProductName();
-    return userHome.replace('/', File.separatorChar) + File.separator + productName.replace(" ", "") + "Projects";
-  }
-
-  @NotNull
-  private static String guessProjectViewFromLocation(
-    @NotNull WorkspaceRoot workspaceRoot,
-    @NotNull File importDirectory) {
-
-    WorkspacePath mainModuleGoogle3RelativePath = workspaceRoot.workspacePathFor(importDirectory);
-    WorkspacePath testModuleGoogle3RelativePath = guessTestRelativePath(
-      workspaceRoot,
-      mainModuleGoogle3RelativePath);
-
-    ListSection.Builder<DirectoryEntry> directorySectionBuilder = ListSection.builder(DirectorySection.KEY);
-    directorySectionBuilder.add(DirectoryEntry.include(mainModuleGoogle3RelativePath));
-    if (testModuleGoogle3RelativePath != null) {
-      directorySectionBuilder.add(DirectoryEntry.include(testModuleGoogle3RelativePath));
-    }
-
-    ListSection.Builder<TargetExpression> targetSectionBuilder = ListSection.builder(TargetSection.KEY);
-    targetSectionBuilder.add(TargetExpression.fromString("//" + mainModuleGoogle3RelativePath + "/...:all"));
-    if (testModuleGoogle3RelativePath != null) {
-      targetSectionBuilder.add(TargetExpression.fromString("//" + testModuleGoogle3RelativePath + "/...:all"));
-    }
-
-    return ProjectViewParser.projectViewToString(
-      ProjectView.builder()
-        .put(directorySectionBuilder)
-        .put(targetSectionBuilder)
-        .build()
-    );
-  }
-
-  @Nullable
-  private static WorkspacePath guessTestRelativePath(
-    @NotNull WorkspaceRoot workspaceRoot,
-    @NotNull WorkspacePath projectWorkspacePath) {
-    String projectRelativePath = projectWorkspacePath.relativePath();
-    String testBuildFileRelativePath = null;
-    if (projectRelativePath.startsWith("java/")) {
-      testBuildFileRelativePath = projectRelativePath.replaceFirst("java/", "javatests/");
-    }
-    else if (projectRelativePath.contains("/java/")) {
-      testBuildFileRelativePath = projectRelativePath.replaceFirst("/java/", "/javatests/");
-    }
-    if (testBuildFileRelativePath != null) {
-      File testBuildFile = workspaceRoot.fileForPath(new WorkspacePath(testBuildFileRelativePath));
-      if (testBuildFile.exists()) {
-        return new WorkspacePath(testBuildFileRelativePath);
-      }
-    }
-    return null;
-  }
-
-  /**
-   * Returns a unique file path by appending numbers until a non-collision is found.
-   */
-  private static String newUniquePath(File location) {
-    if (!location.exists()) {
-      return location.getAbsolutePath();
-    }
-
-    String name = location.getName();
-    File directory = location.getParentFile();
-    int tries = 0;
-    while (true) {
-      String candidateName = String.format("%s-%02d", name, tries);
-      File candidateFile = new File(directory, candidateName);
-      if (!candidateFile.exists()) {
-        return candidateFile.getAbsolutePath();
-      }
-      tries++;
-    }
-  }
-
-  @Nullable
-  private BlazeValidationError validateProjectDataDirectory(@NotNull File projectDataDirPath) {
-    if (workspaceRoot.isInWorkspace(projectDataDirPath)) {
-      return new BlazeValidationError(
-        "Project data directory should be placed outside of the client directory"
-      );
-    }
-    return null;
-  }
-
-  @NotNull
-  public BlazeValidationResult validate() {
-    // Validate project settings fields
-    String projectName = projectNameField.getText().trim();
-    if (StringUtil.isEmpty(projectName)) {
-      return BlazeValidationResult.failure(new BlazeValidationError("Project name is not specified"));
-    }
-    String projectDataDirPath = projectDataDirField.getText().trim();
-    if (StringUtil.isEmpty(projectDataDirPath)) {
-      return BlazeValidationResult.failure(new BlazeValidationError("Project data directory is not specified"));
-    }
-    File projectDataDir = new File(projectDataDirPath);
-    if (!projectDataDir.isAbsolute()) {
-      return BlazeValidationResult.failure(new BlazeValidationError("Project data directory is not valid"));
-    }
-    BlazeValidationError projectDataDirectoryValidation = validateProjectDataDirectory(projectDataDir);
-    if (projectDataDirectoryValidation != null) {
-      return BlazeValidationResult.failure(projectDataDirectoryValidation);
-    }
-
-    List<IssueOutput> issues = Lists.newArrayList();
-
-    ProjectViewSet projectViewSet = projectViewUi.parseProjectView(issues);
-    BlazeValidationError projectViewParseError = validationErrorFromIssueList(issues);
-    if (projectViewParseError != null) {
-      return BlazeValidationResult.failure(projectViewParseError);
-    }
-
-    return BlazeValidationResult.success();
-  }
-
-  public ImportResults getResults() {
-    String projectName = projectNameField.getText().trim();
-    String projectDataDirectory = projectDataDirField.getText().trim();
-
-    // Create unique location hash
-    final String locationHash = createLocationHash(projectName);
-
-    // Only support blaze in the old import wizard. TODO: remove this wizard prior to public bazel release.
-    BuildSystem fixedBuildSystem = BuildSystem.Blaze;
-
-    File sharedProjectViewFile = this.sharedProjectViewFile;
-    File localProjectViewFile = ProjectViewStorageManager.getLocalProjectViewFileName(fixedBuildSystem, new File(projectDataDirectory));
-
-    BlazeImportSettings importSettings = new BlazeImportSettings(
-      workspaceRoot.directory().getPath(),
-      projectName,
-      projectDataDirectory,
-      locationHash,
-      localProjectViewFile.getPath(),
-      fixedBuildSystem
-    );
-
-    boolean useSharedProjectView = projectViewUi.getUseSharedProjectView();
-
-    // If we're using a shared project view, synthesize a local one that imports the shared one
-    final ProjectView projectView;
-    if (useSharedProjectView && sharedProjectViewFile != null) {
-      WorkspaceRoot workspaceRoot = WorkspaceRoot.fromImportSettings(importSettings);
-      projectView = ProjectView.builder()
-        .put(ScalarSection.builder(ImportSection.KEY)
-          .set(workspaceRoot.workspacePathFor(sharedProjectViewFile)))
-        .build();
-    } else {
-      ProjectViewSet parseResult = projectViewUi.parseProjectView(Lists.<IssueOutput>newArrayList());
-      ProjectViewSet.ProjectViewFile projectViewFile = parseResult.getTopLevelProjectViewFile();
-      assert projectViewFile != null;
-      projectView = projectViewFile.projectView;
-    }
-
-    return new ImportResults(
-      importSettings,
-      projectView,
-      calculateProjectName(projectName, vcsClientName),
-      projectDataDirectory
-    );
-  }
-
-  @NotNull
-  private static String createLocationHash(@NotNull String projectName) {
-    String uuid = UUID.randomUUID().toString();
-    uuid = uuid.substring(0, Math.min(uuid.length(), 8));
-    return projectName.replaceAll("[^a-zA-Z0-9]", "") + "-" + uuid;
-  }
-
-  private static String calculateProjectName(
-    @NotNull String projectName,
-    @Nullable String vcsClientName) {
-    if (vcsClientName != null) {
-      projectName = String.format("%s (%s)", projectName, vcsClientName);
-    }
-    return projectName;
-  }
-
-  @Nullable
-  private static BlazeValidationError validationErrorFromIssueList(List<IssueOutput> issues) {
-    List<IssueOutput> errors = issues
-      .stream()
-      .filter(issue -> issue.getCategory() == IssueOutput.Category.ERROR)
-      .collect(Collectors.toList());
-
-    if (!errors.isEmpty()) {
-      StringBuilder errorMessage = new StringBuilder();
-      errorMessage.append("The following issues were found:\n\n");
-      for (IssueOutput issue : errors) {
-        errorMessage.append(issue.getMessage());
-        errorMessage.append('\n');
-      }
-      return new BlazeValidationError(errorMessage.toString());
-    }
-    return null;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/wizard/ImportResults.java b/blaze-base/src/com/google/idea/blaze/base/wizard/ImportResults.java
deleted file mode 100644
index 2d99054..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/wizard/ImportResults.java
+++ /dev/null
@@ -1,42 +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.wizard;
-
-import com.google.idea.blaze.base.projectview.ProjectView;
-import com.google.idea.blaze.base.settings.BlazeImportSettings;
-
-import javax.annotation.concurrent.Immutable;
-
-@Immutable
-public final class ImportResults {
-
-  public final BlazeImportSettings importSettings;
-  public final ProjectView projectView;
-  public final String projectName;
-  public final String projectDataDirectory;
-
-  public ImportResults(
-    BlazeImportSettings importSettings,
-    ProjectView projectView,
-    String projectName,
-    String projectDataDirectory
-  ) {
-    this.importSettings = importSettings;
-    this.projectView = projectView;
-    this.projectName = projectName;
-    this.projectDataDirectory = projectDataDirectory;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/wizard/ImportSource.java b/blaze-base/src/com/google/idea/blaze/base/wizard/ImportSource.java
deleted file mode 100644
index 986ddef..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/wizard/ImportSource.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.wizard;
-
-import com.google.idea.blaze.base.bazel.BuildSystemProvider;
-import com.google.idea.blaze.base.bazel.WorkspaceRootProvider;
-import com.google.idea.blaze.base.projectview.ProjectViewStorageManager;
-import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
-import com.intellij.openapi.vfs.VirtualFile;
-import org.jetbrains.annotations.NotNull;
-
-import java.io.File;
-
-/**
- * Some convenience methods regarding your import source.
- */
-public class ImportSource {
-  static boolean isBuildFile(@NotNull File file) {
-    return file.getName().equals("BUILD");
-  }
-
-  static boolean canImport(@NotNull File file) {
-    WorkspaceRootProvider helper = BuildSystemProvider.getWorkspaceRootProvider(BuildSystem.Blaze);
-    if (helper.isWorkspaceRoot(file)) {
-      return true;
-    }
-    if (ProjectViewStorageManager.isProjectViewFile(file.getName()) || isBuildFile(file)) {
-      if (helper.isInWorkspace(file)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  public static boolean canImport(@NotNull VirtualFile file) {
-    return canImport(new File(file.getPath()));
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/wizard2/BazelWizardOptionProvider.java b/blaze-base/src/com/google/idea/blaze/base/wizard2/BazelWizardOptionProvider.java
deleted file mode 100644
index e0c4ba4..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/wizard2/BazelWizardOptionProvider.java
+++ /dev/null
@@ -1,44 +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.wizard2;
-
-import com.google.common.collect.ImmutableList;
-
-import java.util.Collection;
-
-/**
- * Provides bazel options for the wizard.
- */
-public class BazelWizardOptionProvider implements BlazeWizardOptionProvider {
-
-  @Override
-  public Collection<BlazeSelectWorkspaceOption> getSelectWorkspaceOptions(BlazeNewProjectBuilder builder) {
-    return ImmutableList.of(
-      new UseExistingBazelWorkspaceOption(builder)
-    );
-  }
-
-  @Override
-  public Collection<BlazeSelectProjectViewOption> getSelectProjectViewOptions(BlazeNewProjectBuilder builder) {
-    return ImmutableList.of(
-      new CreateFromScratchProjectViewOption(),
-      new ImportFromWorkspaceProjectViewOption(builder),
-      new GenerateFromBuildFileSelectProjectViewOption(builder),
-      new CopyExternalProjectViewOption(builder)
-    );
-  }
-
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/wizard2/BlazeNewProjectBuilder.java b/blaze-base/src/com/google/idea/blaze/base/wizard2/BlazeNewProjectBuilder.java
deleted file mode 100644
index 2738b20..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/wizard2/BlazeNewProjectBuilder.java
+++ /dev/null
@@ -1,200 +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.wizard2;
-
-import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
-import com.google.idea.blaze.base.plugin.dependency.PluginDependencyHelper;
-import com.google.idea.blaze.base.projectview.ProjectView;
-import com.google.idea.blaze.base.projectview.ProjectViewSet;
-import com.google.idea.blaze.base.projectview.ProjectViewStorageManager;
-import com.google.idea.blaze.base.projectview.parser.ProjectViewParser;
-import com.google.idea.blaze.base.settings.Blaze;
-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;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.text.StringUtil;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.UUID;
-
-/**
- * Contains the state to build a new project throughout the new project wizard process.
- */
-public final class BlazeNewProjectBuilder {
-  private static final Logger LOG = Logger.getInstance(BlazeNewProjectBuilder.class);
-
-  // Stored in user settings as the last imported workspace
-  private static final String LAST_IMPORTED_BLAZE_WORKSPACE = "blaze-wizard.last-imported-workspace";
-  private static final String LAST_IMPORTED_BAZEL_WORKSPACE = "blaze-wizard.last-imported-bazel-workspace";
-
-  public static String lastImportedWorkspaceKey(BuildSystem buildSystem) {
-    switch (buildSystem) {
-      case Blaze: return LAST_IMPORTED_BLAZE_WORKSPACE;
-      case Bazel: return LAST_IMPORTED_BAZEL_WORKSPACE;
-      default: throw new RuntimeException("Unrecognized build system type: " + buildSystem);
-    }
-  }
-
-  private final BlazeWizardUserSettings userSettings;
-  private BlazeSelectWorkspaceOption workspaceOption;
-  private BlazeSelectProjectViewOption projectViewOption;
-  private File projectViewFile;
-  private ProjectView projectView;
-  private ProjectViewSet projectViewSet;
-  private String projectName;
-  private String projectDataDirectory;
-  private WorkspaceRoot workspaceRoot;
-  private BuildSystem buildSystem;
-
-  public BlazeNewProjectBuilder() {
-    this.userSettings = BlazeWizardUserSettingsStorage.getInstance().copyUserSettings();
-  }
-
-  public BlazeWizardUserSettings getUserSettings() {
-    return userSettings;
-  }
-
-  public BlazeSelectWorkspaceOption getWorkspaceOption() {
-    return workspaceOption;
-  }
-
-  public BlazeSelectProjectViewOption getProjectViewOption() {
-    return projectViewOption;
-  }
-
-  public String getProjectName() {
-    return projectName;
-  }
-
-  public ProjectView getProjectView() {
-    return projectView;
-  }
-
-  public ProjectViewSet getProjectViewSet() {
-    return projectViewSet;
-  }
-
-  public String getProjectDataDirectory() {
-    return projectDataDirectory;
-  }
-
-  public BuildSystem getBuildSystem() {
-    return buildSystem;
-  }
-
-  public String getBuildSystemName() {
-    if (buildSystem != null) {
-      return buildSystem.getName();
-    }
-    return Blaze.defaultBuildSystemName();
-  }
-
-  public BlazeNewProjectBuilder setWorkspaceOption(BlazeSelectWorkspaceOption workspaceOption) {
-    this.workspaceOption = workspaceOption;
-    this.buildSystem = workspaceOption.getBuildSystemForWorkspace();
-    return this;
-  }
-
-  public BlazeNewProjectBuilder setProjectViewOption(BlazeSelectProjectViewOption projectViewOption) {
-    this.projectViewOption = projectViewOption;
-    return this;
-  }
-
-  public BlazeNewProjectBuilder setProjectView(ProjectView projectView) {
-    this.projectView = projectView;
-    return this;
-  }
-
-  public BlazeNewProjectBuilder setProjectViewFile(File projectViewFile) {
-    this.projectViewFile = projectViewFile;
-    return this;
-  }
-
-  public BlazeNewProjectBuilder setProjectViewSet(ProjectViewSet projectViewSet) {
-    this.projectViewSet = projectViewSet;
-    return this;
-  }
-
-  public BlazeNewProjectBuilder setProjectName(String projectName) {
-    this.projectName = projectName;
-    return this;
-  }
-
-  public BlazeNewProjectBuilder setProjectDataDirectory(String projectDataDirectory) {
-    this.projectDataDirectory = projectDataDirectory;
-    return this;
-  }
-
-  /**
-   * Commits the project. May report errors.
-   */
-  public void commit() throws BlazeProjectCommitException {
-    this.workspaceRoot = workspaceOption.commit();
-    projectViewOption.commit();
-
-    String workspaceKey = lastImportedWorkspaceKey(workspaceOption.getBuildSystemForWorkspace());
-    userSettings.put(workspaceKey, workspaceRoot.toString());
-
-    if (!StringUtil.isEmpty(projectDataDirectory)) {
-      File projectDataDir = new File(projectDataDirectory);
-      if (!projectDataDir.exists()) {
-        if (!projectDataDir.mkdirs()) {
-          throw new BlazeProjectCommitException("Unable to create the project directory: " + projectDataDirectory);
-        }
-      }
-    }
-
-    try {
-      LOG.assertTrue(projectViewFile != null);
-      ProjectViewStorageManager.getInstance().writeProjectView(
-        ProjectViewParser.projectViewToString(projectView),
-        projectViewFile
-      );
-    } catch (IOException e) {
-      throw new BlazeProjectCommitException("Could not create project view file", e);
-    }
-  }
-
-  /**
-   * Commits the project data. This method mustn't fail, because the project
-   * has already been created.
-   */
-  public void commitToProject(Project project) {
-    BlazeWizardUserSettingsStorage.getInstance().commit(userSettings);
-
-    BlazeImportSettings importSettings = new BlazeImportSettings(
-      workspaceRoot.directory().getPath(),
-      projectName,
-      projectDataDirectory,
-      createLocationHash(projectName),
-      projectViewFile.getPath(),
-      buildSystem
-    );
-
-    BlazeImportSettingsManager.getInstance(project).setImportSettings(importSettings);
-    PluginDependencyHelper.addDependencyOnSyncPlugin(project);
-    // Initial sync of the project happens in BlazeSyncStartupActivity
-  }
-
-  private static String createLocationHash(String projectName) {
-    String uuid = UUID.randomUUID().toString();
-    uuid = uuid.substring(0, Math.min(uuid.length(), 8));
-    return projectName.replaceAll("[^a-zA-Z0-9]", "") + "-" + uuid;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/wizard2/BlazeProjectCommitException.java b/blaze-base/src/com/google/idea/blaze/base/wizard2/BlazeProjectCommitException.java
deleted file mode 100644
index f52e94d..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/wizard2/BlazeProjectCommitException.java
+++ /dev/null
@@ -1,33 +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.wizard2;
-
-/**
- * Throws during the commit stage of the new project wizard.
- */
-public class BlazeProjectCommitException extends Exception {
-  public BlazeProjectCommitException(String message) {
-    super(message);
-  }
-
-  public BlazeProjectCommitException(String message, Throwable cause) {
-    super(message, cause);
-  }
-
-  public BlazeProjectCommitException(Throwable cause) {
-    super(cause);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/wizard2/BlazeSelectProjectViewOption.java b/blaze-base/src/com/google/idea/blaze/base/wizard2/BlazeSelectProjectViewOption.java
deleted file mode 100644
index f5e24ce..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/wizard2/BlazeSelectProjectViewOption.java
+++ /dev/null
@@ -1,33 +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.wizard2;
-
-import com.google.idea.blaze.base.model.primitives.WorkspacePath;
-
-import javax.annotation.Nullable;
-
-/**
- * Provides an option on the "Select .blazeproject" screen
- */
-public interface BlazeSelectProjectViewOption extends BlazeWizardOption {
-  @Nullable
-  WorkspacePath getSharedProjectView();
-
-  @Nullable
-  String getInitialProjectViewText();
-
-  void commit();
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/wizard2/BlazeSelectWorkspaceOption.java b/blaze-base/src/com/google/idea/blaze/base/wizard2/BlazeSelectWorkspaceOption.java
deleted file mode 100644
index 3a2a941..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/wizard2/BlazeSelectWorkspaceOption.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.wizard2;
-
-import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
-import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
-
-/**
- * Provides an option on the "Select workspace" screen
- */
-public interface BlazeSelectWorkspaceOption extends BlazeWizardOption {
-  /**
-   * @return a location to use when browsing for workspace paths.
-   */
-  WorkspaceRoot getTemporaryWorkspaceRoot();
-
-  /**
-   * @return the name of the workspace. Used to generate default project names.
-   */
-  String getWorkspaceName();
-
-  BuildSystem getBuildSystemForWorkspace();
-
-  WorkspaceRoot commit() throws BlazeProjectCommitException;
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/wizard2/BlazeWizardOption.java b/blaze-base/src/com/google/idea/blaze/base/wizard2/BlazeWizardOption.java
deleted file mode 100644
index 110804e..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/wizard2/BlazeWizardOption.java
+++ /dev/null
@@ -1,47 +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.wizard2;
-
-import com.google.idea.blaze.base.ui.BlazeValidationResult;
-
-import javax.annotation.Nullable;
-import javax.swing.*;
-
-/**
- * Base class for the workspace and project view options.
- */
-public interface BlazeWizardOption {
-  int HEADER_LABEL_WIDTH = 80;
-  int MAX_INPUT_FIELD_WIDTH = 600;
-
-  /**
-   * @return A stable option name, used to remember which option was selected.
-   */
-  String getOptionName();
-
-  /**
-   * @return the option text, eg "Create workspace from scratch"
-   */
-  String getOptionText();
-
-  /**
-   * @return a ui component to be added below the corresponding radio button
-   */
-  @Nullable
-  JComponent getUiComponent();
-
-  BlazeValidationResult validate();
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/wizard2/BlazeWizardOptionProvider.java b/blaze-base/src/com/google/idea/blaze/base/wizard2/BlazeWizardOptionProvider.java
deleted file mode 100644
index b928774..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/wizard2/BlazeWizardOptionProvider.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.base.wizard2;
-
-import com.intellij.openapi.extensions.ExtensionPointName;
-
-import java.util.Collection;
-
-/**
- * Provides options during the import process.
- */
-public interface BlazeWizardOptionProvider {
-  ExtensionPointName<BlazeWizardOptionProvider> EP_NAME =
-    ExtensionPointName.create("com.google.idea.blaze.BlazeWizardOptionProvider");
-
-  Collection<BlazeSelectWorkspaceOption> getSelectWorkspaceOptions(BlazeNewProjectBuilder builder);
-
-  Collection<BlazeSelectProjectViewOption> getSelectProjectViewOptions(BlazeNewProjectBuilder builder);
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/wizard2/BlazeWizardUserSettings.java b/blaze-base/src/com/google/idea/blaze/base/wizard2/BlazeWizardUserSettings.java
deleted file mode 100644
index bafe536..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/wizard2/BlazeWizardUserSettings.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.wizard2;
-
-import com.google.common.base.Objects;
-import com.google.common.collect.Maps;
-import com.intellij.util.xmlb.annotations.MapAnnotation;
-import com.intellij.util.xmlb.annotations.Tag;
-
-import java.util.Map;
-
-/**
- * A bundle of settings that are stored between invocations of the wizard.
- *
- * <p>It's the user's responsibility to appropriately namespace the keys.
- */
-public class BlazeWizardUserSettings {
-  Map<String, String> values = Maps.newHashMap();
-
-  public BlazeWizardUserSettings() {
-  }
-
-  public BlazeWizardUserSettings(BlazeWizardUserSettings state) {
-    values.putAll(state.getValues());
-  }
-
-  public String get(String key, String defaultValue) {
-    return values.getOrDefault(key, defaultValue);
-  }
-
-  public void put(String key, String value) {
-    values.put(key, value);
-  }
-
-  @SuppressWarnings("unused")
-  @Tag("settings")
-  @MapAnnotation(surroundWithTag = false)
-  public Map<String, String> getValues() {
-    return values;
-  }
-
-  @SuppressWarnings("unused")
-  public void setValues(Map<String, String> values) {
-    this.values = values;
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) return true;
-    if (o == null || getClass() != o.getClass()) return false;
-    BlazeWizardUserSettings that = (BlazeWizardUserSettings)o;
-    return Objects.equal(values, that.values);
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hashCode(values);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/wizard2/BlazeWizardUserSettingsStorage.java b/blaze-base/src/com/google/idea/blaze/base/wizard2/BlazeWizardUserSettingsStorage.java
deleted file mode 100644
index 9492476..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/wizard2/BlazeWizardUserSettingsStorage.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.wizard2;
-
-import com.intellij.openapi.components.PersistentStateComponent;
-import com.intellij.openapi.components.ServiceManager;
-import com.intellij.openapi.components.State;
-import com.intellij.openapi.components.Storage;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * Stores wizard user settings between runs.
- */
-@State(
-  name = "BlazeWizardUserSettings",
-  storages = @Storage(value = "blaze.wizard.settings.xml")
-)
-public class BlazeWizardUserSettingsStorage implements PersistentStateComponent<BlazeWizardUserSettings> {
-  private BlazeWizardUserSettings state = new BlazeWizardUserSettings();
-
-  static BlazeWizardUserSettingsStorage getInstance() {
-    return ServiceManager.getService(BlazeWizardUserSettingsStorage.class);
-  }
-
-  @Nullable
-  @Override
-  public BlazeWizardUserSettings getState() {
-    return state;
-  }
-
-  @Override
-  public void loadState(BlazeWizardUserSettings state) {
-    this.state = state;
-  }
-
-  BlazeWizardUserSettings copyUserSettings() {
-    return new BlazeWizardUserSettings(state);
-  }
-
-  void commit(BlazeWizardUserSettings userSettings) {
-    this.state = userSettings;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/wizard2/CopyExternalProjectViewOption.java b/blaze-base/src/com/google/idea/blaze/base/wizard2/CopyExternalProjectViewOption.java
deleted file mode 100644
index 8d05996..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/wizard2/CopyExternalProjectViewOption.java
+++ /dev/null
@@ -1,150 +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.wizard2;
-
-import com.google.idea.blaze.base.model.primitives.WorkspacePath;
-import com.google.idea.blaze.base.projectview.ProjectViewStorageManager;
-import com.google.idea.blaze.base.ui.BlazeValidationResult;
-import com.google.idea.blaze.base.ui.UiUtil;
-import com.intellij.openapi.fileChooser.FileChooserDescriptor;
-import com.intellij.openapi.fileChooser.FileChooserDialog;
-import com.intellij.openapi.fileChooser.FileChooserFactory;
-import com.intellij.openapi.vfs.LocalFileSystem;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.ui.components.panels.HorizontalLayout;
-
-import javax.annotation.Nullable;
-import javax.swing.*;
-import java.awt.*;
-import java.io.File;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Paths;
-
-class CopyExternalProjectViewOption implements BlazeSelectProjectViewOption {
-  private static final String LAST_WORKSPACE_PATH = "copy-external.last-project-view-path";
-
-  final BlazeWizardUserSettings userSettings;
-  final JComponent component;
-  final JTextField projectViewPathField;
-
-  CopyExternalProjectViewOption(BlazeNewProjectBuilder builder) {
-    this.userSettings = builder.getUserSettings();
-
-    String defaultWorkspacePath = userSettings.get(LAST_WORKSPACE_PATH, "");
-
-    JPanel panel = new JPanel(new HorizontalLayout(10));
-    JLabel label = new JLabel("Project view:");
-    UiUtil.setPreferredWidth(label, HEADER_LABEL_WIDTH);
-    panel.add(label);
-    this.projectViewPathField = new JTextField();
-    projectViewPathField.setText(defaultWorkspacePath);
-    UiUtil.setPreferredWidth(projectViewPathField, MAX_INPUT_FIELD_WIDTH);
-    panel.add(projectViewPathField);
-    JButton button = new JButton("...");
-    button.addActionListener(action -> chooseWorkspacePath());
-    int buttonSize = projectViewPathField.getPreferredSize().height;
-    button.setPreferredSize(new Dimension(buttonSize, buttonSize));
-    panel.add(button);
-    this.component = panel;
-  }
-
-  @Override
-  public String getOptionName() {
-    return "copy-external";
-  }
-
-  @Override
-  public String getOptionText() {
-    return "Copy external";
-  }
-
-  @Override
-  public JComponent getUiComponent() {
-    return component;
-  }
-
-  @Override
-  public BlazeValidationResult validate() {
-    if (getProjectViewPath().isEmpty()) {
-      return BlazeValidationResult.failure("Path to project view file cannot be empty.");
-    }
-    File file = new File(getProjectViewPath());
-    if (!file.exists()) {
-      return BlazeValidationResult.failure("Project view file does not exist.");
-    }
-    return BlazeValidationResult.success();
-  }
-
-  @Nullable
-  @Override
-  public WorkspacePath getSharedProjectView() {
-    return null;
-  }
-
-  @Nullable
-  @Override
-  public String getInitialProjectViewText() {
-    try {
-      byte[] bytes = Files.readAllBytes(Paths.get(getProjectViewPath()));
-      return new String(bytes, StandardCharsets.UTF_8);
-    }
-    catch (IOException e) {
-      return null;
-    }
-  }
-
-  @Override
-  public void commit() {
-    userSettings.put(LAST_WORKSPACE_PATH, getProjectViewPath());
-  }
-
-  private String getProjectViewPath() {
-    return projectViewPathField.getText().trim();
-  }
-
-  private void chooseWorkspacePath() {
-    FileChooserDescriptor descriptor = new FileChooserDescriptor(true, false, false, false, false, false)
-      .withShowHiddenFiles(true) // Show root project view file
-      .withHideIgnored(false)
-      .withTitle("Select Project View File")
-      .withDescription("Select a project view file to import.")
-      .withFileFilter(virtualFile -> ProjectViewStorageManager.isProjectViewFile(new File(virtualFile.getPath())));
-    FileChooserDialog chooser = FileChooserFactory.getInstance().createFileChooser(descriptor, null, null);
-
-    File startingLocation = null;
-    String projectViewPath = getProjectViewPath();
-    if (!projectViewPath.isEmpty()) {
-      File fileLocation = new File(projectViewPath);
-      if (fileLocation.exists()) {
-        startingLocation = fileLocation;
-      }
-    }
-    final VirtualFile[] files;
-    if (startingLocation != null) {
-      VirtualFile toSelect = LocalFileSystem.getInstance().refreshAndFindFileByPath(startingLocation.getPath());
-      files = chooser.choose(null, toSelect);
-    } else {
-      files = chooser.choose(null);
-    }
-    if (files.length == 0) {
-      return;
-    }
-    VirtualFile file = files[0];
-    projectViewPathField.setText(file.getPath());
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/wizard2/CreateFromScratchProjectViewOption.java b/blaze-base/src/com/google/idea/blaze/base/wizard2/CreateFromScratchProjectViewOption.java
deleted file mode 100644
index 89cbb32..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/wizard2/CreateFromScratchProjectViewOption.java
+++ /dev/null
@@ -1,60 +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.wizard2;
-
-import com.google.idea.blaze.base.model.primitives.WorkspacePath;
-import com.google.idea.blaze.base.ui.BlazeValidationResult;
-
-import javax.annotation.Nullable;
-import javax.swing.*;
-
-class CreateFromScratchProjectViewOption implements BlazeSelectProjectViewOption {
-  @Override
-  public String getOptionName() {
-    return "create-from-scratch";
-  }
-
-  @Override
-  public String getOptionText() {
-    return "Create from scratch";
-  }
-
-  @Override
-  public JComponent getUiComponent() {
-    return null;
-  }
-
-  @Nullable
-  @Override
-  public WorkspacePath getSharedProjectView() {
-    return null;
-  }
-
-  @Nullable
-  @Override
-  public String getInitialProjectViewText() {
-    return "";
-  }
-
-  @Override
-  public void commit() {
-  }
-
-  @Override
-  public BlazeValidationResult validate() {
-    return BlazeValidationResult.success();
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/wizard2/GenerateFromBuildFileSelectProjectViewOption.java b/blaze-base/src/com/google/idea/blaze/base/wizard2/GenerateFromBuildFileSelectProjectViewOption.java
deleted file mode 100644
index 1c2c41c..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/wizard2/GenerateFromBuildFileSelectProjectViewOption.java
+++ /dev/null
@@ -1,202 +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.wizard2;
-
-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;
-import com.google.idea.blaze.base.projectview.section.sections.DirectoryEntry;
-import com.google.idea.blaze.base.projectview.section.sections.DirectorySection;
-import com.google.idea.blaze.base.projectview.section.sections.TargetSection;
-import com.google.idea.blaze.base.ui.BlazeValidationResult;
-import com.google.idea.blaze.base.ui.UiUtil;
-import com.intellij.openapi.fileChooser.FileChooserDescriptor;
-import com.intellij.openapi.fileChooser.FileChooserDialog;
-import com.intellij.openapi.fileChooser.FileChooserFactory;
-import com.intellij.openapi.util.io.FileUtil;
-import com.intellij.openapi.vfs.LocalFileSystem;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.ui.components.panels.HorizontalLayout;
-
-import javax.annotation.Nullable;
-import javax.swing.*;
-import java.awt.*;
-import java.io.File;
-
-class GenerateFromBuildFileSelectProjectViewOption implements BlazeSelectProjectViewOption {
-  private static final String LAST_WORKSPACE_PATH = "generate-from-build-file.last-workspace-path";
-  private final BlazeNewProjectBuilder builder;
-  private final BlazeWizardUserSettings userSettings;
-  private final JTextField buildFilePathField;
-  private final JPanel component;
-
-  public GenerateFromBuildFileSelectProjectViewOption(
-    BlazeNewProjectBuilder builder) {
-    this.builder = builder;
-    this.userSettings = builder.getUserSettings();
-
-    String defaultWorkspacePath = userSettings.get(LAST_WORKSPACE_PATH, "");
-
-    JPanel panel = new JPanel(new HorizontalLayout(10));
-    JLabel pathLabel = new JLabel("BUILD file:");
-    UiUtil.setPreferredWidth(pathLabel, HEADER_LABEL_WIDTH);
-    panel.add(pathLabel);
-    this.buildFilePathField = new JTextField();
-    buildFilePathField.setText(defaultWorkspacePath);
-    UiUtil.setPreferredWidth(buildFilePathField, MAX_INPUT_FIELD_WIDTH);
-    panel.add(buildFilePathField);
-    JButton button = new JButton("...");
-    button.addActionListener(action -> chooseWorkspacePath());
-
-    int buttonSize = buildFilePathField.getPreferredSize().height;
-    button.setPreferredSize(new Dimension(buttonSize, buttonSize));
-    panel.add(button);
-    this.component = panel;
-  }
-
-  @Override
-  public String getOptionName() {
-    return "generate-from-build-file";
-  }
-
-  @Override
-  public String getOptionText() {
-    return "Generate from BUILD file";
-  }
-
-  @Override
-  public JComponent getUiComponent() {
-    return component;
-  }
-
-  @Override
-  public BlazeValidationResult validate() {
-    if (getBuildFilePath().isEmpty()) {
-      return BlazeValidationResult.failure("BUILD file field cannot be empty.");
-    }
-    WorkspaceRoot workspaceRoot = builder.getWorkspaceOption().getTemporaryWorkspaceRoot();
-    File file = workspaceRoot.fileForPath(new WorkspacePath(getBuildFilePath()));
-    if (!file.exists()) {
-      return BlazeValidationResult.failure("BUILD file does not exist.");
-    }
-
-    return BlazeValidationResult.success();
-  }
-
-  @Nullable
-  @Override
-  public WorkspacePath getSharedProjectView() {
-    return null;
-  }
-
-  @Nullable
-  @Override
-  public String getInitialProjectViewText() {
-    WorkspaceRoot workspaceRoot = builder.getWorkspaceOption().getTemporaryWorkspaceRoot();
-    WorkspacePath workspacePath = new WorkspacePath(getBuildFilePath());
-    return guessProjectViewFromLocation(workspaceRoot,
-                                        workspaceRoot.workspacePathFor(workspaceRoot.fileForPath(workspacePath).getParentFile()));
-  }
-
-  @Override
-  public void commit() {
-    userSettings.put(LAST_WORKSPACE_PATH, getBuildFilePath());
-  }
-
-  private static String guessProjectViewFromLocation(WorkspaceRoot workspaceRoot, WorkspacePath workspacePath) {
-
-    WorkspacePath mainModuleWorkspaceRelativePath = workspacePath;
-    WorkspacePath testModuleWorkspaceRelativePath = guessTestRelativePath(
-      workspaceRoot,
-      mainModuleWorkspaceRelativePath);
-
-    ListSection.Builder<DirectoryEntry> directorySectionBuilder = ListSection.builder(DirectorySection.KEY);
-    directorySectionBuilder.add(DirectoryEntry.include(mainModuleWorkspaceRelativePath));
-    if (testModuleWorkspaceRelativePath != null) {
-      directorySectionBuilder.add(DirectoryEntry.include(testModuleWorkspaceRelativePath));
-    }
-
-    ListSection.Builder<TargetExpression> targetSectionBuilder = ListSection.builder(TargetSection.KEY);
-    targetSectionBuilder.add(TargetExpression.allFromPackageRecursive(mainModuleWorkspaceRelativePath));
-    if (testModuleWorkspaceRelativePath != null) {
-      targetSectionBuilder.add(TargetExpression.allFromPackageRecursive(testModuleWorkspaceRelativePath));
-    }
-
-    return ProjectViewParser.projectViewToString(
-      ProjectView.builder()
-        .put(directorySectionBuilder)
-        .put(targetSectionBuilder)
-        .build()
-    );
-  }
-
-  @Nullable
-  private static WorkspacePath guessTestRelativePath(
-    WorkspaceRoot workspaceRoot,
-    WorkspacePath projectWorkspacePath) {
-    String projectRelativePath = projectWorkspacePath.relativePath();
-    String testBuildFileRelativePath = null;
-    if (projectRelativePath.startsWith("java/")) {
-      testBuildFileRelativePath = projectRelativePath.replaceFirst("java/", "javatests/");
-    }
-    else if (projectRelativePath.contains("/java/")) {
-      testBuildFileRelativePath = projectRelativePath.replaceFirst("/java/", "/javatests/");
-    }
-    if (testBuildFileRelativePath != null) {
-      File testBuildFile = workspaceRoot.fileForPath(new WorkspacePath(testBuildFileRelativePath));
-      if (testBuildFile.exists()) {
-        return new WorkspacePath(testBuildFileRelativePath);
-      }
-    }
-    return null;
-  }
-
-  private String getBuildFilePath() {
-    return buildFilePathField.getText().trim();
-  }
-
-  private void chooseWorkspacePath() {
-    FileChooserDescriptor descriptor = new FileChooserDescriptor(true, false, false, false, false, false)
-      .withShowHiddenFiles(true) // Show root project view file
-      .withHideIgnored(false)
-      .withTitle("Select BUILD File")
-      .withDescription("Select a BUILD file to synthesize a project view from.")
-      .withFileFilter(virtualFile -> virtualFile.getName().equals("BUILD"));
-    FileChooserDialog chooser = FileChooserFactory.getInstance().createFileChooser(descriptor, null, null);
-
-    WorkspaceRoot workspaceRoot = builder.getWorkspaceOption().getTemporaryWorkspaceRoot();
-
-    File startingLocation = workspaceRoot.directory();
-    String buildFilePath = getBuildFilePath();
-    if (!buildFilePath.isEmpty()) {
-      File fileLocation = workspaceRoot.fileForPath(new WorkspacePath(buildFilePath));
-      if (fileLocation.exists()) {
-        startingLocation = fileLocation;
-      }
-    }
-    VirtualFile toSelect = LocalFileSystem.getInstance().refreshAndFindFileByPath(startingLocation.getPath());
-    VirtualFile[] files = chooser.choose(null, toSelect);
-    if (files.length == 0) {
-      return;
-    }
-    VirtualFile file = files[0];
-    String newWorkspacePath = FileUtil.getRelativePath(workspaceRoot.directory(), new File(file.getPath()));
-    buildFilePathField.setText(newWorkspacePath);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/wizard2/ImportFromWorkspaceProjectViewOption.java b/blaze-base/src/com/google/idea/blaze/base/wizard2/ImportFromWorkspaceProjectViewOption.java
deleted file mode 100644
index 3fa8388..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/wizard2/ImportFromWorkspaceProjectViewOption.java
+++ /dev/null
@@ -1,157 +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.wizard2;
-
-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.ui.BlazeValidationResult;
-import com.google.idea.blaze.base.ui.UiUtil;
-import com.intellij.openapi.fileChooser.FileChooserDescriptor;
-import com.intellij.openapi.fileChooser.FileChooserDialog;
-import com.intellij.openapi.fileChooser.FileChooserFactory;
-import com.intellij.openapi.ui.Messages;
-import com.intellij.openapi.util.io.FileUtil;
-import com.intellij.openapi.vfs.LocalFileSystem;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.ui.components.panels.HorizontalLayout;
-
-import javax.annotation.Nullable;
-import javax.swing.*;
-import java.awt.*;
-import java.io.File;
-
-class ImportFromWorkspaceProjectViewOption implements BlazeSelectProjectViewOption {
-  private static final String LAST_WORKSPACE_PATH = "import-from-workspace.last-workspace-path";
-
-  final BlazeNewProjectBuilder builder;
-  final BlazeWizardUserSettings userSettings;
-  final JComponent component;
-  final JTextField projectViewPathField;
-
-  ImportFromWorkspaceProjectViewOption(BlazeNewProjectBuilder builder) {
-    this.builder = builder;
-    this.userSettings = builder.getUserSettings();
-
-    String defaultWorkspacePath = userSettings.get(LAST_WORKSPACE_PATH, "");
-
-    JPanel panel = new JPanel(new HorizontalLayout(10));
-    JLabel projectViewLabel = new JLabel("Project view:");
-    UiUtil.setPreferredWidth(projectViewLabel, HEADER_LABEL_WIDTH);
-    panel.add(projectViewLabel);
-    this.projectViewPathField = new JTextField();
-    projectViewPathField.setText(defaultWorkspacePath);
-    UiUtil.setPreferredWidth(projectViewPathField, MAX_INPUT_FIELD_WIDTH);
-    panel.add(projectViewPathField);
-    JButton button = new JButton("...");
-    button.addActionListener(action -> chooseWorkspacePath());
-    int buttonSize = projectViewPathField.getPreferredSize().height;
-    button.setPreferredSize(new Dimension(buttonSize, buttonSize));
-    panel.add(button);
-    this.component = panel;
-  }
-
-  @Override
-  public String getOptionName() {
-    return "import-from-workspace";
-  }
-
-  @Override
-  public String getOptionText() {
-    return "Import from workspace";
-  }
-
-  @Override
-  public JComponent getUiComponent() {
-    return component;
-  }
-
-  @Override
-  public BlazeValidationResult validate() {
-    if (getProjectViewPath().isEmpty()) {
-      return BlazeValidationResult.failure("Workspace path to project view file cannot be empty.");
-    }
-    WorkspaceRoot workspaceRoot = builder.getWorkspaceOption().getTemporaryWorkspaceRoot();
-    File file = workspaceRoot.fileForPath(getSharedProjectView());
-    if (!file.exists()) {
-      return BlazeValidationResult.failure("Project view file does not exist.");
-    }
-
-    return BlazeValidationResult.success();
-  }
-
-  @Nullable
-  @Override
-  public WorkspacePath getSharedProjectView() {
-    return new WorkspacePath(getProjectViewPath());
-  }
-
-  @Nullable
-  @Override
-  public String getInitialProjectViewText() {
-    return null;
-  }
-
-  @Override
-  public void commit() {
-    userSettings.put(LAST_WORKSPACE_PATH, getProjectViewPath());
-  }
-
-  private String getProjectViewPath() {
-    return projectViewPathField.getText().trim();
-  }
-
-  private void chooseWorkspacePath() {
-    FileChooserDescriptor descriptor = new FileChooserDescriptor(true, false, false, false, false, false)
-      .withShowHiddenFiles(true) // Show root project view file
-      .withHideIgnored(false)
-      .withTitle("Select Project View File")
-      .withDescription("Select a project view file to import.")
-      .withFileFilter(virtualFile -> ProjectViewStorageManager.isProjectViewFile(new File(virtualFile.getPath())));
-    FileChooserDialog chooser = FileChooserFactory.getInstance().createFileChooser(descriptor, null, null);
-
-    WorkspaceRoot workspaceRoot = builder.getWorkspaceOption().getTemporaryWorkspaceRoot();
-
-    File startingLocation = workspaceRoot.directory();
-    String projectViewPath = getProjectViewPath();
-    if (!projectViewPath.isEmpty()) {
-      File fileLocation = workspaceRoot.fileForPath(new WorkspacePath(projectViewPath));
-      if (fileLocation.exists()) {
-        startingLocation = fileLocation;
-      }
-    }
-    VirtualFile toSelect = LocalFileSystem.getInstance().refreshAndFindFileByPath(startingLocation.getPath());
-    VirtualFile[] files = chooser.choose(null, toSelect);
-    if (files.length == 0) {
-      return;
-    }
-    VirtualFile file = files[0];
-
-    if (!FileUtil.isAncestor(workspaceRoot.directory().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.",
-          workspaceRoot.directory().getPath()
-        ),
-        "Cannot Use Project View File"
-      );
-      return;
-    }
-
-    String newWorkspacePath = FileUtil.getRelativePath(workspaceRoot.directory(), new File(file.getPath()));
-    projectViewPathField.setText(newWorkspacePath);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/wizard2/UseExistingBazelWorkspaceOption.java b/blaze-base/src/com/google/idea/blaze/base/wizard2/UseExistingBazelWorkspaceOption.java
deleted file mode 100644
index a19feb3..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/wizard2/UseExistingBazelWorkspaceOption.java
+++ /dev/null
@@ -1,60 +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.wizard2;
-
-import com.google.idea.blaze.base.bazel.BuildSystemProvider;
-import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
-import com.intellij.openapi.vfs.VirtualFile;
-import icons.BlazeIcons;
-
-import javax.swing.*;
-import java.io.File;
-
-class UseExistingBazelWorkspaceOption extends UseExistingWorkspaceOption {
-
-  UseExistingBazelWorkspaceOption(BlazeNewProjectBuilder builder) {
-    super(builder, BuildSystem.Bazel);
-  }
-
-  @Override
-  protected boolean isWorkspaceRoot(VirtualFile file) {
-    return BuildSystemProvider.getWorkspaceRootProvider(BuildSystem.Bazel).isWorkspaceRoot(new File(file.getPath()));
-  }
-
-  @Override
-  public String getOptionName() {
-    return "use-existing-bazel-workspace";
-  }
-  @Override
-  public String getOptionText() {
-    return "Use existing bazel workspace";
-  }
-
-  @Override
-  protected String getWorkspaceName(File workspaceRoot) {
-    return workspaceRoot.getName();
-  }
-
-  @Override
-  protected String fileChooserDescription() {
-    return "Select the directory of the workspace you want to use.";
-  }
-
-  @Override
-  protected Icon getBuildSystemIcon() {
-    return BlazeIcons.BazelLeaf;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/wizard2/UseExistingWorkspaceOption.java b/blaze-base/src/com/google/idea/blaze/base/wizard2/UseExistingWorkspaceOption.java
deleted file mode 100644
index 79875aa..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/wizard2/UseExistingWorkspaceOption.java
+++ /dev/null
@@ -1,170 +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.wizard2;
-
-import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
-import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
-import com.google.idea.blaze.base.ui.BlazeValidationResult;
-import com.google.idea.blaze.base.ui.UiUtil;
-import com.intellij.icons.AllIcons;
-import com.intellij.openapi.fileChooser.FileChooserDescriptor;
-import com.intellij.openapi.fileChooser.FileChooserDialog;
-import com.intellij.openapi.fileChooser.FileChooserFactory;
-import com.intellij.openapi.util.IconLoader;
-import com.intellij.openapi.vfs.LocalFileSystem;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.ui.components.panels.HorizontalLayout;
-
-import javax.annotation.Nullable;
-import javax.swing.*;
-import java.awt.*;
-import java.io.File;
-
-public abstract class UseExistingWorkspaceOption implements BlazeSelectWorkspaceOption {
-
-  private final BlazeWizardUserSettings userSettings;
-  private final JComponent component;
-  private final JTextField directoryField;
-  private final BuildSystem buildSystem;
-
-  protected UseExistingWorkspaceOption(BlazeNewProjectBuilder builder, BuildSystem buildSystem) {
-    this.userSettings = builder.getUserSettings();
-    this.buildSystem = buildSystem;
-
-    String defaultDirectory = userSettings.get(BlazeNewProjectBuilder.lastImportedWorkspaceKey(buildSystem), "");
-
-    JPanel panel = new JPanel(new HorizontalLayout(10));
-    panel.add(getIconComponent());
-    JLabel workspaceRootLabel = new JLabel("Workspace:");
-    UiUtil.setPreferredWidth(workspaceRootLabel, HEADER_LABEL_WIDTH);
-    panel.add(workspaceRootLabel);
-    this.directoryField = new JTextField();
-    directoryField.setText(defaultDirectory);
-    UiUtil.setPreferredWidth(directoryField, MAX_INPUT_FIELD_WIDTH);
-    panel.add(directoryField);
-    JButton button = new JButton("...");
-    button.addActionListener(action -> chooseDirectory());
-    int buttonSize = directoryField.getPreferredSize().height;
-    button.setPreferredSize(new Dimension(buttonSize, buttonSize));
-    panel.add(button);
-    this.component = panel;
-  }
-
-  protected abstract boolean isWorkspaceRoot(VirtualFile file);
-
-  protected abstract String fileChooserDescription();
-
-  protected abstract Icon getBuildSystemIcon();
-
-  protected abstract String getWorkspaceName(File workspaceRoot);
-
-  @Override
-  public BuildSystem getBuildSystemForWorkspace() {
-    return buildSystem;
-  }
-
-  @Override
-  public JComponent getUiComponent() {
-    return component;
-  }
-
-  @Override
-  public WorkspaceRoot commit() throws BlazeProjectCommitException {
-    return new WorkspaceRoot(new File(getDirectory()));
-  }
-
-  @Nullable
-  @Override
-  public WorkspaceRoot getTemporaryWorkspaceRoot() {
-    return new WorkspaceRoot(new File(getDirectory()));
-  }
-
-  @Override
-  public String getWorkspaceName() {
-    File workspaceRoot = new File(getDirectory());
-    return getWorkspaceName(workspaceRoot);
-  }
-
-  @Override
-  public BlazeValidationResult validate() {
-    if (getDirectory().isEmpty()) {
-      return BlazeValidationResult.failure("Please select a workspace");
-    }
-
-    File workspaceRoot = new File(getDirectory());
-    if (!workspaceRoot.exists()) {
-      return BlazeValidationResult.failure("Workspace does not exist");
-    }
-    return BlazeValidationResult.success();
-  }
-
-  private String getDirectory() {
-    return directoryField.getText().trim();
-  }
-
-  private void chooseDirectory() {
-    FileChooserDescriptor descriptor = new FileChooserDescriptor(false, true, false, false, false, false)
-    {
-      @Override
-      public boolean isFileSelectable(VirtualFile file) {
-        // Default implementation doesn't filter directories, we want to make sure only workspace roots are selectable
-        return super.isFileSelectable(file) && isWorkspaceRoot(file);
-      }
-
-      @Override
-      public Icon getIcon(VirtualFile file) {
-        if (buildSystem == BuildSystem.Bazel) {
-          // isWorkspaceRoot requires file system calls -- it's too expensive
-          return super.getIcon(file);
-        }
-        if (isWorkspaceRoot(file)) {
-          return AllIcons.Nodes.SourceFolder;
-        }
-        return super.getIcon(file);
-      }
-    }
-      .withHideIgnored(false)
-      .withTitle("Select Workspace Root")
-      .withDescription(fileChooserDescription())
-      .withFileFilter(this::isWorkspaceRoot);
-    FileChooserDialog chooser = FileChooserFactory.getInstance().createFileChooser(descriptor, null, null);
-
-    final VirtualFile[] files;
-    File existingLocation = new File(getDirectory());
-    if (existingLocation.exists()) {
-      VirtualFile toSelect = LocalFileSystem.getInstance().refreshAndFindFileByPath(existingLocation.getPath());
-      files = chooser.choose(null, toSelect);
-    } else {
-      files = chooser.choose(null);
-    }
-    if (files.length == 0) {
-      return;
-    }
-    VirtualFile file = files[0];
-    directoryField.setText(file.getPath());
-  }
-
-  private Component getIconComponent() {
-    JLabel iconPanel = new JLabel(IconLoader.getIconSnapshot(getBuildSystemIcon())) {
-      @Override
-      public boolean isEnabled() {
-        return true;
-      }
-    };
-    UiUtil.setPreferredWidth(iconPanel, 16);
-    return iconPanel;
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/wizard2/ui/BlazeEditProjectViewControl.java b/blaze-base/src/com/google/idea/blaze/base/wizard2/ui/BlazeEditProjectViewControl.java
deleted file mode 100644
index a4d41d3..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/wizard2/ui/BlazeEditProjectViewControl.java
+++ /dev/null
@@ -1,303 +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.wizard2.ui;
-
-import com.google.common.collect.Lists;
-import com.google.common.hash.HashCode;
-import com.google.common.hash.Hashing;
-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.ProjectViewSet;
-import com.google.idea.blaze.base.projectview.ProjectViewStorageManager;
-import com.google.idea.blaze.base.projectview.section.ScalarSection;
-import com.google.idea.blaze.base.projectview.section.sections.ImportSection;
-import com.google.idea.blaze.base.scope.output.IssueOutput;
-import com.google.idea.blaze.base.settings.ui.JPanelProvidingProject;
-import com.google.idea.blaze.base.settings.ui.ProjectViewUi;
-import com.google.idea.blaze.base.ui.BlazeValidationError;
-import com.google.idea.blaze.base.ui.BlazeValidationResult;
-import com.google.idea.blaze.base.ui.UiUtil;
-import com.google.idea.blaze.base.wizard2.BlazeNewProjectBuilder;
-import com.google.idea.blaze.base.wizard2.BlazeSelectProjectViewOption;
-import com.google.idea.blaze.base.wizard2.BlazeSelectWorkspaceOption;
-import com.intellij.ide.RecentProjectsManager;
-import com.intellij.openapi.Disposable;
-import com.intellij.openapi.application.ApplicationNamesInfo;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.fileChooser.FileChooserDescriptor;
-import com.intellij.openapi.ui.TextComponentAccessor;
-import com.intellij.openapi.ui.TextFieldWithBrowseButton;
-import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.ui.components.JBLabel;
-import com.intellij.util.SystemProperties;
-import org.jetbrains.annotations.Nullable;
-
-import javax.swing.*;
-import java.awt.*;
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
-import java.util.stream.Collectors;
-
-/**
- * The UI control to collect project settings when importing a Blaze project.
- */
-public final class BlazeEditProjectViewControl {
-
-  private static final FileChooserDescriptor PROJECT_FOLDER_DESCRIPTOR =
-    new FileChooserDescriptor(false, true, false, false, false, false);
-  private static final Logger LOG = Logger.getInstance(BlazeEditProjectViewControl.class);
-
-  private final JPanel component;
-  private final String buildSystemName;
-  private final ProjectViewUi projectViewUi;
-
-  private TextFieldWithBrowseButton projectDataDirField;
-  private JTextField projectNameField;
-  private HashCode paramsHash;
-
-  public BlazeEditProjectViewControl(BlazeNewProjectBuilder builder, Disposable parentDisposable) {
-    this.projectViewUi = new ProjectViewUi(parentDisposable);
-    JPanel component = new JPanelProvidingProject(ProjectViewUi.getProject(), new GridBagLayout());
-    fillUi(component, 0, parentDisposable);
-    update(builder);
-    UiUtil.fillBottom(component);
-    this.component = component;
-    this.buildSystemName = builder.getBuildSystemName();
-  }
-
-  public Component getUiComponent() {
-    return component;
-  }
-
-  private void fillUi(JPanel canvas, int indentLevel, Disposable parentDisposable) {
-    JLabel projectDataDirLabel = new JBLabel("Project data directory:");
-
-    Dimension minSize = ProjectViewUi.getMinimumSize();
-    // Add 120 pixels so we have room for our extra fields
-    minSize.setSize(minSize.width, minSize.height + 120);
-    canvas.setMinimumSize(minSize);
-    canvas.setPreferredSize(minSize);
-
-    projectDataDirField = new TextFieldWithBrowseButton();
-    projectDataDirField.addBrowseFolderListener("", buildSystemName + " project data directory", null,
-                                                PROJECT_FOLDER_DESCRIPTOR,
-                                                TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT, false);
-    final String dataDirToolTipText =
-      "Directory in which to store the project's metadata. Choose a directory outside of your workspace.";
-    projectDataDirField.setToolTipText(dataDirToolTipText);
-    projectDataDirLabel.setToolTipText(dataDirToolTipText);
-
-    canvas.add(projectDataDirLabel, UiUtil.getLabelConstraints(indentLevel));
-    canvas.add(projectDataDirField, UiUtil.getFillLineConstraints(0));
-
-    JLabel projectNameLabel = new JLabel("Project name:");
-    projectNameField = new JTextField();
-    final String projectNameToolTipText =
-      "Project display name.";
-    projectNameField.setToolTipText(projectNameToolTipText);
-    projectNameLabel.setToolTipText(projectNameToolTipText);
-    canvas.add(projectNameLabel, UiUtil.getLabelConstraints(indentLevel));
-    canvas.add(projectNameField, UiUtil.getFillLineConstraints(0));
-
-    projectViewUi.fillUi(canvas, indentLevel);
-  }
-
-  public void update(BlazeNewProjectBuilder builder) {
-    BlazeSelectWorkspaceOption workspaceOption = builder.getWorkspaceOption();
-    BlazeSelectProjectViewOption projectViewOption = builder.getProjectViewOption();
-    String workspaceName = workspaceOption.getWorkspaceName();
-    WorkspaceRoot workspaceRoot = workspaceOption.getTemporaryWorkspaceRoot();
-    WorkspacePath workspacePath = projectViewOption.getSharedProjectView();
-    String initialProjectViewText = projectViewOption.getInitialProjectViewText();
-
-    HashCode hashCode = Hashing.md5().newHasher()
-      .putUnencodedChars(workspaceName)
-      .putUnencodedChars(workspaceRoot.toString())
-      .putUnencodedChars(workspacePath != null ? workspacePath.toString() : "")
-      .putUnencodedChars(initialProjectViewText != null ? initialProjectViewText : "")
-      .hash();
-
-    // If any params have changed, reinit the control
-    if (!hashCode.equals(paramsHash)) {
-      this.paramsHash = hashCode;
-      init(workspaceName, workspaceRoot, workspacePath, initialProjectViewText);
-    }
-  }
-
-  private void init(String workspaceName,
-                    WorkspaceRoot workspaceRoot,
-                    @Nullable WorkspacePath sharedProjectView,
-                    @Nullable String initialProjectViewText) {
-
-    projectNameField.setText(workspaceName);
-    String defaultDataDir = getDefaultProjectDataDirectory(workspaceName);
-    projectDataDirField.setText(defaultDataDir);
-
-    String projectViewText = "";
-    File sharedProjectViewFile = null;
-
-    if (sharedProjectView != null) {
-      sharedProjectViewFile = workspaceRoot.fileForPath(sharedProjectView);
-
-      try {
-        projectViewText = ProjectViewStorageManager.getInstance().loadProjectView(sharedProjectViewFile);
-        if (projectViewText == null) {
-          LOG.error("Could not load project view: " + sharedProjectViewFile);
-          projectViewText = "";
-        }
-      }
-      catch (IOException e) {
-        LOG.error(e);
-      }
-    } else {
-      projectViewText = initialProjectViewText;
-      LOG.assertTrue(projectViewText != null);
-    }
-
-    projectViewUi.init(
-      workspaceRoot,
-      projectViewText,
-      sharedProjectViewFile != null ? projectViewText : null,
-      sharedProjectViewFile,
-      sharedProjectViewFile != null,
-      false /* allowEditShared - not allowed during import */
-    );
-  }
-
-  private static String getDefaultProjectDataDirectory(String projectName) {
-    File defaultDataDirectory = new File(getDefaultProjectsDirectory());
-    File desiredLocation = new File(defaultDataDirectory, projectName);
-    return newUniquePath(desiredLocation);
-  }
-
-  private static String getDefaultProjectsDirectory() {
-    final String lastProjectLocation = RecentProjectsManager.getInstance().getLastProjectCreationLocation();
-    if (lastProjectLocation != null) {
-      return lastProjectLocation.replace('/', File.separatorChar);
-    }
-    final String userHome = SystemProperties.getUserHome();
-    String productName = ApplicationNamesInfo.getInstance().getLowercaseProductName();
-    return userHome.replace('/', File.separatorChar) + File.separator + productName.replace(" ", "") + "Projects";
-  }
-
-  /**
-   * Returns a unique file path by appending numbers until a non-collision is found.
-   */
-  private static String newUniquePath(File location) {
-    if (!location.exists()) {
-      return location.getAbsolutePath();
-    }
-
-    String name = location.getName();
-    File directory = location.getParentFile();
-    int tries = 0;
-    while (true) {
-      String candidateName = String.format("%s-%02d", name, tries);
-      File candidateFile = new File(directory, candidateName);
-      if (!candidateFile.exists()) {
-        return candidateFile.getAbsolutePath();
-      }
-      tries++;
-    }
-  }
-
-  public BlazeValidationResult validate() {
-    // Validate project settings fields
-    String projectName = projectNameField.getText().trim();
-    if (StringUtil.isEmpty(projectName)) {
-      return BlazeValidationResult.failure(new BlazeValidationError("Project name is not specified"));
-    }
-    String projectDataDirPath = projectDataDirField.getText().trim();
-    if (StringUtil.isEmpty(projectDataDirPath)) {
-      return BlazeValidationResult.failure(new BlazeValidationError("Project data directory is not specified"));
-    }
-    File projectDataDir = new File(projectDataDirPath);
-    if (!projectDataDir.isAbsolute()) {
-      return BlazeValidationResult.failure(new BlazeValidationError("Project data directory is not valid"));
-    }
-
-    List<IssueOutput> issues = Lists.newArrayList();
-
-    projectViewUi.parseProjectView(issues);
-    BlazeValidationError projectViewParseError = validationErrorFromIssueList(issues);
-    if (projectViewParseError != null) {
-      return BlazeValidationResult.failure(projectViewParseError);
-    }
-
-    return BlazeValidationResult.success();
-  }
-
-  @Nullable
-  private static BlazeValidationError validationErrorFromIssueList(List<IssueOutput> issues) {
-    List<IssueOutput> errors = issues
-      .stream()
-      .filter(issue -> issue.getCategory() == IssueOutput.Category.ERROR)
-      .collect(Collectors.toList());
-
-    if (!errors.isEmpty()) {
-      StringBuilder errorMessage = new StringBuilder();
-      errorMessage.append("The following issues were found:\n\n");
-      for (IssueOutput issue : errors) {
-        errorMessage.append(issue.getMessage());
-        errorMessage.append('\n');
-      }
-      return new BlazeValidationError(errorMessage.toString());
-    }
-    return null;
-  }
-
-  public void updateBuilder(BlazeNewProjectBuilder builder) {
-    String projectName = projectNameField.getText().trim();
-    String projectDataDirectory = projectDataDirField.getText().trim();
-    File localProjectViewFile = ProjectViewStorageManager.getLocalProjectViewFileName(
-      builder.getBuildSystem(), new File(projectDataDirectory)
-    );
-
-    BlazeSelectProjectViewOption selectProjectViewOption = builder.getProjectViewOption();
-    boolean useSharedProjectView = projectViewUi.getUseSharedProjectView();
-
-
-    // If we're using a shared project view, synthesize a local one that imports the shared one
-    ProjectViewSet parseResult = projectViewUi.parseProjectView(Lists.newArrayList());
-
-    final ProjectView projectView;
-    final ProjectViewSet projectViewSet;
-    if (useSharedProjectView && selectProjectViewOption.getSharedProjectView() != null) {
-      projectView = ProjectView.builder()
-        .put(ScalarSection.builder(ImportSection.KEY)
-               .set(selectProjectViewOption.getSharedProjectView()))
-        .build();
-      projectViewSet = ProjectViewSet.builder()
-        .addAll(parseResult.getProjectViewFiles())
-        .add(localProjectViewFile, projectView)
-        .build();
-    } else {
-      ProjectViewSet.ProjectViewFile projectViewFile = parseResult.getTopLevelProjectViewFile();
-      assert projectViewFile != null;
-      projectView = projectViewFile.projectView;
-      projectViewSet = parseResult;
-    }
-
-    builder
-      .setProjectView(projectView)
-      .setProjectViewFile(localProjectViewFile)
-      .setProjectViewSet(projectViewSet)
-      .setProjectName(projectName)
-      .setProjectDataDirectory(projectDataDirectory);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/wizard2/ui/BlazeSelectOptionControl.java b/blaze-base/src/com/google/idea/blaze/base/wizard2/ui/BlazeSelectOptionControl.java
deleted file mode 100644
index 8a5a8d6..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/wizard2/ui/BlazeSelectOptionControl.java
+++ /dev/null
@@ -1,163 +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.wizard2.ui;
-
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.settings.ui.ProjectViewUi;
-import com.google.idea.blaze.base.ui.BlazeValidationResult;
-import com.google.idea.blaze.base.ui.UiUtil;
-import com.google.idea.blaze.base.wizard2.BlazeNewProjectBuilder;
-import com.google.idea.blaze.base.wizard2.BlazeWizardOption;
-import com.google.idea.blaze.base.wizard2.BlazeWizardUserSettings;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.ui.components.panels.HorizontalLayout;
-import com.intellij.ui.components.panels.VerticalLayout;
-
-import javax.swing.*;
-import javax.swing.border.EmptyBorder;
-import java.awt.*;
-import java.util.Collection;
-
-
-/**
- * UI for selecting a client during the import process.
- */
-public abstract class BlazeSelectOptionControl<T extends BlazeWizardOption> {
-  private static final Logger LOG = Logger.getInstance(BlazeSelectOptionControl.class);
-
-  private final BlazeWizardUserSettings userSettings;
-  private final JPanel canvas;
-  private final JLabel titleLabel;
-  private final Collection<OptionUiEntry<T>> optionUiEntryList;
-
-  static class OptionUiEntry<T> {
-    final T option;
-    final JRadioButton radioButton;
-    OptionUiEntry(T option, JRadioButton radioButton) {
-      this.option = option;
-      this.radioButton = radioButton;
-    }
-  }
-
-  BlazeSelectOptionControl(BlazeNewProjectBuilder builder,
-                           Collection<T> options) {
-    if (options == null) {
-      LOG.error("No options on select screen '" + getTitle() + "'");
-    }
-
-    this.userSettings = builder.getUserSettings();
-
-    JPanel canvas = new JPanel(new VerticalLayout(4));
-
-    Dimension minSize = ProjectViewUi.getMinimumSize();
-    canvas.setPreferredSize(minSize);
-
-    titleLabel = new JLabel(getTitle());
-    canvas.add(titleLabel);
-    canvas.add(new JSeparator());
-
-    JPanel content = new JPanel(new VerticalLayout(12));
-    content.setBorder(new EmptyBorder(20, 100, 0, 0));
-    canvas.add(content);
-
-    ButtonGroup buttonGroup = new ButtonGroup();
-    Collection<OptionUiEntry<T>> optionUiEntryList = Lists.newArrayList();
-    for (T option : options) {
-      JPanel vertical = new JPanel(new VerticalLayout(10));
-      JRadioButton radioButton = new JRadioButton();
-      radioButton.setText(option.getOptionText());
-      vertical.add(radioButton);
-
-      JComponent optionComponent = option.getUiComponent();
-      if (optionComponent != null) {
-        JPanel horizontal = new JPanel(new HorizontalLayout(0));
-        horizontal.setBorder(new EmptyBorder(0, 25, 0, 0));
-        horizontal.add(optionComponent);
-        vertical.add(horizontal);
-
-        UiUtil.setEnabledRecursive(optionComponent, false);
-        radioButton.addItemListener(itemEvent -> {
-          boolean isSelected = radioButton.isSelected();
-          UiUtil.setEnabledRecursive(optionComponent, isSelected);
-        });
-      }
-
-      content.add(vertical);
-      buttonGroup.add(radioButton);
-      optionUiEntryList.add(new OptionUiEntry<>(option, radioButton));
-    }
-
-    OptionUiEntry selected = null;
-    String previouslyChosenOption = userSettings.get(getOptionKey(), null);
-    if (previouslyChosenOption != null) {
-      for (OptionUiEntry<T> entry : optionUiEntryList) {
-        if (entry.option.getOptionName().equals(previouslyChosenOption)) {
-          selected = entry;
-          break;
-        }
-      }
-    }
-    if (selected == null) {
-      selected = Iterables.getFirst(optionUiEntryList, null);
-    }
-    if (selected != null) {
-      selected.radioButton.setSelected(true);
-    }
-
-    this.canvas = canvas;
-    this.optionUiEntryList = optionUiEntryList;
-  }
-
-  public BlazeValidationResult validate() {
-    T option = getSelectedOption();
-    if (option == null) {
-      return BlazeValidationResult.failure("No option selected.");
-    }
-    return option.validate();
-  }
-
-  public JComponent getUiComponent() {
-    return canvas;
-  }
-
-  public T getSelectedOption() {
-    for (OptionUiEntry<T> entry : optionUiEntryList) {
-      if (entry.radioButton.isSelected()) {
-        return entry.option;
-      }
-    }
-    return null;
-  }
-
-  public void commit() {
-    T selectedOption = getSelectedOption();
-    if (selectedOption != null) {
-      userSettings.put(getOptionKey(), selectedOption.getOptionName());
-    }
-  }
-
-  /**
-   * Call when the title changes.
-   */
-  protected void setTitle(String newTitle) {
-    titleLabel.setText(newTitle);
-  }
-
-  abstract String getTitle();
-
-  abstract String getOptionKey();
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/wizard2/ui/BlazeSelectProjectViewControl.java b/blaze-base/src/com/google/idea/blaze/base/wizard2/ui/BlazeSelectProjectViewControl.java
deleted file mode 100644
index 9668287..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/wizard2/ui/BlazeSelectProjectViewControl.java
+++ /dev/null
@@ -1,80 +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.wizard2.ui;
-
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.projectview.ProjectViewStorageManager;
-import com.google.idea.blaze.base.ui.BlazeValidationResult;
-import com.google.idea.blaze.base.wizard2.BlazeNewProjectBuilder;
-import com.google.idea.blaze.base.wizard2.BlazeSelectProjectViewOption;
-import com.google.idea.blaze.base.wizard2.BlazeWizardOptionProvider;
-
-import javax.swing.*;
-import java.util.Collection;
-
-
-/**
- * UI for selecting the project view during the import process.
- */
-public class BlazeSelectProjectViewControl {
-
-  private BlazeSelectOptionControl<BlazeSelectProjectViewOption> selectOptionControl;
-
-  public BlazeSelectProjectViewControl(BlazeNewProjectBuilder builder) {
-    Collection<BlazeSelectProjectViewOption> options = Lists.newArrayList();
-    for (BlazeWizardOptionProvider optionProvider : BlazeWizardOptionProvider.EP_NAME.getExtensions()) {
-      options.addAll(optionProvider.getSelectProjectViewOptions(builder));
-    }
-
-    this.selectOptionControl =
-      new BlazeSelectOptionControl<BlazeSelectProjectViewOption>(builder, options) {
-        @Override
-        String getTitle() {
-          return BlazeSelectProjectViewControl.getTitle(builder);
-        }
-
-        @Override
-        String getOptionKey() {
-          return "select-project-view.selected-option";
-        }
-      };
-  }
-
-  public JComponent getUiComponent() {
-    return selectOptionControl.getUiComponent();
-  }
-
-  public BlazeValidationResult validate() {
-    return selectOptionControl.validate();
-  }
-
-  public void update(BlazeNewProjectBuilder builder) {
-    selectOptionControl.setTitle(getTitle(builder));
-  }
-
-  public void updateBuilder(BlazeNewProjectBuilder builder) {
-    builder.setProjectViewOption(selectOptionControl.getSelectedOption());
-  }
-
-  public void commit() {
-    selectOptionControl.commit();
-  }
-
-  private static String getTitle(BlazeNewProjectBuilder builder) {
-    String projectViewString = ProjectViewStorageManager.getProjectViewFileName(builder.getBuildSystem());
-    return String.format("Select project view (%s file)", projectViewString);
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/wizard2/ui/BlazeSelectWorkspaceControl.java b/blaze-base/src/com/google/idea/blaze/base/wizard2/ui/BlazeSelectWorkspaceControl.java
deleted file mode 100644
index d32deb1..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/wizard2/ui/BlazeSelectWorkspaceControl.java
+++ /dev/null
@@ -1,69 +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.wizard2.ui;
-
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.ui.BlazeValidationResult;
-import com.google.idea.blaze.base.wizard2.BlazeNewProjectBuilder;
-import com.google.idea.blaze.base.wizard2.BlazeSelectWorkspaceOption;
-import com.google.idea.blaze.base.wizard2.BlazeWizardOptionProvider;
-
-import javax.swing.*;
-import java.util.Collection;
-
-
-/**
- * UI for selecting a client during the import process.
- */
-public class BlazeSelectWorkspaceControl {
-  BlazeSelectOptionControl<BlazeSelectWorkspaceOption> selectOptionControl;
-
-  public BlazeSelectWorkspaceControl(BlazeNewProjectBuilder builder) {
-    Collection<BlazeSelectWorkspaceOption> options = Lists.newArrayList();
-    for (BlazeWizardOptionProvider optionProvider : BlazeWizardOptionProvider.EP_NAME.getExtensions()) {
-      options.addAll(optionProvider.getSelectWorkspaceOptions(builder));
-    }
-
-    this.selectOptionControl =
-      new BlazeSelectOptionControl<BlazeSelectWorkspaceOption>(builder, options) {
-        @Override
-        String getTitle() {
-          return "Select workspace";
-        }
-
-        @Override
-        String getOptionKey() {
-          return "select-workspace.selected-option";
-        }
-      };
-  }
-
-  public JComponent getUiComponent() {
-    return selectOptionControl.getUiComponent();
-  }
-
-  public BlazeValidationResult validate() {
-    return selectOptionControl.validate();
-  }
-
-  public void updateBuilder(BlazeNewProjectBuilder builder) {
-    builder.setWorkspaceOption(selectOptionControl.getSelectedOption());
-  }
-
-  public void commit() {
-    selectOptionControl.commit();
-  }
-}
diff --git a/blaze-base/src/com/google/idea/blaze/base/wizard2/ui/SelectBazelBinaryControl.java b/blaze-base/src/com/google/idea/blaze/base/wizard2/ui/SelectBazelBinaryControl.java
deleted file mode 100644
index d2acbb6..0000000
--- a/blaze-base/src/com/google/idea/blaze/base/wizard2/ui/SelectBazelBinaryControl.java
+++ /dev/null
@@ -1,123 +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.wizard2.ui;
-
-import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableList;
-import com.google.idea.blaze.base.async.process.ExternalTask;
-import com.google.idea.blaze.base.io.FileAttributeProvider;
-import com.google.idea.blaze.base.settings.BlazeUserSettings;
-import com.google.idea.blaze.base.settings.ui.BlazeUserSettingsConfigurable;
-import com.google.idea.blaze.base.ui.BlazeValidationResult;
-import com.google.idea.blaze.base.ui.FileSelectorWithStoredHistory;
-import com.google.idea.blaze.base.wizard2.BlazeNewProjectBuilder;
-import com.intellij.ui.components.panels.VerticalLayout;
-
-import javax.annotation.Nullable;
-import javax.swing.*;
-import javax.swing.border.EmptyBorder;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-
-/**
- * UI for selecting the build system binary during the import process.
- */
-public class SelectBazelBinaryControl {
-
-  public final BlazeNewProjectBuilder builder;
-
-  private boolean uiInitialized = false;
-  private JPanel component;
-  private FileSelectorWithStoredHistory bazelBinaryPath;
-
-  public SelectBazelBinaryControl(BlazeNewProjectBuilder builder) {
-    this.builder = builder;
-  }
-
-  public JComponent getUiComponent() {
-    if (!uiInitialized) {
-      initUi();
-      uiInitialized = true;
-    }
-    return component;
-  }
-
-  private void initUi() {
-    bazelBinaryPath = FileSelectorWithStoredHistory.create(
-      BlazeUserSettingsConfigurable.BAZEL_BINARY_PATH_KEY,
-      "Specify the bazel binary path");
-    bazelBinaryPath.setText(getInitialBinaryPath());
-
-    component = new JPanel(new VerticalLayout(4));
-    component.add(new JLabel("Select a bazel binary"));
-    component.add(new JSeparator());
-
-    JPanel content = new JPanel(new VerticalLayout(12));
-    content.setBorder(new EmptyBorder(50, 100, 0, 100));
-    component.add(content);
-
-    content.add(new JLabel("Specify a bazel binary to be used for all bazel projects"));
-    content.add(bazelBinaryPath);
-  }
-
-  public BlazeValidationResult validate() {
-    String binaryPath = getBazelPath();
-    if (Strings.isNullOrEmpty(binaryPath)) {
-      return BlazeValidationResult.failure("Select a bazel binary");
-    }
-    if (!FileAttributeProvider.getInstance().isFile(new File(binaryPath))) {
-      return BlazeValidationResult.failure("Invalid bazel binary: file does not exist");
-    }
-    return BlazeValidationResult.success();
-  }
-
-  public void commit() {
-    if (!Strings.isNullOrEmpty(getBazelPath())) {
-      BlazeUserSettings.getInstance().setBazelBinaryPath(getBazelPath());
-    }
-  }
-
-  private String getBazelPath() {
-    String text = bazelBinaryPath.getText();
-    return text != null ? text.trim() : "";
-  }
-
-  private static String getInitialBinaryPath() {
-    String existingPath = BlazeUserSettings.getInstance().getBazelBinaryPath();
-    if (existingPath != null) {
-      return existingPath;
-    }
-    return guessBinaryPath();
-  }
-
-  /**
-   * Try to guess an initial binary path
-   */
-  @Nullable
-  private static String guessBinaryPath() {
-    ByteArrayOutputStream stdout = new ByteArrayOutputStream();
-    int retVal = ExternalTask.builder(new File("/"), ImmutableList.of("which", "bazel"))
-      .stdout(stdout)
-      .build()
-      .run();
-
-    if (retVal != 0) {
-      return null;
-    }
-    return stdout.toString().trim();
-  }
-
-}
diff --git a/blaze-base/src/icons/BlazeIcons.java b/blaze-base/src/icons/BlazeIcons.java
deleted file mode 100644
index b21c7ef..0000000
--- a/blaze-base/src/icons/BlazeIcons.java
+++ /dev/null
@@ -1,44 +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 icons;
-
-import com.intellij.openapi.util.IconLoader;
-
-import javax.swing.*;
-
-/**
- * Class to manage icons used by the Blaze plugin.
- */
-public class BlazeIcons {
-
-  public static final Icon Blaze = load("/blaze-base/resources/icons/blaze.png"); // 16x16
-  public static final Icon BlazeSlow = load("/blaze-base/resources/icons/blaze_slow.png"); // 16x16
-  public static final Icon BlazeDirty = load("/blaze-base/resources/icons/blaze_dirty.png"); // 16x16
-  public static final Icon BlazeClean = load("/blaze-base/resources/icons/blaze_clean.png"); // 16x16
-  public static final Icon BlazeFailed = load("/blaze-base/resources/icons/blaze_failed.png"); // 16x16
-  // This is just the Blaze icon scaled down to the size IJ wants for tool windows.
-  public static final Icon BlazeToolWindow = load("/blaze-base/resources/icons/blazeToolWindow.png"); // 13x13
-
-  public static final Icon BazelLeaf = load("/blaze-base/resources/icons/bazel_leaf.png"); // 16x16
-
-  // Build file support icons
-  public static final Icon BuildFile = load("/blaze-base/resources/icons/build_file.png"); // 16x16
-  public static final Icon BuildRule = load("/blaze-base/resources/icons/build_rule.png"); // 16x16
-
-  private static Icon load(String path) {
-    return IconLoader.getIcon(path, BlazeIcons.class);
-  }
-}
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/ArgumentCompletionContributorTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/ArgumentCompletionContributorTest.java
deleted file mode 100644
index 31ec67c..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/ArgumentCompletionContributorTest.java
+++ /dev/null
@@ -1,64 +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.completion;
-
-import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
-import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
-import com.intellij.codeInsight.lookup.LookupElement;
-import com.intellij.openapi.editor.Editor;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Tests for code completion of funcall arguments.
- */
-public class ArgumentCompletionContributorTest extends BuildFileIntegrationTestCase {
-
-  public void testIncompleteFuncall() {
-    BuildFile file = createBuildFile(
-      "BUILD",
-      "def function(name, deps, srcs):",
-      "  # empty function",
-      "function(d");
-
-    Editor editor = openFileInEditor(file.getVirtualFile());
-    setCaretPosition(editor, 2, "function(n".length());
-
-    LookupElement[] completionItems = testFixture.completeBasic();
-    assertThat(completionItems).isNull();
-
-    assertFileContents(
-      file,
-      "def function(name, deps, srcs):",
-      "  # empty function",
-      "function(deps");
-  }
-
-  public void testExistingKeywordArg() {
-    BuildFile file = createBuildFile(
-      "BUILD",
-      "def function(name, deps, srcs):",
-      "  # empty function",
-      "function(name = \"lib\")");
-
-    Editor editor = openFileInEditor(file.getVirtualFile());
-    setCaretPosition(editor, 2, "function(".length());
-
-    String[] completionItems = getCompletionItemsAsStrings();
-    assertThat(completionItems).hasLength(4);
-    assertThat(completionItems).asList().containsAllOf("name", "deps", "srcs", "function");
-  }
-}
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/BuiltInFunctionAttributeCompletionContributorTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/BuiltInFunctionAttributeCompletionContributorTest.java
deleted file mode 100644
index 7b0f616..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/BuiltInFunctionAttributeCompletionContributorTest.java
+++ /dev/null
@@ -1,135 +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.completion;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
-import com.google.idea.blaze.base.lang.buildfile.language.semantics.AttributeDefinition;
-import com.google.idea.blaze.base.lang.buildfile.language.semantics.BuildLanguageSpec;
-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.repackaged.devtools.build.lib.query2.proto.proto2api.Build;
-import com.intellij.codeInsight.lookup.LookupElement;
-import com.intellij.openapi.editor.Editor;
-import com.intellij.openapi.project.Project;
-
-import javax.annotation.Nullable;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Tests for BuiltInFunctionAttributeCompletionContributor.
- */
-public class BuiltInFunctionAttributeCompletionContributorTest extends BuildFileIntegrationTestCase {
-
-  private MockBuildLanguageSpecProvider specProvider;
-
-  @Override
-  protected void doSetup() {
-    super.doSetup();
-    specProvider = new MockBuildLanguageSpecProvider();
-    registerApplicationService(BuildLanguageSpecProvider.class, specProvider);
-  }
-
-  public void testSimpleCompletion() {
-    setRuleAndAttributes("sh_binary",
-                         "name", "deps", "srcs", "data");
-
-    BuildFile file = createBuildFile(
-      "BUILD",
-      "sh_binary(");
-
-    Editor editor = openFileInEditor(file.getVirtualFile());
-    setCaretPosition(editor, 0, "sh_binary(".length());
-
-    String[] completionItems = getCompletionItemsAsStrings();
-    assertThat(completionItems).asList().containsAllOf("name", "deps", "srcs", "data");
-  }
-
-  public void testSimpleSingleCompletion() {
-    setRuleAndAttributes("sh_binary",
-                         "name", "deps", "srcs", "data");
-
-    BuildFile file = createBuildFile(
-      "BUILD",
-      "sh_binary(",
-      "    n");
-
-    Editor editor = openFileInEditor(file.getVirtualFile());
-    setCaretPosition(editor, 1, "    n".length());
-
-    String[] completionItems = getCompletionItemsAsStrings();
-    assertThat(completionItems).isNull();
-    assertFileContents(file,
-                       "sh_binary(",
-                       "    name");
-  }
-
-  public void testNoCompletionInUnknownRule() {
-    setRuleAndAttributes("sh_binary",
-                         "name", "deps", "srcs", "data");
-
-    BuildFile file = createBuildFile(
-      "BUILD",
-      "java_binary(");
-
-    Editor editor = openFileInEditor(file.getVirtualFile());
-    setCaretPosition(editor, 0, "java_binary(".length());
-
-    LookupElement[] completionItems = testFixture.completeBasic();
-    assertThat(completionItems).isEmpty();
-  }
-
-  public void testCompletionInSkylarkExtension() {
-    setRuleAndAttributes("sh_binary",
-                         "name", "deps", "srcs", "data");
-
-    BuildFile file = createBuildFile(
-      "skylark.bzl",
-      "native.sh_binary(");
-
-    Editor editor = openFileInEditor(file.getVirtualFile());
-    setCaretPosition(editor, 0, "native.sh_binary(".length());
-
-    String[] completionItems = getCompletionItemsAsStrings();
-    assertThat(completionItems).asList().containsAllOf("name", "deps", "srcs", "data");
-  }
-
-  private void setRuleAndAttributes(String ruleName, String... attributes) {
-    ImmutableMap.Builder<String, AttributeDefinition> map = ImmutableMap.builder();
-    for (String attr : attributes) {
-      map.put(attr, new AttributeDefinition(attr, Build.Attribute.Discriminator.UNKNOWN, false, null, null));
-    }
-    RuleDefinition rule = new RuleDefinition(ruleName, map.build(), null);
-    specProvider.setRules(ImmutableMap.of(ruleName, rule));
-  }
-
-  private static class MockBuildLanguageSpecProvider implements BuildLanguageSpecProvider {
-
-    BuildLanguageSpec languageSpec;
-
-    void setRules(ImmutableMap<String, RuleDefinition> rules) {
-      languageSpec = new BuildLanguageSpec(rules);
-    }
-
-    @Nullable
-    @Override
-    public BuildLanguageSpec getLanguageSpec(Project project) {
-      return languageSpec;
-    }
-  }
-}
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/BuiltInFunctionCompletionContributorTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/BuiltInFunctionCompletionContributorTest.java
deleted file mode 100644
index 008217b..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/BuiltInFunctionCompletionContributorTest.java
+++ /dev/null
@@ -1,141 +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.completion;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
-import com.google.idea.blaze.base.lang.buildfile.language.semantics.BuildLanguageSpec;
-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.intellij.codeInsight.lookup.LookupElement;
-import com.intellij.openapi.editor.Editor;
-import com.intellij.openapi.project.Project;
-
-import javax.annotation.Nullable;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Tests BuiltInFunctionCompletionContributor
- */
-public class BuiltInFunctionCompletionContributorTest extends BuildFileIntegrationTestCase {
-
-  private MockBuildLanguageSpecProvider specProvider;
-
-  @Override
-  protected void doSetup() {
-    super.doSetup();
-    specProvider = new MockBuildLanguageSpecProvider();
-    registerApplicationService(BuildLanguageSpecProvider.class, specProvider);
-  }
-
-  public void testSimpleTopLevelCompletion() {
-    setRules("java_library", "android_binary");
-
-    BuildFile file = createBuildFile(
-      "BUILD",
-      "");
-
-    Editor editor = openFileInEditor(file.getVirtualFile());
-    setCaretPosition(editor, 0, 0);
-
-    LookupElement[] completionItems = testFixture.completeBasic();
-    assertThat(completionItems).hasLength(2);
-    assertThat(completionItems[0].getLookupString()).isEqualTo("android_binary");
-    assertThat(completionItems[1].getLookupString()).isEqualTo("java_library");
-
-    assertFileContents(file, "");
-  }
-
-  public void testUniqueTopLevelCompletion() {
-    setRules("java_library", "android_binary");
-
-    BuildFile file = createBuildFile(
-      "BUILD",
-      "ja");
-
-    Editor editor = openFileInEditor(file.getVirtualFile());
-    setCaretPosition(editor, 0, 2);
-
-    LookupElement[] completionItems = testFixture.completeBasic();
-    assertThat(completionItems).isNull();
-
-    assertFileContents(file, "java_library()");
-    assertCaretPosition(editor, 0, "java_library(".length());
-  }
-
-  public void testSkylarkNativeCompletion() {
-    setRules("java_library", "android_binary");
-
-    BuildFile file = createBuildFile(
-      "build_defs.bzl",
-      "def function():",
-      "  native.j");
-
-    Editor editor = openFileInEditor(file.getVirtualFile());
-    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());
-  }
-
-  public void testNoCompletionInsideRule() {
-    setRules("java_library", "android_binary");
-
-    String[] contents = {
-      "java_library(",
-      "    name = \"lib\"",
-      ""};
-
-    BuildFile file = createBuildFile("BUILD", contents);
-
-    Editor editor = openFileInEditor(file.getVirtualFile());
-    setCaretPosition(editor, 2, 0);
-
-    LookupElement[] completionItems = testFixture.completeBasic();
-    assertThat(completionItems).isEmpty();
-    assertFileContents(file, contents);
-  }
-
-  private void setRules(String... ruleNames) {
-    ImmutableMap.Builder<String, RuleDefinition> rules = ImmutableMap.builder();
-    for (String name : ruleNames) {
-      rules.put(name, new RuleDefinition(name, ImmutableMap.of(), null));
-    }
-    specProvider.setRules(rules.build());
-  }
-
-  private static class MockBuildLanguageSpecProvider implements BuildLanguageSpecProvider {
-
-    BuildLanguageSpec languageSpec;
-
-    void setRules(ImmutableMap<String, RuleDefinition> rules) {
-      languageSpec = new BuildLanguageSpec(rules);
-    }
-
-    @Nullable
-    @Override
-    public BuildLanguageSpec getLanguageSpec(Project project) {
-      return languageSpec;
-    }
-  }
-}
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/FilePathCompletionTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/FilePathCompletionTest.java
deleted file mode 100644
index 36b8857..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/FilePathCompletionTest.java
+++ /dev/null
@@ -1,126 +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.completion;
-
-import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
-import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
-import com.intellij.openapi.editor.Editor;
-import com.intellij.openapi.vfs.VirtualFile;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Tests file path code completion in BUILD file labels.
- */
-public class FilePathCompletionTest extends BuildFileIntegrationTestCase {
-
-  public void testUniqueDirectoryCompleted() {
-    BuildFile file = createBuildFile(
-      "java/BUILD",
-      "'//'");
-
-    Editor editor = openFileInEditor(file);
-    setCaretPosition(editor, 0, "'//".length());
-
-    assertThat(completeIfUnique()).isTrue();
-    assertFileContents(file, "'//java'");
-    // check caret remains inside closing quote
-    assertCaretPosition(editor, 0, "'//java".length());
-  }
-
-  public void testUniqueMultiSegmentDirectoryCompleted() {
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "'//'");
-
-    Editor editor = openFileInEditor(file);
-    setCaretPosition(editor, 0, "'//".length());
-
-    assertThat(completeIfUnique()).isTrue();
-    assertFileContents(file, "'//java/com/google'");
-  }
-
-  // expected to be a typical workflow -- complete a segment, get the possibilities, then start typing
-  // next segment and complete again
-  public void testMultiStageCompletion() {
-    createDirectory("foo");
-    createDirectory("bar");
-    createDirectory("other");
-    createDirectory("other/foo");
-    createDirectory("other/bar");
-
-    BuildFile file = createBuildFile(
-      "BUILD",
-      "'//'");
-
-    Editor editor = openFileInEditor(file);
-    setCaretPosition(editor, 0, "'//".length());
-
-    String[] completionItems = getCompletionItemsAsStrings();
-    assertThat(completionItems).hasLength(3);
-
-    performTypingAction(editor, 'o');
-    assertThat(completeIfUnique()).isTrue();
-    assertFileContents(file, "'//other'");
-    assertCaretPosition(editor, 0, "'//other".length());
-
-    performTypingAction(editor, '/');
-    performTypingAction(editor, 'f');
-    assertThat(completeIfUnique()).isTrue();
-    assertFileContents(file, "'//other/foo'");
-    assertCaretPosition(editor, 0, "'//other/foo".length());
-  }
-
-  public void testCompletionSuggestionString() {
-    createDirectory("foo");
-    createDirectory("bar");
-    createDirectory("other");
-    createDirectory("ostrich/foo");
-    createDirectory("ostrich/fooz");
-
-    VirtualFile file = createAndSetCaret(
-      "BUILD",
-      "'//o<caret>'");
-
-    String[] completionItems = getCompletionItemsAsSuggestionStrings();
-    assertThat(completionItems).asList().containsExactly("other", "ostrich");
-
-    performTypingAction(testFixture.getEditor(), 's');
-
-    assertThat(completeIfUnique()).isTrue();
-    assertFileContents(file, "'//ostrich'");
-
-    completionItems = getCompletionItemsAsSuggestionStrings();
-    assertThat(completionItems).asList().containsExactly("/foo", "/fooz");
-
-    performTypingAction(testFixture.getEditor(), '/');
-
-    completionItems = getCompletionItemsAsSuggestionStrings();
-    assertThat(completionItems).asList().containsExactly("foo", "fooz");
-
-    performTypingAction(testFixture.getEditor(), 'f');
-
-    completionItems = getCompletionItemsAsSuggestionStrings();
-    assertThat(completionItems).asList().containsExactly("foo", "fooz");
-  }
-
-  private VirtualFile createAndSetCaret(String filePath, String... fileContents) {
-    VirtualFile file = createFile(filePath, fileContents);
-    testFixture.configureFromExistingVirtualFile(file);
-    return file;
-  }
-
-}
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/LocalSymbolCompletionTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/LocalSymbolCompletionTest.java
deleted file mode 100644
index 4d24fab..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/LocalSymbolCompletionTest.java
+++ /dev/null
@@ -1,143 +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.completion;
-
-import com.google.common.base.Joiner;
-import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
-import com.intellij.psi.PsiFile;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Tests code completion works with general symbols in scope.
- */
-public class LocalSymbolCompletionTest extends BuildFileIntegrationTestCase {
-
-  private PsiFile setInput(String... fileContents) {
-    return testFixture.configureByText("BUILD", Joiner.on("\n").join(fileContents));
-  }
-
-  private void assertResult(String... resultingFileContents) {
-    String s = testFixture.getFile().getText();
-    testFixture.checkResult(Joiner.on("\n").join(resultingFileContents));
-  }
-
-  public void testLocalVariable() {
-    setInput(
-      "var = [a, b]",
-      "def function(name, deps, srcs):",
-      "  v<caret>"
-    );
-
-    completeIfUnique();
-
-    assertResult(
-      "var = [a, b]",
-      "def function(name, deps, srcs):",
-      "  var<caret>"
-    );
-  }
-
-  public void testLocalFunction() {
-    setInput(
-      "def fnName():return True",
-      "def function(name, deps, srcs):",
-      "  fnN<caret>"
-    );
-
-    completeIfUnique();
-
-    assertResult(
-      "def fnName():return True",
-      "def function(name, deps, srcs):",
-      "  fnName<caret>"
-    );
-  }
-
-  public void testNoCompletionAfterDot() {
-    setInput(
-      "var = [a, b]",
-      "def function(name, deps, srcs):",
-      "  ext.v<caret>"
-    );
-
-    String[] completionItems = getCompletionItemsAsStrings();
-    assertThat(completionItems).isEmpty();
-  }
-
-  public void testFunctionParam() {
-    setInput(
-      "def test(var):",
-      "  v<caret>"
-    );
-
-    completeIfUnique();
-
-    assertResult(
-      "def test(var):",
-      "  var<caret>"
-    );
-  }
-
-  // b/28912523: when symbol is present in multiple assignment statements, should only be
-  // included once in the code-completion dialog
-  public void testSymbolAssignedMultipleTimes() {
-    setInput(
-      "var = 1",
-      "var = 2",
-      "var = 3",
-      "<caret>"
-    );
-
-    completeIfUnique();
-
-    assertResult(
-      "var = 1",
-      "var = 2",
-      "var = 3",
-      "var<caret>"
-    );
-  }
-
-  public void testSymbolDefinedOutsideScope() {
-    setInput(
-      "<caret>",
-      "var = 1"
-    );
-
-    assertThat(getCompletionItemsAsStrings()).isEmpty();
-  }
-
-  public void testSymbolDefinedOutsideScope2() {
-    setInput(
-      "def fn():",
-      "  var = 1",
-      "v<caret>"
-    );
-
-    assertThat(testFixture.completeBasic()).isEmpty();
-  }
-
-  public void testSymbolDefinedOutsideScope3() {
-    setInput(
-      "for var in (1, 2, 3): print var",
-      "v<caret>"
-    );
-
-    assertThat(testFixture.completeBasic()).isEmpty();
-  }
-
-}
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/ParameterCompletionContributorTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/ParameterCompletionContributorTest.java
deleted file mode 100644
index 9aeb6ba..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/ParameterCompletionContributorTest.java
+++ /dev/null
@@ -1,58 +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.completion;
-
-import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
-import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
-import com.intellij.codeInsight.lookup.LookupElement;
-import com.intellij.openapi.editor.Editor;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Tests ParameterCompletionContributor.
- */
-public class ParameterCompletionContributorTest extends BuildFileIntegrationTestCase {
-
-  public void testArgsCompletion() {
-    BuildFile file = createBuildFile(
-      "BUILD",
-      "def function(arg1, *");
-
-    Editor editor = openFileInEditor(file.getVirtualFile());
-    setCaretPosition(editor, 0, "def function(arg1, *".length());
-
-    LookupElement[] completionItems = testFixture.completeBasic();
-    assertThat(completionItems).isNull();
-
-    assertFileContents(file, "def function(arg1, *args");
-  }
-
-  public void testKwargsCompletion() {
-    BuildFile file = createBuildFile(
-      "BUILD",
-      "def function(arg1, **");
-
-    Editor editor = openFileInEditor(file.getVirtualFile());
-    setCaretPosition(editor, 0, "def function(arg1, **".length());
-
-    LookupElement[] completionItems = testFixture.completeBasic();
-    assertThat(completionItems).isNull();
-
-    assertFileContents(file, "def function(arg1, **kwargs");
-  }
-
-}
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/RuleTargetCompletionTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/RuleTargetCompletionTest.java
deleted file mode 100644
index a2e8599..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/RuleTargetCompletionTest.java
+++ /dev/null
@@ -1,150 +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.completion;
-
-import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
-import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
-import com.intellij.codeInsight.lookup.LookupElement;
-import com.intellij.openapi.editor.Editor;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Tests code completion of rule target labels.
- */
-public class RuleTargetCompletionTest extends BuildFileIntegrationTestCase {
-
-  public void testLocalTarget() {
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "java_library(name = 'lib')",
-      "java_library(",
-      "    name = 'test',",
-      "    deps = [':']");
-
-    Editor editor = openFileInEditor(file);
-    setCaretPosition(editor, 3, "    deps = [':".length());
-
-    LookupElement[] completionItems = testFixture.completeBasic();
-    assertThat(completionItems).hasLength(1);
-    assertThat(completionItems[0].toString()).isEqualTo("':lib'");
-  }
-
-  public void testIgnoreContainingTarget() {
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "java_library(",
-      "    name = 'lib',",
-      "    deps = [':']");
-
-    Editor editor = openFileInEditor(file);
-    setCaretPosition(editor, 2, "    deps = [':".length());
-
-    LookupElement[] completionItems = testFixture.completeBasic();
-    assertThat(completionItems).isEmpty();
-  }
-
-  public void testNotCodeCompletionInNameField() {
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "java_library(name = 'lib')",
-      "java_library(",
-      "    name = 'l'",
-      ")");
-
-    Editor editor = openFileInEditor(file);
-    setCaretPosition(editor, 2, "    name = 'l".length());
-
-    String[] completionItems = getCompletionItemsAsStrings();
-    assertThat(completionItems).isEmpty();
-  }
-
-  public void testNonLocalTarget() {
-    BuildFile foo = createBuildFile(
-      "java/com/google/foo/BUILD",
-      "java_library(name = 'foo_lib')");
-
-    BuildFile bar = createBuildFile(
-      "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());
-
-    String[] completionItems = getCompletionItemsAsStrings();
-    assertThat(completionItems).asList().containsExactly("'//java/com/google/foo:foo_lib'");
-  }
-
-  public void testNonLocalRulesNotCompletedWithoutColon() {
-    BuildFile foo = createBuildFile(
-      "java/com/google/foo/BUILD",
-      "java_library(name = 'foo_lib')");
-
-    BuildFile bar = createBuildFile(
-      "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());
-
-    String[] completionItems = getCompletionItemsAsStrings();
-    assertThat(completionItems).isEmpty();
-  }
-
-  public void testPackageLocalRulesCompletedWithoutColon() {
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "java_library(name = 'lib')",
-      "java_library(",
-      "    name = 'test',",
-      "    deps = ['']");
-
-    Editor editor = openFileInEditor(file);
-    setCaretPosition(editor, 3, "    deps = ['".length());
-
-    assertThat(completeIfUnique()).isTrue();
-    assertFileContents(
-      file,
-      "java_library(name = 'lib')",
-      "java_library(",
-      "    name = 'test',",
-      "    deps = ['lib']");
-  }
-
-  public void testLocalPathIgnoredForNonLocalLabels() {
-    BuildFile rootPackage = createBuildFile(
-      "java/BUILD",
-      "java_library(name = 'root_rule')");
-
-    BuildFile otherPackage = createBuildFile(
-      "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());
-
-    String[] completionItems = getCompletionItemsAsStrings();
-    assertThat(completionItems).asList().contains("'//java:root_rule'");
-    assertThat(completionItems).asList().doesNotContain("'//java/com/google:other_rule'");
-  }
-}
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/SkylarkExtensionCompletionTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/SkylarkExtensionCompletionTest.java
deleted file mode 100644
index 01bac37..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/SkylarkExtensionCompletionTest.java
+++ /dev/null
@@ -1,164 +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.completion;
-
-import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
-import com.intellij.openapi.vfs.VirtualFile;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Tests auto-complete of skylark bzl files in 'load' statements.
- */
-public class SkylarkExtensionCompletionTest extends BuildFileIntegrationTestCase {
-
-  private VirtualFile createAndSetCaret(String filePath, String... fileContents) {
-    VirtualFile file = createFile(filePath, fileContents);
-    testFixture.configureFromExistingVirtualFile(file);
-    return file;
-  }
-
-  public void testSimpleCase() {
-    createFile("skylark.bzl");
-    VirtualFile file = createAndSetCaret(
-      "BUILD",
-      "load(':<caret>'");
-
-    assertThat(completeIfUnique()).isTrue();
-    assertFileContents(file, "load(':skylark.bzl'");
-  }
-
-  public void testSelfNotInResults() {
-    createFile("BUILD");
-    VirtualFile file = createAndSetCaret(
-      "self.bzl",
-      "load(':<caret>'");
-
-    assertThat(testFixture.completeBasic()).isEmpty();
-  }
-
-  public void testSelfNotInResults2() {
-    createFile("skylark.bzl");
-    createFile("BUILD");
-    VirtualFile file = createAndSetCaret(
-      "self.bzl",
-      "load(':<caret>'");
-
-    assertThat(completeIfUnique()).isTrue();
-    assertFileContents(file, "load(':skylark.bzl'");
-  }
-
-  public void testNoRulesInResults() {
-    createFile("java/com/google/foo/skylark.bzl");
-    createFile(
-      "java/com/google/foo/BUILD",
-      "java_library(name = 'foo')");
-    VirtualFile file = createAndSetCaret(
-      "java/com/google/bar/BUILD",
-      "load('//java/com/google/foo:<caret>'");
-
-    assertThat(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>'");
-
-    assertThat(completeIfUnique()).isTrue();
-    assertFileContents(file, "'//java/com/google/foo:foo'");
-  }
-
-  public void testNonSkylarkFilesNotInResults() {
-    createFile("java/com/google/foo/text.txt");
-
-    VirtualFile file = createAndSetCaret(
-      "java/com/google/bar/BUILD",
-      "load('//java/com/google/foo:<caret>'");
-
-    assertThat(testFixture.completeBasic()).isEmpty();
-  }
-
-  public void testLabelStartsWithColon() {
-    createFile("java/com/google/skylark.bzl");
-    VirtualFile file = createAndSetCaret(
-      "java/com/google/BUILD",
-      "load(':<caret>'");
-
-    assertThat(completeIfUnique()).isTrue();
-    assertFileContents(file, "load(':skylark.bzl'");
-  }
-
-  public void testLabelStartsWithSlashes() {
-    createFile("java/com/google/skylark.bzl");
-    VirtualFile file = createAndSetCaret(
-      "java/com/google/BUILD",
-      "load('//java/com/google:<caret>'");
-
-    assertThat(completeIfUnique()).isTrue();
-    assertFileContents(file, "load('//java/com/google:skylark.bzl'");
-  }
-
-  public void testLabelStartsWithSlashesWithoutColon() {
-    createFile("java/com/google/skylark.bzl");
-    VirtualFile file = createAndSetCaret(
-      "java/com/google/BUILD",
-      "load('//java/com/google<caret>'");
-
-    assertThat(completeIfUnique()).isTrue();
-    assertFileContents(file, "load('//java/com/google:skylark.bzl'");
-  }
-
-  public void testDirectoryCompletionInLoadStatement() {
-    createFile("java/com/google/skylark.bzl");
-    VirtualFile file = createAndSetCaret(
-      "java/com/google/BUILD",
-      "load('//<caret>'");
-
-    assertThat(completeIfUnique()).isTrue();
-    assertFileContents(file, "load('//java/com/google'");
-
-    assertThat(completeIfUnique()).isTrue();
-    assertFileContents(file, "load('//java/com/google:skylark.bzl'");
-  }
-
-  public void testMultipleFiles() {
-    createFile("java/com/google/skylark.bzl");
-    createFile("java/com/google/other.bzl");
-    VirtualFile file = createAndSetCaret(
-      "java/com/google/BUILD",
-      "load('//java/com/google:<caret>'");
-
-    String[] strings = getCompletionItemsAsStrings();
-    assertThat(strings).hasLength(2);
-    assertThat(strings).asList().containsExactly("'//java/com/google:other.bzl'", "'//java/com/google:skylark.bzl'");
-  }
-
-  // relative paths in skylark extensions which lie in subdirectories are relative to the parent blaze package directory
-  public void testRelativePathInSubdirectory() {
-    createFile("java/com/google/BUILD");
-    createFile(
-      "java/com/google/nonPackageSubdirectory/skylark.bzl",
-      "def function(): return");
-    VirtualFile file = createAndSetCaret(
-      "java/com/google/nonPackageSubdirectory/other.bzl",
-      "load(':n<caret>'");
-
-    assertThat(completeIfUnique()).isTrue();
-    assertFileContents(file, "load(':nonPackageSubdirectory/skylark.bzl'");
-  }
-
-}
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/SkylarkExtensionSymbolCompletionTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/SkylarkExtensionSymbolCompletionTest.java
deleted file mode 100644
index c5870e5..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/SkylarkExtensionSymbolCompletionTest.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.lang.buildfile.completion;
-
-import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
-import com.intellij.openapi.vfs.VirtualFile;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Tests auto-complete of symbols loaded from skylark bzl files.
- */
-public class SkylarkExtensionSymbolCompletionTest extends BuildFileIntegrationTestCase {
-
-  private VirtualFile createAndSetCaret(String filePath, String... fileContents) {
-    VirtualFile file = createFile(filePath, fileContents);
-    testFixture.configureFromExistingVirtualFile(file);
-    return file;
-  }
-
-  public void testGlobalVariable() {
-    createFile(
-      "skylark.bzl",
-      "VAR = []");
-    VirtualFile file = createAndSetCaret(
-      "BUILD",
-      "load(':skylark.bzl', '<caret>')");
-
-    assertThat(completeIfUnique()).isTrue();
-    assertFileContents(file, "load(':skylark.bzl', 'VAR')");
-  }
-
-  public void testFunctionStatement() {
-    createFile(
-      "skylark.bzl",
-      "def fn(param):stmt");
-    VirtualFile file = createAndSetCaret(
-      "BUILD",
-      "load(':skylark.bzl', '<caret>')");
-
-    assertThat(completeIfUnique()).isTrue();
-    assertFileContents(file, "load(':skylark.bzl', 'fn')");
-  }
-
-  public void testMultipleOptions() {
-    createFile(
-      "skylark.bzl",
-      "def fn(param):stmt",
-      "VAR = []");
-    VirtualFile file = createAndSetCaret(
-      "BUILD",
-      "load(':skylark.bzl', '<caret>')");
-
-    String[] options = getCompletionItemsAsStrings();
-    assertThat(options).asList().containsExactly("'fn'", "'VAR'");
-  }
-
-  public void testRulesNotIncluded() {
-    createFile(
-      "skylark.bzl",
-      "java_library(name = 'lib')",
-      "native.java_library(name = 'foo'");
-
-    VirtualFile file = createAndSetCaret(
-      "BUILD",
-      "load(':skylark.bzl', '<caret>')");
-
-    assertThat(testFixture.completeBasic()).isEmpty();
-  }
-
-  public void testLoadedSymbols() {
-    createFile(
-      "other.bzl",
-      "def function()");
-    createFile(
-      "skylark.bzl",
-      "load(':other.bzl', 'function')");
-    VirtualFile file = createAndSetCaret(
-      "BUILD",
-      "load(':skylark.bzl', '<caret>')");
-
-    assertThat(completeIfUnique()).isTrue();
-    assertFileContents(file, "load(':skylark.bzl', 'function')");
-  }
-
-  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>')");
-
-    assertThat(completeIfUnique()).isTrue();
-    assertFileContents(file, "load(':skylark.bzl', 'function')");
-  }
-
-}
\ No newline at end of file
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/editor/BuildBraceMatcherTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/editor/BuildBraceMatcherTest.java
deleted file mode 100644
index 7f2544c..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/editor/BuildBraceMatcherTest.java
+++ /dev/null
@@ -1,140 +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.editor;
-
-import com.google.common.base.Joiner;
-import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
-import com.intellij.psi.PsiFile;
-
-/**
- * Test brace matching (auto-inserting closing braces when appropriate)
- */
-public class BuildBraceMatcherTest extends BuildFileIntegrationTestCase {
-
-  private PsiFile setInput(String... fileContents) {
-    return testFixture.configureByText("BUILD", Joiner.on("\n").join(fileContents));
-  }
-
-  public void testClosingParenInserted() {
-    PsiFile file = setInput(
-      "java_library<caret>"
-    );
-
-    performTypingAction(testFixture.getEditor(), '(');
-
-    assertFileContents(
-      file,
-      "java_library()"
-    );
-  }
-
-  public void testClosingBraceInserted() {
-    PsiFile file = setInput(
-      "<caret>"
-    );
-
-    performTypingAction(testFixture.getEditor(), '{');
-
-    assertFileContents(
-      file,
-      "{}"
-    );
-  }
-
-
-  public void testClosingBracketInserted() {
-    PsiFile file = setInput(
-      "<caret>"
-    );
-
-    performTypingAction(testFixture.getEditor(), '[');
-
-    assertFileContents(
-      file,
-      "[]"
-    );
-  }
-
-  public void testNoClosingBracketInsertedIfLaterDanglingRBracket() {
-    PsiFile file = setInput(
-      "java_library(",
-      "    srcs =<caret> 'source.java']",
-      ")"
-    );
-
-    performTypingAction(testFixture.getEditor(), '[');
-
-    assertFileContents(
-      file,
-      "java_library(",
-      "    srcs =[ 'source.java']",
-      ")"
-    );
-  }
-
-  public void testClosingBracketInsertedIfFollowedByWhitespace() {
-    PsiFile file = setInput(
-      "java_library(",
-      "    srcs =<caret> 'source.java'",
-      ")"
-    );
-
-    performTypingAction(testFixture.getEditor(), '[');
-
-    assertFileContents(
-      file,
-      "java_library(",
-      "    srcs =[] 'source.java'",
-      ")"
-    );
-  }
-
-  public void testNoClosingBraceInsertedWhenFollowedByIdentifier() {
-    PsiFile file = setInput(
-      "hello = <caret>test"
-    );
-
-    performTypingAction(testFixture.getEditor(), '(');
-
-    assertFileContents(
-      file,
-      "hello = (test"
-    );
-
-    file = setInput(
-      "hello = <caret>test"
-    );
-
-    performTypingAction(testFixture.getEditor(), '[');
-
-    assertFileContents(
-      file,
-      "hello = [test"
-    );
-
-    file = setInput(
-      "hello = <caret>test"
-    );
-
-    performTypingAction(testFixture.getEditor(), '{');
-
-    assertFileContents(
-      file,
-      "hello = {test"
-    );
-  }
-
-}
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/editor/BuildIndentOnEnterTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/editor/BuildIndentOnEnterTest.java
deleted file mode 100644
index 06f5c70..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/editor/BuildIndentOnEnterTest.java
+++ /dev/null
@@ -1,485 +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.editor;
-
-import com.google.common.base.Joiner;
-import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
-import com.intellij.openapi.actionSystem.IdeActions;
-
-/**
- * Tests that indents are inserted correctly when enter is pressed.
- */
-public class BuildIndentOnEnterTest extends BuildFileIntegrationTestCase {
-
-  private void setInput(String... fileContents) {
-    testFixture.configureByText("BUILD", Joiner.on("\n").join(fileContents));
-  }
-
-  private void pressEnterAndAssertResult(String... resultingFileContents) {
-    pressButton(IdeActions.ACTION_EDITOR_ENTER);
-    String s = testFixture.getFile().getText();
-    testFixture.checkResult(Joiner.on("\n").join(resultingFileContents));
-  }
-
-  public void testSimpleIndent() {
-    setInput(
-      "a=1<caret>");
-    pressEnterAndAssertResult(
-      "a=1",
-      "<caret>");
-  }
-
-  public void testAlignInListMiddle() {
-    setInput(
-      "target = [a,<caret>",
-      "          c]");
-    pressEnterAndAssertResult(
-      "target = [a,",
-      "          <caret>",
-      "          c]");
-  }
-
-  public void testNoAlignAfterList() {
-    setInput(
-      "target = [",
-      "    arg",
-      "]<caret>");
-    pressEnterAndAssertResult(
-      "target = [",
-      "    arg",
-      "]",
-      "<caret>");
-  }
-
-  public void testAlignInDict() {
-    setInput(
-      "some_call({'aaa': 'v1',<caret>})");
-    pressEnterAndAssertResult(
-      "some_call({'aaa': 'v1',",
-      "           <caret>})");
-  }
-
-  public void testAlignInDictInParams() {  // PY-1947
-    setInput(
-      "foobar({<caret>})");
-    pressEnterAndAssertResult(
-      "foobar({",
-      "    <caret>",
-      "})");
-  }
-
-  public void testAlignInEmptyList() {
-    setInput(
-      "target = [<caret>]");
-    pressEnterAndAssertResult(
-      "target = [",
-      "    <caret>",
-      "]");
-  }
-
-  public void testAlignInEmptyParens() {
-    setInput(
-      "foo(<caret>)");
-    pressEnterAndAssertResult(
-      "foo(",
-      "    <caret>",
-      ")");
-  }
-
-  public void testAlignInEmptyDict() {
-    setInput(
-      "{<caret>}");
-    pressEnterAndAssertResult(
-      "{",
-      "    <caret>",
-      "}");
-  }
-
-  public void testAlignInEmptyTuple() {
-    setInput(
-      "(<caret>)");
-    pressEnterAndAssertResult(
-      "(",
-      "    <caret>",
-      ")");
-  }
-
-  public void testEnterInNonEmptyArgList() {
-    setInput(
-      "func(<caret>params=1)");
-    pressEnterAndAssertResult(
-      "func(",
-      "    <caret>params=1)");
-  }
-
-  public void testEmptyFuncallStart() {
-    setInput(
-      "func(<caret>",
-      ")");
-    pressEnterAndAssertResult(
-      "func(",
-      "    <caret>",
-      ")");
-  }
-
-  public void testEmptyFuncallAfterNewlineNoIndent() {
-    setInput(
-      "func(",
-      "<caret>)");
-    pressEnterAndAssertResult(
-      "func(",
-      "",
-      "<caret>)");
-  }
-
-  public void testEmptyFuncallAfterNewlineWithIndent() {
-    setInput(
-      "func(",
-      "    <caret>",
-      ")");
-    pressEnterAndAssertResult(
-      "func(",
-      "    ",
-      "    <caret>",
-      ")");
-  }
-
-  public void testFuncallAfterFirstArg() {
-    setInput(
-      "func(",
-      "    arg1,<caret>",
-      ")");
-    pressEnterAndAssertResult(
-      "func(",
-      "    arg1,",
-      "    <caret>",
-      ")");
-  }
-
-  public void testFuncallFirstArgOnSameLine() {
-    setInput(
-      "func(arg1, arg2,<caret>");
-    pressEnterAndAssertResult(
-      "func(arg1, arg2,",
-      "     <caret>");
-  }
-
-  public void testFuncallFirstArgOnSameLineWithClosingBrace() {
-    setInput(
-      "func(arg1, arg2,<caret>)");
-    pressEnterAndAssertResult(
-      "func(arg1, arg2,",
-      "     <caret>)");
-  }
-
-  public void testNonEmptyDict() {
-    setInput(
-      "{key1 : value1,<caret>}");
-    pressEnterAndAssertResult(
-      "{key1 : value1,",
-      " <caret>}");
-  }
-
-  public void testNonEmptyDictFirstArgIndented() {
-    setInput(
-      "{",
-      "    key1 : value1,<caret>" +
-      "}");
-    pressEnterAndAssertResult(
-      "{",
-      "    key1 : value1,",
-      "    <caret>" +
-      "}");
-  }
-
-  public void testEmptyDictAlreadyIndented() {
-    setInput(
-      "{",
-      "    <caret>" +
-      "}");
-    pressEnterAndAssertResult(
-      "{",
-      "    ",
-      "    <caret>" +
-      "}");
-  }
-
-  public void testEmptyParamIndent() {
-    setInput(
-      "def fn(<caret>)");
-    pressEnterAndAssertResult(
-      "def fn(",
-      "    <caret>",
-      ")");
-  }
-
-  public void testNonEmptyParamIndent() {
-    setInput(
-      "def fn(param1,<caret>)");
-    pressEnterAndAssertResult(
-      "def fn(param1,",
-      "       <caret>)");
-  }
-
-  public void testFunctionDefAfterColon() {
-    setInput(
-      "def fn():<caret>");
-    pressEnterAndAssertResult(
-      "def fn():",
-      "  <caret>");
-  }
-
-  // def fn():stmt* (THIS IS CURRENTLY BROKEN -- shouldn't indent but does)
-  public void testFunctionDefSingleStatement() {
-    setInput(
-      "def fn():stmt<caret>");
-    pressEnterAndAssertResult(
-      "def fn():stmt",
-      "<caret>");
-  }
-
-  public void testFunctionDefAfterFirstSuiteStatement() {
-    setInput(
-      "def fn():",
-      "  stmt1<caret>");
-    pressEnterAndAssertResult(
-      "def fn():",
-      "  stmt1",
-      "  <caret>");
-  }
-
-  public void testNoIndentAfterSuiteDedentOnEmptyLine() {
-    setInput(
-      "def fn():",
-      "  stmt1",
-      "  stmt2",
-      "<caret>");
-    pressEnterAndAssertResult(
-      "def fn():",
-      "  stmt1",
-      "  stmt2",
-      "",
-      "<caret>");
-  }
-
-  public void testIndentAfterIf() {
-    setInput(
-      "if condition:<caret>"
-    );
-    pressEnterAndAssertResult(
-      "if condition:",
-      "  <caret>"
-    );
-  }
-
-  public void testNoIndentAfterIfPlusStatement() {
-    setInput(
-      "if condition:stmt<caret>"
-    );
-    pressEnterAndAssertResult(
-      "if condition:stmt",
-      "<caret>"
-    );
-  }
-
-  public void testIndentAfterElseIf() {
-    setInput(
-      "if condition:",
-      "  stmt",
-      "elif:<caret>"
-    );
-    pressEnterAndAssertResult(
-      "if condition:",
-      "  stmt",
-      "elif:",
-      "  <caret>"
-    );
-  }
-
-  public void testNoIndentAfterElseIfPlusStatement() {
-    setInput(
-      "if condition:",
-      "  stmt",
-      "elif:stmt<caret>"
-    );
-    pressEnterAndAssertResult(
-      "if condition:",
-      "  stmt",
-      "elif:stmt",
-      "<caret>"
-    );
-  }
-
-  public void testIndentAfterElse() {
-    setInput(
-      "if condition:",
-      "  stmt",
-      "else:<caret>"
-    );
-    pressEnterAndAssertResult(
-      "if condition:",
-      "  stmt",
-      "else:",
-      "  <caret>"
-    );
-  }
-
-  public void testNoIndentAfterElsePlusStatement() {
-    setInput(
-      "if condition:",
-      "  stmt",
-      "else:stmt<caret>"
-    );
-    pressEnterAndAssertResult(
-      "if condition:",
-      "  stmt",
-      "else:stmt",
-      "<caret>"
-    );
-  }
-
-  public void testIndentAfterForColon() {
-    setInput(
-      "for x in list:<caret>"
-    );
-    pressEnterAndAssertResult(
-      "for x in list:",
-      "  <caret>"
-    );
-  }
-
-  public void testNoIndentAfterForPlusStatement() {
-    setInput(
-      "for x in list:do_action<caret>"
-    );
-    pressEnterAndAssertResult(
-      "for x in list:do_action",
-      "<caret>"
-    );
-  }
-
-  public void testCommonRuleCase1() {
-    setInput(
-      "java_library(",
-      "    name = 'lib'",
-      "    srcs = [<caret>]");
-    pressEnterAndAssertResult(
-      "java_library(",
-      "    name = 'lib'",
-      "    srcs = [",
-      "        <caret>",
-      "    ]");
-  }
-
-  public void testCommonRuleCase2() {
-    setInput(
-      "java_library(",
-      "    name = 'lib'",
-      "    srcs = [",
-      "        'source',<caret>",
-      "    ]");
-    pressEnterAndAssertResult(
-      "java_library(",
-      "    name = 'lib'",
-      "    srcs = [",
-      "        'source',",
-      "        <caret>",
-      "    ]");
-  }
-
-  public void testCommonRuleCase3() {
-    setInput(
-      "java_library(",
-      "    name = 'lib'",
-      "    srcs = ['first',<caret>]");
-    pressEnterAndAssertResult(
-      "java_library(",
-      "    name = 'lib'",
-      "    srcs = ['first',",
-      "            <caret>]");
-  }
-
-  public void testDedentAfterReturn() {
-    setInput(
-      "def fn():",
-      "  return None<caret>");
-    pressEnterAndAssertResult(
-      "def fn():",
-      "  return None",
-      "<caret>");
-  }
-
-  public void testDedentAfterEmptyReturn() {
-    setInput(
-      "def fn():",
-      "  return<caret>");
-    pressEnterAndAssertResult(
-      "def fn():",
-      "  return",
-      "<caret>");
-  }
-
-  public void testDedentAfterReturnWithTrailingWhitespace() {
-    setInput(
-      "def fn():",
-      "  return<caret>   ");
-    pressEnterAndAssertResult(
-      "def fn():",
-      "  return",
-      "<caret>");
-  }
-
-  public void testDedentAfterComplexReturn() {
-    setInput(
-      "def fn():",
-      "  return a == b<caret>");
-    pressEnterAndAssertResult(
-      "def fn():",
-      "  return a == b",
-      "<caret>");
-  }
-
-  public void testDedentAfterPass() {
-    setInput(
-      "def fn():",
-      "  pass<caret>");
-    pressEnterAndAssertResult(
-      "def fn():",
-      "  pass",
-      "<caret>");
-  }
-
-  public void testDedentAfterPassInLoop() {
-    setInput(
-      "def fn():",
-      "  for a in (1,2,3):",
-      "    pass<caret>");
-    pressEnterAndAssertResult(
-      "def fn():",
-      "  for a in (1,2,3):",
-      "    pass",
-      "  <caret>");
-  }
-
-  // regression test for b/29564041
-  public void testNoExceptionPressingEnterAtStartOfFile() {
-    setInput("#<caret>");
-    pressEnterAndAssertResult(
-      "#",
-      "<caret>");
-  }
-
-}
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/editor/BuildQuoteHandlerTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/editor/BuildQuoteHandlerTest.java
deleted file mode 100644
index 77d45ee..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/editor/BuildQuoteHandlerTest.java
+++ /dev/null
@@ -1,137 +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.editor;
-
-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;
-
-/**
- * Tests for BuildQuoteHandler.
- */
-public class BuildQuoteHandlerTest extends BuildFileIntegrationTestCase {
-
-  public void testClosingQuoteInserted() {
-    BuildFile file = createBuildFile("BUILD", "");
-
-    performTypingAction(file, '"');
-    assertFileContents(file, "\"\"");
-  }
-
-  public void testClosingSingleQuoteInserted() {
-    BuildFile file = createBuildFile("BUILD", "");
-
-    performTypingAction(file, '\'');
-    assertFileContents(file, "''");
-  }
-
-  public void testClosingTripleQuoteInserted() {
-    BuildFile file = createBuildFile("BUILD", "");
-
-    performTypingAction(file, '"');
-    performTypingAction(file, '"');
-    performTypingAction(file, '"');
-    assertFileContents(file, "\"\"\"\"\"\"");
-  }
-
-  public void testClosingTripleSingleQuoteInserted() {
-    BuildFile file = createBuildFile("BUILD", "");
-
-    performTypingAction(file, '\'');
-    performTypingAction(file, '\'');
-    performTypingAction(file, '\'');
-    assertFileContents(file, "''''''");
-  }
-
-  public void testOnlyCaretMovedWhenCompletingExistingClosingQuotes() {
-    BuildFile file = createBuildFile(
-      "BUILD",
-      "'text<caret>'",
-      "laterContents");
-
-    testFixture.configureFromExistingVirtualFile(file.getVirtualFile());
-
-    performTypingAction(file, '\'');
-
-    testFixture.checkResult(Joiner.on("\n").join(
-      "'text'<caret>",
-      "laterContents"
-    ));
-  }
-
-  public void testOnlyCaretMovedWhenCompletingExistingClosingTripleQuotes() {
-    BuildFile file = createBuildFile(
-      "BUILD",
-      "'''text<caret>'''",
-      "laterContents");
-
-    testFixture.configureFromExistingVirtualFile(file.getVirtualFile());
-
-    performTypingAction(file, '\'');
-
-    testFixture.checkResult(Joiner.on("\n").join(
-      "'''text'<caret>''",
-      "laterContents"
-    ));
-
-    performTypingAction(file, '\'');
-
-    testFixture.checkResult(Joiner.on("\n").join(
-      "'''text''<caret>'",
-      "laterContents"
-    ));
-
-    performTypingAction(file, '\'');
-
-    testFixture.checkResult(Joiner.on("\n").join(
-      "'''text'''<caret>",
-      "laterContents"
-    ));
-  }
-
-  public void testAdditionalTripleQuotesNotInsertedWhenClosingQuotes() {
-    BuildFile file = createBuildFile(
-      "BUILD",
-      "'''text''<caret>",
-      "laterContents");
-
-    testFixture.configureFromExistingVirtualFile(file.getVirtualFile());
-
-    performTypingAction(file, '\'');
-
-    testFixture.checkResult(Joiner.on("\n").join(
-      "'''text'''<caret>",
-      "laterContents"
-    ));
-  }
-
-  public void testAdditionalQuoteNotInsertedWhenClosingQuotes() {
-    BuildFile file = createBuildFile(
-      "BUILD",
-      "'text<caret>",
-      "laterContents");
-
-    testFixture.configureFromExistingVirtualFile(file.getVirtualFile());
-
-    performTypingAction(file, '\'');
-
-    testFixture.checkResult(Joiner.on("\n").join(
-      "'text'<caret>",
-      "laterContents"
-    ));
-  }
-
-}
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/editor/EnterInLineCommentTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/editor/EnterInLineCommentTest.java
deleted file mode 100644
index a48dbf1..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/editor/EnterInLineCommentTest.java
+++ /dev/null
@@ -1,60 +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.editor;
-
-import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
-import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
-import com.intellij.openapi.editor.Editor;
-
-/**
- * Test that comments are continued when creating a newline mid comment.
- */
-public class EnterInLineCommentTest extends BuildFileIntegrationTestCase {
-
-  public void testInternalNewlineCommented() {
-    BuildFile file = createBuildFile(
-      "BUILD",
-      "# first line comment",
-      "# second line comment");
-
-    Editor editor = openFileInEditor(file.getVirtualFile());
-    setCaretPosition(editor, 1, "# second ".length());
-    performTypingAction(editor, '\n');
-    assertFileContents(
-      file,
-      "# first line comment",
-      "# second ",
-      "# line comment");
-    assertCaretPosition(editor, 2, 2);
-  }
-
-  public void testNewlineAtEndOfComment() {
-    BuildFile file = createBuildFile(
-      "BUILD",
-      "# first line comment",
-      "# second line comment");
-
-    Editor editor = openFileInEditor(file.getVirtualFile());
-    setCaretPosition(editor, 1, "# second line comment".length());
-    performTypingAction(editor, '\n');
-    assertFileContents(
-      file,
-      "# first line comment",
-      "# second line comment",
-      "");
-    assertCaretPosition(editor, 2, 0);
-  }
-}
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/BlazePackageFindUsagesTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/BlazePackageFindUsagesTest.java
deleted file mode 100644
index b212f9b..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/BlazePackageFindUsagesTest.java
+++ /dev/null
@@ -1,79 +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.findusages;
-
-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.StringLiteral;
-import com.google.idea.blaze.base.lang.buildfile.search.FindUsages;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiReference;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Tests that all references to a blaze package (including in the package components of labels)
- * are found by the 'Find Usages' action.
- */
-public class BlazePackageFindUsagesTest extends BuildFileIntegrationTestCase {
-
-  public void testDirectReferenceFound() {
-    BuildFile foo = createBuildFile(
-      "java/com/google/foo/BUILD");
-
-    BuildFile bar = createBuildFile(
-      "java/com/google/bar/BUILD",
-      "package_group(name = \"grp\", packages = [\"//java/com/google/foo\"])");
-
-    PsiReference[] references = FindUsages.findAllReferences(foo);
-    assertThat(references).hasLength(1);
-
-    PsiElement ref = references[0].getElement();
-    assertThat(ref).isInstanceOf(StringLiteral.class);
-    assertThat(ref.getContainingFile()).isEqualTo(bar);
-  }
-
-  public void testLabelFragmentReferenceFound() {
-    BuildFile foo = createBuildFile(
-      "java/com/google/foo/BUILD",
-      "java_library(name = \"lib\")");
-
-    BuildFile bar = createBuildFile(
-      "java/com/google/bar/BUILD",
-      "java_library(name = \"lib2\", exports = [\"//java/com/google/foo:lib\"])");
-
-    PsiReference[] references = FindUsages.findAllReferences(foo);
-    assertThat(references).hasLength(1);
-
-    PsiElement ref = references[0].getElement();
-    assertThat(ref).isInstanceOf(StringLiteral.class);
-    assertThat(ref.getContainingFile()).isEqualTo(bar);
-  }
-
-  /**
-   * If these don't resolve, directory rename refactoring won't update all labels correctly
-   */
-  public void testInternalReferencesResolve() {
-    BuildFile buildFile = createBuildFile(
-      "java/com/google/BUILD",
-      "java_library(name = \"lib\")",
-      "java_library(name = \"other\", deps = [\"//java/com/google:lib\"])");
-
-    PsiReference[] references = FindUsages.findAllReferences(buildFile);
-    assertThat(references).hasLength(1);
-  }
-
-}
\ No newline at end of file
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/ExternalFileUsagesTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/ExternalFileUsagesTest.java
deleted file mode 100644
index a83b080..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/ExternalFileUsagesTest.java
+++ /dev/null
@@ -1,142 +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.findusages;
-
-import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
-import com.google.idea.blaze.base.lang.buildfile.psi.Argument;
-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.StringLiteral;
-import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
-import com.google.idea.blaze.base.lang.buildfile.search.FindUsages;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiFile;
-import com.intellij.psi.PsiReference;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Tests that references to external files (e.g. Java classes, text files) are found by the 'Find Usages' action
- */
-public class ExternalFileUsagesTest extends BuildFileIntegrationTestCase {
-
-  public void testJavaClassUsagesFound() {
-    PsiFile javaFile = createPsiFile(
-      "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\"])");
-
-    PsiReference[] references = FindUsages.findAllReferences(javaFile);
-    assertThat(references).hasLength(1);
-
-    Argument.Keyword arg = buildFile.findChildByClass(FuncallExpression.class)
-      .getKeywordArgument("srcs");
-
-    PsiElement ref = references[0].getElement();
-    assertThat(ref).isInstanceOf(StringLiteral.class);
-    assertThat(PsiUtils.getParentOfType(ref, Argument.Keyword.class))
-      .isEqualTo(arg);
-  }
-
-  public void testTextFileUsagesFound() {
-    PsiFile textFile = createPsiFile("com/google/foo/data.txt");
-
-    BuildFile buildFile = createBuildFile(
-      "com/google/foo/BUILD",
-      "filegroup(name = \"lib\", srcs = [\"data.txt\"])",
-      "filegroup(name = \"lib2\", srcs = [\"//com/google/foo:data.txt\"])");
-
-    PsiReference[] references = FindUsages.findAllReferences(textFile);
-    assertThat(references).hasLength(2);
-  }
-
-  public void testInvalidReferenceDoesntResolve() {
-    BuildFile packageFoo = createBuildFile("com/google/foo/BUILD");
-    PsiFile textFileInFoo = createPsiFile("com/google/foo/data.txt");
-
-    BuildFile packageBar = createBuildFile(
-      "com/google/bar/BUILD",
-      "filegroup(name = \"lib\", srcs = [\":data.txt\"])");
-
-    PsiReference[] references = FindUsages.findAllReferences(textFileInFoo);
-    assertThat(references).isEmpty();
-  }
-
-  public void testSkylarkExtensionUsagesFound() {
-    BuildFile ext = createBuildFile(
-      "com/google/foo/ext.bzl",
-      "def fn(): return");
-    createBuildFile(
-      "com/google/foo/BUILD",
-      "load(':ext.bzl', 'fn')",
-      "load('ext.bzl', 'fn')",
-      "load('//com/google/foo:ext.bzl', 'fn')"
-    );
-
-    PsiReference[] references = FindUsages.findAllReferences(ext);
-    assertThat(references).hasLength(3);
-  }
-
-  public void testSkylarkExtensionInSubDirectoryUsagesFound() {
-    BuildFile ext = createBuildFile(
-      "com/google/foo/subdir/ext.bzl",
-      "def fn(): return");
-    createBuildFile(
-      "com/google/foo/BUILD",
-      "load(':subdir/ext.bzl', 'fn')",
-      "load('subdir/ext.bzl', 'fn')",
-      "load('//com/google/foo:subdir/ext.bzl', 'fn')"
-    );
-
-    PsiReference[] references = FindUsages.findAllReferences(ext);
-    assertThat(references).hasLength(3);
-  }
-
-  public void testSkylarkExtensionInSubDirectoryOfDifferentPackage() {
-    BuildFile otherPkg = createBuildFile(
-      "com/google/foo/BUILD");
-    BuildFile ext = createBuildFile(
-      "com/google/foo/subdir/ext.bzl",
-      "def fn(): return");
-
-    createBuildFile(
-      "com/google/bar/BUILD",
-      "load('//com/google/foo:subdir/ext.bzl', 'fn')"
-    );
-
-    PsiReference[] references = FindUsages.findAllReferences(ext);
-    assertThat(references).hasLength(1);
-  }
-
-  public void testSkylarkExtensionReferencedFromSubpackage() {
-    BuildFile pkg = createBuildFile(
-      "com/google/foo/BUILD");
-    BuildFile ext1 = createBuildFile(
-      "com/google/foo/subdir/testing.bzl",
-      "def fn(): return");
-    BuildFile ext2 = createBuildFile(
-      "com/google/foo/subdir/other.bzl",
-      "load(':subdir/testing.bzl', 'fn')");
-
-    PsiReference[] references = FindUsages.findAllReferences(ext1);
-    assertThat(references).hasLength(1);
-  }
-
-}
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/FindParameterUsagesTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/FindParameterUsagesTest.java
deleted file mode 100644
index a9a3ae8..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/FindParameterUsagesTest.java
+++ /dev/null
@@ -1,69 +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.findusages;
-
-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.FunctionStatement;
-import com.google.idea.blaze.base.lang.buildfile.psi.ParameterList;
-import com.google.idea.blaze.base.lang.buildfile.search.FindUsages;
-import com.intellij.psi.PsiReference;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Tests that usages of function parameters (i.e. by named args in funcall expressions) are found
- */
-public class FindParameterUsagesTest extends BuildFileIntegrationTestCase {
-
-  public void testLocalReferences() {
-    BuildFile buildFile = createBuildFile(
-      "java/com/google/build_defs.bzl",
-      "def function(arg1, arg2)",
-      "function(arg1 = 1, arg2 = \"name\")");
-
-    FunctionStatement fn = buildFile.findChildByClass(FunctionStatement.class);
-    ParameterList params = fn.getParameterList();
-
-    PsiReference[] references = FindUsages.findAllReferences(params.findParameterByName("arg1"));
-    assertThat(references).hasLength(1);
-
-    references = FindUsages.findAllReferences(params.findParameterByName("arg2"));
-    assertThat(references).hasLength(1);
-  }
-
-  public void testNonLocalReferences() {
-    BuildFile foo = createBuildFile(
-      "java/com/google/build_defs.bzl",
-      "def function(arg1, arg2)");
-
-    BuildFile bar = createBuildFile(
-      "java/com/google/other/BUILD",
-      "load(\"//java/com/google:build_defs.bzl\", \"function\")",
-      "function(arg1 = 1, arg2 = \"name\", extra = x)");
-
-    FunctionStatement fn = foo.findChildByClass(FunctionStatement.class);
-    ParameterList params = fn.getParameterList();
-
-    PsiReference[] references = FindUsages.findAllReferences(params.findParameterByName("arg1"));
-    assertThat(references).hasLength(1);
-    assertThat(references[0].getElement().getContainingFile()).isEqualTo(bar);
-
-    references = FindUsages.findAllReferences(params.findParameterByName("arg2"));
-    assertThat(references).hasLength(1);
-    assertThat(references[0].getElement().getContainingFile()).isEqualTo(bar);
-  }
-}
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/FindRuleUsagesTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/FindRuleUsagesTest.java
deleted file mode 100644
index 3a24bfd..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/FindRuleUsagesTest.java
+++ /dev/null
@@ -1,129 +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.findusages;
-
-import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
-import com.google.idea.blaze.base.lang.buildfile.psi.*;
-import com.google.idea.blaze.base.lang.buildfile.search.FindUsages;
-import com.intellij.codeInsight.navigation.actions.GotoDeclarationAction;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiReference;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Tests that usages of build rules are found
- */
-public class FindRuleUsagesTest extends BuildFileIntegrationTestCase {
-
-  public void testLocalReferences() {
-    BuildFile buildFile = createBuildFile(
-      "java/com/google/BUILD",
-      "java_library(name = \"target\")",
-      "top_level_ref = \":target\"",
-      "java_library(name = \"other\", deps = [\":target\"]");
-
-    FuncallExpression target = buildFile.findChildByClass(FuncallExpression.class);
-
-    PsiReference[] references = FindUsages.findAllReferences(target);
-    assertThat(references).hasLength(2);
-
-    PsiElement firstRef = references[0].getElement();
-    assertThat(firstRef).isInstanceOf(StringLiteral.class);
-    assertThat(firstRef.getParent()).isInstanceOf(AssignmentStatement.class);
-
-    PsiElement secondRef = references[1].getElement();
-    assertThat(secondRef).isInstanceOf(StringLiteral.class);
-    assertThat(secondRef.getParent()).isInstanceOf(ListLiteral.class);
-  }
-
-  // test full package references, made locally
-  public void testLocalFullReference() {
-    BuildFile buildFile = createBuildFile(
-      "java/com/google/BUILD",
-      "java_library(name = \"target\")",
-      "java_library(name = \"other\", deps = [\"//java/com/google:target\"]");
-
-    FuncallExpression target = buildFile.findChildByClass(FuncallExpression.class);
-
-    PsiReference[] references = FindUsages.findAllReferences(target);
-    assertThat(references).hasLength(1);
-
-    PsiElement ref = references[0].getElement();
-    assertThat(ref).isInstanceOf(StringLiteral.class);
-    assertThat(ref.getParent()).isInstanceOf(ListLiteral.class);
-  }
-
-  public void testNonLocalReferences() {
-    BuildFile targetFile = createBuildFile(
-      "java/com/google/foo/BUILD",
-      "java_library(name = \"target\")");
-
-    BuildFile refFile = createBuildFile(
-      "java/com/google/bar/BUILD",
-      "java_library(name = \"ref\", exports = [\"//java/com/google/foo:target\"])");
-
-    FuncallExpression target = targetFile.findChildByClass(FuncallExpression.class);
-
-    PsiReference[] references = FindUsages.findAllReferences(target);
-    assertThat(references).hasLength(1);
-
-    PsiElement ref = references[0].getElement();
-    assertThat(ref).isInstanceOf(StringLiteral.class);
-    assertThat(ref.getContainingFile()).isEqualTo(refFile);
-  }
-
-  public void testFindUsagesWorksFromNameString() {
-    BuildFile targetFile = createBuildFile(
-      "java/com/google/foo/BUILD",
-      "java_library(name = \"tar<caret>get\")");
-
-    BuildFile refFile = createBuildFile(
-      "java/com/google/bar/BUILD",
-      "java_library(name = \"ref\", exports = [\"//java/com/google/foo:target\"])");
-
-    testFixture.configureFromExistingVirtualFile(targetFile.getVirtualFile());
-
-    PsiElement targetElement = GotoDeclarationAction.findElementToShowUsagesOf(
-      testFixture.getEditor(),
-      testFixture.getEditor().getCaretModel().getOffset());
-
-    PsiReference[] references = FindUsages.findAllReferences(targetElement);
-    assertThat(references).hasLength(1);
-
-    PsiElement ref = references[0].getElement();
-    assertThat(ref).isInstanceOf(StringLiteral.class);
-    assertThat(ref.getContainingFile()).isEqualTo(refFile);
-  }
-
-  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\")");
-
-    BuildFile refFile = createBuildFile(
-      "java/com/google/bar/BUILD",
-      "java_library(name = \"ref\", exports = [\":target\"])");
-
-    FuncallExpression target = targetFile.findChildByClass(FuncallExpression.class);
-    assertThat(target).isNotNull();
-
-    PsiReference[] references = FindUsages.findAllReferences(target);
-    assertThat(references).hasLength(0);
-  }
-
-}
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/FunctionStatementUsagesTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/FunctionStatementUsagesTest.java
deleted file mode 100644
index b2840c3..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/FunctionStatementUsagesTest.java
+++ /dev/null
@@ -1,92 +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.findusages;
-
-import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
-import com.google.idea.blaze.base.lang.buildfile.psi.*;
-import com.google.idea.blaze.base.lang.buildfile.search.FindUsages;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiReference;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Tests that usages of function declarations are found
- */
-public class FunctionStatementUsagesTest extends BuildFileIntegrationTestCase {
-
-  public void testLocalReferences() {
-    BuildFile buildFile = createBuildFile(
-      "java/com/google/build_defs.bzl",
-      "def function(name, srcs, deps):",
-      "    # function body",
-      "function(name = \"foo\")");
-
-    FunctionStatement funcDef = buildFile.findChildByClass(FunctionStatement.class);
-
-    PsiReference[] references = FindUsages.findAllReferences(funcDef);
-    assertThat(references).hasLength(1);
-
-    PsiElement ref = references[0].getElement();
-    assertThat(ref).isInstanceOf(FuncallExpression.class);
-  }
-
-  public void testLoadedFunctionReferences() {
-    BuildFile extFile = createBuildFile(
-      "java/com/google/build_defs.bzl",
-      "def function(name, deps)");
-
-    BuildFile buildFile = createBuildFile(
-      "java/com/google/BUILD",
-      "load(",
-      "\"//java/com/google:build_defs.bzl\",",
-      "\"function\"",
-      ")");
-
-    FunctionStatement funcDef = extFile.findChildByClass(FunctionStatement.class);
-    LoadStatement load = buildFile.firstChildOfClass(LoadStatement.class);
-
-    PsiReference[] references = FindUsages.findAllReferences(funcDef);
-    assertThat(references).hasLength(1);
-
-    PsiElement ref = references[0].getElement();
-    assertThat(ref).isInstanceOf(StringLiteral.class);
-    assertThat(ref.getParent()).isEqualTo(load);
-  }
-
-  public void testFuncallReference() {
-    BuildFile extFile = createBuildFile(
-      "java/com/google/tools/build_defs.bzl",
-      "def function(name, deps)");
-
-    BuildFile buildFile = createBuildFile(
-      "java/com/google/BUILD",
-      "load(",
-      "\"//java/com/google/tools:build_defs.bzl\",",
-      "\"function\"",
-      ")",
-      "function(name = \"name\", deps = []");
-
-    FunctionStatement function = extFile.firstChildOfClass(FunctionStatement.class);
-    FuncallExpression funcall = buildFile.firstChildOfClass(FuncallExpression.class);
-
-    PsiReference[] references = FindUsages.findAllReferences(function);
-    assertThat(references).hasLength(2);
-
-    assertThat(references[1].getElement()).isEqualTo(funcall);
-  }
-
-}
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/GlobFindUsagesTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/GlobFindUsagesTest.java
deleted file mode 100644
index 57ddf67..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/GlobFindUsagesTest.java
+++ /dev/null
@@ -1,188 +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.findusages;
-
-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.GlobExpression;
-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.intellij.psi.PsiDirectory;
-import com.intellij.psi.PsiFile;
-import com.intellij.psi.PsiManager;
-import com.intellij.psi.PsiReference;
-import com.intellij.psi.impl.PsiManagerEx;
-import com.intellij.psi.impl.file.impl.FileManager;
-import com.intellij.testFramework.LightVirtualFile;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Tests that file references in globs are included in the 'find usages' results.
- */
-public class GlobFindUsagesTest extends BuildFileIntegrationTestCase {
-
-  public void testSimpleGlobReferencingSingleFile() {
-    PsiFile ref = createPsiFile("java/com/google/Test.java");
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "glob(['**/*.java'])");
-
-    PsiReference[] references = FindUsages.findAllReferences(ref);
-    assertThat(references).hasLength(1);
-    assertThat(references[0].getElement()).isInstanceOf(GlobExpression.class);
-  }
-
-  public void testSimpleGlobReferencingSingleFile2() {
-    PsiFile ref = createPsiFile("java/com/google/Test.java");
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "glob(['*.java'])");
-
-    GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
-
-    PsiReference[] references = FindUsages.findAllReferences(ref);
-    assertThat(references).hasLength(1);
-    assertThat(references[0].getElement()).isEqualTo(glob);
-  }
-
-  public void testSimpleGlobReferencingSingleFile3() {
-    PsiFile ref = createPsiFile("java/com/google/Test.java");
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "glob(['T*t.java'])");
-
-    GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
-
-    PsiReference[] references = FindUsages.findAllReferences(ref);
-    assertThat(references).hasLength(1);
-    assertThat(references[0].getElement()).isEqualTo(glob);
-  }
-
-  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'])");
-
-    GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
-
-    PsiReference[] references = FindUsages.findAllReferences(ref1);
-    assertThat(references).hasLength(1);
-    assertThat(references[0].getElement()).isEqualTo(glob);
-
-    references = FindUsages.findAllReferences(ref2);
-    assertThat(references).hasLength(1);
-    assertThat(references[0].getElement()).isEqualTo(glob);
-  }
-
-  public void testFindsSubDirectories() {
-    PsiFile ref1 = createPsiFile("java/com/google/test/Test.java");
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "glob(['**/*.java'])");
-
-    GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
-
-    PsiReference[] references = FindUsages.findAllReferences(ref1);
-    assertThat(references).hasLength(1);
-    assertThat(references[0].getElement()).isEqualTo(glob);
-  }
-
-  public void testGlobWithExcludes() {
-    PsiFile test = createPsiFile("java/com/google/tests/Test.java");
-    PsiFile foo = createPsiFile("java/com/google/Foo.java");
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "glob(" +
-      "  ['**/*.java']," +
-      "  exclude = ['tests/*.java'])");
-
-    GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
-
-    PsiReference[] references = FindUsages.findAllReferences(foo);
-    assertThat(references).hasLength(1);
-    assertThat(references[0].getElement()).isEqualTo(glob);
-
-    assertThat(FindUsages.findAllReferences(test)).isEmpty();
-  }
-
-  public void testIncludeDirectories() {
-    PsiDirectory dir = createPsiDirectory("java/com/google/tests");
-    PsiFile test = createPsiFile("java/com/google/tests/Test.java");
-    PsiFile foo = createPsiFile("java/com/google/Foo.java");
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "glob(" +
-      "  ['**/*']," +
-      "  exclude = ['BUILD']," +
-      "  exclude_directories = 0)");
-
-    GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
-
-    PsiReference[] references = FindUsages.findAllReferences(dir);
-    assertThat(references).hasLength(1);
-    assertThat(references[0].getElement()).isEqualTo(glob);
-  }
-
-  public void testExcludeDirectories() {
-    PsiDirectory dir = createPsiDirectory("java/com/google/tests");
-    PsiFile test = createPsiFile("java/com/google/tests/Test.java");
-    PsiFile foo = createPsiFile("java/com/google/Foo.java");
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "glob(" +
-      "  ['**/*']," +
-      "  exclude = ['BUILD'])");
-
-    GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
-
-    PsiReference[] references = FindUsages.findAllReferences(dir);
-    assertThat(references).isEmpty();
-  }
-
-  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");
-
-    GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(pkg, GlobExpression.class);
-
-    PsiReference[] references = FindUsages.findAllReferences(subPkg);
-    assertThat(references).isEmpty();
-  }
-
-  // regression test for b/29267289
-  public void testInMemoryFileHandledGracefully() {
-    BuildFile pkg = createBuildFile(
-      "java/com/google/BUILD",
-      "glob(['**/*.java'])");
-
-    LightVirtualFile inMemoryFile = new LightVirtualFile("mockProjectViewFile", ProjectViewLanguage.INSTANCE, "");
-
-    FileManager fileManager = ((PsiManagerEx) PsiManager.getInstance(getProject())).getFileManager();
-    fileManager.setViewProvider(inMemoryFile, fileManager.createFileViewProvider(inMemoryFile, true));
-
-    PsiFile psiFile = fileManager.findFile(inMemoryFile);
-
-    PsiReference[] references = FindUsages.findAllReferences(psiFile);
-  }
-
-}
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/LocalVariableUsagesTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/LocalVariableUsagesTest.java
deleted file mode 100644
index 3ff6409..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/LocalVariableUsagesTest.java
+++ /dev/null
@@ -1,85 +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.findusages;
-
-import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
-import com.google.idea.blaze.base.lang.buildfile.psi.*;
-import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
-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.intellij.psi.PsiElement;
-import com.intellij.psi.PsiReference;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Tests that references to local variables are found by the 'Find Usages' action
- * TODO: Support comprehension suffix, and add test for it
- */
-public class LocalVariableUsagesTest extends BuildFileIntegrationTestCase {
-
-  public void testLocalReferences() {
-    BuildFile buildFile = createBuildFile(
-      "java/com/google/BUILD",
-      "localVar = 5",
-      "funcall(localVar)",
-      "def function(name):",
-      "    tempVar = localVar");
-
-    TargetExpression target = buildFile
-      .findChildByClass(AssignmentStatement.class)
-      .getLeftHandSideExpression();
-
-    PsiReference[] references = FindUsages.findAllReferences(target);
-    assertThat(references).hasLength(2);
-
-    FuncallExpression funcall = buildFile.findChildByClass(FuncallExpression.class);
-    assertThat(funcall).isNotNull();
-
-    PsiElement firstRef = references[0].getElement();
-    assertThat(PsiUtils.getParentOfType(firstRef, FuncallExpression.class))
-      .isEqualTo(funcall);
-
-    FunctionStatement function = buildFile.findChildByClass(FunctionStatement.class);
-    assertThat(function).isNotNull();
-
-    PsiElement secondRef = references[1].getElement();
-    assertThat(secondRef.getParent()).isInstanceOf(AssignmentStatement.class);
-    assertThat(PsiUtils.getParentOfType(secondRef, FunctionStatement.class))
-      .isEqualTo(function);
-  }
-
-  // the case where a symbol is the target of multiple assignment statements
-  public void testMultipleAssignments() {
-    BuildFile buildFile = createBuildFile(
-      "java/com/google/BUILD",
-      "var = 5",
-      "var += 1",
-      "var = 0");
-
-    TargetExpression target = buildFile
-      .findChildByClass(AssignmentStatement.class)
-      .getLeftHandSideExpression();
-
-    PsiReference[] references = FindUsages.findAllReferences(target);
-    assertThat(references).hasLength(2);
-
-    assertThat(references[0]).isInstanceOf(LocalReference.class);
-    assertThat(references[1]).isInstanceOf(TargetReference.class);
-  }
-
-}
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/formatting/BuildFileFoldingBuilderTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/formatting/BuildFileFoldingBuilderTest.java
deleted file mode 100644
index 97ade21..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/formatting/BuildFileFoldingBuilderTest.java
+++ /dev/null
@@ -1,90 +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.formatting;
-
-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.intellij.lang.folding.FoldingDescriptor;
-import com.intellij.openapi.editor.Editor;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Tests for {@link BuildFileFoldingBuilder}.
- */
-public class BuildFileFoldingBuilderTest extends BuildFileIntegrationTestCase {
-
-  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():");
-
-    getFoldingRegions(file);
-  }
-
-  public void testFuncDefStatementsFolded() {
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "# multi-line comment, not folded",
-      "# second line of comment",
-      "def function(arg1, arg2):",
-      "    stmt1",
-      "    stmt2",
-      "",
-      "variable = 1");
-
-    FoldingDescriptor[] foldingRegions = getFoldingRegions(file);
-    assertThat(foldingRegions).hasLength(1);
-    assertThat(foldingRegions[0].getElement().getPsi())
-      .isEqualTo(file.findFunctionInScope("function"));
-  }
-
-  public void testRulesFolded() {
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "java_library(",
-      "    name = 'lib',",
-      "    srcs = glob(['*.java']),",
-      ")");
-
-    FoldingDescriptor[] foldingRegions = getFoldingRegions(file);
-    assertThat(foldingRegions).hasLength(1);
-    assertThat(foldingRegions[0].getElement().getPsi())
-      .isEqualTo(file.findRule("lib"));
-  }
-
-  public void testLoadStatementFolded() {
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "load(",
-      "   '//java/com/foo/build_defs.bzl',",
-      "   'function1',",
-      "   'function2',",
-      ")");
-
-    FoldingDescriptor[] foldingRegions = getFoldingRegions(file);
-    assertThat(foldingRegions).hasLength(1);
-    assertThat(foldingRegions[0].getElement().getPsi())
-      .isEqualTo(file.findChildByClass(LoadStatement.class));
-  }
-
-  private FoldingDescriptor[] getFoldingRegions(BuildFile file) {
-    Editor editor = openFileInEditor(file.getVirtualFile());
-    return new BuildFileFoldingBuilder().buildFoldRegions(file.getNode(), editor.getDocument());
-  }
-}
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/language/BuildFileTypeTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/language/BuildFileTypeTest.java
deleted file mode 100644
index b53d30e..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/language/BuildFileTypeTest.java
+++ /dev/null
@@ -1,52 +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.language;
-
-import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
-import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
-import com.intellij.psi.PsiFile;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Tests that BUILD files are recognized as such
- */
-public class BuildFileTypeTest extends BuildFileIntegrationTestCase {
-
-  public void testSkylarkExtensionRecognized() {
-    PsiFile file = createPsiFile("java/com/google/foo/build_defs.bzl");
-    assertThat(file).isInstanceOf(BuildFile.class);
-  }
-
-  public void testExactNameMatch() {
-    PsiFile file = createPsiFile("java/com/google/foo/BUILD");
-    assertThat(file).isInstanceOf(BuildFile.class);
-  }
-
-  /**
-   * We may want to support these in the future (and in the meantime the user can manually have them recognized as BUILD files,
-   * for syntax highlighting, etc.).<br>
-   * Currently, turned off by default because references won't resolve correctly -- they'll point back to normal BUILD files.
-   */
-  public void testOtherBuildFilesNotRecognized() {
-    PsiFile file = createPsiFile("java/com/google/foo/BUILD.tools");
-    assertThat(file).isNotInstanceOf(BuildFile.class);
-
-    file = createPsiFile("java/com/google/foo/BUILD.bazel");
-    assertThat(file).isNotInstanceOf(BuildFile.class);
-  }
-
-}
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/lexer/AbstractLexerTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/lexer/AbstractLexerTest.java
deleted file mode 100644
index 5c16e80..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/lexer/AbstractLexerTest.java
+++ /dev/null
@@ -1,271 +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.lexer;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import static org.junit.Assert.*;
-
-/**
- * Tests of tokenization behavior of {@link BuildLexerBase}.
- */
-@RunWith(JUnit4.class)
-public abstract class AbstractLexerTest {
-
-  private final BuildLexerBase.LexerMode mode;
-  protected String lastError;
-
-  protected AbstractLexerTest(BuildLexerBase.LexerMode mode) {
-    this.mode = mode;
-  }
-
-  /**
-   * Create a lexer which takes input from the specified string. Resets the
-   * error handler beforehand.
-   */
-  protected BuildLexerBase createLexer(String input) {
-    lastError = null;
-    return new BuildLexerBase(input, 0, mode) {
-      @Override
-      protected void error(String message, int start, int end) {
-        super.error(message, start, end);
-        lastError = message;
-      }
-    };
-  }
-
-  protected Token[] tokens(String input) {
-    Token[] tokens = createLexer(input).getTokens().toArray(new Token[0]);
-    assertNoCharactersMissing(input.length(), tokens);
-    return tokens;
-  }
-
-  /**
-   * Both the syntax highlighter and the parser require every character be accounted for by
-   * a lexical element.
-   */
-  private static void assertNoCharactersMissing(int totalLength, Token[] tokens) {
-    if (tokens.length != 0 && tokens[tokens.length - 1].right != totalLength) {
-      throw new AssertionError(String.format(
-        "Last tokenized character '%s' doesn't match document length '%s'",
-        tokens[tokens.length - 1].right, totalLength));
-    }
-    int start = 0;
-    for (int i = 0; i < tokens.length; i++) {
-      Token token = tokens[i];
-      if (token.left != start) {
-        throw new AssertionError("Gap/inconsistency at: " + start);
-      }
-      start = token.right;
-    }
-  }
-
-  /**
-   * Returns a string containing the names of the tokens and their associated
-   * values. (String-literals are printed without escaping.)
-   */
-  protected String values(Token[] tokens) {
-    StringBuilder buffer = new StringBuilder();
-    for (Token token : tokens) {
-      if (isIgnored(token.kind)) {
-        continue;
-      }
-      if (buffer.length() > 0) {
-        buffer.append(' ');
-      }
-      buffer.append(token.kind.name());
-      if (token.kind != TokenKind.WHITESPACE && token.value != null) {
-        buffer.append('(').append(token.value).append(')');
-      }
-    }
-    return buffer.toString();
-  }
-
-  /**
-   * Returns a string containing just the names of the tokens.
-   */
-  protected String names(Token[] tokens) {
-    StringBuilder buf = new StringBuilder();
-    for (Token token : tokens) {
-      if (isIgnored(token.kind)) {
-        continue;
-      }
-      if (buf.length() > 0) {
-        buf.append(' ');
-      }
-      buf.append(token.kind.name());
-    }
-    return buf.toString();
-  }
-
-  private boolean isIgnored(TokenKind kind) {
-    if (mode == BuildLexerBase.LexerMode.Parsing) {
-      return kind == TokenKind.WHITESPACE || kind == TokenKind.COMMENT;
-    }
-    return false;
-  }
-
-  /**
-   * Returns a string containing just the half-open position intervals of each
-   * token. e.g. "[3,4) [4,9)".
-   */
-  protected String positions(Token[] tokens) {
-    StringBuilder buf = new StringBuilder();
-    for (Token token : tokens) {
-      if (isIgnored(token.kind)) {
-        continue;
-      }
-      if (buf.length() > 0) {
-        buf.append(' ');
-      }
-      buf.append('[')
-        .append(token.left)
-        .append(',')
-        .append(token.right)
-        .append(')');
-    }
-    return buf.toString();
-  }
-
-  @Test
-  public void testIntegers() throws Exception {
-    // Detection of MINUS immediately following integer constant proves we
-    // don't consume too many chars.
-
-    // decimal
-    assertEquals("INT(12345) MINUS", values(tokens("12345-")));
-
-    // octal
-    assertEquals("INT(5349) MINUS", values(tokens("012345-")));
-
-    // octal (bad)
-    assertEquals("INT(0) MINUS", values(tokens("012349-")));
-    assertEquals("invalid base-8 integer constant: 012349", lastError);
-
-    // hexadecimal (uppercase)
-    assertEquals("INT(1193055) MINUS", values(tokens("0X12345F-")));
-
-    // hexadecimal (lowercase)
-    assertEquals("INT(1193055) MINUS", values(tokens("0x12345f-")));
-
-    // hexadecimal (lowercase) [note: "g" cause termination of token]
-    assertEquals("INT(74565) IDENTIFIER(g) MINUS",
-      values(tokens("0x12345g-")));
-  }
-
-  @Test
-  public void testStringDelimiters() throws Exception {
-    assertEquals("STRING(foo)", values(tokens("\"foo\"")));
-    assertEquals("STRING(foo)", values(tokens("'foo'")));
-  }
-
-  @Test
-  public void testQuotesInStrings() throws Exception {
-    assertEquals("STRING(foo'bar)", values(tokens("'foo\\'bar'")));
-    assertEquals("STRING(foo'bar)", values(tokens("\"foo'bar\"")));
-    assertEquals("STRING(foo\"bar)", values(tokens("'foo\"bar'")));
-    assertEquals("STRING(foo\"bar)",
-      values(tokens("\"foo\\\"bar\"")));
-  }
-
-  @Test
-  public void testStringEscapes() throws Exception {
-    assertEquals("STRING(a\tb\nc\rd)",
-      values(tokens("'a\\tb\\nc\\rd'"))); // \t \r \n
-    assertEquals("STRING(x\\hx)",
-      values(tokens("'x\\hx'"))); // \h is unknown => "\h"
-    assertEquals("STRING(\\$$)", values(tokens("'\\$$'")));
-    assertEquals("STRING(ab)",
-      values(tokens("'a\\\nb'"))); // escape end of line
-    assertEquals("STRING(abcd)",
-      values(tokens("\"ab\\ucd\"")));
-    assertEquals("escape sequence not implemented: \\u", lastError);
-  }
-
-  @Test
-  public void testRawString() throws Exception {
-    assertEquals("STRING(abcd)",
-      values(tokens("r'abcd'")));
-    assertEquals("STRING(abcd)",
-      values(tokens("r\"abcd\"")));
-    assertEquals("STRING(a\\tb\\nc\\rd)",
-      values(tokens("r'a\\tb\\nc\\rd'"))); // r'a\tb\nc\rd'
-    assertEquals("STRING(a\\\")",
-      values(tokens("r\"a\\\"\""))); // r"a\""
-    assertEquals("STRING(a\\\\b)",
-      values(tokens("r'a\\\\b'"))); // r'a\\b'
-    assertEquals("STRING(ab) IDENTIFIER(r)",
-      values(tokens("r'ab'r")));
-
-    // Unterminated raw string
-    values(tokens("r'\\'")); // r'\'
-    assertEquals("unterminated string literal at eof", lastError);
-  }
-
-  @Test
-  public void testTripleRawString() throws Exception {
-    // r'''a\ncd'''
-    assertEquals("STRING(ab\\ncd)", values(tokens("r'''ab\\ncd'''")));
-    // r"""ab
-    // cd"""
-    assertEquals(
-      "STRING(ab\ncd)",
-      values(tokens("\"\"\"ab\ncd\"\"\"")));
-
-    // Unterminated raw string
-    values(tokens("r'''\\'''")); // r'''\'''
-    assertEquals("unterminated string literal at eof", lastError);
-  }
-
-  @Test
-  public void testOctalEscapes() throws Exception {
-    // Regression test for a bug.
-    assertEquals("STRING(\0 \1 \t \u003f I I1 \u00ff \u00ff \u00fe)",
-      values(tokens("'\\0 \\1 \\11 \\77 \\111 \\1111 \\377 \\777 \\776'")));
-    // Test boundaries (non-octal char, EOF).
-    assertEquals("STRING(\1b \1)", values(tokens("'\\1b \\1'")));
-  }
-
-  @Test
-  public void testTripleQuotedStrings() throws Exception {
-    assertEquals("STRING(a\"b'c \n d\"\"e)",
-      values(tokens("\"\"\"a\"b'c \n d\"\"e\"\"\"")));
-    assertEquals("STRING(a\"b'c \n d\"\"e)",
-      values(tokens("'''a\"b'c \n d\"\"e'''")));
-  }
-
-  @Test
-  public void testBadChar() throws Exception {
-    assertEquals("IDENTIFIER(a) ILLEGAL($) IDENTIFIER(b)", values(tokens("a$b")));
-    assertEquals("invalid character: '$'", lastError);
-  }
-
-  @Test
-  public void testContainsErrors() throws Exception {
-    BuildLexerBase lexerSuccess = createLexer("foo");
-    assertFalse(lexerSuccess.containsErrors());
-
-    BuildLexerBase lexerFail = createLexer("f$o");
-    assertTrue(lexerFail.containsErrors());
-
-    String s = "'unterminated";
-    lexerFail = createLexer(s);
-    assertTrue(lexerFail.containsErrors());
-    assertEquals("STRING(unterminated)", values(tokens(s)));
-  }
-}
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/lexer/BlazeLexerTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/lexer/BlazeLexerTest.java
deleted file mode 100644
index 6d0b2e1..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/lexer/BlazeLexerTest.java
+++ /dev/null
@@ -1,179 +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.lexer;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import static org.junit.Assert.assertEquals;
-
-/**
- * Tests of tokenization behavior of {@link BuildLexerBase} in 'parsing mode' (see {@link BuildLexerBase.LexerMode})
- */
-@RunWith(JUnit4.class)
-public class BlazeLexerTest extends AbstractLexerTest {
-
-  public BlazeLexerTest() {
-    super(BuildLexerBase.LexerMode.Parsing);
-  }
-
-  @Test
-  public void testBasics1() throws Exception {
-    assertEquals("IDENTIFIER RPAREN", names(tokens("wiz) ")));
-    assertEquals("IDENTIFIER RPAREN", names(tokens("wiz )")));
-    assertEquals("IDENTIFIER RPAREN", names(tokens(" wiz)")));
-    assertEquals("IDENTIFIER RPAREN", names(tokens(" wiz ) ")));
-    assertEquals("IDENTIFIER RPAREN", names(tokens("wiz\t)")));
-  }
-
-  @Test
-  public void testBasics2() throws Exception {
-    assertEquals("RPAREN", names(tokens(")")));
-    assertEquals("RPAREN", names(tokens(" )")));
-    assertEquals("RPAREN", names(tokens(" ) ")));
-    assertEquals("RPAREN", names(tokens(") ")));
-  }
-
-  @Test
-  public void testBasics3() throws Exception {
-    assertEquals("INT NEWLINE INT", names(tokens("123#456\n789")));
-    assertEquals("INT NEWLINE INT", names(tokens("123 #456\n789")));
-    assertEquals("INT NEWLINE INT", names(tokens("123#456 \n789")));
-    assertEquals("INT NEWLINE INDENT INT", names(tokens("123#456\n 789")));
-    assertEquals("INT NEWLINE INT", names(tokens("123#456\n789 ")));
-  }
-
-  @Test
-  public void testBasics4() throws Exception {
-    assertEquals("", names(tokens("")));
-    assertEquals("", names(tokens("# foo")));
-    assertEquals("INT INT INT INT", names(tokens("1 2 3 4")));
-    assertEquals("INT DOT INT", names(tokens("1.234")));
-    assertEquals("IDENTIFIER LPAREN IDENTIFIER COMMA IDENTIFIER RPAREN", names(tokens("foo(bar, wiz)")));
-  }
-
-  @Test
-  public void testIntegersAndDot() throws Exception {
-    assertEquals("INT(1) DOT INT(2345)", values(tokens("1.2345")));
-
-    assertEquals("INT(1) DOT INT(2) DOT INT(345)", values(tokens("1.2.345")));
-
-    assertEquals("INT(1) DOT INT(0)", values(tokens("1.23E10")));
-    assertEquals("invalid base-10 integer constant: 23E10", lastError);
-
-    assertEquals("INT(1) DOT INT(0) MINUS INT(10)", values(tokens("1.23E-10")));
-    assertEquals("invalid base-10 integer constant: 23E", lastError);
-
-    assertEquals("DOT INT(123)", values(tokens(". 123")));
-    assertEquals("DOT INT(123)", values(tokens(".123")));
-    assertEquals("DOT IDENTIFIER(abc)", values(tokens(".abc")));
-
-    assertEquals("IDENTIFIER(foo) DOT INT(123)", values(tokens("foo.123")));
-    assertEquals("IDENTIFIER(foo) DOT IDENTIFIER(bcd)", values(tokens("foo.bcd"))); // 'b' are hex chars
-    assertEquals("IDENTIFIER(foo) DOT IDENTIFIER(xyz)", values(tokens("foo.xyz")));
-  }
-
-  @Test
-  public void testIndentation() throws Exception {
-    assertEquals("INT(1) NEWLINE INT(2) NEWLINE INT(3)",
-      values(tokens("1\n2\n3")));
-    assertEquals("INT(1) NEWLINE INDENT INT(2) NEWLINE INT(3) NEWLINE DEDENT INT(4)",
-      values(tokens("1\n  2\n  3\n4 ")));
-    assertEquals("INT(1) NEWLINE INDENT INT(2) NEWLINE INT(3)",
-      values(tokens("1\n  2\n  3")));
-    assertEquals("INT(1) NEWLINE INDENT INT(2) NEWLINE INDENT INT(3)",
-      values(tokens("1\n  2\n    3")));
-    assertEquals("INT(1) NEWLINE INDENT INT(2) NEWLINE INDENT INT(3) NEWLINE "
-        + "DEDENT INT(4) NEWLINE DEDENT INT(5)",
-      values(tokens("1\n  2\n    3\n  4\n5")));
-
-    assertEquals("INT(1) NEWLINE INDENT INT(2) NEWLINE INDENT INT(3) NEWLINE "
-        + "DEDENT INT(4) NEWLINE DEDENT INT(5)",
-      values(tokens("1\n  2\n    3\n   4\n5")));
-    assertEquals("indentation error", lastError);
-  }
-
-  @Test
-  public void testIndentationInsideParens() throws Exception {
-    // Indentation is ignored inside parens:
-    assertEquals("INT(1) LPAREN INT(2) INT(3) INT(4) INT(5)",
-      values(tokens("1 (\n  2\n    3\n  4\n5")));
-    assertEquals("INT(1) LBRACE INT(2) INT(3) INT(4) INT(5)",
-      values(tokens("1 {\n  2\n    3\n  4\n5")));
-    assertEquals("INT(1) LBRACKET INT(2) INT(3) INT(4) INT(5)",
-      values(tokens("1 [\n  2\n    3\n  4\n5")));
-    assertEquals("INT(1) LBRACKET INT(2) RBRACKET NEWLINE INDENT INT(3) "
-        + "NEWLINE INT(4) NEWLINE DEDENT INT(5)",
-      values(tokens("1 [\n  2]\n    3\n    4\n5")));
-  }
-
-  @Test
-  public void testNoIndentationAtEOF() throws Exception {
-    assertEquals("INDENT INT(1)", values(tokens("\n  1")));
-  }
-
-  @Test
-  public void testBlankLineIndentation() throws Exception {
-    // Blank lines and comment lines should not generate any indents
-    // (but note that every input ends with).
-    assertEquals("", names(tokens("\n      #\n")));
-    assertEquals("", names(tokens("      #")));
-    assertEquals("NEWLINE", names(tokens("      #\n")));
-    assertEquals("NEWLINE", names(tokens("      #comment\n")));
-    assertEquals("DEF IDENTIFIER LPAREN IDENTIFIER RPAREN COLON NEWLINE "
-        + "INDENT RETURN IDENTIFIER NEWLINE DEDENT",
-      names(tokens("def f(x):\n"
-        + "  # comment\n"
-        + "\n"
-        + "  \n"
-        + "  return x\n")));
-  }
-
-  @Test
-  public void testMultipleCommentLines() throws Exception {
-    assertEquals("NEWLINE "
-        + "DEF IDENTIFIER LPAREN IDENTIFIER RPAREN COLON NEWLINE "
-        + "INDENT RETURN IDENTIFIER NEWLINE DEDENT",
-      names(tokens("# Copyright\n"
-        + "#\n"
-        + "# A comment line\n"
-        + "# An adjoining line\n"
-        + "def f(x):\n"
-        + "  return x\n")));
-  }
-
-  @Test
-  public void testBackslash() throws Exception {
-    // backslash followed by newline marked as whitespace (skipped by parser)
-    assertEquals("IDENTIFIER IDENTIFIER",
-      names(tokens("a\\\nb")));
-    assertEquals("IDENTIFIER ILLEGAL IDENTIFIER",
-      names(tokens("a\\ b")));
-    assertEquals("IDENTIFIER LPAREN INT RPAREN",
-      names(tokens("a(\\\n2)")));
-  }
-
-  @Test
-  public void testTokenPositions() throws Exception {
-    //            foo   (     bar   ,     {      1       :
-    assertEquals("[0,3) [3,4) [4,7) [7,8) [9,10) [10,11) [11,12)"
-        //      'quux'  }       )
-        + " [13,19) [19,20) [20,21)",
-      positions(tokens("foo(bar, {1: 'quux'})")));
-  }
-
-}
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/lexer/HighlightingLexerTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/lexer/HighlightingLexerTest.java
deleted file mode 100644
index 40e91fd..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/lexer/HighlightingLexerTest.java
+++ /dev/null
@@ -1,175 +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.lexer;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import static org.junit.Assert.assertEquals;
-
-/**
- * Tests of tokenization behavior of {@link BuildLexerBase} in 'highlighting mode' (see {@link BuildLexerBase.LexerMode})
- */
-@RunWith(JUnit4.class)
-public class HighlightingLexerTest extends AbstractLexerTest {
-
-  public HighlightingLexerTest() {
-    super(BuildLexerBase.LexerMode.SyntaxHighlighting);
-  }
-
-  @Test
-  public void testBasics1() throws Exception {
-    assertEquals("IDENTIFIER RPAREN WHITESPACE", names(tokens("wiz) ")));
-    assertEquals("IDENTIFIER WHITESPACE RPAREN", names(tokens("wiz )")));
-    assertEquals("WHITESPACE IDENTIFIER RPAREN", names(tokens(" wiz)")));
-    assertEquals("WHITESPACE IDENTIFIER WHITESPACE RPAREN WHITESPACE", names(tokens(" wiz ) ")));
-    assertEquals("IDENTIFIER WHITESPACE RPAREN", names(tokens("wiz\t)")));
-  }
-
-  @Test
-  public void testBasics2() throws Exception {
-    assertEquals("RPAREN", names(tokens(")")));
-    assertEquals("WHITESPACE RPAREN", names(tokens(" )")));
-    assertEquals("WHITESPACE RPAREN WHITESPACE", names(tokens(" ) ")));
-    assertEquals("RPAREN WHITESPACE", names(tokens(") ")));
-  }
-
-  @Test
-  public void testBasics3() throws Exception {
-    assertEquals("INT COMMENT NEWLINE INT", names(tokens("123#456\n789")));
-    assertEquals("INT WHITESPACE COMMENT NEWLINE INT", names(tokens("123 #456\n789")));
-    assertEquals("INT COMMENT NEWLINE INT", names(tokens("123#456 \n789")));
-    assertEquals("INT COMMENT NEWLINE WHITESPACE INT", names(tokens("123#456\n 789")));
-    assertEquals("INT COMMENT NEWLINE INT WHITESPACE", names(tokens("123#456\n789 ")));
-  }
-
-  @Test
-  public void testBasics4() throws Exception {
-    assertEquals("", names(tokens("")));
-    assertEquals("COMMENT", names(tokens("# foo")));
-    assertEquals("INT WHITESPACE INT WHITESPACE INT WHITESPACE INT", names(tokens("1 2 3 4")));
-    assertEquals("INT DOT INT", names(tokens("1.234")));
-    assertEquals("IDENTIFIER LPAREN IDENTIFIER COMMA WHITESPACE IDENTIFIER RPAREN", names(tokens("foo(bar, wiz)")));
-  }
-
-  @Test
-  public void testIntegersAndDot() throws Exception {
-    assertEquals("INT(1) DOT INT(2345)", values(tokens("1.2345")));
-
-    assertEquals("INT(1) DOT INT(2) DOT INT(345)", values(tokens("1.2.345")));
-
-    assertEquals("INT(1) DOT INT(0)", values(tokens("1.23E10")));
-    assertEquals("invalid base-10 integer constant: 23E10", lastError);
-
-    assertEquals("INT(1) DOT INT(0) MINUS INT(10)", values(tokens("1.23E-10")));
-    assertEquals("invalid base-10 integer constant: 23E", lastError);
-
-    assertEquals("DOT WHITESPACE INT(123)", values(tokens(". 123")));
-    assertEquals("DOT INT(123)", values(tokens(".123")));
-    assertEquals("DOT IDENTIFIER(abc)", values(tokens(".abc")));
-
-    assertEquals("IDENTIFIER(foo) DOT INT(123)", values(tokens("foo.123")));
-    assertEquals("IDENTIFIER(foo) DOT IDENTIFIER(bcd)", values(tokens("foo.bcd"))); // 'b' are hex chars
-    assertEquals("IDENTIFIER(foo) DOT IDENTIFIER(xyz)", values(tokens("foo.xyz")));
-  }
-
-  @Test
-  public void testNoIndentation() throws Exception {
-    assertEquals("INT(1) NEWLINE INT(2) NEWLINE INT(3)",
-      values(tokens("1\n2\n3")));
-    assertEquals("INT(1) NEWLINE WHITESPACE INT(2) NEWLINE WHITESPACE INT(3) NEWLINE INT(4) WHITESPACE",
-      values(tokens("1\n  2\n  3\n4 ")));
-    assertEquals("INT(1) NEWLINE WHITESPACE INT(2) NEWLINE WHITESPACE INT(3)",
-      values(tokens("1\n  2\n  3")));
-    assertEquals("INT(1) NEWLINE WHITESPACE INT(2) NEWLINE WHITESPACE INT(3)",
-      values(tokens("1\n  2\n    3")));
-    assertEquals("INT(1) NEWLINE WHITESPACE INT(2) NEWLINE WHITESPACE INT(3) NEWLINE WHITESPACE INT(4) NEWLINE INT(5)",
-      values(tokens("1\n  2\n    3\n  4\n5")));
-
-    assertEquals("INT(1) NEWLINE WHITESPACE INT(2) NEWLINE WHITESPACE INT(3) NEWLINE WHITESPACE INT(4) NEWLINE INT(5)",
-      values(tokens("1\n  2\n    3\n   4\n5")));
-  }
-
-  @Test
-  public void testIndentationInsideParens() throws Exception {
-    // Indentation is ignored inside parens:
-    assertEquals("INT(1) WHITESPACE LPAREN NEWLINE WHITESPACE INT(2) NEWLINE " +
-      "WHITESPACE INT(3) NEWLINE WHITESPACE INT(4) NEWLINE INT(5)",
-      values(tokens("1 (\n  2\n    3\n  4\n5")));
-    assertEquals("INT(1) WHITESPACE LBRACE NEWLINE WHITESPACE INT(2) NEWLINE " +
-      "WHITESPACE INT(3) NEWLINE WHITESPACE INT(4) NEWLINE INT(5)",
-      values(tokens("1 {\n  2\n    3\n  4\n5")));
-    assertEquals("INT(1) WHITESPACE LBRACKET NEWLINE WHITESPACE INT(2) NEWLINE " +
-      "WHITESPACE INT(3) NEWLINE WHITESPACE INT(4) NEWLINE INT(5)",
-      values(tokens("1 [\n  2\n    3\n  4\n5")));
-    assertEquals("INT(1) WHITESPACE LBRACKET NEWLINE WHITESPACE INT(2) RBRACKET " +
-      "NEWLINE WHITESPACE INT(3) NEWLINE WHITESPACE INT(4) NEWLINE INT(5)",
-      values(tokens("1 [\n  2]\n    3\n    4\n5")));
-  }
-
-  @Test
-  public void testNoIndentationAtEOF() throws Exception {
-    assertEquals("NEWLINE WHITESPACE INT(1)", values(tokens("\n  1")));
-  }
-
-  @Test
-  public void testBlankLineIndentation() throws Exception {
-    // Blank lines and comment lines should not generate any newlines indents
-    // (but note that every input ends with).
-    assertEquals("NEWLINE WHITESPACE COMMENT NEWLINE", names(tokens("\n      #\n")));
-    assertEquals("WHITESPACE COMMENT", names(tokens("      #")));
-    assertEquals("WHITESPACE COMMENT NEWLINE", names(tokens("      #\n")));
-    assertEquals("WHITESPACE COMMENT NEWLINE", names(tokens("      #comment\n")));
-    assertEquals("DEF WHITESPACE IDENTIFIER LPAREN IDENTIFIER RPAREN COLON NEWLINE WHITESPACE " +
-      "COMMENT NEWLINE NEWLINE WHITESPACE NEWLINE WHITESPACE RETURN WHITESPACE IDENTIFIER NEWLINE",
-      names(tokens("def f(x):\n"
-        + "  # comment\n"
-        + "\n"
-        + "  \n"
-        + "  return x\n")));
-  }
-
-  @Test
-  public void testMultipleCommentLines() throws Exception {
-    assertEquals("COMMENT NEWLINE COMMENT NEWLINE COMMENT NEWLINE COMMENT NEWLINE DEF WHITESPACE IDENTIFIER " +
-      "LPAREN IDENTIFIER RPAREN COLON NEWLINE WHITESPACE RETURN WHITESPACE IDENTIFIER NEWLINE",
-      names(tokens("# Copyright\n"
-        + "#\n"
-        + "# A comment line\n"
-        + "# An adjoining line\n"
-        + "def f(x):\n"
-        + "  return x\n")));
-  }
-
-  @Test
-  public void testBackslash() throws Exception {
-    // illegal characters marked as whitespace (skipped by parser)
-    assertEquals("IDENTIFIER WHITESPACE IDENTIFIER",
-      names(tokens("a\\\nb")));
-    assertEquals("IDENTIFIER ILLEGAL WHITESPACE IDENTIFIER",
-      names(tokens("a\\ b")));
-    assertEquals("IDENTIFIER LPAREN WHITESPACE INT RPAREN",
-      names(tokens("a(\\\n2)")));
-  }
-
-  @Test
-  public void testTokenPositions() throws Exception {
-    assertEquals("[0,3) [3,4) [4,7) [7,8) [8,9) [9,10) [10,11) [11,12) [12,13) [13,19) [19,20) [20,21)",
-      positions(tokens("foo(bar, {1: 'quux'})")));
-  }
-
-}
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/parser/BuildParserTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/parser/BuildParserTest.java
deleted file mode 100644
index 620bf24..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/parser/BuildParserTest.java
+++ /dev/null
@@ -1,577 +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.parser;
-
-import com.google.common.base.Joiner;
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
-import com.google.idea.blaze.base.lang.buildfile.psi.BuildElement;
-import com.google.idea.blaze.base.lang.buildfile.psi.LoadStatement;
-import com.intellij.lang.ASTNode;
-import com.intellij.lang.FileASTNode;
-import com.intellij.lang.ParserDefinition;
-import com.intellij.lang.PsiParser;
-import com.intellij.lang.impl.PsiBuilderAdapter;
-import com.intellij.lang.impl.PsiBuilderImpl;
-import com.intellij.lexer.Lexer;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.impl.source.CharTableImpl;
-import com.intellij.psi.impl.source.tree.LeafElement;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.stream.Collectors;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Test for the BUILD file parser (converting lexical elements into PSI elements)
- */
-public class BuildParserTest extends BuildFileIntegrationTestCase {
-
-  private final List<String> errors = Lists.newArrayList();
-
-  @Override
-  protected void doTearDown() {
-    errors.clear();
-  }
-
-  public void testAugmentedAssign() throws Exception {
-    assertThat(parse("x += 1"))
-      .isEqualTo("aug_assign(reference, int)");
-    assertThat(parse("x -= 1"))
-      .isEqualTo("aug_assign(reference, int)");
-    assertThat(parse("x *= 1"))
-      .isEqualTo("aug_assign(reference, int)");
-    assertThat(parse("x /= 1"))
-      .isEqualTo("aug_assign(reference, int)");
-    assertThat(parse("x %= 1"))
-      .isEqualTo("aug_assign(reference, int)");
-    assertNoErrors();
-  }
-
-  public void testAssign() throws Exception {
-    assertThat(parse("a, b = 5\n"))
-      .isEqualTo("assignment(list(reference, target), int)");
-    assertNoErrors();
-  }
-
-  public void testAssign2() throws Exception {
-    assertThat(parse("a = b;c = d\n"))
-      .isEqualTo(Joiner.on("").join(
-        "assignment(target, reference), ",
-        "assignment(target, reference)"));
-    assertNoErrors();
-  }
-
-  public void testInvalidAssign() throws Exception {
-    parse("1 + (b = c)");
-    assertContainsErrors();
-  }
-
-  public void testTupleAssign() throws Exception {
-    assertThat(parse("list[0] = 5; dict['key'] = value\n"))
-      .isEqualTo(Joiner.on("").join(
-        "assignment(function_call(reference, positional(int)), int), ",
-        "assignment(function_call(reference, positional(string)), reference)"));
-    assertNoErrors();
-  }
-
-  public void testPrimary() throws Exception {
-    assertThat(parse("f(1 + 2)"))
-      .isEqualTo("function_call(reference, arg_list(positional(binary_op(int, int))))");
-    assertNoErrors();
-  }
-
-  public void testSecondary() throws Exception {
-    assertThat(parse("f(1 % 2)"))
-      .isEqualTo("function_call(reference, arg_list(positional(binary_op(int, int))))");
-    assertNoErrors();
-  }
-
-  public void testDoesNotGetStuck() throws Exception {
-    // Make sure the parser does not get stuck when trying
-    // to parse an expression containing a syntax error.
-    parse("f(1, ], 3)");
-    parse("f(1, ), 3)");
-    parse("[ ) for v in 3)");
-    parse("f(1, [x for foo foo foo foo], 3)");
-  }
-
-  public void testInvalidFunctionStatementDoesNotGetStuck() throws Exception {
-    // Make sure the parser does not get stuck when trying
-    // to parse a function statement containing a syntax error.
-    parse("def is ");
-    parse("def fn(");
-    parse("def empty)");
-  }
-
-  public void testSubstring() throws Exception {
-    assertThat(parse("'FOO.CC'[:].lower()[1:]"))
-      .isEqualTo(Joiner.on("").join(
-        "function_call(",
-        "function_call(function_call(string), reference, arg_list), ",
-        "positional(int))"));
-    assertNoErrors();
-  }
-
-  public void testFuncallExpr() throws Exception {
-    assertThat(parse("foo(1, 2, bar=wiz)"))
-      .isEqualTo(Joiner.on("").join(
-        "function_call(reference, arg_list(",
-        "positional(int), ",
-        "positional(int), ",
-        "keyword(reference)))"));
-    assertNoErrors();
-  }
-
-  public void testMethCallExpr() throws Exception {
-    assertThat(parse("foo.foo(1, 2, bar=wiz)"))
-      .isEqualTo(Joiner.on("").join(
-        "function_call(reference, reference, ",
-        "arg_list(positional(int), positional(int), keyword(reference)))"));
-    assertNoErrors();
-  }
-
-  public void testChainedMethCallExpr() throws Exception {
-    assertThat(parse("foo.replace().split(1)"))
-      .isEqualTo("function_call(function_call(reference, reference, arg_list), reference, arg_list(positional(int)))");
-    assertNoErrors();
-  }
-
-  public void testPropRefExpr() throws Exception {
-    assertThat(parse("foo.foo"))
-      .isEqualTo("dot_expr(reference, reference)");
-    assertNoErrors();
-  }
-
-  public void testStringMethExpr() throws Exception {
-    assertThat(parse("'foo'.foo()"))
-      .isEqualTo("function_call(string, reference, arg_list)");
-    assertNoErrors();
-  }
-
-  public void testFuncallLocation() throws Exception {
-    assertThat(parse("a(b);c = d\n"))
-      .isEqualTo(Joiner.on("").join(
-        "function_call(reference, arg_list(positional(reference))), ",
-        "assignment(target, reference)"));
-    assertNoErrors();
-  }
-
-  public void testList() throws Exception {
-    assertThat(parse("[0,f(1),2]"))
-      .isEqualTo("list(int, function_call(reference, arg_list(positional(int))), int)");
-    assertNoErrors();
-  }
-
-  public void testDict() throws Exception {
-    assertThat(parse("{1:2,2:f(1),3:4}"))
-      .isEqualTo(Joiner.on("").join(
-        "dict(",
-        "dict_entry(int, int), ",
-        "dict_entry(int, function_call(reference, arg_list(positional(int)))), ",
-        "dict_entry(int, int)",
-        ")"));
-    assertNoErrors();
-  }
-
-  public void testArgumentList() throws Exception {
-    assertThat(parse("f(0,g(1,2),2)"))
-      .isEqualTo(Joiner.on("").join(
-        "function_call(reference, arg_list(",
-        "positional(int), ",
-        "positional(function_call(reference, arg_list(positional(int), positional(int)))), ",
-        "positional(int)))"));
-    assertNoErrors();
-  }
-
-  public void testForBreakContinue() throws Exception {
-    String parsed = parse(
-      "def foo():",
-      "  for i in [1, 2]:",
-      "    break",
-      "    continue",
-      "    break");
-    assertThat(parsed)
-      .isEqualTo(Joiner.on("").join(
-        "function_def(parameter_list, ",
-        "stmt_list(for(target, list(int, int), ",
-        "stmt_list(flow, flow, flow))))"));
-    assertNoErrors();
-  }
-
-  public void testEmptyTuple() throws Exception {
-    assertThat(parse("()"))
-      .isEqualTo("list");
-    assertNoErrors();
-  }
-
-  public void testTupleTrailingComma() throws Exception {
-    assertThat(parse("(42,)"))
-      .isEqualTo("list(int)");
-    assertNoErrors();
-  }
-
-  public void testSingleton() throws Exception {
-    assertThat(parse("(42)")) // not a tuple!
-      .isEqualTo("list(int)");
-    assertNoErrors();
-  }
-
-  public void testDictionaryLiterals() throws Exception {
-    assertThat(parse("{1:42}"))
-      .isEqualTo("dict(dict_entry(int, int))");
-    assertNoErrors();
-  }
-
-  public void testDictionaryLiterals1() throws Exception {
-    assertThat(parse("{}"))
-      .isEqualTo("dict");
-    assertNoErrors();
-  }
-
-  public void testDictionaryLiterals2() throws Exception {
-    assertThat(parse("{1:42,}"))
-      .isEqualTo("dict(dict_entry(int, int))");
-    assertNoErrors();
-  }
-
-  public void testDictionaryLiterals3() throws Exception {
-    assertThat(parse("{1:42,2:43,3:44}"))
-      .isEqualTo(Joiner.on("").join(
-        "dict(",
-        "dict_entry(int, int), ",
-        "dict_entry(int, int), ",
-        "dict_entry(int, int))"));
-    assertNoErrors();
-  }
-
-  public void testInvalidListComprehensionSyntax() throws Exception {
-    assertThat(parse("[x for x for y in ['a']]"))
-      .isEqualTo("list_comp(reference, reference)");
-    assertContainsErrors();
-  }
-
-  public void testListComprehensionEmptyList() throws Exception {
-    // At the moment, we just parse the components of comprehension suffixes.
-    assertThat(parse("['foo/%s.java' % x for x in []]"))
-      .isEqualTo("list_comp(binary_op(string, reference), target, list)");
-    assertNoErrors();
-  }
-
-  public void testListComprehension() throws Exception {
-    assertThat(parse("['foo/%s.java' % x for x in ['bar', 'wiz', 'quux']]"))
-      .isEqualTo(Joiner.on("").join(
-        "list_comp(binary_op(string, reference), ",
-        "target, ",
-        "list(string, string, string))"));
-    assertNoErrors();
-  }
-
-  public void testDoesntGetStuck2() throws Exception {
-    parse(
-      "def foo():",
-      "  a = 2 for 4",  // parse error
-      "  b = [3, 4]",
-      "",
-      "d = 4 ada",  // parse error
-      "",
-      "def bar():",
-      "  a = [3, 4]",
-      "  b = 2 + + 5",  // parse error
-      "");
-    assertContainsErrors();
-  }
-
-  public void testDoesntGetStuck3() throws Exception {
-    parse("load(*)");
-    parse("load()");
-    parse("load(,)");
-    parse("load)");
-    parse("load(,");
-    parse("load(,\"string\"");
-    assertContainsErrors();
-  }
-
-  public void testExprAsStatement() throws Exception {
-    String parsed = parse(
-      "li = []",
-      "li.append('a.c')",
-      "\"\"\" string comment \"\"\"",
-      "foo(bar)");
-    assertThat(parsed)
-      .isEqualTo(Joiner.on("").join(
-        "assignment(target, list), ",
-        "function_call(reference, reference, arg_list(positional(string))), ",
-        "string, ",
-        "function_call(reference, arg_list(positional(reference)))"));
-    assertNoErrors();
-  }
-
-  public void testPrecedence1() {
-    assertThat(parse("'%sx' % 'foo' + 'bar'"))
-      .isEqualTo("binary_op(binary_op(string, string), string)");
-    assertNoErrors();
-  }
-
-  public void testPrecedence2() {
-    assertThat(parse("('%sx' + 'foo') * 'bar'"))
-      .isEqualTo("binary_op(list(binary_op(string, string)), string)");
-    assertNoErrors();
-  }
-
-  public void testPrecedence3() {
-    assertThat(parse("'%sx' % ('foo' + 'bar')"))
-      .isEqualTo("binary_op(string, list(binary_op(string, string)))");
-    assertNoErrors();
-  }
-
-  public void testPrecedence4() throws Exception {
-    assertThat(parse("1 + - (2 - 3)"))
-      .isEqualTo("binary_op(int, positional(list(binary_op(int, int))))");
-    assertNoErrors();
-  }
-
-  public void testPrecedence5() throws Exception {
-    assertThat(parse("2 * x | y + 1"))
-      .isEqualTo("binary_op(binary_op(int, reference), binary_op(reference, int))");
-    assertNoErrors();
-  }
-
-  public void testNotIsIgnored() throws Exception {
-    assertThat(parse("not 'b'"))
-      .isEqualTo("string");
-    assertNoErrors();
-  }
-
-  public void testNotIn() throws Exception {
-    assertThat(parse("'a' not in 'b'"))
-      .isEqualTo("binary_op(string, string)");
-    assertNoErrors();
-  }
-
-  public void testParseBuildFileWithSingeRule() throws Exception {
-    ASTNode tree = createAST(
-      "genrule(name = 'foo',",
-      "   srcs = ['input.csv'],",
-      "   outs = [ 'result.txt',",
-      "           'result.log'],",
-      "   cmd = 'touch result.txt result.log')");
-    List<BuildElement> stmts = getTopLevelNodesOfType(tree, BuildElement.class);
-    assertThat(stmts).hasSize(1);
-    assertNoErrors();
-  }
-
-  public void testParseBuildFileWithMultipleRules() throws Exception {
-    ASTNode tree = createAST(
-      "genrule(name = 'foo',",
-      "   srcs = ['input.csv'],",
-      "   outs = [ 'result.txt',",
-      "           'result.log'],",
-      "   srcs = ['input.csv'],",
-      "   cmd = 'touch result.txt result.log')",
-      "",
-      "genrule(name = 'bar',",
-      "   outs = [ 'graph.svg'],",
-      "   cmd = 'touch graph.svg')");
-    List<BuildElement> stmts = getTopLevelNodesOfType(tree, BuildElement.class);
-    assertThat(stmts).hasSize(2);
-    assertNoErrors();
-  }
-
-  public void testMissingComma() throws Exception {
-    // missing comma after name='foo'
-    parse("genrule(name = 'foo'",
-          "   srcs = ['in'])");
-    assertContainsError("',' expected");
-  }
-
-  public void testDoubleSemicolon() throws Exception {
-    parse("x = 1; ; x = 2;");
-    assertContainsError("expected an expression");
-  }
-
-  public void testMissingBlock() throws Exception {
-    parse(
-      "x = 1;",
-      "def foo(x):",
-      "x = 2;\n");
-    assertContainsError("'indent' expected");
-  }
-
-  public void testFunCallBadSyntax() throws Exception {
-    parse("f(1,\n");
-    assertContainsError("')' expected");
-  }
-
-  public void testFunCallBadSyntax2() throws Exception {
-    parse("f(1, 5, ,)\n");
-    assertContainsError("expected an expression");
-  }
-
-  public void testLoad() throws Exception {
-    ASTNode tree = createAST("load('file', 'foo', '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.getImportedSymbolNames()).isEqualTo(new String[] {"foo", "bar"});
-    assertNoErrors();
-  }
-
-  public void testLoadNoSymbol() throws Exception {
-    parse("load('/foo/bar/file')\n");
-    assertContainsError("'load' statements must include at least one loaded function");
-  }
-
-  public void testFunctionDefinition() throws Exception {
-    ASTNode tree = createAST(
-      "def function(name = 'foo', srcs, outs, *args, **kwargs):",
-      "   native.java_library(",
-      "     name = name,",
-      "     srcs = srcs,",
-      "   )",
-      "   return");
-    List<BuildElement> stmts = getTopLevelNodesOfType(tree, BuildElement.class);
-    assertThat(stmts).hasSize(1);
-    assertNoErrors();
-  }
-
-  public void testFunctionCall() throws Exception {
-    ASTNode tree = createAST("function(name = 'foo', srcs, *args, **kwargs)");
-    List<BuildElement> stmts = getTopLevelNodesOfType(tree, BuildElement.class);
-    assertThat(stmts).hasSize(1);
-    assertThat(treeToString(tree))
-      .isEqualTo(Joiner.on("").join(
-        "function_call(reference, arg_list(",
-        "keyword(string), ",
-        "positional(reference), ",
-        "*(reference), ",
-        "**(reference)))"));
-    assertNoErrors();
-  }
-
-  public void testConditionalStatement() throws Exception {
-    // we don't yet bother specifying which kind of conditionals we hit
-    assertThat(parse("if x : y elif a : b else c"))
-      .isEqualTo(Joiner.on("").join(
-        "if(",
-        "if_part(reference, reference), ",
-        "else_if_part(reference, reference), ",
-        "else_part(reference))"));
-  }
-
-  private ASTNode createAST(String... lines) {
-    StringBuilder builder = new StringBuilder();
-    for (String line : lines) {
-      builder.append(line).append("\n");
-    }
-    return createAST(builder.toString());
-  }
-
-  private ASTNode createAST(String text) {
-    ParserDefinition definition = new BuildParserDefinition();
-    PsiParser parser = definition.createParser(getProject());
-    Lexer lexer = definition.createLexer(getProject());
-    PsiBuilderImpl psiBuilder = new PsiBuilderImpl(getProject(), null, definition, lexer, new CharTableImpl(), text, null, null);
-    PsiBuilderAdapter adapter = new PsiBuilderAdapter(psiBuilder) {
-      @Override
-      public void error(String messageText) {
-        super.error(messageText);
-        errors.add(messageText);
-      }
-    };
-    return parser.parse(definition.getFileNodeType(), adapter);
-  }
-
-  private String parse(String... lines) {
-    StringBuilder builder = new StringBuilder();
-    for (String line : lines) {
-      builder.append(line).append("\n");
-    }
-    return parse(builder.toString());
-  }
-
-  private String parse(String text) {
-    ASTNode tree = createAST(text);
-    return treeToString(tree);
-  }
-
-  private String treeToString(ASTNode tree) {
-    StringBuilder builder = new StringBuilder();
-    nodeToString(tree, builder);
-    return builder.toString();
-  }
-
-  private void nodeToString(ASTNode node, StringBuilder builder) {
-    if (node instanceof LeafElement || node.getPsi() == null) {
-      return;
-    }
-    PsiElement[] childPsis = getChildBuildPsis(node);
-    if (node instanceof FileASTNode) {
-      appendChildren(childPsis, builder, false);
-      return;
-    }
-    builder.append(node.getElementType());
-    appendChildren(childPsis, builder, true);
-  }
-
-  private void appendChildren(PsiElement[] childPsis, StringBuilder builder, boolean bracket) {
-    if (childPsis.length == 0) {
-      return;
-    }
-    if (bracket) {
-      builder.append("(");
-    }
-    nodeToString(childPsis[0].getNode(), builder);
-    for (int i = 1; i < childPsis.length; i++) {
-      builder.append(", ");
-      nodeToString(childPsis[i].getNode(), builder);
-    }
-    if (bracket) {
-      builder.append(")");
-    }
-  }
-
-  private static <T> List<T> getTopLevelNodesOfType(ASTNode node, Class<T> clazz) {
-    return (List) Arrays.stream(node.getChildren(null))
-      .map(ASTNode::getPsi)
-      .filter(psiElement -> clazz.isInstance(psiElement))
-      .collect(Collectors.toList());
-  }
-
-  private PsiElement[] getChildBuildPsis(ASTNode node) {
-    return Arrays.stream(node.getChildren(null))
-      .map(ASTNode::getPsi)
-      .filter(psiElement -> psiElement instanceof BuildElement)
-      .toArray(PsiElement[]::new);
-  }
-
-  private void assertNoErrors() {
-    assertThat(errors).isEmpty();
-  }
-
-  private void assertContainsErrors() {
-    assertThat(errors).isNotEmpty();
-  }
-
-  private void assertContainsError(String message) {
-    assertThat(errors).contains(message);
-  }
-
-}
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/refactor/FileCopyTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/refactor/FileCopyTest.java
deleted file mode 100644
index 0039011..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/refactor/FileCopyTest.java
+++ /dev/null
@@ -1,55 +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.refactor;
-
-import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
-import com.intellij.openapi.command.WriteCommandAction;
-import com.intellij.psi.PsiDirectory;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiFile;
-import com.intellij.refactoring.copy.CopyHandler;
-
-/**
- * Tests copying files
- */
-public class FileCopyTest extends BuildFileIntegrationTestCase {
-
-  public void testCopyingJavaFileReferencedByGlob() {
-    createDirectory("java");
-    PsiFile javaFile = createPsiFile(
-      "java/Test.java",
-      "package java;",
-      "public class Test {}");
-
-    PsiFile javaFile2 = createPsiFile(
-      "java/Test2.java",
-      "package java;",
-      "public class Test2 {}");
-
-    createBuildFile(
-      "java/BUILD",
-      "java_library(",
-      "    name = 'lib',",
-      "    srcs = glob(['**/*.java']),",
-      ")");
-
-    PsiDirectory otherDir = createPsiDirectory("java/other");
-
-    WriteCommandAction.runWriteCommandAction(null, () -> {
-      CopyHandler.doCopy(new PsiElement[] {javaFile, javaFile2}, otherDir);
-    });
-  }
-}
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/refactor/RenameRefactoringTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/refactor/RenameRefactoringTest.java
deleted file mode 100644
index 26e0030..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/refactor/RenameRefactoringTest.java
+++ /dev/null
@@ -1,196 +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.refactor;
-
-import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
-import com.google.idea.blaze.base.lang.buildfile.psi.*;
-import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
-import com.intellij.psi.PsiFile;
-
-import java.util.List;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Tests that BUILD file references are correctly updated when performing rename refactors.
- */
-public class RenameRefactoringTest extends BuildFileIntegrationTestCase {
-
-  public void testRenameJavaClass() {
-    PsiFile javaFile = createPsiFile(
-      "com/google/foo/JavaClass.java",
-      "package com.google.foo;",
-      "public class JavaClass {}");
-
-    BuildFile buildFile = createBuildFile(
-      "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\"])");
-
-    List<StringLiteral> references = findAllReferencingElementsOfType(javaFile, StringLiteral.class);
-    assertThat(references).hasSize(3);
-
-    assertThat(references.get(0).getStringContents()).isEqualTo("//com/google/foo:JavaClass.java");
-    assertThat(references.get(1).getStringContents()).isEqualTo("JavaClass.java");
-    assertThat(references.get(2).getStringContents()).isEqualTo(":JavaClass.java");
-
-    renamePsiElement(javaFile, "NewName.java");
-    assertThat(references.get(0).getStringContents()).isEqualTo("//com/google/foo:NewName.java");
-    assertThat(references.get(1).getStringContents()).isEqualTo("NewName.java");
-    assertThat(references.get(2).getStringContents()).isEqualTo(":NewName.java");
-  }
-
-  public void testRenameRule() {
-    BuildFile fooPackage = createBuildFile(
-      "com/google/foo/BUILD",
-      "rule_type(name = \"target\")",
-      "java_library(name = \"local_ref\", srcs = [\":target\"])");
-
-    BuildFile barPackage = createBuildFile(
-      "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");
-
-    assertFileContents(fooPackage,
-      "rule_type(name = \"newTargetName\")",
-      "java_library(name = \"local_ref\", srcs = [\":newTargetName\"])");
-
-    assertFileContents(barPackage,
-      "rule_type(name = \"ref\", arg = \"//com/google/foo:newTargetName\")",
-      "top_level_ref = \"//com/google/foo:newTargetName\"");
-  }
-
-  public void testRenameSkylarkExtension() {
-    BuildFile extFile = createBuildFile(
-      "java/com/google/tools/build_defs.bzl",
-      "def function(name, deps)");
-
-    BuildFile buildFile = createBuildFile(
-      "java/com/google/BUILD",
-      "load(",
-      "\"//java/com/google:tools/build_defs.bzl\",",
-      "\"function\"",
-      ")",
-      "function(name = \"name\", deps = []");
-
-    renamePsiElement(extFile, "skylark.bzl");
-
-    assertFileContents(buildFile,
-      "load(",
-      "\"//java/com/google:tools/skylark.bzl\",",
-      "\"function\"",
-      ")",
-      "function(name = \"name\", deps = []");
-  }
-
-  public void testRenameLoadedFunction() {
-    BuildFile extFile = createBuildFile(
-      "java/com/google/tools/build_defs.bzl",
-      "def function(name, deps)");
-
-    BuildFile buildFile = createBuildFile(
-      "java/com/google/BUILD",
-      "load(",
-      "\"//java/com/google/tools:build_defs.bzl\",",
-      "\"function\"",
-      ")",
-      "function(name = \"name\", deps = []");
-
-    FunctionStatement fn = extFile.findChildByClass(FunctionStatement.class);
-    renamePsiElement(fn, "action");
-
-    assertFileContents(extFile,
-      "def action(name, deps)");
-
-    assertFileContents(buildFile,
-      "load(",
-      "\"//java/com/google/tools:build_defs.bzl\",",
-      "\"action\"",
-      ")",
-      "action(name = \"name\", deps = []");
-  }
-
-  public void testRenameLocalVariable() {
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "a = 1",
-      "c = a");
-
-    TargetExpression target = PsiUtils.findFirstChildOfClassRecursive(file, TargetExpression.class);
-    assertThat(target.getText()).isEqualTo("a");
-
-    renamePsiElement(target, "b");
-
-    assertFileContents(file,
-      "b = 1",
-      "c = b");
-  }
-
-  // all references, including path fragments in labels, should be renamed.
-  public void testRenameDirectory() {
-    BuildFile bazPackage = createBuildFile("java/com/baz/BUILD");
-    BuildFile toolsSubpackage = createBuildFile("java/com/google/tools/BUILD");
-    BuildFile buildFile = createBuildFile(
-      "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");
-
-    assertFileContents(buildFile,
-      "load(",
-      "\"//java/alt/google/tools:build_defs.bzl\",",
-      "\"function\"",
-      ")",
-      "function(name = \"name\", deps = [\"//java/alt/baz:target\"]");
-  }
-
-  public void testRenameFunctionParameter() {
-    BuildFile extFile = createBuildFile(
-      "java/com/google/tools/build_defs.bzl",
-      "def function(name, deps)");
-
-    BuildFile buildFile = createBuildFile(
-      "java/com/google/BUILD",
-      "load(",
-      "\"//java/com/google/tools:build_defs.bzl\",",
-      "\"function\"",
-      ")",
-      "function(name = \"name\", deps = []");
-
-    FunctionStatement fn = extFile.findChildByClass(FunctionStatement.class);
-    Parameter param = fn.getParameterList().findParameterByName("deps");
-    renamePsiElement(param, "exports");
-
-    assertFileContents(extFile,
-      "def function(name, exports)");
-
-    assertFileContents(buildFile,
-      "load(",
-      "\"//java/com/google/tools:build_defs.bzl\",",
-      "\"function\"",
-      ")",
-      "function(name = \"name\", exports = []");
-  }
-
-}
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/GlobReferenceTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/GlobReferenceTest.java
deleted file mode 100644
index 09ffe7e..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/GlobReferenceTest.java
+++ /dev/null
@@ -1,169 +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.references;
-
-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.GlobExpression;
-import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiFile;
-import com.intellij.psi.ResolveResult;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.Objects;
-import java.util.stream.Collectors;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Tests that glob references are resolved correctly.
- */
-public class GlobReferenceTest extends BuildFileIntegrationTestCase {
-
-  public void testSimpleGlobReferencingSingleFile() {
-    PsiFile ref = createPsiFile("java/com/google/Test.java");
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "glob(['**/*.java'])");
-
-    GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
-    List<PsiElement> references = multiResolve(glob);
-    assertThat(references).hasSize(1);
-    assertThat(references).containsExactly(ref);
-  }
-
-  public void testSimpleGlobReferencingSingleFile2() {
-    PsiFile ref = createPsiFile("java/com/google/Test.java");
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "glob(['*.java'])");
-
-    GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
-    List<PsiElement> references = multiResolve(glob);
-    assertThat(references).hasSize(1);
-    assertThat(references).containsExactly(ref);
-  }
-
-  public void testSimpleGlobReferencingSingleFile3() {
-    PsiFile ref = createPsiFile("java/com/google/Test.java");
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "glob(['T*t.java'])");
-
-    GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
-    List<PsiElement> references = multiResolve(glob);
-    assertThat(references).hasSize(1);
-    assertThat(references).containsExactly(ref);
-  }
-
-  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'])");
-
-    GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
-    List<PsiElement> references = multiResolve(glob);
-    assertThat(references).hasSize(2);
-    assertThat(references).containsExactly(ref1, ref2);
-  }
-
-  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'])");
-
-    GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
-    List<PsiElement> references = multiResolve(glob);
-    assertThat(references).hasSize(2);
-    assertThat(references).containsExactly(ref1, ref2);
-  }
-
-  public void testGlobWithExcludes() {
-    PsiFile test = createPsiFile("java/com/google/tests/Test.java");
-    PsiFile foo = createPsiFile("java/com/google/Foo.java");
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "glob(" +
-      "  ['**/*.java']," +
-      "  exclude = ['tests/*.java'])");
-
-    GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
-    List<PsiElement> references = multiResolve(glob);
-    assertThat(references).hasSize(1);
-    assertThat(references).containsExactly(foo);
-  }
-
-  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");
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "glob(" +
-      "  ['**/*']," +
-      "  exclude = ['BUILD']," +
-      "  exclude_directories = 0)");
-
-    GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
-    List<PsiElement> references = multiResolve(glob);
-    assertThat(references).hasSize(3);
-    assertThat(references).containsExactly(foo, test, test.getParent());
-  }
-
-  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");
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "glob(" +
-      "  ['**/*']," +
-      "  exclude = ['BUILD'])");
-
-    GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
-    List<PsiElement> references = multiResolve(glob);
-    assertThat(references).hasSize(2);
-    assertThat(references).containsExactly(foo, 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");
-
-    GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(pkg, GlobExpression.class);
-    List<PsiElement> references = multiResolve(glob);
-    assertThat(references).isEmpty();
-  }
-
-  private List<PsiElement> multiResolve(GlobExpression glob) {
-    ResolveResult[] result = glob.getReference().multiResolve(false);
-    return Arrays.stream(result)
-      .map(ResolveResult::getElement)
-      .filter(Objects::nonNull)
-      .collect(Collectors.toList());
-  }
-
-
-}
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/KeywordReferenceTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/KeywordReferenceTest.java
deleted file mode 100644
index da60773..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/KeywordReferenceTest.java
+++ /dev/null
@@ -1,55 +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.references;
-
-import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
-import com.google.idea.blaze.base.lang.buildfile.psi.*;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Tests that keyword references are correctly resolved.
- */
-public class KeywordReferenceTest extends BuildFileIntegrationTestCase {
-
-  public void testPlainKeywordReference() {
-    BuildFile file = createBuildFile(
-      "java/com/google/build_defs.bzl",
-      "def function(name, deps)",
-      "function(name = \"name\", deps = [])");
-
-    ParameterList params = file.firstChildOfClass(FunctionStatement.class).getParameterList();
-    assertThat(params.getElements()).hasLength(2);
-
-    ArgumentList args = file.firstChildOfClass(FuncallExpression.class).getArgList();
-    assertThat(args.getKeywordArgument("name").getReferencedElement())
-      .isEqualTo(params.findParameterByName("name"));
-
-    assertThat(args.getKeywordArgument("deps").getReferencedElement())
-      .isEqualTo(params.findParameterByName("deps"));
-  }
-
-  public void testKwargsReference() {
-    BuildFile file = createBuildFile(
-      "java/com/google/build_defs.bzl",
-      "def function(name, **kwargs)",
-      "function(name = \"name\", deps = [])");
-
-    ArgumentList args = file.firstChildOfClass(FuncallExpression.class).getArgList();
-    assertThat(args.getKeywordArgument("deps").getReferencedElement()).isInstanceOf(Parameter.StarStar.class);
-  }
-
-}
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/LabelReferenceTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/LabelReferenceTest.java
deleted file mode 100644
index 09bdbff..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/LabelReferenceTest.java
+++ /dev/null
@@ -1,177 +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.references;
-
-import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
-import com.google.idea.blaze.base.lang.buildfile.psi.Argument;
-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.StringLiteral;
-import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
-import com.google.idea.blaze.base.lang.buildfile.search.FindUsages;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiFile;
-import com.intellij.psi.PsiReference;
-
-import java.util.List;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Tests that string literal references are correctly resolved.
- */
-public class LabelReferenceTest extends BuildFileIntegrationTestCase {
-
-  public void testExternalFileReference() {
-    BuildFile file = createBuildFile(
-      "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");
-
-    List<StringLiteral> strings = PsiUtils.findAllChildrenOfClassRecursive(file, StringLiteral.class);
-    assertThat(strings).hasSize(2);
-    assertThat(strings.get(0).getReferencedElement())
-      .isEqualTo(txtFile);
-    assertThat(strings.get(1).getReferencedElement())
-      .isEqualTo(xmlFile);
-  }
-
-  public void testLocalRuleReference() {
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "java_library(name = \"lib\")",
-      "java_library(name = \"foo\", deps = [\":lib\"])",
-      "java_library(name = \"bar\", deps = [\"//java/com/google:lib\"])");
-
-    FuncallExpression lib = file.findRule("lib");
-    FuncallExpression foo = file.findRule("foo");
-    FuncallExpression bar = file.findRule("bar");
-
-    assertThat(lib).isNotNull();
-
-    StringLiteral label = PsiUtils.findFirstChildOfClassRecursive(foo.getKeywordArgument("deps"), StringLiteral.class);
-    assertThat(label.getReferencedElement()).isEqualTo(lib);
-
-    label = PsiUtils.findFirstChildOfClassRecursive(bar.getKeywordArgument("deps"), StringLiteral.class);
-    assertThat(label.getReferencedElement()).isEqualTo(lib);
-  }
-
-  public void testTargetInAnotherPackageResolves() {
-    BuildFile targetFile = createBuildFile(
-      "java/com/google/foo/BUILD",
-      "rule(name = \"target\")");
-
-    BuildFile referencingFile = createBuildFile(
-      "java/com/google/bar/BUILD",
-      "rule(name = \"other\", dep = \"//java/com/google/foo:target\")");
-
-    FuncallExpression target = targetFile.findRule("target");
-    assertThat(target).isNotNull();
-
-    Argument.Keyword depArgument = referencingFile
-      .findRule("other")
-      .getKeywordArgument("dep");
-
-    assertThat(depArgument.getValue().getReferencedElement())
-      .isEqualTo(target);
-  }
-
-  public void testRuleNameDoesntCrossPackageBoundaries() {
-    BuildFile targetFile = createBuildFile(
-      "java/com/google/pkg/subpkg/BUILD",
-      "rule(name = \"target\")");
-
-    BuildFile referencingFile = createBuildFile(
-      "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");
-    assertThat(ref.resolve()).isNotNull();
-    assertThat(ref.resolve()).isEqualTo(targetFile.findRule("target"));
-  }
-
-  public void testLabelWithImplicitRuleName() {
-    BuildFile targetFile = createBuildFile(
-      "java/com/google/foo/BUILD",
-      "rule(name = \"foo\")");
-
-    BuildFile referencingFile = createBuildFile(
-      "java/com/google/bar/BUILD",
-      "rule(name = \"other\", dep = \"//java/com/google/foo\")");
-
-    FuncallExpression target = targetFile.findRule("foo");
-    assertThat(target).isNotNull();
-
-    Argument.Keyword depArgument = referencingFile
-      .findRule("other")
-      .getKeywordArgument("dep");
-
-    assertThat(depArgument.getValue().getReferencedElement())
-      .isEqualTo(target);
-  }
-
-  public void testAbsoluteLabelInSkylarkExtension() {
-    BuildFile targetFile = createBuildFile(
-      "java/com/google/foo/BUILD",
-      "rule(name = \"foo\")");
-
-    BuildFile referencingFile = createBuildFile(
-      "java/com/google/foo/skylark.bzl",
-      "LIST = ['//java/com/google/foo:foo']");
-
-    FuncallExpression target = targetFile.findRule("foo");
-    assertThat(target).isNotNull();
-
-    StringLiteral label = PsiUtils.findFirstChildOfClassRecursive(referencingFile, StringLiteral.class);
-    assertThat(label.getReferencedElement()).isEqualTo(target);
-  }
-
-  public void testRulePreferredOverFile() {
-    BuildFile targetFile = createBuildFile(
-      "java/com/foo/BUILD",
-      "java_library(name = 'lib')");
-
-    createDirectory("java/com/foo/lib");
-
-    BuildFile referencingFile = createBuildFile(
-      "java/com/google/bar/BUILD",
-      "java_library(",
-      "    name = 'bar',",
-      "    src = glob(['**/*.java'])," +
-      "    deps = ['//java/com/foo:lib'],",
-      ")");
-
-    FuncallExpression target = targetFile.findRule("lib");
-    assertThat(target).isNotNull();
-
-    PsiReference[] references = FindUsages.findAllReferences(target);
-    assertThat(references).hasLength(1);
-
-    PsiElement element = references[0].getElement();
-    FuncallExpression rule = PsiUtils.getParentOfType(element, FuncallExpression.class);
-    assertThat(rule.getName()).isEqualTo("bar");
-    assertThat(rule.getContainingFile()).isEqualTo(referencingFile);
-  }
-}
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/LoadedSkylarkExtensionTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/LoadedSkylarkExtensionTest.java
deleted file mode 100644
index d1c6356..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/LoadedSkylarkExtensionTest.java
+++ /dev/null
@@ -1,150 +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.references;
-
-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.FuncallExpression;
-import com.google.idea.blaze.base.lang.buildfile.psi.FunctionStatement;
-import com.google.idea.blaze.base.lang.buildfile.psi.LoadStatement;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Tests that funcall references and load statement contents are correctly resolved.
- */
-public class LoadedSkylarkExtensionTest extends BuildFileIntegrationTestCase {
-
-  public void testStandardLoadReference() {
-    BuildFile extFile = createBuildFile(
-      "java/com/google/build_defs.bzl",
-      "def function(name, deps)");
-
-    BuildFile buildFile = createBuildFile(
-      "java/com/google/BUILD",
-      "load(",
-      "\"//java/com/google:build_defs.bzl\",",
-      "\"function\"",
-      ")");
-
-    LoadStatement load = buildFile.firstChildOfClass(LoadStatement.class);
-    assertThat(load.getImportPsiElement().getReferencedElement()).isEqualTo(extFile);
-
-    FunctionStatement function = extFile.firstChildOfClass(FunctionStatement.class);
-    assertThat(function).isNotNull();
-
-    assertThat(load.getImportedSymbolElements()).hasLength(1);
-    assertThat(load.getImportedSymbolElements()[0].getReferencedElement()).isEqualTo(function);
-  }
-
-  // TODO: If we want to support this deprecated format, we should start by relaxing the ":" requirement in Label
-  //public void testDeprecatedImportLabelFormat() {
-  //  BuildFile extFile = createBuildFile(
-  //    "java/com/google/build_defs.bzl",
-  //    "def function(name, deps)");
-  //
-  //  BuildFile buildFile = createBuildFile(
-  //    "java/com/google/tools/BUILD",
-  //    "load(",
-  //    "\"//java/com/google/build_defs.bzl\",",
-  //    "\"function\"",
-  //    ")");
-  //
-  //  LoadStatement load = buildFile.firstChildOfClass(LoadStatement.class);
-  //  assertThat(load.getImportPsiElement().getReferencedElement()).isEqualTo(extFile);
-  //}
-
-  public void testPackageLocalImportLabelFormat() {
-    BuildFile extFile = createBuildFile(
-      "java/com/google/tools/build_defs.bzl",
-      "def function(name, deps)");
-
-    BuildFile buildFile = createBuildFile(
-      "java/com/google/tools/BUILD",
-      "load(",
-      "\":build_defs.bzl\",",
-      "\"function\"",
-      ")");
-
-    LoadStatement load = buildFile.firstChildOfClass(LoadStatement.class);
-    assertThat(load.getImportPsiElement().getReferencedElement()).isEqualTo(extFile);
-  }
-
-  public void testMultipleImportedFunctions() {
-    BuildFile extFile = createBuildFile(
-      "java/com/google/build_defs.bzl",
-      "def fn1(name, deps)",
-      "def fn2(name, deps)");
-
-    BuildFile buildFile = createBuildFile(
-      "java/com/google/BUILD",
-      "load(",
-      "\"//java/com/google:build_defs.bzl\",",
-      "\"fn1\"",
-      "\"fn2\"",
-      ")");
-
-    LoadStatement load = buildFile.firstChildOfClass(LoadStatement.class);
-    assertThat(load.getImportPsiElement().getReferencedElement()).isEqualTo(extFile);
-
-    FunctionStatement[] functions = extFile.childrenOfClass(FunctionStatement.class);
-    assertThat(functions).hasLength(2);
-    assertThat(load.getImportedFunctionReferences()).isEqualTo(functions);
-  }
-
-  public void testFuncallReference() {
-    BuildFile extFile = createBuildFile(
-      "java/com/google/tools/build_defs.bzl",
-      "def function(name, deps)");
-
-    BuildFile buildFile = createBuildFile(
-      "java/com/google/BUILD",
-      "load(",
-      "\"//java/com/google/tools:build_defs.bzl\",",
-      "\"function\"",
-      ")",
-      "function(name = \"name\", deps = []");
-
-    FunctionStatement function = extFile.firstChildOfClass(FunctionStatement.class);
-    FuncallExpression funcall = buildFile.firstChildOfClass(FuncallExpression.class);
-
-    assertThat(function).isNotNull();
-    assertThat(funcall.getReferencedElement()).isEqualTo(function);
-  }
-
-  // relative paths in skylark extensions which lie in subdirectories are relative to the parent blaze package directory
-  public void testRelativePathInSubdirectory() {
-    createFile("java/com/google/BUILD");
-    BuildFile referencedFile = createBuildFile(
-      "java/com/google/nonPackageSubdirectory/skylark.bzl",
-      "def function(): return");
-    BuildFile file = createBuildFile(
-      "java/com/google/nonPackageSubdirectory/other.bzl",
-      "load(" +
-      "    ':nonPackageSubdirectory/skylark.bzl',",
-      "    'function',",
-      ")",
-      "function()"
-    );
-
-    FunctionStatement function = referencedFile.firstChildOfClass(FunctionStatement.class);
-    FuncallExpression funcall = file.firstChildOfClass(FuncallExpression.class);
-
-    assertThat(function).isNotNull();
-    assertThat(funcall.getReferencedElement()).isEqualTo(function);
-  }
-
-}
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/LocalReferenceTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/LocalReferenceTest.java
deleted file mode 100644
index b50325f..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/LocalReferenceTest.java
+++ /dev/null
@@ -1,68 +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.references;
-
-import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
-import com.google.idea.blaze.base.lang.buildfile.psi.*;
-import com.intellij.psi.PsiElement;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Tests that local references (to TargetExpressions within a given file) are correctly resolved.
- */
-public class LocalReferenceTest extends BuildFileIntegrationTestCase {
-
-  public void testCreatesReference() {
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "a = 1",
-      "c = a");
-
-    AssignmentStatement[] stmts = file.childrenOfClass(AssignmentStatement.class);
-    assertThat(stmts).hasLength(2);
-    assertThat(stmts[1].getAssignedValue()).isInstanceOf(ReferenceExpression.class);
-
-    ReferenceExpression ref = (ReferenceExpression) stmts[1].getAssignedValue();
-    assertThat(ref.getReference()).isInstanceOf(LocalReference.class);
-  }
-
-  public void testReferenceResolves() {
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "a = 1",
-      "c = a");
-
-    AssignmentStatement[] stmts = file.childrenOfClass(AssignmentStatement.class);
-    ReferenceExpression ref = (ReferenceExpression) stmts[1].getAssignedValue();
-
-    PsiElement referencedElement = ref.getReferencedElement();
-    assertThat(referencedElement).isEqualTo(stmts[0].getLeftHandSideExpression());
-  }
-
-  public void testTargetInOuterScope() {
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "a = 1",
-      "function(c = a)");
-
-    TargetExpression target = file.findChildByClass(TargetExpression.class);
-    FuncallExpression funcall = file.findChildByClass(FuncallExpression.class);
-    ReferenceExpression ref = funcall.firstChildOfClass(ReferenceExpression.class);
-    assertThat(ref.getReferencedElement()).isEqualTo(target);
-  }
-
-}
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/PackageReferenceTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/PackageReferenceTest.java
deleted file mode 100644
index eed179a..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/PackageReferenceTest.java
+++ /dev/null
@@ -1,68 +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.references;
-
-import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
-import com.google.idea.blaze.base.lang.buildfile.psi.Argument;
-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.StringLiteral;
-import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
-import com.intellij.psi.PsiReference;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Tests that package references in string literals are correctly resolved.
- */
-public class PackageReferenceTest extends BuildFileIntegrationTestCase {
-
-  public void testDirectReferenceResolves() {
-    BuildFile buildFile1 = createBuildFile(
-      "java/com/google/tools/BUILD",
-      "# contents");
-
-    BuildFile buildFile2 = createBuildFile(
-      "java/com/google/other/BUILD",
-      "package_group(name = \"grp\", packages = [\"//java/com/google/tools\"])");
-
-    Argument.Keyword packagesArg = buildFile2.firstChildOfClass(FuncallExpression.class).getArgList().getKeywordArgument("packages");
-    StringLiteral string = PsiUtils.findFirstChildOfClassRecursive(packagesArg, StringLiteral.class);
-    assertThat(string.getReferencedElement()).isEqualTo(buildFile1);
-  }
-
-  public void testLabelFragmentResolves() {
-    BuildFile buildFile1 = createBuildFile(
-      "java/com/google/tools/BUILD",
-      "java_library(name = \"lib\")");
-
-    BuildFile buildFile2 = createBuildFile(
-      "java/com/google/other/BUILD",
-      "java_library(name = \"lib2\", exports = [\"//java/com/google/tools:lib\"])");
-
-    FuncallExpression libTarget = buildFile1.firstChildOfClass(FuncallExpression.class);
-    assertThat(libTarget).isNotNull();
-
-    Argument.Keyword packagesArg = buildFile2.firstChildOfClass(FuncallExpression.class).getArgList().getKeywordArgument("exports");
-    StringLiteral string = PsiUtils.findFirstChildOfClassRecursive(packagesArg, StringLiteral.class);
-
-    PsiReference[] references = string.getReferences();
-    assertThat(references).hasLength(2);
-    assertThat(references[0].resolve()).isEqualTo(libTarget);
-    assertThat(references[1].resolve()).isEqualTo(buildFile1);
-  }
-
-}
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/search/BlazePackageTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/search/BlazePackageTest.java
deleted file mode 100644
index c7e9240..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/search/BlazePackageTest.java
+++ /dev/null
@@ -1,66 +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.search;
-
-import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
-import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
-import com.intellij.psi.PsiFile;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Tests for BlazePackage
- */
-public class BlazePackageTest extends BuildFileIntegrationTestCase {
-
-  public void testFindPackage() {
-    BuildFile packageFile = createBuildFile("java/com/google/BUILD");
-    PsiFile subDirFile = createPsiFile("java/com/google/tools/test.txt");
-    BlazePackage blazePackage = BlazePackage.getContainingPackage(subDirFile);
-    assertThat(blazePackage).isNotNull();
-    assertThat(blazePackage.buildFile).isEqualTo(packageFile);
-  }
-
-  public void testScopeDoesntCrossPackageBoundary() {
-    BuildFile pkg = createBuildFile("java/com/google/BUILD");
-    BuildFile subpkg = createBuildFile("java/com/google/other/BUILD");
-
-    BlazePackage blazePackage = BlazePackage.getContainingPackage(pkg);
-    assertThat(blazePackage.buildFile).isEqualTo(pkg);
-    assertFalse(blazePackage.getSearchScope(false).contains(subpkg.getVirtualFile()));
-  }
-
-  public void testScopeIncludesSubdirectoriesWhichAreNotBlazePackages() {
-    BuildFile pkg = createBuildFile("java/com/google/BUILD");
-    BuildFile subpkg = createBuildFile("java/com/google/foo/bar/BUILD");
-    PsiFile subDirFile = createPsiFile("java/com/google/foo/test.txt");
-
-    BlazePackage blazePackage = BlazePackage.getContainingPackage(subDirFile);
-    assertThat(blazePackage.buildFile).isEqualTo(pkg);
-    assertTrue(blazePackage.getSearchScope(false).contains(subDirFile.getVirtualFile()));
-  }
-
-  public void testScopeLimitedToBlazeFiles() {
-    BuildFile pkg = createBuildFile("java/com/google/BUILD");
-    BuildFile subpkg = createBuildFile("java/com/google/foo/bar/BUILD");
-    PsiFile subDirFile = createPsiFile("java/com/google/foo/test.txt");
-
-    BlazePackage blazePackage = BlazePackage.getContainingPackage(subDirFile);
-    assertThat(blazePackage.buildFile).isEqualTo(pkg);
-    assertFalse(blazePackage.getSearchScope(true).contains(subDirFile.getVirtualFile()));
-  }
-
-}
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/search/GlobalWordIndexTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/search/GlobalWordIndexTest.java
deleted file mode 100644
index 83350b1..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/search/GlobalWordIndexTest.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.lang.buildfile.search;
-
-import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.psi.impl.cache.CacheManager;
-import com.intellij.psi.search.GlobalSearchScope;
-import com.intellij.psi.search.UsageSearchContext;
-import org.intellij.lang.annotations.MagicConstant;
-
-import java.util.Arrays;
-
-/**
- * Test the WordScanner indexes keywords in the way we expect.<br>
- * This is vital for navigation, refactoring, highlighting etc.
- */
-public class GlobalWordIndexTest extends BuildFileIntegrationTestCase {
-
-  public void testWordsInComments() {
-    VirtualFile file = createFile("java/com/google/BUILD",
-                                  "# words in comments");
-    assertContainsWords(file, UsageSearchContext.IN_COMMENTS, "words", "in", "comments");
-  }
-
-  public void testWordsInStrings() {
-    VirtualFile file = createFile("java/com/google/BUILD",
-                                  "name = \"long name with spaces\",",
-                                  "src = [\"name_without_spaces\"]");
-    assertContainsWords(file, UsageSearchContext.IN_STRINGS, "long", "name", "with", "spaces", "name_without_spaces");
-  }
-
-  public void testWordsInCode() {
-    VirtualFile file = createFile("java/com/google/BUILD",
-                                  "java_library(",
-                                  "name = \"long name with spaces\",",
-                                  "src = [\"name_without_spaces\"]",
-                                  ")");
-    assertContainsWords(file, UsageSearchContext.IN_CODE, "java_library", "name", "src");
-  }
-
-  private void assertContainsWords(
-    VirtualFile file,
-    @MagicConstant(flagsFromClass = UsageSearchContext.class) short occurenceMask,
-    String... words) {
-
-    for (String word : words) {
-      VirtualFile[] files = CacheManager.SERVICE.getInstance(getProject()).getVirtualFilesWithWord(
-        word,
-        occurenceMask,
-        GlobalSearchScope.fileScope(getProject(), file),
-        true);
-      if (!Arrays.asList(files).contains(file)) {
-        fail(String.format("Word '%s' not found in file '%s'", word, file));
-      }
-    }
-  }
-
-}
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/validation/GlobValidationTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/validation/GlobValidationTest.java
deleted file mode 100644
index 108bfbc..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/validation/GlobValidationTest.java
+++ /dev/null
@@ -1,258 +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.validation;
-
-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.GlobExpression;
-import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
-import com.intellij.codeInsight.daemon.impl.AnnotationHolderImpl;
-import com.intellij.lang.annotation.Annotation;
-import com.intellij.lang.annotation.AnnotationHolder;
-import com.intellij.lang.annotation.AnnotationSession;
-import com.intellij.psi.PsiFile;
-
-import java.util.List;
-import java.util.stream.Collectors;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Tests glob validation.
- */
-public class GlobValidationTest extends BuildFileIntegrationTestCase {
-
-  public void testNormalGlob() {
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "glob(['**/*.java'])");
-
-    assertNoErrors(file);
-  }
-
-  public void testNamedIncludeArgument() {
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "glob(include = ['**/*.java'])");
-
-    assertNoErrors(file);
-  }
-
-  public void testAllArguments() {
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "glob(['**/*.java'], exclude = ['test/*.java'], exclude_directories = 0)");
-
-    assertNoErrors(file);
-  }
-
-  public void testEmptyExcludeList() {
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "glob(['**/*.java'], exclude = [])");
-
-    assertNoErrors(file);
-  }
-
-  public void testNoIncludesError() {
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "glob(exclude = ['BUILD'])");
-
-    assertHasError(file, "Glob expression must contain at least one included string");
-  }
-
-  public void testSingletonExcludeArgumentError() {
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "glob(['**/*.java'], exclude = 'BUILD')");
-
-    assertHasError(file, "Glob parameter 'exclude' must be a list of strings");
-  }
-
-  public void testSingletonIncludeArgumentError() {
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "glob(include = '**/*.java')");
-
-    assertHasError(file, "Glob parameter 'include' must be a list of strings");
-  }
-
-  public void testInvalidExcludeDirectoriesValue() {
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "glob(['**/*.java'], exclude = ['test/*.java'], exclude_directories = true)");
-
-    assertHasError(file, "exclude_directories parameter to glob must be 0 or 1");
-  }
-
-  public void testUnrecognizedArgumentError() {
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "glob(['**/*.java'], exclude = ['test/*.java'], extra = 1)");
-
-    assertHasError(file, "Unrecognized glob argument");
-  }
-
-  public void testInvalidListArgumentValue() {
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "glob(include = foo)");
-
-    assertHasError(file, "Glob parameter 'include' must be a list of strings");
-  }
-
-  public void testLocalVariableReference() {
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "foo = ['*.java']",
-      "glob(include = foo)");
-
-    assertNoErrors(file);
-  }
-
-  public void testLoadedVariableReference() {
-    BuildFile ext = createBuildFile(
-      "java/com/foo/vars.bzl",
-      "LIST_VAR = ['*']");
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "load('//java/com/foo:vars.bzl', 'LIST_VAR')",
-      "glob(include = LIST_VAR)");
-
-    assertNoErrors(file);
-  }
-
-  public void testInvalidLoadedVariableReference() {
-    BuildFile ext = createBuildFile(
-      "java/com/foo/vars.bzl",
-      "LIST_VAR = ['*']",
-      "def function()");
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "load('//java/com/foo:vars.bzl', 'LIST_VAR', 'function')",
-      "glob(include = function)");
-
-    assertHasError(file, "Glob parameter 'include' must be a list of strings");
-  }
-
-  public void testUnresolvedReferenceExpression() {
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "glob(include = ref)");
-
-    assertHasError(file, "Glob parameter 'include' must be a list of strings");
-  }
-
-  public void testPossibleListExpressionFuncallExpression() {
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "glob(include = fn.list)");
-
-    assertNoErrors(file);
-  }
-
-  public void testPossibleListExpressionParameter() {
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "def function(param1, param2):",
-      "    glob(include = param1)");
-
-    assertNoErrors(file);
-  }
-
-  public void testNestedGlobs() {
-    // blaze accepts nested globs
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "glob(glob(['*.java']))");
-
-    assertNoErrors(file);
-  }
-
-  public void testKnownInvalidResolvedListExpression() {
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "bool_literal = True",
-      "glob(bool_literal)");
-
-    assertHasError(file, "Glob parameter 'include' must be a list of strings");
-  }
-
-  public void testKnownInvalidResolvedString() {
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "bool_literal = True",
-      "glob([bool_literal])");
-
-    assertHasError(file, "Glob parameter 'include' must be a list of strings");
-  }
-
-  public void testPossibleStringLiteralIfStatement() {
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "glob(include = ['*.java', if test : a else b])");
-
-    // we don't know what the IfStatement evaluates to
-    assertNoErrors(file);
-  }
-
-  public void testPossibleStringLiteralParameter() {
-    BuildFile file = createBuildFile(
-      "java/com/google/BUILD",
-      "def function(param1, param2):",
-      "    glob(include = [param1])");
-
-    assertNoErrors(file);
-  }
-
-  private void assertNoErrors(BuildFile file) {
-    assertThat(validateFile(file)).isEmpty();
-  }
-
-  private void assertHasError(BuildFile file, String error) {
-    assertHasError(validateFile(file), error);
-  }
-
-  private void assertHasError(List<Annotation> annotations, String error) {
-    List<String> messages = annotations.stream()
-      .map(Annotation::getMessage)
-      .collect(Collectors.toList());
-
-    assertThat(messages).contains(error);
-  }
-
-  private List<Annotation> validateFile(BuildFile file) {
-    GlobErrorAnnotator annotator = createAnnotator(file);
-    for (GlobExpression glob : PsiUtils.findAllChildrenOfClassRecursive(file, GlobExpression.class)) {
-      annotator.visitGlobExpression(glob);
-    }
-    return annotationHolder;
-  }
-
-  private GlobErrorAnnotator createAnnotator(PsiFile file) {
-    annotationHolder = new AnnotationHolderImpl(new AnnotationSession(file));
-    return new GlobErrorAnnotator() {
-      @Override
-      protected AnnotationHolder getHolder() {
-        return annotationHolder;
-      }
-    };
-  }
-
-  private AnnotationHolderImpl annotationHolder = null;
-
-}
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/projectview/ProjectViewCompletionTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/projectview/ProjectViewCompletionTest.java
deleted file mode 100644
index a9b4b79..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/projectview/ProjectViewCompletionTest.java
+++ /dev/null
@@ -1,210 +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.projectview;
-
-import com.google.common.base.Joiner;
-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.section.SectionParser;
-import com.google.idea.blaze.base.projectview.section.sections.Sections;
-import com.intellij.codeInsight.lookup.Lookup;
-import com.intellij.codeInsight.lookup.LookupElement;
-import com.intellij.psi.PsiFile;
-
-import java.util.Arrays;
-import java.util.stream.Collectors;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Tests auto-complete in project view files
- */
-public class ProjectViewCompletionTest extends ProjectViewIntegrationTestCase {
-
-  private PsiFile setInput(String... fileContents) {
-    return testFixture.configureByText(".blazeproject", Joiner.on("\n").join(fileContents));
-  }
-
-  private void assertResult(String... resultingFileContents) {
-    String s = testFixture.getFile().getText();
-    testFixture.checkResult(Joiner.on("\n").join(resultingFileContents));
-  }
-
-  public void testSectionTypeKeywords() {
-    setInput(
-      "<caret>");
-    String[] keywords = getCompletionItemsAsStrings();
-
-    assertThat(keywords).asList().containsAllIn(
-      Sections.getUndeprecatedParsers().stream().map(SectionParser::getName).collect(Collectors.toList()));
-  }
-
-  public void testColonAndNewLineAndIndentInsertedAfterListSection() {
-    setInput(
-      "direc<caret>");
-    assertThat(completeIfUnique()).isTrue();
-    assertResult(
-      "directories:",
-      "  <caret>");
-  }
-
-  public void testWhitespaceDividerInsertedAfterScalarSection() {
-    setInput(
-      "impo<caret>");
-
-    LookupElement[] completionItems = testFixture.completeBasic();
-    assertThat(completionItems[0].getLookupString()).isEqualTo("import");
-
-    testFixture.getLookup().setCurrentItem(completionItems[0]);
-    testFixture.finishLookup(Lookup.NORMAL_SELECT_CHAR);
-
-    assertResult(
-      "import <caret>");
-  }
-
-  public void testColonDividerAndSpaceInsertedAfterScalarSection() {
-    setInput(
-      "works<caret>");
-    assertThat(completeIfUnique()).isTrue();
-    assertResult(
-      "workspace_type: <caret>");
-  }
-
-  public void testNoKeywordCompletionInListItem() {
-    setInput(
-      "directories:",
-      "  <caret>");
-
-    String[] completionItems = getCompletionItemsAsStrings();
-    if (completionItems == null) {
-      fail("Spurious completion. New file contents: " + testFixture.getFile().getText());
-    }
-    assertThat(completionItems).isEmpty();
-  }
-
-  public void testNoKeywordCompletionAfterKeyword() {
-    setInput(
-      "import <caret>");
-
-    String[] completionItems = getCompletionItemsAsStrings();
-    if (completionItems == null) {
-      fail("Spurious completion. New file contents: " + testFixture.getFile().getText());
-    }
-    assertThat(completionItems).isEmpty();
-  }
-
-  public void testWorkspaceTypeCompletion() {
-    setInput(
-      "workspace_type: <caret>");
-
-    String[] types = getCompletionItemsAsStrings();
-
-    assertThat(types).asList().containsAllIn(
-      Arrays.stream(WorkspaceType.values()).map(WorkspaceType::getName).collect(Collectors.toList()));
-  }
-
-  public void testAdditionalLanguagesCompletion() {
-    setInput(
-      "additional_languages:",
-      "  <caret>");
-
-    String[] types = getCompletionItemsAsStrings();
-
-    assertThat(types).asList().containsAllIn(
-      Arrays.stream(LanguageClass.values()).map(LanguageClass::getName).collect(Collectors.toList()));
-  }
-
-  public void testUniqueDirectoryCompleted() {
-    setInput(
-      "import <caret>");
-
-    createDirectory("java");
-
-    String[] completionItems = getCompletionItemsAsStrings();
-    assertThat(completionItems).isNull();
-    assertResult(
-      "import java<caret>"
-    );
-  }
-
-  public void testUniqueMultiSegmentDirectoryCompleted() {
-    setInput(
-      "import <caret>");
-
-    createDirectory("java/com/google");
-
-    String[] completionItems = getCompletionItemsAsStrings();
-    assertThat(completionItems).isNull();
-    assertResult(
-      "import java/com/google<caret>"
-    );
-  }
-
-  public void testNonDirectoriesIgnored() {
-    setInput(
-      "import <caret>");
-
-    createDirectory("java/com/google");
-    createFile("java/IgnoredFile.java");
-
-    String[] completionItems = getCompletionItemsAsStrings();
-    assertThat(completionItems).isNull();
-    assertResult(
-      "import java/com/google<caret>"
-    );
-  }
-
-  public void testMultipleDirectoryOptions() {
-    createDirectory("foo");
-    createDirectory("bar");
-    createDirectory("other");
-    createDirectory("ostrich/foo");
-    createDirectory("ostrich/fooz");
-
-    setInput(
-      "targets:",
-      "  //o<caret>");
-
-    String[] completionItems = getCompletionItemsAsSuggestionStrings();
-    assertThat(completionItems).asList().containsExactly("other", "ostrich");
-
-    performTypingAction(testFixture.getEditor(), 's');
-
-    completionItems = getCompletionItemsAsStrings();
-    assertThat(completionItems).isNull();
-    assertResult(
-      "targets:",
-      "  //ostrich<caret>");
-  }
-
-  public void testRuleCompletion() {
-    createFile(
-      "BUILD",
-      "java_library(name = 'lib')"
-    );
-
-    setInput(
-      "targets:",
-      "  //:<caret>");
-
-    String[] completionItems = getCompletionItemsAsSuggestionStrings();
-    assertThat(completionItems).isNull();
-    assertResult(
-      "targets:",
-      "  //:lib<caret>");
-  }
-
-}
\ No newline at end of file
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/projectview/ProjectViewIntegrationTestCase.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/projectview/ProjectViewIntegrationTestCase.java
deleted file mode 100644
index a282bdb..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/projectview/ProjectViewIntegrationTestCase.java
+++ /dev/null
@@ -1,54 +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.projectview;
-
-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.model.BlazeProjectData;
-import com.google.idea.blaze.base.model.primitives.ExecutionRootPath;
-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.WorkspacePathResolverImpl;
-
-/**
- * Project view file specific integration test base
- */
-public abstract class ProjectViewIntegrationTestCase extends BlazeIntegrationTestCase {
-
-  @Override
-  protected void doSetup() {
-    mockBlazeProjectDataManager(getMockBlazeProjectData());
-  }
-
-  private BlazeProjectData getMockBlazeProjectData() {
-    BlazeRoots fakeRoots = new BlazeRoots(
-      null,
-      ImmutableList.of(workspaceRoot.directory()),
-      new ExecutionRootPath("out/crosstool/bin"),
-      new ExecutionRootPath("out/crosstool/gen")
-    );
-    return new BlazeProjectData(0,
-                                ImmutableMap.of(),
-                                fakeRoots,
-                                new WorkingSet(ImmutableList.of(), ImmutableList.of(), ImmutableList.of()),
-                                new WorkspacePathResolverImpl(workspaceRoot, fakeRoots),
-                                null,
-                                null,
-                                null);
-  }
-
-}
\ No newline at end of file
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/projectview/ProjectViewParserTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/projectview/ProjectViewParserTest.java
deleted file mode 100644
index b6a081c..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/projectview/ProjectViewParserTest.java
+++ /dev/null
@@ -1,153 +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.projectview;
-
-import com.google.common.base.Joiner;
-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.intellij.psi.PsiElement;
-import com.intellij.psi.PsiErrorElement;
-import com.intellij.psi.PsiFile;
-import com.intellij.psi.impl.source.tree.LeafElement;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.stream.Collectors;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Tests for the project view file parser
- */
-public class ProjectViewParserTest extends ProjectViewIntegrationTestCase {
-
-  private final List<String> errors = Lists.newArrayList();
-
-  @Override
-  protected void doSetup() {
-    errors.clear();
-    super.doSetup();
-  }
-
-  public void testStandardFile() {
-    assertThat(parse(
-      "directories:",
-      "  java/com/google/work",
-      "  java/com/google/other",
-      "",
-      "targets:",
-      "  //java/com/google/work/...:all",
-      "  //java/com/google/other/...:all"
-    )).isEqualTo(Joiner.on("").join(
-      "list_section(list_item, list_item), ",
-      "list_section(list_item, list_item)"
-    ));
-    assertNoErrors();
-  }
-
-  public void testIncludeScalarSections() {
-    assertThat(parse(
-      "import java/com/google/work/.blazeproject",
-      "",
-      "workspace_type: intellij_plugin",
-      "",
-      "import_target_output:",
-      "  //java/com/google/work:target",
-      "",
-      "excluded_libraries:",
-      "  java/com/google/common/*"
-    )).isEqualTo(Joiner.on("").join(
-      "scalar_section(scalar_item), ",
-      "scalar_section(scalar_item), ",
-      "list_section(list_item), ",
-      "list_section(list_item)"
-    ));
-    assertNoErrors();
-  }
-
-  public void testUnrecognizedKeyword() {
-    parse(
-      "impart java/com/google/work/.blazeproject",
-      "",
-      "workspace_trype: intellij_plugin"
-    );
-
-    assertContainsErrors(
-      "Unrecognized keyword: impart",
-      "Unrecognized keyword: workspace_trype");
-  }
-
-  private String parse(String... lines) {
-    PsiFile file = createPsiFile(".blazeproject", lines);
-    collectErrors(file);
-    return treeToString(file);
-  }
-
-  private String treeToString(PsiElement psi) {
-    StringBuilder builder = new StringBuilder();
-    nodeToString(psi, builder);
-    return builder.toString();
-  }
-
-  private void nodeToString(PsiElement psi, StringBuilder builder) {
-    if (psi.getNode() instanceof LeafElement) {
-      return;
-    }
-    PsiElement[] children = Arrays.stream(psi.getChildren())
-      .filter(t -> t instanceof ProjectViewPsiElement)
-      .toArray(PsiElement[]::new);
-    if (psi instanceof ProjectViewPsiElement) {
-      builder.append(psi.getNode().getElementType());
-      appendChildren(children, builder, true);
-    } else {
-      appendChildren(children, builder, false);
-    }
-  }
-
-  private void appendChildren(PsiElement[] childPsis, StringBuilder builder, boolean bracket) {
-    if (childPsis.length == 0) {
-      return;
-    }
-    if (bracket) {
-      builder.append("(");
-    }
-    nodeToString(childPsis[0], builder);
-    for (int i = 1; i < childPsis.length; i++) {
-      builder.append(", ");
-      nodeToString(childPsis[i], builder);
-    }
-    if (bracket) {
-      builder.append(")");
-    }
-  }
-
-  private void assertNoErrors() {
-    assertThat(errors).isEmpty();
-  }
-
-  private void assertContainsErrors(String... errors) {
-    assertThat(this.errors).containsAllIn(Arrays.asList(errors));
-  }
-
-  private void collectErrors(PsiElement psi) {
-    errors.addAll(PsiUtils.findAllChildrenOfClassRecursive(psi, PsiErrorElement.class)
-                    .stream()
-                    .map(PsiErrorElement::getErrorDescription)
-                    .collect(Collectors.toList()));
-  }
-
-}
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/projectview/lexer/ProjectViewLexerTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/projectview/lexer/ProjectViewLexerTest.java
deleted file mode 100644
index f37da9e..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/lang/projectview/lexer/ProjectViewLexerTest.java
+++ /dev/null
@@ -1,130 +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.projectview.lexer;
-
-import com.google.common.base.Joiner;
-import com.google.idea.blaze.base.lang.projectview.ProjectViewIntegrationTestCase;
-import com.google.idea.blaze.base.lang.projectview.lexer.ProjectViewLexerBase.Token;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Tests for the project view file lexer
- */
-public class ProjectViewLexerTest extends ProjectViewIntegrationTestCase {
-
-  public void testStandardCase() {
-    String result = tokenize(
-      "directories:",
-      "  java/com/google/work",
-      "  java/com/google/other",
-      "",
-      "targets:",
-      "  //java/com/google/work/...:all",
-      "  //java/com/google/other/...:all");
-
-    assertThat(result).isEqualTo(Joiner.on(" ").join(
-      "list_keyword :",
-      "indent identifier",
-      "indent identifier",
-      "list_keyword :",
-      "indent identifier : identifier",
-      "indent identifier : identifier"));
-  }
-
-  public void testIncludeScalarSections() {
-    String result = tokenize(
-      "import java/com/google/work/.blazeproject",
-      "",
-      "workspace_type: intellij_plugin",
-      "",
-      "import_target_output:",
-      "  //java/com/google/work:target",
-      "",
-      "excluded_libraries:",
-      "  java/com/google/common/*");
-
-    assertThat(result).isEqualTo(Joiner.on(" ").join(
-      "scalar_keyword identifier",
-      "scalar_keyword : identifier",
-      "list_keyword :",
-      "indent identifier : identifier",
-      "list_keyword :",
-      "indent identifier"));
-  }
-
-  public void testUnrecognizedKeyword() {
-    String result = tokenize(
-      "impart java/com/google/work/.blazeproject",
-      "",
-      "workspace_trype: intellij_plugin");
-
-    assertThat(result).isEqualTo(Joiner.on(" ").join(
-      "identifier identifier",
-      "identifier : identifier"));
-  }
-
-  private static String tokenize(String... lines) {
-    return names(tokens(Joiner.on("\n").join(lines)));
-  }
-
-  private static Token[] tokens(String input) {
-    Token[] tokens = new ProjectViewLexerBase(input).getTokens().toArray(new Token[0]);
-    assertNoCharactersMissing(input.length(), tokens);
-    return tokens;
-  }
-
-  /**
-   * Both the syntax highlighter and the parser require every character be accounted for by
-   * a lexical element.
-   */
-  private static void assertNoCharactersMissing(int totalLength, Token[] tokens) {
-    if (tokens.length != 0 && tokens[tokens.length - 1].right != totalLength) {
-      throw new AssertionError(String.format(
-        "Last tokenized character '%s' doesn't match document length '%s'",
-        tokens[tokens.length - 1].right, totalLength));
-    }
-    int start = 0;
-    for (int i = 0; i < tokens.length; i++) {
-      Token token = tokens[i];
-      if (token.left != start) {
-        throw new AssertionError("Gap/inconsistency at: " + start);
-      }
-      start = token.right;
-    }
-  }
-
-  /**
-   * Returns a string containing the names of the tokens.
-   */
-  private static String names(Token[] tokens) {
-    StringBuilder buf = new StringBuilder();
-    for (Token token : tokens) {
-      if (isIgnored(token.type)) {
-        continue;
-      }
-      if (buf.length() > 0) {
-        buf.append(' ');
-      }
-      buf.append(token.type);
-    }
-    return buf.toString();
-  }
-
-  private static boolean isIgnored(ProjectViewTokenType kind) {
-    return kind == ProjectViewTokenType.WHITESPACE || kind == ProjectViewTokenType.NEWLINE;
-  }
-}
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/sync/BlazeSyncTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/sync/BlazeSyncTest.java
deleted file mode 100644
index 04fd4ac..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/sync/BlazeSyncTest.java
+++ /dev/null
@@ -1,77 +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;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.ideinfo.RuleMapBuilder;
-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.sync.actions.IncrementalSyncProjectAction;
-import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Tests for the blaze sync process {@link BlazeSyncTask}
- */
-public class BlazeSyncTest extends BlazeSyncIntegrationTestCase {
-
-  public void testSimpleSync() throws Exception {
-    setProjectView(
-      "directories:",
-      "  java/com/google",
-      "targets:",
-      "  //java/com/google:lib",
-      "workspace_type: java"
-    );
-
-    createFile(
-      "java/com/google/Source.java",
-      "package com.google;",
-      "public class Source {}"
-    );
-
-    createFile(
-      "java/com/google/Other.java",
-      "package com.google;",
-      "public class Other {}"
-    );
-
-    ImmutableMap<Label, RuleIdeInfo> ruleMap = RuleMapBuilder.builder()
-      .addRule(RuleIdeInfo.builder()
-                 .setBuildFile(sourceRoot("java/com/google/BUILD"))
-                 .setLabel("//java/com/google:lib")
-                 .setKind("java_library")
-                 .addSource(sourceRoot("java/com/google/Source.java"))
-                 .addSource(sourceRoot("java/com/google/Other.java")))
-      .build();
-
-    setRuleMap(ruleMap);
-
-    runBlazeSync(IncrementalSyncProjectAction.manualSyncParams);
-
-    assertNoErrors();
-
-    BlazeProjectData blazeProjectData = BlazeProjectDataManager.getInstance(getProject()).getBlazeProjectData();
-    assertThat(blazeProjectData).isNotNull();
-    assertThat(blazeProjectData.ruleMap).isEqualTo(ruleMap);
-    assertThat(blazeProjectData.workspaceLanguageSettings.getWorkspaceType())
-      .isEqualTo(WorkspaceType.JAVA);
-  }
-
-}
diff --git a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/sync/ImportRootsTest.java b/blaze-base/tests/integrationtests/com/google/idea/blaze/base/sync/ImportRootsTest.java
deleted file mode 100644
index 8fe8f63..0000000
--- a/blaze-base/tests/integrationtests/com/google/idea/blaze/base/sync/ImportRootsTest.java
+++ /dev/null
@@ -1,83 +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;
-
-import com.google.common.collect.ImmutableList;
-import com.google.idea.blaze.base.BlazeIntegrationTestCase;
-import com.google.idea.blaze.base.bazel.BuildSystemProvider;
-import com.google.idea.blaze.base.model.primitives.Label;
-import com.google.idea.blaze.base.model.primitives.WorkspacePath;
-import com.google.idea.blaze.base.projectview.section.sections.DirectoryEntry;
-import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
-import com.google.idea.blaze.base.sync.projectview.ImportRoots;
-
-import java.util.stream.Collectors;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Tests for ImportRoots
- */
-public class ImportRootsTest extends BlazeIntegrationTestCase {
-
-  public void testBazelArtifactDirectoriesExcluded() {
-    ImportRoots importRoots = ImportRoots.builder(workspaceRoot, BuildSystem.Bazel)
-      .add(new DirectoryEntry(new WorkspacePath(""), true))
-      .build();
-
-    ImmutableList<String> artifactDirs = BuildSystemProvider.getBuildSystemProvider(BuildSystem.Bazel)
-      .buildArtifactDirectories(workspaceRoot);
-
-    assertThat(importRoots.rootDirectories()).containsExactly(new WorkspacePath(""));
-    assertThat(
-      importRoots.excludeDirectories()
-        .stream()
-        .map(WorkspacePath::relativePath)
-        .collect(Collectors.toList())
-    ).containsExactlyElementsIn(artifactDirs);
-
-    assertThat(artifactDirs).contains("bazel-" + workspaceRoot.directory().getName());
-  }
-
-  public void testNoAddedExclusionsWithoutWorkspaceRootInclusion() {
-    ImportRoots importRoots = ImportRoots.builder(workspaceRoot, BuildSystem.Bazel)
-      .add(new DirectoryEntry(new WorkspacePath("foo/bar"), true))
-      .build();
-
-    assertThat(importRoots.rootDirectories()).containsExactly(new WorkspacePath("foo/bar"));
-    assertThat(importRoots.excludeDirectories()).isEmpty();
-  }
-
-  public void testNoAddedExclusionsForBlaze() {
-    ImportRoots importRoots = ImportRoots.builder(workspaceRoot, BuildSystem.Blaze)
-      .add(new DirectoryEntry(new WorkspacePath(""), true))
-      .build();
-
-    assertThat(importRoots.rootDirectories()).containsExactly(new WorkspacePath(""));
-    assertThat(importRoots.excludeDirectories()).isEmpty();
-  }
-
-  // if the workspace root is an included directory, all rules should be imported as sources.
-  public void testAllLabelsIncludedUnderWorkspaceRoot() {
-    ImportRoots importRoots = ImportRoots.builder(workspaceRoot, BuildSystem.Blaze)
-      .add(new DirectoryEntry(new WorkspacePath(""), true))
-      .build();
-
-    assertThat(importRoots.importAsSource(new Label("//:target"))).isTrue();
-    assertThat(importRoots.importAsSource(new Label("//foo/bar:target"))).isTrue();
-  }
-
-}
diff --git a/blaze-base/tests/unittests/com/google/idea/blaze/base/BlazeIconsTest.java b/blaze-base/tests/unittests/com/google/idea/blaze/base/BlazeIconsTest.java
deleted file mode 100644
index 85fbc34..0000000
--- a/blaze-base/tests/unittests/com/google/idea/blaze/base/BlazeIconsTest.java
+++ /dev/null
@@ -1,37 +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;
-
-import static org.junit.Assert.assertNotNull;
-
-import org.junit.Test;
-
-import javax.swing.Icon;
-
-import icons.BlazeIcons;
-
-/**
- * Tests for BlazeIcons
- */
-public class BlazeIconsTest {
-
-  @Test
-  public void testIcon() {
-    Icon icon = BlazeIcons.Blaze;
-
-    assertNotNull(icon);
-  }
-}
diff --git a/blaze-base/tests/unittests/com/google/idea/blaze/base/command/BlazeCommandNameTest.java b/blaze-base/tests/unittests/com/google/idea/blaze/base/command/BlazeCommandNameTest.java
deleted file mode 100644
index 7a55d93..0000000
--- a/blaze-base/tests/unittests/com/google/idea/blaze/base/command/BlazeCommandNameTest.java
+++ /dev/null
@@ -1,65 +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.command;
-
-import com.google.common.base.Function;
-import com.google.common.collect.Collections2;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import javax.annotation.Nullable;
-import java.util.Collection;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.fail;
-
-/**
- * Tests for {@link BlazeCommandName}.
- */
-@RunWith(JUnit4.class)
-public class BlazeCommandNameTest {
-  @Test
-  public void emptyNameShouldThrow() {
-    try {
-      BlazeCommandName.fromString("");
-      fail("Empty commands should not be allowed.");
-    }
-    catch (IllegalArgumentException expected) {
-    }
-  }
-
-  @Test
-  public void hardcodedNamesShouldBeKnown() {
-    assertThat(BlazeCommandName.knownCommands()).contains(BlazeCommandName.MOBILE_INSTALL);
-  }
-
-  @Test
-  public void userCommandNamesShouldBecomeKnown() {
-    Collection<String> knownCommandStrings =
-      Collections2.transform(BlazeCommandName.knownCommands(),
-                             new Function<BlazeCommandName, String>() {
-                               @Nullable
-                               @Override
-                               public String apply(BlazeCommandName input) {
-                                 return input.toString();
-                               }
-                             });
-    assertThat(knownCommandStrings).doesNotContain("user-command");
-    BlazeCommandName userCommand = BlazeCommandName.fromString("user-command");
-    assertThat(BlazeCommandName.knownCommands()).contains(userCommand);
-  }
-}
diff --git a/blaze-base/tests/unittests/com/google/idea/blaze/base/command/BlazeCommandTest.java b/blaze-base/tests/unittests/com/google/idea/blaze/base/command/BlazeCommandTest.java
deleted file mode 100644
index 1782ebc..0000000
--- a/blaze-base/tests/unittests/com/google/idea/blaze/base/command/BlazeCommandTest.java
+++ /dev/null
@@ -1,121 +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.command;
-
-import com.google.common.collect.ImmutableList;
-import com.google.idea.blaze.base.BlazeTestCase;
-import com.google.idea.blaze.base.experiments.ExperimentService;
-import com.google.idea.blaze.base.experiments.MockExperimentService;
-import com.google.idea.blaze.base.model.primitives.Label;
-import com.google.idea.blaze.base.model.primitives.TargetExpression;
-import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
-import com.google.idea.blaze.base.settings.BlazeUserSettings;
-import org.jetbrains.annotations.NotNull;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.Collections;
-import java.util.List;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Tests for {@link BlazeCommand}.
- */
-@RunWith(JUnit4.class)
-public class BlazeCommandTest extends BlazeTestCase {
-
-  @Override
-  protected void initTest(@NotNull Container applicationServices, @NotNull Container projectServices) {
-    ExperimentService experimentService = new MockExperimentService();
-    applicationServices.register(ExperimentService.class, experimentService);
-    applicationServices.register(BlazeUserSettings.class, new BlazeUserSettings());
-  }
-
-  @Test
-  public void addedFlagsShouldGoAtStart() {
-    List<String> flagsCommand = BlazeCommand.builder(BuildSystem.Blaze, BlazeCommandName.RUN)
-      .addTargets(new Label("//a:b"))
-      .addBlazeFlags("--flag1", "--flag2")
-      .addExeFlags("--exeFlag1", "--exeFlag2")
-      .build()
-      .toList();
-    // First three strings are always 'blaze run --tool_tag=ijwb:IDEA:ultimate'
-    assertThat(flagsCommand.subList(3, 5))
-      .isEqualTo(ImmutableList.of("--flag1", "--flag2"));
-  }
-
-  @Test
-  public void targetsShouldGoAfterBlazeFlagsAndDoubleHyphen() {
-    List<String> command = BlazeCommand.builder(BuildSystem.Blaze, BlazeCommandName.RUN)
-      .addTargets(new Label("//a:b"), new Label("//c:d"))
-      .addBlazeFlags("--flag1", "--flag2")
-      .addExeFlags("--exeFlag1", "--exeFlag2")
-      .build()
-      .toList();
-    // First six strings should be 'blaze run --tool_tag=ijwb:IDEA:ultimate --flag1 --flag2 --'
-    assertThat(command.indexOf("--")).isEqualTo(5);
-    assertThat(Collections.indexOfSubList(command, ImmutableList.of("//a:b", "//c:d"))).isEqualTo(6);
-  }
-
-  @Test
-  public void exeFlagsShouldGoLast() {
-    List<String> command = BlazeCommand.builder(BuildSystem.Blaze, BlazeCommandName.RUN)
-      .addTargets(new Label("//a:b"), new Label("//c:d"))
-      .addBlazeFlags("--flag1", "--flag2")
-      .addExeFlags("--exeFlag1", "--exeFlag2")
-      .build()
-      .toList();
-    List<String> finalTwoFlags = command.subList(command.size() - 2, command.size());
-    assertThat(finalTwoFlags).containsExactly("--exeFlag1", "--exeFlag2");
-  }
-
-  @Test
-  public void maintainUserOrderingOfTargets() {
-    List<String> command = BlazeCommand.builder(BuildSystem.Blaze, BlazeCommandName.RUN)
-      .addTargets(new Label("//a:b"), TargetExpression.fromString("-//e:f"), new Label("//c:d"))
-      .addBlazeFlags("--flag1", "--flag2")
-      .addExeFlags("--exeFlag1", "--exeFlag2")
-      .build()
-      .toList();
-
-    ImmutableList<Object> expected = ImmutableList.builder()
-      .add("/usr/bin/blaze")
-      .add("run")
-      .add(BlazeFlags.getToolTagFlag())
-      .add("--flag1")
-      .add("--flag2")
-      .add("--")
-      .add("//a:b")
-      .add("-//e:f")
-      .add("//c:d")
-      .add("--exeFlag1")
-      .add("--exeFlag2")
-      .build();
-    assertThat(command).isEqualTo(expected);
-  }
-
-  @Test
-  public void binaryAndCommandShouldComeFirst() {
-    List<String> command = BlazeCommand.builder(BuildSystem.Blaze, BlazeCommandName.BUILD)
-      .addBlazeFlags("--flag")
-      .addExeFlags("--exeFlag")
-      .build()
-      .toList();
-    assertThat(command.subList(0, 2)).isEqualTo(ImmutableList.of("/usr/bin/blaze", "build"));
-  }
-}
diff --git a/blaze-base/tests/unittests/com/google/idea/blaze/base/experiments/ExperimentServiceImplTest.java b/blaze-base/tests/unittests/com/google/idea/blaze/base/experiments/ExperimentServiceImplTest.java
deleted file mode 100644
index 15255e4..0000000
--- a/blaze-base/tests/unittests/com/google/idea/blaze/base/experiments/ExperimentServiceImplTest.java
+++ /dev/null
@@ -1,120 +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.experiments;
-
-import com.google.common.collect.ImmutableMap;
-import org.jetbrains.annotations.NotNull;
-import org.junit.Test;
-
-import java.util.Map;
-
-import static com.google.common.base.Preconditions.checkState;
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Tests for {@link ExperimentServiceImpl}.
- */
-public class ExperimentServiceImplTest {
-
-  @Test
-  public void testBooleanPropertyTrue() {
-    ExperimentService experimentService =
-        new ExperimentServiceImpl(new MapExperimentLoader("test.property", "1"));
-    assertThat(experimentService.getExperiment("test.property", false)).isTrue();
-  }
-
-  @Test
-  public void testBooleanPropertyFalse() {
-    ExperimentService experimentService =
-        new ExperimentServiceImpl(new MapExperimentLoader("test.property", "0"));
-    assertThat(experimentService.getExperiment("test.property", true)).isFalse();
-  }
-
-  @Test
-  public void testBooleanPropertyReturnsDefaultWhenMissing() {
-    ExperimentService experimentService = new ExperimentServiceImpl(new MapExperimentLoader());
-    assertThat(experimentService.getExperiment("test.notthere", true)).isTrue();
-  }
-
-  @Test
-  public void testStringProperty() {
-    ExperimentService experimentService =
-        new ExperimentServiceImpl(new MapExperimentLoader("test.property", "hi"));
-    assertThat(experimentService.getExperimentString("test.property", null)).isEqualTo("hi");
-  }
-
-  @Test
-  public void testStringPropertyReturnsDefaultWhenMissing() {
-    ExperimentService experimentService = new ExperimentServiceImpl(new MapExperimentLoader());
-    assertThat(experimentService.getExperimentString("test.property", "bye")).isEqualTo("bye");
-  }
-
-  @Test
-  public void testFirstLoaderOverridesSecond() {
-    ExperimentService experimentService =
-        new ExperimentServiceImpl(
-            new MapExperimentLoader("test.property", "1"),
-            new MapExperimentLoader("test.property", "0"));
-    assertThat(experimentService.getExperiment("test.property", false)).isTrue();
-  }
-
-  @Test
-  public void testOnlyInSecondLoader() {
-    ExperimentService experimentService =
-        new ExperimentServiceImpl(
-            new MapExperimentLoader(), new MapExperimentLoader("test.property", "1"));
-    assertThat(experimentService.getExperiment("test.property", false)).isTrue();
-  }
-
-  @Test
-  public void testIntProperty() {
-    ExperimentService experimentService =
-        new ExperimentServiceImpl(new MapExperimentLoader("test.property", "10"));
-    assertThat(experimentService.getExperimentInt("test.property", 0)).isEqualTo(10);
-  }
-
-  @Test
-  public void testIntPropertyDefaultValue() {
-    ExperimentService experimentService = new ExperimentServiceImpl(new MapExperimentLoader());
-    assertThat(experimentService.getExperimentInt("test.property", 100)).isEqualTo(100);
-  }
-
-  @Test
-  public void testIntPropertyThatDoesntParseReturnsDefaultValue() {
-    ExperimentService experimentService =
-        new ExperimentServiceImpl(new MapExperimentLoader("test.property", "hello"));
-    assertThat(experimentService.getExperimentInt("test.property", 111)).isEqualTo(111);
-  }
-
-  private static class MapExperimentLoader implements ExperimentLoader {
-
-    private final Map<String, String> map;
-
-    private MapExperimentLoader(String... keysAndValues) {
-      checkState(keysAndValues.length % 2 == 0);
-      ImmutableMap.Builder<String, String> mapBuilder = ImmutableMap.builder();
-      for (int i = 0; i < keysAndValues.length; i += 2) {
-        mapBuilder.put(keysAndValues[i], keysAndValues[i + 1]);
-      }
-      map = mapBuilder.build();
-    }
-
-    @Override
-    public Map<String, String> getExperiments() {
-      return map;
-    }
-  }
-}
diff --git a/blaze-base/tests/unittests/com/google/idea/blaze/base/experiments/SystemPropertyExperimentLoaderTest.java b/blaze-base/tests/unittests/com/google/idea/blaze/base/experiments/SystemPropertyExperimentLoaderTest.java
deleted file mode 100644
index 61eb30d..0000000
--- a/blaze-base/tests/unittests/com/google/idea/blaze/base/experiments/SystemPropertyExperimentLoaderTest.java
+++ /dev/null
@@ -1,45 +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.experiments;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import static com.google.common.truth.Truth.assertThat;
-
-public class SystemPropertyExperimentLoaderTest {
-
-  private static final String EXPERIMENT = "test.foo";
-  private static final String PROPERTY = "blaze.experiment.test.foo";
-  private static final String VALUE = "true";
-
-  @Before
-  public void setUp() {
-    System.setProperty(PROPERTY, VALUE);
-  }
-
-  @After
-  public void tearDown() {
-    System.clearProperty(PROPERTY);
-  }
-
-  @Test
-  public void testGetExperiment() {
-    ExperimentLoader loader = new SystemPropertyExperimentLoader();
-    assertThat(loader.getExperiments().get(EXPERIMENT)).isEqualTo(VALUE);
-  }
-}
diff --git a/blaze-base/tests/unittests/com/google/idea/blaze/base/io/MockWorkspaceScanner.java b/blaze-base/tests/unittests/com/google/idea/blaze/base/io/MockWorkspaceScanner.java
deleted file mode 100644
index 9229bfb..0000000
--- a/blaze-base/tests/unittests/com/google/idea/blaze/base/io/MockWorkspaceScanner.java
+++ /dev/null
@@ -1,75 +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 org.jetbrains.annotations.NotNull;
-
-import java.util.Set;
-
-/**
- * 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/blaze-base/tests/unittests/com/google/idea/blaze/base/issueparser/BlazeIssueParserTest.java b/blaze-base/tests/unittests/com/google/idea/blaze/base/issueparser/BlazeIssueParserTest.java
deleted file mode 100644
index 7b256e6..0000000
--- a/blaze-base/tests/unittests/com/google/idea/blaze/base/issueparser/BlazeIssueParserTest.java
+++ /dev/null
@@ -1,270 +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.issueparser;
-
-import com.google.idea.blaze.base.BlazeTestCase;
-import com.google.idea.blaze.base.experiments.ExperimentService;
-import com.google.idea.blaze.base.experiments.MockExperimentService;
-import com.google.idea.blaze.base.model.primitives.TargetExpression;
-import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
-import com.google.idea.blaze.base.projectview.ProjectView;
-import com.google.idea.blaze.base.projectview.ProjectViewManager;
-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.sections.TargetSection;
-import com.google.idea.blaze.base.scope.output.IssueOutput;
-import org.jetbrains.annotations.NotNull;
-import org.junit.Test;
-
-import java.io.File;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-/**
- * Tests for {@link BlazeIssueParser}.
- */
-public class BlazeIssueParserTest extends BlazeTestCase {
-
-  private ProjectViewManager projectViewManager;
-  private WorkspaceRoot workspaceRoot;
-
-  @Override
-  protected void initTest(@NotNull Container applicationServices, @NotNull Container projectServices) {
-    super.initTest(applicationServices, projectServices);
-
-    applicationServices.register(ExperimentService.class, new MockExperimentService());
-
-    projectViewManager = mock(ProjectViewManager.class);
-    projectServices.register(ProjectViewManager.class, projectViewManager);
-
-    workspaceRoot = new WorkspaceRoot(new File("/root"));
-  }
-
-  @Test
-  public void testParseTargetError() {
-    BlazeIssueParser blazeIssueParser = new BlazeIssueParser(project, workspaceRoot);
-    IssueOutput issue = blazeIssueParser.parseIssue(
-      "ERROR: invalid target format '//javatests/com/google/devtools/aswb/testapps/aswbtestlib/...:alls': invalid package name 'javatests/com/google/devtools/aswb/testapps/aswbtestlib/...': package name component contains only '.' characters."
-    );
-    assertNotNull(issue);
-    assertThat(issue.getCategory()).isEqualTo(IssueOutput.Category.ERROR);
-  }
-
-  @Test
-  public void testParseCompileError() {
-    BlazeIssueParser blazeIssueParser = new BlazeIssueParser(project, workspaceRoot);
-    IssueOutput issue = blazeIssueParser.parseIssue(
-      "java/com/google/android/samples/helloroot/math/DivideMath.java:17: error: non-static variable this cannot be referenced from a static context"
-    );
-    assertNotNull(issue);
-    assertThat(issue.getFile().getPath()).isEqualTo(
-      "/root/java/com/google/android/samples/helloroot/math/DivideMath.java");
-    assertThat(issue.getLine()).isEqualTo(17);
-    assertThat(issue.getMessage()).isEqualTo("non-static variable this cannot be referenced from a static context");
-    assertThat(issue.getCategory()).isEqualTo(IssueOutput.Category.ERROR);
-  }
-
-  @Test
-  public void testParseCompileErrorWithColumn() {
-    BlazeIssueParser blazeIssueParser = new BlazeIssueParser(project, workspaceRoot);
-    IssueOutput issue = blazeIssueParser.parseIssue(
-      "java/com/google/devtools/aswb/pluginrepo/googleplex/PluginsEndpoint.java:33:26: error: '|' is not preceded with whitespace."
-    );
-    assertNotNull(issue);
-    assertThat(issue.getLine()).isEqualTo(33);
-    assertThat(issue.getMessage()).isEqualTo("'|' is not preceded with whitespace.");
-    assertThat(issue.getCategory()).isEqualTo(IssueOutput.Category.ERROR);
-  }
-
-  @Test
-  public void testParseCompileErrorWithAbsolutePath() {
-    BlazeIssueParser blazeIssueParser = new BlazeIssueParser(project, workspaceRoot);
-    IssueOutput issue = blazeIssueParser.parseIssue(
-      "/root/java/com/google/android/samples/helloroot/math/DivideMath.java:17: error: non-static variable this cannot be referenced from a static context"
-    );
-    assertNotNull(issue);
-    assertThat(issue.getFile().getPath()).isEqualTo(
-      "/root/java/com/google/android/samples/helloroot/math/DivideMath.java");
-  }
-
-  @Test
-  public void testParseCompileErrorWithDepotPath() {
-    BlazeIssueParser blazeIssueParser = new BlazeIssueParser(project, workspaceRoot);
-    IssueOutput issue = blazeIssueParser.parseIssue(
-      "//depot/google3/package_path/DivideMath.java:17: error: non-static variable this cannot be referenced from a static context"
-    );
-    assertNotNull(issue);
-    assertThat(issue.getFile().getPath()).isEqualTo(
-      "/root/package_path/DivideMath.java");
-  }
-
-  @Test
-  public void testParseBuildError() {
-    BlazeIssueParser blazeIssueParser = new BlazeIssueParser(project, workspaceRoot);
-    IssueOutput issue = blazeIssueParser.parseIssue(
-      "ERROR: /path/to/root/javatests/package_path/BUILD:42:12: Target '//java/package_path:helloroot_visibility' failed"
-    );
-    assertNotNull(issue);
-    assertThat(issue.getFile().getPath()).isEqualTo(
-      "/path/to/root/javatests/package_path/BUILD");
-    assertThat(issue.getLine()).isEqualTo(42);
-    assertThat(issue.getMessage()).isEqualTo("Target '//java/package_path:helloroot_visibility' failed");
-    assertThat(issue.getCategory()).isEqualTo(IssueOutput.Category.ERROR);
-  }
-
-  @Test
-  public void testParseLinelessBuildError() {
-    BlazeIssueParser blazeIssueParser = new BlazeIssueParser(project, workspaceRoot);
-    IssueOutput issue = blazeIssueParser.parseIssue(
-      "ERROR: /path/to/root/java/package_path/BUILD:char offsets 1222--1229: name 'grubber' is not defined"
-    );
-    assertNotNull(issue);
-    assertThat(issue.getFile().getPath()).isEqualTo(
-      "/path/to/root/java/package_path/BUILD");
-    assertThat(issue.getMessage()).isEqualTo("name 'grubber' is not defined");
-    assertThat(issue.getCategory()).isEqualTo(IssueOutput.Category.ERROR);
-  }
-
-  @Test
-  public void testLabelProjectViewParser() {
-    ProjectViewSet projectViewSet = ProjectViewSet.builder()
-      .add(new File(".blazeproject"), ProjectView.builder()
-             .put(ListSection.builder(TargetSection.KEY)
-                    .add(TargetExpression.fromString("//package/path:hello4")))
-             .build())
-      .build();
-    when(projectViewManager.getProjectViewSet()).thenReturn(projectViewSet);
-
-    BlazeIssueParser blazeIssueParser = new BlazeIssueParser(project, workspaceRoot);
-    IssueOutput issue = blazeIssueParser.parseIssue(
-      "no such target '//package/path:hello4': target 'hello4' not declared in package 'package/path' defined by /path/to/root/package/path/BUILD"
-    );
-    assertNotNull(issue);
-    assertThat(issue.getFile().getPath()).isEqualTo(".blazeproject");
-    assertThat(issue.getCategory()).isEqualTo(IssueOutput.Category.ERROR);
-  }
-
-  @Test
-  public void testPackageProjectViewParser() {
-    ProjectViewSet projectViewSet = ProjectViewSet.builder()
-      .add(new File(".blazeproject"), ProjectView.builder()
-        .put(ListSection.builder(TargetSection.KEY)
-               .add(TargetExpression.fromString("//package/path:hello4")))
-        .build())
-      .build();
-    when(projectViewManager.getProjectViewSet()).thenReturn(projectViewSet);
-
-    BlazeIssueParser blazeIssueParser = new BlazeIssueParser(project, workspaceRoot);
-    IssueOutput issue = blazeIssueParser.parseIssue(
-      "no such package 'package/path': BUILD file not found on package path"
-    );
-    assertNotNull(issue);
-    assertThat(issue.getFile().getPath()).isEqualTo(".blazeproject");
-    assertThat(issue.getCategory()).isEqualTo(IssueOutput.Category.ERROR);
-  }
-
-  @Test
-  public void testDeletedBUILDFileButLeftPackageInLocalTargets() {
-    ProjectViewSet projectViewSet = ProjectViewSet.builder()
-      .add(new File(".blazeproject"), ProjectView.builder()
-        .put(ListSection.builder(TargetSection.KEY)
-          .add(TargetExpression.fromString("//tests/com/google/a/b/c/d/baz:baz")))
-        .build())
-      .build();
-    when(projectViewManager.getProjectViewSet()).thenReturn(projectViewSet);
-
-    BlazeIssueParser blazeIssueParser = new BlazeIssueParser(project, workspaceRoot);
-    IssueOutput issue = blazeIssueParser.parseIssue(
-      "Error:com.google.a.b.Exception exception in Bar: no targets found beneath " +
-      "'tests/com/google/a/b/c/d/baz' Thrown during call: ..."
-    );
-    assertNotNull(issue);
-    assertNotNull(issue.getFile());
-    assertThat(issue.getFile().getPath()).isEqualTo(".blazeproject");
-    assertThat(issue.getCategory()).isEqualTo(IssueOutput.Category.ERROR);
-    assertThat(issue.getMessage()).isEqualTo(
-      "no targets found beneath 'tests/com/google/a/b/c/d/baz'"
-    );
-  }
-
-  @Test
-  public void testMultilineTraceback() {
-    String[] lines = new String[]{
-      "ERROR: /home/plumpy/whatever:9:12: Traceback (most recent call last):",
-      "\tFile \"/path/to/root/java/com/google/android/samples/helloroot/BUILD\", line 8",
-      "\t\tpackage_group(name = BAD_FUNCTION(\"hellogoogle...\"), ...\"])",
-      "\tFile \"/path/to/root/java/com/google/android/samples/helloroot/BUILD\", line 9, in package_group",
-      "\t\tBAD_FUNCTION",
-      "name 'BAD_FUNCTION' is not defined."};
-
-    BlazeIssueParser blazeIssueParser = new BlazeIssueParser(project, workspaceRoot);
-    for (int i = 0; i < lines.length - 1; ++i) {
-      IssueOutput issue = blazeIssueParser.parseIssue(lines[i]);
-      assertNull(issue);
-    }
-
-    IssueOutput issue = blazeIssueParser.parseIssue(lines[lines.length - 1]);
-    assertNotNull(issue);
-    assertThat(issue.getFile().getPath()).isEqualTo("/home/plumpy/whatever");
-    assertThat(issue.getMessage().split("\n")).hasLength(lines.length);
-    assertThat(issue.getCategory()).isEqualTo(IssueOutput.Category.ERROR);
-  }
-
-  @Test
-  public void testLineAfterTracebackIsAlsoParsed() {
-    String[] lines = new String[]{
-      "ERROR: /home/plumpy/whatever:9:12: Traceback (most recent call last):",
-      "\tFile \"/path/to/root/java/com/google/android/samples/helloroot/BUILD\", line 8",
-      "\t\tpackage_group(name = BAD_FUNCTION(\"hellogoogle...\"), ...\"])",
-      "\tFile \"/path/to/root/java/com/google/android/samples/helloroot/BUILD\", line 9, in package_group",
-      "\t\tBAD_FUNCTION",
-      "name 'BAD_FUNCTION' is not defined."};
-
-    BlazeIssueParser blazeIssueParser = new BlazeIssueParser(project, workspaceRoot);
-    for (int i = 0; i < lines.length; ++i) {
-      blazeIssueParser.parseIssue(lines[i]);
-    }
-
-    IssueOutput issue = blazeIssueParser.parseIssue(
-      "ERROR: /home/plumpy/whatever:char offsets 1222--1229: name 'grubber' is not defined"
-    );
-    assertNotNull(issue);
-    assertThat(issue.getFile().getPath()).isEqualTo("/home/plumpy/whatever");
-    assertThat(issue.getMessage()).isEqualTo("name 'grubber' is not defined");
-    assertThat(issue.getCategory()).isEqualTo(IssueOutput.Category.ERROR);
-  }
-
-  @Test
-  public void testMultipleIssues() {
-    BlazeIssueParser blazeIssueParser = new BlazeIssueParser(project, workspaceRoot);
-    IssueOutput issue = blazeIssueParser.parseIssue(
-      "ERROR: /home/plumpy/whatever:char offsets 1222--1229: name 'grubber' is not defined"
-    );
-    assertNotNull(issue);
-    issue = blazeIssueParser.parseIssue(
-      "ERROR: /home/plumpy/whatever:char offsets 1222--1229: name 'grubber' is not defined"
-    );
-    assertNotNull(issue);
-    issue = blazeIssueParser.parseIssue(
-      "ERROR: /home/plumpy/whatever:char offsets 1222--1229: name 'grubber' is not defined"
-    );
-    assertNotNull(issue);
-  }
-}
diff --git a/blaze-base/tests/unittests/com/google/idea/blaze/base/metrics/ActionTest.java b/blaze-base/tests/unittests/com/google/idea/blaze/base/metrics/ActionTest.java
deleted file mode 100644
index 6e0d869..0000000
--- a/blaze-base/tests/unittests/com/google/idea/blaze/base/metrics/ActionTest.java
+++ /dev/null
@@ -1,46 +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.metrics;
-
-import com.google.common.collect.Sets;
-import org.junit.Assert;
-import org.junit.Test;
-
-import java.util.HashSet;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class ActionTest {
-
-  @Test
-  public void ensureAllActionEnumsHaveUniqueNames() {
-    HashSet<String> names = Sets.newHashSet();
-    for (Action action : Action.values()) {
-      String name = action.getName();
-      Assert.assertTrue(name + " is not unique", names.add(name));
-    }
-  }
-
-  @Test
-  public void ensureAllActionEnumNamesAreAlphanumeric() {
-    Pattern pattern = Pattern.compile("[a-zA-Z0-9]*");
-    for (Action action : Action.values()) {
-      String name = action.getName();
-      Matcher matcher = pattern.matcher(name);
-      Assert.assertTrue(name + " is not valid", matcher.matches());
-    }
-  }
-}
diff --git a/blaze-base/tests/unittests/com/google/idea/blaze/base/model/blaze/DeepEqualsTesterTest.java b/blaze-base/tests/unittests/com/google/idea/blaze/base/model/blaze/DeepEqualsTesterTest.java
deleted file mode 100644
index 8227ee1..0000000
--- a/blaze-base/tests/unittests/com/google/idea/blaze/base/model/blaze/DeepEqualsTesterTest.java
+++ /dev/null
@@ -1,560 +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.blaze;
-
-import com.google.common.base.Objects;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-import com.google.idea.blaze.base.model.blaze.deepequalstester.DeepEqualsTester;
-import com.google.idea.blaze.base.model.blaze.deepequalstester.DeepEqualsTester.TestCorrectnessException;
-import com.google.idea.blaze.base.model.blaze.deepequalstester.DeepEqualsTesterUtil;
-import com.google.idea.blaze.base.model.blaze.deepequalstester.Examples;
-import com.google.idea.blaze.base.model.blaze.deepequalstester.Examples.ExampleNotFoundException;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-
-import java.io.File;
-import java.io.Serializable;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Tests to verify that our equals tester is working correctly
- */
-public class DeepEqualsTesterTest {
-
-  // The equals method does not work correctly if T is an array
-  private static class Box<T> implements Serializable {
-
-    public T data;
-
-    public Box(T data) {
-      this.data = data;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-      if (this == o) {
-        return true;
-      }
-      if (o == null || getClass() != o.getClass()) {
-        return false;
-      }
-      Box<?> box = (Box<?>)o;
-      return Objects.equal(data, box.data);
-    }
-
-    @Override
-    public int hashCode() {
-      return Objects.hashCode(data);
-    }
-  }
-
-  private static class CorrectEqualsAndHash implements Serializable {
-
-    public String name;
-
-    public CorrectEqualsAndHash(String name) {
-      this.name = name;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-      if (this == o) {
-        return true;
-      }
-      if (o == null || getClass() != o.getClass()) {
-        return false;
-      }
-      CorrectEqualsAndHash foo = (CorrectEqualsAndHash)o;
-      return Objects.equal(name, foo.name);
-    }
-
-    @Override
-    public int hashCode() {
-      return Objects.hashCode(name);
-    }
-  }
-
-  private static class ClassWithCorrectEqualsMember implements Serializable {
-
-    public String myName;
-    public CorrectEqualsAndHash myCorrectEqualsAndHash;
-
-    public ClassWithCorrectEqualsMember(String name, String innerName) {
-      this.myName = name;
-      this.myCorrectEqualsAndHash = new CorrectEqualsAndHash(innerName);
-    }
-
-    @Override
-    public boolean equals(Object o) {
-      if (this == o) {
-        return true;
-      }
-      if (o == null || getClass() != o.getClass()) {
-        return false;
-      }
-      ClassWithCorrectEqualsMember that = (ClassWithCorrectEqualsMember)o;
-      return Objects.equal(myName, that.myName) &&
-             Objects.equal(myCorrectEqualsAndHash, that.myCorrectEqualsAndHash);
-    }
-
-    @Override
-    public int hashCode() {
-      return Objects.hashCode(myName, myCorrectEqualsAndHash);
-    }
-  }
-
-  private static class IncorrectHash implements Serializable {
-
-    public String name;
-    public int num;
-
-    public IncorrectHash(String name, int num) {
-      this.name = name;
-      this.num = num;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-      if (this == o) {
-        return true;
-      }
-      if (o == null || getClass() != o.getClass()) {
-        return false;
-      }
-      IncorrectHash foo = (IncorrectHash)o;
-      return Objects.equal(name, foo.name) && Objects.equal(num, foo.num);
-    }
-
-    @Override
-    public int hashCode() {
-      return Objects.hashCode(name);
-    }
-  }
-
-  private static class IncorrectEqualsAndHash implements Serializable {
-
-    public String name;
-    public int num;
-
-    public IncorrectEqualsAndHash(String name, int num) {
-      this.name = name;
-      this.num = num;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-      if (this == o) {
-        return true;
-      }
-      if (o == null || getClass() != o.getClass()) {
-        return false;
-      }
-      IncorrectEqualsAndHash foo = (IncorrectEqualsAndHash)o;
-      return Objects.equal(name, foo.name);
-    }
-
-    @Override
-    public int hashCode() {
-      return Objects.hashCode(name);
-    }
-  }
-
-  private static class ClassWithIncorrectEqualsMember implements Serializable {
-
-    public String myName;
-    public IncorrectEqualsAndHash myIncorrectEqualsAndHash;
-
-    public ClassWithIncorrectEqualsMember(String name, String innerName, int innerNum) {
-      this.myName = name;
-      this.myIncorrectEqualsAndHash = new IncorrectEqualsAndHash(innerName, innerNum);
-    }
-
-    @Override
-    public boolean equals(Object o) {
-      if (this == o) {
-        return true;
-      }
-      if (o == null || getClass() != o.getClass()) {
-        return false;
-      }
-      ClassWithIncorrectEqualsMember that = (ClassWithIncorrectEqualsMember)o;
-      return Objects.equal(myName, that.myName) &&
-             Objects.equal(myIncorrectEqualsAndHash, that.myIncorrectEqualsAndHash);
-    }
-
-    @Override
-    public int hashCode() {
-      return Objects.hashCode(myName, myIncorrectEqualsAndHash);
-    }
-  }
-
-  private static class IncorrectEqualsWithArray implements Serializable {
-
-    public IncorrectEqualsAndHash[] array;
-
-    public IncorrectEqualsWithArray(IncorrectEqualsAndHash[] array) {
-      this.array = array;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-      if (this == o) {
-        return true;
-      }
-      if (o == null || getClass() != o.getClass()) {
-        return false;
-      }
-      IncorrectEqualsWithArray that = (IncorrectEqualsWithArray)o;
-      return Arrays.equals(array, that.array);
-    }
-
-    @Override
-    public int hashCode() {
-      return Objects.hashCode((Object[])array);
-    }
-  }
-
-  private static class SubIncorrectEqualsAndHash extends IncorrectEqualsAndHash {
-
-    public Long num;
-
-    public SubIncorrectEqualsAndHash(String name, int iNum, Long num) {
-      super(name, iNum);
-      this.num = num;
-    }
-  }
-
-  private static enum ENUMS {
-    ONE, TWO, THREE
-  }
-
-  private static class DeepClass<T> implements Serializable {
-
-    public ENUMS myEnum;
-    public char myC;
-    public T data;
-    public File f;
-
-    public DeepClass(ENUMS myEnum, char c, T data, File f) {
-      this.myEnum = myEnum;
-      this.myC = c;
-      this.data = data;
-      this.f = f;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-      if (this == o) {
-        return true;
-      }
-      if (o == null || getClass() != o.getClass()) {
-        return false;
-      }
-      DeepClass<?> deepClass = (DeepClass<?>)o;
-      return Objects.equal(myC, deepClass.myC) &&
-             Objects.equal(myEnum, deepClass.myEnum) &&
-             Objects.equal(data, deepClass.data) &&
-             Objects.equal(f, deepClass.f);
-    }
-
-    @Override
-    public int hashCode() {
-      return Objects.hashCode(myEnum, myC, data, f);
-    }
-  }
-
-  private static class SimpleClassWithSet implements Serializable {
-
-    public Set<File> files;
-
-    public SimpleClassWithSet(Set<File> files) {
-      this.files = files;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-      if (this == o) {
-        return true;
-      }
-      if (o == null || getClass() != o.getClass()) {
-        return false;
-      }
-      SimpleClassWithSet that = (SimpleClassWithSet)o;
-      return Objects.equal(files, that.files);
-    }
-
-    @Override
-    public int hashCode() {
-      return Objects.hashCode(files);
-    }
-  }
-
-  private static class MapWithIncorrectKey implements Serializable {
-
-    public Map<IncorrectEqualsAndHash, File> files;
-
-    public MapWithIncorrectKey(Map<IncorrectEqualsAndHash, File> files) {
-      this.files = files;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-      if (this == o) {
-        return true;
-      }
-      if (o == null || getClass() != o.getClass()) {
-        return false;
-      }
-      MapWithIncorrectKey that = (MapWithIncorrectKey)o;
-      return Objects.equal(files, that.files);
-    }
-
-    @Override
-    public int hashCode() {
-      return Objects.hashCode(files);
-    }
-  }
-
-  private static class MapWithIncorrectValue implements Serializable {
-
-    public Map<String, IncorrectEqualsAndHash> map;
-
-    public MapWithIncorrectValue(Map<String, IncorrectEqualsAndHash> map) {
-      this.map = map;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-      if (this == o) {
-        return true;
-      }
-      if (o == null || getClass() != o.getClass()) {
-        return false;
-      }
-      MapWithIncorrectValue that = (MapWithIncorrectValue)o;
-      return Objects.equal(map, that.map);
-    }
-
-    @Override
-    public int hashCode() {
-      return Objects.hashCode(map);
-    }
-  }
-
-  private static class MapWithCorrectKeyAndValues implements Serializable {
-
-    public Map<String, String> map;
-
-    public MapWithCorrectKeyAndValues(Map<String, String> map) {
-      this.map = map;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-      if (this == o) {
-        return true;
-      }
-      if (o == null || getClass() != o.getClass()) {
-        return false;
-      }
-      MapWithCorrectKeyAndValues that = (MapWithCorrectKeyAndValues)o;
-      return Objects.equal(map, that.map);
-    }
-
-    @Override
-    public int hashCode() {
-      return Objects.hashCode(map);
-    }
-  }
-
-  private Examples testExamples;
-
-  @Before
-  public void populateExtraExamples() {
-    testExamples = new Examples();
-    testExamples.addExample(CorrectEqualsAndHash.class, new CorrectEqualsAndHash("A"),
-                            new CorrectEqualsAndHash("B"));
-    testExamples.addExample(IncorrectEqualsAndHash.class, new IncorrectEqualsAndHash("A", 100),
-                            new IncorrectEqualsAndHash("A", 200));
-    testExamples
-      .addExample(IncorrectHash.class, new IncorrectHash("A", 100), new IncorrectHash("A", 200));
-  }
-
-  @Test
-  public void testCorrectEqualsAndHashPassesTester()
-    throws IllegalAccessException, InstantiationException, NoSuchFieldException, ExampleNotFoundException, TestCorrectnessException {
-    CorrectEqualsAndHash myFoo = new CorrectEqualsAndHash("test");
-    DeepEqualsTester.doDeepEqualsAndHashTest(myFoo, testExamples);
-  }
-
-  @Test(expected = AssertionError.class)
-  public void testIncorrectEqualsFailsTester()
-    throws IllegalAccessException, InstantiationException, NoSuchFieldException, ExampleNotFoundException, TestCorrectnessException {
-    IncorrectEqualsAndHash myFoo = new IncorrectEqualsAndHash("test", 4);
-    DeepEqualsTester.doDeepEqualsAndHashTest(myFoo, testExamples);
-  }
-
-  @Test(expected = AssertionError.class)
-  public void testIncorrectHashFailsTester()
-    throws IllegalAccessException, InstantiationException, NoSuchFieldException, ExampleNotFoundException, TestCorrectnessException {
-    IncorrectHash myFoo = new IncorrectHash("test", 4);
-    DeepEqualsTester.doDeepEqualsAndHashTest(myFoo, testExamples);
-  }
-
-  @Test
-  public void testCorrectDeepEqualsAndHashPassesTester()
-    throws IllegalAccessException, InstantiationException, NoSuchFieldException, ExampleNotFoundException, TestCorrectnessException {
-    ClassWithCorrectEqualsMember myFoo = new ClassWithCorrectEqualsMember("test", "inner test");
-    DeepEqualsTester.doDeepEqualsAndHashTest(myFoo, testExamples);
-  }
-
-  @Test(expected = AssertionError.class)
-  public void testDeepIncorrectEqualsFailsTester()
-    throws IllegalAccessException, InstantiationException, NoSuchFieldException, ExampleNotFoundException, TestCorrectnessException {
-    ClassWithIncorrectEqualsMember myFoo = new ClassWithIncorrectEqualsMember("test", "inner test",
-                                                                              4);
-    DeepEqualsTester.doDeepEqualsAndHashTest(myFoo, testExamples);
-  }
-
-  @Test(expected = AssertionError.class)
-  public void testIncorrectEqualsInSuperclassFailsTester()
-    throws IllegalAccessException, InstantiationException, NoSuchFieldException, ExampleNotFoundException, TestCorrectnessException {
-    SubIncorrectEqualsAndHash myFoo = new SubIncorrectEqualsAndHash("test", 4, new Long(39903));
-    DeepEqualsTester.doDeepEqualsAndHashTest(myFoo, testExamples);
-  }
-
-  @Test
-  public void testCorrectEqualsAndHashInArrayPassesTester()
-    throws IllegalAccessException, InstantiationException, NoSuchFieldException, ExampleNotFoundException, TestCorrectnessException {
-    CorrectEqualsAndHash myFoo = new CorrectEqualsAndHash("test");
-    CorrectEqualsAndHash[] array = new CorrectEqualsAndHash[1];
-    array[0] = myFoo;
-    DeepEqualsTester.doDeepEqualsAndHashTest(array, testExamples);
-  }
-
-  @Test(expected = AssertionError.class)
-  public void testIncorrectEqualsInArrayFailsTester()
-    throws IllegalAccessException, InstantiationException, NoSuchFieldException, ExampleNotFoundException, TestCorrectnessException {
-    IncorrectEqualsAndHash myFoo = new IncorrectEqualsAndHash("test", 4);
-    IncorrectEqualsAndHash[] array = new IncorrectEqualsAndHash[1];
-    array[0] = myFoo;
-    IncorrectEqualsWithArray toTest = new IncorrectEqualsWithArray(array);
-    DeepEqualsTester.doDeepEqualsAndHashTest(toTest, testExamples);
-  }
-
-  @Test
-  public void testClassWithSetWithCorrectDeepEqualsAndHashPassesTester()
-    throws IllegalAccessException, InstantiationException, NoSuchFieldException, ExampleNotFoundException, TestCorrectnessException {
-    Set<File> myFiles = Sets.newHashSet();
-    myFiles.add(new File("foo"));
-    myFiles.add(new File("bar"));
-    SimpleClassWithSet myFoo = new SimpleClassWithSet(myFiles);
-    DeepEqualsTester.doDeepEqualsAndHashTest(myFoo, testExamples);
-  }
-
-  @Ignore("causes java reflection return a type variable instead of a concrete type")
-  @Test
-  public void testCorrectEqualsAndHashInSetPassesTester()
-    throws IllegalAccessException, InstantiationException, NoSuchFieldException, ExampleNotFoundException, TestCorrectnessException {
-    CorrectEqualsAndHash myFoo = new CorrectEqualsAndHash("test");
-    HashSet<CorrectEqualsAndHash> set = Sets.newHashSet();
-    set.add(myFoo);
-    DeepEqualsTester.doDeepEqualsAndHashTest(new Box<HashSet<CorrectEqualsAndHash>>(set),
-                                             testExamples);
-  }
-
-  @Test(expected = AssertionError.class)
-  public void testIncorrectEqualsInSetFailsTester()
-    throws IllegalAccessException, InstantiationException, NoSuchFieldException, ExampleNotFoundException, TestCorrectnessException {
-    IncorrectEqualsAndHash myFoo = new IncorrectEqualsAndHash("test", 4);
-    HashSet<IncorrectEqualsAndHash> set = Sets.newHashSet();
-    set.add(myFoo);
-    DeepEqualsTester.doDeepEqualsAndHashTest(new Box<HashSet<IncorrectEqualsAndHash>>(set),
-                                             testExamples);
-  }
-
-  @Ignore("causes java reflection return a type variable instead of a concrete type")
-  @Test
-  public void testCorrectDeepEqualsAndHashInSetPassesTester()
-    throws IllegalAccessException, InstantiationException, NoSuchFieldException, ExampleNotFoundException, TestCorrectnessException {
-    CorrectEqualsAndHash myFoo = new CorrectEqualsAndHash("test");
-    DeepClass<CorrectEqualsAndHash> data = new DeepClass<CorrectEqualsAndHash>(ENUMS.THREE, 'z',
-                                                                               myFoo,
-                                                                               new File("home"));
-    HashSet<DeepClass<CorrectEqualsAndHash>> set = Sets.newHashSet();
-    set.add(data);
-    DeepEqualsTester
-      .doDeepEqualsAndHashTest(new Box<HashSet<DeepClass<CorrectEqualsAndHash>>>(set),
-                               testExamples);
-  }
-
-  @Test(expected = AssertionError.class)
-  public void testIncorrectDeepEqualsInSetFailsTester()
-    throws IllegalAccessException, InstantiationException, NoSuchFieldException, ExampleNotFoundException, TestCorrectnessException {
-    IncorrectEqualsAndHash myFoo = new IncorrectEqualsAndHash("test", 4);
-    DeepClass<IncorrectEqualsAndHash> data = new DeepClass<IncorrectEqualsAndHash>(ENUMS.ONE, 'e',
-                                                                                   myFoo,
-                                                                                   new File("home"));
-    HashSet<DeepClass<IncorrectEqualsAndHash>> set = Sets.newHashSet();
-    set.add(data);
-    DeepEqualsTester
-      .doDeepEqualsAndHashTest(new Box<HashSet<DeepClass<IncorrectEqualsAndHash>>>(set),
-                               testExamples);
-  }
-
-  @Test(expected = AssertionError.class)
-  public void testIncorrectEqualsForMapKeyFailsTester()
-    throws IllegalAccessException, InstantiationException, NoSuchFieldException, ExampleNotFoundException, TestCorrectnessException {
-    IncorrectEqualsAndHash myFoo = new IncorrectEqualsAndHash("test", 4);
-    Map<IncorrectEqualsAndHash, File> map = Maps.newHashMap();
-    map.put(myFoo, new File("file"));
-    MapWithIncorrectKey data = new MapWithIncorrectKey(map);
-    DeepEqualsTester.doDeepEqualsAndHashTest(data, testExamples);
-  }
-
-  @Test(expected = AssertionError.class)
-  public void testIncorrectEqualsForMapValueFailsTester()
-    throws IllegalAccessException, InstantiationException, NoSuchFieldException, ExampleNotFoundException, TestCorrectnessException {
-    IncorrectEqualsAndHash myFoo = new IncorrectEqualsAndHash("test", 4);
-    Map<String, IncorrectEqualsAndHash> map = Maps.newHashMap();
-    map.put("first", myFoo);
-    MapWithIncorrectValue data = new MapWithIncorrectValue(map);
-    DeepEqualsTester.doDeepEqualsAndHashTest(data, testExamples);
-  }
-
-  @Test
-  public void testCorrectEqualsAndHashForMapKeyValuePassesTester()
-    throws IllegalAccessException, InstantiationException, NoSuchFieldException, ExampleNotFoundException, TestCorrectnessException {
-    Map<String, String> map = Maps.newHashMap();
-    map.put("key", "value");
-    MapWithCorrectKeyAndValues data = new MapWithCorrectKeyAndValues(map);
-    DeepEqualsTester.doDeepEqualsAndHashTest(data, testExamples);
-  }
-
-  @Test
-  public void testEqualsAfterCloneReturnsTrue() {
-    CorrectEqualsAndHash myData = new CorrectEqualsAndHash("my data");
-    CorrectEqualsAndHash clone = (CorrectEqualsAndHash)DeepEqualsTesterUtil
-      .cloneWithSerialization(myData);
-    Assert.assertEquals(myData, clone);
-  }
-}
diff --git a/blaze-base/tests/unittests/com/google/idea/blaze/base/model/blaze/deepequalstester/DeepEqualsTester.java b/blaze-base/tests/unittests/com/google/idea/blaze/base/model/blaze/deepequalstester/DeepEqualsTester.java
deleted file mode 100644
index bb8b982..0000000
--- a/blaze-base/tests/unittests/com/google/idea/blaze/base/model/blaze/deepequalstester/DeepEqualsTester.java
+++ /dev/null
@@ -1,139 +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.blaze.deepequalstester;
-
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.model.blaze.deepequalstester.Examples.ExampleNotFoundException;
-import com.google.idea.blaze.base.model.blaze.deepequalstester.Examples.Pair;
-import com.google.idea.blaze.base.model.blaze.deepequalstester.ReachabilityAnalysis.ReachableClasses;
-import org.jetbrains.annotations.NotNull;
-import org.junit.Assert;
-
-import java.io.Serializable;
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
-import java.util.ArrayList;
-import java.util.List;
-
-public final class DeepEqualsTester {
-
-  public static class TestCorrectnessException extends Exception {
-
-    public TestCorrectnessException(String s) {
-      super(s);
-    }
-  }
-
-  /**
-   * Ensure that the equals method of {@param rootObject} uses all of its fields in its comparison.
-   * Recurse into every field of {@param rootObject} to ensure that they also use all of their
-   * fields in their equals method. Continue recursion until primitives are hit.
-   * <p/>
-   * If multiple failures could occur, there is no guarantee that they will always occur in the same
-   * order. Only the first failure is reported.
-   *
-   * @param rootObject an example instantiation of the class we want to test for deep equals sanity.
-   *                   The object must be a standard java object (no collections, no arrays, no primitives). If you
-   *                   would like to pass these types in, you should put them in a box first.
-   * @param examples   examples of objects to use for comparison. This should contain a pair of
-   *                   examples for every type that could be reachable from the root object. This value may be
-   *                   mutated by this test.
-   */
-  public static <T extends Serializable> void doDeepEqualsAndHashTest(@NotNull T rootObject,
-                                                                      Examples examples)
-    throws InstantiationException, IllegalAccessException, NoSuchFieldException, ExampleNotFoundException, TestCorrectnessException {
-    ReachableClasses reachableClasses = new ReachableClasses();
-
-    try {
-      ArrayList<String> initialPath = Lists.newArrayList("root");
-      // Find all of the classes reachable from the root object. This is not sound since it
-      // ignores subtypes (or supertypes) that could be used
-      ReachabilityAnalysis
-        .computeReachableFromObject(rootObject, rootObject.getClass(), initialPath,
-                                    reachableClasses);
-    }
-    catch (ClassNotFoundException e) {
-      e.printStackTrace();
-    }
-
-    // Add the root object to our list of reachable classes so we can do all the testing in one
-    // loop
-    reachableClasses.addPath(rootObject.getClass(), Lists.newArrayList("root"));
-    // In our situations, we never need a second example of the root object
-    examples.addExample(rootObject.getClass(), rootObject, rootObject);
-    // For each reachable class, do a shallow equals test where we change each value of the
-    // object one at a time and test for equality
-    for (Class<? extends Serializable> clazz : reachableClasses.getClasses()) {
-      Serializable workitem = (Serializable)examples.getExamples(clazz).getFirst();
-      testShallowEquals(workitem, reachableClasses, examples);
-    }
-  }
-
-  private static String getFailureMessage(String method, Field field, List<String> examplePath) {
-    StringBuilder sb = new StringBuilder();
-    sb.append(field.toString()).append(" is not represented in it's parent's ")
-      .append(method).append(" method\n");
-    for (String path : examplePath) {
-      sb.append("\t").append(path).append("\n");
-    }
-    sb.append("\n");
-    return sb.toString();
-  }
-
-  /**
-   * Mutate each field in the object one at a time and test for equality of the object. Assert a
-   * failure if any mutation doesn't result in the two objects not being equal
-   */
-  private static <T extends Serializable> void testShallowEquals(@NotNull T original,
-                                                                 ReachableClasses reachableClasses, Examples examples)
-    throws ExampleNotFoundException, IllegalAccessException, TestCorrectnessException {
-    T clone = (T)DeepEqualsTesterUtil.cloneWithSerialization(original);
-    List<Field> allFields = DeepEqualsTesterUtil.getAllFields(original.getClass());
-    for (Field field : allFields) {
-      if (!Modifier.isStatic(field.getModifiers())) {
-        field.setAccessible(true);
-        Pair<?, ?> examplesPair = examples.
-          getExamples((Class<? extends Serializable>)field.getType());
-        Object newValueForOriginal = examplesPair.getFirst();
-        Object newValueForClone = examplesPair.getSecond();
-        Object oldValueForOriginal = field.get(original);
-        Object oldValueForClone = field.get(clone);
-        // Ensure that the two objects really are equal before we tweak them
-        boolean objectsTheSameBeforeTweak = original.equals(clone);
-        if (!objectsTheSameBeforeTweak) {
-          throw new TestCorrectnessException(
-            "original was not equal to clone before tweaking them");
-        }
-        boolean objectsHashTheSameBeforeTweak = original.hashCode() == clone.hashCode();
-        if (!objectsHashTheSameBeforeTweak) {
-          throw new TestCorrectnessException(
-            "original hash code was not equal to clone hash code before tweaking the objects");
-        }
-        field.set(original, newValueForOriginal);
-        field.set(clone, newValueForClone);
-        boolean equalsWorksAsIntended = !original.equals(clone);
-        boolean hashWorksAsIntended = original.hashCode() != clone.hashCode();
-        // Return to our original state before possibly failing
-        field.set(original, oldValueForOriginal);
-        field.set(clone, oldValueForClone);
-        Assert.assertTrue(getFailureMessage("equals", field, reachableClasses.getExamplePathTo(
-          original.getClass())), equalsWorksAsIntended);
-        Assert.assertTrue(getFailureMessage("hash", field, reachableClasses.getExamplePathTo(
-          original.getClass())), hashWorksAsIntended);
-      }
-    }
-  }
-}
diff --git a/blaze-base/tests/unittests/com/google/idea/blaze/base/model/blaze/deepequalstester/DeepEqualsTesterUtil.java b/blaze-base/tests/unittests/com/google/idea/blaze/base/model/blaze/deepequalstester/DeepEqualsTesterUtil.java
deleted file mode 100644
index 71f0c6d..0000000
--- a/blaze-base/tests/unittests/com/google/idea/blaze/base/model/blaze/deepequalstester/DeepEqualsTesterUtil.java
+++ /dev/null
@@ -1,102 +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.blaze.deepequalstester;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.Lists;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.lang.reflect.Field;
-import java.util.List;
-
-@VisibleForTesting
-public final class DeepEqualsTesterUtil {
-  public static List<Field> getAllFields(Class<?> clazz) {
-    List<Field> fields = Lists.newArrayList();
-
-    Field[] declaredFields = clazz.getDeclaredFields();
-    for (Field field : declaredFields) {
-      fields.add(field);
-    }
-
-    Class<?> superclass = clazz.getSuperclass();
-    if (superclass != null) {
-      fields.addAll(getAllFields(superclass));
-    }
-    return fields;
-  }
-
-  public static Class getClass(Class declaredClass, Object o) {
-    if (o == null) {
-      return declaredClass;
-    }
-    // The two classes *should* be the same in this case, but the class from o.getClass won't
-    // return true from isPrimitive
-    if (declaredClass.isPrimitive()) {
-      return declaredClass;
-    }
-    return o.getClass();
-  }
-
-  /**
-   * Do a deep clone of an object using reflection
-   */
-  public static Object cloneWithSerialization(Object o) {
-    if (o == null) {
-      return null;
-    }
-
-    try {
-      ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-      ObjectOutputStream objOut = new ObjectOutputStream(outputStream);
-      objOut.writeObject(o);
-      ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
-      ObjectInputStream objIn = new ObjectInputStream(inputStream);
-      return objIn.readObject();
-    }
-    catch (Exception e) {
-      return null;
-    }
-  }
-
-  public static boolean isSubclassOf(@Nullable Class<?> possibleSubClass, @NotNull Class<?> possibleSuperClass) {
-    if (possibleSubClass == null) {
-      return false;
-    }
-
-    if (possibleSubClass.equals(possibleSuperClass)) {
-      return true;
-    }
-
-    if (possibleSubClass.equals(Object.class)) {
-      return false;
-    }
-
-    Class<?>[] interfaces = possibleSubClass.getInterfaces();
-    for (Class<?> interfaze : interfaces) {
-      if (interfaze.equals(possibleSuperClass)) {
-        return true;
-      }
-    }
-
-    return isSubclassOf(possibleSubClass.getSuperclass(), possibleSuperClass);
-  }
-}
diff --git a/blaze-base/tests/unittests/com/google/idea/blaze/base/model/blaze/deepequalstester/Examples.java b/blaze-base/tests/unittests/com/google/idea/blaze/base/model/blaze/deepequalstester/Examples.java
deleted file mode 100644
index c989196..0000000
--- a/blaze-base/tests/unittests/com/google/idea/blaze/base/model/blaze/deepequalstester/Examples.java
+++ /dev/null
@@ -1,187 +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.blaze.deepequalstester;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableList.Builder;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Maps;
-import org.jetbrains.annotations.NotNull;
-
-import java.io.File;
-import java.io.Serializable;
-import java.lang.reflect.Array;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import static com.google.idea.blaze.base.model.blaze.deepequalstester.DeepEqualsTesterUtil.isSubclassOf;
-
-public final class Examples {
-
-  public static final class Pair<First, Second> {
-
-    private final First first;
-    private final Second second;
-
-    public static <First, Second> Pair<First, Second> of(First first, Second second) {
-      return new Pair<First, Second>(first, second);
-    }
-
-    public Pair(First first, Second second) {
-      this.first = first;
-      this.second = second;
-    }
-
-    First getFirst() {
-      return first;
-    }
-
-    Second getSecond() {
-      return second;
-    }
-  }
-
-  private static Map<Class<? extends Object>, Pair<? extends Object, ? extends Object>> BASE_EXAMPLES;
-  private static Map<Class<? extends Object>, Pair<? extends Object, ? extends Object>> ARRAY_EXAMPLES;
-
-  private final Map<Class<? extends Object>, Pair<? extends Object, ? extends Object>> customExamples;
-
-  static {
-    BASE_EXAMPLES = Maps.newHashMap();
-    BASE_EXAMPLES.put(String.class, Pair.of("foo", "foobar"));
-    BASE_EXAMPLES.put(File.class, Pair.of(new File("a"), new File("b")));
-    BASE_EXAMPLES.put(Integer.class, Pair.of(1, 2));
-    BASE_EXAMPLES.put(Integer.TYPE, Pair.of(1, 2));
-    BASE_EXAMPLES.put(Long.class, Pair.of(1L, 2L));
-    BASE_EXAMPLES.put(Long.TYPE, Pair.of(1L, 2L));
-    BASE_EXAMPLES.put(Short.class, Pair.of((short)1, (short)2));
-    BASE_EXAMPLES.put(Short.TYPE, Pair.of((short)1, (short)2));
-    BASE_EXAMPLES.put(Character.class, Pair.of('a', 'b'));
-    BASE_EXAMPLES.put(Character.TYPE, Pair.of('a', 'b'));
-    BASE_EXAMPLES.put(Byte.class, Pair.of((byte)1, (byte)2));
-    BASE_EXAMPLES.put(Byte.TYPE, Pair.of((byte)1, (byte)2));
-    BASE_EXAMPLES.put(Float.class, Pair.of((float)1.0, (float)2.0));
-    BASE_EXAMPLES.put(Float.TYPE, Pair.of((float)1.0, (float)2.0));
-    BASE_EXAMPLES.put(Double.class, Pair.of(1.0, 2.0));
-    BASE_EXAMPLES.put(Double.TYPE, Pair.of(1.0, 2.0));
-    BASE_EXAMPLES.put(Boolean.class, Pair.of(true, false));
-    BASE_EXAMPLES.put(Boolean.TYPE, Pair.of(true, false));
-    Map<String, String> mapA = Maps.newHashMap();
-    mapA.put("foo", "bar");
-    Map<String, String> mapB = Maps.newHashMap();
-    mapB.put("tip", "top");
-    BASE_EXAMPLES.put(Map.class, Pair.of(mapA, mapB));
-    Set<String> setA = new ImmutableSet.Builder<String>().add("A").build();
-    Set<String> setB = new ImmutableSet.Builder<String>().add("A").add("B").build();
-    BASE_EXAMPLES.put(Set.class, Pair.of(setA, setB));
-    BASE_EXAMPLES.put(Collection.class, Pair.of(setA, setB));
-    Builder<String> listABuilder = ImmutableList.builder();
-    List<String> listA = listABuilder.add("A").build();
-    Builder<String> listBBuilder = ImmutableList.builder();
-    List<String> listB = listBBuilder.add("A").add("B").build();
-    BASE_EXAMPLES.put(List.class, Pair.of(listA, listB));
-
-    ARRAY_EXAMPLES = Maps.newHashMap();
-    int[] intArrA = {1, 2};
-    int[] intArrB = {3, 4};
-    ARRAY_EXAMPLES.put(Integer.TYPE, Pair.of(intArrA, intArrB));
-    long[] longArrA = {1, 2};
-    long[] longArrB = {3, 4};
-    ARRAY_EXAMPLES.put(Long.TYPE, Pair.of(longArrA, longArrB));
-    short[] shortArrA = {1, 2};
-    short[] shortArrB = {3, 4};
-    ARRAY_EXAMPLES.put(Short.TYPE, Pair.of(shortArrA, shortArrB));
-    char[] charArrA = {'a', 'b'};
-    char[] charArrB = {'c', 'd'};
-    ARRAY_EXAMPLES.put(Character.TYPE, Pair.of(charArrA, charArrB));
-    byte[] byteArrA = {1, 2};
-    byte[] byteArrB = {3, 4};
-    ARRAY_EXAMPLES.put(Byte.TYPE, Pair.of(byteArrA, byteArrB));
-    boolean[] boolArrA = {true, false};
-    boolean[] boolArrB = {false, false};
-    ARRAY_EXAMPLES.put(Boolean.TYPE, Pair.of(boolArrA, boolArrB));
-    float[] floatArrA = {1.0f, 2.0f};
-    float[] floatArrB = {3.0f, 4.0f};
-    ARRAY_EXAMPLES.put(Float.TYPE, Pair.of(floatArrA, floatArrB));
-    double[] doubleArrA = {1.0, 2.0};
-    double[] doubleArrB = {3.0, 4.0};
-    ARRAY_EXAMPLES.put(Double.TYPE, Pair.of(doubleArrA, doubleArrB));
-  }
-
-  public static class ExampleNotFoundException extends Exception {
-
-    private final Class<?> clazz;
-
-    public ExampleNotFoundException(Class<?> clazz) {
-      this.clazz = clazz;
-    }
-
-    @Override
-    public String getMessage() {
-      return "Could not find example for: " + clazz.toString();
-    }
-  }
-
-  public Examples() {
-    this.customExamples = Maps.newHashMap();
-  }
-
-  public void addExample(Class<? extends Object> clazz, Object a, Object b) {
-    customExamples.put(clazz, Pair.of(a, b));
-  }
-
-  public <T extends Serializable> Pair<? extends Object, ? extends Object> getExamples(
-    @NotNull Class<T> clazz)
-    throws ExampleNotFoundException {
-    if (customExamples.containsKey(clazz)) {
-      return customExamples.get(clazz);
-    }
-    if (BASE_EXAMPLES.containsKey(clazz)) {
-      return BASE_EXAMPLES.get(clazz);
-    }
-    // Special case subclasses of collections
-    if (isSubclassOf(clazz, Set.class)) {
-      return BASE_EXAMPLES.get(Set.class);
-    }
-    if (isSubclassOf(clazz, List.class)) {
-      return BASE_EXAMPLES.get(List.class);
-    }
-    if (isSubclassOf(clazz, Collection.class)) {
-      return BASE_EXAMPLES.get(Collection.class);
-    }
-
-    // If we have an array of Objects, we have to do a little trickery to create one we can swap
-    // in
-    if (clazz.isArray()) {
-      Class<?> arrayType = clazz.getComponentType();
-      if (!arrayType.isPrimitive()) {
-        Pair<?, ?> examples = getExamples((Class<? extends Serializable>)arrayType);
-        Object arrayA = Array.newInstance(arrayType, 1);
-        Array.set(arrayA, 0, examples.getFirst());
-        Object arrayB = Array.newInstance(arrayType, 1);
-        Array.set(arrayB, 0, examples.getSecond());
-        return Pair.of(arrayA, arrayB);
-      }
-      if (ARRAY_EXAMPLES.containsKey(arrayType)) {
-        return ARRAY_EXAMPLES.get(arrayType);
-      }
-    }
-
-    throw new ExampleNotFoundException(clazz);
-  }
-}
diff --git a/blaze-base/tests/unittests/com/google/idea/blaze/base/model/blaze/deepequalstester/ReachabilityAnalysis.java b/blaze-base/tests/unittests/com/google/idea/blaze/base/model/blaze/deepequalstester/ReachabilityAnalysis.java
deleted file mode 100644
index f8485c8..0000000
--- a/blaze-base/tests/unittests/com/google/idea/blaze/base/model/blaze/deepequalstester/ReachabilityAnalysis.java
+++ /dev/null
@@ -1,188 +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.blaze.deepequalstester;
-
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-import org.junit.Assert;
-
-import java.io.File;
-import java.io.Serializable;
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
-import java.lang.reflect.ParameterizedType;
-import java.lang.reflect.Type;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-
-public final class ReachabilityAnalysis {
-
-  /**
-   * Wrapper around a map from class to set of paths that lead to that path from the root object
-   */
-  public static final class ReachableClasses {
-
-    Map<Class<? extends Serializable>, Set<List<String>>> map;
-
-    public ReachableClasses() {
-      map = Maps.newHashMap();
-    }
-
-    boolean alreadyFound(Class<? extends Serializable> clazz) {
-      return map.containsKey(clazz);
-    }
-
-    void addPath(Class<? extends Serializable> clazz, List<String> path) {
-      Set<List<String>> paths;
-      if (map.containsKey(clazz)) {
-        paths = map.get(clazz);
-      }
-      else {
-        paths = Sets.newHashSet();
-        map.put(clazz, paths);
-      }
-      paths.add(path);
-    }
-
-    @Override
-    public String toString() {
-      StringBuilder sb = new StringBuilder();
-
-      Set<? extends Entry<Class<? extends Serializable>, ? extends Set<? extends List<String>>>> entries = map
-        .entrySet();
-      for (Entry<Class<? extends Serializable>, ? extends Set<? extends List<String>>> entry : entries) {
-        sb.append(entry.getKey().toString());
-        sb.append("\n");
-      }
-
-      return sb.toString();
-    }
-
-    public Set<Class<? extends Serializable>> getClasses() {
-      return map.keySet();
-    }
-
-    public List<String> getExamplePathTo(Class<? extends Serializable> aClass) {
-      if (map.containsKey(aClass)) {
-        return map.get(aClass).iterator().next();
-      }
-      return Lists.newArrayList();
-    }
-  }
-
-  /**
-   * Find all of the classes reachable from a root object
-   *
-   * @param root              object to start reachability calculation from
-   * @param declaredRootClass declared class of the root object
-   * @param currentPath       field access path to get to root
-   * @param reachableClasses  output: add classes reachable from root to this object
-   * @throws IllegalAccessException
-   * @throws ClassNotFoundException
-   */
-  public static void computeReachableFromObject(Object root, Class<?> declaredRootClass,
-                                                List<String> currentPath, ReachableClasses reachableClasses)
-    throws IllegalAccessException, ClassNotFoundException {
-    final Class<?> concreteRootClass = DeepEqualsTesterUtil.getClass(declaredRootClass, root);
-    List<Field> allFields = DeepEqualsTesterUtil.getAllFields(concreteRootClass);
-    for (Field field : allFields) {
-      if (!Modifier.isStatic(field.getModifiers())) {
-        field.setAccessible(true);
-        final Object fieldObject;
-        if (root == null) {
-          fieldObject = null;
-        }
-        else {
-          fieldObject = field.get(root);
-        }
-        List<String> childPath = Lists.newArrayList();
-        childPath.addAll(currentPath);
-        childPath.add(field.toString());
-        addToReachableAndRecurse(fieldObject, field.getType(), field.getGenericType(),
-                                 childPath, reachableClasses);
-      }
-    }
-  }
-
-  /**
-   * Determine the action we should take based on the type of Object and then take it. In the
-   * normal object case, this results in a recursive call to
-   * {@link #computeReachableFromObject(Object, Class, List, ReachableClasses)}. In the case of
-   * Collections, we skip the Collection type and continue on with the type contained in the
-   * collection.
-   */
-  private static void addToReachableAndRecurse(Object object, Class<?> declaredObjectClass,
-                                               Type genericType, List<String> currentPath, ReachableClasses reachableClasses)
-    throws ClassNotFoundException, IllegalAccessException {
-    Class<? extends Serializable> objectType = DeepEqualsTesterUtil
-      .getClass(declaredObjectClass, object);
-    // TODO(salguarnieri) modify if so all ignored classes are taken care of together
-    if (objectType.isPrimitive()) {
-      // ignore
-    }
-    else if (objectType.isEnum()) {
-      // assume enums do the right thing, ignore
-    }
-    else if (DeepEqualsTesterUtil.isSubclassOf(objectType, String.class)) {
-      // ignore
-    }
-    else if (DeepEqualsTesterUtil.isSubclassOf(objectType, File.class)) {
-      // ignore
-    }
-    else if (DeepEqualsTesterUtil.isSubclassOf(objectType, Collection.class) || DeepEqualsTesterUtil
-      .isSubclassOf(objectType, Map.class)) {
-      if (genericType instanceof ParameterizedType) {
-        ParameterizedType parameterType = (ParameterizedType)genericType;
-        Type[] actualTypeArguments = parameterType.getActualTypeArguments();
-        for (Type typeArgument : actualTypeArguments) {
-          if (typeArgument instanceof Class) {
-            List<String> childPath = Lists.newArrayList();
-            childPath.addAll(currentPath);
-            childPath.add("[[IN COLLECTION]]");
-            // this does not properly handle subtyping
-            addToReachableAndRecurse(null, (Class)typeArgument, null, childPath,
-                                     reachableClasses);
-          }
-          else {
-            Assert.fail("This case is not handled yet");
-          }
-        }
-      }
-      else {
-        Assert.fail("This case is not handled yet");
-      }
-    }
-    else if (objectType.isArray()) {
-      Class<?> typeInArray = objectType.getComponentType();
-      // This does not properly handle subtyping
-      List<String> childPath = Lists.newArrayList();
-      childPath.addAll(currentPath);
-      childPath.add("[[IN ARRAY]]");
-      addToReachableAndRecurse(null, typeInArray, null, childPath, reachableClasses);
-    }
-    else {
-      boolean doRecursion = !reachableClasses.alreadyFound(objectType);
-      reachableClasses.addPath(objectType, currentPath);
-      if (doRecursion) {
-        computeReachableFromObject(object, declaredObjectClass, currentPath, reachableClasses);
-      }
-    }
-  }
-}
diff --git a/blaze-base/tests/unittests/com/google/idea/blaze/base/model/primitives/ExecutionRootPathTest.java b/blaze-base/tests/unittests/com/google/idea/blaze/base/model/primitives/ExecutionRootPathTest.java
deleted file mode 100644
index b0426fb..0000000
--- a/blaze-base/tests/unittests/com/google/idea/blaze/base/model/primitives/ExecutionRootPathTest.java
+++ /dev/null
@@ -1,122 +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 com.google.idea.blaze.base.BlazeTestCase;
-import org.junit.Test;
-import org.mockito.Mockito;
-import org.mockito.stubbing.Answer;
-
-import java.io.File;
-
-import static com.google.common.truth.Truth.assertThat;
-
-public class ExecutionRootPathTest extends BlazeTestCase {
-  @Test
-  public void testSingleLevelPathEndInSlash() {
-    ExecutionRootPath executionRootPath = new ExecutionRootPath("foo");
-    assertThat(executionRootPath.getAbsoluteOrRelativeFile()).isEqualTo(new File("foo/"));
-
-    ExecutionRootPath executionRootPath2 = new ExecutionRootPath("foo/");
-    assertThat(executionRootPath2.getAbsoluteOrRelativeFile()).isEqualTo(new File("foo/"));
-  }
-
-  @Test
-  public void testMultiLevelPathEndInSlash() {
-    ExecutionRootPath executionRootPath = new ExecutionRootPath("foo/bar");
-    assertThat(executionRootPath.getAbsoluteOrRelativeFile()).isEqualTo(new File("foo/bar/"));
-
-    ExecutionRootPath executionRootPath2 = new ExecutionRootPath("foo/bar/");
-    assertThat(executionRootPath2.getAbsoluteOrRelativeFile()).isEqualTo(new File("foo/bar/"));
-  }
-
-  @Test
-  public void testAbsoluteFileDoesNotGetRerooted() {
-    ExecutionRootPath executionRootPath = new ExecutionRootPath("/root/foo/bar");
-    File rootedFile = executionRootPath.getFileRootedAt(new File("/core/dev"));
-    assertThat(rootedFile).isEqualTo(new File("/root/foo/bar"));
-  }
-
-  @Test
-  public void testRelativeFileGetsRerooted() {
-    ExecutionRootPath executionRootPath = new ExecutionRootPath("foo/bar");
-    File rootedFile = executionRootPath.getFileRootedAt(new File("/root"));
-    assertThat(rootedFile).isEqualTo(new File("/root/foo/bar"));
-  }
-
-  @Test
-  public void testCreateRelativePathWithTwoRelativePaths() {
-    ExecutionRootPath relativePathFragment = ExecutionRootPath.createAncestorRelativePath(
-      createMockDirectory("code/lib/fastmath"),
-      createMockDirectory("code/lib/fastmath/lib1")
-    );
-    assertThat(relativePathFragment).isNotNull();
-    assertThat(relativePathFragment.getAbsoluteOrRelativeFile()).isEqualTo(new File("lib1"));
-  }
-
-  @Test
-  public void testCreateRelativePathWithTwoRelativePathsWithNoRelativePath() {
-    ExecutionRootPath relativePathFragment = ExecutionRootPath.createAncestorRelativePath(
-      createMockDirectory("obj/lib/fastmath"),
-      createMockDirectory("code/lib/slowmath")
-    );
-    assertThat(relativePathFragment).isNull();
-  }
-
-  @Test
-  public void testCreateRelativePathWithTwoAbsolutePaths() {
-    ExecutionRootPath relativePathFragment = ExecutionRootPath.createAncestorRelativePath(
-      createMockDirectory("/code/lib/fastmath"),
-      createMockDirectory("/code/lib/fastmath/lib1")
-    );
-    assertThat(relativePathFragment).isNotNull();
-    assertThat(relativePathFragment.getAbsoluteOrRelativeFile()).isEqualTo(new File("lib1"));
-  }
-
-  @Test
-  public void testCreateRelativePathWithTwoAbsolutePathsWithNoRelativePath() {
-    ExecutionRootPath relativePathFragment = ExecutionRootPath.createAncestorRelativePath(
-      createMockDirectory("/obj/lib/fastmath"),
-      createMockDirectory("/code/lib/slowmath")
-    );
-    assertThat(relativePathFragment).isNull();
-  }
-
-  @Test
-  public void testCreateRelativePathWithOneAbsolutePathAndOneRelativePathReturnsNull1() {
-    ExecutionRootPath relativePathFragment = ExecutionRootPath.createAncestorRelativePath(
-      createMockDirectory("/code/lib/fastmath"),
-      createMockDirectory("code/lib/fastmath/lib1")
-    );
-    assertThat(relativePathFragment).isNull();
-  }
-
-  @Test
-  public void testCreateRelativePathWithOneAbsolutePathAndOneRelativePathReturnsNull2() {
-    ExecutionRootPath relativePathFragment = ExecutionRootPath.createAncestorRelativePath(
-      createMockDirectory("code/lib/fastmath"),
-      createMockDirectory("/code/lib/slowmath")
-    );
-    assertThat(relativePathFragment).isNull();
-  }
-
-  private static File createMockDirectory(String path) {
-    File org = new File(path);
-    File spy = Mockito.spy(org);
-    Mockito.when(spy.isDirectory()).then((Answer<Boolean>)invocationOnMock -> true);
-    return spy;
-  }
-}
diff --git a/blaze-base/tests/unittests/com/google/idea/blaze/base/model/primitives/LabelTest.java b/blaze-base/tests/unittests/com/google/idea/blaze/base/model/primitives/LabelTest.java
deleted file mode 100644
index a865ff1..0000000
--- a/blaze-base/tests/unittests/com/google/idea/blaze/base/model/primitives/LabelTest.java
+++ /dev/null
@@ -1,67 +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 com.google.idea.blaze.base.BlazeTestCase;
-import com.google.idea.blaze.base.experiments.ExperimentService;
-import com.google.idea.blaze.base.experiments.MockExperimentService;
-import org.jetbrains.annotations.NotNull;
-import org.junit.Test;
-
-import static com.google.common.truth.Truth.assertThat;
-
-public class LabelTest extends BlazeTestCase {
-
-  @Override
-  protected void initTest(
-    @NotNull Container applicationServices,
-    @NotNull Container projectServices) {
-    super.initTest(applicationServices, projectServices);
-    applicationServices.register(ExperimentService.class, new MockExperimentService());
-  }
-
-  @Test
-  public void testValidatePackage() {
-    // Legal names
-    assertThat(Label.validatePackagePath("foo")).isTrue();
-    assertThat(Label.validatePackagePath("f")).isTrue();
-    assertThat(Label.validatePackagePath("fooBAR")).isTrue();
-    assertThat(Label.validatePackagePath("foo/bar")).isTrue();
-    assertThat(Label.validatePackagePath("f9oo")).isTrue();
-    assertThat(Label.validatePackagePath("f_9oo")).isTrue();
-    // This is not advised but is technically legal
-    assertThat(Label.validatePackagePath("")).isTrue();
-
-    // Illegal names
-    assertThat(Label.validatePackagePath("Foo")).isFalse();
-    assertThat(Label.validatePackagePath("foo//bar")).isFalse();
-    assertThat(Label.validatePackagePath("foo/")).isFalse();
-    assertThat(Label.validatePackagePath("9oo")).isFalse();
-  }
-
-  @Test
-  public void testValidateLabel() {
-    // Valid labels
-    assertThat(Label.validate("//foo:bar")).isTrue();
-    assertThat(Label.validate("//foo/baz:bar")).isTrue();
-    assertThat(Label.validate("//:bar")).isTrue();
-
-    // Invalid labels
-    assertThat(Label.validate("//foo")).isFalse();
-    assertThat(Label.validate("foo")).isFalse();
-    assertThat(Label.validate("foo:bar")).isFalse();
-  }
-}
diff --git a/blaze-base/tests/unittests/com/google/idea/blaze/base/model/primitives/RuleNameTest.java b/blaze-base/tests/unittests/com/google/idea/blaze/base/model/primitives/RuleNameTest.java
deleted file mode 100644
index 34ccbe7..0000000
--- a/blaze-base/tests/unittests/com/google/idea/blaze/base/model/primitives/RuleNameTest.java
+++ /dev/null
@@ -1,52 +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 com.google.idea.blaze.base.BlazeTestCase;
-import org.junit.Test;
-
-import static com.google.common.truth.Truth.assertThat;
-
-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/blaze-base/tests/unittests/com/google/idea/blaze/base/model/primitives/TargetExpressionTest.java b/blaze-base/tests/unittests/com/google/idea/blaze/base/model/primitives/TargetExpressionTest.java
deleted file mode 100644
index a59a77b..0000000
--- a/blaze-base/tests/unittests/com/google/idea/blaze/base/model/primitives/TargetExpressionTest.java
+++ /dev/null
@@ -1,63 +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 com.google.idea.blaze.base.BlazeTestCase;
-import com.google.idea.blaze.base.experiments.ExperimentService;
-import com.google.idea.blaze.base.experiments.MockExperimentService;
-import org.jetbrains.annotations.NotNull;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.fail;
-
-/**
- * Tests for {@link com.google.idea.blaze.base.model.primitives.TargetExpressionFactory}.
- */
-@RunWith(JUnit4.class)
-public class TargetExpressionTest extends BlazeTestCase {
-  @Override
-  protected void initTest(
-    @NotNull Container applicationServices,
-    @NotNull Container projectServices) {
-    super.initTest(applicationServices, projectServices);
-    applicationServices.register(ExperimentService.class, new MockExperimentService());
-  }
-
-  @Test
-  public void validLabelShouldYieldLabel() {
-    TargetExpression target = TargetExpression.fromString("//package:rule");
-    assertThat(target).isInstanceOf(Label.class);
-  }
-
-  @Test
-  public void globExpressionShouldYieldGeneralTargetExpression() {
-    TargetExpression target = TargetExpression.fromString("//package/...");
-    assertThat(target.getClass()).isSameAs(TargetExpression.class);
-  }
-
-  @Test
-  public void emptyExpressionShouldThrow() {
-    try {
-      TargetExpression.fromString("");
-      fail("Empty expressions should not be allowed.");
-    }
-    catch (IllegalArgumentException expected) {
-    }
-  }
-}
diff --git a/blaze-base/tests/unittests/com/google/idea/blaze/base/model/primitives/WorkspacePathTest.java b/blaze-base/tests/unittests/com/google/idea/blaze/base/model/primitives/WorkspacePathTest.java
deleted file mode 100644
index 0e0c049..0000000
--- a/blaze-base/tests/unittests/com/google/idea/blaze/base/model/primitives/WorkspacePathTest.java
+++ /dev/null
@@ -1,65 +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 com.google.common.collect.Lists;
-import com.google.idea.blaze.base.BlazeTestCase;
-import com.google.idea.blaze.base.ui.BlazeValidationError;
-import org.junit.Test;
-
-import java.util.List;
-
-import static com.google.common.truth.Truth.assertThat;
-
-public class WorkspacePathTest extends BlazeTestCase {
-
-  @Test
-  public void testValidation() {
-    // Valid workspace paths
-    assertThat(WorkspacePath.validate("")).isTrue();
-    assertThat(WorkspacePath.validate("foo")).isTrue();
-    assertThat(WorkspacePath.validate("foo")).isTrue();
-    assertThat(WorkspacePath.validate("foo/bar")).isTrue();
-    assertThat(WorkspacePath.validate("foo/bar/baz")).isTrue();
-
-    // Invalid workspace paths
-    assertThat(WorkspacePath.validate("/foo")).isFalse();
-    assertThat(WorkspacePath.validate("//foo")).isFalse();
-    assertThat(WorkspacePath.validate("/")).isFalse();
-    assertThat(WorkspacePath.validate("foo/")).isFalse();
-    assertThat(WorkspacePath.validate("foo:")).isFalse();
-    assertThat(WorkspacePath.validate(":")).isFalse();
-    assertThat(WorkspacePath.validate("foo:bar")).isFalse();
-
-
-    List<BlazeValidationError> errors = Lists.newArrayList();
-
-    WorkspacePath.validate("/foo", errors);
-    assertThat(errors.get(0).getError()).isEqualTo("Workspace path may not start with '/': /foo");
-    errors.clear();
-
-    WorkspacePath.validate("/", errors);
-    assertThat(errors.get(0).getError()).isEqualTo("Workspace path may not start with '/': /");
-    errors.clear();
-
-    WorkspacePath.validate("foo/", errors);
-    assertThat(errors.get(0).getError()).isEqualTo("Workspace path may not end with '/': foo/");
-    errors.clear();
-
-    WorkspacePath.validate("foo:bar", errors);
-    assertThat(errors.get(0).getError()).isEqualTo("Workspace path may not contain ':': foo:bar");
-  }
-}
diff --git a/blaze-base/tests/unittests/com/google/idea/blaze/base/projectview/ProjectViewSetTest.java b/blaze-base/tests/unittests/com/google/idea/blaze/base/projectview/ProjectViewSetTest.java
deleted file mode 100644
index 0c295cf..0000000
--- a/blaze-base/tests/unittests/com/google/idea/blaze/base/projectview/ProjectViewSetTest.java
+++ /dev/null
@@ -1,68 +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.projectview;
-
-import com.google.idea.blaze.base.BlazeTestCase;
-import com.google.idea.blaze.base.TestUtils;
-import com.google.idea.blaze.base.experiments.ExperimentService;
-import com.google.idea.blaze.base.experiments.MockExperimentService;
-import com.google.idea.blaze.base.model.primitives.*;
-import com.google.idea.blaze.base.projectview.section.*;
-import com.google.idea.blaze.base.projectview.section.sections.*;
-import com.google.idea.blaze.base.sync.BlazeSyncPlugin;
-import org.jetbrains.annotations.NotNull;
-import org.junit.Test;
-
-import static com.google.common.truth.Truth.assertThat;
-
-public class ProjectViewSetTest extends BlazeTestCase {
-
-  @Override
-  protected void initTest(@NotNull Container applicationServices, @NotNull Container projectServices) {
-    super.initTest(applicationServices, projectServices);
-    applicationServices.register(ExperimentService.class, new MockExperimentService());
-    registerExtensionPoint(BlazeSyncPlugin.EP_NAME, BlazeSyncPlugin.class);
-  }
-
-  @Test
-  public void testProjectViewSetSerializable() throws Exception {
-    ProjectViewSet projectViewSet = ProjectViewSet.builder()
-      .add(ProjectView.builder()
-             .put(ListSection.builder(DirectorySection.KEY).add(DirectoryEntry.include(new WorkspacePath("test"))))
-             .put(ListSection.builder(TargetSection.KEY).add(TargetExpression.fromString("//test:all")))
-             .put(ScalarSection.builder(ImportSection.KEY).set(new WorkspacePath("test")))
-             .put(ListSection.builder(TestSourceSection.KEY).add(new Glob("javatests/*")))
-             .put(ListSection.builder(ExcludedSourceSection.KEY).add(new Glob("*.java")))
-             .put(ListSection.builder(BuildFlagsSection.KEY).add("--android_sdk=abcd"))
-             .put(ListSection.builder(ImportTargetOutputSection.KEY).add(new Label("//test:test")))
-             .put(ListSection.builder(ExcludeTargetSection.KEY).add(new Label("//test:test")))
-             .put(ScalarSection.builder(WorkspaceTypeSection.KEY).set(WorkspaceType.JAVA))
-             .put(ListSection.builder(AdditionalLanguagesSection.KEY).add(LanguageClass.JAVA))
-             .put(ScalarSection.builder(MetricsProjectSection.KEY).set("my project"))
-             .build())
-      .build();
-
-    // Assert we have all sections
-    assert projectViewSet.getTopLevelProjectViewFile() != null;
-    ProjectView projectView = projectViewSet.getTopLevelProjectViewFile().projectView;
-    for (SectionParser parser : Sections.getParsers()) {
-      Section section = projectView.getSectionOfType(parser.getSectionKey());
-      assertThat(section).isNotNull();
-    }
-
-    TestUtils.assertIsSerializable(projectViewSet);
-  }
-}
diff --git a/blaze-base/tests/unittests/com/google/idea/blaze/base/projectview/ProjectViewVerifierTest.java b/blaze-base/tests/unittests/com/google/idea/blaze/base/projectview/ProjectViewVerifierTest.java
deleted file mode 100644
index c643521..0000000
--- a/blaze-base/tests/unittests/com/google/idea/blaze/base/projectview/ProjectViewVerifierTest.java
+++ /dev/null
@@ -1,168 +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.projectview;
-
-import com.google.common.collect.ImmutableSet;
-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.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.section.ListSection;
-import com.google.idea.blaze.base.projectview.section.sections.DirectoryEntry;
-import com.google.idea.blaze.base.projectview.section.sections.DirectorySection;
-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.WorkspaceLanguageSettings;
-import org.jetbrains.annotations.NotNull;
-import org.junit.Test;
-
-import java.io.File;
-
-/**
- * Tests for ProjectViewVerifier
- */
-public class ProjectViewVerifierTest extends BlazeTestCase {
-
-  private String FAKE_ROOT = "/root";
-  private WorkspaceRoot workspaceRoot = new WorkspaceRoot(new File(FAKE_ROOT));
-  private MockWorkspaceScanner workspaceScanner;
-  private ErrorCollector errorCollector = new ErrorCollector();
-  private BlazeContext context;
-  private WorkspaceLanguageSettings workspaceLanguageSettings = new WorkspaceLanguageSettings(
-    WorkspaceType.JAVA,
-    ImmutableSet.of( LanguageClass.JAVA)
-  );
-
-  @Override
-  protected void initTest(@NotNull Container applicationServices, @NotNull Container projectServices) {
-    super.initTest(applicationServices, projectServices);
-    registerExtensionPoint(BlazeSyncPlugin.EP_NAME, BlazeSyncPlugin.class);
-
-    workspaceScanner = new MockWorkspaceScanner();
-    applicationServices.register(WorkspaceScanner.class, workspaceScanner);
-    context = new BlazeContext();
-    context.addOutputSink(IssueOutput.class, errorCollector);
-  }
-
-  @Test
-  public void testNoIssues() {
-    ProjectViewSet projectViewSet = ProjectViewSet.builder()
-      .add(ProjectView.builder()
-             .put(ListSection.builder(DirectorySection.KEY)
-                    .add(DirectoryEntry.include(new WorkspacePath("java/com/google/android/apps/example")))
-                    .add(DirectoryEntry.include(new WorkspacePath("java/com/google/android/apps/example2"))))
-             .build())
-      .build();
-    workspaceScanner.addProjectView(workspaceRoot, projectViewSet);
-    ProjectViewVerifier.verifyProjectView(context, workspaceRoot, projectViewSet, workspaceLanguageSettings);
-    errorCollector.assertNoIssues();
-  }
-
-  @Test
-  public void testExcludingExactRootResultsInIssue() {
-    ProjectViewSet projectViewSet = ProjectViewSet.builder()
-      .add(ProjectView.builder()
-             .put(ListSection.builder(DirectorySection.KEY)
-                    .add(DirectoryEntry.include(new WorkspacePath("java/com/google/android/apps/example")))
-                    .add(DirectoryEntry.exclude(new WorkspacePath("java/com/google/android/apps/example"))))
-             .build())
-      .build();
-    workspaceScanner.addProjectView(workspaceRoot, projectViewSet);
-    ProjectViewVerifier.verifyProjectView(context, workspaceRoot, projectViewSet, workspaceLanguageSettings);
-    errorCollector.assertIssues(
-      "java/com/google/android/apps/example is included, but that contradicts java/com/google/android/apps/example which was excluded");
-  }
-
-  @Test
-  public void testExcludingRootViaParentResultsInIssue() {
-    ProjectViewSet projectViewSet = ProjectViewSet.builder()
-      .add(ProjectView.builder()
-             .put(ListSection.builder(DirectorySection.KEY)
-                    .add(DirectoryEntry.include(new WorkspacePath("java/com/google/android/apps/example")))
-                    .add(DirectoryEntry.exclude(new WorkspacePath("java/com/google/android/apps"))))
-             .build())
-      .build();
-    workspaceScanner.addProjectView(workspaceRoot, projectViewSet);
-    ProjectViewVerifier.verifyProjectView(context, workspaceRoot, projectViewSet, workspaceLanguageSettings);
-    errorCollector.assertIssues(
-      "java/com/google/android/apps/example is included, but that contradicts java/com/google/android/apps which was excluded");
-  }
-
-  @Test
-  public void testExcludingSubdirectoryOfRootResultsInNoIssues() {
-    ProjectViewSet projectViewSet = ProjectViewSet.builder()
-      .add(ProjectView.builder()
-             .put(ListSection.builder(DirectorySection.KEY)
-                    .add(DirectoryEntry.include(new WorkspacePath("java/com/google/android/apps/example")))
-                    .add(DirectoryEntry.exclude(new WorkspacePath("java/com/google/android/apps/example/subdir"))))
-             .build())
-      .build();
-    workspaceScanner.addProjectView(workspaceRoot, projectViewSet);
-    ProjectViewVerifier.verifyProjectView(context, workspaceRoot, projectViewSet, workspaceLanguageSettings);
-    errorCollector.assertNoIssues();
-  }
-
-  @Test
-  public void testImportRootMissingResultsInIssue() {
-    ProjectViewSet projectViewSet = ProjectViewSet.builder()
-      .add(ProjectView.builder()
-             .put(ListSection.builder(DirectorySection.KEY)
-                    .add(DirectoryEntry.include(new WorkspacePath("java/com/google/android/apps/example"))))
-             .build())
-      .build();
-    ProjectViewVerifier.verifyProjectView(context, workspaceRoot, projectViewSet, workspaceLanguageSettings);
-    errorCollector.assertIssues(
-      String.format("Directory '%s' specified in import roots not found under workspace root '%s'",
-                    "java/com/google/android/apps/example", "/root"));
-  }
-
-  @Test
-  public void testOverlappingDirectoriesResultInIssue() {
-    ProjectViewSet projectViewSet = ProjectViewSet.builder()
-      .add(ProjectView.builder()
-             .put(ListSection.builder(DirectorySection.KEY)
-                    .add(DirectoryEntry.include(new WorkspacePath("java/com/google/android/apps/example"))))
-             .build())
-      .add(ProjectView.builder()
-             .put(ListSection.builder(DirectorySection.KEY)
-                    .add(DirectoryEntry.include(new WorkspacePath("java/com/google/android"))))
-             .build())
-      .build();
-    workspaceScanner.addProjectView(workspaceRoot, projectViewSet);
-    ProjectViewVerifier.verifyProjectView(context, workspaceRoot, projectViewSet, workspaceLanguageSettings);
-    errorCollector.assertIssues(
-      "Overlapping directories: java/com/google/android/apps/example already included by java/com/google/android"
-    );
-  }
-
-  @Test
-  public void testRootDirectoryNotSpuriouslyOverlappingItself() {
-    ProjectViewSet projectViewSet = ProjectViewSet.builder()
-      .add(ProjectView.builder()
-             .put(ListSection.builder(DirectorySection.KEY)
-                    .add(DirectoryEntry.include(new WorkspacePath("."))))
-             .build())
-      .build();
-    workspaceScanner.addProjectView(workspaceRoot, projectViewSet);
-    ProjectViewVerifier.verifyProjectView(context, workspaceRoot, projectViewSet, workspaceLanguageSettings);
-    errorCollector.assertNoIssues();
-  }
-}
diff --git a/blaze-base/tests/unittests/com/google/idea/blaze/base/projectview/parser/ProjectViewParserTest.java b/blaze-base/tests/unittests/com/google/idea/blaze/base/projectview/parser/ProjectViewParserTest.java
deleted file mode 100644
index b7a8e84..0000000
--- a/blaze-base/tests/unittests/com/google/idea/blaze/base/projectview/parser/ProjectViewParserTest.java
+++ /dev/null
@@ -1,337 +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.projectview.parser;
-
-import com.google.common.base.Joiner;
-import com.google.common.collect.Maps;
-import com.google.idea.blaze.base.BlazeTestCase;
-import com.google.idea.blaze.base.experiments.ExperimentService;
-import com.google.idea.blaze.base.experiments.MockExperimentService;
-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.ProjectViewSet;
-import com.google.idea.blaze.base.projectview.ProjectViewStorageManager;
-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.DirectoryEntry;
-import com.google.idea.blaze.base.projectview.section.sections.DirectorySection;
-import com.google.idea.blaze.base.projectview.section.sections.ImportSection;
-import com.google.idea.blaze.base.projectview.section.sections.TargetSection;
-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.workspace.WorkspacePathResolverImpl;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import org.junit.Test;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Collection;
-import java.util.Map;
-
-import static com.google.common.truth.Truth.assertThat;
-
-public class ProjectViewParserTest extends BlazeTestCase {
-  private ProjectViewParser projectViewParser;
-  private BlazeContext context;
-  private ErrorCollector errorCollector;
-  private WorkspaceRoot workspaceRoot = new WorkspaceRoot(new File("/"));
-  private MockProjectViewStorageManager projectViewStorageManager;
-
-  static class MockProjectViewStorageManager extends ProjectViewStorageManager {
-    Map<String, String> projectViewFiles = Maps.newHashMap();
-    @Nullable
-    @Override
-    public String loadProjectView(@NotNull File projectViewFile) throws IOException {
-      return projectViewFiles.get(projectViewFile.getPath());
-    }
-
-    @Override
-    public void writeProjectView(@NotNull String projectViewText, @NotNull File projectViewFile) throws IOException {
-      // no-op
-    }
-
-    void add(String name, String... text) {
-      projectViewFiles.put(name, Joiner.on('\n').join(text));
-    }
-  }
-
-  @Override
-  protected void initTest(@NotNull Container applicationServices, @NotNull Container projectServices) {
-    super.initTest(applicationServices, projectServices);
-    context = new BlazeContext();
-    errorCollector = new ErrorCollector();
-    context.addOutputSink(IssueOutput.class, errorCollector);
-    projectViewParser = new ProjectViewParser(context, new WorkspacePathResolverImpl(workspaceRoot));
-    projectViewStorageManager = new MockProjectViewStorageManager();
-    applicationServices.register(ProjectViewStorageManager.class, projectViewStorageManager);
-    applicationServices.register(ExperimentService.class, new MockExperimentService());
-    registerExtensionPoint(BlazeSyncPlugin.EP_NAME, BlazeSyncPlugin.class);
-  }
-
-  @Test
-  public void testDirectoriesAndTargets() throws Exception {
-    projectViewStorageManager.add(".blazeproject",
-                                  "directories:",
-                                  "  java/com/google",
-                                  "  java/com/google/android",
-                                  "  -java/com/google/android/notme",
-                                  "",
-                                  "targets:",
-                                  "  //java/com/google:all",
-                                  "  //java/com/google/...:all",
-                                  "  -//java/com/google:thistarget"
-    );
-    projectViewParser.parseProjectView(new File(".blazeproject"));
-    errorCollector.assertNoIssues();
-
-    ProjectViewSet projectViewSet = projectViewParser.getResult();
-    ProjectViewSet.ProjectViewFile projectViewFile = projectViewSet.getTopLevelProjectViewFile();
-    assertThat(projectViewFile).isNotNull();
-    assertThat(projectViewFile.projectViewFile).isEqualTo(new File(".blazeproject"));
-    assertThat(projectViewSet.getProjectViewFiles()).containsExactly(projectViewFile);
-
-    ProjectView projectView = projectViewFile.projectView;
-    assertThat(projectView.getSectionOfType(DirectorySection.KEY).items())
-      .containsExactly(
-        new DirectoryEntry(new WorkspacePath("java/com/google"), true),
-        new DirectoryEntry(new WorkspacePath("java/com/google/android"), true),
-        new DirectoryEntry(new WorkspacePath("java/com/google/android/notme"), false)
-      );
-    assertThat(projectView.getSectionOfType(TargetSection.KEY).items())
-      .containsExactly(
-        TargetExpression.fromString("//java/com/google:all"),
-        TargetExpression.fromString("//java/com/google/...:all"),
-        TargetExpression.fromString("-//java/com/google:thistarget")
-      );
-  }
-
-  @Test
-  public void testRootDirectory() throws Exception {
-    projectViewStorageManager.add(".blazeproject",
-                                  "directories:",
-                                  "  .",
-                                  "  -java/com/google/android/notme",
-                                  "",
-                                  "targets:",
-                                  "  //java/com/google:all"
-    );
-    projectViewParser.parseProjectView(new File(".blazeproject"));
-    errorCollector.assertNoIssues();
-
-    ProjectViewSet projectViewSet = projectViewParser.getResult();
-    ProjectViewSet.ProjectViewFile projectViewFile = projectViewSet.getTopLevelProjectViewFile();
-    assertThat(projectViewFile).isNotNull();
-    assertThat(projectViewFile.projectViewFile).isEqualTo(new File(".blazeproject"));
-    assertThat(projectViewSet.getProjectViewFiles()).containsExactly(projectViewFile);
-
-    ProjectView projectView = projectViewFile.projectView;
-    assertThat(projectView.getSectionOfType(DirectorySection.KEY).items())
-      .containsExactly(
-        new DirectoryEntry(new WorkspacePath(""), true),
-        new DirectoryEntry(new WorkspacePath("java/com/google/android/notme"), false)
-      );
-    assertThat(projectView.getSectionOfType(TargetSection.KEY).items())
-      .containsExactly(
-        TargetExpression.fromString("//java/com/google:all")
-      );
-
-    String text = ProjectViewParser.projectViewToString(projectView);
-    assertThat(text).isEqualTo(
-      Joiner.on('\n').join(
-        "directories:",
-        "  .",
-        "  -java/com/google/android/notme",
-        "",
-        "targets:",
-        "  //java/com/google:all",
-        ""
-      )
-    );
-  }
-
-  @Test
-  public void testPrint() {
-    ProjectView projectView = ProjectView.builder()
-      .put(ListSection.builder(DirectorySection.KEY)
-             .add(DirectoryEntry.include(new WorkspacePath("java/com/google/one")))
-             .add(DirectoryEntry.exclude(new WorkspacePath("java/com/google/two"))))
-      .put(ListSection.builder(TargetSection.KEY)
-             .add(TargetExpression.fromString("//java/com/google:one"))
-             .add(TargetExpression.fromString("//java/com/google:two")))
-      .put(ScalarSection.builder(ImportSection.KEY)
-             .set(new WorkspacePath("some/file.blazeproject")))
-      .build();
-    String text = ProjectViewParser.projectViewToString(projectView);
-    assertThat(text).isEqualTo(
-      Joiner.on('\n').join(
-        "import some/file.blazeproject",
-        "",
-        "directories:",
-        "  java/com/google/one",
-        "  -java/com/google/two",
-        "",
-        "targets:",
-        "  //java/com/google:one",
-        "  //java/com/google:two",
-        ""
-      ));
-  }
-
-  @Test
-  public void testImport() {
-    projectViewStorageManager.add("/parent.blazeproject",
-                                  "directories:",
-                                  "  parent",
-                                  "");
-    projectViewStorageManager.add(".blazeproject",
-                                  "import parent.blazeproject",
-                                  "directories:",
-                                  "  child",
-                                  "");
-    projectViewParser.parseProjectView(new File(".blazeproject"));
-    errorCollector.assertNoIssues();
-
-    ProjectViewSet projectViewSet = projectViewParser.getResult();
-    assertThat(projectViewSet.getProjectViewFiles()).hasSize(2);
-    Collection<DirectoryEntry> entries = projectViewSet.listItems(DirectorySection.KEY);
-    assertThat(entries).containsExactly(
-      new DirectoryEntry(new WorkspacePath("parent"), true),
-      new DirectoryEntry(new WorkspacePath("child"), true)
-    );
-  }
-
-  @Test
-  public void testMinimumIndentRequired() {
-    projectViewStorageManager.add(".blazeproject",
-                                  "directories:",
-                                  "  java/com/google",
-                                  "java/com/google2",
-                                  "");
-    projectViewParser.parseProjectView(new File(".blazeproject"));
-    errorCollector.assertIssues("Could not parse: 'java/com/google2'");
-  }
-
-  @Test
-  public void testIncorrectIndentationResultsInIssue() {
-    projectViewStorageManager.add(".blazeproject",
-                                  "directories:",
-                                  "  java/com/google",
-                                  " java/com/google2",
-                                  "");
-    projectViewParser.parseProjectView(new File(".blazeproject"));
-    errorCollector.assertIssues("Invalid indentation. Project view files are indented with 2 spaces.");
-  }
-
-  @Test
-  public void testCanParseWithMissingCarriageReturnAtEndOfSection() {
-    projectViewStorageManager.add(".blazeproject",
-                                  "directories:",
-                                  "  java/com/google");
-    projectViewParser.parseProjectView(new File(".blazeproject"));
-    ProjectView projectView = projectViewParser.getResult().getTopLevelProjectViewFile().projectView;
-    assertThat(projectView.getSectionOfType(DirectorySection.KEY).items())
-      .containsExactly(new DirectoryEntry(new WorkspacePath("java/com/google"), true));
-  }
-
-  @Test
-  public void testWhitespaceIsIgnoredBetweenSections() {
-    projectViewStorageManager.add(".blazeproject",
-                                  "",
-                                  "directories:",
-                                  "  java/com/google",
-                                  "",
-                                  "");
-    projectViewParser.parseProjectView(new File(".blazeproject"));
-    ProjectView projectView = projectViewParser.getResult().getTopLevelProjectViewFile().projectView;
-    assertThat(projectView.getSectionOfType(DirectorySection.KEY).items())
-      .containsExactly(new DirectoryEntry(new WorkspacePath("java/com/google"), true));
-  }
-
-  @Test
-  public void testImportMissingFileResultsInIssue() {
-    projectViewStorageManager.add(".blazeproject",
-                                  "import parent.blazeproject");
-    projectViewParser.parseProjectView(new File(".blazeproject"));
-    errorCollector.assertIssues("Could not load project view file: '/parent.blazeproject'");
-  }
-
-  @Test
-  public void testDuplicateSectionsResultsInIssue() {
-    projectViewStorageManager.add(".blazeproject",
-                                  "directories:",
-                                  "  java/com/google",
-                                  "directories:",
-                                  "  java/com/google");
-    projectViewParser.parseProjectView(new File(".blazeproject"));
-    errorCollector.assertIssues("Duplicate attribute: 'directories'");
-  }
-
-  @Test
-  public void testMissingSectionResultsInIssue() {
-    projectViewStorageManager.add(".blazeproject",
-                                  "nosuchsection:",
-                                  "  java/com/google");
-    projectViewParser.parseProjectView(new File(".blazeproject"));
-    errorCollector.assertIssues("Could not parse: 'nosuchsection:'");
-  }
-
-  @Test
-  public void testMissingColonResultInIssue() {
-    projectViewStorageManager.add(".blazeproject",
-                                  "directories",
-                                  "  java/com/google");
-    projectViewParser.parseProjectView(new File(".blazeproject"));
-    errorCollector.assertIssues("Could not parse: 'directories'");
-  }
-
-  @Test
-  public void testEmptySectionYieldsError() {
-    projectViewStorageManager.add(".blazeproject",
-                                  "directories:",
-                                  "");
-    projectViewParser.parseProjectView(new File(".blazeproject"));
-    errorCollector.assertIssues("Empty section: 'directories'");
-  }
-
-  @Test
-  public void testCommentsAreSkipped() throws Exception {
-    projectViewStorageManager.add(".blazeproject",
-                                  "# comment",
-                                  "directories:",
-                                  "# another comment",
-                                  "  java/com/google",
-                                  "  # comment",
-                                  "  java/com/google/android",
-                                  ""
-    );
-    projectViewParser.parseProjectView(new File(".blazeproject"));
-    errorCollector.assertNoIssues();
-
-    ProjectViewSet projectViewSet = projectViewParser.getResult();
-    ProjectViewSet.ProjectViewFile projectViewFile = projectViewSet.getTopLevelProjectViewFile();
-    ProjectView projectView = projectViewFile.projectView;
-    assertThat(projectView.getSectionOfType(DirectorySection.KEY).items())
-      .containsExactly(
-        new DirectoryEntry(new WorkspacePath("java/com/google"), true),
-        new DirectoryEntry(new WorkspacePath("java/com/google/android"), true)
-      );
-  }
-}
diff --git a/blaze-base/tests/unittests/com/google/idea/blaze/base/rulemaps/ReverseDependencyMapTest.java b/blaze-base/tests/unittests/com/google/idea/blaze/base/rulemaps/ReverseDependencyMapTest.java
deleted file mode 100644
index 7a3a524..0000000
--- a/blaze-base/tests/unittests/com/google/idea/blaze/base/rulemaps/ReverseDependencyMapTest.java
+++ /dev/null
@@ -1,149 +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.ImmutableMap;
-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.model.primitives.Label;
-import com.google.idea.blaze.base.ideinfo.RuleMapBuilder;
-import org.jetbrains.annotations.NotNull;
-import org.junit.Test;
-
-import static com.google.common.truth.Truth.assertThat;
-
-public class ReverseDependencyMapTest extends BlazeTestCase {
-  @Override
-  protected void initTest(@NotNull Container applicationServices, @NotNull Container projectServices) {
-    super.initTest(applicationServices, projectServices);
-  }
-
-  @Test
-  public void testSingleDep() {
-    RuleMapBuilder builder = RuleMapBuilder.builder();
-    ImmutableMap<Label, RuleIdeInfo> ruleMap = builder
-      .addRule(RuleIdeInfo.builder()
-                 .setBuildFile(sourceRoot("test/BUILD"))
-                 .setLabel("//l:l1")
-                 .setKind("java_library")
-                 .addDependency("//l:l2"))
-      .addRule(RuleIdeInfo.builder()
-                 .setBuildFile(sourceRoot("test/BUILD"))
-                 .setLabel("//l:l2")
-                 .setKind("java_library"))
-      .build();
-
-    ImmutableMultimap<Label, Label> reverseDependencies = ReverseDependencyMap.createRdepsMap(ruleMap);
-    assertThat(reverseDependencies).containsEntry(new Label("//l:l2"), new Label("//l:l1"));
-  }
-
-  @Test
-  public void testLabelDepsOnTwoLabels() {
-    RuleMapBuilder builder = RuleMapBuilder.builder();
-    ImmutableMap<Label, RuleIdeInfo> ruleMap = builder
-      .addRule(RuleIdeInfo.builder()
-                 .setBuildFile(sourceRoot("test/BUILD"))
-                 .setLabel("//l:l1")
-                 .setKind("java_library")
-                 .addDependency("//l:l2")
-                 .addDependency("//l:l3"))
-      .addRule(RuleIdeInfo.builder()
-                 .setBuildFile(sourceRoot("test/BUILD"))
-                 .setLabel("//l:l2")
-                 .setKind("java_library"))
-      .addRule(RuleIdeInfo.builder()
-                 .setBuildFile(sourceRoot("test/BUILD"))
-                 .setLabel("//l:l3")
-                 .setKind("java_library"))
-      .build();
-
-    ImmutableMultimap<Label, Label> reverseDependencies = ReverseDependencyMap.createRdepsMap(ruleMap);
-    assertThat(reverseDependencies).containsEntry(new Label("//l:l2"), new Label("//l:l1"));
-    assertThat(reverseDependencies).containsEntry(new Label("//l:l3"), new Label("//l:l1"));
-  }
-
-  @Test
-  public void testTwoLabelsDepOnSameLabel() {
-    RuleMapBuilder builder = RuleMapBuilder.builder();
-    ImmutableMap<Label, RuleIdeInfo> ruleMap = builder
-      .addRule(RuleIdeInfo.builder()
-                 .setBuildFile(sourceRoot("test/BUILD"))
-                 .setLabel("//l:l1")
-                 .setKind("java_library")
-                 .addDependency("//l:l3"))
-      .addRule(RuleIdeInfo.builder()
-                 .setBuildFile(sourceRoot("test/BUILD"))
-                 .setLabel("//l:l2")
-                 .addDependency("//l:l3")
-                 .setKind("java_library"))
-      .addRule(RuleIdeInfo.builder()
-                 .setBuildFile(sourceRoot("test/BUILD"))
-                 .setLabel("//l:l3")
-                 .setKind("java_library"))
-      .build();
-
-    ImmutableMultimap<Label, Label> reverseDependencies = ReverseDependencyMap.createRdepsMap(ruleMap);
-    assertThat(reverseDependencies).containsEntry(new Label("//l:l3"), new Label("//l:l1"));
-    assertThat(reverseDependencies).containsEntry(new Label("//l:l3"), new Label("//l:l2"));
-  }
-
-  @Test
-  public void testThreeLevelGraph() {
-    RuleMapBuilder builder = RuleMapBuilder.builder();
-    ImmutableMap<Label, RuleIdeInfo> ruleMap = builder
-      .addRule(RuleIdeInfo.builder()
-                 .setBuildFile(sourceRoot("test/BUILD"))
-                 .setLabel("//l:l1")
-                 .setKind("java_library")
-                 .addDependency("//l:l3"))
-      .addRule(RuleIdeInfo.builder()
-                 .setBuildFile(sourceRoot("test/BUILD"))
-                 .setLabel("//l:l2")
-                 .addDependency("//l:l3")
-                 .setKind("java_library"))
-      .addRule(RuleIdeInfo.builder()
-                 .setBuildFile(sourceRoot("test/BUILD"))
-                 .setLabel("//l:l3")
-                 .setKind("java_library"))
-      .addRule(RuleIdeInfo.builder()
-                 .setBuildFile(sourceRoot("test/BUILD"))
-                 .setLabel("//l:l4")
-                 .addDependency("//l:l3")
-                 .setKind("java_library"))
-      .addRule(RuleIdeInfo.builder()
-                 .setBuildFile(sourceRoot("test/BUILD"))
-                 .setLabel("//l:l5")
-                 .addDependency("//l:l4")
-                 .setKind("java_library"))
-      .build();
-
-    ImmutableMultimap<Label, Label> reverseDependencies = ReverseDependencyMap.createRdepsMap(ruleMap);
-    assertThat(reverseDependencies).containsEntry(new Label("//l:l3"), new Label("//l:l1"));
-    assertThat(reverseDependencies).containsEntry(new Label("//l:l3"), new Label("//l:l2"));
-    assertThat(reverseDependencies).containsEntry(new Label("//l:l3"), new Label("//l:l4"));
-    assertThat(reverseDependencies).containsEntry(new Label("//l:l4"), new Label("//l:l5"));
-  }
-
-  private static ArtifactLocation sourceRoot(String relativePath) {
-    return ArtifactLocation.builder()
-      .setRootPath("/")
-      .setRelativePath(relativePath)
-      .setIsSource(true)
-      .build();
-  }
-}
diff --git a/blaze-base/tests/unittests/com/google/idea/blaze/base/run/testmap/TestMapTest.java b/blaze-base/tests/unittests/com/google/idea/blaze/base/run/testmap/TestMapTest.java
deleted file mode 100644
index 2930623..0000000
--- a/blaze-base/tests/unittests/com/google/idea/blaze/base/run/testmap/TestMapTest.java
+++ /dev/null
@@ -1,242 +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.testmap;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableMultimap;
-import com.google.idea.blaze.base.BlazeTestCase;
-import com.google.idea.blaze.base.experiments.ExperimentService;
-import com.google.idea.blaze.base.experiments.MockExperimentService;
-import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.ideinfo.RuleMapBuilder;
-import com.google.idea.blaze.base.ideinfo.TestIdeInfo;
-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 org.jetbrains.annotations.NotNull;
-import org.junit.Test;
-
-import java.io.File;
-
-import static com.google.common.truth.Truth.assertThat;
-
-public class TestMapTest extends BlazeTestCase {
-  private RuleMapBuilder ruleMapBuilder;
-
-  @Override
-  protected void initTest(@NotNull Container applicationServices, @NotNull Container projectServices) {
-    super.initTest(applicationServices, projectServices);
-    applicationServices.register(ExperimentService.class, new MockExperimentService());
-    ruleMapBuilder = RuleMapBuilder.builder();
-  }
-
-  @Test
-  public void testTrivialTestMap() throws Exception {
-    ImmutableMap<Label, RuleIdeInfo> ruleMap = ruleMapBuilder
-      .addRule(RuleIdeInfo.builder()
-                 .setBuildFile(sourceRoot("test/BUILD"))
-                 .setLabel("//test:test")
-                 .setKind("java_test")
-                 .addSource(sourceRoot("test/Test.java")))
-      .build();
-
-    TestMap testMap = new TestMap(project, ruleMap);
-    ImmutableMultimap<Label, Label> reverseDependencies = ReverseDependencyMap.createRdepsMap(ruleMap);
-    assertThat(testMap.testTargetsForSourceFile(reverseDependencies, new File("/test/Test.java"), null))
-      .containsExactly(new Label("//test:test"));
-  }
-
-  @Test
-  public void testOneStepRemovedTestMap() throws Exception {
-    ImmutableMap<Label, RuleIdeInfo> ruleMap = ruleMapBuilder
-      .addRule(RuleIdeInfo.builder()
-                 .setBuildFile(sourceRoot("test/BUILD"))
-                 .setLabel("//test:test")
-                 .setKind("java_test")
-                 .addDependency("//test:lib"))
-      .addRule(RuleIdeInfo.builder()
-                 .setBuildFile(sourceRoot("test/BUILD"))
-                 .setLabel("//test:lib")
-                 .setKind("java_library")
-                 .addSource(sourceRoot("test/Test.java")))
-      .build();
-
-    TestMap testMap = new TestMap(project, ruleMap);
-    ImmutableMultimap<Label, Label> reverseDependencies = ReverseDependencyMap.createRdepsMap(ruleMap);
-    assertThat(testMap.testTargetsForSourceFile(reverseDependencies, new File("/test/Test.java"), null))
-      .containsExactly(new Label("//test:test"));
-  }
-
-  @Test
-  public void testTwoCandidatesTestMap() throws Exception {
-    ImmutableMap<Label, RuleIdeInfo> ruleMap = ruleMapBuilder
-      .addRule(RuleIdeInfo.builder()
-                 .setBuildFile(sourceRoot("test/BUILD"))
-                 .setLabel("//test:test")
-                 .setKind("java_test")
-                 .addDependency("//test:lib"))
-      .addRule(RuleIdeInfo.builder()
-                 .setBuildFile(sourceRoot("test/BUILD"))
-                 .setLabel("//test:test2")
-                 .setKind("java_test")
-                 .addDependency("//test:lib"))
-      .addRule(RuleIdeInfo.builder()
-                 .setBuildFile(sourceRoot("test/BUILD"))
-                 .setLabel("//test:lib")
-                 .setKind("java_library")
-                 .addSource(sourceRoot("test/Test.java")))
-      .build();
-
-    TestMap testMap = new TestMap(project, ruleMap);
-    ImmutableMultimap<Label, Label> reverseDependencies = ReverseDependencyMap.createRdepsMap(ruleMap);
-    assertThat(testMap.testTargetsForSourceFile(reverseDependencies, new File("/test/Test.java"), null))
-      .containsExactly(new Label("//test:test"), new Label("//test:test2"));
-  }
-
-  @Test
-  public void testBfsPreferred() throws Exception {
-    ImmutableMap<Label, RuleIdeInfo> ruleMap = ruleMapBuilder
-      .addRule(RuleIdeInfo.builder()
-                 .setBuildFile(sourceRoot("test/BUILD"))
-                 .setLabel("//test:lib")
-                 .setKind("java_library")
-                 .addSource(sourceRoot("test/Test.java")))
-      .addRule(RuleIdeInfo.builder()
-                 .setBuildFile(sourceRoot("test/BUILD"))
-                 .setLabel("//test:lib2")
-                 .setKind("java_library")
-                 .addDependency("//test:lib"))
-      .addRule(RuleIdeInfo.builder()
-                 .setBuildFile(sourceRoot("test/BUILD"))
-                 .setLabel("//test:test2")
-                 .setKind("java_test")
-                 .addDependency("//test:lib2"))
-      .addRule(RuleIdeInfo.builder()
-                 .setBuildFile(sourceRoot("test/BUILD"))
-                 .setLabel("//test:test")
-                 .setKind("java_test")
-                 .addDependency("//test:lib"))
-      .build();
-
-    TestMap testMap = new TestMap(project, ruleMap);
-    ImmutableMultimap<Label, Label> reverseDependencies = ReverseDependencyMap.createRdepsMap(ruleMap);
-    assertThat(testMap.testTargetsForSourceFile(reverseDependencies, new File("/test/Test.java"), null))
-      .containsExactly(new Label("//test:test"), new Label("//test:test2"))
-      .inOrder();
-  }
-
-  @Test
-  public void testTestSizeFilter() throws Exception {
-    ImmutableMap<Label, RuleIdeInfo> ruleMap = ruleMapBuilder
-      .addRule(RuleIdeInfo.builder()
-                 .setBuildFile(sourceRoot("test/BUILD"))
-                 .setLabel("//test:lib")
-                 .setKind("java_library")
-                 .addSource(sourceRoot("test/Test.java")))
-      .addRule(RuleIdeInfo.builder()
-                 .setBuildFile(sourceRoot("test/BUILD"))
-                 .setLabel("//test:lib2")
-                 .setKind("java_library")
-                 .addDependency("//test:lib"))
-      .addRule(RuleIdeInfo.builder()
-                 .setBuildFile(sourceRoot("test/BUILD"))
-                 .setLabel("//test:test2")
-                 .setKind("java_test")
-                 .setTestInfo(TestIdeInfo.builder()
-                       .setTestSize(TestIdeInfo.TestSize.LARGE))
-                 .addDependency("//test:lib2"))
-      .addRule(RuleIdeInfo.builder()
-                 .setBuildFile(sourceRoot("test/BUILD"))
-                 .setLabel("//test:test")
-                 .setKind("java_test")
-                 .setTestInfo(TestIdeInfo.builder())
-                 .addDependency("//test:lib"))
-      .build();
-
-    TestMap testMap = new TestMap(project, ruleMap);
-    ImmutableMultimap<Label, Label> reverseDependencies = ReverseDependencyMap.createRdepsMap(ruleMap);
-    assertThat(testMap.testTargetsForSourceFile(reverseDependencies, new File("/test/Test.java"), TestIdeInfo.TestSize.LARGE))
-      .containsExactly(new Label("//test:test2"))
-      .inOrder();
-  }
-
-  @Test
-  public void testSourceIncludedMultipleTimesFindsAll() throws Exception {
-    ImmutableMap<Label, RuleIdeInfo> ruleMap = ruleMapBuilder
-      .addRule(RuleIdeInfo.builder()
-                 .setBuildFile(sourceRoot("test/BUILD"))
-                 .setLabel("//test:test")
-                 .setKind("java_test")
-                 .addDependency("//test:lib"))
-      .addRule(RuleIdeInfo.builder()
-                 .setBuildFile(sourceRoot("test/BUILD"))
-                 .setLabel("//test:test2")
-                 .setKind("java_test")
-                 .addDependency("//test:lib2"))
-      .addRule(RuleIdeInfo.builder()
-                 .setBuildFile(sourceRoot("test/BUILD"))
-                 .setLabel("//test:lib")
-                 .setKind("java_library")
-                 .addSource(sourceRoot("test/Test.java")))
-      .addRule(RuleIdeInfo.builder()
-                 .setBuildFile(sourceRoot("test/BUILD"))
-                 .setLabel("//test:lib2")
-                 .setKind("java_library")
-                 .addSource(sourceRoot("test/Test.java")))
-      .build();
-
-    TestMap testMap = new TestMap(project, ruleMap);
-    ImmutableMultimap<Label, Label> reverseDependencies = ReverseDependencyMap.createRdepsMap(ruleMap);
-    assertThat(testMap.testTargetsForSourceFile(reverseDependencies, new File("/test/Test.java"), null))
-      .containsExactly(new Label("//test:test"), new Label("//test:test2"));
-  }
-
-  @Test
-  public void testSourceIncludedMultipleTimesShouldOnlyGiveOneInstanceOfTest() throws Exception {
-    ImmutableMap<Label, RuleIdeInfo> ruleMap = ruleMapBuilder
-      .addRule(RuleIdeInfo.builder()
-                 .setBuildFile(sourceRoot("test/BUILD"))
-                 .setLabel("//test:test")
-                 .setKind("java_test")
-                 .addDependency("//test:lib")
-                 .addDependency("//test:lib2"))
-      .addRule(RuleIdeInfo.builder()
-                 .setBuildFile(sourceRoot("test/BUILD"))
-                 .setLabel("//test:lib")
-                 .setKind("java_library")
-                 .addSource(sourceRoot("test/Test.java")))
-      .addRule(RuleIdeInfo.builder()
-                 .setBuildFile(sourceRoot("test/BUILD"))
-                 .setLabel("//test:lib2")
-                 .setKind("java_library")
-                 .addSource(sourceRoot("test/Test.java")))
-      .build();
-
-    TestMap testMap = new TestMap(project, ruleMap);
-    ImmutableMultimap<Label, Label> reverseDependencies = ReverseDependencyMap.createRdepsMap(ruleMap);
-    assertThat(testMap.testTargetsForSourceFile(reverseDependencies, new File("/test/Test.java"), null))
-      .containsExactly(new Label("//test:test"));
-  }
-
-  private ArtifactLocation sourceRoot(String relativePath) {
-    return ArtifactLocation.builder()
-      .setRootPath("/")
-      .setRelativePath(relativePath)
-      .setIsSource(true)
-      .build();
-  }
-}
diff --git a/blaze-base/tests/unittests/com/google/idea/blaze/base/scope/BlazeContextTest.java b/blaze-base/tests/unittests/com/google/idea/blaze/base/scope/BlazeContextTest.java
deleted file mode 100644
index 0b0f2a5..0000000
--- a/blaze-base/tests/unittests/com/google/idea/blaze/base/scope/BlazeContextTest.java
+++ /dev/null
@@ -1,390 +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.scope;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.BlazeTestCase;
-import org.jetbrains.annotations.NotNull;
-import org.junit.Test;
-
-import java.util.List;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-/**
- * Tests for {@link BlazeContext}.
- */
-public class BlazeContextTest extends BlazeTestCase {
-
-  @Test
-  public void testScopeBeginsWhenPushedToContext() {
-    BlazeContext context = new BlazeContext();
-    final BlazeScope scope = mock(BlazeScope.class);
-    context.push(scope);
-    verify(scope).onScopeBegin(context);
-  }
-
-  @Test
-  public void testScopeEndsWhenContextEnds() {
-    BlazeContext context = new BlazeContext();
-    final BlazeScope scope = mock(BlazeScope.class);
-    context.push(scope);
-    context.endScope();
-    verify(scope).onScopeEnd(context);
-  }
-
-  @Test
-  public void testEndingTwiceHasNoEffect() {
-    BlazeContext context = new BlazeContext();
-    final BlazeScope scope = mock(BlazeScope.class);
-    context.push(scope);
-    context.endScope();
-    context.endScope();
-    verify(scope).onScopeEnd(context);
-  }
-
-  @Test
-  public void testEndingScopeNormallyDoesntEndParent() {
-    BlazeContext parentContext = new BlazeContext();
-    BlazeContext childContext = new BlazeContext(parentContext);
-    childContext.endScope();
-    assertTrue(childContext.isEnding());
-    assertFalse(parentContext.isEnding());
-  }
-
-  @Test
-  public void testCancellingScopeCancelsParent() {
-    BlazeContext parentContext = new BlazeContext();
-    BlazeContext childContext = new BlazeContext(parentContext);
-    childContext.setCancelled();
-    assertTrue(childContext.isCancelled());
-    assertTrue(parentContext.isCancelled());
-  }
-
-  /**
-   * A simple scope that records its start and end by ID.
-   */
-  static class RecordScope implements BlazeScope {
-    private final int id;
-    private final List<String> record;
-
-    public RecordScope(int id, List<String> record) {
-      this.id = id;
-      this.record = record;
-    }
-
-    @Override
-    public void onScopeBegin(@NotNull BlazeContext context) {
-      record.add("begin" + id);
-    }
-
-    @Override
-    public void onScopeEnd(@NotNull BlazeContext context) {
-      record.add("end" + id);
-    }
-  }
-
-  @Test
-  public void testScopesBeginAndEndInStackOrder() {
-    List<String> record = Lists.newArrayList();
-    BlazeContext context = new BlazeContext();
-    context
-      .push(new RecordScope(1, record))
-      .push(new RecordScope(2, record))
-      .push(new RecordScope(3, record));
-    context.endScope();
-    assertThat(record)
-      .isEqualTo(ImmutableList.of("begin1", "begin2", "begin3", "end3", "end2", "end1"));
-  }
-
-  @Test
-  public void testParentFoundInStackOrder() {
-    BlazeContext context = new BlazeContext();
-    BlazeScope scope1 = mock(BlazeScope.class);
-    BlazeScope scope2 = mock(BlazeScope.class);
-    BlazeScope scope3 = mock(BlazeScope.class);
-    context
-      .push(scope1)
-      .push(scope2)
-      .push(scope3);
-    assertThat(context.getParentScope(scope3)).isEqualTo(scope2);
-    assertThat(context.getParentScope(scope2)).isEqualTo(scope1);
-    assertThat(context.getParentScope(scope1)).isNull();
-  }
-
-  @Test
-  public void testParentFoundInStackOrderAcrossContexts() {
-    BlazeContext parentContext = new BlazeContext();
-    BlazeContext childContext = new BlazeContext(parentContext);
-    BlazeScope scope1 = mock(BlazeScope.class);
-    BlazeScope scope2 = mock(BlazeScope.class);
-    BlazeScope scope3 = mock(BlazeScope.class);
-    parentContext
-      .push(scope1)
-      .push(scope2);
-    childContext
-      .push(scope3);
-    assertThat(childContext.getParentScope(scope3)).isEqualTo(scope2);
-  }
-
-  static class TestOutput1 implements Output {
-  }
-
-  static class TestOutput2 implements Output {
-  }
-
-  static class TestOutputSink<T extends Output> implements OutputSink<T> {
-    public boolean gotOutput;
-
-    @Override
-    public Propagation onOutput(@NotNull T output) {
-      gotOutput = true;
-      return Propagation.Continue;
-    }
-  }
-
-  static class TestOutputSink1 extends TestOutputSink<TestOutput1> {
-  }
-
-  static class TestOutputSink2 extends TestOutputSink<TestOutput2> {
-  }
-
-  @Test
-  public void testOutputGoesToRegisteredSink() {
-    BlazeContext context = new BlazeContext();
-    TestOutputSink1 sink = new TestOutputSink1();
-    context.addOutputSink(TestOutput1.class, sink);
-
-    assertFalse(sink.gotOutput);
-    context.output(new TestOutput1());
-    assertTrue(sink.gotOutput);
-  }
-
-  @Test
-  public void testOutputDoesntGoToWrongSink() {
-    BlazeContext context = new BlazeContext();
-    TestOutputSink2 sink = new TestOutputSink2();
-    context.addOutputSink(TestOutput2.class, sink);
-
-    assertFalse(sink.gotOutput);
-    context.output(new TestOutput1());
-    assertFalse(sink.gotOutput);
-  }
-
-  @Test
-  public void testOutputGoesToParentContexts() {
-    BlazeContext parentContext = new BlazeContext();
-    BlazeContext childContext = new BlazeContext(parentContext);
-    TestOutputSink1 sink = new TestOutputSink1();
-    parentContext.addOutputSink(TestOutput1.class, sink);
-
-    assertFalse(sink.gotOutput);
-    childContext.output(new TestOutput1());
-    assertTrue(sink.gotOutput);
-  }
-
-  @Test
-  public void testHoldingPreventsEndingContext() {
-    BlazeContext context = new BlazeContext();
-    context.hold();
-    context.endScope();
-    assertFalse(context.isEnding());
-    context.release();
-    assertTrue(context.isEnding());
-  }
-
-  private static class StringScope implements BlazeScope {
-
-    public final String str;
-
-    public StringScope(String s) {
-      this.str = s;
-    }
-
-    @Override
-    public void onScopeBegin(@NotNull BlazeContext context) {
-
-    }
-
-    @Override
-    public void onScopeEnd(@NotNull BlazeContext context) {
-
-    }
-  }
-
-  private static class CollectorScope implements BlazeScope {
-
-    public final List<String> output;
-
-    public CollectorScope(List<String> output) {
-      this.output = output;
-    }
-
-    @Override
-    public void onScopeBegin(@NotNull BlazeContext context) {
-
-    }
-
-    @Override
-    public void onScopeEnd(@NotNull BlazeContext context) {
-      List<StringScope> scopes = context.getScopes(StringScope.class, this);
-      for (StringScope scope : scopes) {
-        output.add(scope.str);
-      }
-    }
-  }
-
-  @Test
-  public void testGetScopesOnlyReturnsScopesLowerOnTheStack() {
-    List<String> output1 = Lists.newArrayList();
-    List<String> output2 = Lists.newArrayList();
-    List<String> output3 = Lists.newArrayList();
-
-    BlazeContext context = new BlazeContext();
-    context.push(new StringScope("a"));
-    context.push(new StringScope("b"));
-    CollectorScope scope = new CollectorScope(output1);
-    context.push(scope);
-    context.push(new StringScope("c"));
-    context.push(new CollectorScope(output2));
-    context.push(new StringScope("d"));
-    context.push(new StringScope("e"));
-    context.push(new CollectorScope(output3));
-    context.endScope();
-
-    assertThat(output1).isEqualTo(ImmutableList.of("b", "a"));
-    assertThat(output2).isEqualTo(ImmutableList.of("c", "b", "a"));
-    assertThat(output3).isEqualTo(ImmutableList.of("e", "d", "c", "b", "a"));
-  }
-
-  @Test
-  public void testGetScopesOnlyReturnsScopesLowerOnTheStackForMultipleContexts() {
-    List<String> output1 = Lists.newArrayList();
-    List<String> output2 = Lists.newArrayList();
-    List<String> output3 = Lists.newArrayList();
-
-    BlazeContext context1 = new BlazeContext();
-    context1.push(new StringScope("a"));
-    context1.push(new StringScope("b"));
-    CollectorScope scope = new CollectorScope(output1);
-    context1.push(scope);
-
-    BlazeContext context2 = new BlazeContext(context1);
-    context2.push(new StringScope("c"));
-    context2.push(new CollectorScope(output2));
-    context2.push(new StringScope("d"));
-    context2.push(new StringScope("e"));
-
-    BlazeContext context3 = new BlazeContext(context2);
-    context3.push(new CollectorScope(output3));
-    context3.endScope();
-    context2.endScope();
-    context1.endScope();
-
-    assertThat(output1).isEqualTo(ImmutableList.of("b", "a"));
-    assertThat(output2).isEqualTo(ImmutableList.of("c", "b", "a"));
-    assertThat(output3).isEqualTo(ImmutableList.of("e", "d", "c", "b", "a"));
-  }
-
-  @Test
-  public void testGetScopesOnlyReturnsScopesIfStartingScopeInContext() {
-    List<String> output1 = Lists.newArrayList();
-
-    BlazeContext context1 = new BlazeContext();
-    context1.push(new StringScope("a"));
-    context1.push(new StringScope("b"));
-    CollectorScope scope = new CollectorScope(output1);
-    context1.push(scope);
-
-    BlazeContext context2 = new BlazeContext(context1);
-    context2.push(new StringScope("c"));
-
-    List<StringScope> scopes = context2.getScopes(StringScope.class, scope);
-    assertThat(scopes).isEqualTo(ImmutableList.of());
-  }
-
-  @Test
-  public void testGetScopesIncludesStartingScope() {
-    BlazeContext context1 = new BlazeContext();
-    StringScope a = new StringScope("a");
-    context1.push(a);
-    StringScope b = new StringScope("b");
-    context1.push(b);
-
-    List<StringScope> scopes = context1.getScopes(StringScope.class, b);
-    assertThat(scopes).isEqualTo(ImmutableList.of(b, a));
-  }
-
-  @Test
-  public void testGetScopesIndexIsNoninclusive() {
-    BlazeContext context1 = new BlazeContext();
-    StringScope scopeA = new StringScope("a");
-    context1.push(scopeA);
-    StringScope scopeB = new StringScope("b");
-    context1.push(scopeB);
-
-    List<StringScope> scopes = Lists.newArrayList();
-    context1.getScopes(scopes, StringScope.class, 1);
-    assertThat(scopes).isEqualTo(ImmutableList.of(scopeA));
-  }
-
-  @Test
-  public void testGetScopesWithoutStartScopeGetsAll() {
-    BlazeContext context1 = new BlazeContext();
-    StringScope a = new StringScope("a");
-    context1.push(a);
-    StringScope b = new StringScope("b");
-    context1.push(b);
-
-    List<StringScope> scopes = context1.getScopes(StringScope.class);
-    assertThat(scopes).isEqualTo(ImmutableList.of(b, a));
-  }
-
-  static class NonPropagatingOutputSink implements OutputSink<TestOutput1> {
-    boolean gotOutput;
-
-    @Override
-    public Propagation onOutput(@NotNull TestOutput1 output) {
-      this.gotOutput = true;
-      return Propagation.Stop;
-    }
-  }
-
-  @Test
-  public void testOutputIsTerminatedByFirstSink() {
-    NonPropagatingOutputSink sink1 = new NonPropagatingOutputSink();
-    NonPropagatingOutputSink sink2 = new NonPropagatingOutputSink();
-    NonPropagatingOutputSink sink3 = new NonPropagatingOutputSink();
-
-    BlazeContext context1 = new BlazeContext();
-    context1.addOutputSink(TestOutput1.class, sink1);
-
-    BlazeContext context2 = new BlazeContext(context1);
-    context2.addOutputSink(TestOutput1.class, sink2);
-    context2.addOutputSink(TestOutput1.class, sink3);
-
-    context2.output(new TestOutput1());
-
-    assertThat(sink1.gotOutput).isFalse();
-    assertThat(sink2.gotOutput).isFalse();
-    assertThat(sink3.gotOutput).isTrue();
-  }
-}
diff --git a/blaze-base/tests/unittests/com/google/idea/blaze/base/scope/ScopeTest.java b/blaze-base/tests/unittests/com/google/idea/blaze/base/scope/ScopeTest.java
deleted file mode 100644
index e9db247..0000000
--- a/blaze-base/tests/unittests/com/google/idea/blaze/base/scope/ScopeTest.java
+++ /dev/null
@@ -1,112 +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.scope;
-
-import com.google.idea.blaze.base.BlazeTestCase;
-import org.jetbrains.annotations.NotNull;
-import org.junit.Test;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-/**
- * Tests for {@link Scope}.
- */
-public class ScopeTest extends BlazeTestCase {
-
-  @Test
-  public void testScopedOperationRuns() {
-    final boolean[] ran = new boolean[1];
-    Scope.root(new ScopedOperation() {
-      @Override
-      public void execute(@NotNull BlazeContext context) {
-        ran[0] = true;
-      }
-    });
-    assertTrue(ran[0]);
-  }
-
-  @Test
-  public void testScopedFunctionReturnsValue() {
-    String result = Scope.root(new ScopedFunction<String>() {
-      @Override
-      public String execute(@NotNull BlazeContext context) {
-        return "test";
-      }
-    });
-    assertThat(result).isEqualTo("test");
-  }
-
-  @Test
-  public void testScopedOperationEndsContext() {
-    final BlazeScope scope = mock(BlazeScope.class);
-    Scope.root(new ScopedOperation() {
-      @Override
-      public void execute(@NotNull BlazeContext context) {
-        context.push(scope);
-      }
-    });
-    verify(scope).onScopeEnd(any(BlazeContext.class));
-  }
-
-  @Test
-  public void testScopedFunctionEndsContext() {
-    final BlazeScope scope = mock(BlazeScope.class);
-    Scope.root(new ScopedFunction<String>() {
-      @Override
-      public String execute(@NotNull BlazeContext context) {
-        context.push(scope);
-        return "";
-      }
-    });
-    verify(scope).onScopeEnd(any(BlazeContext.class));
-  }
-
-  /*
-  @Test
-  public void testThrowingExceptionEndsScopedOperationWithFailure() {
-    final RuntimeException e = new RuntimeException();
-    final BlazeScope scope = mock(BlazeScope.class);
-    Throwable throwable = Scope.root(project, new ScopedOperation() {
-      @Override
-      public void execute(@NotNull BlazeContext context) {
-        context.push(scope);
-        throw e;
-      }
-    }).throwable;
-    verify(scope).onScopeEnd(any(BlazeContext.class));
-    assertThat(e).isEqualTo(throwable);
-  }
-
-  @Test
-  public void testThrowingExceptionEndsScopeFunctionWithFailure() {
-    final RuntimeException e = new RuntimeException();
-    final BlazeScope scope = mock(BlazeScope.class);
-    Throwable throwable = Scope.root(project, new ScopedFunction<String>() {
-      @Override
-      public String execute(@NotNull BlazeContext context) {
-        context.push(scope);
-        throw e;
-      }
-    }).throwable;
-    verify(scope).onScopeEnd(any(BlazeContext.class));
-    assertThat(e).isEqualTo(throwable);
-  }
-  */
-}
diff --git a/blaze-base/tests/unittests/com/google/idea/blaze/base/sync/LanguageSupportTest.java b/blaze-base/tests/unittests/com/google/idea/blaze/base/sync/LanguageSupportTest.java
deleted file mode 100644
index 7318c00..0000000
--- a/blaze-base/tests/unittests/com/google/idea/blaze/base/sync/LanguageSupportTest.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;
-
-import com.google.common.collect.ImmutableSet;
-import com.google.idea.blaze.base.BlazeTestCase;
-import com.google.idea.blaze.base.sync.projectview.LanguageSupport;
-import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
-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.ScalarSection;
-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.intellij.openapi.extensions.impl.ExtensionPointImpl;
-import org.jetbrains.annotations.NotNull;
-import org.junit.Test;
-
-import java.util.Set;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Test cases for {@link LanguageSupport}
- */
-public class LanguageSupportTest extends BlazeTestCase {
-  private ErrorCollector errorCollector = new ErrorCollector();
-  private BlazeContext context;
-  private ExtensionPointImpl<BlazeSyncPlugin> syncPlugins;
-
-  @Override
-  protected void initTest(@NotNull Container applicationServices, @NotNull Container projectServices) {
-    super.initTest(applicationServices, projectServices);
-
-    syncPlugins = registerExtensionPoint(BlazeSyncPlugin.EP_NAME, BlazeSyncPlugin.class);
-
-    context = new BlazeContext();
-    context.addOutputSink(IssueOutput.class, errorCollector);
-  }
-
-  @Test
-  public void testSimpleCase() {
-    syncPlugins.registerExtension(new BlazeSyncPlugin.Adapter() {
-      @Override
-      public Set<LanguageClass> getSupportedLanguagesInWorkspace(WorkspaceType workspaceType) {
-        return ImmutableSet.of(LanguageClass.C);
-      }
-    });
-
-    ProjectViewSet projectViewSet = ProjectViewSet.builder()
-      .add(ProjectView.builder()
-             .put(ScalarSection.builder(WorkspaceTypeSection.KEY)
-                    .set(WorkspaceType.C))
-             .build())
-      .build();
-    WorkspaceLanguageSettings workspaceLanguageSettings = LanguageSupport.createWorkspaceLanguageSettings(context, projectViewSet);
-    errorCollector.assertNoIssues();
-    assertThat(workspaceLanguageSettings).isEqualTo(
-      new WorkspaceLanguageSettings(WorkspaceType.C, ImmutableSet.of(LanguageClass.C))
-    );
-  }
-
-  @Test
-  public void testFailWithUnsupportedLanguage() {
-    ProjectViewSet projectViewSet = ProjectViewSet.builder()
-      .add(ProjectView.builder()
-             .put(ScalarSection.builder(WorkspaceTypeSection.KEY)
-                    .set(WorkspaceType.C))
-             .build())
-      .build();
-    LanguageSupport.createWorkspaceLanguageSettings(context, projectViewSet);
-    errorCollector.assertIssues(
-      "Language 'c' is not supported for this plugin with workspace type: 'c'");
-  }
-
-  /**
-   * Tests that we ask for java and android when the workspace type is android.
-   */
-  @Test
-  public void testWorkspaceTypeImpliesLanguages() {
-    syncPlugins.registerExtension(new BlazeSyncPlugin.Adapter() {
-      @Override
-      public Set<LanguageClass> getSupportedLanguagesInWorkspace(WorkspaceType workspaceType) {
-        return ImmutableSet.of(LanguageClass.ANDROID, LanguageClass.JAVA, LanguageClass.C);
-      }
-    });
-
-    ProjectViewSet projectViewSet = ProjectViewSet.builder()
-      .add(ProjectView.builder()
-             .put(ScalarSection.builder(WorkspaceTypeSection.KEY)
-                    .set(WorkspaceType.ANDROID))
-             .build())
-      .build();
-    WorkspaceLanguageSettings workspaceLanguageSettings = LanguageSupport.createWorkspaceLanguageSettings(context, projectViewSet);
-    assertThat(workspaceLanguageSettings).isEqualTo(
-      new WorkspaceLanguageSettings(WorkspaceType.ANDROID, ImmutableSet.of(LanguageClass.JAVA, LanguageClass.ANDROID))
-    );
-  }
-}
diff --git a/blaze-base/tests/unittests/com/google/idea/blaze/base/sync/aspects/BlazeIdeInterfaceAspectsImplTest.java b/blaze-base/tests/unittests/com/google/idea/blaze/base/sync/aspects/BlazeIdeInterfaceAspectsImplTest.java
deleted file mode 100644
index 0f9b711..0000000
--- a/blaze-base/tests/unittests/com/google/idea/blaze/base/sync/aspects/BlazeIdeInterfaceAspectsImplTest.java
+++ /dev/null
@@ -1,113 +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.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-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.experiments.ExperimentService;
-import com.google.idea.blaze.base.experiments.MockExperimentService;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.io.FileAttributeProvider;
-import com.google.idea.blaze.base.model.primitives.*;
-import com.google.idea.blaze.base.sync.filediff.FileDiffService;
-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;
-import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolverImpl;
-import com.google.repackaged.devtools.build.lib.ideinfo.androidstudio.AndroidStudioIdeInfo;
-import org.jetbrains.annotations.NotNull;
-import org.junit.Test;
-
-import java.io.File;
-
-/**
- * Tests for {@link BlazeIdeInterfaceAspectsImpl}.
- */
-public class BlazeIdeInterfaceAspectsImplTest extends BlazeTestCase {
-
-  private static final File DUMMY_ROOT = new File("/");
-  private static final WorkspaceRoot WORKSPACE_ROOT = new WorkspaceRoot(DUMMY_ROOT);
-  private static final BlazeRoots BLAZE_ROOTS = new BlazeRoots(
-    DUMMY_ROOT,
-    ImmutableList.of(DUMMY_ROOT),
-    new ExecutionRootPath("out/crosstool/bin"),
-    new ExecutionRootPath("out/crosstool/gen")
-  );
-  private static final ArtifactLocationDecoder DUMMY_DECODER = new ArtifactLocationDecoder(
-    BLAZE_ROOTS,
-    new WorkspacePathResolverImpl(WORKSPACE_ROOT, BLAZE_ROOTS)
-  );
-
-  @Override
-  protected void initTest(@NotNull Container applicationServices,
-                          @NotNull Container projectServices) {
-    super.initTest(applicationServices, projectServices);
-    applicationServices.register(ExperimentService.class, new MockExperimentService());
-    applicationServices.register(FileAttributeProvider.class, new FileAttributeProvider());
-  }
-
-  @Test
-  public void testRuleIdeInfoIsSerializable() {
-    AndroidStudioIdeInfo.RuleIdeInfo ideProto = AndroidStudioIdeInfo.RuleIdeInfo.newBuilder()
-      .setLabel("//test:test")
-      .setBuildFile("build")
-      .setKindString("android_binary")
-      .addDependencies("//test:dep")
-      .addTags("tag")
-      .setJavaRuleIdeInfo(AndroidStudioIdeInfo.JavaRuleIdeInfo.newBuilder()
-                            .addJars(AndroidStudioIdeInfo.LibraryArtifact.newBuilder()
-                                       .setJar(artifactLocation("jar.jar")).build())
-                            .addGeneratedJars(AndroidStudioIdeInfo.LibraryArtifact.newBuilder()
-                                                .setJar(artifactLocation("jar.jar")).build())
-                            .addSources(artifactLocation("source.java")))
-      .setAndroidRuleIdeInfo(AndroidStudioIdeInfo.AndroidRuleIdeInfo.newBuilder()
-                             .addResources(artifactLocation("res"))
-                             .setApk(artifactLocation("apk"))
-                             .addDependencyApk(artifactLocation("apk"))
-                             .setJavaPackage("package"))
-      .build();
-
-    WorkspaceLanguageSettings workspaceLanguageSettings = new WorkspaceLanguageSettings(WorkspaceType.ANDROID,
-                                                                                        ImmutableSet.of(LanguageClass.ANDROID));
-    RuleIdeInfo ruleIdeInfo = IdeInfoFromProtobuf.makeRuleIdeInfo(workspaceLanguageSettings, DUMMY_DECODER, ideProto);
-    TestUtils.assertIsSerializable(ruleIdeInfo);
-  }
-
-  @Test
-  public void testBlazeStateIsSerializable() {
-    BlazeIdeInterfaceAspectsImpl.State state = new BlazeIdeInterfaceAspectsImpl.State();
-    state.fileToLabel = ImmutableMap.of(new File("fileName"), new Label("//java/com/test:test"));
-    state.fileState = new FileDiffService.State();
-    state.androidPlatformDirectory = new File("");
-    state.androidPlatformDirectory  = new File("dir");
-    state.ruleMap = 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().setRootPath(rootPath).setRelativePath(relativePath).build();
-  }
-
-}
diff --git a/blaze-base/tests/unittests/com/google/idea/blaze/base/sync/aspects/UnfilteredCompilerOptionsTest.java b/blaze-base/tests/unittests/com/google/idea/blaze/base/sync/aspects/UnfilteredCompilerOptionsTest.java
deleted file mode 100644
index e95f0eb..0000000
--- a/blaze-base/tests/unittests/com/google/idea/blaze/base/sync/aspects/UnfilteredCompilerOptionsTest.java
+++ /dev/null
@@ -1,80 +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.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.BlazeTestCase;
-import org.junit.Test;
-
-import java.util.List;
-
-import static com.google.common.truth.Truth.assertThat;
-
-public class UnfilteredCompilerOptionsTest extends BlazeTestCase {
-  @Test
-  public void testUnfilteredOptionsParsingForISystemOptions() {
-    ImmutableList<String> unfilteredOptions = ImmutableList.of(
-      "-isystem",
-      "sys/inc1",
-      "-VER2",
-      "-isystem",
-      "sys2/inc1",
-      "-isystem",
-      "sys3/inc1",
-      "-isystm",
-      "sys4/inc1"
-    );
-    List<String> sysIncludes = Lists.newArrayList();
-    List<String> flags = Lists.newArrayList();
-    UnfilteredCompilerOptions.splitUnfilteredCompilerOptions(unfilteredOptions, sysIncludes, flags);
-
-    assertThat(sysIncludes).containsExactly(
-      "sys/inc1",
-      "sys2/inc1",
-      "sys3/inc1"
-    );
-
-    assertThat(flags).containsExactly(
-      "-VER2",
-      "-isystm",
-      "sys4/inc1"
-    );
-  }
-
-  @Test
-  public void testUnfilteredOptionsParsingForISystemOptionsNoSpaceAfterIsystem() {
-    ImmutableList<String> unfilteredOptions = ImmutableList.of(
-      "-isystem",
-      "sys/inc1",
-      "-VER2",
-      "-isystemsys2/inc1",
-      "-isystem",
-      "sys3/inc1"
-    );
-    List<String> sysIncludes = Lists.newArrayList();
-    List<String> flags = Lists.newArrayList();
-    UnfilteredCompilerOptions.splitUnfilteredCompilerOptions(unfilteredOptions, sysIncludes, flags);
-
-    assertThat(sysIncludes).containsExactly(
-      "sys/inc1",
-      "sys2/inc1",
-      "sys3/inc1"
-    );
-
-    assertThat(flags).containsExactly("-VER2");
-  }
-}
diff --git a/blaze-base/tests/unittests/com/google/idea/blaze/base/sync/filediff/FileDiffServiceTest.java b/blaze-base/tests/unittests/com/google/idea/blaze/base/sync/filediff/FileDiffServiceTest.java
deleted file mode 100644
index b5d107e..0000000
--- a/blaze-base/tests/unittests/com/google/idea/blaze/base/sync/filediff/FileDiffServiceTest.java
+++ /dev/null
@@ -1,109 +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.filediff;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Lists;
-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.experiments.ExperimentService;
-import com.google.idea.blaze.base.experiments.MockExperimentService;
-import com.google.idea.blaze.base.io.FileAttributeProvider;
-import org.jetbrains.annotations.NotNull;
-import org.junit.Test;
-
-import java.io.File;
-import java.util.List;
-import java.util.Map;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Tests for FileDiffService
- */
-public class FileDiffServiceTest extends BlazeTestCase {
-  private MockFileAttributeProvider fileModificationProvider;
-  private FileDiffService fileDiffService;
-
-  private static class MockFileAttributeProvider extends FileAttributeProvider {
-    List<Long> times = Lists.newArrayList();
-    int index;
-
-    public MockFileAttributeProvider add(long time) {
-      times.add(time);
-      return this;
-    }
-
-    @Override
-    public long getFileModifiedTime(@NotNull File file) {
-      return times.get(index++);
-    }
-  }
-
-  @Override
-  protected void initTest(@NotNull Container applicationServices,
-                          @NotNull Container projectServices) {
-    super.initTest(applicationServices, projectServices);
-    applicationServices.register(ExperimentService.class, new MockExperimentService());
-    applicationServices.register(BlazeExecutor.class, new MockBlazeExecutor());
-
-    this.fileModificationProvider = new MockFileAttributeProvider();
-    applicationServices.register(FileAttributeProvider.class, fileModificationProvider);
-    this.fileDiffService = new FileDiffService();
-  }
-
-  @Test
-  public void testDiffWithDiffMethodTimestamp() throws Exception {
-    Map<File, FileDiffService.FileEntry> oldFiles = fileMap(
-      fileEntry("file1", 13),
-      fileEntry("file2", 17),
-      fileEntry("file3", 21)
-    );
-    FileDiffService.State oldState = new FileDiffService.State();
-    oldState.fileEntryMap = oldFiles;
-    List<File> fileList = ImmutableList.of(new File("file1"), new File("file2"));
-    fileModificationProvider.add(13).add(122);
-
-    List<File> newFiles = Lists.newArrayList();
-    List<File> removedFiles = Lists.newArrayList();
-    fileDiffService.updateFiles(
-      oldState,
-      fileList,
-      newFiles,
-      removedFiles
-    );
-
-    assertThat(newFiles).containsExactly(new File("file2"));
-    assertThat(removedFiles).containsExactly(new File("file3"));
-  }
-
-  static Map<File, FileDiffService.FileEntry> fileMap(FileDiffService.FileEntry... fileEntries) {
-    ImmutableMap.Builder<File, FileDiffService.FileEntry> builder = ImmutableMap.builder();
-    for (FileDiffService.FileEntry fileEntry : fileEntries) {
-      builder.put(fileEntry.file, fileEntry);
-    }
-    return builder.build();
-  }
-
-  static FileDiffService.FileEntry fileEntry(@NotNull String filePath, long timestamp) {
-    FileDiffService.FileEntry fileEntry = new FileDiffService.FileEntry();
-    fileEntry.file = new File(filePath);
-    fileEntry.timestamp = timestamp;
-    return fileEntry;
-  }
-}
diff --git a/blaze-base/tests/unittests/com/google/idea/blaze/base/sync/workspace/ArtifactLocationDecoderTest.java b/blaze-base/tests/unittests/com/google/idea/blaze/base/sync/workspace/ArtifactLocationDecoderTest.java
deleted file mode 100644
index 3440275..0000000
--- a/blaze-base/tests/unittests/com/google/idea/blaze/base/sync/workspace/ArtifactLocationDecoderTest.java
+++ /dev/null
@@ -1,286 +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.workspace;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-import com.google.idea.blaze.base.BlazeTestCase;
-import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
-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.repackaged.devtools.build.lib.ideinfo.androidstudio.AndroidStudioIdeInfo;
-import com.google.repackaged.devtools.build.lib.ideinfo.androidstudio.PackageManifestOuterClass;
-import org.jetbrains.annotations.NotNull;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.io.File;
-import java.util.List;
-import java.util.Set;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Test cases for {@link ArtifactLocationDecoder}.
- */
-@RunWith(JUnit4.class)
-public class ArtifactLocationDecoderTest extends BlazeTestCase {
-
-  private static final WorkspaceRoot WORKSPACE_ROOT = new WorkspaceRoot(new File("/path/to/root"));
-  private static final String EXECUTION_ROOT = "/path/to/_blaze_user/1234bf129e/root";
-
-  private static final BlazeRoots BLAZE_GIT5_ROOTS = new BlazeRoots(
-    new File(EXECUTION_ROOT),
-    ImmutableList.of(
-      WORKSPACE_ROOT.directory(),
-      new File(WORKSPACE_ROOT.directory().getParentFile(), "READONLY/root")
-    ),
-    new ExecutionRootPath("root/blaze-out/crosstool/bin"),
-    new ExecutionRootPath("root/blaze-out/crosstool/genfiles")
-  );
-
-  private static final BlazeRoots BLAZE_CITC_ROOTS = new BlazeRoots(
-    new File(EXECUTION_ROOT),
-    ImmutableList.of(WORKSPACE_ROOT.directory()),
-    new ExecutionRootPath("root/blaze-out/crosstool/bin"),
-    new ExecutionRootPath("root/blaze-out/crosstool/genfiles")
-  );
-
-  static class MockFileAttributeProvider extends FileAttributeProvider {
-    final Set<File> files = Sets.newHashSet();
-
-    void addFiles(@NotNull File... files) {
-      this.files.addAll(Lists.newArrayList(files));
-    }
-
-    @Override
-    public boolean exists(@NotNull File file) {
-      return files.contains(file);
-    }
-  }
-
-  private MockFileAttributeProvider fileChecker;
-
-  @Override
-  protected void initTest(
-    @NotNull Container applicationServices,
-    @NotNull Container projectServices) {
-    super.initTest(applicationServices, projectServices);
-
-    fileChecker = new MockFileAttributeProvider();
-    applicationServices.register(
-      FileAttributeProvider.class,
-      fileChecker
-    );
-  }
-
-  @Test
-  public void testManualPackagePaths() throws Exception {
-    List<File> packagePaths = ImmutableList.of(
-      WORKSPACE_ROOT.directory(),
-      new File(WORKSPACE_ROOT.directory().getParentFile(), "READONLY/root"),
-      new File(WORKSPACE_ROOT.directory().getParentFile(), "CUSTOM/root")
-    );
-
-    BlazeRoots BLAZE_ROOTS = new BlazeRoots(
-      new File(EXECUTION_ROOT),
-      packagePaths,
-      new ExecutionRootPath("root/blaze-out/crosstool/bin"),
-      new ExecutionRootPath("root/blaze-out/crosstool/genfiles")
-    );
-
-    fileChecker.addFiles(new File(packagePaths.get(0), "com/google/Bla.java"),
-                         new File(packagePaths.get(1), "com/google/Foo.java"),
-                         new File(packagePaths.get(2), "com/other/Test.java"));
-
-    ArtifactLocationDecoder decoder = new ArtifactLocationDecoder(BLAZE_ROOTS, new WorkspacePathResolverImpl(WORKSPACE_ROOT, BLAZE_ROOTS));
-
-    ArtifactLocationBuilder builder = new ArtifactLocationBuilder()
-      .setRootPath("UNUSED_DUMMY")
-      .setRelativePath("com/google/Bla.java")
-      .setIsSource(true)
-      .setVersion(Version.Current);
-
-    assertThat(decoder.decode(builder.buildIdeInfoArtifact()).getRootPath())
-      .isEqualTo(packagePaths.get(0).toString());
-
-    builder.setRelativePath("com/google/Foo.java");
-
-    assertThat(decoder.decode(builder.buildIdeInfoArtifact()).getRootPath())
-      .isEqualTo(packagePaths.get(1).toString());
-
-    builder.setRelativePath("com/other/Test.java");
-
-    assertThat(decoder.decode(builder.buildIdeInfoArtifact()).getRootPath())
-      .isEqualTo(packagePaths.get(2).toString());
-
-    builder
-      .setRootPath(WORKSPACE_ROOT.toString())
-      .setRelativePath("third_party/other/Temp.java");
-
-    assertThat(decoder.decode(builder.buildIdeInfoArtifact())).isNull();
-  }
-
-  @Test
-  public void testDerivedArtifactAllVersions() throws Exception {
-    ArtifactLocationBuilder builder = new ArtifactLocationBuilder()
-      .setRootPath(EXECUTION_ROOT + "/blaze-out/bin")
-      .setRootExecutionPathFragment("/blaze-out/bin")
-      .setRelativePath("com/google/Bla.java")
-      .setIsSource(false)
-      .setVersion(Version.Current);
-
-    ArtifactLocationDecoder decoder = new ArtifactLocationDecoder(BLAZE_CITC_ROOTS, null);
-
-    ArtifactLocation parsed = decoder.decode(builder.buildIdeInfoArtifact());
-
-    assertThat(parsed)
-      .isEqualTo(decoder.decode(builder.buildManifestArtifact()));
-
-    assertThat(parsed).isEqualTo(
-      ArtifactLocation.builder()
-        .setRootPath(EXECUTION_ROOT + "/blaze-out/bin")
-        .setRootExecutionPathFragment("/blaze-out/bin")
-        .setRelativePath("com/google/Bla.java")
-        .setIsSource(false)
-        .build());
-
-    assertThat(parsed).isEqualTo(
-      decoder.decode(
-        builder.setVersion(Version.Past).buildIdeInfoArtifact()
-      ));
-
-    ArtifactLocation future = decoder.decode(
-      builder.setVersion(Version.Future).buildIdeInfoArtifact());
-
-    assertThat(future).isEqualTo(
-      ArtifactLocation.builder()
-        .setRootPath(EXECUTION_ROOT + "/blaze-out/bin")
-        .setRootExecutionPathFragment("/blaze-out/bin")
-        .setRelativePath("com/google/Bla.java")
-        .setIsSource(false)
-        .build());
-  }
-
-  @Test
-  public void testSourceArtifactAllVersions() throws Exception {
-    ArtifactLocationBuilder builder = new ArtifactLocationBuilder()
-      .setRootPath(WORKSPACE_ROOT.toString())
-      .setRelativePath("com/google/Bla.java")
-      .setIsSource(true)
-      .setVersion(Version.Current);
-
-    ArtifactLocationDecoder decoder = new ArtifactLocationDecoder(BLAZE_CITC_ROOTS,
-                                                                  new WorkspacePathResolverImpl(WORKSPACE_ROOT, BLAZE_CITC_ROOTS));
-
-    ArtifactLocation parsed = decoder.decode(builder.buildIdeInfoArtifact());
-
-    assertThat(parsed)
-      .isEqualTo(decoder.decode(builder.buildManifestArtifact()));
-
-    assertThat(parsed).isEqualTo(
-      ArtifactLocation.builder()
-        .setRootPath(WORKSPACE_ROOT.toString())
-        .setRelativePath("com/google/Bla.java")
-        .setIsSource(true)
-        .build());
-
-    assertThat(parsed).isEqualTo(
-      decoder.decode(
-        builder.setVersion(Version.Past).buildIdeInfoArtifact()
-      ));
-
-    ArtifactLocation future = decoder.decode(
-      builder.setVersion(Version.Future).buildIdeInfoArtifact());
-
-    assertThat(future).isEqualTo(
-      ArtifactLocation.builder()
-        .setRootPath(WORKSPACE_ROOT.toString())
-        .setRelativePath("com/google/Bla.java")
-        .setIsSource(true)
-        .build());
-  }
-
-  enum Version {
-    Past, // no rootExecutionPathFragment
-    Current, // everything
-    Future // no rootPath
-  }
-
-  static class ArtifactLocationBuilder {
-    Version version;
-    String rootPath;
-    String rootExecutionPathFragment = "";
-    String relativePath;
-    boolean isSource;
-
-
-    ArtifactLocationBuilder setVersion(Version version) {
-      this.version = version;
-      return this;
-    }
-
-    ArtifactLocationBuilder setRootPath(String rootPath) {
-      this.rootPath = rootPath;
-      return this;
-    }
-
-    ArtifactLocationBuilder setRootExecutionPathFragment(String rootExecutionPathFragment) {
-      this.rootExecutionPathFragment = rootExecutionPathFragment;
-      return this;
-    }
-
-    ArtifactLocationBuilder setRelativePath(String relativePath) {
-      this.relativePath = relativePath;
-      return this;
-    }
-
-    ArtifactLocationBuilder setIsSource(boolean isSource) {
-      this.isSource = isSource;
-      return this;
-    }
-
-    AndroidStudioIdeInfo.ArtifactLocation buildIdeInfoArtifact() {
-      AndroidStudioIdeInfo.ArtifactLocation.Builder builder = AndroidStudioIdeInfo.ArtifactLocation.newBuilder()
-        .setIsSource(isSource)
-        .setRelativePath(relativePath);
-      if (version != Version.Past) {
-        builder.setRootExecutionPathFragment(rootExecutionPathFragment);
-      }
-      if (version != Version.Future) {
-        builder.setRootPath(rootPath);
-      }
-      return builder.build();
-    }
-
-    PackageManifestOuterClass.ArtifactLocation buildManifestArtifact() {
-      PackageManifestOuterClass.ArtifactLocation.Builder builder = PackageManifestOuterClass.ArtifactLocation.newBuilder()
-        .setIsSource(isSource)
-        .setRelativePath(relativePath);
-      if (version != Version.Past) {
-        builder.setRootExecutionPathFragment(rootExecutionPathFragment);
-      }
-      if (version != Version.Future) {
-        builder.setRootPath(rootPath);
-      }
-      return builder.build();
-    }
-  }
-
-}
diff --git a/blaze-base/tests/unittests/com/google/idea/blaze/base/sync/workspace/WorkspacePathResolverImplTest.java b/blaze-base/tests/unittests/com/google/idea/blaze/base/sync/workspace/WorkspacePathResolverImplTest.java
deleted file mode 100644
index 95b7efe..0000000
--- a/blaze-base/tests/unittests/com/google/idea/blaze/base/sync/workspace/WorkspacePathResolverImplTest.java
+++ /dev/null
@@ -1,54 +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.workspace;
-
-import com.google.common.collect.ImmutableList;
-import com.google.idea.blaze.base.BlazeTestCase;
-import com.google.idea.blaze.base.model.primitives.ExecutionRootPath;
-import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
-import org.junit.Test;
-
-import java.io.File;
-
-import static com.google.common.truth.Truth.assertThat;
-
-public class WorkspacePathResolverImplTest extends BlazeTestCase {
-  private static final WorkspaceRoot WORKSPACE_ROOT = new WorkspaceRoot(new File("/path/to/root"));
-  private static final String EXECUTION_ROOT = "/path/to/_blaze_user/1234bf129e/root";
-
-  private static final BlazeRoots BLAZE_CITC_ROOTS = new BlazeRoots(
-    new File(EXECUTION_ROOT),
-    ImmutableList.of(WORKSPACE_ROOT.directory()),
-    new ExecutionRootPath("blaze-out/crosstool/bin"),
-    new ExecutionRootPath("blaze-out/crosstool/genfiles")
-  );
-
-  @Test
-  public void testResolveToIncludeDirectories() {
-    WorkspacePathResolver workspacePathResolver = new WorkspacePathResolverImpl(WORKSPACE_ROOT, BLAZE_CITC_ROOTS);
-    ImmutableList<File> files = workspacePathResolver.resolveToIncludeDirectories(new ExecutionRootPath("tools/fast"));
-    assertThat(files).containsExactly(new File("/path/to/root/tools/fast"));
-  }
-
-  @Test
-  public void testResolveToIncludeDirectoriesForExecRootPath() {
-    WorkspacePathResolver workspacePathResolver = new WorkspacePathResolverImpl(WORKSPACE_ROOT, BLAZE_CITC_ROOTS);
-    ImmutableList<File> files = workspacePathResolver.resolveToIncludeDirectories(
-      new ExecutionRootPath("blaze-out/crosstool/bin/tools/fast")
-    );
-    assertThat(files).containsExactly(new File("/path/to/root/blaze-out/crosstool/bin/tools/fast"));
-  }
-}
diff --git a/blaze-base/tests/unittests/com/google/idea/blaze/base/vcs/git/GitStatusLineProcessorTest.java b/blaze-base/tests/unittests/com/google/idea/blaze/base/vcs/git/GitStatusLineProcessorTest.java
deleted file mode 100644
index 8bb373e..0000000
--- a/blaze-base/tests/unittests/com/google/idea/blaze/base/vcs/git/GitStatusLineProcessorTest.java
+++ /dev/null
@@ -1,82 +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.vcs.git;
-
-import com.google.common.collect.ImmutableList;
-import com.google.idea.blaze.base.model.primitives.WorkspacePath;
-import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.io.File;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Tests for {@link GitStatusLineProcessor}
- */
-@RunWith(JUnit4.class)
-public class GitStatusLineProcessorTest {
-
-  @Test
-  public void testGitStatusParser() {
-    GitStatusLineProcessor lineProcessor = new GitStatusLineProcessor(new WorkspaceRoot(new File("/usr/blah")), "/usr/blah");
-    for (String line : ImmutableList.of(
-      "D    root/README",
-      "M    root/blaze-base/src/com/google/idea/blaze/base/root/citc/CitcUtil.java",
-      "A    root/blah",
-      "A    java/com/google/Test.java",
-      "M    java/com/other/"
-    )) {
-      lineProcessor.processLine(line);
-    }
-    assertThat(lineProcessor.addedFiles).containsExactly(
-      new WorkspacePath("root/blah"),
-      new WorkspacePath("java/com/google/Test.java")
-    );
-    assertThat(lineProcessor.modifiedFiles).containsExactly(
-      new WorkspacePath("root/blaze-base/src/com/google/idea/blaze/base/root/citc/CitcUtil.java"),
-      new WorkspacePath("java/com/other")
-    );
-    assertThat(lineProcessor.deletedFiles).containsExactly(
-      new WorkspacePath("root/README")
-    );
-  }
-
-  @Test
-  public void testGitStatusParserDifferentRoots() {
-    GitStatusLineProcessor lineProcessor = new GitStatusLineProcessor(new WorkspaceRoot(new File("/usr/blah/root")), "/usr/blah");
-    for (String line : ImmutableList.of(
-      "D    root/README",
-      "M    root/blaze-base/src/com/google/idea/blaze/base/root/citc/CitcUtil.java",
-      "A    root/blah",
-      "A    java/com/google/Test.java",
-      "M    java/com/other/"
-    )) {
-      lineProcessor.processLine(line);
-    }
-    assertThat(lineProcessor.addedFiles).containsExactly(
-      new WorkspacePath("blah")
-    );
-    assertThat(lineProcessor.modifiedFiles).containsExactly(
-      new WorkspacePath("blaze-base/src/com/google/idea/blaze/base/root/citc/CitcUtil.java")
-    );
-    assertThat(lineProcessor.deletedFiles).containsExactly(
-      new WorkspacePath("README")
-    );
-  }
-}
diff --git a/blaze-base/tests/utils/integration/com/google/idea/blaze/base/BlazeIntegrationTestCase.java b/blaze-base/tests/utils/integration/com/google/idea/blaze/base/BlazeIntegrationTestCase.java
deleted file mode 100644
index 16cda1f..0000000
--- a/blaze-base/tests/utils/integration/com/google/idea/blaze/base/BlazeIntegrationTestCase.java
+++ /dev/null
@@ -1,479 +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;
-
-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.WorkspaceRoot;
-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;
-import com.google.idea.blaze.base.sync.BlazeSyncPlugin;
-import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
-import com.google.idea.blaze.base.sync.projectstructure.ModuleEditorProvider;
-
-import com.intellij.codeInsight.lookup.Lookup;
-import com.intellij.codeInsight.lookup.LookupElement;
-import com.intellij.codeInsight.lookup.LookupElementPresentation;
-import com.intellij.openapi.actionSystem.IdeActions;
-import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.application.PathManager;
-import com.intellij.openapi.command.CommandProcessor;
-import com.intellij.openapi.editor.Editor;
-import com.intellij.openapi.editor.LogicalPosition;
-import com.intellij.openapi.extensions.ExtensionPoint;
-import com.intellij.openapi.extensions.ExtensionPointName;
-import com.intellij.openapi.extensions.Extensions;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.projectRoots.ProjectJdkTable;
-import com.intellij.openapi.util.Disposer;
-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.*;
-import com.intellij.psi.impl.source.PostprocessReformattingAspect;
-import com.intellij.refactoring.move.moveClassesOrPackages.MoveDirectoryWithClassesProcessor;
-import com.intellij.testFramework.*;
-import com.intellij.testFramework.EditorTestUtil.CaretAndSelectionState;
-import com.intellij.testFramework.EditorTestUtil.CaretInfo;
-import com.intellij.testFramework.fixtures.*;
-import com.intellij.testFramework.fixtures.impl.LightTempDirTestFixtureImpl;
-import org.picocontainer.MutablePicoContainer;
-
-import javax.annotation.Nullable;
-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 static com.google.common.truth.Truth.assertThat;
-
-/**
- * Base test class for blaze integration tests.
- */
-public abstract class BlazeIntegrationTestCase extends UsefulTestCase {
-
-  private static final LightProjectDescriptor projectDescriptor = LightCodeInsightFixtureTestCase.JAVA_8;
-
-  private static boolean isRunThroughBlaze() {
-    return System.getenv("JAVA_RUNFILES") != null;
-  }
-
-  protected CodeInsightTestFixture testFixture;
-  protected WorkspaceRoot workspaceRoot;
-  private String oldPluginPathProperty;
-
-  @Override
-  protected final void setUp() throws Exception {
-    if (!isRunThroughBlaze()) {
-      // If running directly through the IDE, don't try to load plugins from the sandbox environment.
-      // Instead we'll rely on the slightly more hermetic module classpath
-      oldPluginPathProperty = System.getProperty(PathManager.PROPERTY_PLUGINS_PATH);
-      System.setProperty(PathManager.PROPERTY_PLUGINS_PATH, "/dev/null");
-    }
-
-    super.setUp();
-
-    IdeaTestFixtureFactory factory = IdeaTestFixtureFactory.getFixtureFactory();
-    TestFixtureBuilder<IdeaProjectTestFixture> fixtureBuilder = factory.createLightFixtureBuilder(projectDescriptor);
-    final IdeaProjectTestFixture fixture = fixtureBuilder.getFixture();
-    testFixture = factory.createCodeInsightFixture(fixture, createTempDirFixture());
-    testFixture.setUp();
-
-    ApplicationManager.getApplication().runWriteAction(() -> ProjectJdkTable.getInstance().addJdk(IdeaTestUtil.getMockJdk18()));
-
-    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()
-    ));
-
-    registerApplicationService(FileAttributeProvider.class, new TempFileAttributeProvider());
-    registerApplicationService(InputStreamProvider.class, file -> {
-      VirtualFile vf = findFile(file.getPath());
-      if (vf == null) {
-        throw new FileNotFoundException();
-      }
-      return vf.getInputStream();
-    });
-
-    doSetup();
-  }
-
-  /**
-   * Override to run tests with bazel specified as the project's build system.
-   */
-  protected BuildSystem buildSystem() {
-    return BuildSystem.Blaze;
-  }
-
-  protected void doSetup() throws Exception {
-  }
-
-  @Override
-  protected final void tearDown() throws Exception {
-    if (oldPluginPathProperty != null) {
-      System.setProperty(PathManager.PROPERTY_PLUGINS_PATH, oldPluginPathProperty);
-    } else {
-      System.clearProperty(PathManager.PROPERTY_PLUGINS_PATH);
-    }
-    testFixture.tearDown();
-    testFixture = null;
-    super.tearDown();
-    clearFields(this);
-    doTearDown();
-  }
-
-  protected void doTearDown() throws Exception {
-  }
-
-  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) {
-    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) {
-    CaretInfo info = new CaretInfo(new LogicalPosition(lineNumber, columnNumber), null);
-    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 void assertFileContents(String filePath, String... contentLines) {
-    assertFileContents(findFile(filePath), contentLines);
-  }
-
-  protected void assertFileContents(VirtualFile file, String... contentLines) {
-    assertFileContents(getPsiFile(file), contentLines);
-  }
-
-  protected void assertFileContents(PsiFile file, String... contentLines) {
-    String contents = Joiner.on("\n").join(contentLines);
-    assertThat(file.getText()).isEqualTo(contents);
-  }
-
-  /**
-   * 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) {
-    PsiFile psiFile = PsiManager.getInstance(getProject()).findFile(file);
-    assertThat(psiFile).isNotNull();
-    return psiFile;
-  }
-
-  /**
-   * Finds PsiDirectory, and asserts that it's not null.
-   */
-  protected PsiDirectory getPsiDirectory(VirtualFile file) {
-    PsiDirectory psiFile = PsiManager.getInstance(getProject()).findDirectory(file);
-    assertThat(psiFile).isNotNull();
-    return psiFile;
-  }
-
-  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 void mockBlazeProjectDataManager(BlazeProjectData data) {
-    BlazeProjectDataManager mockProjectDataManager = new BlazeProjectDataManager() {
-      @Nullable
-      @Override
-      public BlazeProjectData getBlazeProjectData() {
-        return data;
-      }
-      @Override
-      public BlazeSyncPlugin.ModuleEditor editModules() {
-        return ModuleEditorProvider.getInstance().getModuleEditor(
-          getProject(),
-          BlazeImportSettingsManager.getInstance(getProject()).getImportSettings()
-        );
-      }
-    };
-    registerProjectService(BlazeProjectDataManager.class, mockProjectDataManager);
-  }
-
-  protected <T> void registerApplicationService(Class<T> key, T implementation) {
-    registerComponentInstance((MutablePicoContainer) ApplicationManager.getApplication().getPicoContainer(), key, implementation);
-  }
-
-  protected <T> void registerProjectService(Class<T> key, T implementation) {
-    registerComponentInstance((MutablePicoContainer) getProject().getPicoContainer(), key, implementation);
-  }
-
-  protected <T> void registerComponentInstance(MutablePicoContainer container, Class<T> key, T implementation) {
-    Object old = container.getComponentInstance(key);
-    container.unregisterComponent(key.getName());
-    container.registerComponentInstance(key.getName(), implementation);
-    Disposer.register(getTestRootDisposable(), () -> {
-      container.unregisterComponent(key.getName());
-      if (old != null) {
-        container.registerComponentInstance(key.getName(), old);
-      }
-    });
-  }
-
-  protected <T> void registerExtension(ExtensionPointName<T> name, T instance) {
-    ExtensionPoint<T> ep = Extensions.getRootArea().getExtensionPoint(name);
-    ep.registerExtension(instance);
-    Disposer.register(getTestRootDisposable(), () -> ep.unregisterExtension(instance));
-  }
-
-  /**
-   * 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/blaze-base/tests/utils/integration/com/google/idea/blaze/base/lang/buildfile/BuildFileIntegrationTestCase.java b/blaze-base/tests/utils/integration/com/google/idea/blaze/base/lang/buildfile/BuildFileIntegrationTestCase.java
deleted file mode 100644
index 6db82a3..0000000
--- a/blaze-base/tests/utils/integration/com/google/idea/blaze/base/lang/buildfile/BuildFileIntegrationTestCase.java
+++ /dev/null
@@ -1,78 +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;
-
-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.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.sync.workspace.BlazeRoots;
-import com.google.idea.blaze.base.sync.workspace.WorkingSet;
-import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolverImpl;
-import com.intellij.lang.ASTNode;
-import com.intellij.psi.PsiFile;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * BUILD file specific integration test base
- */
-public abstract class BuildFileIntegrationTestCase extends BlazeIntegrationTestCase {
-
-  @Override
-  protected void doSetup() {
-    mockBlazeProjectDataManager(getMockBlazeProjectData());
-  }
-
-  /**
-   * 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);
-    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));
-    });
-  }
-
-  private BlazeProjectData getMockBlazeProjectData() {
-    BlazeRoots fakeRoots = new BlazeRoots(
-      null,
-      ImmutableList.of(workspaceRoot.directory()),
-      new ExecutionRootPath("out/crosstool/bin"),
-      new ExecutionRootPath("out/crosstool/gen")
-    );
-    return new BlazeProjectData(0,
-                                ImmutableMap.of(),
-                                fakeRoots,
-                                new WorkingSet(ImmutableList.of(), ImmutableList.of(), ImmutableList.of()),
-                                new WorkspacePathResolverImpl(workspaceRoot, fakeRoots),
-                                null,
-                                null,
-                                null);
-  }
-
-}
diff --git a/blaze-base/tests/utils/integration/com/google/idea/blaze/base/sync/BlazeSyncIntegrationTestCase.java b/blaze-base/tests/utils/integration/com/google/idea/blaze/base/sync/BlazeSyncIntegrationTestCase.java
deleted file mode 100644
index 1ca2255..0000000
--- a/blaze-base/tests/utils/integration/com/google/idea/blaze/base/sync/BlazeSyncIntegrationTestCase.java
+++ /dev/null
@@ -1,316 +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;
-
-import com.google.common.base.Joiner;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListeningExecutorService;
-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.RuleIdeInfo;
-import com.google.idea.blaze.base.io.WorkspaceScanner;
-import com.google.idea.blaze.base.model.SyncState;
-import com.google.idea.blaze.base.model.primitives.Label;
-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.ProjectViewManager;
-import com.google.idea.blaze.base.projectview.ProjectViewSet;
-import com.google.idea.blaze.base.projectview.parser.ProjectViewParser;
-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;
-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;
-import com.google.idea.blaze.base.sync.aspects.BlazeIdeInterface;
-import com.google.idea.blaze.base.sync.projectstructure.ModuleEditorImpl;
-import com.google.idea.blaze.base.sync.projectstructure.ModuleEditorProvider;
-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.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.vcs.BlazeVcsHandler;
-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 javax.annotation.Nullable;
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
-import java.util.Map;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * 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 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;
-  private MockBlazeIdeInterface blazeIdeInterface;
-
-  protected ErrorCollector errorCollector;
-  protected BlazeContext context;
-
-  @Override
-  protected void doSetup() throws IOException {
-    // Set up a workspace root outside of the tracked temp file system.
-    tempDirectoryHandler = new LightTempDirTestFixtureImpl();
-    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(ModuleEditorProvider.class, new ModuleEditorProvider() {
-      @Override
-      public ModuleEditorImpl getModuleEditor(Project project, BlazeImportSettings importSettings) {
-        return new ModuleEditorImpl(project, importSettings) {
-          @Override
-          public void commit() {
-            // don't commit module changes, but make sure they're properly disposed when the test is finished
-            for (ModifiableRootModel model : modifiableModels) {
-              Disposer.register(myTestRootDisposable, model::dispose);
-            }
-          }
-        };
-      }
-    });
-
-    errorCollector = new ErrorCollector();
-    context = new BlazeContext();
-    context.addOutputSink(IssueOutput.class, errorCollector);
-
-    tempDirectoryHandler.findOrCreateDir(PROJECT_DATA_DIR + "/.blaze/modules");
-
-    setBlazeInfoResults(ImmutableMap.of(
-      BlazeInfo.blazeBinKey(Blaze.getBuildSystem(getProject())), BLAZE_BIN,
-      BlazeInfo.blazeGenfilesKey(Blaze.getBuildSystem(getProject())), BLAZE_GENFILES,
-      BlazeInfo.EXECUTION_ROOT_KEY, EXECUTION_ROOT,
-      BlazeInfo.PACKAGE_PATH_KEY, workspaceRoot.toString()
-    ));
-  }
-
-  @Override
-  protected void doTearDown() throws Exception {
-    if (tempDirectoryHandler != null) {
-      tempDirectoryHandler.tearDown();
-    }
-    super.doTearDown();
-  }
-
-  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()
-      .setRootPath(workspaceRoot.toString())
-      .setRelativePath(relativePath)
-      .setIsSource(true)
-      .build();
-  }
-
-  protected void setProjectView(String... contents) {
-    ProjectViewParser projectViewParser = new ProjectViewParser(context, new WorkspacePathResolverImpl(workspaceRoot));
-    projectViewParser.parseProjectView(Joiner.on("\n").join(contents));
-
-    ProjectViewSet result = projectViewParser.getResult();
-    assertThat(result.getProjectViewFiles()).isNotEmpty();
-    assertNoErrors();
-    setProjectViewSet(result);
-  }
-
-  protected void setProjectViewSet(ProjectViewSet projectViewSet) {
-    projectViewManager.projectViewSet = projectViewSet;
-  }
-
-  protected void setRuleMap(Map<Label, RuleIdeInfo> rules) {
-    blazeIdeInterface.ruleMap.clear();
-    blazeIdeInterface.ruleMap.putAll(rules);
-  }
-
-  protected void setBlazeInfoResults(Map<String, String> blazeInfoResults) {
-    blazeInfoData.setResults(blazeInfoResults);
-  }
-
-  protected void runBlazeSync(BlazeSyncParams syncParams) {
-    Project project = getProject();
-    final BlazeSyncTask syncTask = new BlazeSyncTask(
-      project,
-      BlazeImportSettingsManager.getInstance(project).getImportSettings(),
-      syncParams);
-    syncTask.syncProject(context);
-  }
-
-  private static class MockProjectViewManager extends ProjectViewManager {
-
-    private ProjectViewSet projectViewSet;
-
-    @Nullable
-    @Override
-    public ProjectViewSet getProjectViewSet() {
-      return projectViewSet;
-    }
-
-    @Nullable
-    @Override
-    public ProjectViewSet reloadProjectView(BlazeContext context, WorkspacePathResolver workspacePathResolver) {
-      return getProjectViewSet();
-    }
-  }
-
-  private class MockBlazeVcsHandler implements BlazeVcsHandler {
-
-    private List<WorkspacePath> addedFiles = Lists.newArrayList();
-
-    @Nullable
-    @Override
-    public String getClientName(WorkspaceRoot workspaceRoot) {
-      return null;
-    }
-
-    @Override
-    public boolean handlesProject(Project project, WorkspaceRoot workspaceRoot) {
-      return project == getProject();
-    }
-
-    @Override
-    public ListenableFuture<WorkingSet> getWorkingSet(Project project, WorkspaceRoot workspaceRoot, ListeningExecutorService executor) {
-      WorkingSet workingSet = new WorkingSet(ImmutableList.copyOf(addedFiles), ImmutableList.of(), ImmutableList.of());
-      return Futures.immediateFuture(workingSet);
-    }
-
-    @Nullable
-    @Override
-    public BlazeVcsSyncHandler createSyncHandler(Project project,
-                                                 WorkspaceRoot workspaceRoot) {
-      return null;
-    }
-  }
-
-  protected static class MockBlazeInfo extends BlazeInfo {
-    private final Map<String, String> results = Maps.newHashMap();
-
-    @Override
-    public ListenableFuture<String> runBlazeInfo(@Nullable BlazeContext context,
-                                                 BuildSystem buildSystem,
-                                                 WorkspaceRoot workspaceRoot,
-                                                 List<String> blazeFlags,
-                                                 String key) {
-      return Futures.immediateFuture(results.get(key));
-    }
-
-    @Override
-    public ListenableFuture<byte[]> runBlazeInfoGetBytes(@Nullable BlazeContext context,
-                                                         BuildSystem buildSystem,
-                                                         WorkspaceRoot workspaceRoot,
-                                                         List<String> blazeFlags,
-                                                         String key) {
-      return Futures.immediateFuture(null);
-    }
-
-    @Override
-    public ListenableFuture<ImmutableMap<String, String>> runBlazeInfo(@Nullable BlazeContext context,
-                                                                       BuildSystem buildSystem,
-                                                                       WorkspaceRoot workspaceRoot,
-                                                                       List<String> blazeFlags) {
-      return Futures.immediateFuture(ImmutableMap.copyOf(results));
-    }
-
-    public void setResults(Map<String, String> results) {
-      this.results.clear();
-      this.results.putAll(results);
-    }
-  }
-
-  protected static class MockBlazeIdeInterface extends BlazeIdeInterface {
-    private final Map<Label, RuleIdeInfo> ruleMap = Maps.newHashMap();
-
-    @Nullable
-    @Override
-    public IdeResult updateBlazeIdeState(Project project,
-                                         BlazeContext context,
-                                         WorkspaceRoot workspaceRoot,
-                                         ProjectViewSet projectViewSet,
-                                         List<TargetExpression> targets,
-                                         WorkspaceLanguageSettings workspaceLanguageSettings,
-                                         ArtifactLocationDecoder artifactLocationDecoder,
-                                         SyncState.Builder syncStateBuilder,
-                                         @Nullable SyncState previousSyncState,
-                                         boolean requiresAndroidSdk) {
-      return new IdeResult(ImmutableMap.copyOf(ruleMap), null);
-    }
-
-    @Override
-    public void resolveIdeArtifacts(Project project,
-                                    BlazeContext context,
-                                    WorkspaceRoot workspaceRoot,
-                                    ProjectViewSet projectViewSet,
-                                    List<TargetExpression> targets) {
-    }
-  }
-
-}
diff --git a/blaze-base/tests/utils/unit/com/google/idea/blaze/base/BlazeTestCase.java b/blaze-base/tests/utils/unit/com/google/idea/blaze/base/BlazeTestCase.java
deleted file mode 100644
index b1a90aa..0000000
--- a/blaze-base/tests/utils/unit/com/google/idea/blaze/base/BlazeTestCase.java
+++ /dev/null
@@ -1,107 +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;
-
-import com.intellij.mock.MockProject;
-import com.intellij.openapi.Disposable;
-import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.extensions.*;
-import com.intellij.openapi.extensions.impl.ExtensionPointImpl;
-import com.intellij.openapi.extensions.impl.ExtensionsAreaImpl;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.Disposer;
-import org.jetbrains.annotations.NotNull;
-import org.junit.After;
-import org.junit.Before;
-import org.picocontainer.MutablePicoContainer;
-
-/**
- * Test base class.
- * <p/>
- * <p>Provides a mock application and a mock project.
- */
-public class BlazeTestCase {
-
-  protected Project project;
-  private ExtensionsAreaImpl extensionsArea;
-  private Disposable testDisposable;
-
-  private static class RootDisposable implements Disposable {
-    @Override
-    public void dispose() {
-    }
-  }
-
-  public static class Container {
-    private final MutablePicoContainer container;
-
-    Container(@NotNull MutablePicoContainer container) {
-      this.container = container;
-    }
-
-    public <T> Container register(Class<T> klass, T instance) {
-      this.container.registerComponentInstance(klass.getName(), instance);
-      return this;
-    }
-  }
-
-  @Before
-  public final void setup() {
-    testDisposable = new RootDisposable();
-    TestUtils.createMockApplication(testDisposable);
-    MutablePicoContainer applicationContainer = (MutablePicoContainer)
-      ApplicationManager.getApplication().getPicoContainer();
-    MockProject mockProject = TestUtils.mockProject(applicationContainer, testDisposable);
-
-    Extensions.cleanRootArea(testDisposable);
-    extensionsArea = (ExtensionsAreaImpl) Extensions.getRootArea();
-
-    this.project = mockProject;
-
-    initTest(
-      new Container(applicationContainer),
-      new Container(mockProject.getPicoContainer())
-    );
-  }
-
-  @After
-  public final void tearDown() {
-    Disposer.dispose(testDisposable);
-  }
-
-  public final Project getProject() {
-    return project;
-  }
-
-  protected void initTest(
-    @NotNull Container applicationServices,
-    @NotNull Container projectServices) {
-  }
-
-  protected <T> ExtensionPointImpl<T> registerExtensionPoint(@NotNull ExtensionPointName<T> name, @NotNull Class<T> type) {
-    ExtensionPointImpl<T> extensionPoint = new ExtensionPointImpl<T>(
-      name.getName(),
-      type.getName(),
-      ExtensionPoint.Kind.INTERFACE,
-      extensionsArea,
-      null,
-      new Extensions.SimpleLogProvider(),
-      new DefaultPluginDescriptor(PluginId.getId(type.getName()), type.getClassLoader())
-    );
-    extensionsArea.registerExtensionPoint(extensionPoint);
-    return extensionPoint;
-  }
-}
diff --git a/blaze-base/tests/utils/unit/com/google/idea/blaze/base/TestUtils.java b/blaze-base/tests/utils/unit/com/google/idea/blaze/base/TestUtils.java
deleted file mode 100644
index c04062c..0000000
--- a/blaze-base/tests/utils/unit/com/google/idea/blaze/base/TestUtils.java
+++ /dev/null
@@ -1,126 +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;
-
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.common.util.concurrent.MoreExecutors;
-import com.intellij.mock.MockApplicationEx;
-import com.intellij.mock.MockProject;
-import com.intellij.openapi.Disposable;
-import com.intellij.openapi.application.Application;
-import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.extensions.Extensions;
-import com.intellij.openapi.fileTypes.FileTypeManager;
-import com.intellij.openapi.util.Disposer;
-import com.intellij.openapi.vfs.encoding.EncodingManager;
-import com.intellij.openapi.vfs.encoding.EncodingManagerImpl;
-import com.intellij.util.pico.DefaultPicoContainer;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import org.picocontainer.PicoContainer;
-
-import java.io.*;
-import java.util.concurrent.Callable;
-import java.util.concurrent.Future;
-
-import static org.junit.Assert.fail;
-
-/**
- * Test utilities.
- */
-public class TestUtils {
-
-  static class BlazeMockApplication extends MockApplicationEx {
-    private final ListeningExecutorService executor = MoreExecutors.sameThreadExecutor();
-
-    public BlazeMockApplication(@NotNull Disposable parentDisposable) {
-      super(parentDisposable);
-    }
-
-    @NotNull
-    @Override
-    public Future<?> executeOnPooledThread(@NotNull Runnable action) {
-      return executor.submit(action);
-    }
-
-    @NotNull
-    @Override
-    public <T> Future<T> executeOnPooledThread(@NotNull Callable<T> action) {
-      return executor.submit(action);
-    }
-  }
-
-  public static void createMockApplication(Disposable parentDisposable) {
-    final BlazeMockApplication instance = new BlazeMockApplication(parentDisposable);
-
-    // If there was no previous application, ApplicationManager leaves the MockApplication in place, which can break future tests.
-    Application oldApplication = ApplicationManager.getApplication();
-    if (oldApplication == null) {
-      Disposer.register(parentDisposable, () -> {
-        new ApplicationManager() {
-          { ourApplication = null; }
-        };
-      });
-    }
-
-    ApplicationManager.setApplication(instance,
-                                      FileTypeManager::getInstance,
-                                      parentDisposable);
-    instance.registerService(EncodingManager.class, EncodingManagerImpl.class);
-  }
-
-  @NotNull
-  public static MockProject mockProject(@Nullable PicoContainer container,
-                                        Disposable parentDisposable) {
-    Extensions.registerAreaClass("IDEA_PROJECT", null);
-    container = container != null
-                ? container
-                : new DefaultPicoContainer();
-    return new MockProject(container, parentDisposable);
-  }
-
-  public static void assertIsSerializable(@NotNull Serializable object) {
-    ObjectOutputStream out = null;
-    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
-    try {
-      out = new ObjectOutputStream(byteArrayOutputStream);
-      out.writeObject(object);
-    }
-    catch (NotSerializableException e) {
-      fail("An object is not serializable: " + e.getMessage());
-    }
-    catch (IOException e) {
-      fail("Could not serialize object: " + e.getMessage());
-    }
-    finally {
-      if (out != null) {
-        try {
-          out.close();
-        }
-        catch (IOException e) {
-          // ignore
-        }
-      }
-      try {
-        byteArrayOutputStream.close();
-      }
-      catch (IOException e) {
-        // ignore
-      }
-    }
-  }
-
-}
diff --git a/blaze-base/tests/utils/unit/com/google/idea/blaze/base/async/executor/MockBlazeExecutor.java b/blaze-base/tests/utils/unit/com/google/idea/blaze/base/async/executor/MockBlazeExecutor.java
deleted file mode 100644
index b7c8691..0000000
--- a/blaze-base/tests/utils/unit/com/google/idea/blaze/base/async/executor/MockBlazeExecutor.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.async.executor;
-
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.common.util.concurrent.MoreExecutors;
-
-import java.util.concurrent.Callable;
-
-/**
- * Used in tests.
- */
-public class MockBlazeExecutor extends BlazeExecutor {
-
-  private final ListeningExecutorService executor = MoreExecutors.sameThreadExecutor();
-
-  @Override
-  public <T> ListenableFuture<T> submit(final Callable<T> callable) {
-    return executor.submit(callable);
-  }
-
-  @Override
-  public ListeningExecutorService getExecutor() {
-    return executor;
-  }
-}
diff --git a/blaze-base/tests/utils/unit/com/google/idea/blaze/base/experiments/MockExperimentService.java b/blaze-base/tests/utils/unit/com/google/idea/blaze/base/experiments/MockExperimentService.java
deleted file mode 100644
index ece1b23..0000000
--- a/blaze-base/tests/utils/unit/com/google/idea/blaze/base/experiments/MockExperimentService.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.experiments;
-
-import com.google.common.collect.Maps;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.util.Map;
-
-/**
- * Used for tests.
- */
-public class MockExperimentService implements ExperimentService {
-
-  private Map<String, Object> experiments = Maps.newHashMap();
-
-  @Override
-  public void reloadExperiments() {
-  }
-
-  @Override
-  public void startExperimentScope() {
-  }
-
-  @Override
-  public void endExperimentScope() {
-  }
-
-  public void setExperiment(@NotNull BoolExperiment experiment, @NotNull boolean value) {
-    experiments.put(experiment.getKey(), value);
-  }
-
-  @Override
-  public boolean getExperiment(@NotNull String key, boolean defaultValue) {
-    if (experiments.containsKey(key)) {
-      return (Boolean)experiments.get(key);
-    }
-    return defaultValue;
-  }
-
-  @Override
-  @Nullable
-  public String getExperimentString(@NotNull String key, @Nullable String defaultValue) {
-    if (experiments.containsKey(key)) {
-      return (String)experiments.get(key);
-    }
-    return defaultValue;
-  }
-
-  @Override
-  public int getExperimentInt(@NotNull String key, int defaultValue) {
-    if (experiments.containsKey(key)) {
-      return (Integer)experiments.get(key);
-    }
-    return defaultValue;
-  }
-}
diff --git a/blaze-base/tests/utils/unit/com/google/idea/blaze/base/ideinfo/RuleMapBuilder.java b/blaze-base/tests/utils/unit/com/google/idea/blaze/base/ideinfo/RuleMapBuilder.java
deleted file mode 100644
index fd20f3f..0000000
--- a/blaze-base/tests/utils/unit/com/google/idea/blaze/base/ideinfo/RuleMapBuilder.java
+++ /dev/null
@@ -1,53 +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 com.google.idea.blaze.base.model.primitives.Label;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.List;
-
-/**
- * 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 ImmutableMap<Label, RuleIdeInfo> build() {
-    ImmutableMap.Builder<Label, RuleIdeInfo> ruleMap = ImmutableMap.builder();
-    for (RuleIdeInfo rule : rules) {
-      ruleMap.put(rule.label, rule);
-    }
-    return ruleMap.build();
-  }
-}
diff --git a/blaze-base/tests/utils/unit/com/google/idea/blaze/base/prefetch/MockPrefetchService.java b/blaze-base/tests/utils/unit/com/google/idea/blaze/base/prefetch/MockPrefetchService.java
deleted file mode 100644
index 85c08b7..0000000
--- a/blaze-base/tests/utils/unit/com/google/idea/blaze/base/prefetch/MockPrefetchService.java
+++ /dev/null
@@ -1,37 +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.prefetch;
-
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.intellij.openapi.project.Project;
-
-import java.io.File;
-import java.util.List;
-
-/**
- * Mocks the prefetch service.
- */
-public class MockPrefetchService implements PrefetchService {
-  @Override
-  public ListenableFuture<?> prefetchFiles(List<File> files, boolean synchronous) {
-    return Futures.immediateFuture(null);
-  }
-
-  @Override
-  public void prefetchProjectFiles(Project project) {
-  }
-}
diff --git a/blaze-base/tests/utils/unit/com/google/idea/blaze/base/scope/ErrorCollector.java b/blaze-base/tests/utils/unit/com/google/idea/blaze/base/scope/ErrorCollector.java
deleted file mode 100644
index 4b5cb71..0000000
--- a/blaze-base/tests/utils/unit/com/google/idea/blaze/base/scope/ErrorCollector.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.scope;
-
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.scope.output.IssueOutput;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.List;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Test class that collects issues.
- */
-public class ErrorCollector implements OutputSink<IssueOutput> {
-  List<IssueOutput> issues = Lists.newArrayList();
-
-  @Override
-  public Propagation onOutput(@NotNull IssueOutput output) {
-    issues.add(output);
-    return Propagation.Continue;
-  }
-
-  public void assertNoIssues() {
-    assertThat(issues).isEmpty();
-  }
-
-  public void assertIssues(@NotNull String... requiredMessages) {
-    List<String> messages = Lists.newArrayList();
-    for (IssueOutput issue : issues) {
-      messages.add(issue.getMessage());
-    }
-    assertThat(messages).containsExactly((Object[]) requiredMessages);
-  }
-
-  public void assertIssueContaining(@NotNull String s) {
-    assertThat(issues.stream().anyMatch((issue) -> issue.getMessage().contains(s)))
-      .named("Issues must contain: " + s)
-      .isTrue();
-  }
-}
-
diff --git a/blaze-cpp/BUILD b/blaze-cpp/BUILD
deleted file mode 100644
index 9e7261b..0000000
--- a/blaze-cpp/BUILD
+++ /dev/null
@@ -1,28 +0,0 @@
-package(default_visibility = ["//visibility:public"])
-
-java_library(
-    name = "blaze-cpp",
-    srcs = glob(["src/**/*.java"]),
-    deps = [
-        "//blaze-base",
-        "//intellij-platform-sdk:plugin_api",
-        "//third_party:jsr305",
-    ],
-)
-
-filegroup(
-    name = "plugin_xml",
-    srcs = ["src/META-INF/blaze-cpp.xml"],
-)
-
-java_library(
-    name = "test_lib",
-    srcs = glob(["tests/**/*.java"]),
-    deps = [
-        ":blaze-cpp",
-        "//blaze-base:unit_test_utils",
-        "//intellij-platform-sdk:plugin_api_for_tests",
-        "//third_party:jsr305",
-        "//third_party:test_lib",
-    ],
-)
diff --git a/blaze-cpp/src/META-INF/blaze-cpp.xml b/blaze-cpp/src/META-INF/blaze-cpp.xml
deleted file mode 100644
index 668e64a..0000000
--- a/blaze-cpp/src/META-INF/blaze-cpp.xml
+++ /dev/null
@@ -1,30 +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.
-  -->
-<idea-plugin>
-  <depends>com.intellij.modules.cidr.lang</depends>
-  <depends>com.intellij.modules.cidr.debugger</depends>
-
-  <extensions defaultExtensionNs="com.google.idea.blaze">
-    <SyncPlugin implementation="com.google.idea.blaze.cpp.BlazeCSyncPlugin"/>
-  </extensions>
-
-  <extensions defaultExtensionNs="cidr.lang">
-    <languageKindHelper implementation="com.google.idea.blaze.cpp.BlazeLanguageKindCalculatorHelper"/>
-  </extensions>
-  <extensions defaultExtensionNs="com.intellij">
-    <projectService serviceImplementation="com.google.idea.blaze.cpp.BlazeCWorkspace"/>
-  </extensions>
-</idea-plugin>
diff --git a/blaze-cpp/src/com/google/idea/blaze/cpp/BlazeCSyncPlugin.java b/blaze-cpp/src/com/google/idea/blaze/cpp/BlazeCSyncPlugin.java
deleted file mode 100644
index 75a7297..0000000
--- a/blaze-cpp/src/com/google/idea/blaze/cpp/BlazeCSyncPlugin.java
+++ /dev/null
@@ -1,70 +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.cpp;
-
-import com.google.common.collect.ImmutableSet;
-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.WorkspaceRoot;
-import com.google.idea.blaze.base.model.primitives.WorkspaceType;
-import com.google.idea.blaze.base.projectview.ProjectViewSet;
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.google.idea.blaze.base.scope.Scope;
-import com.google.idea.blaze.base.scope.scopes.TimingScope;
-import com.google.idea.blaze.base.sync.BlazeSyncPlugin;
-import com.intellij.openapi.module.Module;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.roots.ModifiableRootModel;
-import com.jetbrains.cidr.lang.workspace.OCWorkspace;
-import com.jetbrains.cidr.lang.workspace.OCWorkspaceManager;
-
-import javax.annotation.Nullable;
-import java.util.Set;
-
-public final class BlazeCSyncPlugin extends BlazeSyncPlugin.Adapter {
-  @Override
-  public Set<LanguageClass> getSupportedLanguagesInWorkspace(WorkspaceType workspaceType) {
-    if (workspaceType == WorkspaceType.C) {
-      return ImmutableSet.of(LanguageClass.C);
-    }
-    return ImmutableSet.of();
-  }
-
-  @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.C)) {
-      return;
-    }
-
-    Scope.push(context, childContext -> {
-      childContext.push(new TimingScope("Setup C Workspace"));
-
-      OCWorkspace workspace = OCWorkspaceManager.getWorkspace(project);
-      if (workspace instanceof BlazeCWorkspace) {
-        BlazeCWorkspace blazeCWorkspace = (BlazeCWorkspace)workspace;
-        blazeCWorkspace.update(childContext, blazeProjectData);
-      }
-    });
-  }
-}
diff --git a/blaze-cpp/src/com/google/idea/blaze/cpp/BlazeCWorkspace.java b/blaze-cpp/src/com/google/idea/blaze/cpp/BlazeCWorkspace.java
deleted file mode 100644
index 2c6f8ef..0000000
--- a/blaze-cpp/src/com/google/idea/blaze/cpp/BlazeCWorkspace.java
+++ /dev/null
@@ -1,145 +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.cpp;
-
-import com.google.common.collect.ImmutableList;
-import com.google.idea.blaze.base.metrics.Action;
-import com.google.idea.blaze.base.metrics.LoggingService;
-import com.google.idea.blaze.base.model.BlazeProjectData;
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.google.idea.blaze.base.settings.Blaze;
-import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.components.ServiceManager;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.jetbrains.cidr.lang.symbols.OCSymbol;
-import com.jetbrains.cidr.lang.workspace.OCResolveConfiguration;
-import com.jetbrains.cidr.lang.workspace.OCWorkspace;
-import com.jetbrains.cidr.lang.workspace.OCWorkspaceModificationTrackers;
-
-import javax.annotation.Nullable;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * Main entry point for C/CPP configuration data.
- */
-public final class BlazeCWorkspace implements OCWorkspace {
-  private static final Logger LOG = Logger.getInstance(BlazeCWorkspace.class);
-
-  @Nullable private final Project project;
-  @Nullable private final OCWorkspaceModificationTrackers modTrackers;
-
-  @Nullable private BlazeConfigurationResolver configurationResolver;
-
-  private BlazeCWorkspace(Project project) {
-    if (Blaze.isBlazeProject(project)) {
-      this.project = project;
-      this.modTrackers = new OCWorkspaceModificationTrackers(project);
-      this.configurationResolver = new BlazeConfigurationResolver(project);
-    } else {
-      this.project = null;
-      this.modTrackers = null;
-    }
-  }
-
-  public static BlazeCWorkspace getInstance(Project project) {
-    return ServiceManager.getService(project, BlazeCWorkspace.class);
-  }
-
-  public void update(BlazeContext context, BlazeProjectData blazeProjectData) {
-    LOG.assertTrue(project != null);
-    LOG.assertTrue(modTrackers != null);
-    LOG.assertTrue(configurationResolver != null);
-
-    long start = System.currentTimeMillis();
-    // Non-incremental update to our c configurations.
-    configurationResolver.update(context, blazeProjectData);
-    long end = System.currentTimeMillis();
-
-    LOG.info(String.format("Blaze OCWorkspace update took: %d ms", (end - start)));
-
-    ApplicationManager.getApplication().runReadAction(() -> {
-      if (project.isDisposed()) {
-        return;
-      }
-
-      // TODO(salguarnieri) Avoid bumping all of these trackers; figure out what has changed.
-      modTrackers.getProjectFilesListTracker().incModificationCount();
-      modTrackers.getSourceFilesListTracker().incModificationCount();
-      modTrackers.getBuildConfigurationChangesTracker().incModificationCount();
-      modTrackers.getBuildSettingsChangesTracker().incModificationCount();
-    });
-  }
-
-  @Override
-  public Collection<VirtualFile> getLibraryFilesToBuildSymbols() {
-  // This method should return all the header files themselves, not the head file directories.
-  // (And not header files in the project; just the ones in the SDK and in any dependencies)
-    return ImmutableList.of();
-  }
-
-  @Override
-  public boolean areFromSameProject(@Nullable VirtualFile a, @Nullable VirtualFile b) {
-    return false;
-  }
-
-  @Override
-  public boolean areFromSamePackage(@Nullable VirtualFile a, @Nullable VirtualFile b) {
-    return false;
-  }
-
-  @Override
-  public boolean isInSDK(@Nullable VirtualFile file) {
-    return false;
-  }
-
-  @Override
-  public boolean isFromWrongSDK(OCSymbol symbol, @Nullable VirtualFile contextFile) {
-    return false;
-  }
-
-  @Nullable
-  @Override
-  public OCResolveConfiguration getSelectedResolveConfiguration() {
-    return null;
-  }
-
-  @Override
-  public OCWorkspaceModificationTrackers getModificationTrackers() {
-    LOG.assertTrue(modTrackers != null);
-    return modTrackers;
-  }
-
-  @Override
-  public List<? extends OCResolveConfiguration> getConfigurations() {
-    return configurationResolver == null ? ImmutableList.of() : configurationResolver.getAllConfigurations();
-  }
-
-  @Override
-  public List<? extends OCResolveConfiguration> getConfigurationsForFile(@Nullable VirtualFile sourceFile) {
-    LoggingService.reportEvent(project, Action.C_RESOLVE_FILE);
-
-    if (sourceFile == null || !sourceFile.isValid() || configurationResolver == null) {
-      return ImmutableList.of();
-    }
-    OCResolveConfiguration config = configurationResolver.getConfigurationForFile(sourceFile);
-    return config == null ? ImmutableList.of() : ImmutableList.of(config);
-  }
-}
-
diff --git a/blaze-cpp/src/com/google/idea/blaze/cpp/BlazeCompilerMacros.java b/blaze-cpp/src/com/google/idea/blaze/cpp/BlazeCompilerMacros.java
deleted file mode 100644
index 168f691..0000000
--- a/blaze-cpp/src/com/google/idea/blaze/cpp/BlazeCompilerMacros.java
+++ /dev/null
@@ -1,91 +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.cpp;
-
-import com.google.common.base.Joiner;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Maps;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.psi.PsiFile;
-import com.jetbrains.cidr.lang.preprocessor.OCInclusionContext;
-import com.jetbrains.cidr.lang.preprocessor.OCInclusionContextUtil;
-import com.jetbrains.cidr.lang.workspace.compiler.CidrCompilerResult;
-import com.jetbrains.cidr.lang.workspace.compiler.OCCompilerMacros;
-import com.jetbrains.cidr.lang.workspace.compiler.OCCompilerSettings;
-import com.jetbrains.cidr.toolchains.CompilerInfoCache;
-
-import java.util.Map;
-
-final class BlazeCompilerMacros extends OCCompilerMacros {
-  private final CompilerInfoCache compilerInfoCache;
-  private final ImmutableList<String> globalDefines;
-  private final ImmutableMap<String, String> globalFeatures;
-  private final OCCompilerSettings compilerSettings;
-  private final Project project;
-
-  public BlazeCompilerMacros(
-    Project project,
-    CompilerInfoCache compilerInfoCache,
-    OCCompilerSettings compilerSettings,
-    ImmutableList<String> defines,
-    ImmutableMap<String, String> features
-  ) {
-    this.project = project;
-    this.compilerInfoCache = compilerInfoCache;
-    this.compilerSettings = compilerSettings;
-    this.globalDefines = defines;
-    this.globalFeatures = features;
-  }
-
-  @Override
-  protected void fillFileMacros(OCInclusionContext context, PsiFile sourceFile) {
-    // Get the default compiler info for this file.
-    VirtualFile vf = OCInclusionContextUtil.getVirtualFile(sourceFile);
-    CidrCompilerResult<CompilerInfoCache.Entry> compilerInfoProvider = compilerInfoCache.getCompilerInfoCache(
-      project,
-      compilerSettings,
-      context.getLanguageKind(),
-      vf
-    );
-    CompilerInfoCache.Entry compilerInfo = compilerInfoProvider.getResult();
-
-    // Combine the info we got from Blaze with the info we get from IntelliJ's methods.
-    UniqueListBuilder<String> allDefinesBuilder = new UniqueListBuilder<>();
-    // IntelliJ expects a string of "#define [VAR_NAME]\n#define [VAR_NAME2]\n..."
-    for (String globalDefine : globalDefines) {
-      allDefinesBuilder.add("#define " + globalDefine);
-    }
-    if (compilerInfo != null) {
-      String[] split = compilerInfo.defines.split("\n");
-      for (String s : split) {
-        allDefinesBuilder.add(s);
-      }
-    }
-    final String allDefines = Joiner.on("\n").join(allDefinesBuilder.build());
-
-    Map<String, String> allFeatures = Maps.newHashMap();
-    allFeatures.putAll(globalFeatures);
-    if (compilerInfo != null) {
-      allFeatures.putAll(compilerInfo.features);
-    }
-
-    fillSubstitutions(context, allDefines);
-    enableClangFeatures(context, allFeatures);
-    enableClangExtensions(context, allFeatures);
-  }
-}
diff --git a/blaze-cpp/src/com/google/idea/blaze/cpp/BlazeCompilerSettings.java b/blaze-cpp/src/com/google/idea/blaze/cpp/BlazeCompilerSettings.java
deleted file mode 100644
index e3ba4df..0000000
--- a/blaze-cpp/src/com/google/idea/blaze/cpp/BlazeCompilerSettings.java
+++ /dev/null
@@ -1,105 +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.cpp;
-
-import com.google.common.collect.ImmutableList;
-import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.jetbrains.cidr.lang.OCLanguageKind;
-import com.jetbrains.cidr.lang.toolchains.CidrCompilerSwitches;
-import com.jetbrains.cidr.lang.toolchains.CidrSwitchBuilder;
-import com.jetbrains.cidr.lang.toolchains.CidrToolEnvironment;
-import com.jetbrains.cidr.lang.toolchains.DefaultCidrToolEnvironment;
-import com.jetbrains.cidr.lang.workspace.compiler.OCCompilerKind;
-import com.jetbrains.cidr.lang.workspace.compiler.OCCompilerSettings;
-import org.jetbrains.annotations.NotNull;
-
-import javax.annotation.Nullable;
-import java.io.File;
-import java.util.List;
-
-final class BlazeCompilerSettings extends OCCompilerSettings {
-  private final CidrToolEnvironment toolEnvironment = new DefaultCidrToolEnvironment();
-
-  private final Project project;
-  @Nullable private final File cCompiler;
-  @Nullable private final File cppCompiler;
-  private final ImmutableList<String> cFlags;
-  private final ImmutableList<String> cppFlags;
-
-  BlazeCompilerSettings(
-    Project project,
-    @Nullable File cCompiler,
-    @Nullable File cppCompiler,
-    ImmutableList<String> cFlags,
-    ImmutableList<String> cppFlags
-  ) {
-    this.project = project;
-    this.cCompiler = cCompiler;
-    this.cppCompiler = cppCompiler;
-    this.cFlags = cFlags;
-    this.cppFlags = cppFlags;
-  }
-
-  @Override
-  public OCCompilerKind getCompiler(OCLanguageKind languageKind) {
-    return null;
-  }
-
-  @Override
-  public File getCompilerExecutable(@NotNull OCLanguageKind lang) {
-    if (lang == OCLanguageKind.C) {
-      return cCompiler;
-    } else if (lang == OCLanguageKind.CPP) {
-      return cppCompiler;
-    }
-    // We don't support objective c/c++.
-    return null;
-  }
-
-  @Override
-  public File getCompilerWorkingDir() {
-    return WorkspaceRoot.fromProject(project).directory();
-  }
-
-  @Override
-  public CidrToolEnvironment getEnvironment() {
-    return toolEnvironment;
-  }
-
-  @Override
-  public CidrCompilerSwitches getCompilerSwitches(OCLanguageKind lang, @Nullable VirtualFile sourceFile) {
-    final List<String> allCompilerFlags;
-    if (lang == OCLanguageKind.C) {
-      allCompilerFlags = cFlags;
-    } else if (lang == OCLanguageKind.CPP) {
-      allCompilerFlags = cppFlags;
-    } else {
-      allCompilerFlags = ImmutableList.of();
-    }
-
-    CidrSwitchBuilder builder = new CidrSwitchBuilder();
-    // Because there can be both escaped and unescaped spaces in the flag, first unescape the spaces and then escape all of them.
-    allCompilerFlags.stream()
-      .map(flag -> flag.replace("\\ ", " "))
-      .map(flag -> flag.replace(" ", "\\ "))
-      .forEach(builder::addSingle);
-
-    return builder.build();
-  }
-}
-
diff --git a/blaze-cpp/src/com/google/idea/blaze/cpp/BlazeConfigurationResolver.java b/blaze-cpp/src/com/google/idea/blaze/cpp/BlazeConfigurationResolver.java
deleted file mode 100644
index a6deb7e..0000000
--- a/blaze-cpp/src/com/google/idea/blaze/cpp/BlazeConfigurationResolver.java
+++ /dev/null
@@ -1,267 +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.cpp;
-
-import com.google.common.collect.*;
-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.model.BlazeProjectData;
-import com.google.idea.blaze.base.model.primitives.Label;
-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.scopes.TimingScope;
-import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.io.FileUtil;
-import com.intellij.openapi.vfs.VfsUtilCore;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.jetbrains.cidr.lang.workspace.OCResolveConfiguration;
-
-import javax.annotation.Nullable;
-import java.io.File;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.ExecutionException;
-
-final class BlazeConfigurationResolver {
-  private static final class MapEntry {
-    public final Label label;
-    public final BlazeResolveConfiguration configuration;
-
-    public MapEntry(Label label, BlazeResolveConfiguration configuration) {
-      this.label = label;
-      this.configuration = configuration;
-    }
-  }
-
-  private static final Logger LOG = Logger.getInstance(BlazeConfigurationResolver.class);
-  private final Project project;
-
-  private ImmutableMap<Label, BlazeResolveConfiguration> resolveConfigurations = ImmutableMap.of();
-
-  public BlazeConfigurationResolver(Project project) {
-    this.project = project;
-  }
-
-  public void update(BlazeContext context, BlazeProjectData blazeProjectData) {
-    WorkspacePathResolver workspacePathResolver = blazeProjectData.workspacePathResolver;
-    ImmutableMap<Label, CToolchainIdeInfo> toolchainLookupMap = BlazeResolveConfiguration.buildToolchainLookupMap(
-      context,
-      blazeProjectData.ruleMap,
-      blazeProjectData.reverseDependencies
-    );
-    resolveConfigurations = buildBlazeConfigurationMap(context, blazeProjectData, toolchainLookupMap, workspacePathResolver);
-  }
-
-  private ImmutableMap<Label, BlazeResolveConfiguration> buildBlazeConfigurationMap(
-    BlazeContext parentContext,
-    BlazeProjectData blazeProjectData,
-    ImmutableMap<Label, CToolchainIdeInfo> toolchainLookupMap,
-    WorkspacePathResolver workspacePathResolver
-  ) {
-    // Type specification needed to avoid incorrect type inference during command line build.
-    return Scope.push(parentContext, (ScopedFunction<ImmutableMap<Label, 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.values()) {
-        if (rule.kind.getLanguageClass() == LanguageClass.C) {
-          ListenableFuture<MapEntry> future =
-            submit(
-              () -> createResolveConfiguration(
-                rule,
-                toolchainLookupMap,
-                compilerWrapperCache,
-                workspacePathResolver,
-                blazeProjectData)
-            );
-          mapEntryFutures.add(future);
-        }
-      }
-
-      ImmutableMap.Builder<Label, BlazeResolveConfiguration> newResolveConfigurations = ImmutableMap.builder();
-      List<MapEntry> mapEntries;
-      try {
-        mapEntries = Futures.allAsList(mapEntryFutures).get();
-      }
-      catch (InterruptedException | ExecutionException e) {
-        Thread.currentThread().interrupt();
-        LOG.warn("Could not build C resolve configurations", e);
-        context.setCancelled();
-        return ImmutableMap.of();
-      }
-
-      for (MapEntry mapEntry : mapEntries) {
-        // Skip over labels that don't have C configuration data.
-        if (mapEntry != null) {
-          newResolveConfigurations.put(mapEntry.label, mapEntry.configuration);
-        }
-      }
-      return newResolveConfigurations.build();
-    });
-  }
-
-  private static ListenableFuture<MapEntry> submit(Callable<MapEntry> callable) {
-    return BlazeExecutor.getInstance().submit(callable);
-  }
-
-  @Nullable
-  private MapEntry createResolveConfiguration(
-    RuleIdeInfo rule,
-    ImmutableMap<Label, CToolchainIdeInfo> toolchainLookupMap,
-    ConcurrentMap<CToolchainIdeInfo, File> compilerWrapperCache,
-    WorkspacePathResolver workspacePathResolver,
-    BlazeProjectData blazeProjectData
-  ) {
-    Label label = rule.label;
-    LOG.info("Resolving " + label.toString());
-    CToolchainIdeInfo toolchainIdeInfo = toolchainLookupMap.get(label);
-    if (toolchainIdeInfo != null) {
-      File compilerWrapper = findOrCreateCompilerWrapperScript(
-        compilerWrapperCache,
-        toolchainIdeInfo,
-        workspacePathResolver,
-        rule.label
-      );
-      if (compilerWrapper != null) {
-        BlazeResolveConfiguration config = BlazeResolveConfiguration.createConfigurationForTarget(
-          project,
-          workspacePathResolver,
-          blazeProjectData.ruleMap.get(label),
-          toolchainIdeInfo,
-          compilerWrapper
-        );
-        if (config != null) {
-          return new MapEntry(label, config);
-        }
-      }
-    }
-    return null;
-  }
-
-  @Nullable
-  private static File findOrCreateCompilerWrapperScript(
-    Map<CToolchainIdeInfo, File> compilerWrapperCache,
-    CToolchainIdeInfo toolchainIdeInfo,
-    WorkspacePathResolver workspacePathResolver,
-    Label label
-  ) {
-    File compilerWrapper = compilerWrapperCache.get(toolchainIdeInfo);
-    if (compilerWrapper == null) {
-      File cppExecutable = workspacePathResolver.resolveToFile(toolchainIdeInfo.cppExecutable.getAbsoluteOrRelativeFile().getPath());
-      if (cppExecutable == null) {
-        String errorMessage = String.format(
-          "Unable to find compiler executable: %s for rule %s",
-          toolchainIdeInfo.cppExecutable.toString(),
-          label.toString());
-        LOG.warn(errorMessage);
-        compilerWrapper = null;
-      } else {
-        compilerWrapper = createCompilerExecutableWrapper(cppExecutable);
-        if (compilerWrapper != null) {
-          compilerWrapperCache.put(toolchainIdeInfo, compilerWrapper);
-        }
-      }
-    }
-    return compilerWrapper;
-  }
-
-  /**
-   * Create a wrapper script that transforms the CLion compiler invocation into a safe invocation of the compiler script that blaze uses.
-   *
-   * CLion passes arguments to the compiler in an arguments file. The c toolchain compiler wrapper script doesn't handle arguments files, so
-   * we need to move the compiler arguments from the file to the command line.
-   *
-   * @param blazeCompilerExecutableFile blaze compiler wrapper
-   * @return The wrapper script that CLion can call.
-   */
-  @Nullable
-  private static File createCompilerExecutableWrapper(File blazeCompilerExecutableFile) {
-    try {
-      File blazeCompilerWrapper = FileUtil.createTempFile("blaze_compiler", ".sh", true /* deleteOnExit */);
-      if (!blazeCompilerWrapper.setExecutable(true)) {
-        return null;
-      }
-      ImmutableList<String> COMPILER_WRAPPER_SCRIPT_LINES = ImmutableList.of(
-        "#!/bin/bash",
-        "",
-        "# The c toolchain compiler wrapper script doesn't handle arguments files, so we",
-        "# need to move the compiler arguments from the file to the command line.",
-        "",
-        "if [ $# -ne 2 ]; then",
-        "  echo \"Usage: $0 @arg-file compile-file\"",
-        "  exit 2;",
-        "fi",
-        "",
-        "if [[ $1 != @* ]]; then",
-        "  echo \"Usage: $0 @arg-file compile-file\"",
-        "  exit 3;",
-        "fi",
-        "",
-        " # Remove the @ before the arguments file path",
-        "ARG_FILE=${1#@}",
-        "# The actual compiler wrapper script we get from blaze",
-        "EXE=" + blazeCompilerExecutableFile.getPath(),
-        "# Read in the arguments file so we can pass the arguments on the command line.",
-        "ARGS=`cat $ARG_FILE`",
-        "$EXE $ARGS $2"
-      );
-
-      try (PrintWriter pw = new PrintWriter(blazeCompilerWrapper)) {
-        COMPILER_WRAPPER_SCRIPT_LINES.forEach(pw::println);
-      }
-      return blazeCompilerWrapper;
-    }
-    catch (IOException e) {
-      return null;
-    }
-  }
-
-  @Nullable
-  public OCResolveConfiguration getConfigurationForFile(VirtualFile sourceFile) {
-    SourceToRuleMap sourceToRuleMap = SourceToRuleMap.getInstance(project);
-    List<Label> targetsForSourceFile =
-      Lists.newArrayList(sourceToRuleMap.getTargetsForSourceFile(VfsUtilCore.virtualToIoFile(sourceFile)));
-    if (targetsForSourceFile.isEmpty()) {
-      return null;
-    }
-
-    // If a source file is in two different targets, 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()));
-    Label target = Iterables.getFirst(targetsForSourceFile, null);
-    assert(target != null);
-
-    return resolveConfigurations.get(target);
-  }
-
-  public List<? extends OCResolveConfiguration> getAllConfigurations() {
-    return ImmutableList.copyOf(resolveConfigurations.values());
-  }
-}
diff --git a/blaze-cpp/src/com/google/idea/blaze/cpp/BlazeLanguageKindCalculatorHelper.java b/blaze-cpp/src/com/google/idea/blaze/cpp/BlazeLanguageKindCalculatorHelper.java
deleted file mode 100644
index a3f4b91..0000000
--- a/blaze-cpp/src/com/google/idea/blaze/cpp/BlazeLanguageKindCalculatorHelper.java
+++ /dev/null
@@ -1,48 +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.cpp;
-
-import com.google.idea.blaze.base.settings.Blaze;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.io.FileUtilRt;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.jetbrains.cidr.lang.OCLanguageKind;
-import com.jetbrains.cidr.lang.workspace.OCLanguageKindCalculatorHelper;
-
-import javax.annotation.Nullable;
-
-public final class BlazeLanguageKindCalculatorHelper implements OCLanguageKindCalculatorHelper {
-  @Nullable
-  @Override
-  public OCLanguageKind getSpecifiedLanguage(Project project, VirtualFile file) {
-    return null;
-  }
-
-  @Nullable
-  @Override
-  public OCLanguageKind getLanguageByExtension(Project project, String name) {
-    if (Blaze.isBlazeProject(project)) {
-      String extension = FileUtilRt.getExtension(name);
-      if (extension.equalsIgnoreCase("c")) {
-        return OCLanguageKind.C;
-      }
-      if (extension.equalsIgnoreCase("cc")) {
-        return OCLanguageKind.CPP;
-      }
-    }
-    return null;
-  }
-}
diff --git a/blaze-cpp/src/com/google/idea/blaze/cpp/BlazeResolveConfiguration.java b/blaze-cpp/src/com/google/idea/blaze/cpp/BlazeResolveConfiguration.java
deleted file mode 100644
index 06c3f0f..0000000
--- a/blaze-cpp/src/com/google/idea/blaze/cpp/BlazeResolveConfiguration.java
+++ /dev/null
@@ -1,378 +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.cpp;
-
-import com.google.common.collect.*;
-import com.google.idea.blaze.base.ideinfo.CRuleIdeInfo;
-import com.google.idea.blaze.base.ideinfo.CToolchainIdeInfo;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.model.primitives.Label;
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.google.idea.blaze.base.scope.Scope;
-import com.google.idea.blaze.base.scope.scopes.TimingScope;
-import com.google.idea.blaze.base.model.primitives.ExecutionRootPath;
-import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.UserDataHolderBase;
-import com.intellij.openapi.vfs.VfsUtil;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.jetbrains.cidr.lang.OCFileTypeHelpers;
-import com.jetbrains.cidr.lang.OCLanguageKind;
-import com.jetbrains.cidr.lang.preprocessor.OCImportGraph;
-import com.jetbrains.cidr.lang.workspace.OCLanguageKindCalculator;
-import com.jetbrains.cidr.lang.workspace.OCResolveConfiguration;
-import com.jetbrains.cidr.lang.workspace.OCResolveRootAndConfiguration;
-import com.jetbrains.cidr.lang.workspace.OCWorkspaceUtil;
-import com.jetbrains.cidr.lang.workspace.compiler.CidrCompilerResult;
-import com.jetbrains.cidr.lang.workspace.compiler.OCCompilerMacros;
-import com.jetbrains.cidr.lang.workspace.compiler.OCCompilerSettings;
-import com.jetbrains.cidr.lang.workspace.headerRoots.HeaderRoots;
-import com.jetbrains.cidr.lang.workspace.headerRoots.HeadersSearchRoot;
-import com.jetbrains.cidr.lang.workspace.headerRoots.IncludedHeadersRoot;
-import com.jetbrains.cidr.toolchains.CompilerInfoCache;
-
-import javax.annotation.Nullable;
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-
-
-public final class BlazeResolveConfiguration extends UserDataHolderBase implements OCResolveConfiguration {
-  public static final Logger LOG = Logger.getInstance(BlazeResolveConfiguration.class);
-
-  private final WorkspacePathResolver workspacePathResolver;
-
-  private final Project project;
-  private final Label label;
-
-  private final ImmutableList<HeadersSearchRoot> cLibraryIncludeRoots;
-  private final ImmutableList<HeadersSearchRoot> cppLibraryIncludeRoots;
-  private final HeaderRoots projectIncludeRoots;
-
-  private final CompilerInfoCache compilerInfoCache;
-  private final BlazeCompilerMacros compilerMacros;
-  private final BlazeCompilerSettings compilerSettings;
-
-  @Nullable
-  public static BlazeResolveConfiguration createConfigurationForTarget(
-    Project project,
-    WorkspacePathResolver workspacePathResolver,
-    RuleIdeInfo ruleIdeInfo,
-    CToolchainIdeInfo toolchainIdeInfo,
-    File compilerWrapper
-  ) {
-    CRuleIdeInfo cRuleIdeInfo = ruleIdeInfo.cRuleIdeInfo;
-    if (cRuleIdeInfo == null) {
-      return null;
-    }
-
-    UniqueListBuilder<ExecutionRootPath> systemIncludesBuilder = new UniqueListBuilder<>();
-    systemIncludesBuilder.addAll(cRuleIdeInfo.transitiveSystemIncludeDirectories);
-    systemIncludesBuilder.addAll(toolchainIdeInfo.builtInIncludeDirectories);
-    systemIncludesBuilder.addAll(toolchainIdeInfo.unfilteredToolchainSystemIncludes);
-
-    UniqueListBuilder<ExecutionRootPath> userIncludesBuilder = new UniqueListBuilder<>();
-    userIncludesBuilder.addAll(cRuleIdeInfo.transitiveIncludeDirectories);
-
-    UniqueListBuilder<ExecutionRootPath> userQuoteIncludesBuilder = new UniqueListBuilder<>();
-    userQuoteIncludesBuilder.addAll(cRuleIdeInfo.transitiveQuoteIncludeDirectories);
-
-    ImmutableList.Builder<String> cFlagsBuilder = ImmutableList.builder();
-    cFlagsBuilder.addAll(toolchainIdeInfo.baseCompilerOptions);
-    cFlagsBuilder.addAll(toolchainIdeInfo.cCompilerOptions);
-    cFlagsBuilder.addAll(toolchainIdeInfo.unfilteredCompilerOptions);
-
-    ImmutableList.Builder<String> cppFlagsBuilder = ImmutableList.builder();
-    cppFlagsBuilder.addAll(toolchainIdeInfo.baseCompilerOptions);
-    cppFlagsBuilder.addAll(toolchainIdeInfo.cppCompilerOptions);
-    cppFlagsBuilder.addAll(toolchainIdeInfo.unfilteredCompilerOptions);
-
-    ImmutableMap<String, String> features = ImmutableMap.of();
-
-    return new BlazeResolveConfiguration(
-      project,
-      workspacePathResolver,
-      ruleIdeInfo.label,
-      systemIncludesBuilder.build(),
-      systemIncludesBuilder.build(),
-      userQuoteIncludesBuilder.build(),
-      userIncludesBuilder.build(),
-      userIncludesBuilder.build(),
-      cRuleIdeInfo.transitiveDefines,
-      features,
-      compilerWrapper,
-      compilerWrapper,
-      cFlagsBuilder.build(),
-      cppFlagsBuilder.build()
-    );
-  }
-
-  public static ImmutableMap<Label, CToolchainIdeInfo> buildToolchainLookupMap(
-    BlazeContext context,
-    ImmutableMap<Label, RuleIdeInfo> ruleMap,
-    ImmutableMultimap<Label, Label> reverseDependencies
-  ) {
-    return Scope.push(context, childContext -> {
-      childContext.push(new TimingScope("Build toolchain lookup map"));
-
-      List<Label> seeds = Lists.newArrayList();
-      for (Map.Entry<Label, RuleIdeInfo> entry : ruleMap.entrySet()) {
-        CToolchainIdeInfo cToolchainIdeInfo = entry.getValue().cToolchainIdeInfo;
-        if (cToolchainIdeInfo != null) {
-          seeds.add(entry.getKey());
-        }
-      }
-
-      Map<Label, CToolchainIdeInfo> lookupTable = Maps.newHashMap();
-      for (Label seed : seeds) {
-        CToolchainIdeInfo toolchainInfo = ruleMap.get(seed).cToolchainIdeInfo;
-        LOG.assertTrue(toolchainInfo != null);
-        List<Label> worklist = Lists.newArrayList(reverseDependencies.get(seed));
-        while (!worklist.isEmpty()) {
-          // We should never see a label depend on two different toolchains.
-          Label l = worklist.remove(0);
-          CToolchainIdeInfo previousValue = lookupTable.putIfAbsent(l, toolchainInfo);
-          // Don't propagate the toolchain twice.
-          if (previousValue == null) {
-            worklist.addAll(reverseDependencies.get(l));
-          }
-          else {
-            LOG.assertTrue(previousValue.equals(toolchainInfo));
-          }
-        }
-      }
-      return ImmutableMap.copyOf(lookupTable);
-    });
-  }
-
-  public BlazeResolveConfiguration(
-    Project project,
-    WorkspacePathResolver workspacePathResolver,
-    Label label,
-    ImmutableList<ExecutionRootPath> cSystemIncludeDirs,
-    ImmutableList<ExecutionRootPath> cppSystemIncludeDirs,
-    ImmutableList<ExecutionRootPath> quoteIncludeDirs,
-    ImmutableList<ExecutionRootPath> cIncludeDirs,
-    ImmutableList<ExecutionRootPath> cppIncludeDirs,
-    ImmutableList<String> defines,
-    ImmutableMap<String, String> features,
-    File cCompilerExecutable,
-    File cppCompilerExecutable,
-    ImmutableList<String> cCompilerFlags,
-    ImmutableList<String> cppCompilerFlags
-  ) {
-    this.workspacePathResolver = workspacePathResolver;
-    this.project = project;
-    this.label = label;
-
-    ImmutableList.Builder<HeadersSearchRoot> cIncludeRootsBuilder = ImmutableList.builder();
-    collectHeaderRoots(cIncludeRootsBuilder, cIncludeDirs, true /* isUserHeader */);
-    collectHeaderRoots(cIncludeRootsBuilder, cSystemIncludeDirs, false /* isUserHeader */);
-    this.cLibraryIncludeRoots = cIncludeRootsBuilder.build();
-
-    ImmutableList.Builder<HeadersSearchRoot> cppIncludeRootsBuilder = ImmutableList.builder();
-    collectHeaderRoots(cppIncludeRootsBuilder, cppIncludeDirs, true /* isUserHeader */);
-    collectHeaderRoots(cppIncludeRootsBuilder, cppSystemIncludeDirs, false /* isUserHeader */);
-    this.cppLibraryIncludeRoots = cppIncludeRootsBuilder.build();
-
-    ImmutableList.Builder<HeadersSearchRoot> quoteIncludeRootsBuilder = ImmutableList.builder();
-    collectHeaderRoots(quoteIncludeRootsBuilder, quoteIncludeDirs, true /* isUserHeader */);
-    this.projectIncludeRoots = new HeaderRoots(quoteIncludeRootsBuilder.build());
-
-    this.compilerSettings = new BlazeCompilerSettings(
-      project,
-      cCompilerExecutable,
-      cppCompilerExecutable,
-      cCompilerFlags,
-      cppCompilerFlags
-    );
-
-    this.compilerInfoCache = new CompilerInfoCache();
-    this.compilerMacros = new BlazeCompilerMacros(
-      project,
-      compilerInfoCache,
-      compilerSettings,
-      defines,
-      features
-    );
-  }
-
-  @Override
-  public Project getProject() {
-    return project;
-  }
-
-  @Override
-  public String getDisplayName(boolean shorten) {
-    return label.toString();
-  }
-
-  @Nullable
-  @Override
-  public VirtualFile getPrecompiledHeader() {
-    return null;
-  }
-
-  @Nullable
-  @Override
-  public OCLanguageKind getDeclaredLanguageKind(VirtualFile sourceOrHeaderFile) {
-    String fileName = sourceOrHeaderFile.getName();
-    if (OCFileTypeHelpers.isSourceFile(fileName)) {
-      return getLanguageKind(sourceOrHeaderFile);
-    }
-
-    if (OCFileTypeHelpers.isHeaderFile(fileName)) {
-      return getLanguageKind(getSourceFileForHeaderFile(sourceOrHeaderFile));
-    }
-
-    return null;
-  }
-
-  private OCLanguageKind getLanguageKind(@Nullable VirtualFile sourceFile) {
-    OCLanguageKind kind = OCLanguageKindCalculator.tryFileTypeAndExtension(project, sourceFile);
-    return kind != null ? kind : getMaximumLanguageKind();
-  }
-
-  @Nullable
-  private VirtualFile getSourceFileForHeaderFile(VirtualFile headerFile) {
-    ArrayList<VirtualFile> roots = new ArrayList<>(OCImportGraph.getAllHeaderRoots(project, headerFile));
-
-    final String headerNameWithoutExtension = headerFile.getNameWithoutExtension();
-    for (VirtualFile root : roots) {
-      if (root.getNameWithoutExtension().equals(headerNameWithoutExtension)) {
-        return root;
-      }
-    }
-    return null;
-  }
-
-  @Override
-  public OCLanguageKind getPrecompiledLanguageKind() {
-    return getMaximumLanguageKind();
-  }
-
-  @Override
-  public OCLanguageKind getMaximumLanguageKind() {
-    return OCLanguageKind.CPP;
-  }
-
-  @Override
-  public HeaderRoots getProjectHeadersRoots() {
-    return projectIncludeRoots;
-  }
-
-  @Override
-  public HeaderRoots getLibraryHeadersRoots(OCResolveRootAndConfiguration headerContext) {
-    OCLanguageKind languageKind = headerContext.getKind();
-    VirtualFile sourceFile = headerContext.getRootFile();
-    if (languageKind == null) {
-      languageKind = getLanguageKind(sourceFile);
-    }
-
-    UniqueListBuilder<HeadersSearchRoot> roots = new UniqueListBuilder<>();
-    if (languageKind == OCLanguageKind.C) {
-      roots.addAll(cLibraryIncludeRoots);
-    } else {
-      roots.addAll(cppLibraryIncludeRoots);
-    }
-
-    CidrCompilerResult<CompilerInfoCache.Entry> compilerInfoCacheHolder = compilerInfoCache.getCompilerInfoCache(
-      project,
-      compilerSettings,
-      languageKind,
-      sourceFile
-    );
-    CompilerInfoCache.Entry compilerInfo = compilerInfoCacheHolder.getResult();
-    if (compilerInfo != null) {
-      roots.addAll(compilerInfo.headerSearchPaths);
-    }
-
-    return new HeaderRoots(roots.build());
-  }
-
-  private void collectHeaderRoots(
-    ImmutableList.Builder<HeadersSearchRoot> roots,
-    ImmutableList<ExecutionRootPath> paths,
-    boolean isUserHeader
-  ) {
-    for (ExecutionRootPath executionRootPath : paths) {
-      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 {
-          roots.add(new IncludedHeadersRoot(project, vf, false /* recursive */, isUserHeader));
-        }
-      }
-    }
-  }
-
-  @Nullable
-  private static VirtualFile getVirtualFile(File file) {
-    // Fail fast if the file doesn't even exist.
-    if (!file.exists()) {
-      return null;
-    }
-    return VfsUtil.findFileByIoFile(file, true);
-  }
-
-  @Override
-  public OCCompilerMacros getCompilerMacros() {
-    return compilerMacros;
-  }
-
-  @Override
-  public OCCompilerSettings getCompilerSettings() {
-    return compilerSettings;
-  }
-
-  @Nullable
-  @Override
-  public Object getIndexingCluster() {
-    return null;
-  }
-
-  @Override
-  public int compareTo(OCResolveConfiguration other) {
-    return OCWorkspaceUtil.compareConfigurations(this, other);
-  }
-
-  @Override
-  public int hashCode() {
-    // There should only be one configuration per label.
-    return Objects.hash(label);
-  }
-
-  @Override
-  public boolean equals(Object obj) {
-    if (this == obj) {
-      return true;
-    }
-
-    if (!(obj instanceof BlazeResolveConfiguration)) {
-      return false;
-    }
-
-    BlazeResolveConfiguration that = (BlazeResolveConfiguration)obj;
-    return compareTo(that) == 0;
-  }
-}
-
diff --git a/blaze-cpp/src/com/google/idea/blaze/cpp/UniqueListBuilder.java b/blaze-cpp/src/com/google/idea/blaze/cpp/UniqueListBuilder.java
deleted file mode 100644
index a9ebfba..0000000
--- a/blaze-cpp/src/com/google/idea/blaze/cpp/UniqueListBuilder.java
+++ /dev/null
@@ -1,49 +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.cpp;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Sets;
-
-import java.util.Set;
-
-/**
- * A list where an item is only added if it is not already in the list.
- */
-final class UniqueListBuilder<T> {
-  private final Set<T> set = Sets.newLinkedHashSet();
-
-  /**
-   * Add {@param element} if it is not already in the list.
-   * @return true if the element has been added, false otherwise.
-   */
-  public boolean add(T element) {
-    return set.add(element);
-  }
-
-  /**
-   * For each element in {@param elements} add the element to the list if it is not already in the list.
-   */
-  public void addAll(Iterable<T> elements) {
-    for (T element : elements) {
-      add(element);
-    }
-  }
-
-  public ImmutableList<T> build() {
-    return ImmutableList.copyOf(set);
-  }
-}
diff --git a/blaze-cpp/tests/unittests/com/google/idea/blaze/cpp/BlazeCompilerSettingsTest.java b/blaze-cpp/tests/unittests/com/google/idea/blaze/cpp/BlazeCompilerSettingsTest.java
deleted file mode 100644
index fe69893..0000000
--- a/blaze-cpp/tests/unittests/com/google/idea/blaze/cpp/BlazeCompilerSettingsTest.java
+++ /dev/null
@@ -1,97 +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.cpp;
-
-import com.google.common.collect.ImmutableList;
-import com.google.idea.blaze.base.BlazeTestCase;
-import com.jetbrains.cidr.lang.OCLanguageKind;
-import com.jetbrains.cidr.lang.toolchains.CidrCompilerSwitches;
-import org.junit.Test;
-
-import java.io.File;
-import java.util.List;
-
-import static com.google.common.truth.Truth.assertThat;
-
-public class BlazeCompilerSettingsTest extends BlazeTestCase {
-  @Test
-  public void testCompilerSwitchesSimple() {
-    File cppExe = new File("bin/cpp");
-    ImmutableList<String> cFlags = ImmutableList.of("-fast", "-slow");
-    BlazeCompilerSettings settings = new BlazeCompilerSettings(
-      getProject(),
-      cppExe,
-      cppExe,
-      cFlags,
-      cFlags
-    );
-
-    CidrCompilerSwitches compilerSwitches = settings.getCompilerSwitches(OCLanguageKind.C, null);
-    List<String> commandLineArgs = compilerSwitches.getCommandLineArgs();
-    assertThat(commandLineArgs).containsExactly("-fast", "-slow");
-  }
-
-  @Test
-  public void testCompilerSwitchesWithUnescapedSpaces() {
-    File cppExe = new File("bin/cpp");
-    ImmutableList<String> cFlags = ImmutableList.of("-f ast", "-slo w");
-    BlazeCompilerSettings settings = new BlazeCompilerSettings(
-      getProject(),
-      cppExe,
-      cppExe,
-      cFlags,
-      cFlags
-    );
-
-    CidrCompilerSwitches compilerSwitches = settings.getCompilerSwitches(OCLanguageKind.C, null);
-    List<String> commandLineArgs = compilerSwitches.getCommandLineArgs();
-    assertThat(commandLineArgs).containsExactly("-f\\ ast", "-slo\\ w");
-  }
-
-  @Test
-  public void testCompilerSwitchesWithEscapedSpaces() {
-    File cppExe = new File("bin/cpp");
-    ImmutableList<String> cFlags = ImmutableList.of("-f\\ ast", "-slo\\ w");
-    BlazeCompilerSettings settings = new BlazeCompilerSettings(
-      getProject(),
-      cppExe,
-      cppExe,
-      cFlags,
-      cFlags
-    );
-
-    CidrCompilerSwitches compilerSwitches = settings.getCompilerSwitches(OCLanguageKind.C, null);
-    List<String> commandLineArgs = compilerSwitches.getCommandLineArgs();
-    assertThat(commandLineArgs).containsExactly("-f\\ ast", "-slo\\ w");
-  }
-
-  @Test
-  public void testCompilerSwitchesWithUnescapedAndEscapedSpaces() {
-    File cppExe = new File("bin/cpp");
-    ImmutableList<String> cFlags = ImmutableList.of("-f ast", "-slo\\ w");
-    BlazeCompilerSettings settings = new BlazeCompilerSettings(
-      getProject(),
-      cppExe,
-      cppExe,
-      cFlags,
-      cFlags
-    );
-
-    CidrCompilerSwitches compilerSwitches = settings.getCompilerSwitches(OCLanguageKind.C, null);
-    List<String> commandLineArgs = compilerSwitches.getCommandLineArgs();
-    assertThat(commandLineArgs).containsExactly("-f\\ ast", "-slo\\ w");
-  }
-}
diff --git a/blaze-java/BUILD b/blaze-java/BUILD
deleted file mode 100644
index fa0a1e6..0000000
--- a/blaze-java/BUILD
+++ /dev/null
@@ -1,57 +0,0 @@
-package(default_visibility = ["//visibility:public"])
-
-java_library(
-    name = "blaze-java",
-    srcs = glob(["src/**/*.java"]),
-    deps = [
-        "//blaze-base",
-        "//blaze-base:proto-deps",
-        "//intellij-platform-sdk:plugin_api",
-        "//third_party:jsr305",
-    ],
-)
-
-filegroup(
-    name = "plugin_xml",
-    srcs = ["src/META-INF/blaze-java.xml"],
-)
-
-load(
-    "//intellij_test:test_defs.bzl",
-    "intellij_test",
-)
-
-intellij_test(
-    name = "unit_tests",
-    srcs = glob(["tests/unittests/**/*.java"]),
-    test_package_root = "com.google.idea.blaze.java",
-    deps = [
-        ":blaze-java",
-        "//blaze-base",
-        "//blaze-base:proto-deps",
-        "//blaze-base:unit_test_utils",
-        "//intellij-platform-sdk:plugin_api_for_tests",
-        "//intellij_test:lib",
-        "//third_party:jsr305",
-        "//third_party:test_lib",
-    ],
-)
-
-intellij_test(
-    name = "integration_tests",
-    srcs = glob(["tests/integrationtests/**/*.java"]),
-    integration_tests = True,
-    required_plugins = "com.google.idea.blaze.ijwb",
-    test_package_root = "com.google.idea.blaze.java",
-    deps = [
-        ":blaze-java",
-        "//blaze-base",
-        "//blaze-base:integration_test_utils",
-        "//blaze-base:unit_test_utils",
-        "//ijwb:ijwb_bazel",
-        "//intellij-platform-sdk:plugin_api_for_tests",
-        "//intellij_test:lib",
-        "//third_party:jsr305",
-        "//third_party:test_lib",
-    ],
-)
diff --git a/blaze-java/src/META-INF/blaze-java.xml b/blaze-java/src/META-INF/blaze-java.xml
deleted file mode 100644
index 132125d..0000000
--- a/blaze-java/src/META-INF/blaze-java.xml
+++ /dev/null
@@ -1,91 +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.
-  -->
-<idea-plugin>
-  <depends>com.intellij.modules.java</depends>
-  <depends>JUnit</depends>
-
-  <actions>
-    <action class="com.google.idea.blaze.java.libraries.ExcludeLibraryAction"
-            id="Blaze.ExcludeLibraryAction"
-            icon="BlazeIcons.Blaze"
-            text="Exclude Library and Resync">
-      <add-to-group group-id="Blaze.ProjectViewPopupMenu"/>
-    </action>
-    <action class="com.google.idea.blaze.java.libraries.AttachSourceJarAction"
-            id="Blaze.AttachSourceJarAction"
-            icon="BlazeIcons.Blaze"
-            text="Attach Source Jar">
-      <add-to-group group-id="Blaze.ProjectViewPopupMenu"/>
-    </action>
-
-    <!-- IntelliJ specific actions -->
-
-    <action id="Blaze.ImportProject2" class="com.google.idea.blaze.java.wizard2.BlazeImportProjectAction" icon="BlazeIcons.Blaze">
-      <add-to-group group-id="WelcomeScreen.QuickStart" />
-      <add-to-group group-id="OpenProjectGroup" relative-to-action="ImportProject" anchor="after"/>
-    </action>
-
-    <action id="Blaze.ImportProject" class="com.google.idea.blaze.java.wizard.BlazeImportNewJavaProjectAction" text="Import Blaze Project (old)..." icon="BlazeIcons.Blaze">
-      <add-to-group group-id="WelcomeScreen.QuickStart" />
-      <add-to-group group-id="OpenProjectGroup" relative-to-action="ImportProject" anchor="after"/>
-    </action>
-
-    <!-- End IntelliJ specific actions -->
-
-  </actions>
-
-  <extensions defaultExtensionNs="com.google.idea.blaze">
-    <SyncPlugin implementation="com.google.idea.blaze.java.sync.BlazeJavaSyncPlugin"/>
-    <PsiFileProvider implementation="com.google.idea.blaze.java.psi.JavaPsiFileProvider" />
-  </extensions>
-
-  <extensions defaultExtensionNs="com.intellij">
-    <runConfigurationProducer
-        implementation="com.google.idea.blaze.java.run.producers.BlazeJavaTestClassConfigurationProducer"
-        order="first"/>
-    <runConfigurationProducer
-        implementation="com.google.idea.blaze.java.run.producers.BlazeJavaTestMethodConfigurationProducer"
-        order="first"/>
-    <projectViewNodeDecorator implementation="com.google.idea.blaze.java.syncstatus.BlazeJavaSyncStatusClassNodeDecorator"/>
-    <editorTabColorProvider implementation="com.google.idea.blaze.java.syncstatus.BlazeJavaSyncStatusEditorTabColorProvider"/>
-    <editorTabTitleProvider implementation="com.google.idea.blaze.java.syncstatus.BlazeJavaSyncStatusEditorTabTitleProvider"/>
-    <applicationService serviceInterface="com.google.idea.blaze.java.sync.source.JavaSourcePackageReader"
-                        serviceImplementation="com.google.idea.blaze.java.sync.source.JavaSourcePackageReader"/>
-    <applicationService serviceInterface="com.google.idea.blaze.java.sync.source.PackageManifestReader"
-                        serviceImplementation="com.google.idea.blaze.java.sync.source.PackageManifestReader"/>
-    <runConfigurationProducer
-        implementation="com.google.idea.blaze.java.run.producers.AllInPackageBlazeConfigurationProducer"
-        order="first"/>
-    <configurationType implementation="com.google.idea.blaze.java.run.BlazeCommandRunConfigurationType"/>
-    <programRunner implementation="com.google.idea.blaze.java.run.BlazeCommandDebuggerRunner"/>
-    <projectService serviceInterface="com.google.idea.blaze.base.ui.BlazeProblemsView"
-                    serviceImplementation="com.google.idea.blaze.java.ui.BlazeIntelliJProblemsView"/>
-    <projectService serviceImplementation="com.google.idea.blaze.java.libraries.SourceJarManager"/>
-    <refactoring.safeDeleteProcessor id="build_file_safe_delete" order="before javaProcessor"
-                                     implementation="com.google.idea.blaze.java.lang.build.BuildFileSafeDeleteProcessor"/>
-
-    <!-- IntelliJ specific extension points -->
-    <projectImportProvider implementation="com.google.idea.blaze.java.wizard.BlazeNewProjectImportProvider"/>
-    <projectImportBuilder implementation="com.google.idea.blaze.java.wizard.BlazeNewJavaProjectImportBuilder"/>
-    <attachSourcesProvider implementation="com.google.idea.blaze.java.libraries.BlazeAttachSourceProvider"/>
-    <!-- End IntelliJ specific extension points -->
-  </extensions>
-
-  <extensionPoints>
-    <extensionPoint qualifiedName="com.google.idea.blaze.java.JavaSyncAugmenter"
-                    interface="com.google.idea.blaze.java.sync.BlazeJavaSyncAugmenter"/>
-  </extensionPoints>
-</idea-plugin>
diff --git a/blaze-java/src/com/google/idea/blaze/java/lang/build/BuildFileSafeDeleteProcessor.java b/blaze-java/src/com/google/idea/blaze/java/lang/build/BuildFileSafeDeleteProcessor.java
deleted file mode 100644
index 7fe5066..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/lang/build/BuildFileSafeDeleteProcessor.java
+++ /dev/null
@@ -1,150 +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.lang.build;
-
-import com.google.idea.blaze.base.lang.buildfile.references.GlobReference;
-import com.google.idea.blaze.base.lang.buildfile.search.BlazePackage;
-import com.google.idea.blaze.base.lang.buildfile.search.ResolveUtil;
-import com.intellij.openapi.module.Module;
-import com.intellij.openapi.project.Project;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiFileSystemItem;
-import com.intellij.refactoring.safeDelete.JavaSafeDeleteProcessor;
-import com.intellij.refactoring.safeDelete.NonCodeUsageSearchInfo;
-import com.intellij.refactoring.safeDelete.usageInfo.SafeDeleteUsageInfo;
-import com.intellij.usageView.UsageInfo;
-import com.intellij.util.IncorrectOperationException;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-
-/**
- * Removes glob references which don't refer directly to the item(s) being deleted (b/28979434)
- * (e.g. indirect references from glob([*.java])).<p>
- *
- * Runs before JavaSafeDeleteProcessor, and delegates to it, removing indirect glob references.
- * Only the first valid SafeDeleteProcessorDelegate is used in almost all cases*, so this class effectively
- * replaces JavaSafeDeleteProcessor (*in the situations where all processors are used, this class has no effect).
- */
-public class BuildFileSafeDeleteProcessor extends JavaSafeDeleteProcessor {
-
-  /**
-   * Delegates to JavaSafeDeleteProcessor, then removes indirect glob references which we don't want to block safe delete.
-   */
-  @Nullable
-  @Override
-  public NonCodeUsageSearchInfo findUsages(@NotNull PsiElement element,
-                                           @NotNull PsiElement[] allElementsToDelete,
-                                           @NotNull List<UsageInfo> result) {
-    NonCodeUsageSearchInfo superResult = super.findUsages(element, allElementsToDelete, result);
-    Iterator<UsageInfo> iter = result.iterator();
-    while (iter.hasNext()) {
-      if (ignoreUsage(iter.next())) {
-        iter.remove();
-      }
-    }
-    return superResult;
-  }
-
-  /**
-   * We keep globs which reference the file directly (i.e. without wildcards), and remove all
-   * indirect references for the purposes of the 'safe delete' action.
-   */
-  private static boolean ignoreUsage(UsageInfo usage) {
-    if (usage.getReference() instanceof GlobReference && usage instanceof SafeDeleteUsageInfo) {
-      PsiElement referencedElement = ((SafeDeleteUsageInfo) usage).getReferencedElement();
-      PsiFileSystemItem file = ResolveUtil.asFileSystemItemSearch(referencedElement);
-      String relativePath = getBlazePackageRelativePathToFile(file);
-      if (relativePath == null) {
-        return false;
-      }
-      return !((GlobReference) usage.getReference()).matchesDirectly(relativePath, file.isDirectory());
-    }
-    return false;
-  }
-
-  @Nullable
-  private static String getBlazePackageRelativePathToFile(@Nullable PsiFileSystemItem file) {
-    if (file == null) {
-      return null;
-    }
-    BlazePackage containingPackage = BlazePackage.getContainingPackage(file);
-    if (containingPackage == null) {
-      return null;
-    }
-    return containingPackage.getRelativePathToChild(file.getVirtualFile());
-  }
-
-  @Override
-  public boolean handlesElement(PsiElement element) {
-    return super.handlesElement(element);
-  }
-
-  @Nullable
-  @Override
-  public Collection<? extends PsiElement> getElementsToSearch(@NotNull PsiElement element,
-                                                              @Nullable Module module,
-                                                              @NotNull Collection<PsiElement> allElementsToDelete) {
-    return super.getElementsToSearch(element, module, allElementsToDelete);
-  }
-
-  @Nullable
-  @Override
-  public Collection<PsiElement> getAdditionalElementsToDelete(@NotNull PsiElement element,
-                                                              @NotNull Collection<PsiElement> allElementsToDelete,
-                                                              boolean askUser) {
-    return super.getAdditionalElementsToDelete(element, allElementsToDelete, askUser);
-  }
-
-  @Nullable
-  @Override
-  public Collection<String> findConflicts(@NotNull PsiElement element, @NotNull PsiElement[] allElementsToDelete) {
-    return super.findConflicts(element, allElementsToDelete);
-  }
-
-  @Nullable
-  @Override
-  public UsageInfo[] preprocessUsages(Project project, UsageInfo[] usages) {
-    return usages;
-  }
-
-  @Override
-  public void prepareForDeletion(PsiElement element) throws IncorrectOperationException {
-  }
-
-  @Override
-  public boolean isToSearchInComments(PsiElement element) {
-    return super.isToSearchInComments(element);
-  }
-
-  @Override
-  public void setToSearchInComments(PsiElement element, boolean enabled) {
-    super.setToSearchInComments(element, enabled);
-  }
-
-  @Override
-  public boolean isToSearchForTextOccurrences(PsiElement element) {
-    return super.isToSearchForTextOccurrences(element);
-  }
-
-  @Override
-  public void setToSearchForTextOccurrences(PsiElement element, boolean enabled) {
-    super.setToSearchForTextOccurrences(element, enabled);
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/libraries/AttachSourceJarAction.java b/blaze-java/src/com/google/idea/blaze/java/libraries/AttachSourceJarAction.java
deleted file mode 100644
index 143ba48..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/libraries/AttachSourceJarAction.java
+++ /dev/null
@@ -1,92 +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.libraries;
-
-import com.google.idea.blaze.base.actions.BlazeAction;
-import com.google.idea.blaze.base.ideinfo.LibraryArtifact;
-import com.google.idea.blaze.java.sync.model.BlazeLibrary;
-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;
-import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.roots.impl.libraries.ProjectLibraryTable;
-import com.intellij.openapi.roots.libraries.Library;
-import com.intellij.openapi.roots.libraries.LibraryTable;
-import com.intellij.openapi.ui.Messages;
-import org.jetbrains.annotations.NotNull;
-
-public class AttachSourceJarAction extends BlazeAction {
-  @Override
-  public void actionPerformed(AnActionEvent e) {
-    Project project = e.getProject();
-    assert project != null;
-    Library library = LibraryActionHelper.findLibraryForAction(e);
-    if (library != null) {
-      BlazeLibrary blazeLibrary = LibraryActionHelper.findLibraryFromIntellijLibrary(project, library);
-      if (blazeLibrary == null) {
-        Messages.showErrorDialog(project, "Could not find this library in the project.", CommonBundle.getErrorTitle());
-        return;
-      }
-
-      final LibraryArtifact libraryArtifact = blazeLibrary.getLibraryArtifact();
-      if (libraryArtifact == null) {
-        return;
-      }
-      if (libraryArtifact.sourceJar == null) {
-        return;
-      }
-      SourceJarManager sourceJarManager = SourceJarManager.getInstance(project);
-      boolean attachSourceJar = !sourceJarManager.hasSourceJarAttached(blazeLibrary.getKey());
-      sourceJarManager.setHasSourceJarAttached(blazeLibrary.getKey(), attachSourceJar);
-
-      ApplicationManager.getApplication().runWriteAction(() -> {
-        LibraryTable libraryTable = ProjectLibraryTable.getInstance(project);
-        LibraryTable.ModifiableModel libraryTableModel =
-          libraryTable.getModifiableModel();
-        LibraryEditor.updateLibrary(libraryTable, libraryTableModel, blazeLibrary, attachSourceJar);
-        libraryTableModel.commit();
-      });
-    }
-  }
-
-  @Override
-  protected void doUpdate(@NotNull AnActionEvent e) {
-    Presentation presentation = e.getPresentation();
-    String text = "Attach Source Jar";
-    boolean visible = false;
-    boolean enabled = false;
-    Project project = e.getProject();
-    if (project != null) {
-      Library library = LibraryActionHelper.findLibraryForAction(e);
-      if (library != null) {
-        visible = true;
-
-        BlazeLibrary blazeLibrary = LibraryActionHelper.findLibraryFromIntellijLibrary(e.getProject(), library);
-        if (blazeLibrary != null && blazeLibrary.getLibraryArtifact() != null && blazeLibrary.getLibraryArtifact().sourceJar != null) {
-          enabled = true;
-          if (SourceJarManager.getInstance(project).hasSourceJarAttached(blazeLibrary.getKey())) {
-            text = "Detach Source Jar";
-          }
-        }
-      }
-    }
-    presentation.setVisible(visible);
-    presentation.setEnabled(enabled);
-    presentation.setText(text);
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/libraries/BlazeAttachSourceProvider.java b/blaze-java/src/com/google/idea/blaze/java/libraries/BlazeAttachSourceProvider.java
deleted file mode 100644
index c9475a1..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/libraries/BlazeAttachSourceProvider.java
+++ /dev/null
@@ -1,134 +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.libraries;
-
-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.ideinfo.LibraryArtifact;
-import com.google.idea.blaze.base.model.BlazeProjectData;
-import com.google.idea.blaze.base.settings.BlazeUserSettings;
-import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
-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.diagnostic.Logger;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.roots.LibraryOrderEntry;
-import com.intellij.openapi.roots.impl.libraries.ProjectLibraryTable;
-import com.intellij.openapi.roots.libraries.Library;
-import com.intellij.openapi.roots.libraries.LibraryTable;
-import com.intellij.openapi.util.ActionCallback;
-import com.intellij.psi.PsiFile;
-import com.intellij.util.ui.UIUtil;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.Collection;
-import java.util.List;
-
-/**
- * @author Sergey Evdokimov
- */
-public class BlazeAttachSourceProvider implements AttachSourcesProvider {
-  private static final Logger LOG = Logger.getInstance(BlazeAttachSourceProvider.class);
-
-  @NotNull
-  @Override
-  public Collection<AttachSourcesAction> getActions(List<LibraryOrderEntry> orderEntries, final PsiFile psiFile) {
-    Project project = psiFile.getProject();
-    BlazeProjectData blazeProjectData = BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
-    if (blazeProjectData == null) {
-      return ImmutableList.of();
-    }
-
-    List<BlazeLibrary> librariesToAttachSourceTo = Lists.newArrayList();
-    for (LibraryOrderEntry orderEntry : orderEntries) {
-      Library library = orderEntry.getLibrary();
-      if (library == null) {
-        continue;
-      }
-      LibraryKey libraryKey = LibraryKey.fromIntelliJLibrary(library);
-      if (SourceJarManager.getInstance(project).hasSourceJarAttached(libraryKey)) {
-        continue;
-      }
-      BlazeLibrary blazeLibrary = LibraryActionHelper.findLibraryFromIntellijLibrary(project, library);
-      if (blazeLibrary == null) {
-        continue;
-      }
-      LibraryArtifact libraryArtifact = blazeLibrary.getLibraryArtifact();
-      if (libraryArtifact == null) {
-        continue;
-      }
-      ArtifactLocation artifactLocation = libraryArtifact.sourceJar;
-      if (artifactLocation == null) {
-        continue;
-      }
-      librariesToAttachSourceTo.add(blazeLibrary);
-    }
-
-    if (librariesToAttachSourceTo.isEmpty()) {
-      return ImmutableList.of();
-    }
-
-    /**
-     * Semi-hack: When sources are requested and we have them, we attach
-     * them automatically if the corresponding user setting is active.
-     */
-    if (BlazeUserSettings.getInstance().getAttachSourcesOnDemand()) {
-      UIUtil.invokeLaterIfNeeded(() -> {
-        attachSources(project, librariesToAttachSourceTo);
-      });
-      return ImmutableList.of();
-    }
-
-    return ImmutableList.of(new AttachSourcesAction() {
-      @Override
-      public String getName() {
-        return "Attach Blaze Source Jars";
-      }
-
-      @Override
-      public String getBusyText() {
-        return "Attaching source jars...";
-      }
-
-      @Override
-      public ActionCallback perform(List<LibraryOrderEntry> orderEntriesContainingFile) {
-        attachSources(project, librariesToAttachSourceTo);
-        return ActionCallback.DONE;
-      }
-    });
-  }
-
-  static void attachSources(Project project, Collection<BlazeLibrary> librariesToAttachSourceTo) {
-    ApplicationManager.getApplication().runWriteAction(() -> {
-      LibraryTable libraryTable = ProjectLibraryTable.getInstance(project);
-      LibraryTable.ModifiableModel libraryTableModel =
-        libraryTable.getModifiableModel();
-      for (BlazeLibrary blazeLibrary : librariesToAttachSourceTo) {
-        // Make sure we don't do it twice
-        if (SourceJarManager.getInstance(project).hasSourceJarAttached(blazeLibrary.getKey())) {
-          continue;
-        }
-        LibraryEditor.updateLibrary(libraryTable, libraryTableModel, blazeLibrary, true);
-        SourceJarManager.getInstance(project).setHasSourceJarAttached(blazeLibrary.getKey(), true);
-      }
-      libraryTableModel.commit();
-    });
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/libraries/ExcludeLibraryAction.java b/blaze-java/src/com/google/idea/blaze/java/libraries/ExcludeLibraryAction.java
deleted file mode 100644
index 330e811..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/libraries/ExcludeLibraryAction.java
+++ /dev/null
@@ -1,78 +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.libraries;
-
-import com.google.idea.blaze.base.actions.BlazeAction;
-import com.google.idea.blaze.base.ideinfo.LibraryArtifact;
-import com.google.idea.blaze.base.projectview.ProjectViewEdit;
-import com.google.idea.blaze.base.projectview.section.Glob;
-import com.google.idea.blaze.base.projectview.section.ListSection;
-import com.google.idea.blaze.base.sync.BlazeSyncManager;
-import com.google.idea.blaze.base.sync.BlazeSyncParams;
-import com.google.idea.blaze.java.projectview.ExcludeLibrarySection;
-import com.google.idea.blaze.java.sync.model.BlazeLibrary;
-import com.intellij.CommonBundle;
-import com.intellij.openapi.actionSystem.AnActionEvent;
-import com.intellij.openapi.actionSystem.Presentation;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.roots.libraries.Library;
-import com.intellij.openapi.ui.Messages;
-import org.jetbrains.annotations.NotNull;
-
-public class ExcludeLibraryAction extends BlazeAction {
-  @Override
-  public void actionPerformed(AnActionEvent e) {
-    Project project = e.getProject();
-    assert project != null;
-    Library library = LibraryActionHelper.findLibraryForAction(e);
-    if (library != null) {
-      BlazeLibrary blazeLibrary = LibraryActionHelper.findLibraryFromIntellijLibrary(project, library);
-      if (blazeLibrary == null) {
-        Messages.showErrorDialog(project, "Could not find this library in the project.", CommonBundle.getErrorTitle());
-        return;
-      }
-
-      final LibraryArtifact libraryArtifact = blazeLibrary.getLibraryArtifact();
-      if (libraryArtifact == null) {
-        return;
-      }
-
-      final String path = libraryArtifact.jar.getRelativePath();
-
-      ProjectViewEdit edit = ProjectViewEdit.editLocalProjectView(project, builder -> {
-        builder.put(ListSection
-          .update(ExcludeLibrarySection.KEY, builder.get(ExcludeLibrarySection.KEY))
-          .add(new Glob(path))
-        );
-        return true;
-      });
-      edit.apply();
-
-      BlazeSyncManager.getInstance(project).requestProjectSync(new BlazeSyncParams.Builder(
-        "Sync",
-        BlazeSyncParams.SyncMode.INCREMENTAL
-      ).setDoBuild(false).build());
-    }
-  }
-
-  @Override
-  protected void doUpdate(@NotNull AnActionEvent e) {
-    Presentation presentation = e.getPresentation();
-    boolean enabled = LibraryActionHelper.findLibraryForAction(e) != null;
-    presentation.setVisible(enabled);
-    presentation.setEnabled(enabled);
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/libraries/LibraryActionHelper.java b/blaze-java/src/com/google/idea/blaze/java/libraries/LibraryActionHelper.java
deleted file mode 100644
index ad52920..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/libraries/LibraryActionHelper.java
+++ /dev/null
@@ -1,81 +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.libraries;
-
-import com.google.idea.blaze.base.model.BlazeProjectData;
-import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
-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;
-import com.intellij.openapi.actionSystem.DataContext;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.roots.impl.libraries.ProjectLibraryTable;
-import com.intellij.openapi.roots.libraries.Library;
-import com.intellij.openapi.roots.libraries.LibraryTable;
-import com.intellij.openapi.ui.Messages;
-import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.pom.Navigatable;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-public class LibraryActionHelper {
-
-  static BlazeLibrary findLibraryFromIntellijLibrary(Project project, Library library) {
-    LibraryKey libraryKey = LibraryKey.fromIntelliJLibrary(library);
-    BlazeProjectData projectData = BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
-    if (projectData == null) {
-      return null;
-    }
-    BlazeJavaSyncData syncData = projectData.syncState.get(BlazeJavaSyncData.class);
-    if (syncData == null) {
-      Messages.showErrorDialog(project, "Project isn't synced. Please resync project.", "Error");
-      return null;
-    }
-
-    return syncData.importResult.libraries.get(libraryKey);
-  }
-
-  @Nullable
-  public static Library findLibraryForAction(@NotNull AnActionEvent e) {
-    Project project = e.getProject();
-    if (project != null) {
-      NamedLibraryElementNode node = findLibraryNode(e.getDataContext());
-      if (node != null) {
-        String libraryName = node.getName();
-        if (StringUtil.isNotEmpty(libraryName)) {
-          LibraryTable libraryTable = ProjectLibraryTable.getInstance(project);
-          return libraryTable.getLibraryByName(libraryName);
-        }
-      }
-    }
-    return null;
-  }
-
-  @Nullable
-  private static NamedLibraryElementNode findLibraryNode(@NotNull DataContext dataContext) {
-    Navigatable[] navigatables = CommonDataKeys.NAVIGATABLE_ARRAY.getData(dataContext);
-    if (navigatables != null && navigatables.length == 1) {
-      Navigatable navigatable = navigatables[0];
-      if (navigatable instanceof NamedLibraryElementNode) {
-        return (NamedLibraryElementNode)navigatable;
-      }
-    }
-    return null;
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/libraries/SourceJarManager.java b/blaze-java/src/com/google/idea/blaze/java/libraries/SourceJarManager.java
deleted file mode 100644
index 17bc762..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/libraries/SourceJarManager.java
+++ /dev/null
@@ -1,69 +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.libraries;
-
-import com.google.common.collect.Sets;
-import com.google.idea.blaze.java.sync.model.LibraryKey;
-import com.intellij.openapi.components.*;
-import com.intellij.openapi.project.Project;
-import org.jdom.Element;
-
-import java.util.Set;
-
-/**
- * Keeps track of which libraries have source jars attached.
- */
-@State(name = "BlazeSourceJarManager", storages = @Storage(StoragePathMacros.WORKSPACE_FILE))
-public class SourceJarManager implements PersistentStateComponent<Element> {
-  private Set<LibraryKey> librariesWithSourceJarsAttached = Sets.newHashSet();
-
-  public static SourceJarManager getInstance(Project project) {
-    return ServiceManager.getService(project, SourceJarManager.class);
-  }
-
-  public boolean hasSourceJarAttached(LibraryKey libraryKey) {
-    return librariesWithSourceJarsAttached.contains(libraryKey);
-  }
-
-  public void setHasSourceJarAttached(LibraryKey libraryKey, boolean hasSourceJar) {
-    if (hasSourceJar) {
-      librariesWithSourceJarsAttached.add(libraryKey);
-    } else {
-      librariesWithSourceJarsAttached.remove(libraryKey);
-    }
-  }
-
-  @Override
-  public Element getState() {
-    Element element = new Element("state");
-    for (LibraryKey libraryKey : librariesWithSourceJarsAttached) {
-      Element libElement = new Element("library");
-      libElement.setText(libraryKey.getIntelliJLibraryName());
-      element.addContent(libElement);
-    }
-    return element;
-  }
-
-  @Override
-  public void loadState(Element state) {
-    Set<LibraryKey> librariesWithSourceJars = Sets.newHashSet();
-    for (Element libElement : state.getChildren()) {
-      LibraryKey libraryKey = LibraryKey.fromIntelliJLibraryName(libElement.getText());
-      librariesWithSourceJars.add(libraryKey);
-    }
-    this.librariesWithSourceJarsAttached = librariesWithSourceJars;
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/projectview/ExcludeLibrarySection.java b/blaze-java/src/com/google/idea/blaze/java/projectview/ExcludeLibrarySection.java
deleted file mode 100644
index 5323201..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/projectview/ExcludeLibrarySection.java
+++ /dev/null
@@ -1,26 +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.projectview;
-
-import com.google.idea.blaze.base.projectview.section.*;
-
-/**
- * Section for excluding libraries.
- */
-public class ExcludeLibrarySection {
-  public static final SectionKey<Glob, ListSection<Glob>> KEY = SectionKey.of("exclude_library");
-  public static final SectionParser PARSER = new GlobSectionParser(KEY);
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/projectview/ExcludedLibrarySection.java b/blaze-java/src/com/google/idea/blaze/java/projectview/ExcludedLibrarySection.java
deleted file mode 100644
index 5988d3b..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/projectview/ExcludedLibrarySection.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.java.projectview;
-
-import com.google.idea.blaze.base.projectview.section.*;
-
-/**
- * Section for excluding libraries.
- */
-@Deprecated
-public class ExcludedLibrarySection {
-  public static final SectionKey<Glob, ListSection<Glob>> KEY = SectionKey.of("excluded_libraries");
-  public static final SectionParser PARSER = new GlobSectionParser(KEY) {
-    @Override
-    public boolean isDeprecated() {
-      return true;
-    }
-  };
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/projectview/JavaLanguageLevelSection.java b/blaze-java/src/com/google/idea/blaze/java/projectview/JavaLanguageLevelSection.java
deleted file mode 100644
index b94b60d..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/projectview/JavaLanguageLevelSection.java
+++ /dev/null
@@ -1,95 +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.projectview;
-
-import com.google.idea.blaze.base.projectview.ProjectViewSet;
-import com.google.idea.blaze.base.projectview.parser.ParseContext;
-import com.google.idea.blaze.base.projectview.parser.ProjectViewParser;
-import com.google.idea.blaze.base.projectview.section.ScalarSection;
-import com.google.idea.blaze.base.projectview.section.ScalarSectionParser;
-import com.google.idea.blaze.base.projectview.section.SectionKey;
-import com.google.idea.blaze.base.projectview.section.SectionParser;
-import com.intellij.pom.java.LanguageLevel;
-
-import javax.annotation.Nullable;
-
-public class JavaLanguageLevelSection {
-  public static final SectionKey<Integer, ScalarSection<Integer>> KEY =
-    SectionKey.of("java_language_level");
-  public static final SectionParser PARSER = new JavaLanguageLevelParser();
-
-  public static LanguageLevel getLanguageLevel(ProjectViewSet projectViewSet, LanguageLevel defaultValue) {
-    Integer level = projectViewSet.getSectionValue(KEY, null);
-    if (level == null) {
-      return defaultValue;
-    }
-    return getLanguageLevel(level, defaultValue);
-  }
-
-  @Nullable
-  private static LanguageLevel getLanguageLevel(Integer level, @Nullable LanguageLevel defaultValue) {
-    switch (level) {
-      case 3:
-        return LanguageLevel.JDK_1_3;
-      case 4:
-        return LanguageLevel.JDK_1_4;
-      case 5:
-        return LanguageLevel.JDK_1_5;
-      case 6:
-        return LanguageLevel.JDK_1_6;
-      case 7:
-        return LanguageLevel.JDK_1_7;
-      case 8:
-        return LanguageLevel.JDK_1_8;
-      case 9:
-        return LanguageLevel.JDK_1_9;
-      default:
-        return defaultValue;
-    }
-  }
-
-  private static class JavaLanguageLevelParser extends ScalarSectionParser<Integer> {
-    public JavaLanguageLevelParser() {
-      super(KEY, ':');
-    }
-
-    @Nullable
-    @Override
-    protected Integer parseItem(ProjectViewParser parser, ParseContext parseContext, String rest) {
-      try {
-        Integer value = Integer.parseInt(rest);
-        if (getLanguageLevel(value, null) != null) {
-          return value;
-        }
-        // Fall through to error handler
-      } catch (NumberFormatException e) {
-        // Fall through to error handler
-      }
-      parseContext.addError("Illegal java language level: " + rest);
-      return null;
-    }
-
-    @Override
-    protected void printItem(StringBuilder sb, Integer value) {
-      sb.append(value.toString());
-    }
-
-    @Override
-    public ItemType getItemType() {
-      return ItemType.Other;
-    }
-  }
-}
\ No newline at end of file
diff --git a/blaze-java/src/com/google/idea/blaze/java/psi/JavaPsiFileProvider.java b/blaze-java/src/com/google/idea/blaze/java/psi/JavaPsiFileProvider.java
deleted file mode 100644
index 1844300..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/psi/JavaPsiFileProvider.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.java.psi;
-
-import com.google.idea.blaze.base.lang.buildfile.search.PsiFileProvider;
-import com.intellij.psi.PsiClass;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiFile;
-
-import javax.annotation.Nullable;
-
-/**
- * Replaces top-level java classes with their corresponding PsiFile
- */
-public class JavaPsiFileProvider implements PsiFileProvider {
-
-  @Nullable
-  @Override
-  public PsiFile asFileSearch(PsiElement elementToSearch) {
-    if (elementToSearch instanceof PsiClass) {
-      elementToSearch = elementToSearch.getParent();
-    }
-    return elementToSearch instanceof PsiFile ? (PsiFile) elementToSearch : null;
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/run/BlazeCommandDebuggerRunner.java b/blaze-java/src/com/google/idea/blaze/java/run/BlazeCommandDebuggerRunner.java
deleted file mode 100644
index cb68930..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/run/BlazeCommandDebuggerRunner.java
+++ /dev/null
@@ -1,61 +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.run;
-
-import com.intellij.debugger.impl.GenericDebuggerRunner;
-import com.intellij.execution.ExecutionException;
-import com.intellij.execution.configurations.*;
-import com.intellij.execution.executors.DefaultDebugExecutor;
-import com.intellij.execution.runners.ExecutionEnvironment;
-import com.intellij.execution.ui.RunContentDescriptor;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * A runner that adapts the GenericDebuggerRunner to work with Blaze run configurations.
- */
-public class BlazeCommandDebuggerRunner extends GenericDebuggerRunner {
-  @Override
-  @NotNull
-  public String getRunnerId() {
-    return "Blaze-Debug";
-  }
-
-  @Override
-  public boolean canRun(@NotNull final String executorId, @NotNull final RunProfile profile) {
-    return executorId.equals(DefaultDebugExecutor.EXECUTOR_ID)
-           && profile instanceof BlazeCommandRunConfiguration;
-  }
-
-  @Override
-  public void patch(JavaParameters javaParameters, RunnerSettings runnerSettings,
-                    RunProfile runProfile, final boolean beforeExecution) {
-    // We don't want to support Java run configuration patching.
-  }
-
-  @Override
-  @Nullable
-  public RunContentDescriptor createContentDescriptor(
-    @NotNull RunProfileState state, @NotNull ExecutionEnvironment environment)
-    throws ExecutionException {
-    if (!(state instanceof BlazeCommandRunProfileState)) {
-      return null;
-    }
-    BlazeCommandRunProfileState blazeState = (BlazeCommandRunProfileState)state;
-    RemoteConnection connection = blazeState.getRemoteConnection();
-    return attachVirtualMachine(state, environment, connection, true /* pollConnection */);
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/run/BlazeCommandRunConfiguration.java b/blaze-java/src/com/google/idea/blaze/java/run/BlazeCommandRunConfiguration.java
deleted file mode 100644
index bf97eea..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/run/BlazeCommandRunConfiguration.java
+++ /dev/null
@@ -1,281 +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.run;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableList;
-import com.google.idea.blaze.base.command.BlazeCommandName;
-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.BlazeRunConfiguration;
-import com.google.idea.blaze.base.settings.Blaze;
-import com.google.idea.blaze.base.ui.UiUtil;
-import com.intellij.execution.ExecutionException;
-import com.intellij.execution.Executor;
-import com.intellij.execution.configurations.ConfigurationFactory;
-import com.intellij.execution.configurations.LocatableConfigurationBase;
-import com.intellij.execution.configurations.RuntimeConfigurationError;
-import com.intellij.execution.configurations.RuntimeConfigurationException;
-import com.intellij.execution.executors.DefaultDebugExecutor;
-import com.intellij.execution.runners.ExecutionEnvironment;
-import com.intellij.openapi.options.ConfigurationException;
-import com.intellij.openapi.options.SettingsEditor;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.ui.ComboBox;
-import com.intellij.openapi.util.InvalidDataException;
-import com.intellij.openapi.util.WriteExternalException;
-import com.intellij.util.execution.ParametersListUtil;
-import org.jdom.Element;
-
-import javax.annotation.Nullable;
-import javax.swing.*;
-import java.util.List;
-
-/**
- * A run configuration which executes Blaze commands.
- */
-public class BlazeCommandRunConfiguration extends LocatableConfigurationBase implements BlazeRunConfiguration {
-  private static final String TARGET_TAG = "blaze-target";
-  private static final String COMMAND_ATTR = "blaze-command";
-  private static final String USER_BLAZE_FLAG_TAG = "blaze-user-flag";
-  private static final String USER_EXE_FLAG_TAG = "blaze-user-exe-flag";
-
-  protected final String buildSystemName;
-
-  @Nullable private TargetExpression target;
-  @Nullable private BlazeCommandName command;
-  private ImmutableList<String> blazeFlags = ImmutableList.of();
-  private ImmutableList<String> exeFlags = ImmutableList.of();
-
-  public BlazeCommandRunConfiguration(Project project,
-                                      ConfigurationFactory factory,
-                                      String name) {
-    super(project, factory, name);
-    buildSystemName = Blaze.buildSystemName(project);
-  }
-
-  @Override
-  @Nullable
-  public TargetExpression getTarget() {
-    return target;
-  }
-
-  @Nullable
-  public BlazeCommandName getCommand() {
-    return command;
-  }
-
-  /**
-   * @return The list of blaze flags that the user specified manually.
-   */
-  protected List<String> getBlazeFlags() {
-    return blazeFlags;
-  }
-
-  /**
-   * @return The list of executable flags the user specified manually.
-   */
-  protected List<String> getExeFlags() {
-    return exeFlags;
-  }
-
-  /**
-   * @return The list of all flags to be used on the Blaze command line for blaze. Subclasses should override this method to add flags for
-   * higher-level settings (e.g. "run locally").
-   */
-  public List<String> getAllBlazeFlags() {
-    return getBlazeFlags();
-  }
-
-  /**
-   * @return The list of all flags to be used for the executable on the Blaze command line. Subclasses should override this method to add
-   * flags if desired.
-   */
-  public List<String> getAllExeFlags() {
-    return getExeFlags();
-  }
-
-  public void setTarget(@Nullable TargetExpression target) {
-    this.target = target;
-  }
-
-  public void setCommand(@Nullable BlazeCommandName command) {
-    this.command = command;
-  }
-
-  public final void setBlazeFlags(List<String> flags) {
-    this.blazeFlags = ImmutableList.copyOf(flags);
-  }
-
-  public final void setExeFlags(List<String> flags) {
-    this.exeFlags = ImmutableList.copyOf(flags);
-  }
-
-  @Override
-  public void checkConfiguration() throws RuntimeConfigurationException {
-    if (target == null) {
-      throw new RuntimeConfigurationError(String.format("You must specify a %s target expression.", buildSystemName));
-    }
-    if (command == null) {
-      throw new RuntimeConfigurationError(String.format("You must specify a command.", buildSystemName));
-    }
-  }
-
-  @Override
-  public void readExternal(Element element) throws InvalidDataException {
-    super.readExternal(element);
-    // Target is persisted as a tag to permit multiple targets in the future.
-    Element targetElement = element.getChild(TARGET_TAG);
-    if (targetElement != null && !Strings.isNullOrEmpty(targetElement.getTextTrim())) {
-      target = TargetExpression.fromString(targetElement.getTextTrim());
-    }
-    else {
-      target = null;
-    }
-    String commandString = element.getAttributeValue(COMMAND_ATTR);
-    command = Strings.isNullOrEmpty(commandString) ?
-              null : BlazeCommandName.fromString(commandString);
-    blazeFlags = loadUserFlags(element, USER_BLAZE_FLAG_TAG);
-    exeFlags = loadUserFlags(element, USER_EXE_FLAG_TAG);
-  }
-
-  private static ImmutableList<String> loadUserFlags(Element root, String tag) {
-    ImmutableList.Builder<String> flagsBuilder = ImmutableList.builder();
-    for (Element e : root.getChildren(tag)) {
-      String flag = e.getTextTrim();
-      if (flag != null && !flag.isEmpty()) {
-        flagsBuilder.add(flag);
-      }
-    }
-    return flagsBuilder.build();
-  }
-
-  @Override
-  public void writeExternal(Element element) throws WriteExternalException {
-    super.writeExternal(element);
-    if (target != null) {
-      // Target is persisted as a tag to permit multiple targets in the future.
-      Element targetElement = new Element(TARGET_TAG);
-      targetElement.setText(target.toString());
-      element.addContent(targetElement);
-    }
-    if (command != null) {
-      element.setAttribute(COMMAND_ATTR, command.toString());
-    }
-    saveUserFlags(element, blazeFlags, USER_BLAZE_FLAG_TAG);
-    saveUserFlags(element, exeFlags, USER_EXE_FLAG_TAG);
-  }
-
-  private static void saveUserFlags(Element root, List<String> flags, String tag) {
-    for (String flag : flags) {
-      Element child = new Element(tag);
-      child.setText(flag);
-      root.addContent(child);
-    }
-  }
-
-  @Override
-  public BlazeCommandRunConfiguration clone() {
-    final BlazeCommandRunConfiguration configuration = (BlazeCommandRunConfiguration)super.clone();
-    configuration.target = target;
-    configuration.command = command;
-    configuration.blazeFlags = blazeFlags;
-    configuration.exeFlags = exeFlags;
-    return configuration;
-  }
-
-  @Override
-  @Nullable
-  public String suggestedName() {
-    TargetExpression target = getTarget();
-    if (!(target instanceof Label)) {
-      return null;
-    }
-    return String.format("%s %s: %s", buildSystemName, command.toString(), ((Label)target).ruleName().toString());
-  }
-
-
-  @Nullable
-  @Override
-  public BlazeCommandRunProfileState getState(Executor executor,
-                                              ExecutionEnvironment environment) throws ExecutionException {
-    return new BlazeCommandRunProfileState(environment, executor instanceof DefaultDebugExecutor);
-  }
-
-  @Override
-  public SettingsEditor<? extends BlazeCommandRunConfiguration> getConfigurationEditor() {
-    return new BlazeCommandRunConfigurationSettingsEditor(buildSystemName);
-  }
-
-  @VisibleForTesting
-  static class BlazeCommandRunConfigurationSettingsEditor extends SettingsEditor<BlazeCommandRunConfiguration> {
-    private final String buildSystemName;
-    private final JTextField targetField = new JTextField();
-    private final ComboBox commandCombo;
-    private final JTextArea blazeFlagsField = new JTextArea(5, 0);
-    private final JTextArea exeFlagsField = new JTextArea(5, 0);
-
-    public BlazeCommandRunConfigurationSettingsEditor(String buildSystemName) {
-      this.buildSystemName = buildSystemName;
-      commandCombo = new ComboBox(
-        new DefaultComboBoxModel(BlazeCommandName.knownCommands().toArray()));
-      // Allow the user to manually specify an unlisted command.
-      commandCombo.setEditable(true);
-    }
-
-    @VisibleForTesting
-    @Override
-    public void resetEditorFrom(BlazeCommandRunConfiguration s) {
-      targetField.setText(s.target == null ? null : s.target.toString());
-      commandCombo.setSelectedItem(s.command);
-      blazeFlagsField.setText(ParametersListUtil.join(s.blazeFlags));
-      exeFlagsField.setText(ParametersListUtil.join(s.exeFlags));
-    }
-
-    @VisibleForTesting
-    @Override
-    public void applyEditorTo(BlazeCommandRunConfiguration s) throws ConfigurationException {
-      String targetString = targetField.getText();
-      s.target = Strings.isNullOrEmpty(targetString) ?
-                 null : TargetExpression.fromString(targetString);
-      Object selectedCommand = commandCombo.getSelectedItem();
-      if (selectedCommand instanceof BlazeCommandName) {
-        s.command = (BlazeCommandName)selectedCommand;
-      }
-      else {
-        s.command = Strings.isNullOrEmpty((String)selectedCommand) ?
-                    null : BlazeCommandName.fromString(selectedCommand.toString());
-      }
-      s.blazeFlags = ImmutableList.copyOf(ParametersListUtil.parse(Strings.nullToEmpty(blazeFlagsField.getText())));
-      s.exeFlags = ImmutableList.copyOf(ParametersListUtil.parse(Strings.nullToEmpty(exeFlagsField.getText())));
-    }
-
-    @Override
-    protected JComponent createEditor() {
-      return UiUtil.createBox(
-        new JLabel("Target expression:"),
-        targetField,
-        new JLabel(buildSystemName + " command:"),
-        commandCombo,
-        new JLabel(buildSystemName +" flags:"),
-        blazeFlagsField,
-        new JLabel("Executable flags:"),
-        exeFlagsField
-      );
-    }
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/run/BlazeCommandRunConfigurationType.java b/blaze-java/src/com/google/idea/blaze/java/run/BlazeCommandRunConfigurationType.java
deleted file mode 100644
index c3705a3..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/run/BlazeCommandRunConfigurationType.java
+++ /dev/null
@@ -1,107 +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.run;
-
-
-import com.google.idea.blaze.base.settings.Blaze;
-import com.intellij.execution.BeforeRunTask;
-import com.intellij.execution.configurations.ConfigurationFactory;
-import com.intellij.execution.configurations.ConfigurationType;
-import com.intellij.execution.configurations.ConfigurationTypeUtil;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.Key;
-import icons.BlazeIcons;
-import org.jetbrains.annotations.NotNull;
-
-import javax.swing.*;
-
-/**
- * A type for run configurations that execute Blaze commands.
- */
-public class BlazeCommandRunConfigurationType implements ConfigurationType {
-  @NotNull
-  private final BlazeCommandRunConfigurationFactory factory =
-    new BlazeCommandRunConfigurationFactory(this);
-
-  public static class BlazeCommandRunConfigurationFactory extends ConfigurationFactory {
-    protected BlazeCommandRunConfigurationFactory(@NotNull ConfigurationType type) {
-      super(type);
-    }
-
-    @Override
-    public boolean isApplicable(@NotNull Project project) {
-      return Blaze.isBlazeProject(project);
-    }
-
-    @Override
-    public BlazeCommandRunConfiguration createTemplateConfiguration(Project project) {
-      return new BlazeCommandRunConfiguration(project, this, "Unnamed");
-    }
-
-    @Override
-    public void configureBeforeRunTaskDefaults(
-      Key<? extends BeforeRunTask> providerID, BeforeRunTask task) {
-      // We don't need or want any before run tasks by default.
-      task.setEnabled(false);
-    }
-
-    @Override
-    public boolean isConfigurationSingletonByDefault() {
-      return true;
-    }
-  }
-
-  @NotNull
-  public static BlazeCommandRunConfigurationType getInstance() {
-    return ConfigurationTypeUtil.findConfigurationType(BlazeCommandRunConfigurationType.class);
-  }
-
-  @NotNull
-  @Override
-  public String getDisplayName() {
-    return Blaze.defaultBuildSystemName() + " Command";
-  }
-
-  @NotNull
-  @Override
-  public String getConfigurationTypeDescription() {
-    return String.format("Configuration for launching arbitrary %s commands.", Blaze.guessBuildSystemName());
-  }
-
-  @NotNull
-  @Override
-  public Icon getIcon() {
-    return BlazeIcons.Blaze;
-  }
-
-  @NotNull
-  @Override
-  public String getId() {
-    return "BlazeCommandRunConfigurationType";
-  }
-
-  @NotNull
-  @Override
-  public BlazeCommandRunConfigurationFactory[] getConfigurationFactories() {
-    return new BlazeCommandRunConfigurationFactory[]{factory};
-  }
-
-  @NotNull
-  public BlazeCommandRunConfigurationFactory getFactory() {
-    return factory;
-  }
-
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/run/BlazeCommandRunProfileState.java b/blaze-java/src/com/google/idea/blaze/java/run/BlazeCommandRunProfileState.java
deleted file mode 100644
index 93bef0b..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/run/BlazeCommandRunProfileState.java
+++ /dev/null
@@ -1,157 +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.run;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.ImmutableList;
-import com.google.idea.blaze.base.async.process.LineProcessingOutputStream;
-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.RuleIdeInfo;
-import com.google.idea.blaze.base.issueparser.IssueOutputLineProcessor;
-import com.google.idea.blaze.base.metrics.Action;
-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.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.run.rulefinder.RuleFinder;
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.google.idea.blaze.base.run.processhandler.LineProcessingProcessAdapter;
-import com.google.idea.blaze.base.run.processhandler.ScopedBlazeProcessHandler;
-import com.google.idea.blaze.base.scope.scopes.IssuesScope;
-import com.google.idea.blaze.base.scope.scopes.LoggedTimingScope;
-import com.google.idea.blaze.base.settings.Blaze;
-import com.google.idea.blaze.base.settings.BlazeImportSettings;
-import com.google.idea.blaze.base.settings.BlazeImportSettingsManager;
-import com.intellij.execution.ExecutionException;
-import com.intellij.execution.configurations.CommandLineState;
-import com.intellij.execution.configurations.RemoteConnection;
-import com.intellij.execution.configurations.RemoteState;
-import com.intellij.execution.configurations.RunProfile;
-import com.intellij.execution.process.ProcessHandler;
-import com.intellij.execution.process.ProcessListener;
-import com.intellij.execution.runners.ExecutionEnvironment;
-import com.intellij.openapi.project.Project;
-
-/**
- * A Blaze run configuration set up with a an executor, program runner, and other settings, ready to
- * be executed. This class creates a command line for Blaze and exposes debug connection information
- * when using a debug executor.
- */
-final class BlazeCommandRunProfileState extends CommandLineState implements RemoteState {
-  // Blaze seems to always use this port for --java_debug.
-  // TODO(joshgiles): Look at manually identifying and setting port.
-  private static final int DEBUG_PORT = 5005;
-  private static final String DEBUG_HOST_NAME = "localhost";
-
-  private final BlazeCommandRunConfiguration configuration;
-  private final boolean debug;
-
-  public BlazeCommandRunProfileState(ExecutionEnvironment environment, boolean debug) {
-    super(environment);
-    RunProfile runProfile = environment.getRunProfile();
-    assert runProfile instanceof BlazeCommandRunConfiguration;
-    configuration = (BlazeCommandRunConfiguration)runProfile;
-    this.debug = debug;
-  }
-
-  @Override
-  protected ProcessHandler startProcess() throws ExecutionException {
-    Project project = configuration.getProject();
-    BlazeImportSettings importSettings =
-      BlazeImportSettingsManager.getInstance(project).getImportSettings();
-    assert importSettings != null;
-
-    ProjectViewSet projectViewSet = ProjectViewManager.getInstance(project).getProjectViewSet();
-    assert projectViewSet != null;
-
-    BlazeCommand blazeCommand = getBlazeCommand(project, configuration, projectViewSet, debug);
-    WorkspaceRoot workspaceRoot = WorkspaceRoot.fromImportSettings(importSettings);
-    return new ScopedBlazeProcessHandler(
-      blazeCommand,
-      workspaceRoot,
-      new ScopedBlazeProcessHandler.ScopedProcessHandlerDelegate() {
-        @Override
-        public void onBlazeContextStart(BlazeContext context) {
-          context
-            .push(new LoggedTimingScope(project, Action.BLAZE_COMMAND_USAGE))
-            .push(new IssuesScope(project))
-          ;
-        }
-
-        @Override
-        public ImmutableList<ProcessListener> createProcessListeners(BlazeContext context) {
-          LineProcessingOutputStream outputStream = LineProcessingOutputStream.of(
-            new IssueOutputLineProcessor(project, context, workspaceRoot)
-          );
-          return ImmutableList.of(new LineProcessingProcessAdapter(outputStream));
-        }
-      }
-    );
-  }
-
-  @Override
-  public RemoteConnection getRemoteConnection() {
-    if (!debug) {
-      return null;
-    }
-    return new RemoteConnection(true /* useSockets */,
-                                DEBUG_HOST_NAME,
-                                Integer.toString(DEBUG_PORT),
-                                false /* serverMode */
-    );
-  }
-
-  @VisibleForTesting
-  static BlazeCommand getBlazeCommand(
-    Project project,
-    BlazeCommandRunConfiguration configuration,
-    ProjectViewSet projectViewSet,
-    boolean debug) {
-
-    BlazeCommandName blazeCommand = configuration.getCommand();
-    assert blazeCommand != null;
-    BlazeCommand.Builder command = BlazeCommand.builder(Blaze.getBuildSystem(project), blazeCommand)
-      .addTargets(configuration.getTarget())
-      .addBlazeFlags(BlazeFlags.buildFlags(project, projectViewSet))
-      .addBlazeFlags(configuration.getAllBlazeFlags())
-      ;
-
-    if (debug) {
-      boolean isJavaBinary = false;
-      TargetExpression targetExpression = configuration.getTarget();
-      if (targetExpression instanceof Label) {
-        RuleIdeInfo rule = RuleFinder.getInstance().ruleForTarget(configuration.getProject(), (Label)targetExpression);
-        if (rule != null && (rule.kind == Kind.JAVA_BINARY)) {
-          isJavaBinary = true;
-        }
-      }
-
-      if (isJavaBinary) {
-        command.addExeFlags(BlazeFlags.JAVA_BINARY_DEBUG);
-      } else {
-        command.addBlazeFlags(BlazeFlags.JAVA_TEST_DEBUG);
-      }
-
-    }
-
-    command.addExeFlags(configuration.getAllExeFlags());
-    return command.build();
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/run/RunUtil.java b/blaze-java/src/com/google/idea/blaze/java/run/RunUtil.java
deleted file mode 100644
index 963245e..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/run/RunUtil.java
+++ /dev/null
@@ -1,82 +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.run;
-
-import com.google.common.collect.Iterables;
-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.google.idea.blaze.base.run.TestRuleFinder;
-import com.google.idea.blaze.base.run.rulefinder.RuleFinder;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.psi.PsiClass;
-import com.intellij.psi.PsiFile;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.io.File;
-
-/**
- * Utility methods for finding rules and Android facets.
- */
-public final class RunUtil {
-
-  private RunUtil() {
-  }
-
-  /**
-   * @return The Blaze test rule containing the target test class. In the case of multiple
-   * containing rules, the first rule sorted alphabetically by label.
-   */
-  @Nullable
-  public static RuleIdeInfo ruleForTestClass(@NotNull Project project,
-                                             @NotNull PsiClass testClass,
-                                             @Nullable TestIdeInfo.TestSize testSize) {
-    File testFile = getFileForClass(testClass);
-    if (testFile == null) {
-      return null;
-    }
-    TestRuleFinder testRuleFinder = TestRuleFinder.getInstance(project);
-    // We don't expose multiple rule choices, just pick first
-    Label testLabel = Iterables.getFirst(testRuleFinder.testTargetsForSourceFile(testFile, testSize), null);
-    if (testLabel == null) {
-      return null;
-    }
-    return RuleFinder.getInstance().ruleForTarget(project, testLabel);
-  }
-
-  /**
-   * Returns an instance of {@link java.io.File} related to the containing file of the given class.
-   * It returns {@code null} if the given class is not contained in a file and only exists in
-   * memory.
-   */
-  @Nullable
-  private static File getFileForClass(@NotNull PsiClass aClass) {
-    PsiFile containingFile = aClass.getContainingFile();
-    if (containingFile == null) {
-      return null;
-    }
-
-    VirtualFile virtualFile = containingFile.getVirtualFile();
-    if (virtualFile == null) {
-      return null;
-    }
-
-    return new File(virtualFile.getPath());
-  }
-
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/run/producers/AllInPackageBlazeConfigurationProducer.java b/blaze-java/src/com/google/idea/blaze/java/run/producers/AllInPackageBlazeConfigurationProducer.java
deleted file mode 100644
index 0dccb0f..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/run/producers/AllInPackageBlazeConfigurationProducer.java
+++ /dev/null
@@ -1,116 +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.run.producers;
-
-import com.google.idea.blaze.base.command.BlazeCommandName;
-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.settings.Blaze;
-import com.google.idea.blaze.java.run.BlazeCommandRunConfiguration;
-import com.google.idea.blaze.java.run.BlazeCommandRunConfigurationType;
-import com.intellij.execution.ExecutionBundle;
-import com.intellij.execution.actions.ConfigurationContext;
-import com.intellij.openapi.util.Ref;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.psi.PsiDirectory;
-import com.intellij.psi.PsiElement;
-
-import javax.annotation.Nullable;
-
-/**
- * Runs tests in all packages below selected directory
- */
-public class AllInPackageBlazeConfigurationProducer extends BlazeTestRunConfigurationProducer<BlazeCommandRunConfiguration> {
-
-  public AllInPackageBlazeConfigurationProducer() {
-    super(BlazeCommandRunConfigurationType.getInstance());
-  }
-
-  @Override
-  protected boolean doSetupConfigFromContext(
-    BlazeCommandRunConfiguration configuration,
-    ConfigurationContext context,
-    Ref<PsiElement> sourceElement) {
-
-    PsiDirectory dir = getTestDirectory(context);
-    if (dir == null) {
-      return false;
-    }
-    WorkspaceRoot root = WorkspaceRoot.fromProject(context.getModule().getProject());
-    WorkspacePath packagePath = getWorkspaceRelativeDirectoryPath(root, dir);
-    if (packagePath == null) {
-      return false;
-    }
-    sourceElement.set(dir);
-
-    configuration.setCommand(BlazeCommandName.TEST);
-    configuration.setTarget(TargetExpression.allFromPackageRecursive(packagePath));
-    configuration.setName(
-      String.format("%s %s",
-                    Blaze.buildSystemName(context.getProject()),
-                    ExecutionBundle.message("test.in.scope.presentable.text", packagePath)));
-    return true;
-  }
-
-  @Override
-  protected boolean doIsConfigFromContext(
-    BlazeCommandRunConfiguration configuration,
-    ConfigurationContext context) {
-
-    PsiDirectory dir = getTestDirectory(context);
-    if (dir == null) {
-      return false;
-    }
-    WorkspaceRoot root = WorkspaceRoot.fromProject(context.getModule().getProject());
-    WorkspacePath packagePath = getWorkspaceRelativeDirectoryPath(root, dir);
-    if (packagePath == null) {
-      return false;
-    }
-    return configuration.getCommand() == BlazeCommandName.TEST &&
-           configuration.getTarget() == TargetExpression.allFromPackageRecursive(packagePath);
-  }
-
-  @Nullable
-  private static PsiDirectory getTestDirectory(ConfigurationContext context) {
-    WorkspaceRoot root = WorkspaceRoot.fromProject(context.getModule().getProject());
-    PsiElement location = context.getPsiLocation();
-    if (location instanceof PsiDirectory) {
-      PsiDirectory dir = (PsiDirectory) location;
-      if (isInWorkspace(root, dir)) {
-        return dir;
-      }
-    }
-    return null;
-  }
-
-  @Nullable
-  private static WorkspacePath getWorkspaceRelativeDirectoryPath(
-    WorkspaceRoot root,
-    PsiDirectory dir) {
-    VirtualFile file = dir.getVirtualFile();
-    if (isInWorkspace(root, dir)) {
-      return root.workspacePathFor(file);
-    }
-    return null;
-  }
-
-  private static boolean isInWorkspace(WorkspaceRoot root,
-                                       PsiDirectory dir) {
-    return root.isInWorkspace(dir.getVirtualFile());
-  }
-
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/run/producers/BlazeJavaTestClassConfigurationProducer.java b/blaze-java/src/com/google/idea/blaze/java/run/producers/BlazeJavaTestClassConfigurationProducer.java
deleted file mode 100644
index 7c1c39a..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/run/producers/BlazeJavaTestClassConfigurationProducer.java
+++ /dev/null
@@ -1,135 +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.run.producers;
-
-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.TestIdeInfo;
-import com.google.idea.blaze.base.settings.Blaze;
-import com.google.idea.blaze.java.run.RunUtil;
-import com.google.idea.blaze.java.run.BlazeCommandRunConfiguration;
-import com.google.idea.blaze.java.run.BlazeCommandRunConfigurationType;
-import com.intellij.execution.JavaExecutionUtil;
-import com.intellij.execution.Location;
-import com.intellij.execution.actions.ConfigurationContext;
-import com.intellij.execution.junit.JUnitUtil;
-import com.intellij.openapi.util.Ref;
-import com.intellij.psi.PsiClass;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiMethod;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.List;
-
-/**
- * Producer for run configurations related to Java test classes in Blaze.
- */
-public class BlazeJavaTestClassConfigurationProducer extends BlazeTestRunConfigurationProducer<BlazeCommandRunConfiguration> {
-
-  public BlazeJavaTestClassConfigurationProducer() {
-    super(BlazeCommandRunConfigurationType.getInstance());
-  }
-
-  @Override
-  protected boolean doSetupConfigFromContext(
-    @NotNull BlazeCommandRunConfiguration configuration,
-    @NotNull ConfigurationContext context,
-    @NotNull Ref<PsiElement> sourceElement) {
-
-    final Location contextLocation = context.getLocation();
-    assert contextLocation != null;
-    final Location location = JavaExecutionUtil.stepIntoSingleClass(contextLocation);
-    if (location == null) {
-      return false;
-    }
-
-    if (JUnitConfigurationUtil.isMultipleElementsSelected(context)) {
-      return false;
-    }
-
-    PsiClass testClass = JUnitUtil.getTestClass(location);
-    if (testClass == null) {
-      return false;
-    }
-    sourceElement.set(testClass);
-
-    TestIdeInfo.TestSize testSize = TestSizeAnnotationMap.getTestSize(testClass);
-    RuleIdeInfo rule = RunUtil.ruleForTestClass(context.getProject(), testClass, testSize);
-    if (rule == null) {
-      return false;
-    }
-
-    configuration.setCommand(BlazeCommandName.TEST);
-    configuration.setTarget(rule.label);
-
-    ImmutableList.Builder<String> flags = ImmutableList.builder();
-
-    String qualifiedName = testClass.getQualifiedName();
-    if (qualifiedName != null) {
-      flags.add(BlazeFlags.testFilterFlagForClass(qualifiedName));
-    }
-
-    flags.add(BlazeFlags.TEST_OUTPUT_STREAMED);
-
-    configuration.setBlazeFlags(flags.build());
-    configuration.setName(
-      String.format("%s test: %s (%s)",
-                    Blaze.buildSystemName(configuration.getProject()),
-                    testClass.getName(),
-                    rule.label.toString()));
-    return true;
-  }
-
-  @Override
-  protected boolean doIsConfigFromContext(
-    @NotNull BlazeCommandRunConfiguration configuration,
-    @NotNull ConfigurationContext context) {
-
-    final Location contextLocation = context.getLocation();
-    assert contextLocation != null;
-    final Location location = JavaExecutionUtil.stepIntoSingleClass(contextLocation);
-    if (location == null) {
-      return false;
-    }
-
-    if (JUnitConfigurationUtil.isMultipleElementsSelected(context)) {
-      return false;
-    }
-
-    Location<PsiMethod> methodLocation = ProducerUtils.getMethodLocation(contextLocation);
-    if (methodLocation != null) {
-      return false;
-    }
-
-    PsiClass testClass = JUnitUtil.getTestClass(location);
-    if (testClass == null) {
-      return false;
-    }
-
-    return checkIfAttributesAreTheSame(configuration, testClass);
-  }
-
-  private boolean checkIfAttributesAreTheSame(
-    @NotNull BlazeCommandRunConfiguration configuration,
-    @NotNull PsiClass testClass) {
-
-    List<String> flags = configuration.getAllBlazeFlags();
-
-    return flags.contains(BlazeFlags.testFilterFlagForClass(testClass.getQualifiedName()));
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/run/producers/BlazeJavaTestMethodConfigurationProducer.java b/blaze-java/src/com/google/idea/blaze/java/run/producers/BlazeJavaTestMethodConfigurationProducer.java
deleted file mode 100644
index da906ca..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/run/producers/BlazeJavaTestMethodConfigurationProducer.java
+++ /dev/null
@@ -1,132 +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.run.producers;
-
-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.TestIdeInfo;
-import com.google.idea.blaze.base.settings.Blaze;
-import com.google.idea.blaze.java.run.BlazeCommandRunConfiguration;
-import com.google.idea.blaze.java.run.BlazeCommandRunConfigurationType;
-import com.google.idea.blaze.java.run.RunUtil;
-import com.intellij.execution.Location;
-import com.intellij.execution.actions.ConfigurationContext;
-import com.intellij.openapi.util.Ref;
-import com.intellij.psi.PsiClass;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiMethod;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.List;
-
-/**
- * Producer for run configurations related to Java test methods in Blaze.
- */
-public class BlazeJavaTestMethodConfigurationProducer extends BlazeTestRunConfigurationProducer<BlazeCommandRunConfiguration> {
-
-  public BlazeJavaTestMethodConfigurationProducer() {
-    super(BlazeCommandRunConfigurationType.getInstance());
-  }
-
-  @Override
-  protected boolean doSetupConfigFromContext(
-    @NotNull BlazeCommandRunConfiguration configuration,
-    @NotNull ConfigurationContext context,
-    @NotNull Ref<PsiElement> sourceElement) {
-
-    if (JUnitConfigurationUtil.isMultipleElementsSelected(context)) {
-      return false;
-    }
-
-    final Location contextLocation = context.getLocation();
-    assert contextLocation != null;
-    Location<PsiMethod> methodLocation = ProducerUtils.getMethodLocation(contextLocation);
-    if (methodLocation == null) {
-      return false;
-    }
-
-    final PsiMethod psiMethod = methodLocation.getPsiElement();
-    sourceElement.set(psiMethod);
-
-    final PsiClass containingClass = psiMethod.getContainingClass();
-    if (containingClass == null) {
-      return false;
-    }
-
-    TestIdeInfo.TestSize testSize = TestSizeAnnotationMap.getTestSize(psiMethod);
-    RuleIdeInfo rule = RunUtil.ruleForTestClass(context.getProject(), containingClass, testSize);
-    if (rule == null) {
-      return false;
-    }
-
-    configuration.setCommand(BlazeCommandName.TEST);
-    configuration.setTarget(rule.label);
-
-    ImmutableList.Builder<String> flags = ImmutableList.builder();
-
-    String qualifiedName = containingClass.getQualifiedName();
-    if (qualifiedName != null) {
-      flags.add(BlazeFlags.testFilterFlagForClassAndMethod(qualifiedName, psiMethod.getName()));
-    }
-
-    flags.add(BlazeFlags.TEST_OUTPUT_STREAMED);
-
-    configuration.setBlazeFlags(flags.build());
-    configuration.setName(
-      String.format("%s test: %s.%s (%s)",
-                    Blaze.buildSystemName(configuration.getProject()),
-                    containingClass.getName(),
-                    psiMethod.getName(),
-                    rule.label.toString()));
-    return true;
-  }
-
-  @Override
-  protected boolean doIsConfigFromContext(
-    @NotNull BlazeCommandRunConfiguration configuration,
-    @NotNull ConfigurationContext context) {
-
-    if (JUnitConfigurationUtil.isMultipleElementsSelected(context)) {
-      return false;
-    }
-
-    final Location contextLocation = context.getLocation();
-    assert contextLocation != null;
-
-    Location<PsiMethod> methodLocation = ProducerUtils.getMethodLocation(contextLocation);
-    if (methodLocation == null) {
-      return false;
-    }
-
-    final PsiMethod psiMethod = methodLocation.getPsiElement();
-    final PsiClass containingClass = psiMethod.getContainingClass();
-    if (containingClass == null) {
-      return false;
-    }
-
-    return checkIfAttributesAreTheSame(configuration, psiMethod);
-  }
-
-  private static boolean checkIfAttributesAreTheSame(
-    BlazeCommandRunConfiguration configuration, PsiMethod testMethod) {
-
-    List<String> flags = configuration.getAllBlazeFlags();
-
-    return flags.contains(BlazeFlags.testFilterFlagForClassAndMethod(testMethod.getContainingClass().getQualifiedName(), testMethod.getName()));
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/run/producers/BlazeTestRunConfigurationProducer.java b/blaze-java/src/com/google/idea/blaze/java/run/producers/BlazeTestRunConfigurationProducer.java
deleted file mode 100644
index fc0e439..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/run/producers/BlazeTestRunConfigurationProducer.java
+++ /dev/null
@@ -1,96 +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.run.producers;
-
-import com.google.idea.blaze.base.settings.Blaze;
-import com.intellij.execution.actions.ConfigurationContext;
-import com.intellij.execution.actions.ConfigurationFromContext;
-import com.intellij.execution.actions.RunConfigurationProducer;
-import com.intellij.execution.configurations.ConfigurationType;
-import com.intellij.execution.configurations.RunConfiguration;
-import com.intellij.openapi.module.Module;
-import com.intellij.openapi.util.NullUtils;
-import com.intellij.openapi.util.Ref;
-import com.intellij.psi.PsiElement;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Base class for Blaze test run configuration producers.
- */
-public abstract class BlazeTestRunConfigurationProducer<T extends RunConfiguration> extends RunConfigurationProducer<T> {
-
-  protected BlazeTestRunConfigurationProducer(ConfigurationType configurationType) {
-    super(configurationType);
-  }
-
-  @Override
-  public boolean isPreferredConfiguration(ConfigurationFromContext self, ConfigurationFromContext other) {
-    return Blaze.isBlazeProject(self.getConfiguration().getProject());
-  }
-
-  @Override
-  public boolean shouldReplace(ConfigurationFromContext self, ConfigurationFromContext other) {
-    return Blaze.isBlazeProject(self.getConfiguration().getProject()) &&
-           !other.isProducedBy(BlazeTestRunConfigurationProducer.class);
-  }
-
-  @Override
-  protected final boolean setupConfigurationFromContext(T configuration, ConfigurationContext context, Ref<PsiElement> sourceElement) {
-    if (NullUtils.hasNull(configuration, context, sourceElement)) {
-      return false;
-    }
-    if (!validContext(context)) {
-      return false;
-    }
-    return doSetupConfigFromContext(configuration, context, sourceElement);
-  }
-
-  protected abstract boolean doSetupConfigFromContext(
-    @NotNull T configuration,
-    @NotNull ConfigurationContext context,
-    @NotNull Ref<PsiElement> sourceElement);
-
-  @Override
-  public final boolean isConfigurationFromContext(T configuration, ConfigurationContext context) {
-    if (NullUtils.hasNull(configuration, context)) {
-      return false;
-    }
-    if (!validContext(context)) {
-      return false;
-    }
-    return doIsConfigFromContext(configuration, context);
-  }
-
-  protected abstract boolean doIsConfigFromContext(
-    @NotNull T configuration,
-    @NotNull ConfigurationContext context);
-
-  private static boolean validContext(@NotNull ConfigurationContext context) {
-    Module module = context.getModule();
-    if (module == null) {
-      return false;
-    }
-    if (!isBlazeContext(context)) {
-      return false;
-    }
-    return true;
-  }
-
-  private static boolean isBlazeContext(@NotNull ConfigurationContext context) {
-    return Blaze.isBlazeProject(context.getProject());
-  }
-
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/run/producers/JUnitConfigurationUtil.java b/blaze-java/src/com/google/idea/blaze/java/run/producers/JUnitConfigurationUtil.java
deleted file mode 100644
index fa757c4..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/run/producers/JUnitConfigurationUtil.java
+++ /dev/null
@@ -1,171 +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.run.producers;
-
-import com.intellij.execution.Location;
-import com.intellij.execution.actions.ConfigurationContext;
-import com.intellij.execution.junit.JUnitUtil;
-import com.intellij.execution.junit2.PsiMemberParameterizedLocation;
-import com.intellij.execution.junit2.info.MethodLocation;
-import com.intellij.execution.testframework.TestsUIUtil;
-import com.intellij.openapi.actionSystem.CommonDataKeys;
-import com.intellij.openapi.actionSystem.DataContext;
-import com.intellij.openapi.actionSystem.LangDataKeys;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.psi.*;
-import com.intellij.psi.search.PsiElementProcessor;
-import com.intellij.psi.util.ClassUtil;
-
-import java.util.ArrayList;
-import java.util.LinkedHashSet;
-import java.util.List;
-
-/**
- * Cloned from PatternConfigurationProducer, stripped down to only contain isMultipleElementsSelected.
- */
-public class JUnitConfigurationUtil {
-  protected static boolean isTestClass(PsiClass psiClass) {
-    return JUnitUtil.isTestClass(psiClass);
-  }
-
-  protected static boolean isTestMethod(boolean checkAbstract, PsiElement psiElement) {
-    return JUnitUtil.getTestMethod(psiElement, checkAbstract) != null;
-  }
-
-  public static boolean isMultipleElementsSelected(ConfigurationContext context) {
-    final DataContext dataContext = context.getDataContext();
-    if (TestsUIUtil.isMultipleSelectionImpossible(dataContext)) return false;
-    final LinkedHashSet<String> classes = new LinkedHashSet<String>();
-    final PsiElementProcessor.CollectElementsWithLimit<PsiElement> processor = new PsiElementProcessor.CollectElementsWithLimit<PsiElement>(2);
-    final PsiElement[] locationElements = collectLocationElements(classes, dataContext);
-    if (locationElements != null) {
-      collectTestMembers(locationElements, false, false, processor);
-    }
-    else {
-      collectContextElements(dataContext, false, false, classes, processor);
-    }
-    return processor.getCollection().size() > 1;
-  }
-
-  public static void collectTestMembers(PsiElement[] psiElements,
-                                 boolean checkAbstract,
-                                 boolean checkIsTest,
-                                 PsiElementProcessor.CollectElements<PsiElement> collectingProcessor) {
-    for (PsiElement psiElement : psiElements) {
-      if (psiElement instanceof PsiClassOwner) {
-        final PsiClass[] classes = ((PsiClassOwner)psiElement).getClasses();
-        for (PsiClass aClass : classes) {
-          if ((!checkIsTest && aClass.hasModifierProperty(PsiModifier.PUBLIC) || checkIsTest && isTestClass(aClass)) && 
-              !collectingProcessor.execute(aClass)) {
-            return;
-          }
-        }
-      } else if (psiElement instanceof PsiClass) {
-        if ((!checkIsTest && ((PsiClass)psiElement).hasModifierProperty(PsiModifier.PUBLIC) || checkIsTest && isTestClass((PsiClass)psiElement)) && 
-            !collectingProcessor.execute(psiElement)) {
-          return;
-        }
-      } else if (psiElement instanceof PsiMethod) {
-        if (checkIsTest && isTestMethod(checkAbstract, psiElement) && !collectingProcessor.execute(psiElement)) {
-          return;
-        }
-        if (!checkIsTest) {
-          final PsiClass containingClass = ((PsiMethod)psiElement).getContainingClass();
-          if (containingClass != null && containingClass.hasModifierProperty(PsiModifier.PUBLIC) && !collectingProcessor.execute(psiElement)) {
-            return;
-          }
-        }
-      } else if (psiElement instanceof PsiDirectory) {
-        final PsiPackage aPackage = JavaDirectoryService.getInstance().getPackage((PsiDirectory)psiElement);
-        if (aPackage != null && !collectingProcessor.execute(aPackage)) {
-          return;
-        }
-      }
-    }
-  }
-
-  private static boolean collectContextElements(DataContext dataContext,
-                                         boolean checkAbstract,
-                                         boolean checkIsTest, 
-                                         LinkedHashSet<String> classes,
-                                         PsiElementProcessor.CollectElements<PsiElement> processor) {
-    PsiElement[] elements = LangDataKeys.PSI_ELEMENT_ARRAY.getData(dataContext);
-    if (elements != null) {
-      collectTestMembers(elements, checkAbstract, checkIsTest, processor);
-      for (PsiElement psiClass : processor.getCollection()) {
-        classes.add(getQName(psiClass));
-      }
-      return true;
-    }
-    else {
-      final VirtualFile[] files = CommonDataKeys.VIRTUAL_FILE_ARRAY.getData(dataContext);
-      if (files != null) {
-        Project project = CommonDataKeys.PROJECT.getData(dataContext);
-        if (project != null) {
-          final PsiManager psiManager = PsiManager.getInstance(project);
-          for (VirtualFile file : files) {
-            final PsiFile psiFile = psiManager.findFile(file);
-            if (psiFile instanceof PsiClassOwner) {
-              collectTestMembers(((PsiClassOwner)psiFile).getClasses(), checkAbstract, checkIsTest, processor);
-              for (PsiElement psiMember : processor.getCollection()) {
-                classes.add(((PsiClass)psiMember).getQualifiedName());
-              }
-            }
-          }
-          return true;
-        }
-      }
-    }
-    return false;
-  }
-
-  private static PsiElement[] collectLocationElements(LinkedHashSet<String> classes, DataContext dataContext) {
-    final Location<?>[] locations = Location.DATA_KEYS.getData(dataContext);
-    if (locations != null) {
-      List<PsiElement> elements = new ArrayList<PsiElement>();
-      for (Location<?> location : locations) {
-        final PsiElement psiElement = location.getPsiElement();
-        classes.add(getQName(psiElement, location));
-        elements.add(psiElement);
-      }
-      return elements.toArray(new PsiElement[elements.size()]);
-    }
-    return null;
-  }
-
-  public static String getQName(PsiElement psiMember) {
-    return getQName(psiMember, null);
-  }
-
-  public static String getQName(PsiElement psiMember, Location location) {
-    if (psiMember instanceof PsiClass) {
-      return ClassUtil.getJVMClassName((PsiClass)psiMember);
-    }
-    else if (psiMember instanceof PsiMember) {
-      final PsiClass containingClass = location instanceof MethodLocation
-                                       ? ((MethodLocation)location).getContainingClass()
-                                       : location instanceof PsiMemberParameterizedLocation ? ((PsiMemberParameterizedLocation)location).getContainingClass() 
-                                                                                            : ((PsiMember)psiMember).getContainingClass();
-      assert containingClass != null;
-      return ClassUtil.getJVMClassName(containingClass) + "," + ((PsiMember)psiMember).getName();
-    } else if (psiMember instanceof PsiPackage) {
-      return ((PsiPackage)psiMember).getQualifiedName();
-    }
-    assert false;
-    return null;
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/run/producers/ProducerUtils.java b/blaze-java/src/com/google/idea/blaze/java/run/producers/ProducerUtils.java
deleted file mode 100644
index bbcce9b..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/run/producers/ProducerUtils.java
+++ /dev/null
@@ -1,64 +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.run.producers;
-
-import com.intellij.execution.Location;
-import com.intellij.execution.junit.JUnitUtil;
-import com.intellij.execution.junit2.PsiMemberParameterizedLocation;
-import com.intellij.execution.junit2.info.MethodLocation;
-import com.intellij.psi.PsiClass;
-import com.intellij.psi.PsiMethod;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.util.Iterator;
-
-/**
- * Copy of {@link org.jetbrains.plugins.gradle.execution.test.runner.TestRunnerUtils}.
- * <p/>
- * <p>Do not modify.
- */
-public class ProducerUtils {
-  @Nullable
-  public static Location<PsiMethod> getMethodLocation(@NotNull Location contextLocation) {
-    Location<PsiMethod> methodLocation = getTestMethod(contextLocation);
-    if (methodLocation == null) {
-      return null;
-    }
-
-    if (contextLocation instanceof PsiMemberParameterizedLocation) {
-      PsiClass containingClass =
-        ((PsiMemberParameterizedLocation)contextLocation).getContainingClass();
-      if (containingClass != null) {
-        methodLocation = MethodLocation
-          .elementInClass(methodLocation.getPsiElement(), containingClass);
-      }
-    }
-    return methodLocation;
-  }
-
-  @Nullable
-  public static Location<PsiMethod> getTestMethod(final Location<?> location) {
-    for (Iterator<Location<PsiMethod>> iterator = location.getAncestors(PsiMethod.class, false);
-         iterator.hasNext(); ) {
-      final Location<PsiMethod> methodLocation = iterator.next();
-      if (JUnitUtil.isTestMethod(methodLocation, false)) {
-        return methodLocation;
-      }
-    }
-    return null;
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/run/producers/TestSizeAnnotationMap.java b/blaze-java/src/com/google/idea/blaze/java/run/producers/TestSizeAnnotationMap.java
deleted file mode 100644
index 0322bc0..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/run/producers/TestSizeAnnotationMap.java
+++ /dev/null
@@ -1,73 +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.run.producers;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.idea.blaze.base.ideinfo.TestIdeInfo;
-import com.intellij.psi.PsiAnnotation;
-import com.intellij.psi.PsiClass;
-import com.intellij.psi.PsiMethod;
-import com.intellij.psi.PsiModifierList;
-
-import javax.annotation.Nullable;
-
-/**
- * Maps method and class annotations to our test size enumeration.
- */
-public class TestSizeAnnotationMap {
-  private static ImmutableMap<String, TestIdeInfo.TestSize> ANNOTATION_TO_TEST_SIZE = ImmutableMap.<String, TestIdeInfo.TestSize>builder()
-    .put("com.google.testing.testsize.SmallTest", TestIdeInfo.TestSize.SMALL)
-    .put("com.google.testing.testsize.MediumTest", TestIdeInfo.TestSize.MEDIUM)
-    .put("com.google.testing.testsize.LargeTest", TestIdeInfo.TestSize.LARGE)
-    .put("com.google.testing.testsize.EnormousTest", TestIdeInfo.TestSize.ENORMOUS)
-    .build();
-
-  @Nullable
-  public static TestIdeInfo.TestSize getTestSize(PsiMethod psiMethod) {
-    PsiAnnotation[] annotations = psiMethod.getModifierList().getAnnotations();
-    TestIdeInfo.TestSize testSize = getTestSize(annotations);
-    if (testSize != null) {
-      return testSize;
-    }
-    return getTestSize(psiMethod.getContainingClass());
-  }
-
-  @Nullable
-  public static TestIdeInfo.TestSize getTestSize(PsiClass psiClass) {
-    PsiModifierList psiModifierList = psiClass.getModifierList();
-    if (psiModifierList == null) {
-      return null;
-    }
-    PsiAnnotation[] annotations = psiModifierList.getAnnotations();
-    TestIdeInfo.TestSize testSize = getTestSize(annotations);
-    if (testSize == null) {
-      return null;
-    }
-    return testSize;
-  }
-
-  @Nullable
-  private static TestIdeInfo.TestSize getTestSize(PsiAnnotation[] annotations) {
-    for (PsiAnnotation annotation : annotations) {
-      String qualifiedName = annotation.getQualifiedName();
-      TestIdeInfo.TestSize testSize = ANNOTATION_TO_TEST_SIZE.get(qualifiedName);
-      if (testSize != null) {
-        return testSize;
-      }
-    }
-    return null;
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/sync/BlazeJavaSyncAugmenter.java b/blaze-java/src/com/google/idea/blaze/java/sync/BlazeJavaSyncAugmenter.java
deleted file mode 100644
index ba7219c..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/sync/BlazeJavaSyncAugmenter.java
+++ /dev/null
@@ -1,46 +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.idea.blaze.base.model.BlazeProjectData;
-import com.google.idea.blaze.base.projectview.section.Glob;
-import com.google.idea.blaze.java.sync.model.BlazeLibrary;
-import com.intellij.openapi.extensions.ExtensionPointName;
-
-import java.util.Collection;
-
-/**
- * Augments the java importer
- */
-public interface BlazeJavaSyncAugmenter {
-  ExtensionPointName<BlazeJavaSyncAugmenter> EP_NAME = ExtensionPointName.create("com.google.idea.blaze.java.JavaSyncAugmenter");
-
-  /**
-   * 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);
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/sync/BlazeJavaSyncPlugin.java b/blaze-java/src/com/google/idea/blaze/java/sync/BlazeJavaSyncPlugin.java
deleted file mode 100644
index 06e97e2..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/sync/BlazeJavaSyncPlugin.java
+++ /dev/null
@@ -1,363 +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.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.experiments.BoolExperiment;
-import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
-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.model.SyncState;
-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.WorkspaceRoot;
-import com.google.idea.blaze.base.model.primitives.WorkspaceType;
-import com.google.idea.blaze.base.projectview.ProjectViewSet;
-import com.google.idea.blaze.base.projectview.section.Glob;
-import com.google.idea.blaze.base.projectview.section.SectionParser;
-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;
-import com.google.idea.blaze.base.scope.output.PerformanceWarning;
-import com.google.idea.blaze.base.scope.output.PrintOutput;
-import com.google.idea.blaze.base.scope.scopes.TimingScope;
-import com.google.idea.blaze.base.settings.BlazeUserSettings;
-import com.google.idea.blaze.base.sync.BlazeSyncPlugin;
-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;
-import com.google.idea.blaze.base.sync.workspace.WorkingSet;
-import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver;
-import com.google.idea.blaze.java.projectview.ExcludeLibrarySection;
-import com.google.idea.blaze.java.projectview.ExcludedLibrarySection;
-import com.google.idea.blaze.java.projectview.JavaLanguageLevelSection;
-import com.google.idea.blaze.java.sync.importer.BlazeJavaWorkspaceImporter;
-import com.google.idea.blaze.java.sync.jdeps.JdepsFileReader;
-import com.google.idea.blaze.java.sync.jdeps.JdepsMap;
-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.diagnostic.Logger;
-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 javax.annotation.Nullable;
-import java.io.File;
-import java.util.Collection;
-import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-/**
- * Sync support for Java.
- */
-public class BlazeJavaSyncPlugin extends BlazeSyncPlugin.Adapter {
-  private static final BoolExperiment USE_WORKING_SET = new BoolExperiment("use.working.set", true);
-  private static final Logger LOG = Logger.getInstance(BlazeJavaSyncPlugin.class);
-  private final JdepsFileReader jdepsFileReader = new JdepsFileReader();
-
-  @Nullable
-  @Override
-  public WorkspaceType getDefaultWorkspaceType() {
-    return WorkspaceType.JAVA;
-  }
-
-  @Override
-  public Set<LanguageClass> getSupportedLanguagesInWorkspace(WorkspaceType workspaceType) {
-    if (workspaceType == WorkspaceType.JAVA) {
-      return ImmutableSet.of(LanguageClass.JAVA);
-    }
-    return ImmutableSet.of();
-  }
-
-  @Nullable
-  @Override
-  public ModuleType getWorkspaceModuleType(WorkspaceType workspaceType) {
-    if (workspaceType == WorkspaceType.JAVA) {
-      return StdModuleTypes.JAVA;
-    }
-    return null;
-  }
-
-  @Override
-  public void updateSyncState(Project project,
-                              BlazeContext context,
-                              WorkspaceRoot workspaceRoot,
-                              ProjectViewSet projectViewSet,
-                              WorkspaceLanguageSettings workspaceLanguageSettings,
-                              BlazeRoots blazeRoots,
-                              @Nullable WorkingSet workingSet,
-                              WorkspacePathResolver workspacePathResolver,
-                              ImmutableMap<Label, RuleIdeInfo> ruleMap,
-                              @Deprecated @Nullable File androidPlatformDirectory,
-                              SyncState.Builder syncStateBuilder,
-                              @Nullable SyncState previousSyncState) {
-    JavaWorkingSet javaWorkingSet = null;
-    if (USE_WORKING_SET.getValue() && workingSet != null) {
-      javaWorkingSet = new JavaWorkingSet(workspaceRoot, workingSet);
-    }
-
-    JdepsMap jdepsMap = jdepsFileReader.loadJdepsFiles(context, ruleMap, syncStateBuilder, previousSyncState);
-
-    BlazeJavaWorkspaceImporter blazeJavaWorkspaceImporter = new BlazeJavaWorkspaceImporter(
-      project,
-      workspaceRoot,
-      projectViewSet,
-      ruleMap,
-      jdepsMap,
-      javaWorkingSet,
-      new ArtifactLocationDecoder(blazeRoots, workspacePathResolver)
-    );
-    BlazeJavaImportResult importResult = Scope.push(context, (childContext) -> {
-      childContext.push(new TimingScope("JavaWorkspaceImporter"));
-      return blazeJavaWorkspaceImporter.importWorkspace(childContext);
-    });
-    Glob.GlobSet excludedLibraries = new Glob.GlobSet(
-      ImmutableList.<Glob>builder()
-        .addAll(projectViewSet.listItems(ExcludeLibrarySection.KEY))
-        .addAll(projectViewSet.listItems(ExcludedLibrarySection.KEY))
-        .build()
-    );
-    for (BlazeJavaSyncAugmenter syncAugmenter : BlazeJavaSyncAugmenter.EP_NAME.getExtensions()) {
-      syncAugmenter.addLibraryFilter(excludedLibraries);
-    }
-    BlazeJavaSyncData syncData = new BlazeJavaSyncData(
-      importResult,
-      excludedLibraries,
-      BlazeUserSettings.getInstance().getAttachSourcesByDefault()
-    );
-    syncStateBuilder.put(BlazeJavaSyncData.class, syncData);
-  }
-
-  @Override
-  public void updateSdk(Project project,
-                        BlazeContext context,
-                        ProjectViewSet projectViewSet,
-                        BlazeProjectData blazeProjectData) {
-    if (!blazeProjectData.workspaceLanguageSettings.isWorkspaceType(WorkspaceType.JAVA)) {
-      return;
-    }
-    updateJdk(project, context, projectViewSet, blazeProjectData);
-  }
-
-  @Override
-  public void updateContentEntries(Project project,
-                                   BlazeContext context,
-                                   WorkspaceRoot workspaceRoot,
-                                   ProjectViewSet projectViewSet,
-                                   BlazeProjectData blazeProjectData,
-                                   Collection<ContentEntry> contentEntries) {
-    if (!blazeProjectData.workspaceLanguageSettings.isLanguageActive(LanguageClass.JAVA)) {
-      return;
-    }
-    BlazeJavaSyncData syncData = blazeProjectData.syncState.get(BlazeJavaSyncData.class);
-    if (syncData == null) {
-      return;
-    }
-
-    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;
-    }
-
-    @Nullable BlazeJavaSyncData oldSyncData = oldBlazeProjectData != null
-                                              ? oldBlazeProjectData.syncState.get(BlazeJavaSyncData.class)
-                                              : null;
-
-    if (syncData.attachSourceJarsByDefault) {
-      context.output(PrintOutput.output(
-        "Attaching source jars by default. This may lead to significant increases in indexing time,"
-        + " project opening time, and IDE memory usage. To turn off go to"
-        + " Settings > Other Settings > Blaze."
-      ));
-    }
-
-    List<BlazeLibrary> newLibraries = getLibraries(blazeProjectData, syncData);
-    final List<BlazeLibrary> oldLibraries;
-    if (oldSyncData != null && oldSyncData.attachSourceJarsByDefault == syncData.attachSourceJarsByDefault) {
-      oldLibraries = getLibraries(oldBlazeProjectData, oldSyncData);
-    } else {
-      oldLibraries = ImmutableList.of();
-    }
-
-    LibraryEditor.updateProjectLibraries(
-      project,
-      context,
-      blazeProjectData,
-      newLibraries,
-      oldLibraries
-    );
-
-    LibraryEditor.configureDependencies(
-      project,
-      context,
-      workspaceModifiableModel,
-      newLibraries
-    );
-  }
-
-  private static List<BlazeLibrary> getLibraries(BlazeProjectData blazeProjectData,
-                                                 BlazeJavaSyncData syncData) {
-    Glob.GlobSet excludedLibraries = syncData.excludedLibraries;
-
-    List<BlazeLibrary> libraries = Lists.newArrayList();
-    libraries.addAll(syncData.importResult.libraries.values());
-    for (BlazeJavaSyncAugmenter syncAugmenter : BlazeJavaSyncAugmenter.EP_NAME.getExtensions()) {
-      libraries.addAll(syncAugmenter.getAdditionalLibraries(blazeProjectData));
-    }
-    return libraries
-      .stream()
-      .filter(blazeLibrary -> !isExcluded(excludedLibraries, blazeLibrary.getLibraryArtifact()))
-      .collect(Collectors.toList());
-  }
-
-  private static boolean isExcluded(Glob.GlobSet excludedLibraries, @Nullable LibraryArtifact libraryArtifact) {
-    if (libraryArtifact == null) {
-      return false;
-    }
-    ArtifactLocation jar = libraryArtifact.jar;
-    ArtifactLocation runtimeJar = libraryArtifact.runtimeJar;
-    return excludedLibraries.matches(jar.getRelativePath())
-           || (runtimeJar != null && excludedLibraries.matches(runtimeJar.getRelativePath()));
-  }
-
-  private static void updateJdk(
-    Project project,
-    BlazeContext context,
-    ProjectViewSet projectViewSet,
-    BlazeProjectData blazeProjectData) {
-
-    LanguageLevel javaLanguageLevel = JavaLanguageLevelHelper
-      .getJavaLanguageLevel(projectViewSet, blazeProjectData, LanguageLevel.JDK_1_7);
-
-    final Sdk sdk = Jdks.chooseOrCreateJavaSdk(javaLanguageLevel);
-    if (sdk == null) {
-      String msg = String.format(
-        "Unable to find a JDK %1$s installed.\n", javaLanguageLevel.getPresentableText());
-      msg += "After configuring a suitable JDK in the \"Project Structure\" dialog, "
-             + "sync the project again.";
-      IssueOutput.error(msg).submit(context);
-      return;
-    }
-    setProjectSdkAndLanguageLevel(project, sdk, javaLanguageLevel);
-  }
-
-  private static void setProjectSdkAndLanguageLevel(
-    final Project project,
-    final Sdk sdk,
-    final LanguageLevel javaLanguageLevel) {
-    UIUtil.invokeAndWaitIfNeeded((Runnable)() -> ApplicationManager.getApplication().runWriteAction(() -> {
-      ProjectRootManagerEx rootManager = ProjectRootManagerEx.getInstanceEx(project);
-      rootManager.setProjectSdk(sdk);
-      LanguageLevelProjectExtension ext = LanguageLevelProjectExtension.getInstance(project);
-      ext.setLanguageLevel(javaLanguageLevel);
-    }));
-  }
-
-  @Override
-  public boolean validate(Project project,
-                          BlazeContext context,
-                          BlazeProjectData blazeProjectData) {
-    BlazeJavaSyncData syncData = blazeProjectData.syncState.get(BlazeJavaSyncData.class);
-    if (syncData == null) {
-      return true;
-    }
-    warnAboutDeployJars(context, syncData);
-    return true;
-  }
-
-  @Override
-  public Collection<SectionParser> getSections() {
-    return ImmutableList.of(
-      ExcludedLibrarySection.PARSER,
-      ExcludeLibrarySection.PARSER,
-      JavaLanguageLevelSection.PARSER
-    );
-  }
-
-  @Override
-  public boolean requiresResolveIdeArtifacts() {
-    return true;
-  }
-
-  /**
-   * 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 project.
-   */
-  private static void warnAboutDeployJars(
-    BlazeContext context,
-    BlazeJavaSyncData syncData) {
-    for (BlazeLibrary library : syncData.importResult.libraries.values()) {
-      LibraryArtifact libraryArtifact = library.getLibraryArtifact();
-      if (libraryArtifact == null) {
-        continue;
-      }
-      ArtifactLocation artifactLocation = libraryArtifact.jar;
-      if (artifactLocation.getRelativePath().endsWith("deploy.jar")
-          || artifactLocation.getRelativePath().endsWith("deploy-ijar.jar")
-          || artifactLocation.getRelativePath().endsWith("deploy-hjar.jar")) {
-        context.output(new PerformanceWarning(
-          "Performance warning: You have added a deploy jar as a library. "
-          + "This can lead to poor indexing performance, and the debugger may "
-          + "become confused and step into the deploy jar instead of your code. "
-          + "Consider redoing the rule to not use deploy jars, exclude the target "
-          + "from your .blazeproject, or exclude the library.\n"
-          + "Library path: " + artifactLocation.getRelativePath()
-        ));
-      }
-    }
-  }
-
-  @Override
-  public Set<String> prefetchSrcFileExtensions() {
-    return ImmutableSet.of("java");
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/sync/DuplicateSourceDetector.java b/blaze-java/src/com/google/idea/blaze/java/sync/DuplicateSourceDetector.java
deleted file mode 100644
index 1120f41..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/sync/DuplicateSourceDetector.java
+++ /dev/null
@@ -1,82 +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.ArrayListMultimap;
-import com.google.common.collect.Lists;
-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.model.primitives.Label;
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.google.idea.blaze.base.scope.output.PerformanceWarning;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Detects and reports duplicate sources
- */
-public class DuplicateSourceDetector {
-  Multimap<ArtifactLocation, Label> artifacts = ArrayListMultimap.create();
-
-  public void add(Label label, ArtifactLocation artifactLocation) {
-    artifacts.put(artifactLocation, label);
-  }
-
-  static class Duplicate {
-    final ArtifactLocation artifactLocation;
-    final Collection<Label> labels;
-    public Duplicate(ArtifactLocation artifactLocation, Collection<Label> labels) {
-      this.artifactLocation = artifactLocation;
-      this.labels = labels;
-    }
-  }
-
-  public void reportDuplicates(BlazeContext context) {
-    List<Duplicate> duplicates = Lists.newArrayList();
-    for (ArtifactLocation key : artifacts.keySet()) {
-      Collection<Label> 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<Label> labelSet = Sets.newHashSet(labels);
-        if (labelSet.size() > 1) {
-          duplicates.add(new Duplicate(key, labelSet));
-        }
-      }
-    }
-
-    if (duplicates.isEmpty()) {
-      return;
-    }
-
-    Collections.sort(duplicates, (lhs, rhs) -> lhs.artifactLocation.getRelativePath().compareTo(rhs.artifactLocation.getRelativePath()));
-
-    context.output(new PerformanceWarning("Duplicate sources detected:"));
-    for (Duplicate duplicate : duplicates) {
-      ArtifactLocation artifactLocation = duplicate.artifactLocation;
-      context.output(new PerformanceWarning("  Source: " + artifactLocation.getRelativePath()));
-      context.output(new PerformanceWarning("  Consumed by rules:"));
-      for (Label label : duplicate.labels) {
-        context.output(new PerformanceWarning("    " + label));
-      }
-      context.output(new PerformanceWarning("")); // Newline
-    }
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/sync/JavaLanguageLevelHelper.java b/blaze-java/src/com/google/idea/blaze/java/sync/JavaLanguageLevelHelper.java
deleted file mode 100644
index 8f6bfe4..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/sync/JavaLanguageLevelHelper.java
+++ /dev/null
@@ -1,59 +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.base.Strings;
-import com.google.idea.blaze.base.model.BlazeProjectData;
-import com.google.idea.blaze.base.projectview.ProjectViewSet;
-import com.google.idea.blaze.java.projectview.JavaLanguageLevelSection;
-import com.google.idea.blaze.java.sync.model.BlazeJavaSyncData;
-import com.intellij.pom.java.LanguageLevel;
-
-/**
- * Called by sync plugins to determine the appropriate java language level.
- */
-public class JavaLanguageLevelHelper {
-
-  public static LanguageLevel getJavaLanguageLevel(
-    ProjectViewSet projectViewSet,
-    BlazeProjectData blazeProjectData,
-    LanguageLevel defaultLanguageLevel) {
-
-    defaultLanguageLevel = getLanguageLevelFromToolchain(blazeProjectData, defaultLanguageLevel);
-    return JavaLanguageLevelSection.getLanguageLevel(projectViewSet, defaultLanguageLevel);
-  }
-
-  private static LanguageLevel getLanguageLevelFromToolchain(BlazeProjectData blazeProjectData, LanguageLevel defaultLanguageLevel) {
-    BlazeJavaSyncData blazeJavaSyncData = blazeProjectData.syncState.get(BlazeJavaSyncData.class);
-    if (blazeJavaSyncData != null) {
-      String sourceVersion = blazeJavaSyncData.importResult.sourceVersion;
-      if (!Strings.isNullOrEmpty(sourceVersion)) {
-        switch (sourceVersion) {
-          case "6":
-            return LanguageLevel.JDK_1_6;
-          case "7":
-            return LanguageLevel.JDK_1_7;
-          case "8":
-            return LanguageLevel.JDK_1_8;
-          case "9":
-            return LanguageLevel.JDK_1_9;
-        }
-      }
-    }
-    return defaultLanguageLevel;
-  }
-
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/sync/importer/BlazeJavaWorkspaceImporter.java b/blaze-java/src/com/google/idea/blaze/java/sync/importer/BlazeJavaWorkspaceImporter.java
deleted file mode 100644
index 9868352..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/sync/importer/BlazeJavaWorkspaceImporter.java
+++ /dev/null
@@ -1,371 +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.importer;
-
-import com.google.common.collect.*;
-import com.google.idea.blaze.base.ideinfo.*;
-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.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.PrintOutput;
-import com.google.idea.blaze.base.settings.Blaze;
-import com.google.idea.blaze.base.sync.projectview.ImportRoots;
-import com.google.idea.blaze.base.sync.projectview.ProjectViewRuleImportFilter;
-import com.google.idea.blaze.base.sync.projectview.SourceTestConfig;
-import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
-import com.google.idea.blaze.java.sync.DuplicateSourceDetector;
-import com.google.idea.blaze.java.sync.jdeps.JdepsMap;
-import com.google.idea.blaze.java.sync.model.BlazeContentEntry;
-import com.google.idea.blaze.java.sync.model.BlazeJavaImportResult;
-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.source.SourceArtifact;
-import com.google.idea.blaze.java.sync.source.SourceDirectoryCalculator;
-import com.google.idea.blaze.java.sync.workingset.JavaWorkingSet;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.project.Project;
-
-import javax.annotation.Nullable;
-import java.io.File;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-/**
- * Builds a BlazeWorkspace.
- */
-public final class BlazeJavaWorkspaceImporter {
-  private static final Logger LOG = Logger.getInstance(BlazeJavaWorkspaceImporter.class);
-
-  private final Project project;
-  private final WorkspaceRoot workspaceRoot;
-  private final ImportRoots importRoots;
-  private final ImmutableMap<Label, RuleIdeInfo> ruleMap;
-  private final SourceTestConfig sourceTestConfig;
-  private final JdepsMap jdepsMap;
-  @Nullable private final JavaWorkingSet workingSet;
-  private final ArtifactLocationDecoder artifactLocationDecoder;
-  private final ProjectViewRuleImportFilter importFilter;
-  private final DuplicateSourceDetector duplicateSourceDetector = new DuplicateSourceDetector();
-
-  public BlazeJavaWorkspaceImporter(
-    Project project,
-    WorkspaceRoot workspaceRoot,
-    ProjectViewSet projectViewSet,
-    ImmutableMap<Label, RuleIdeInfo> ruleMap,
-    JdepsMap jdepsMap,
-    @Nullable JavaWorkingSet workingSet,
-    ArtifactLocationDecoder artifactLocationDecoder) {
-    this.project = project;
-    this.workspaceRoot = workspaceRoot;
-    this.importRoots = ImportRoots.builder(workspaceRoot, Blaze.getBuildSystem(project))
-      .add(projectViewSet)
-      .build();
-    this.ruleMap = ruleMap;
-    this.jdepsMap = jdepsMap;
-    this.workingSet = workingSet;
-    this.artifactLocationDecoder = artifactLocationDecoder;
-    this.importFilter = new ProjectViewRuleImportFilter(project, workspaceRoot, projectViewSet);
-    this.sourceTestConfig = new SourceTestConfig(projectViewSet);
-  }
-
-  public BlazeJavaImportResult importWorkspace(BlazeContext context) {
-    List<RuleIdeInfo> includedRules = ruleMap.values().stream()
-      .filter(rule -> !importFilter.excludeTarget(rule))
-      .collect(Collectors.toList());
-
-    List<RuleIdeInfo> javaRules = includedRules.stream()
-      .filter(rule -> rule.javaRuleIdeInfo != null)
-      .collect(Collectors.toList());
-
-    List<RuleIdeInfo> sourceRules = Lists.newArrayList();
-    List<RuleIdeInfo> libraryRules = Lists.newArrayList();
-    for (RuleIdeInfo rule : javaRules) {
-      boolean importAsSource =
-        importFilter.isSourceRule(rule)
-        && canImportAsSource(rule)
-        && !allSourcesGenerated(rule);
-
-      if (importAsSource) {
-        sourceRules.add(rule);
-      } else {
-        libraryRules.add(rule);
-      }
-    }
-
-    List<RuleIdeInfo> protoLibraries = includedRules.stream()
-      .filter(rule -> rule.kind == Kind.PROTO_LIBRARY)
-      .collect(Collectors.toList());
-
-    WorkspaceBuilder workspaceBuilder = new WorkspaceBuilder();
-    for (RuleIdeInfo rule : sourceRules) {
-      addRuleAsSource(workspaceBuilder, rule);
-    }
-
-    SourceDirectoryCalculator sourceDirectoryCalculator = new SourceDirectoryCalculator();
-    ImmutableList<BlazeContentEntry> contentEntries = sourceDirectoryCalculator.calculateContentEntries(
-      context,
-      workspaceRoot,
-      sourceTestConfig,
-      artifactLocationDecoder,
-      importRoots.rootDirectories(),
-      workspaceBuilder.sourceArtifacts,
-      workspaceBuilder.javaPackageManifests
-    );
-
-    int totalContentEntryCount = 0;
-    for (BlazeContentEntry contentEntry : contentEntries) {
-      totalContentEntryCount += contentEntry.sources.size();
-    }
-    context.output(PrintOutput.output("Java content entry count: " + totalContentEntryCount));
-
-    ImmutableMap<LibraryKey, BlazeLibrary> libraries = buildLibraries(workspaceBuilder, ruleMap, libraryRules, protoLibraries);
-
-    duplicateSourceDetector.reportDuplicates(context);
-
-    String sourceVersion = findSourceVersion(ruleMap);
-
-    return new BlazeJavaImportResult(
-      contentEntries,
-      libraries,
-      ImmutableList.copyOf(workspaceBuilder.buildOutputJars
-                             .stream()
-                             .sorted()
-                             .collect(Collectors.toList())),
-      ImmutableSet.copyOf(workspaceBuilder.addedSourceFiles),
-      sourceVersion
-    );
-  }
-
-  private boolean canImportAsSource(RuleIdeInfo rule) {
-    return !rule.kindIsOneOf(Kind.JAVA_WRAP_CC, Kind.JAVA_IMPORT);
-  }
-
-  private boolean allSourcesGenerated(RuleIdeInfo rule) {
-    return !rule.sources.isEmpty() && rule.sources.stream().allMatch(ArtifactLocation::isGenerated);
-  }
-
-  private ImmutableMap<LibraryKey, BlazeLibrary> buildLibraries(WorkspaceBuilder workspaceBuilder,
-                                                                Map<Label, RuleIdeInfo> ruleMap,
-                                                                List<RuleIdeInfo> libraryRules,
-                                                                List<RuleIdeInfo> protoLibraries) {
-    // Build library maps
-    Multimap<Label, LibraryArtifact> labelToLibrary = ArrayListMultimap.create();
-    Map<String, LibraryArtifact> jdepsPathToLibrary = Maps.newHashMap();
-    for (RuleIdeInfo rule : libraryRules) {
-      JavaRuleIdeInfo javaRuleIdeInfo = rule.javaRuleIdeInfo;
-      if (javaRuleIdeInfo == null) {
-        continue;
-      }
-      Iterable<LibraryArtifact> libraries = Iterables.concat(javaRuleIdeInfo.jars, javaRuleIdeInfo.generatedJars);
-      labelToLibrary.putAll(rule.label, libraries);
-      for (LibraryArtifact libraryArtifact : libraries) {
-        addLibraryToJdeps(jdepsPathToLibrary, libraryArtifact);
-      }
-    }
-
-    // proto legacy jdeps support
-    for (RuleIdeInfo rule : protoLibraries) {
-      ProtoLibraryLegacyInfo protoLibraryLegacyInfo = rule.protoLibraryLegacyInfo;
-      if (protoLibraryLegacyInfo == null) {
-        continue;
-      }
-      for (LibraryArtifact libraryArtifact : Iterables.concat(protoLibraryLegacyInfo.jarsV1,
-                                                              protoLibraryLegacyInfo.jarsMutable,
-                                                              protoLibraryLegacyInfo.jarsImmutable)) {
-        addLibraryToJdeps(jdepsPathToLibrary, libraryArtifact);
-      }
-    }
-
-    // Collect jars from jdep references
-    Set<LibraryArtifact> libraries = Sets.newHashSet();
-    for (String jdepsPath : workspaceBuilder.jdeps) {
-      LibraryArtifact libraryArtifact = jdepsPathToLibrary.get(jdepsPath);
-      if (libraryArtifact != null) {
-        libraries.add(libraryArtifact);
-      }
-    }
-
-    // Collect jars referenced by direct deps from your working set
-    for (Label deps : workspaceBuilder.directDeps) {
-      libraries.addAll(labelToLibrary.get(deps));
-    }
-
-    // Collect legacy proto libraries from direct deps
-    addProtoLegacyLibrariesFromDirectDeps(workspaceBuilder, ruleMap, libraries);
-
-    // Collect generated jars from source rules
-    libraries.addAll(workspaceBuilder.generatedJars);
-
-    ImmutableMap.Builder<LibraryKey, BlazeLibrary> result = ImmutableMap.builder();
-    for (LibraryArtifact libraryArtifact : libraries) {
-      File jar = libraryArtifact.jar.getFile();
-      LibraryKey key = LibraryKey.fromJarFile(jar);
-      BlazeLibrary blazeLibrary = new BlazeLibrary(key, libraryArtifact);
-      result.put(key, blazeLibrary);
-    }
-    return result.build();
-  }
-
-  private void addProtoLegacyLibrariesFromDirectDeps(WorkspaceBuilder workspaceBuilder,
-                                                     Map<Label, RuleIdeInfo> ruleMap,
-                                                     Set<LibraryArtifact> result) {
-    List<Label> version1Roots = Lists.newArrayList();
-    List<Label> immutableRoots = Lists.newArrayList();
-    List<Label> mutableRoots = Lists.newArrayList();
-    for (Label label : workspaceBuilder.directDeps) {
-      RuleIdeInfo rule = ruleMap.get(label);
-      if (rule == null) {
-        continue;
-      }
-      ProtoLibraryLegacyInfo protoLibraryLegacyInfo = rule.protoLibraryLegacyInfo;
-      if (protoLibraryLegacyInfo == null) {
-        continue;
-      }
-      switch (protoLibraryLegacyInfo.apiFlavor) {
-        case VERSION_1:
-          version1Roots.add(label);
-          break;
-        case IMMUTABLE:
-          immutableRoots.add(label);
-          break;
-        case MUTABLE:
-          mutableRoots.add(label);
-          break;
-        case BOTH:
-          mutableRoots.add(label);
-          immutableRoots.add(label);
-          break;
-        default:
-          // Can't happen
-          break;
-      }
-    }
-
-    addProtoLegacyLibrariesFromDirectDepsForFlavor(ruleMap, ProtoLibraryLegacyInfo.ApiFlavor.VERSION_1, version1Roots, result);
-    addProtoLegacyLibrariesFromDirectDepsForFlavor(ruleMap, ProtoLibraryLegacyInfo.ApiFlavor.IMMUTABLE, immutableRoots, result);
-    addProtoLegacyLibrariesFromDirectDepsForFlavor(ruleMap, ProtoLibraryLegacyInfo.ApiFlavor.MUTABLE, mutableRoots, result);
-  }
-
-  private void addProtoLegacyLibrariesFromDirectDepsForFlavor(Map<Label, RuleIdeInfo> ruleMap,
-                                                              ProtoLibraryLegacyInfo.ApiFlavor apiFlavor,
-                                                              List<Label> roots,
-                                                              Set<LibraryArtifact> result) {
-    Set<Label> seen = Sets.newHashSet();
-    while (!roots.isEmpty()) {
-      Label label = roots.remove(roots.size() - 1);
-      if (!seen.add(label)) {
-        continue;
-      }
-      RuleIdeInfo rule = ruleMap.get(label);
-      if (rule == null) {
-        continue;
-      }
-      ProtoLibraryLegacyInfo protoLibraryLegacyInfo = rule.protoLibraryLegacyInfo;
-      if (protoLibraryLegacyInfo == null) {
-        continue;
-      }
-      switch (apiFlavor) {
-        case VERSION_1:
-          result.addAll(protoLibraryLegacyInfo.jarsV1);
-          break;
-        case MUTABLE:
-          result.addAll(protoLibraryLegacyInfo.jarsMutable);
-          break;
-        case IMMUTABLE:
-          result.addAll(protoLibraryLegacyInfo.jarsImmutable);
-          break;
-        default:
-          // Can't happen
-          break;
-      }
-
-      roots.addAll(rule.dependencies);
-    }
-  }
-
-  private void addLibraryToJdeps(Map<String, LibraryArtifact> jdepsPathToLibrary, LibraryArtifact libraryArtifact) {
-    ArtifactLocation jar = libraryArtifact.jar;
-    jdepsPathToLibrary.put(jar.getExecutionRootRelativePath(), libraryArtifact);
-    ArtifactLocation runtimeJar = libraryArtifact.runtimeJar;
-    if (runtimeJar != null) {
-      jdepsPathToLibrary.put(runtimeJar.getExecutionRootRelativePath(), libraryArtifact);
-    }
-  }
-
-  private void addRuleAsSource(
-    WorkspaceBuilder workspaceBuilder,
-    RuleIdeInfo rule) {
-    JavaRuleIdeInfo javaRuleIdeInfo = rule.javaRuleIdeInfo;
-    if (javaRuleIdeInfo == null) {
-      return;
-    }
-
-    Collection<String> jars = jdepsMap.getDependenciesForRule(rule.label);
-    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)) {
-      workspaceBuilder.directDeps.addAll(rule.dependencies);
-    }
-
-    for (ArtifactLocation artifactLocation : rule.sources) {
-      if (!artifactLocation.isGenerated()) {
-        duplicateSourceDetector.add(rule.label, artifactLocation);
-        workspaceBuilder.sourceArtifacts.add(new SourceArtifact(rule.label, artifactLocation));
-        workspaceBuilder.addedSourceFiles.add(artifactLocation.getFile());
-      }
-    }
-
-    ArtifactLocation manifest = javaRuleIdeInfo.packageManifest;
-    if (manifest != null) {
-      workspaceBuilder.javaPackageManifests.put(rule.label, manifest);
-    }
-    for (LibraryArtifact libraryArtifact : javaRuleIdeInfo.jars) {
-      ArtifactLocation runtimeJar = libraryArtifact.runtimeJar;
-      if (runtimeJar != null) {
-        workspaceBuilder.buildOutputJars.add(runtimeJar.getFile());
-      }
-    }
-    workspaceBuilder.generatedJars.addAll(javaRuleIdeInfo.generatedJars);
-  }
-
-  @Nullable
-  private String findSourceVersion(ImmutableMap<Label, RuleIdeInfo> ruleMap) {
-    for (RuleIdeInfo rule : ruleMap.values()) {
-      if (rule.javaToolchainIdeInfo != null) {
-        return rule.javaToolchainIdeInfo.sourceVersion;
-      }
-    }
-    return null;
-  }
-
-  static class WorkspaceBuilder {
-    Set<String> jdeps = Sets.newHashSet();
-    Set<Label> directDeps = Sets.newHashSet();
-    Set<File> addedSourceFiles = Sets.newHashSet();
-    List<LibraryArtifact> generatedJars = Lists.newArrayList();
-    List<File> buildOutputJars = Lists.newArrayList();
-    List<SourceArtifact> sourceArtifacts = Lists.newArrayList();
-    Map<Label, ArtifactLocation> javaPackageManifests = Maps.newHashMap();
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/sync/importer/LibraryBuilder.java b/blaze-java/src/com/google/idea/blaze/java/sync/importer/LibraryBuilder.java
deleted file mode 100644
index 0d660f5..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/sync/importer/LibraryBuilder.java
+++ /dev/null
@@ -1,96 +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.importer;
-
-import com.google.common.collect.*;
-import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
-import com.google.idea.blaze.base.ideinfo.LibraryArtifact;
-import com.google.idea.blaze.base.model.primitives.Label;
-import com.google.idea.blaze.java.sync.model.BlazeLibrary;
-import com.google.idea.blaze.java.sync.model.LibraryKey;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Manages libraries during the module import stage.
- */
-public class LibraryBuilder {
-
-  private final Map<LibraryKey, BlazeLibrary> libraries = Maps.newHashMap();
-  private final Multimap<Label, LibraryKey> labelToLibraryKeys = ArrayListMultimap.create();
-  private final Map<String, LibraryKey> jdepsPathToLibraryKey = Maps.newHashMap();
-  private final Set<LibraryKey> referencedLibraryKeys = Sets.newHashSet();
-
-  void createLibraryForRule(Label label, LibraryArtifact libraryArtifact) {
-    LibraryKey libraryKey = createLibrary(libraryArtifact);
-    labelToLibraryKeys.put(label, libraryKey);
-  }
-
-  public void createLibraryForModule(LibraryArtifact libraryArtifact) {
-    LibraryKey libraryKey = createLibrary(libraryArtifact);
-    referencedLibraryKeys.add(libraryKey);
-  }
-
-  void referenceLibraryFromModule(Label label) {
-    Collection<LibraryKey> libraryKeys = labelToLibraryKeys.get(label);
-    referencedLibraryKeys.addAll(libraryKeys);
-  }
-
-  void referenceLibraryFromModule(String jdepsPath) {
-    LibraryKey libraryKey = jdepsPathToLibraryKey.get(jdepsPath);
-    if (libraryKey != null) {
-      referencedLibraryKeys.add(libraryKey);
-    }
-  }
-
-  private LibraryKey createLibrary(LibraryArtifact libraryArtifact) {
-    File jar = libraryArtifact.jar.getFile();
-    LibraryKey key = LibraryKey.fromJarFile(jar);
-    BlazeLibrary library = new BlazeLibrary(key, libraryArtifact);
-    addLibrary(key, library);
-    return key;
-  }
-
-  private void addLibrary(LibraryKey key,
-                          BlazeLibrary library) {
-    BlazeLibrary existingLibrary = libraries.putIfAbsent(key, library);
-    existingLibrary = existingLibrary != null ? existingLibrary : library;
-
-    LibraryArtifact libraryArtifact = existingLibrary.getLibraryArtifact();
-
-    // Index the library by jar for jdeps support
-    if (libraryArtifact != null) {
-      ArtifactLocation jar = libraryArtifact.jar;
-      jdepsPathToLibraryKey.put(jar.getExecutionRootRelativePath(), key);
-
-      ArtifactLocation runtimeJar = libraryArtifact.runtimeJar;
-      if (runtimeJar != null) {
-        jdepsPathToLibraryKey.put(runtimeJar.getExecutionRootRelativePath(), key);
-      }
-    }
-  }
-
-  ImmutableMap<LibraryKey, BlazeLibrary> build() {
-    ImmutableMap.Builder<LibraryKey, BlazeLibrary> result = ImmutableMap.builder();
-    for (LibraryKey libraryKey : referencedLibraryKeys) {
-      result.put(libraryKey, libraries.get(libraryKey));
-    }
-    return result.build();
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/sync/jdeps/JdepsFileReader.java b/blaze-java/src/com/google/idea/blaze/java/sync/jdeps/JdepsFileReader.java
deleted file mode 100644
index c7e1333..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/sync/jdeps/JdepsFileReader.java
+++ /dev/null
@@ -1,176 +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.jdeps;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.idea.blaze.base.async.FutureUtil;
-import com.google.idea.blaze.base.async.executor.BlazeExecutor;
-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.model.SyncState;
-import com.google.idea.blaze.base.model.primitives.Label;
-import com.google.idea.blaze.base.prefetch.PrefetchService;
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.google.idea.blaze.base.scope.Scope;
-import com.google.idea.blaze.base.scope.output.PrintOutput;
-import com.google.idea.blaze.base.scope.scopes.TimingScope;
-import com.google.idea.blaze.base.sync.filediff.FileDiffService;
-import com.google.repackaged.devtools.build.lib.view.proto.Deps;
-import com.intellij.openapi.diagnostic.Logger;
-
-import javax.annotation.Nullable;
-import java.io.*;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.atomic.AtomicLong;
-
-/**
- * Reads jdeps from the ide info result.
- */
-public class JdepsFileReader {
-  private static final Logger LOG = Logger.getInstance(JdepsFileReader.class);
-  private final FileDiffService fileDiffService = new FileDiffService();
-
-  static class JdepsState implements Serializable {
-    private static final long serialVersionUID = 2L;
-    private FileDiffService.State fileState = null;
-    private Map<File, Label> fileToLabelMap = Maps.newHashMap();
-    private Map<Label, List<String>> labelToJdeps = Maps.newHashMap();
-  }
-
-  private static class Result {
-    File file;
-    Label label;
-    List<String> dependencies;
-    public Result(File file, Label label, List<String> dependencies) {
-      this.file = file;
-      this.label = label;
-      this.dependencies = dependencies;
-    }
-  }
-
-  /**
-   * Loads any updated jdeps files since the last invocation of this method.
-   */
-  @Nullable
-  public JdepsMap loadJdepsFiles(BlazeContext parentContext,
-                                 ImmutableMap<Label, RuleIdeInfo> ruleMap,
-                                 SyncState.Builder syncStateBuilder,
-                                 @Nullable SyncState previousSyncState) {
-    JdepsState oldState = previousSyncState != null ? previousSyncState.get(JdepsState.class) : null;
-    JdepsState jdepsState = Scope.push(parentContext, (context) -> {
-      context.push(new TimingScope("LoadJdepsFiles"));
-      return doLoadJdepsFiles(context, oldState, ruleMap);
-    });
-    if (jdepsState == null) {
-      return null;
-    }
-    syncStateBuilder.put(JdepsState.class, jdepsState);
-    return label -> jdepsState.labelToJdeps.get(label);
-  }
-
-  private JdepsState doLoadJdepsFiles(BlazeContext context,
-                                      @Nullable JdepsState oldState,
-                                      ImmutableMap<Label, RuleIdeInfo> ruleMap) {
-    JdepsState state = new JdepsState();
-    if (oldState != null) {
-      state.labelToJdeps = Maps.newHashMap(oldState.labelToJdeps);
-      state.fileToLabelMap = Maps.newHashMap(oldState.fileToLabelMap);
-    }
-
-    List<File> files = Lists.newArrayList();
-    for (RuleIdeInfo ruleIdeInfo : ruleMap.values()) {
-      JavaRuleIdeInfo javaRuleIdeInfo = ruleIdeInfo.javaRuleIdeInfo;
-      if (javaRuleIdeInfo != null) {
-        ArtifactLocation jdepsFile = javaRuleIdeInfo.jdepsFile;
-        if (jdepsFile != null) {
-          files.add(jdepsFile.getFile());
-        }
-      }
-    }
-
-    List<File> updatedFiles = Lists.newArrayList();
-    List<File> removedFiles = Lists.newArrayList();
-    state.fileState = fileDiffService.updateFiles(oldState != null ? oldState.fileState : null, files, updatedFiles, removedFiles);
-
-    ListenableFuture<?> fetchFuture = PrefetchService.getInstance().prefetchFiles(updatedFiles, true);
-    if (!FutureUtil.waitForFuture(context, fetchFuture)
-      .timed("FetchJdeps")
-      .run()
-      .success()) {
-      return null;
-    }
-
-    for (File removedFile : removedFiles) {
-      Label label = state.fileToLabelMap.remove(removedFile);
-      if (label != null) {
-        state.labelToJdeps.remove(label);
-      }
-    }
-
-    AtomicLong totalSizeLoaded = new AtomicLong(0);
-
-    List<ListenableFuture<Result>> futures = Lists.newArrayList();
-    for (File updatedFile : updatedFiles) {
-      futures.add(submit(() -> {
-        totalSizeLoaded.addAndGet(updatedFile.length());
-        try (InputStream inputStream = new FileInputStream(updatedFile)){
-          Deps.Dependencies dependencies = Deps.Dependencies.parseFrom(inputStream);
-          if (dependencies != null) {
-            if (dependencies.hasRuleLabel()) {
-              Label label = new Label(dependencies.getRuleLabel());
-              List<String> dependencyStringList = Lists.newArrayList();
-              for (Deps.Dependency dependency : dependencies.getDependencyList()) {
-                dependencyStringList.add(dependency.getPath());
-              }
-              return new Result(updatedFile, label, dependencyStringList);
-            }
-          }
-        } catch (FileNotFoundException e) {
-          LOG.info("Could not open jdeps file: " + updatedFile);
-        }
-        return null;
-      }));
-    }
-    try {
-      for (Result result : Futures.allAsList(futures).get()) {
-        if (result != null) {
-          state.fileToLabelMap.put(result.file, result.label);
-          state.labelToJdeps.put(result.label, result.dependencies);
-        }
-      }
-      context.output(new PrintOutput(String.format(
-        "Loaded %d jdeps files, total size %dkB", updatedFiles.size(), totalSizeLoaded.get() / 1024
-      )));
-    }
-    catch (InterruptedException | ExecutionException e) {
-      LOG.error(e);
-      return null;
-    }
-    return state;
-  }
-
-  private static <T> ListenableFuture<T> submit(Callable<T> callable) {
-    return BlazeExecutor.getInstance().submit(callable);
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/sync/jdeps/JdepsMap.java b/blaze-java/src/com/google/idea/blaze/java/sync/jdeps/JdepsMap.java
deleted file mode 100644
index 9d7b301..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/sync/jdeps/JdepsMap.java
+++ /dev/null
@@ -1,39 +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.jdeps;
-
-import com.google.idea.blaze.base.model.primitives.Label;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.util.List;
-
-/**
- * Map of rule -> jdeps dependencies.
- */
-public interface JdepsMap {
-  /**
-   * For a given rule, returns workspace root relative paths of artifacts that
-   * were used during compilation.
-   *
-   * It's not specified whether jars or ijars are used during compilation.
-   *
-   * If the rule doesn't have source or otherwise wasn't instrumented,
-   * null is returned.
-   */
-  @Nullable
-  List<String> getDependenciesForRule(@NotNull Label label);
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/sync/model/BlazeContentEntry.java b/blaze-java/src/com/google/idea/blaze/java/sync/model/BlazeContentEntry.java
deleted file mode 100644
index ebb4d70..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/sync/model/BlazeContentEntry.java
+++ /dev/null
@@ -1,89 +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.model;
-
-import com.google.common.base.Objects;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-
-import javax.annotation.concurrent.Immutable;
-import java.io.File;
-import java.io.Serializable;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Corresponds to an IntelliJ content entry.
- */
-@Immutable
-public class BlazeContentEntry implements Serializable {
-  private static final long serialVersionUID = 1L;
-
-  public final File contentRoot;
-  public final ImmutableList<BlazeSourceDirectory> sources;
-
-  public BlazeContentEntry(File contentRoot,
-                           ImmutableList<BlazeSourceDirectory> sources) {
-    this.contentRoot = contentRoot;
-    this.sources = sources;
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) return true;
-    if (o == null || getClass() != o.getClass()) return false;
-    BlazeContentEntry that = (BlazeContentEntry)o;
-    return Objects.equal(contentRoot, that.contentRoot) &&
-           Objects.equal(sources, that.sources);
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hashCode(contentRoot, sources);
-  }
-
-  @Override
-  public String toString() {
-    return "BlazeContentEntry {\n"
-           + "  contentRoot: " + contentRoot + "\n"
-           + "  sources: " + sources + "\n"
-           + '}';
-  }
-
-  public static Builder builder(String contentRoot) {
-    return new Builder(new File(contentRoot));
-  }
-
-  public static Builder builder(File contentRoot) {
-    return new Builder(contentRoot);
-  }
-
-  public static class Builder {
-    File contentRoot;
-    List<BlazeSourceDirectory> sources = Lists.newArrayList();
-    public Builder(File contentRoot) {
-      this.contentRoot = contentRoot;
-    }
-    public Builder addSource(BlazeSourceDirectory sourceDirectory) {
-      this.sources.add(sourceDirectory);
-      return this;
-    }
-    public BlazeContentEntry build() {
-      Collections.sort(sources, BlazeSourceDirectory.COMPARATOR);
-      return new BlazeContentEntry(contentRoot, ImmutableList.copyOf(sources));
-    }
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/sync/model/BlazeJavaImportResult.java b/blaze-java/src/com/google/idea/blaze/java/sync/model/BlazeJavaImportResult.java
deleted file mode 100644
index ac43b94..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/sync/model/BlazeJavaImportResult.java
+++ /dev/null
@@ -1,52 +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.model;
-
-import com.google.common.collect.ImmutableCollection;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-
-import javax.annotation.Nullable;
-import javax.annotation.concurrent.Immutable;
-import java.io.File;
-import java.io.Serializable;
-
-/**
- * The result of a blaze import operation.
- */
-@Immutable
-public class BlazeJavaImportResult implements Serializable {
-  private static final long serialVersionUID = 3L;
-
-  public final ImmutableList<BlazeContentEntry> contentEntries;
-  public final ImmutableMap<LibraryKey, BlazeLibrary> libraries;
-  public final ImmutableCollection<File> buildOutputJars;
-  public final ImmutableSet<File> javaSourceFiles;
-  @Nullable public final String sourceVersion;
-
-  public BlazeJavaImportResult(ImmutableList<BlazeContentEntry> contentEntries,
-                               ImmutableMap<LibraryKey, BlazeLibrary> libraries,
-                               ImmutableCollection<File> buildOutputJars,
-                               ImmutableSet<File> javaSourceFiles,
-                               @Nullable String sourceVersion) {
-    this.contentEntries = contentEntries;
-    this.libraries = libraries;
-    this.buildOutputJars = buildOutputJars;
-    this.javaSourceFiles = javaSourceFiles;
-    this.sourceVersion = sourceVersion;
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/sync/model/BlazeJavaSyncData.java b/blaze-java/src/com/google/idea/blaze/java/sync/model/BlazeJavaSyncData.java
deleted file mode 100644
index 44367b9..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/sync/model/BlazeJavaSyncData.java
+++ /dev/null
@@ -1,39 +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.model;
-
-import com.google.idea.blaze.base.projectview.section.Glob;
-
-import java.io.Serializable;
-
-/**
- * Sync data for the java plugin.
- */
-public class BlazeJavaSyncData implements Serializable {
-  private static final long serialVersionUID = 2L;
-
-  public final BlazeJavaImportResult importResult;
-  public final Glob.GlobSet excludedLibraries;
-  public final boolean attachSourceJarsByDefault;
-
-  public BlazeJavaSyncData(BlazeJavaImportResult importResult,
-                           Glob.GlobSet excludedLibraries,
-                           boolean attachSourceJarsByDefault) {
-    this.importResult = importResult;
-    this.excludedLibraries = excludedLibraries;
-    this.attachSourceJarsByDefault = attachSourceJarsByDefault;
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/sync/model/BlazeLibrary.java b/blaze-java/src/com/google/idea/blaze/java/sync/model/BlazeLibrary.java
deleted file mode 100644
index a656a1b..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/sync/model/BlazeLibrary.java
+++ /dev/null
@@ -1,109 +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.model;
-
-import com.google.common.base.Objects;
-import com.google.idea.blaze.base.ideinfo.LibraryArtifact;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import javax.annotation.concurrent.Immutable;
-import java.io.File;
-import java.io.Serializable;
-import java.util.Collection;
-
-/**
- * An immutable reference to a .jar required by a rule. This class supports value semantics when
- * used as a key to a hash map.
- */
-@Immutable
-public final class BlazeLibrary implements Serializable {
-  private static final long serialVersionUID = 6L;
-
-  @NotNull
-  private final LibraryKey key;
-
-  @Nullable
-  private final LibraryArtifact libraryArtifact;
-
-  @Nullable
-  private final Collection<File> sources;
-
-  public BlazeLibrary(
-    @NotNull LibraryKey key,
-    @NotNull LibraryArtifact libraryArtifact) {
-    this(key, libraryArtifact, null);
-  }
-
-  public BlazeLibrary(
-    @NotNull LibraryKey key,
-    @NotNull Collection<File> sources) {
-    this(key, null, sources);
-  }
-
-  private BlazeLibrary(
-    @NotNull LibraryKey key,
-    @Nullable LibraryArtifact libraryArtifact,
-    @Nullable Collection<File> sources) {
-    this.key = key;
-    this.libraryArtifact = libraryArtifact;
-    this.sources = sources;
-  }
-
-  /**
-   * Returns the library key.
-   */
-  @NotNull
-  public LibraryKey getKey() {
-    return key;
-  }
-
-  @Nullable
-  public LibraryArtifact getLibraryArtifact() {
-    return libraryArtifact;
-  }
-
-  @Nullable
-  public Collection<File> getSources() {
-    return sources;
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hashCode(key, libraryArtifact, sources);
-  }
-
-  @Override
-  public String toString() {
-    return key.toString();
-  }
-
-  @Override
-  public boolean equals(Object other) {
-    if (this == other) {
-      return true;
-    }
-    if (!(other instanceof BlazeLibrary)) {
-      return false;
-    }
-
-    BlazeLibrary that = (BlazeLibrary)other;
-
-    return Objects.equal(key, that.key)
-           && Objects.equal(libraryArtifact, that.libraryArtifact)
-           && Objects.equal(sources, that.sources);
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/sync/model/BlazeSourceDirectory.java b/blaze-java/src/com/google/idea/blaze/java/sync/model/BlazeSourceDirectory.java
deleted file mode 100644
index f0b89d5..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/sync/model/BlazeSourceDirectory.java
+++ /dev/null
@@ -1,175 +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.model;
-
-import com.google.common.base.Objects;
-import org.jetbrains.annotations.NotNull;
-
-import javax.annotation.concurrent.Immutable;
-import java.io.File;
-import java.io.Serializable;
-import java.util.Comparator;
-
-/**
- * A source directory.
- */
-@Immutable
-public final class BlazeSourceDirectory implements Serializable {
-  private static final long serialVersionUID = 2L;
-
-  public static final Comparator<BlazeSourceDirectory> COMPARATOR =
-    (o1, o2) -> String.CASE_INSENSITIVE_ORDER.compare(
-      o1.getDirectory().getPath(),
-      o2.getDirectory().getPath());
-
-  @NotNull
-  private final File directory;
-  private final boolean isTest;
-  private final boolean isGenerated;
-  private final boolean isResource;
-  @NotNull
-  private final String packagePrefix;
-
-  public static class Builder {
-    @NotNull private final File directory;
-    @NotNull private String packagePrefix = "";
-    private boolean isTest;
-    private boolean isResource;
-    private boolean isGenerated;
-
-    private Builder(@NotNull File directory) {
-      this.directory = directory;
-    }
-    public Builder setPackagePrefix(@NotNull String packagePrefix) {
-      this.packagePrefix = packagePrefix;
-      return this;
-    }
-    public Builder setTest(boolean isTest) {
-      this.isTest = isTest;
-      return this;
-    }
-    public Builder setResource(boolean isResource) {
-      this.isResource = isResource;
-      return this;
-    }
-    public Builder setGenerated(boolean isGenerated) {
-      this.isGenerated = isGenerated;
-      return this;
-    }
-
-    public BlazeSourceDirectory build() {
-      return new BlazeSourceDirectory(directory, isTest, isResource, isGenerated, packagePrefix);
-    }
-  }
-
-  @NotNull
-  public static Builder builder(@NotNull String directory) {
-    return new Builder(new File(directory));
-  }
-
-  @NotNull
-  public static Builder builder(@NotNull File directory) {
-    return new Builder(directory);
-  }
-
-  private BlazeSourceDirectory(
-    @NotNull File directory,
-    boolean isTest,
-    boolean isResource,
-    boolean isGenerated,
-    @NotNull String packagePrefix) {
-    this.directory = directory;
-    this.isTest = isTest;
-    this.isResource = isResource;
-    this.isGenerated = isGenerated;
-    this.packagePrefix = packagePrefix;
-  }
-
-  /**
-   * Returns the full path name of the root of a source directory.
-   */
-  @NotNull
-  public File getDirectory() {
-    return directory;
-  }
-
-  /**
-   * Returns {@code true} if the directory contains test sources.
-   */
-  public boolean getIsTest() {
-    return isTest;
-  }
-
-  /**
-   * Returns {@code true} if the directory contains resources.
-   */
-  public boolean getIsResource() {
-    return isResource;
-  }
-
-  /**
-   * Returns {@code true} if the directory contains generated files.
-   */
-  public boolean getIsGenerated() {
-    return isGenerated;
-  }
-
-  /**
-   * Returns the package prefix for the directory. If the directory is a source root, such as a
-   * "src" directory, then this returns an empty string.
-   */
-  @NotNull
-  public String getPackagePrefix() {
-    return packagePrefix;
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hashCode(
-      directory,
-      isTest,
-      isResource,
-      packagePrefix,
-      isGenerated);
-  }
-
-  @Override
-  public boolean equals(Object other) {
-    if (this == other) {
-      return true;
-    }
-    if (!(other instanceof BlazeSourceDirectory)) {
-      return false;
-    }
-    BlazeSourceDirectory that = (BlazeSourceDirectory)other;
-    return directory.equals(that.directory)
-           && packagePrefix.equals(that.packagePrefix)
-           && isResource == that.isResource
-           && isTest == that.isTest
-           && isGenerated == that.isGenerated;
-  }
-
-  @Override
-  public String toString() {
-    return "BlazeSourceDirectory {\n"
-           + "  directory: " + directory + "\n"
-           + "  isTest: " + isTest + "\n"
-           + "  isGenerated: " + isGenerated + "\n"
-           + "  isResource: " + isResource + "\n"
-           + "  packagePrefix: " + packagePrefix + "\n"
-           + '}';
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/sync/model/LibraryKey.java b/blaze-java/src/com/google/idea/blaze/java/sync/model/LibraryKey.java
deleted file mode 100644
index 1ab31b5..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/sync/model/LibraryKey.java
+++ /dev/null
@@ -1,100 +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.model;
-
-import com.intellij.openapi.roots.libraries.Library;
-import com.intellij.openapi.util.io.FileUtil;
-import org.jetbrains.annotations.NotNull;
-
-import javax.annotation.concurrent.Immutable;
-import java.io.File;
-import java.io.Serializable;
-import java.util.Comparator;
-
-/**
- * Uniquely identifies a library as imported into IntellJ.
- */
-@Immutable
-public final class LibraryKey implements Serializable {
-  public static final long serialVersionUID = 1L;
-
-  public static final Comparator<LibraryKey> COMPARATOR = new Comparator<LibraryKey>() {
-    @Override
-    public int compare(LibraryKey o1, LibraryKey o2) {
-      return String.CASE_INSENSITIVE_ORDER.compare(o1.name, o2.name);
-    }
-  };
-
-  @NotNull
-  private final String name;
-
-  @NotNull
-  public static LibraryKey fromJarFile(@NotNull File jarFile) {
-    int parentHash = jarFile.getParent().hashCode();
-    String name = FileUtil.getNameWithoutExtension(jarFile) + "_" + Integer.toHexString(parentHash);
-    return new LibraryKey(name);
-  }
-
-  @NotNull
-  public static LibraryKey forResourceLibrary() {
-    return new LibraryKey("external_resources_library");
-  }
-
-  @NotNull
-  public static LibraryKey fromIntelliJLibrary(@NotNull Library library) {
-    String name = library.getName();
-    if (name == null) {
-      throw new IllegalArgumentException("Null library name");
-    }
-    return fromIntelliJLibraryName(name);
-  }
-
-  @NotNull
-  public static LibraryKey fromIntelliJLibraryName(@NotNull String name) {
-    return new LibraryKey(name);
-  }
-
-  LibraryKey(@NotNull String name) {
-    this.name = name;
-  }
-
-  public String getIntelliJLibraryName() {
-    return name;
-  }
-
-  @Override
-  public String toString() {
-    return name;
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) {
-      return true;
-    }
-    if (o == null || getClass() != o.getClass()) {
-      return false;
-    }
-
-    LibraryKey that = (LibraryKey)o;
-    return name.equals(that.name);
-  }
-
-  @Override
-  public int hashCode() {
-    return name.hashCode();
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/sync/projectstructure/Jdks.java b/blaze-java/src/com/google/idea/blaze/java/sync/projectstructure/Jdks.java
deleted file mode 100644
index c63b1dd..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/sync/projectstructure/Jdks.java
+++ /dev/null
@@ -1,206 +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.projectstructure;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.model.primitives.LanguageClass;
-import com.google.idea.blaze.base.sync.sdk.DefaultSdkProvider;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.projectRoots.JavaSdk;
-import com.intellij.openapi.projectRoots.JavaSdkVersion;
-import com.intellij.openapi.projectRoots.ProjectJdkTable;
-import com.intellij.openapi.projectRoots.Sdk;
-import com.intellij.pom.java.LanguageLevel;
-import com.intellij.util.SystemProperties;
-import org.jetbrains.annotations.NonNls;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-
-import static com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil.createAndAddSDK;
-import static com.intellij.openapi.util.io.FileUtil.notNullize;
-import static com.intellij.openapi.util.text.StringUtil.isNotEmpty;
-import static java.util.Collections.emptyList;
-
-/**
- * Utility methods related to IDEA JDKs.
- */
-public class Jdks {
-  @NonNls
-  private static final LanguageLevel DEFAULT_LANG_LEVEL = LanguageLevel.JDK_1_7;
-
-  @Nullable
-  public static Sdk chooseOrCreateJavaSdk(LanguageLevel langLevel) {
-    for (Sdk sdk : ProjectJdkTable.getInstance().getAllJdks()) {
-      if (isApplicableJdk(sdk, langLevel)) {
-        return sdk;
-      }
-    }
-    String jdkHomePath = null;
-    for (DefaultSdkProvider defaultSdkProvider : DefaultSdkProvider.EP_NAME.getExtensions()) {
-      File sdk = defaultSdkProvider.provideSdkForLanguage(LanguageClass.JAVA);
-      if (sdk != null) {
-        jdkHomePath = sdk.getPath();
-        break;
-      }
-    }
-
-    if (jdkHomePath == null) {
-      jdkHomePath = getJdkHomePath(langLevel);
-    }
-
-    if (jdkHomePath == null) {
-      return null;
-    }
-
-    return createJdk(jdkHomePath);
-  }
-
-  public static boolean isApplicableJdk(@NotNull Sdk jdk, @Nullable LanguageLevel langLevel) {
-    if (!(jdk.getSdkType() instanceof JavaSdk)) {
-      return false;
-    }
-    if (langLevel == null) {
-      langLevel = DEFAULT_LANG_LEVEL;
-    }
-    JavaSdkVersion version = JavaSdk.getInstance().getVersion(jdk);
-    if (version != null) {
-      //noinspection TestOnlyProblems
-      return hasMatchingLangLevel(version, langLevel);
-    }
-    return false;
-  }
-
-  @Nullable
-  public static String getJdkHomePath(@NotNull LanguageLevel langLevel) {
-    Collection<String> jdkHomePaths = new ArrayList<String>(JavaSdk.getInstance().suggestHomePaths());
-    if (jdkHomePaths.isEmpty()) {
-      return null;
-    }
-    // prefer jdk path of getJavaHome(), since we have to allow access to it in tests
-    // see AndroidProjectDataServiceTest#testImportData()
-    final List<String> list = new ArrayList<String>();
-    String javaHome = SystemProperties.getJavaHome();
-
-    if (javaHome != null && !javaHome.isEmpty()) {
-      for (Iterator<String> it = jdkHomePaths.iterator(); it.hasNext(); ) {
-        final String path = it.next();
-
-        if (path != null && javaHome.startsWith(path)) {
-          it.remove();
-          list.add(path);
-        }
-      }
-    }
-    list.addAll(jdkHomePaths);
-    return getBestJdkHomePath(list, langLevel);
-
-  }
-
-  @VisibleForTesting
-  @Nullable
-  static String getBestJdkHomePath(@NotNull Collection<String> jdkHomePaths, @NotNull LanguageLevel langLevel) {
-    // Search for JDKs in both the suggest folder and all its sub folders.
-    List<String> roots = Lists.newArrayList();
-    for (String jdkHomePath : jdkHomePaths) {
-      if (isNotEmpty(jdkHomePath)) {
-        roots.add(jdkHomePath);
-        roots.addAll(getChildrenPaths(jdkHomePath));
-      }
-    }
-    return getBestJdk(roots, langLevel);
-  }
-
-  @NotNull
-  private static List<String> getChildrenPaths(@NotNull String dirPath) {
-    File dir = new File(dirPath);
-    if (!dir.isDirectory()) {
-      return emptyList();
-    }
-    List<String> childrenPaths = Lists.newArrayList();
-    for (File child : notNullize(dir.listFiles())) {
-      boolean directory = child.isDirectory();
-      if (directory) {
-        childrenPaths.add(child.getAbsolutePath());
-      }
-    }
-    return childrenPaths;
-  }
-
-  @Nullable
-  private static String getBestJdk(@NotNull List<String> jdkRoots, @NotNull LanguageLevel langLevel) {
-    String bestJdk = null;
-    for (String jdkRoot : jdkRoots) {
-      if (JavaSdk.getInstance().isValidSdkHome(jdkRoot)) {
-        if (bestJdk == null && hasMatchingLangLevel(jdkRoot, langLevel)) {
-          bestJdk = jdkRoot;
-        }
-        else if (bestJdk != null) {
-          bestJdk = selectJdk(bestJdk, jdkRoot, langLevel);
-        }
-      }
-    }
-    return bestJdk;
-  }
-
-  @Nullable
-  private static String selectJdk(@NotNull String jdk1, @NotNull String jdk2, @NotNull LanguageLevel langLevel) {
-    if (hasMatchingLangLevel(jdk1, langLevel)) {
-      return jdk1;
-    }
-    if (hasMatchingLangLevel(jdk2, langLevel)) {
-      return jdk2;
-    }
-    return null;
-  }
-
-  private static boolean hasMatchingLangLevel(@NotNull String jdkRoot, @NotNull LanguageLevel langLevel) {
-    JavaSdkVersion version = getVersion(jdkRoot);
-    return hasMatchingLangLevel(version, langLevel);
-  }
-
-  @VisibleForTesting
-  static boolean hasMatchingLangLevel(@NotNull JavaSdkVersion jdkVersion, @NotNull LanguageLevel langLevel) {
-    LanguageLevel max = jdkVersion.getMaxLanguageLevel();
-    return max.isAtLeast(langLevel);
-  }
-
-  @NotNull
-  private static JavaSdkVersion getVersion(@NotNull String jdkRoot) {
-    String version = JavaSdk.getInstance().getVersionString(jdkRoot);
-    if (version == null) {
-      return JavaSdkVersion.JDK_1_0;
-    }
-    JavaSdkVersion sdkVersion = JavaSdk.getInstance().getVersion(version);
-    return sdkVersion == null ? JavaSdkVersion.JDK_1_0 : sdkVersion;
-  }
-
-  @Nullable
-  public static Sdk createJdk(@NotNull String jdkHomePath) {
-    Sdk jdk = createAndAddSDK(jdkHomePath, JavaSdk.getInstance());
-    if (jdk == null) {
-      String msg = String.format("Unable to create JDK from path '%1$s'", jdkHomePath);
-      Logger.getInstance(Jdks.class).error(msg);
-    }
-    return jdk;
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/sync/projectstructure/LibraryEditor.java b/blaze-java/src/com/google/idea/blaze/java/sync/projectstructure/LibraryEditor.java
deleted file mode 100644
index a454628..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/sync/projectstructure/LibraryEditor.java
+++ /dev/null
@@ -1,210 +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.projectstructure;
-
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-import com.google.idea.blaze.base.ideinfo.LibraryArtifact;
-import com.google.idea.blaze.base.model.BlazeProjectData;
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.google.idea.blaze.base.scope.output.PrintOutput;
-import com.google.idea.blaze.base.settings.BlazeUserSettings;
-import com.google.idea.blaze.java.libraries.SourceJarManager;
-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;
-import com.intellij.openapi.roots.OrderRootType;
-import com.intellij.openapi.roots.impl.libraries.ProjectLibraryTable;
-import com.intellij.openapi.roots.libraries.Library;
-import com.intellij.openapi.roots.libraries.LibraryTable;
-import com.intellij.openapi.util.io.FileUtil;
-import com.intellij.openapi.util.io.FileUtilRt;
-import com.intellij.openapi.vfs.StandardFileSystems;
-import com.intellij.openapi.vfs.VirtualFileManager;
-import com.intellij.util.io.URLUtil;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-/**
- * Edits IntelliJ libraries
- */
-public class LibraryEditor {
-  private static final Logger LOG = Logger.getInstance(LibraryEditor.class);
-
-  public static void updateProjectLibraries(Project project,
-                                            BlazeContext context,
-                                            BlazeProjectData blazeProjectData,
-                                            Collection<BlazeLibrary> newLibraries,
-                                            Collection<BlazeLibrary> oldLibraries) {
-    Set<LibraryKey> intelliJLibraryState = Sets.newHashSet();
-    for (Library library : ProjectLibraryTable.getInstance(project).getLibraries()) {
-      String name = library.getName();
-      if (name != null) {
-        intelliJLibraryState.add(LibraryKey.fromIntelliJLibraryName(name));
-      }
-    }
-    Collection<BlazeLibrary> librariesToUpdate = getUpdatedObjects(oldLibraries,
-                                                                   newLibraries,
-                                                                   intelliJLibraryState);
-    if (oldLibraries.isEmpty()) {
-      context.output(new PrintOutput(
-        String.format(
-          "Importing %d libraries",
-          librariesToUpdate.size())));
-    }
-    else {
-      String consoleMessage = String.format(
-        "Total libraries: %d\n"
-        + "Updating %d modified libraries",
-        newLibraries.size(),
-        librariesToUpdate.size());
-      context.output(new PrintOutput(consoleMessage));
-    }
-
-    Set<String> externallyAddedLibraries = Sets.newHashSet();
-    for (BlazeJavaSyncAugmenter syncAugmenter : BlazeJavaSyncAugmenter.EP_NAME.getExtensions()) {
-      externallyAddedLibraries.addAll(syncAugmenter.getExternallyAddedLibraries(blazeProjectData));
-    }
-
-    LibraryTable libraryTable = ProjectLibraryTable.getInstance(project);
-    LibraryTable.ModifiableModel libraryTableModel =
-      libraryTable.getModifiableModel();
-    try {
-      boolean attachSourcesByDefault = BlazeUserSettings.getInstance().getAttachSourcesByDefault();
-      SourceJarManager sourceJarManager = SourceJarManager.getInstance(project);
-      for (BlazeLibrary library : librariesToUpdate) {
-        boolean attachSources = attachSourcesByDefault || sourceJarManager.hasSourceJarAttached(library.getKey());
-        updateLibrary(libraryTable, libraryTableModel, library, attachSources);
-      }
-
-      // Garbage collect unused libraries
-      Set<LibraryKey> newLibraryKeys = newLibraries.stream().map(BlazeLibrary::getKey).collect(Collectors.toSet());
-      for (LibraryKey libraryKey : intelliJLibraryState) {
-        String libraryIntellijName = libraryKey.getIntelliJLibraryName();
-        if (!newLibraryKeys.contains(libraryKey) && !externallyAddedLibraries.contains(libraryIntellijName)) {
-          Library library = libraryTable.getLibraryByName(libraryIntellijName);
-          if (library != null) {
-            libraryTableModel.removeLibrary(library);
-          }
-        }
-      }
-    }
-    finally {
-      libraryTableModel.commit();
-    }
-  }
-
-
-  public static void updateLibrary(
-    LibraryTable libraryTable,
-    LibraryTable.ModifiableModel libraryTableModel,
-    BlazeLibrary blazeLibrary,
-    boolean attachSourceJar) {
-    String libraryName = blazeLibrary.getKey().getIntelliJLibraryName();
-    Library library = libraryTable.getLibraryByName(libraryName);
-    if (library != null) {
-      libraryTableModel.removeLibrary(library);
-    }
-    library = libraryTableModel.createLibrary(libraryName);
-
-    Library.ModifiableModel libraryModel = library.getModifiableModel();
-    try {
-      LibraryArtifact libraryArtifact = blazeLibrary.getLibraryArtifact();
-      if (libraryArtifact != null) {
-        libraryModel.addRoot(
-          pathToUrl(libraryArtifact.jar.getFile()),
-          OrderRootType.CLASSES
-        );
-        if (attachSourceJar && libraryArtifact.sourceJar != null) {
-          libraryModel.addRoot(
-            pathToUrl(libraryArtifact.sourceJar.getFile()),
-            OrderRootType.SOURCES
-          );
-        }
-      }
-      if (blazeLibrary.getSources() != null) {
-        for (File file : blazeLibrary.getSources()) {
-          libraryModel.addRoot(
-            pathToUrl(file),
-            OrderRootType.SOURCES
-          );
-        }
-      }
-    }
-    finally {
-      libraryModel.commit();
-    }
-  }
-
-  static Collection<BlazeLibrary> getUpdatedObjects(Collection<BlazeLibrary> oldObjects,
-                                                    Collection<BlazeLibrary> newObjects,
-                                                    Set<LibraryKey> intelliJState) {
-    List<BlazeLibrary> result = Lists.newArrayList();
-    Set<BlazeLibrary> oldObjectSet = Sets.newHashSet(oldObjects);
-    for (BlazeLibrary value : newObjects) {
-      LibraryKey key = value.getKey();
-      if (!intelliJState.contains(key) || !oldObjectSet.contains(value)) {
-        result.add(value);
-      }
-    }
-    return result;
-  }
-
-  private static String pathToUrl(File path) {
-    String name = path.getName();
-    boolean isJarFile = FileUtilRt.extensionEquals(name, "jar") ||
-                        FileUtilRt.extensionEquals(name, "zip");
-    // .jar files require an URL with "jar" protocol.
-    String protocol = isJarFile
-                      ? StandardFileSystems.JAR_PROTOCOL
-                      : StandardFileSystems.FILE_PROTOCOL;
-    String filePath = FileUtil.toSystemIndependentName(path.getPath());
-    String url = VirtualFileManager.constructUrl(protocol, filePath);
-    if (isJarFile) {
-      url += URLUtil.JAR_SEPARATOR;
-    }
-    return url;
-  }
-
-  public static void configureDependencies(
-    Project project,
-    BlazeContext context,
-    ModifiableRootModel modifiableRootModel,
-    Collection<BlazeLibrary> libraries) {
-    for (BlazeLibrary library : libraries) {
-      updateLibraryDependency(modifiableRootModel, library.getKey());
-    }
-  }
-
-  private static void updateLibraryDependency(
-    ModifiableRootModel model,
-    LibraryKey libraryKey) {
-    LibraryTable libraryTable = ProjectLibraryTable.getInstance(model.getProject());
-    Library library = libraryTable.getLibraryByName(libraryKey.getIntelliJLibraryName());
-    if (library == null) {
-      LOG.error("Library missing: " + libraryKey.getIntelliJLibraryName() + ". Please resync project to resolve.");
-      return;
-    }
-    model.addLibraryEntry(library);
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/sync/projectstructure/SourceFolderEditor.java b/blaze-java/src/com/google/idea/blaze/java/sync/projectstructure/SourceFolderEditor.java
deleted file mode 100644
index a24d559..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/sync/projectstructure/SourceFolderEditor.java
+++ /dev/null
@@ -1,118 +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.projectstructure;
-
-import com.google.common.base.Strings;
-import com.google.common.collect.Maps;
-import com.google.idea.blaze.java.sync.model.BlazeContentEntry;
-import com.google.idea.blaze.java.sync.model.BlazeJavaImportResult;
-import com.google.idea.blaze.java.sync.model.BlazeSourceDirectory;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.roots.ContentEntry;
-import com.intellij.openapi.roots.SourceFolder;
-import com.intellij.openapi.util.io.FileUtil;
-import com.intellij.openapi.vfs.VfsUtilCore;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.util.io.URLUtil;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.jps.model.JpsElement;
-import org.jetbrains.jps.model.java.JavaResourceRootType;
-import org.jetbrains.jps.model.java.JavaSourceRootProperties;
-import org.jetbrains.jps.model.module.JpsModuleSourceRoot;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.Map;
-
-public class SourceFolderEditor {
-  private static final Logger LOG = Logger.getInstance(SourceFolderEditor.class);
-
-  public static void modifyContentEntries(
-    BlazeJavaImportResult importResult,
-    Collection<ContentEntry> contentEntries) {
-
-    Map<File, BlazeContentEntry> contentEntryMap = Maps.newHashMap();
-    for (BlazeContentEntry contentEntry : importResult.contentEntries) {
-      contentEntryMap.put(contentEntry.contentRoot, contentEntry);
-    }
-
-    for (ContentEntry contentEntry : contentEntries) {
-      VirtualFile virtualFile = contentEntry.getFile();
-      if (virtualFile == null) {
-        continue;
-      }
-
-      File contentRoot = new File(virtualFile.getPath());
-      BlazeContentEntry javaContentEntry = contentEntryMap.get(contentRoot);
-      if (javaContentEntry != null) {
-        for (BlazeSourceDirectory sourceDirectory : javaContentEntry.sources) {
-          addSourceFolderToContentEntry(
-            contentEntry,
-            sourceDirectory
-          );
-        }
-      }
-    }
-  }
-
-  private static void addSourceFolderToContentEntry(
-    ContentEntry contentEntry,
-    BlazeSourceDirectory sourceDirectory) {
-    File sourceDir = sourceDirectory.getDirectory();
-
-    // Create the source folder
-    SourceFolder sourceFolder;
-    if (sourceDirectory.getIsResource()) {
-      JavaResourceRootType resourceRootType = sourceDirectory.getIsTest()
-                                              ? JavaResourceRootType.TEST_RESOURCE
-                                              : JavaResourceRootType.RESOURCE;
-      sourceFolder = contentEntry.addSourceFolder(pathToUrl(sourceDir.getPath()),
-                                                  resourceRootType);
-    }
-    else {
-      sourceFolder = contentEntry
-        .addSourceFolder(pathToUrl(sourceDir.getPath()), sourceDirectory.getIsTest());
-    }
-    JpsModuleSourceRoot sourceRoot = sourceFolder.getJpsElement();
-    JpsElement properties = sourceRoot.getProperties();
-    if (properties instanceof JavaSourceRootProperties) {
-      JavaSourceRootProperties rootProperties = (JavaSourceRootProperties)properties;
-      if (sourceDirectory.getIsGenerated()) {
-        rootProperties.setForGeneratedSources(true);
-      }
-      String packagePrefix = sourceDirectory.getPackagePrefix();
-      if (!Strings.isNullOrEmpty(packagePrefix)) {
-        rootProperties.setPackagePrefix(packagePrefix);
-      }
-    }
-  }
-
-  @NotNull
-  private static String pathToUrl(@NotNull String filePath) {
-    filePath = FileUtil.toSystemIndependentName(filePath);
-    if (filePath.endsWith(".srcjar") || filePath.endsWith(".jar")) {
-      return URLUtil.JAR_PROTOCOL + URLUtil.SCHEME_SEPARATOR +
-             filePath + URLUtil.JAR_SEPARATOR;
-    }
-    else if (filePath.contains("src.jar!")) {
-      return URLUtil.JAR_PROTOCOL + URLUtil.SCHEME_SEPARATOR +
-             filePath;
-    }
-    else {
-      return VfsUtilCore.pathToUrl(filePath);
-    }
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/sync/source/FilePathJavaPackageReader.java b/blaze-java/src/com/google/idea/blaze/java/sync/source/FilePathJavaPackageReader.java
deleted file mode 100644
index ba9f1f7..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/sync/source/FilePathJavaPackageReader.java
+++ /dev/null
@@ -1,35 +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.source;
-
-import com.google.idea.blaze.base.model.primitives.WorkspacePath;
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.google.idea.blaze.base.util.PackagePrefixCalculator;
-
-/**
- * Gets the package from a java file by its file path alone (i.e. without opening the file).
- */
-public final class FilePathJavaPackageReader extends JavaPackageReader {
-  @Override
-  public String getDeclaredPackageOfJavaFile(BlazeContext context, SourceArtifact sourceArtifact) {
-    String directory = sourceArtifact.artifactLocation.getRelativePath();
-    int i = directory.lastIndexOf('/');
-    if (i >= 0) {
-      directory = directory.substring(0, i);
-    }
-    return PackagePrefixCalculator.packagePrefixOf(new WorkspacePath(directory));
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/sync/source/JavaPackageReader.java b/blaze-java/src/com/google/idea/blaze/java/sync/source/JavaPackageReader.java
deleted file mode 100644
index 5cf63c3..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/sync/source/JavaPackageReader.java
+++ /dev/null
@@ -1,28 +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.source;
-
-import com.google.idea.blaze.base.scope.BlazeContext;
-
-import javax.annotation.Nullable;
-
-/**
- * Reads java packages from files.
- */
-public abstract class JavaPackageReader {
-  @Nullable
-  abstract String getDeclaredPackageOfJavaFile(BlazeContext context, SourceArtifact sourceArtifact);
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/sync/source/JavaSourcePackageReader.java b/blaze-java/src/com/google/idea/blaze/java/sync/source/JavaSourcePackageReader.java
deleted file mode 100644
index a9001f2..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/sync/source/JavaSourcePackageReader.java
+++ /dev/null
@@ -1,79 +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.source;
-
-import com.google.idea.blaze.base.io.InputStreamProvider;
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.google.idea.blaze.base.scope.output.IssueOutput;
-import com.intellij.openapi.components.ServiceManager;
-import com.intellij.openapi.diagnostic.Logger;
-
-import javax.annotation.Nullable;
-import java.io.*;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Parse package string directly from java source
- */
-public class JavaSourcePackageReader extends JavaPackageReader {
-
-  public static JavaSourcePackageReader getInstance() {
-    return ServiceManager.getService(JavaSourcePackageReader.class);
-  }
-
-  private static final Logger LOG = Logger.getInstance(SourceDirectoryCalculator.class);
-
-  private static final Pattern JAVA_PACKAGE_PATTERN =
-    Pattern.compile("^\\s*package\\s+([\\w\\.]+);");
-
-  @Override
-  @Nullable
-  public String getDeclaredPackageOfJavaFile(BlazeContext context, SourceArtifact sourceArtifact) {
-    if (sourceArtifact.artifactLocation.isGenerated()) {
-      return null;
-    }
-    InputStreamProvider inputStreamProvider = InputStreamProvider.getInstance();
-    File sourceFile = sourceArtifact.artifactLocation.getFile();
-    try (InputStream javaInputStream = inputStreamProvider.getFile(sourceFile)) {
-      BufferedReader javaReader = new BufferedReader(new InputStreamReader(javaInputStream));
-      String javaLine;
-
-      while ((javaLine = javaReader.readLine()) != null) {
-        Matcher packageMatch = JAVA_PACKAGE_PATTERN.matcher(javaLine);
-        if (packageMatch.find()) {
-          return packageMatch.group(1);
-        }
-      }
-      IssueOutput
-        .warn("No package name string found in java source file: " + sourceFile)
-        .inFile(sourceFile)
-        .submit(context);
-      return null;
-    }
-    catch (FileNotFoundException e) {
-      IssueOutput
-        .warn("No source file found for: " + sourceFile)
-        .inFile(sourceFile)
-        .submit(context);
-      return null;
-    }
-    catch (IOException e) {
-      LOG.error(e);
-      return null;
-    }
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/sync/source/ManifestFilePackageReader.java b/blaze-java/src/com/google/idea/blaze/java/sync/source/ManifestFilePackageReader.java
deleted file mode 100644
index 71b1707..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/sync/source/ManifestFilePackageReader.java
+++ /dev/null
@@ -1,41 +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.source;
-
-import com.google.idea.blaze.base.model.primitives.Label;
-import com.google.idea.blaze.base.scope.BlazeContext;
-
-import javax.annotation.Nullable;
-import java.util.Map;
-
-public class ManifestFilePackageReader extends JavaPackageReader {
-
-  private final Map<Label, Map<String, String>> manifestMap;
-
-  public ManifestFilePackageReader(Map<Label, Map<String, String>> manifestMap) {
-    this.manifestMap = manifestMap;
-  }
-
-  @Nullable
-  @Override
-  String getDeclaredPackageOfJavaFile(BlazeContext context, SourceArtifact sourceArtifact) {
-    Map<String, String> manifestMapForRule = manifestMap.get(sourceArtifact.originatingRule);
-    if (manifestMapForRule != null) {
-      return manifestMapForRule.get(sourceArtifact.artifactLocation.getFile().getPath());
-    }
-    return null;
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/sync/source/PackageManifestReader.java b/blaze-java/src/com/google/idea/blaze/java/sync/source/PackageManifestReader.java
deleted file mode 100644
index afa6ba7..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/sync/source/PackageManifestReader.java
+++ /dev/null
@@ -1,151 +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.source;
-
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.idea.blaze.base.async.FutureUtil;
-import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
-import com.google.idea.blaze.base.io.InputStreamProvider;
-import com.google.idea.blaze.base.model.primitives.Label;
-import com.google.idea.blaze.base.prefetch.PrefetchService;
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.google.idea.blaze.base.sync.filediff.FileDiffService;
-import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
-import com.google.repackaged.devtools.build.lib.ideinfo.androidstudio.PackageManifestOuterClass.JavaSourcePackage;
-import com.google.repackaged.devtools.build.lib.ideinfo.androidstudio.PackageManifestOuterClass.PackageManifest;
-import com.intellij.openapi.components.ServiceManager;
-import com.intellij.openapi.diagnostic.Logger;
-
-import javax.annotation.Nullable;
-import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ExecutionException;
-
-public class PackageManifestReader {
-  private static final Logger LOG = Logger.getInstance(SourceDirectoryCalculator.class);
-
-  public static PackageManifestReader getInstance() {
-    return ServiceManager.getService(PackageManifestReader.class);
-  }
-
-  private FileDiffService fileDiffService = new FileDiffService();
-  private FileDiffService.State fileDiffState;
-
-  private Map<File, Label> fileToLabelMap = Maps.newHashMap();
-  private final Map<Label, Map<String, String>> manifestMap = Maps.newConcurrentMap();
-
-  /**
-   * @return A map from java source absolute file path to declared package string.
-   */
-  public Map<Label, Map<String, String>> readPackageManifestFiles(
-    BlazeContext context,
-    ArtifactLocationDecoder decoder,
-    Map<Label, ArtifactLocation> javaPackageManifests,
-    ListeningExecutorService executorService) {
-
-    Map<File, Label> fileToLabelMap = Maps.newHashMap();
-    for (Map.Entry<Label, ArtifactLocation> entry : javaPackageManifests.entrySet()) {
-      Label label = entry.getKey();
-      File file = entry.getValue().getFile();
-      fileToLabelMap.put(file, label);
-    }
-    List<File> updatedFiles = Lists.newArrayList();
-    List<File> removedFiles = Lists.newArrayList();
-    fileDiffState = fileDiffService.updateFiles(fileDiffState, fileToLabelMap.keySet(), updatedFiles, removedFiles);
-
-    ListenableFuture<?> fetchFuture = PrefetchService.getInstance().prefetchFiles(updatedFiles, true);
-    if (!FutureUtil.waitForFuture(context, fetchFuture)
-      .timed("FetchPackageManifests")
-      .run()
-      .success()) {
-      return null;
-    }
-
-    List<ListenableFuture<Void>> futures = Lists.newArrayList();
-    for (File file : updatedFiles) {
-      futures.add(executorService.submit(() -> {
-        Map<String, String> manifest = parseManifestFile(decoder, file);
-        manifestMap.put(fileToLabelMap.get(file), manifest);
-        return null;
-      }));
-    }
-    for (File file : removedFiles) {
-      Label label = this.fileToLabelMap.get(file);
-      if (label != null) {
-        manifestMap.remove(label);
-      }
-    }
-    this.fileToLabelMap = fileToLabelMap;
-
-    try {
-      Futures.allAsList(futures).get();
-    } catch (ExecutionException | InterruptedException e) {
-      LOG.error(e);
-      throw new IllegalStateException("Could not read sources");
-    }
-    return manifestMap;
-  }
-
-  protected Map<String, String> parseManifestFile(ArtifactLocationDecoder decoder,
-                                                  File packageManifest) {
-    Map<String, String> outputMap = Maps.newHashMap();
-    InputStreamProvider inputStreamProvider = InputStreamProvider.getInstance();
-
-    try (InputStream input = inputStreamProvider.getFile(packageManifest)) {
-      try (BufferedInputStream bufferedInputStream = new BufferedInputStream(input)) {
-        PackageManifest proto = PackageManifest.parseFrom(bufferedInputStream);
-        for (JavaSourcePackage source : proto.getSourcesList()) {
-          String absPath = getAbsolutePath(decoder, source);
-          if (absPath != null) {
-            outputMap.put(absPath, source.getPackageString());
-          }
-        }
-      }
-      return outputMap;
-    }
-    catch (IOException e) {
-      LOG.error(e);
-      return outputMap;
-    }
-  }
-
-  /**
-   * Returns null if the artifact location file can't be found,
-   * presumably because it's been removed from the file system since the blaze build.
-   */
-  @Nullable
-  private static String getAbsolutePath(ArtifactLocationDecoder decoder,
-                                        JavaSourcePackage source) {
-    if (!source.hasArtifactLocation()) {
-      return source.getAbsolutePath();
-    }
-    ArtifactLocation location = decoder.decode(source.getArtifactLocation());
-    if (location == null) {
-      return null;
-    }
-    return location.getFile().getAbsolutePath();
-  }
-
-}
-
diff --git a/blaze-java/src/com/google/idea/blaze/java/sync/source/SourceArtifact.java b/blaze-java/src/com/google/idea/blaze/java/sync/source/SourceArtifact.java
deleted file mode 100644
index 4f9c877..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/sync/source/SourceArtifact.java
+++ /dev/null
@@ -1,58 +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.source;
-
-import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
-import com.google.idea.blaze.base.model.primitives.Label;
-
-/**
- * Pairing of rule and source artifact.
- */
-public class SourceArtifact {
-  public final Label originatingRule;
-  public final ArtifactLocation artifactLocation;
-
-  public SourceArtifact(Label originatingRule, ArtifactLocation artifactLocation) {
-    this.originatingRule = originatingRule;
-    this.artifactLocation = artifactLocation;
-  }
-
-  public static Builder builder(Label originatingRule) {
-    return new Builder(originatingRule);
-  }
-
-  public static class Builder {
-    Label originatingRule;
-    ArtifactLocation artifactLocation;
-
-    Builder(Label originatingRule) {
-      this.originatingRule = originatingRule;
-    }
-
-    public Builder setArtifactLocation(ArtifactLocation artifactLocation) {
-      this.artifactLocation = artifactLocation;
-      return this;
-    }
-
-    public Builder setArtifactLocation(ArtifactLocation.Builder artifactLocation) {
-      return setArtifactLocation(artifactLocation.build());
-    }
-
-    public SourceArtifact build() {
-      return new SourceArtifact(originatingRule, artifactLocation);
-    }
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/sync/source/SourceDirectoryCalculator.java b/blaze-java/src/com/google/idea/blaze/java/sync/source/SourceDirectoryCalculator.java
deleted file mode 100644
index c312bbe..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/sync/source/SourceDirectoryCalculator.java
+++ /dev/null
@@ -1,468 +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.source;
-
-import com.google.common.base.*;
-import com.google.common.base.Objects;
-import com.google.common.collect.*;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListeningExecutorService;
-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.model.primitives.Label;
-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;
-import com.google.idea.blaze.base.scope.Scope;
-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.projectview.SourceTestConfig;
-import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
-import com.google.idea.blaze.base.util.PackagePrefixCalculator;
-import com.google.idea.blaze.java.sync.model.BlazeContentEntry;
-import com.google.idea.blaze.java.sync.model.BlazeSourceDirectory;
-import com.intellij.openapi.diagnostic.Logger;
-
-import javax.annotation.Nullable;
-import java.io.File;
-import java.util.*;
-import java.util.concurrent.ExecutionException;
-import java.util.stream.Collectors;
-
-/**
- * This is a utility class for calculating the java sources and their package prefixes given
- * a module and its Blaze {@link ArtifactLocation} list.
- */
-public final class SourceDirectoryCalculator {
-
-  private static final Logger LOG = Logger.getInstance(SourceDirectoryCalculator.class);
-
-  private static final Splitter PACKAGE_SPLITTER = Splitter.on('.');
-  private static final Splitter PATH_SPLITTER = Splitter.on('/');
-  private static final Joiner PACKAGE_JOINER = Joiner.on('.');
-  private static final Joiner PATH_JOINER = Joiner.on('/');
-
-  private static final Comparator<WorkspacePath> WORKSPACE_PATH_COMPARATOR =
-    (o1, o2) -> o1.relativePath().compareTo(o2.relativePath());
-
-  private static final JavaPackageReader generatedFileJavaPackageReader = new FilePathJavaPackageReader();
-  private final ListeningExecutorService executorService = MoreExecutors.sameThreadExecutor();
-  private final ListeningExecutorService packageReaderExecutorService = MoreExecutors.listeningDecorator(new TransientExecutor(16));
-
-  public ImmutableList<BlazeContentEntry> calculateContentEntries(
-    BlazeContext context,
-    WorkspaceRoot workspaceRoot,
-    SourceTestConfig sourceTestConfig,
-    ArtifactLocationDecoder artifactLocationDecoder,
-    Collection<WorkspacePath> rootDirectories,
-    Collection<SourceArtifact> sources,
-    Map<Label, ArtifactLocation> javaPackageManifests) {
-
-    ManifestFilePackageReader manifestFilePackageReader = Scope.push(context, (childContext) -> {
-      childContext.push(new TimingScope("ReadPackageManifests"));
-      Map<Label, Map<String, String>> manifestMap = PackageManifestReader.getInstance().readPackageManifestFiles(
-        childContext,
-        artifactLocationDecoder,
-        javaPackageManifests,
-        packageReaderExecutorService
-      );
-      return new ManifestFilePackageReader(manifestMap);
-    });
-
-    final List<JavaPackageReader> javaPackageReaders = Lists.newArrayList(
-      manifestFilePackageReader,
-      JavaSourcePackageReader.getInstance(),
-      generatedFileJavaPackageReader);
-
-    Collection<SourceArtifact> nonGeneratedSources = filterGeneratedArtifacts(sources);
-
-    // Sort artifacts and excludes into their respective workspace paths
-    Multimap<WorkspacePath, SourceArtifact> sourcesUnderDirectoryRoot =
-      sortArtifactLocationsByRootDirectory(context, rootDirectories, nonGeneratedSources);
-
-    List<BlazeContentEntry> result = Lists.newArrayList();
-    for (WorkspacePath workspacePath : rootDirectories) {
-      File contentRoot = workspaceRoot.fileForPath(workspacePath);
-      ImmutableList<BlazeSourceDirectory> sourceDirectories = calculateSourceDirectoriesForContentRoot(
-        context,
-        sourceTestConfig,
-        workspaceRoot,
-        workspacePath,
-        sourcesUnderDirectoryRoot.get(workspacePath),
-        javaPackageReaders
-      );
-      if (!sourceDirectories.isEmpty()) {
-        result.add(new BlazeContentEntry(contentRoot, sourceDirectories));
-      }
-    }
-    Collections.sort(result, (lhs, rhs) -> lhs.contentRoot.compareTo(rhs.contentRoot));
-    return ImmutableList.copyOf(result);
-  }
-
-  private Collection<SourceArtifact> filterGeneratedArtifacts(Collection<SourceArtifact> artifactLocations) {
-    return artifactLocations
-      .stream()
-      .filter(sourceArtifact -> sourceArtifact.artifactLocation.isSource())
-      .collect(Collectors.toList());
-  }
-
-  private static Multimap<WorkspacePath, SourceArtifact> sortArtifactLocationsByRootDirectory(
-    BlazeContext context,
-    Collection<WorkspacePath> rootDirectories,
-    Collection<SourceArtifact> sources) {
-
-    Multimap<WorkspacePath, SourceArtifact> result = ArrayListMultimap.create();
-
-    for (SourceArtifact sourceArtifact : sources) {
-      WorkspacePath foundWorkspacePath = rootDirectories
-        .stream()
-        .filter(rootDirectory -> isUnderRootDirectory(rootDirectory, sourceArtifact.artifactLocation.getRelativePath()))
-        .findFirst()
-        .orElse(null);
-
-      if (foundWorkspacePath != null) {
-        result.put(foundWorkspacePath, sourceArtifact);
-      }
-      else if (sourceArtifact.artifactLocation.isSource()) {
-        File sourceFile = sourceArtifact.artifactLocation.getFile();
-        String message = String.format(
-          "Did not add %s. You're probably using a java file from outside the workspace"
-          + "that has been exported using export_files. Don't do that.", sourceFile);
-        IssueOutput
-          .warn(message)
-          .inFile(sourceFile)
-          .submit(context);
-      }
-    }
-    return result;
-  }
-
-  private static boolean isUnderRootDirectory(WorkspacePath rootDirectory, String relativePath) {
-    if (rootDirectory.isWorkspaceRoot()) {
-      return true;
-    }
-    String rootDirectoryString = rootDirectory.toString();
-    return relativePath.startsWith(rootDirectoryString)
-      && (relativePath.length() == rootDirectoryString.length()
-          || (relativePath.charAt(rootDirectoryString.length()) == '/'));
-  }
-
-  /**
-   * Calculates all source directories for a single content root.
-   */
-  private ImmutableList<BlazeSourceDirectory> calculateSourceDirectoriesForContentRoot(
-    BlazeContext context,
-    SourceTestConfig sourceTestConfig,
-    WorkspaceRoot workspaceRoot,
-    WorkspacePath directoryRoot,
-    Collection<SourceArtifact> sourceArtifacts,
-    Collection<JavaPackageReader> javaPackageReaders) {
-
-    // Split out java files
-    List<SourceArtifact> javaArtifacts = Lists.newArrayList();
-    for (SourceArtifact sourceArtifact : sourceArtifacts) {
-      if (isJavaFile(sourceArtifact.artifactLocation)) {
-        javaArtifacts.add(sourceArtifact);
-      }
-    }
-
-    List<BlazeSourceDirectory> result = Lists.newArrayList();
-
-    // Add java source directories
-    calculateJavaSourceDirectories(
-      context,
-      workspaceRoot,
-      directoryRoot,
-      sourceTestConfig,
-      javaArtifacts,
-      javaPackageReaders,
-      result
-    );
-
-    Collections.sort(result, BlazeSourceDirectory.COMPARATOR);
-    return ImmutableList.copyOf(result);
-  }
-
-  /**
-   * Adds the java source directories.
-   */
-  private void calculateJavaSourceDirectories(
-    BlazeContext context,
-    WorkspaceRoot workspaceRoot,
-    WorkspacePath directoryRoot,
-    SourceTestConfig sourceTestConfig,
-    Collection<SourceArtifact> javaArtifacts,
-    Collection<JavaPackageReader> javaPackageReaders,
-    Collection<BlazeSourceDirectory> result) {
-
-    List<SourceRoot> sourceRootsPerFile = Lists.newArrayList();
-
-    // Get java sources
-    List<ListenableFuture<SourceRoot>> sourceRootFutures = Lists.newArrayList();
-    for (final SourceArtifact sourceArtifact : javaArtifacts) {
-      ListenableFuture<SourceRoot> future = executorService.submit(() -> sourceRootForJavaSource(
-        context,
-        sourceArtifact,
-        javaPackageReaders
-      ));
-      sourceRootFutures.add(future);
-    }
-    try {
-      for (SourceRoot sourceRoot : Futures.allAsList(sourceRootFutures).get()) {
-        if (sourceRoot != null) {
-          sourceRootsPerFile.add(sourceRoot);
-        }
-      }
-    }
-    catch (ExecutionException | InterruptedException e) {
-      LOG.error(e);
-      throw new IllegalStateException("Could not read sources");
-    }
-
-    // Sort source roots into their respective directories
-    Multimap<WorkspacePath, SourceRoot> sourceDirectoryToSourceRoots = HashMultimap.create();
-    for (SourceRoot sourceRoot : sourceRootsPerFile) {
-      sourceDirectoryToSourceRoots.put(sourceRoot.workspacePath, sourceRoot);
-    }
-
-    // Create a mapping from directory to package prefix
-    Map<WorkspacePath, SourceRoot> workspacePathToCandidateRoot = Maps.newHashMap();
-    for (WorkspacePath workspacePath : sourceDirectoryToSourceRoots.keySet()) {
-      Collection<SourceRoot> sources = sourceDirectoryToSourceRoots.get(workspacePath);
-      Multiset<String> packages = HashMultiset.create();
-
-      for (SourceRoot source : sources) {
-        packages.add(source.packagePrefix);
-      }
-
-      final String directoryPackagePrefix;
-      // Common case -- all source files agree on a single package
-      if (packages.elementSet().size() == 1) {
-        directoryPackagePrefix = packages.elementSet().iterator().next();
-      }
-      else {
-        directoryPackagePrefix = pickMostFrequentlyOccurring(packages);
-      }
-
-      // These properties must be the same for all files in the directory
-      SourceRoot sourceFile = sources.iterator().next();
-
-      SourceRoot candidateRoot = new SourceRoot(workspacePath, directoryPackagePrefix);
-      workspacePathToCandidateRoot.put(workspacePath, candidateRoot);
-    }
-
-    // Add content entry base if it doesn't exist
-    if (!workspacePathToCandidateRoot.containsKey(directoryRoot)) {
-      SourceRoot candidateRoot = new SourceRoot(directoryRoot, PackagePrefixCalculator.packagePrefixOf(directoryRoot));
-      workspacePathToCandidateRoot.put(directoryRoot, candidateRoot);
-    }
-
-    // Merge source roots
-    // We have to do this in directory order to ensure we encounter roots before
-    // their subdirectories
-    Map<WorkspacePath, SourceRoot> mergedSourceRoots = Maps.newHashMap();
-    List<WorkspacePath> sortedWorkspacePaths = Lists.newArrayList(workspacePathToCandidateRoot.keySet());
-    Collections.sort(sortedWorkspacePaths, WORKSPACE_PATH_COMPARATOR);
-    for (WorkspacePath workspacePath : sortedWorkspacePaths) {
-      SourceRoot candidateRoot = workspacePathToCandidateRoot.get(workspacePath);
-      SourceRoot bestNewRoot = candidateRoot;
-      for (SourceRoot mergedSourceRoot : new CandidateRoots(directoryRoot, candidateRoot)) {
-        SourceRoot existingSourceRoot = mergedSourceRoots.get(mergedSourceRoot.workspacePath);
-        if (existingSourceRoot != null) {
-          if (existingSourceRoot.packagePrefix.equals(mergedSourceRoot.packagePrefix)) {
-            // Do not create new source root -- merge into preexisting source root
-            // Since we already decided to establish one here, there is also
-            // no need to go further up the tree
-            bestNewRoot = null;
-          }
-          break;
-        }
-        bestNewRoot = mergedSourceRoot;
-      }
-
-      if (bestNewRoot != null) {
-        mergedSourceRoots.put(bestNewRoot.workspacePath, bestNewRoot);
-      }
-    }
-
-    // Add merged source roots
-    for (SourceRoot sourceRoot : mergedSourceRoots.values()) {
-      result.add(BlazeSourceDirectory.builder(workspaceRoot.fileForPath(sourceRoot.workspacePath))
-                              .setPackagePrefix(sourceRoot.packagePrefix)
-                              .setTest(sourceTestConfig.isTestSource(sourceRoot.workspacePath.relativePath()))
-                              .setGenerated(false)
-                              .build());
-    }
-  }
-
-  private static <T> T pickMostFrequentlyOccurring(Multiset<T> set) {
-    Preconditions.checkArgument(set.size() > 0);
-
-    T best = null;
-    int bestCount = 0;
-
-    for (T candidate : set.elementSet()) {
-      int candidateCount = set.count(candidate);
-      if (candidateCount > bestCount) {
-        best = candidate;
-        bestCount = candidateCount;
-      }
-    }
-    return best;
-  }
-
-  @Nullable
-  private static SourceRoot sourceRootForJavaSource(
-    BlazeContext context,
-    SourceArtifact sourceArtifact,
-    Collection<JavaPackageReader> javaPackageReaders) {
-
-    File javaFile = sourceArtifact.artifactLocation.getFile();
-
-    String declaredPackage = null;
-    for (JavaPackageReader reader : javaPackageReaders) {
-      declaredPackage = reader.getDeclaredPackageOfJavaFile(context, sourceArtifact);
-      if (declaredPackage != null) {
-        break;
-      }
-    }
-    if (declaredPackage == null) {
-      IssueOutput
-        .warn("Failed to inspect the package name of java source file: " + javaFile)
-        .inFile(javaFile)
-        .submit(context);
-      return null;
-    }
-    return new SourceRoot(
-      new WorkspacePath(new File(sourceArtifact.artifactLocation.getRelativePath()).getParent()),
-      declaredPackage
-    );
-  }
-
-  static class SourceRoot {
-    final WorkspacePath workspacePath;
-    final String packagePrefix;
-    public SourceRoot(WorkspacePath workspacePath, String packagePrefix) {
-      this.workspacePath = workspacePath;
-      this.packagePrefix = packagePrefix;
-    }
-    @Override
-    public boolean equals(Object o) {
-      if (o == null || getClass() != o.getClass()) {
-        return false;
-      }
-      SourceRoot that = (SourceRoot)o;
-      return Objects.equal(workspacePath, that.workspacePath)
-             && Objects.equal(packagePrefix, that.packagePrefix);
-    }
-    @Override
-    public int hashCode() {
-      return Objects.hashCode(workspacePath, packagePrefix);
-    }
-
-    @Override
-    public String toString() {
-      return "SourceRoot {" + '\n'
-             + "  workspacePath: " + workspacePath + '\n'
-             + "  packagePrefix: " + packagePrefix + '\n'
-             + '}';
-    }
-  }
-
-  private static boolean isJavaFile(ArtifactLocation artifactLocation) {
-    return artifactLocation.getRelativePath().endsWith(".java");
-  }
-
-  static class CandidateRoots implements Iterable<SourceRoot> {
-    private static final List<String> EMPTY_LIST = ImmutableList.of();
-
-    private final SourceRoot candidateRoot;
-    private final WorkspacePath directoryRoot;
-
-    CandidateRoots(
-      WorkspacePath directoryRoot,
-      SourceRoot candidateRoot) {
-      this.directoryRoot = directoryRoot;
-      this.candidateRoot = candidateRoot;
-    }
-
-    @Override
-    public Iterator<SourceRoot> iterator() {
-      return new CandidateRootIterator();
-    }
-
-    class CandidateRootIterator implements Iterator<SourceRoot> {
-      private final List<String> packageComponents;
-      private final List<String> pathComponents;
-      private int packageIndex;
-      private int pathIndex;
-
-      CandidateRootIterator() {
-        int directoryRootLength = directoryRoot.relativePath().length();
-        String relativePath = candidateRoot.workspacePath.relativePath();
-        final String sourcePathRelativeToModule;
-        if (relativePath.length() > directoryRootLength) {
-          if (directoryRootLength > 0) {
-            sourcePathRelativeToModule = relativePath.substring(directoryRootLength + 1);
-          } else {
-            sourcePathRelativeToModule = relativePath;
-          }
-        } else {
-          sourcePathRelativeToModule = "";
-        }
-
-        this.packageComponents = PACKAGE_SPLITTER.splitToList(candidateRoot.packagePrefix);
-        this.pathComponents = !Strings.isNullOrEmpty(sourcePathRelativeToModule)
-                              ? PATH_SPLITTER.splitToList(sourcePathRelativeToModule) : EMPTY_LIST;
-        this.packageIndex = packageComponents.size() - 1;
-        this.pathIndex = pathComponents.size() - 1;
-      }
-
-      @Override
-      public boolean hasNext() {
-        return (packageIndex >= 0 && pathIndex >= 0 && packageComponents.get(packageIndex).equals(pathComponents.get(pathIndex)));
-      }
-
-      @Override
-      public SourceRoot next() {
-        String directoryRootRelativePath = PATH_JOINER.join(pathComponents.subList(0, pathIndex));
-        final WorkspacePath workspacePath;
-        if (directoryRootRelativePath.isEmpty()){
-          workspacePath = directoryRoot;
-        } else if (directoryRoot.isWorkspaceRoot()) {
-          workspacePath = new WorkspacePath(directoryRootRelativePath);
-        } else {
-          workspacePath = new WorkspacePath(PATH_JOINER.join(directoryRoot.relativePath(), directoryRootRelativePath));
-        }
-
-        SourceRoot sourceRoot = new SourceRoot(
-          workspacePath,
-          PACKAGE_JOINER.join(packageComponents.subList(0, packageIndex))
-        );
-        --packageIndex;
-        --pathIndex;
-        return sourceRoot;
-      }
-
-      @Override
-      public void remove() {
-        throw new UnsupportedOperationException();
-      }
-    }
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/sync/workingset/JavaWorkingSet.java b/blaze-java/src/com/google/idea/blaze/java/sync/workingset/JavaWorkingSet.java
deleted file mode 100644
index f804eb3..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/sync/workingset/JavaWorkingSet.java
+++ /dev/null
@@ -1,88 +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.workingset;
-
-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.model.primitives.WorkspacePath;
-import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
-import com.google.idea.blaze.base.sync.workspace.WorkingSet;
-
-import java.util.Set;
-
-/**
- * Computes the working set of files of directories from source control.
- *
- * The working set is:
- *   - All new untracked directories (git only)
- *   - All modified BUILD files
- *   - All modified java files
- *
- * A rule is considered part of the working set if any of the following is true:
- *   - Its BUILD file is modified
- *   - Its BUILD file is under a new directory
- *   - Any of its java files are modified
- *   - Any of its java files are under a new directory
- *
- * Rules in the working set get an expanded classpath of their direct deps,
- * i.e. they temporarily defeat classpath reduction.
- */
-public class JavaWorkingSet {
-  private final Set<String> modifiedBuildFileRelativePaths;
-  private final Set<String> modifiedJavaFileRelativePaths;
-
-  public JavaWorkingSet(WorkspaceRoot workspaceRoot, WorkingSet workingSet) {
-    Set<String> modifiedBuildFileRelativePaths = Sets.newHashSet();
-    Set<String> modifiedJavaFileRelativePaths = Sets.newHashSet();
-
-    for (WorkspacePath workspacePath : Iterables.concat(workingSet.addedFiles, workingSet.modifiedFiles)) {
-      if (workspaceRoot.fileForPath(workspacePath).getName().equals("BUILD")) {
-        modifiedBuildFileRelativePaths.add(workspacePath.relativePath());
-      } else if (workspacePath.relativePath().endsWith(".java")){
-        modifiedJavaFileRelativePaths.add(workspacePath.relativePath());
-      }
-    }
-
-    this.modifiedBuildFileRelativePaths = modifiedBuildFileRelativePaths;
-    this.modifiedJavaFileRelativePaths = modifiedJavaFileRelativePaths;
-  }
-
-  public boolean isRuleInWorkingSet(RuleIdeInfo ruleIdeInfo) {
-    ArtifactLocation buildFile = ruleIdeInfo.buildFile;
-    if (buildFile != null) {
-      if (modifiedBuildFileRelativePaths.contains(buildFile.getRelativePath())) {
-        return true;
-      }
-    }
-
-    for (ArtifactLocation artifactLocation : ruleIdeInfo.sources) {
-      if (isInWorkingSet(artifactLocation)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  public boolean isInWorkingSet(ArtifactLocation artifactLocation) {
-    return isInWorkingSet(artifactLocation.getRelativePath());
-  }
-
-  boolean isInWorkingSet(String relativePath) {
-    return modifiedJavaFileRelativePaths.contains(relativePath);
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/syncstatus/BlazeJavaSyncStatusClassNodeDecorator.java b/blaze-java/src/com/google/idea/blaze/java/syncstatus/BlazeJavaSyncStatusClassNodeDecorator.java
deleted file mode 100644
index dd61284..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/syncstatus/BlazeJavaSyncStatusClassNodeDecorator.java
+++ /dev/null
@@ -1,63 +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.syncstatus;
-
-import com.intellij.ide.projectView.PresentationData;
-import com.intellij.ide.projectView.ProjectViewNode;
-import com.intellij.ide.projectView.ProjectViewNodeDecorator;
-import com.intellij.ide.projectView.impl.nodes.ClassTreeNode;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.packageDependencies.ui.PackageDependenciesNode;
-import com.intellij.psi.PsiClass;
-import com.intellij.psi.PsiFile;
-import com.intellij.ui.ColoredTreeCellRenderer;
-import com.intellij.ui.SimpleTextAttributes;
-
-/**
- * Grays out any unreachable java classes.
- */
-public class BlazeJavaSyncStatusClassNodeDecorator implements ProjectViewNodeDecorator {
-  @Override
-  public void decorate(ProjectViewNode node, PresentationData data) {
-    if (!(node instanceof ClassTreeNode)) {
-      return;
-    }
-    PsiClass psiClass = ((ClassTreeNode)node).getPsiClass();
-    if (psiClass == null) {
-      return;
-    }
-    PsiFile psiFile = psiClass.getContainingFile();
-    if (psiFile == null) {
-      return;
-    }
-    VirtualFile virtualFile = psiFile.getVirtualFile();
-    if (virtualFile == null) {
-      return;
-    }
-
-    Project project = node.getProject();
-    if (SyncStatusHelper.isUnsynced(project, virtualFile)) {
-      data.clearText();
-      data.addText(psiClass.getName(), SimpleTextAttributes.GRAY_ATTRIBUTES);
-      data.addText(" (unsynced)", SimpleTextAttributes.GRAY_ATTRIBUTES);
-    }
-  }
-
-  @Override
-  public void decorate(PackageDependenciesNode node, ColoredTreeCellRenderer cellRenderer) {
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/syncstatus/BlazeJavaSyncStatusEditorTabColorProvider.java b/blaze-java/src/com/google/idea/blaze/java/syncstatus/BlazeJavaSyncStatusEditorTabColorProvider.java
deleted file mode 100644
index 0c9e188..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/syncstatus/BlazeJavaSyncStatusEditorTabColorProvider.java
+++ /dev/null
@@ -1,41 +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.syncstatus;
-
-import com.intellij.openapi.fileEditor.impl.EditorTabColorProvider;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.ui.JBColor;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.awt.*;
-
-/**
- * Changes the color for unsynced files.
- */
-public class BlazeJavaSyncStatusEditorTabColorProvider implements EditorTabColorProvider {
-  private static final JBColor UNSYNCED_COLOR = new JBColor(new Color(252, 234, 234), new Color(121, 105, 105));
-
-  @Nullable
-  @Override
-  public Color getEditorTabColor(@NotNull Project project, @NotNull VirtualFile file) {
-    if (file.getName().endsWith(".java") && SyncStatusHelper.isUnsynced(project, file)) {
-      return UNSYNCED_COLOR;
-    }
-    return null;
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/syncstatus/BlazeJavaSyncStatusEditorTabTitleProvider.java b/blaze-java/src/com/google/idea/blaze/java/syncstatus/BlazeJavaSyncStatusEditorTabTitleProvider.java
deleted file mode 100644
index 642ed83..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/syncstatus/BlazeJavaSyncStatusEditorTabTitleProvider.java
+++ /dev/null
@@ -1,35 +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.syncstatus;
-
-import com.intellij.openapi.fileEditor.impl.EditorTabTitleProvider;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.vfs.VirtualFile;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * Changes the tab title for unsynced files.
- */
-public class BlazeJavaSyncStatusEditorTabTitleProvider implements EditorTabTitleProvider {
-  @Nullable
-  @Override
-  public String getEditorTabTitle(Project project, VirtualFile file) {
-    if (file.getName().endsWith("java") && SyncStatusHelper.isUnsynced(project, file)) {
-      return file.getPresentableName() + " (unsynced)";
-    }
-    return null;
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/syncstatus/SyncStatusHelper.java b/blaze-java/src/com/google/idea/blaze/java/syncstatus/SyncStatusHelper.java
deleted file mode 100644
index d2a3cf9..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/syncstatus/SyncStatusHelper.java
+++ /dev/null
@@ -1,43 +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.syncstatus;
-
-import com.google.idea.blaze.base.model.BlazeProjectData;
-import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
-import com.google.idea.blaze.java.sync.model.BlazeJavaSyncData;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.vfs.VirtualFile;
-
-import java.io.File;
-
-class SyncStatusHelper {
-  static boolean isUnsynced(Project project, VirtualFile virtualFile) {
-    BlazeProjectData blazeProjectData = BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
-    if (blazeProjectData == null) {
-      return false;
-    }
-    BlazeJavaSyncData syncData = blazeProjectData.syncState.get(BlazeJavaSyncData.class);
-    if (syncData == null) {
-      return false;
-    }
-    if (!virtualFile.isInLocalFileSystem()) {
-      return false;
-    }
-
-    File file = new File(virtualFile.getPath());
-    return !syncData.importResult.javaSourceFiles.contains(file);
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/ui/BlazeIntelliJProblemsView.java b/blaze-java/src/com/google/idea/blaze/java/ui/BlazeIntelliJProblemsView.java
deleted file mode 100644
index f31a694..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/ui/BlazeIntelliJProblemsView.java
+++ /dev/null
@@ -1,60 +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.ui;
-
-import com.google.idea.blaze.base.scope.output.IssueOutput;
-import com.google.idea.blaze.base.ui.BlazeProblemsView;
-import com.intellij.compiler.CompilerMessageImpl;
-import com.intellij.compiler.ProblemsView;
-import com.intellij.openapi.compiler.CompilerMessageCategory;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.vfs.VfsUtil;
-import com.intellij.openapi.vfs.VirtualFile;
-
-import java.util.UUID;
-
-public class BlazeIntelliJProblemsView implements BlazeProblemsView {
-  private final Project project;
-
-  private BlazeIntelliJProblemsView(Project project) {
-    this.project = project;
-  }
-
-  @Override
-  public void clearOldMessages(UUID sessionId) {
-    ProblemsView.SERVICE.getInstance(project).clearOldMessages(null, sessionId);
-  }
-
-  @Override
-  public void addMessage(IssueOutput issue, UUID sessionId) {
-    VirtualFile virtualFile = issue.getFile() != null
-                              ? VfsUtil.findFileByIoFile(issue.getFile(), true /* refresh */)
-                              : null;
-    CompilerMessageCategory category = issue.getCategory() == IssueOutput.Category.ERROR
-                                       ? CompilerMessageCategory.ERROR
-                                       : CompilerMessageCategory.WARNING;
-    CompilerMessageImpl message = new CompilerMessageImpl(
-      project,
-      category,
-      issue.getMessage(),
-      virtualFile,
-      issue.getLine(),
-      issue.getColumn(),
-      issue.getNavigatable()
-    );
-    ProblemsView.SERVICE.getInstance(project).addMessage(message, sessionId);
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/wizard/BlazeImportJavaProjectWizard.java b/blaze-java/src/com/google/idea/blaze/java/wizard/BlazeImportJavaProjectWizard.java
deleted file mode 100644
index b00047d..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/wizard/BlazeImportJavaProjectWizard.java
+++ /dev/null
@@ -1,35 +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.wizard;
-
-import com.intellij.ide.util.newProjectWizard.AddModuleWizard;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.projectImport.ProjectImportProvider;
-
-final class BlazeImportJavaProjectWizard extends AddModuleWizard {
-  public BlazeImportJavaProjectWizard(VirtualFile file, ProjectImportProvider provider) {
-    super(null, file.getCanonicalPath(), provider);
-  }
-
-  @Override
-  protected String getDimensionServiceKey() {
-    return null; // No dimension service
-  }
-
-  public boolean runWizard() {
-    return showAndGet();
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/wizard/BlazeImportNewJavaProjectAction.java b/blaze-java/src/com/google/idea/blaze/java/wizard/BlazeImportNewJavaProjectAction.java
deleted file mode 100644
index 5c3c49f..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/wizard/BlazeImportNewJavaProjectAction.java
+++ /dev/null
@@ -1,95 +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.wizard;
-
-import com.google.idea.blaze.base.bazel.BuildSystemProvider;
-import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
-import com.google.idea.blaze.base.wizard.BlazeImportFileChooser;
-import com.intellij.ide.impl.NewProjectUtil;
-import com.intellij.openapi.actionSystem.AnAction;
-import com.intellij.openapi.actionSystem.AnActionEvent;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.options.ConfigurationException;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.ui.Messages;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.projectImport.ProjectImportProvider;
-
-import javax.annotation.Nullable;
-import java.io.IOException;
-
-
-public class BlazeImportNewJavaProjectAction extends AnAction {
-  private static final Logger LOG = Logger.getInstance(BlazeImportNewJavaProjectAction.class);
-
-  public BlazeImportNewJavaProjectAction() {
-    super("Import Project...");
-  }
-
-  @Override
-  public void update(AnActionEvent e) {
-    super.update(e);
-    // this importer only supports importing blaze projects
-    if (!BuildSystemProvider.isBuildSystemAvailable(BuildSystem.Blaze)) {
-      e.getPresentation().setEnabledAndVisible(false);
-    } else {
-      e.getPresentation().setEnabledAndVisible(true);
-    }
-  }
-
-  @Override
-  public void actionPerformed(AnActionEvent e) {
-    try {
-      BlazeImportJavaProjectWizard wizard = selectFileAndCreateWizard();
-      if (wizard != null) {
-        if (!wizard.runWizard()) {
-          return;
-        }
-        //noinspection ConstantConditions
-        NewProjectUtil.createFromWizard(wizard, null);
-      }
-    }
-    catch (IOException | ConfigurationException exception) {
-      handleImportException(e.getProject(), exception);
-    }
-  }
-
-  @Nullable
-  private BlazeImportJavaProjectWizard selectFileAndCreateWizard() throws IOException, ConfigurationException {
-    VirtualFile fileToImport = BlazeImportFileChooser.getFileToImport();
-    if (fileToImport == null) {
-      return null;
-    }
-    return createImportWizard(fileToImport);
-  }
-
-  @Nullable
-  protected BlazeImportJavaProjectWizard createImportWizard(VirtualFile file) throws IOException, ConfigurationException {
-    ProjectImportProvider provider = createBlazeImportProvider();
-    return new BlazeImportJavaProjectWizard(file, provider);
-  }
-
-  private static ProjectImportProvider createBlazeImportProvider() {
-    BlazeNewJavaProjectImportBuilder builder = new BlazeNewJavaProjectImportBuilder();
-    return new BlazeNewProjectImportProvider(builder);
-  }
-
-  private static void handleImportException(@Nullable Project project, Exception e) {
-    String message = String.format("Project import failed: %s", e.getMessage());
-    Messages.showErrorDialog(project, message, "Import Project");
-    LOG.error(e);
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/wizard/BlazeNewJavaProjectImportBuilder.java b/blaze-java/src/com/google/idea/blaze/java/wizard/BlazeNewJavaProjectImportBuilder.java
deleted file mode 100644
index 7378e4a..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/wizard/BlazeNewJavaProjectImportBuilder.java
+++ /dev/null
@@ -1,96 +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.wizard;
-
-import com.google.common.collect.ImmutableList;
-import com.google.idea.blaze.base.model.BlazeProjectData;
-import com.google.idea.blaze.base.projectview.ProjectView;
-import com.google.idea.blaze.base.settings.BlazeImportSettings;
-import com.google.idea.blaze.base.wizard.BlazeNewProjectBuilder;
-import com.intellij.openapi.module.ModifiableModuleModel;
-import com.intellij.openapi.module.Module;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.projectRoots.JavaSdk;
-import com.intellij.openapi.projectRoots.SdkTypeId;
-import com.intellij.openapi.roots.ui.configuration.ModulesProvider;
-import com.intellij.packaging.artifacts.ModifiableArtifactModel;
-import com.intellij.projectImport.ProjectImportBuilder;
-import icons.BlazeIcons;
-import org.jetbrains.annotations.NotNull;
-
-import javax.swing.*;
-import java.util.List;
-
-/**
- * Project builder for Blaze projects.
- */
-public class BlazeNewJavaProjectImportBuilder extends ProjectImportBuilder<BlazeProjectData> {
-  private BlazeImportSettings importSettings;
-  private ProjectView projectView;
-
-  @NotNull
-  @Override
-  public String getName() {
-    return "Blaze";
-  }
-
-  @Override
-  public Icon getIcon() {
-    return BlazeIcons.Blaze;
-  }
-
-  @Override
-  public boolean isSuitableSdkType(SdkTypeId sdk) {
-    return sdk == JavaSdk.getInstance();
-  }
-
-  @Override
-  public List<BlazeProjectData> getList() {
-    return ImmutableList.of();
-  }
-
-  @Override
-  public boolean isMarked(BlazeProjectData element) {
-    return true;
-  }
-
-  @Override
-  public void setList(List<BlazeProjectData> gradleProjects) {
-  }
-
-  @Override
-  public void setOpenProjectSettingsAfter(boolean on) {
-  }
-
-  @Override
-  public List<Module> commit(final Project project,
-                             ModifiableModuleModel model,
-                             ModulesProvider modulesProvider,
-                             ModifiableArtifactModel artifactModel) {
-    assert importSettings != null;
-    assert projectView != null;
-
-    return BlazeNewProjectBuilder.commit(project, importSettings, projectView);
-  }
-
-  public void setImportSettings(BlazeImportSettings importSettings) {
-    this.importSettings = importSettings;
-  }
-
-  public void setProjectView(ProjectView projectView) {
-    this.projectView = projectView;
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/wizard/BlazeNewProjectImportProvider.java b/blaze-java/src/com/google/idea/blaze/java/wizard/BlazeNewProjectImportProvider.java
deleted file mode 100644
index 2ac630c..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/wizard/BlazeNewProjectImportProvider.java
+++ /dev/null
@@ -1,54 +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.wizard;
-
-import com.google.idea.blaze.base.wizard.ImportSource;
-import com.intellij.ide.util.projectWizard.ModuleWizardStep;
-import com.intellij.ide.util.projectWizard.WizardContext;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.projectImport.ProjectImportProvider;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * The import provider for the Blaze plugin.
- */
-public class BlazeNewProjectImportProvider extends ProjectImportProvider {
-
-  public BlazeNewProjectImportProvider(BlazeNewJavaProjectImportBuilder builder) {
-    super(builder);
-  }
-
-  @Override
-  protected boolean canImportFromFile(VirtualFile file) {
-    return ImportSource.canImport(file);
-  }
-
-  @Nullable
-  @Override
-  public String getFileSample() {
-    return "Workspace root, .blazeproject file, or BUILD file";
-  }
-
-  @Override
-  public String getPathToBeImported(VirtualFile file) {
-    return file.getCanonicalPath();
-  }
-
-  @Override
-  public ModuleWizardStep[] createSteps(WizardContext context) {
-    return new ModuleWizardStep[]{new SelectExternalProjectStep(context)};
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/wizard/SelectExternalProjectStep.java b/blaze-java/src/com/google/idea/blaze/java/wizard/SelectExternalProjectStep.java
deleted file mode 100644
index 8d9ef36..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/wizard/SelectExternalProjectStep.java
+++ /dev/null
@@ -1,107 +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.wizard;
-
-import com.google.idea.blaze.base.ui.BlazeValidationResult;
-import com.google.idea.blaze.base.wizard.BlazeProjectSettingsControl;
-import com.google.idea.blaze.base.wizard.ImportResults;
-import com.intellij.ide.util.projectWizard.WizardContext;
-import com.intellij.openapi.options.ConfigurationException;
-import com.intellij.projectImport.ProjectImportWizardStep;
-import org.jetbrains.annotations.NotNull;
-
-import javax.swing.*;
-import java.awt.*;
-import java.io.File;
-
-/**
- * Handles the following responsibilities:
- * <pre>
- * <ul>
- *   <li>allows end user to define external system config file to import from;</li>
- *   <li>processes the input and reacts accordingly - shows error message if the project is invalid or proceeds to the next screen;</li>
- * </ul>
- * </pre>
- *
- * @author Denis Zhdanov
- * @since 8/1/11 4:15 PM
- */
-public class SelectExternalProjectStep extends ProjectImportWizardStep {
-
-  @NotNull
-  private final JPanel component = new JPanel(new BorderLayout());
-
-  @NotNull
-  private final BlazeProjectSettingsControl control;
-
-  private boolean settingsInitialised;
-
-  public SelectExternalProjectStep(@NotNull WizardContext context) {
-    super(context);
-    control = new BlazeProjectSettingsControl(context.getDisposable());
-  }
-
-  @Override
-  public JComponent getComponent() {
-    return component;
-  }
-
-  @Override
-  public void updateStep() {
-    if (!settingsInitialised) {
-      init();
-    }
-  }
-
-  private void init() {
-    BlazeNewJavaProjectImportBuilder builder = getBuilder();
-    File fileToImport = new File(builder.getFileToImport());
-    JPanel importControl = control.createComponent(fileToImport);
-    this.component.add(importControl);
-    settingsInitialised = true;
-  }
-
-  @Override
-  public boolean validate() throws ConfigurationException {
-    BlazeValidationResult validationResult = control.validate();
-    if (validationResult.error != null) {
-      throw new ConfigurationException(validationResult.error.getError());
-    }
-    return validationResult.success;
-  }
-
-  @Override
-  public void updateDataModel() {
-    ImportResults importResults = control.getResults();
-
-    BlazeNewJavaProjectImportBuilder builder = getBuilder();
-    WizardContext wizardContext = getWizardContext();
-
-    builder.setImportSettings(importResults.importSettings);
-    builder.setProjectView(importResults.projectView);
-    wizardContext.setProjectName(importResults.projectName);
-    wizardContext.setProjectFileDirectory(importResults.projectDataDirectory);
-  }
-
-  @Override
-  @NotNull
-  protected BlazeNewJavaProjectImportBuilder getBuilder() {
-    BlazeNewJavaProjectImportBuilder builder =
-      (BlazeNewJavaProjectImportBuilder)getWizardContext().getProjectBuilder();
-    assert builder != null;
-    return builder;
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/wizard2/BlazeEditProjectViewImportWizardStep.java b/blaze-java/src/com/google/idea/blaze/java/wizard2/BlazeEditProjectViewImportWizardStep.java
deleted file mode 100644
index b88ba19..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/wizard2/BlazeEditProjectViewImportWizardStep.java
+++ /dev/null
@@ -1,103 +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.wizard2;
-
-import com.google.idea.blaze.base.ui.BlazeValidationResult;
-import com.google.idea.blaze.base.wizard2.BlazeNewProjectBuilder;
-import com.google.idea.blaze.base.wizard2.BlazeProjectCommitException;
-import com.google.idea.blaze.base.wizard2.ui.BlazeEditProjectViewControl;
-import com.intellij.ide.util.projectWizard.WizardContext;
-import com.intellij.ide.wizard.CommitStepException;
-import com.intellij.openapi.options.ConfigurationException;
-import com.intellij.projectImport.ProjectImportWizardStep;
-import org.jetbrains.annotations.NotNull;
-
-import javax.swing.*;
-import java.awt.*;
-
-/**
- * Shows the edit project view screen.
- */
-class BlazeEditProjectViewImportWizardStep extends ProjectImportWizardStep {
-
-  private final JPanel component = new JPanel(new BorderLayout());
-  private BlazeEditProjectViewControl control;
-  private boolean settingsInitialised;
-
-  public BlazeEditProjectViewImportWizardStep(@NotNull WizardContext context) {
-    super(context);
-  }
-
-  @Override
-  public JComponent getComponent() {
-    return component;
-  }
-
-  @Override
-  public void updateStep() {
-    if (!settingsInitialised) {
-      init();
-    } else {
-      control.update(getProjectBuilder());
-    }
-  }
-
-  private void init() {
-    control = new BlazeEditProjectViewControl(getProjectBuilder(), getWizardContext().getDisposable());
-    this.component.add(control.getUiComponent());
-    settingsInitialised = true;
-  }
-
-  @Override
-  public boolean validate() throws ConfigurationException {
-    BlazeValidationResult validationResult = control.validate();
-    if (validationResult.error != null) {
-      throw new ConfigurationException(validationResult.error.getError());
-    }
-    return validationResult.success;
-  }
-
-  @Override
-  public void updateDataModel() {
-    BlazeNewProjectBuilder builder = getProjectBuilder();
-    control.updateBuilder(builder);
-
-    WizardContext wizardContext = getWizardContext();
-    wizardContext.setProjectName(builder.getProjectName());
-    wizardContext.setProjectFileDirectory(builder.getProjectDataDirectory());
-  }
-
-  @Override
-  public void onWizardFinished() throws CommitStepException {
-    try {
-      getProjectBuilder().commit();
-    }
-    catch (BlazeProjectCommitException e) {
-      throw new CommitStepException(e.getMessage());
-    }
-  }
-
-  @Override
-  public String getHelpId() {
-    return "docs/project-views.md";
-  }
-
-  private BlazeNewProjectBuilder getProjectBuilder() {
-    BlazeProjectImportBuilder builder = (BlazeProjectImportBuilder)getWizardContext().getProjectBuilder();
-    assert builder != null;
-    return builder.builder();
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/wizard2/BlazeImportProjectAction.java b/blaze-java/src/com/google/idea/blaze/java/wizard2/BlazeImportProjectAction.java
deleted file mode 100644
index ee3782d..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/wizard2/BlazeImportProjectAction.java
+++ /dev/null
@@ -1,41 +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.wizard2;
-
-import com.google.idea.blaze.base.settings.Blaze;
-import com.intellij.ide.impl.NewProjectUtil;
-import com.intellij.openapi.actionSystem.AnAction;
-import com.intellij.openapi.actionSystem.AnActionEvent;
-
-
-public class BlazeImportProjectAction extends AnAction {
-  @Override
-  public void actionPerformed(AnActionEvent e) {
-    BlazeNewProjectWizard wizard = new BlazeNewProjectWizard(
-      new BlazeNewProjectImportProvider(new BlazeProjectImportBuilder()));
-    if (!wizard.showAndGet()) {
-      return;
-    }
-    //noinspection ConstantConditions
-    NewProjectUtil.createFromWizard(wizard, null);
-  }
-
-  @Override
-  public void update(AnActionEvent e) {
-    super.update(e);
-    e.getPresentation().setText(String.format("Import %s Project...", Blaze.defaultBuildSystemName()));
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/wizard2/BlazeNewProjectImportProvider.java b/blaze-java/src/com/google/idea/blaze/java/wizard2/BlazeNewProjectImportProvider.java
deleted file mode 100644
index 5aa5683..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/wizard2/BlazeNewProjectImportProvider.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.java.wizard2;
-
-import com.intellij.ide.util.projectWizard.ModuleWizardStep;
-import com.intellij.ide.util.projectWizard.WizardContext;
-import com.intellij.projectImport.ProjectImportProvider;
-
-/**
- * The import provider for the Blaze plugin.
- */
-class BlazeNewProjectImportProvider extends ProjectImportProvider {
-
-  public BlazeNewProjectImportProvider(BlazeProjectImportBuilder builder) {
-    super(builder);
-  }
-
-  @Override
-  public ModuleWizardStep[] createSteps(WizardContext context) {
-    return new ModuleWizardStep[]{
-      new BlazeSelectWorkspaceImportWizardStep(context),
-      new BlazeSelectBuildSystemBinaryStep(context),
-      new BlazeSelectProjectViewImportWizardStep(context),
-      new BlazeEditProjectViewImportWizardStep(context)
-    };
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/wizard2/BlazeNewProjectWizard.java b/blaze-java/src/com/google/idea/blaze/java/wizard2/BlazeNewProjectWizard.java
deleted file mode 100644
index 22fb231..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/wizard2/BlazeNewProjectWizard.java
+++ /dev/null
@@ -1,55 +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.wizard2;
-
-import com.google.idea.blaze.base.help.BlazeHelpHandler;
-import com.intellij.ide.util.newProjectWizard.AddModuleWizard;
-import com.intellij.projectImport.ProjectImportProvider;
-import org.jetbrains.annotations.Nullable;
-
-import java.awt.event.ActionListener;
-
-final class BlazeNewProjectWizard extends AddModuleWizard {
-  public BlazeNewProjectWizard(ProjectImportProvider provider) {
-    super(null, null, provider);
-  }
-
-  @Override
-  protected String getDimensionServiceKey() {
-    return null; // No dimension service
-  }
-
-  @Override
-  protected void helpAction() {
-    doHelpAction();
-  }
-
-  @Override
-  protected void doHelpAction() {
-    String helpId = getHelpID();
-    BlazeHelpHandler helpHandler = BlazeHelpHandler.getInstance();
-    if (helpId != null && helpHandler != null) {
-      helpHandler.handleHelp(helpId);
-    }
-  }
-
-  // Swallow the escape key
-  @Nullable
-  @Override
-  protected ActionListener createCancelAction() {
-    return null;
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/wizard2/BlazeProjectImportBuilder.java b/blaze-java/src/com/google/idea/blaze/java/wizard2/BlazeProjectImportBuilder.java
deleted file mode 100644
index 5e02a22..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/wizard2/BlazeProjectImportBuilder.java
+++ /dev/null
@@ -1,88 +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.wizard2;
-
-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.openapi.module.ModifiableModuleModel;
-import com.intellij.openapi.module.Module;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.projectRoots.JavaSdk;
-import com.intellij.openapi.projectRoots.SdkTypeId;
-import com.intellij.openapi.roots.ui.configuration.ModulesProvider;
-import com.intellij.packaging.artifacts.ModifiableArtifactModel;
-import com.intellij.projectImport.ProjectImportBuilder;
-import icons.BlazeIcons;
-import org.jetbrains.annotations.NotNull;
-
-import javax.swing.*;
-import java.util.List;
-
-/**
- * Wrapper around a {@link BlazeNewProjectBuilder} to fit into
- * IntelliJ's import framework.
- */
-class BlazeProjectImportBuilder extends ProjectImportBuilder<Void> {
-  private BlazeNewProjectBuilder builder = new BlazeNewProjectBuilder();
-
-  @NotNull
-  @Override
-  public String getName() {
-    return Blaze.defaultBuildSystemName();
-  }
-
-  @Override
-  public Icon getIcon() {
-    return BlazeIcons.Blaze;
-  }
-
-  @Override
-  public boolean isSuitableSdkType(SdkTypeId sdk) {
-    return sdk == JavaSdk.getInstance();
-  }
-
-  @Override
-  public List<Void> getList() {
-    return ImmutableList.of();
-  }
-
-  @Override
-  public boolean isMarked(Void element) {
-    return true;
-  }
-
-  @Override
-  public void setList(List<Void> gradleProjects) {
-  }
-
-  @Override
-  public void setOpenProjectSettingsAfter(boolean on) {
-  }
-
-  @Override
-  public List<Module> commit(final Project project,
-                             ModifiableModuleModel model,
-                             ModulesProvider modulesProvider,
-                             ModifiableArtifactModel artifactModel) {
-    builder.commitToProject(project);
-    return ImmutableList.of();
-  }
-
-  public BlazeNewProjectBuilder builder() {
-    return builder;
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/wizard2/BlazeSelectBuildSystemBinaryStep.java b/blaze-java/src/com/google/idea/blaze/java/wizard2/BlazeSelectBuildSystemBinaryStep.java
deleted file mode 100644
index 940fc6c..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/wizard2/BlazeSelectBuildSystemBinaryStep.java
+++ /dev/null
@@ -1,93 +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.wizard2;
-
-import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
-import com.google.idea.blaze.base.settings.BlazeUserSettings;
-import com.google.idea.blaze.base.ui.BlazeValidationResult;
-import com.google.idea.blaze.base.wizard2.BlazeNewProjectBuilder;
-import com.google.idea.blaze.base.wizard2.ui.SelectBazelBinaryControl;
-import com.intellij.ide.util.projectWizard.WizardContext;
-import com.intellij.ide.wizard.CommitStepException;
-import com.intellij.openapi.options.ConfigurationException;
-import com.intellij.projectImport.ProjectImportWizardStep;
-import org.jetbrains.annotations.NotNull;
-
-import javax.swing.*;
-import java.awt.*;
-
-class BlazeSelectBuildSystemBinaryStep extends ProjectImportWizardStep {
-
-  private final JPanel component = new JPanel(new BorderLayout());
-  private SelectBazelBinaryControl control;
-  private boolean settingsInitialized = false;
-
-  public BlazeSelectBuildSystemBinaryStep(@NotNull WizardContext context) {
-    super(context);
-  }
-
-  @Override
-  public boolean isStepVisible() {
-    updateStep();
-    if (control.builder.getBuildSystem() != BuildSystem.Bazel) {
-      return false;
-    }
-    String currentBinaryPath = BlazeUserSettings.getInstance().getBazelBinaryPath();
-    return currentBinaryPath == null;
-  }
-
-  @Override
-  public JComponent getComponent() {
-    return component;
-  }
-
-  @Override
-  public void updateStep() {
-    if (!settingsInitialized) {
-      init();
-    }
-  }
-
-  private void init() {
-    control = new SelectBazelBinaryControl(getProjectBuilder());
-    component.add(control.getUiComponent());
-    settingsInitialized = true;
-  }
-
-  @Override
-  public boolean validate() throws ConfigurationException {
-    BlazeValidationResult result = control.validate();
-    if (!result.success) {
-      throw new ConfigurationException(result.error.getError());
-    }
-    return true;
-  }
-
-  @Override
-  public void updateDataModel() {
-  }
-
-  @Override
-  public void onWizardFinished() throws CommitStepException {
-    control.commit();
-  }
-
-  private BlazeNewProjectBuilder getProjectBuilder() {
-    BlazeProjectImportBuilder builder = (BlazeProjectImportBuilder)getWizardContext().getProjectBuilder();
-    assert builder != null;
-    return builder.builder();
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/wizard2/BlazeSelectProjectViewImportWizardStep.java b/blaze-java/src/com/google/idea/blaze/java/wizard2/BlazeSelectProjectViewImportWizardStep.java
deleted file mode 100644
index 14ba13e..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/wizard2/BlazeSelectProjectViewImportWizardStep.java
+++ /dev/null
@@ -1,89 +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.wizard2;
-
-import com.google.idea.blaze.base.ui.BlazeValidationResult;
-import com.google.idea.blaze.base.wizard2.BlazeNewProjectBuilder;
-import com.google.idea.blaze.base.wizard2.ui.BlazeSelectProjectViewControl;
-import com.intellij.ide.util.projectWizard.WizardContext;
-import com.intellij.ide.wizard.CommitStepException;
-import com.intellij.openapi.options.ConfigurationException;
-import com.intellij.projectImport.ProjectImportWizardStep;
-import org.jetbrains.annotations.NotNull;
-
-import javax.swing.*;
-import java.awt.*;
-
-class BlazeSelectProjectViewImportWizardStep extends ProjectImportWizardStep {
-
-  private final JPanel component = new JPanel(new BorderLayout());
-  private BlazeSelectProjectViewControl control;
-  private boolean settingsInitialised;
-
-  public BlazeSelectProjectViewImportWizardStep(@NotNull WizardContext context) {
-    super(context);
-  }
-
-  @Override
-  public JComponent getComponent() {
-    return component;
-  }
-
-  @Override
-  public void updateStep() {
-    if (!settingsInitialised) {
-      init();
-    } else {
-      control.update(getProjectBuilder());
-    }
-  }
-
-  private void init() {
-    control = new BlazeSelectProjectViewControl(getProjectBuilder());
-    this.component.add(control.getUiComponent());
-    settingsInitialised = true;
-  }
-
-  @Override
-  public boolean validate() throws ConfigurationException {
-    BlazeValidationResult result = control.validate();
-    if (!result.success) {
-      throw new ConfigurationException(result.error.getError());
-    }
-    return true;
-  }
-
-  @Override
-  public void updateDataModel() {
-    control.updateBuilder(getProjectBuilder());
-  }
-
-  @Override
-  public void onWizardFinished() throws CommitStepException {
-    control.commit();
-  }
-
-  @Override
-  public String getHelpId() {
-    return "docs/project-views.md";
-  }
-
-  private BlazeNewProjectBuilder getProjectBuilder() {
-    BlazeProjectImportBuilder builder = (BlazeProjectImportBuilder)getWizardContext().getProjectBuilder();
-    assert builder != null;
-    return builder.builder();
-  }
-}
diff --git a/blaze-java/src/com/google/idea/blaze/java/wizard2/BlazeSelectWorkspaceImportWizardStep.java b/blaze-java/src/com/google/idea/blaze/java/wizard2/BlazeSelectWorkspaceImportWizardStep.java
deleted file mode 100644
index 2923643..0000000
--- a/blaze-java/src/com/google/idea/blaze/java/wizard2/BlazeSelectWorkspaceImportWizardStep.java
+++ /dev/null
@@ -1,87 +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.wizard2;
-
-import com.google.idea.blaze.base.ui.BlazeValidationResult;
-import com.google.idea.blaze.base.wizard2.BlazeNewProjectBuilder;
-import com.google.idea.blaze.base.wizard2.ui.BlazeSelectWorkspaceControl;
-import com.intellij.ide.util.projectWizard.WizardContext;
-import com.intellij.ide.wizard.CommitStepException;
-import com.intellij.openapi.options.ConfigurationException;
-import com.intellij.projectImport.ProjectImportWizardStep;
-import org.jetbrains.annotations.NotNull;
-
-import javax.swing.*;
-import java.awt.*;
-
-class BlazeSelectWorkspaceImportWizardStep extends ProjectImportWizardStep {
-
-  private final JPanel component = new JPanel(new BorderLayout());
-  private BlazeSelectWorkspaceControl control;
-  private boolean settingsInitialised;
-
-  public BlazeSelectWorkspaceImportWizardStep(@NotNull WizardContext context) {
-    super(context);
-  }
-
-  @Override
-  public JComponent getComponent() {
-    return component;
-  }
-
-  @Override
-  public void updateStep() {
-    if (!settingsInitialised) {
-      init();
-    }
-  }
-
-  private void init() {
-    control = new BlazeSelectWorkspaceControl(getProjectBuilder());
-    this.component.add(control.getUiComponent());
-    settingsInitialised = true;
-  }
-
-  @Override
-  public boolean validate() throws ConfigurationException {
-    BlazeValidationResult result = control.validate();
-    if (!result.success) {
-      throw new ConfigurationException(result.error.getError());
-    }
-    return true;
-  }
-
-  @Override
-  public void updateDataModel() {
-    control.updateBuilder(getProjectBuilder());
-  }
-
-  @Override
-  public void onWizardFinished() throws CommitStepException {
-    control.commit();
-  }
-
-  @Override
-  public String getHelpId() {
-    return "docs/import-project.md";
-  }
-
-  private BlazeNewProjectBuilder getProjectBuilder() {
-    BlazeProjectImportBuilder builder = (BlazeProjectImportBuilder)getWizardContext().getProjectBuilder();
-    assert builder != null;
-    return builder.builder();
-  }
-}
diff --git a/blaze-java/tests/integrationtests/com/google/idea/blaze/java/lang/build/JavaClassRenameTest.java b/blaze-java/tests/integrationtests/com/google/idea/blaze/java/lang/build/JavaClassRenameTest.java
deleted file mode 100644
index b4f6d18..0000000
--- a/blaze-java/tests/integrationtests/com/google/idea/blaze/java/lang/build/JavaClassRenameTest.java
+++ /dev/null
@@ -1,45 +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.lang.build;
-
-import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
-import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
-import com.intellij.psi.PsiJavaFile;
-import com.intellij.refactoring.rename.RenameProcessor;
-
-/**
- * Tests that BUILD file references are correctly updated when performing rename refactors.
- */
-public class JavaClassRenameTest extends BuildFileIntegrationTestCase {
-
-  public void testRenameJavaClass() {
-    PsiJavaFile javaFile = (PsiJavaFile) createPsiFile(
-        "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 RenameProcessor(getProject(), javaFile.getClasses()[0], "NewName", false, false).run();
-
-    assertFileContents(
-        buildFile,
-        "java_library(name = \"ref2\", srcs = [\"NewName.java\"])");
-  }
-
-}
diff --git a/blaze-java/tests/integrationtests/com/google/idea/blaze/java/lang/build/SafeDeleteTest.java b/blaze-java/tests/integrationtests/com/google/idea/blaze/java/lang/build/SafeDeleteTest.java
deleted file mode 100644
index ab9390f..0000000
--- a/blaze-java/tests/integrationtests/com/google/idea/blaze/java/lang/build/SafeDeleteTest.java
+++ /dev/null
@@ -1,79 +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.lang.build;
-
-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.util.PsiUtils;
-import com.intellij.psi.PsiClass;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiFile;
-import com.intellij.refactoring.BaseRefactoringProcessor;
-import com.intellij.refactoring.safeDelete.SafeDeleteHandler;
-
-/**
- * Tests for the safe delete action which aren't covered by existing tests.
- */
-public class SafeDeleteTest extends BuildFileIntegrationTestCase {
-
-  public void testIndirectGlobReferencesNotIncluded() {
-    PsiFile javaFile = createPsiFile(
-      "com/google/Test.java",
-      "package com.google;",
-      "public class Test {}");
-
-    PsiClass javaClass = PsiUtils.findFirstChildOfClassRecursive(javaFile, PsiClass.class);
-
-    BuildFile buildFile = createBuildFile(
-      "com/google/BUILD",
-      "java_library(",
-      "    name = 'lib'",
-      "    srcs = glob(['*.java'])",
-      ")");
-
-    try {
-      SafeDeleteHandler.invoke(getProject(), new PsiElement[] {javaClass}, true);
-    } catch (BaseRefactoringProcessor.ConflictsInTestsException e) {
-      fail("Glob reference was incorrectly included");
-      return;
-    }
-  }
-
-  public void testDirectGlobReferencesIncluded() {
-    PsiFile javaFile = createPsiFile(
-      "com/google/Test.java",
-      "package com.google;",
-      "public class Test {}");
-
-    PsiClass javaClass = PsiUtils.findFirstChildOfClassRecursive(javaFile, PsiClass.class);
-
-    BuildFile buildFile = createBuildFile(
-      "com/google/BUILD",
-      "java_library(",
-      "    name = 'lib'",
-      "    srcs = glob(['Test.java'])",
-      ")");
-
-    try {
-      SafeDeleteHandler.invoke(getProject(), new PsiElement[] {javaClass}, true);
-    } catch (BaseRefactoringProcessor.ConflictsInTestsException expected) {
-      return;
-    }
-    fail("Expected an unsafe usage to be found");
-  }
-
-
-}
diff --git a/blaze-java/tests/integrationtests/com/google/idea/blaze/java/sync/JavaSyncTest.java b/blaze-java/tests/integrationtests/com/google/idea/blaze/java/sync/JavaSyncTest.java
deleted file mode 100644
index 52998b1..0000000
--- a/blaze-java/tests/integrationtests/com/google/idea/blaze/java/sync/JavaSyncTest.java
+++ /dev/null
@@ -1,98 +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.ImmutableMap;
-import com.google.idea.blaze.base.ideinfo.JavaRuleIdeInfo;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.ideinfo.RuleMapBuilder;
-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.sync.BlazeSyncIntegrationTestCase;
-import com.google.idea.blaze.base.sync.BlazeSyncParams;
-import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
-import com.google.idea.blaze.java.sync.model.BlazeContentEntry;
-import com.google.idea.blaze.java.sync.model.BlazeJavaSyncData;
-import com.google.idea.blaze.java.sync.model.BlazeSourceDirectory;
-
-import java.util.List;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Java-specific sync integration tests.
- */
-public class JavaSyncTest extends BlazeSyncIntegrationTestCase {
-
-  public void testJavaClassesPresentInClassPath() throws Exception {
-    setProjectView(
-      "directories:",
-      "  java/com/google",
-      "targets:",
-      "  //java/com/google:lib",
-      "workspace_type: java"
-    );
-
-    createWorkspaceFile(
-      "java/com/google/ClassWithUniqueName1.java",
-      "package com.google;",
-      "public class ClassWithUniqueName1 {}"
-    );
-
-    createWorkspaceFile(
-      "java/com/google/ClassWithUniqueName2.java",
-      "package com.google;",
-      "public class ClassWithUniqueName2 {}"
-    );
-
-    ImmutableMap<Label, RuleIdeInfo> ruleMap = RuleMapBuilder.builder()
-      .addRule(RuleIdeInfo.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()))
-      .build();
-
-    setRuleMap(ruleMap);
-
-    BlazeSyncParams syncParams = new BlazeSyncParams.Builder("Full Sync", BlazeSyncParams.SyncMode.FULL).build();
-    runBlazeSync(syncParams);
-
-    assertNoErrors();
-
-    BlazeProjectData blazeProjectData = BlazeProjectDataManager.getInstance(getProject()).getBlazeProjectData();
-    assertThat(blazeProjectData).isNotNull();
-    assertThat(blazeProjectData.ruleMap).isEqualTo(ruleMap);
-    assertThat(blazeProjectData.workspaceLanguageSettings.getWorkspaceType())
-      .isEqualTo(WorkspaceType.JAVA);
-
-    BlazeJavaSyncData javaSyncData = blazeProjectData.syncState.get(BlazeJavaSyncData.class);
-    List<BlazeContentEntry> contentEntries = javaSyncData.importResult.contentEntries;
-    assertThat(contentEntries).hasSize(1);
-
-    BlazeContentEntry contentEntry = contentEntries.get(0);
-    assertThat(contentEntry.contentRoot.getPath()).isEqualTo(tempDirectory.getPath() + "/java/com/google");
-    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");
-  }
-
-}
diff --git a/blaze-java/tests/unittests/com/google/idea/blaze/java/run/BlazeCommandRunConfigurationTest.java b/blaze-java/tests/unittests/com/google/idea/blaze/java/run/BlazeCommandRunConfigurationTest.java
deleted file mode 100644
index 07a59b2..0000000
--- a/blaze-java/tests/unittests/com/google/idea/blaze/java/run/BlazeCommandRunConfigurationTest.java
+++ /dev/null
@@ -1,145 +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.run;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.BlazeTestCase;
-import com.google.idea.blaze.base.command.BlazeCommandName;
-import com.google.idea.blaze.base.experiments.ExperimentService;
-import com.google.idea.blaze.base.experiments.MockExperimentService;
-import com.google.idea.blaze.base.model.primitives.Label;
-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;
-import com.google.idea.blaze.java.run.BlazeCommandRunConfiguration.BlazeCommandRunConfigurationSettingsEditor;
-import com.intellij.openapi.options.ConfigurationException;
-import com.intellij.openapi.util.Disposer;
-import com.intellij.openapi.util.InvalidDataException;
-import com.intellij.openapi.util.WriteExternalException;
-import org.jdom.Element;
-import org.jetbrains.annotations.NotNull;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Tests for {@link BlazeCommandRunConfiguration}.
- */
-@RunWith(JUnit4.class)
-public class BlazeCommandRunConfigurationTest extends BlazeTestCase {
-  private static Label label;
-  private static final BlazeCommandName COMMAND = BlazeCommandName.fromString("command");
-  private static final BlazeImportSettings DUMMY_IMPORT_SETTINGS = new BlazeImportSettings("", "", "", "", "", BuildSystem.Blaze);
-
-  BlazeCommandRunConfigurationType type = new BlazeCommandRunConfigurationType();
-  BlazeCommandRunConfiguration configuration;
-
-
-  @Override
-  protected void initTest(
-    @NotNull Container applicationServices,
-    @NotNull Container projectServices) {
-    super.initTest(applicationServices, projectServices);
-
-    applicationServices.register(ExperimentService.class, new MockExperimentService());
-    projectServices.register(BlazeImportSettingsManager.class, new BlazeImportSettingsManager(project));
-    BlazeImportSettingsManager.getInstance(getProject()).setImportSettings(DUMMY_IMPORT_SETTINGS);
-
-    configuration = type.getFactory().createTemplateConfiguration(project);
-    label = new Label("//package:rule");
-  }
-
-  @Test
-  public void readAndWriteShouldMatch() throws InvalidDataException, WriteExternalException {
-    configuration.setTarget(label);
-    configuration.setCommand(COMMAND);
-    configuration.setBlazeFlags(ImmutableList.of("--flag1", "--flag2"));
-    configuration.setExeFlags(ImmutableList.of("--exeFlag1"));
-    Element element = new Element("test");
-    configuration.writeExternal(element);
-    BlazeCommandRunConfiguration readConfiguration =
-      type.getFactory().createTemplateConfiguration(project);
-    readConfiguration.readExternal(element);
-    assertThat(readConfiguration.getTarget()).isEqualTo(label);
-    assertThat(readConfiguration.getCommand()).isEqualTo(COMMAND);
-    assertThat(readConfiguration.getAllBlazeFlags()).isEqualTo(ImmutableList.of("--flag1", "--flag2"));
-    assertThat(readConfiguration.getAllExeFlags()).isEqualTo(ImmutableList.of("--exeFlag1"));
-  }
-
-  @Test
-  public void readAndWriteShouldHandleNulls() throws InvalidDataException, WriteExternalException {
-    Element element = new Element("test");
-    configuration.writeExternal(element);
-    BlazeCommandRunConfiguration readConfiguration =
-      type.getFactory().createTemplateConfiguration(project);
-    readConfiguration.readExternal(element);
-    assertThat(readConfiguration.getTarget()).isEqualTo(configuration.getTarget());
-    assertThat(readConfiguration.getCommand()).isEqualTo(configuration.getCommand());
-    assertThat(readConfiguration.getAllBlazeFlags()).isEqualTo(configuration.getAllBlazeFlags());
-    assertThat(readConfiguration.getAllExeFlags()).isEqualTo(configuration.getAllExeFlags());
-  }
-
-  @Test
-  public void readShouldOmitEmptyFlags() throws InvalidDataException, WriteExternalException {
-    configuration.setBlazeFlags(Lists.newArrayList("hi ", "", "I'm", " ", "\t", "Josh\r\n", "\n"));
-    configuration.setExeFlags(Lists.newArrayList("hi ", "", "I'm", " ", "\t", "Josh\r\n", "\n"));
-    Element element = new Element("test");
-    configuration.writeExternal(element);
-    BlazeCommandRunConfiguration readConfiguration =
-      type.getFactory().createTemplateConfiguration(project);
-    readConfiguration.readExternal(element);
-    assertThat(readConfiguration.getAllBlazeFlags()).isEqualTo(ImmutableList.of("hi", "I'm", "Josh"));
-    assertThat(readConfiguration.getAllExeFlags()).isEqualTo(ImmutableList.of("hi", "I'm", "Josh"));
-  }
-
-  @Test
-  public void editorApplyToAndResetFromShouldHandleNulls() throws ConfigurationException {
-    BlazeCommandRunConfigurationSettingsEditor editor =
-      new BlazeCommandRunConfigurationSettingsEditor("Blaze");
-    editor.resetFrom(configuration);
-    BlazeCommandRunConfiguration readConfiguration =
-      type.getFactory().createTemplateConfiguration(project);
-    editor.applyEditorTo(readConfiguration);
-    assertThat(readConfiguration.getTarget()).isEqualTo(configuration.getTarget());
-    assertThat(readConfiguration.getCommand()).isEqualTo(configuration.getCommand());
-    assertThat(readConfiguration.getAllBlazeFlags()).isEqualTo(configuration.getAllBlazeFlags());
-    assertThat(readConfiguration.getAllExeFlags()).isEqualTo(configuration.getAllExeFlags());
-
-    Disposer.dispose(editor);
-  }
-
-  @Test
-  public void editorApplyToAndResetFromShouldMatch() throws ConfigurationException {
-    BlazeCommandRunConfigurationSettingsEditor editor = new BlazeCommandRunConfigurationSettingsEditor("Blaze");
-    configuration.setTarget(label);
-    configuration.setCommand(COMMAND);
-    configuration.setBlazeFlags(ImmutableList.of("--flag1", "--flag2"));
-    configuration.setExeFlags(ImmutableList.of("--exeFlag1", "--exeFlag2"));
-    editor.resetFrom(configuration);
-
-    BlazeCommandRunConfiguration readConfiguration = type.getFactory().createTemplateConfiguration(project);
-    editor.applyEditorTo(readConfiguration);
-    assertThat(readConfiguration.getTarget()).isEqualTo(configuration.getTarget());
-    assertThat(readConfiguration.getCommand()).isEqualTo(configuration.getCommand());
-    assertThat(readConfiguration.getAllBlazeFlags()).isEqualTo(configuration.getAllBlazeFlags());
-    assertThat(readConfiguration.getAllExeFlags()).isEqualTo(configuration.getAllExeFlags());
-    
-    Disposer.dispose(editor);
-  }
-}
diff --git a/blaze-java/tests/unittests/com/google/idea/blaze/java/run/BlazeCommandRunProfileStateTest.java b/blaze-java/tests/unittests/com/google/idea/blaze/java/run/BlazeCommandRunProfileStateTest.java
deleted file mode 100644
index 03a0a7f..0000000
--- a/blaze-java/tests/unittests/com/google/idea/blaze/java/run/BlazeCommandRunProfileStateTest.java
+++ /dev/null
@@ -1,136 +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.run;
-
-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.command.BlazeCommandName;
-import com.google.idea.blaze.base.command.BlazeFlags;
-import com.google.idea.blaze.base.command.BuildFlagsProvider;
-import com.google.idea.blaze.base.experiments.ExperimentService;
-import com.google.idea.blaze.base.experiments.MockExperimentService;
-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.google.idea.blaze.base.projectview.ProjectViewSet;
-import com.google.idea.blaze.base.run.rulefinder.RuleFinder;
-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;
-import com.google.idea.blaze.base.settings.BlazeUserSettings;
-import com.intellij.openapi.project.Project;
-import org.jetbrains.annotations.NotNull;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.List;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Tests for {@link BlazeCommandRunProfileState}.
- */
-@RunWith(JUnit4.class)
-public class BlazeCommandRunProfileStateTest extends BlazeTestCase {
-
-  private static final BlazeImportSettings DUMMY_IMPORT_SETTINGS = new BlazeImportSettings("", "", "", "", "", BuildSystem.Blaze);
-
-  private BlazeCommandRunConfiguration configuration;
-
-  @Override
-  protected void initTest(@NotNull Container applicationServices, @NotNull Container projectServices) {
-    projectServices.register(BlazeImportSettingsManager.class, new BlazeImportSettingsManager(project));
-    BlazeImportSettingsManager.getInstance(getProject()).setImportSettings(DUMMY_IMPORT_SETTINGS);
-
-    configuration = new BlazeCommandRunConfigurationType().getFactory().createTemplateConfiguration(project);
-
-    ExperimentService experimentService = new MockExperimentService();
-    applicationServices.register(ExperimentService.class, experimentService);
-    applicationServices.register(RuleFinder.class, new MockRuleFinder());
-    applicationServices.register(BlazeUserSettings.class, new BlazeUserSettings());
-    registerExtensionPoint(BuildFlagsProvider.EP_NAME, BuildFlagsProvider.class);
-  }
-
-  @Test
-  public void flagsShouldBeAppendedIfPresent() {
-    configuration.setTarget(new Label("//label:rule"));
-    configuration.setCommand(BlazeCommandName.fromString("command"));
-    configuration.setBlazeFlags(ImmutableList.of("--flag1", "--flag2"));
-    assertThat(
-      BlazeCommandRunProfileState.getBlazeCommand(project, configuration, ProjectViewSet.builder().build(), false /* debug */).toList())
-      .isEqualTo(ImmutableList.of(
-        "/usr/bin/blaze",
-        "command",
-        BlazeFlags.getToolTagFlag(),
-        "--flag1",
-        "--flag2",
-        "--",
-        "//label:rule"
-      ));
-  }
-
-  @Test
-  public void debugFlagShouldBeIncludedForJavaTest() {
-    configuration.setTarget(new Label("//label:rule"));
-    configuration.setCommand(BlazeCommandName.fromString("command"));
-    assertThat(
-      BlazeCommandRunProfileState.getBlazeCommand(project, configuration, ProjectViewSet.builder().build(), true /* debug */).toList())
-      .isEqualTo(ImmutableList.of(
-        "/usr/bin/blaze",
-        "command",
-        BlazeFlags.getToolTagFlag(),
-        "--java_debug",
-        "--",
-        "//label:rule"
-      ));
-  }
-
-  @Test
-  public void debugFlagShouldBeIncludedForJavaBinary() {
-    configuration.setTarget(new Label("//label:java_binary_rule"));
-    configuration.setCommand(BlazeCommandName.fromString("command"));
-    assertThat(
-      BlazeCommandRunProfileState.getBlazeCommand(project, configuration, ProjectViewSet.builder().build(), true /* debug */).toList())
-      .isEqualTo(ImmutableList.of(
-        "/usr/bin/blaze",
-        "command",
-        BlazeFlags.getToolTagFlag(),
-        "--",
-        "//label:java_binary_rule",
-        "--debug"
-      ));
-  }
-
-  private static class MockRuleFinder extends RuleFinder {
-    @Override
-    public List<RuleIdeInfo> findRules(Project project, Predicate<RuleIdeInfo> 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")) {
-        builder.setKind(Kind.JAVA_BINARY);
-      } else {
-        builder.setKind(Kind.JAVA_TEST);
-      }
-      return builder.build();
-    }
-  }
-}
diff --git a/blaze-java/tests/unittests/com/google/idea/blaze/java/sync/importer/BlazeJavaWorkspaceImporterTest.java b/blaze-java/tests/unittests/com/google/idea/blaze/java/sync/importer/BlazeJavaWorkspaceImporterTest.java
deleted file mode 100644
index 1393cb1..0000000
--- a/blaze-java/tests/unittests/com/google/idea/blaze/java/sync/importer/BlazeJavaWorkspaceImporterTest.java
+++ /dev/null
@@ -1,1146 +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.importer;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-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.experiments.ExperimentService;
-import com.google.idea.blaze.base.experiments.MockExperimentService;
-import com.google.idea.blaze.base.ideinfo.*;
-import com.google.idea.blaze.base.model.primitives.ExecutionRootPath;
-import com.google.idea.blaze.base.model.primitives.Label;
-import com.google.idea.blaze.base.model.primitives.WorkspacePath;
-import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
-import com.google.idea.blaze.base.prefetch.MockPrefetchService;
-import com.google.idea.blaze.base.prefetch.PrefetchService;
-import com.google.idea.blaze.base.projectview.ProjectView;
-import com.google.idea.blaze.base.projectview.ProjectViewSet;
-import com.google.idea.blaze.base.projectview.section.Glob;
-import com.google.idea.blaze.base.projectview.section.ListSection;
-import com.google.idea.blaze.base.projectview.section.sections.*;
-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.settings.BlazeImportSettings;
-import com.google.idea.blaze.base.settings.BlazeImportSettingsManager;
-import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
-import com.google.idea.blaze.base.sync.workspace.BlazeRoots;
-import com.google.idea.blaze.base.sync.workspace.WorkingSet;
-import com.google.idea.blaze.java.sync.jdeps.JdepsMap;
-import com.google.idea.blaze.java.sync.model.*;
-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;
-import com.google.idea.blaze.java.sync.workingset.JavaWorkingSet;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import org.junit.Test;
-
-import java.io.File;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.*;
-
-/**
- * Tests for BlazeJavaWorkspaceImporter
- */
-public class BlazeJavaWorkspaceImporterTest extends BlazeTestCase {
-
-  private String FAKE_WORKSPACE_ROOT = "/root";
-  private WorkspaceRoot workspaceRoot = new WorkspaceRoot(new File(FAKE_WORKSPACE_ROOT));
-
-  private static final String FAKE_GEN_ROOT_EXECUTION_PATH_FRAGMENT =
-    "blaze-out/gcc-4.X.Y-crosstool-v17-hybrid-grtev3-k8-fastbuild/bin";
-
-  private static final String FAKE_GEN_ROOT =
-    "/path/to/8093958afcfde6c33d08b621dfaa4e09/root/"
-    + FAKE_GEN_ROOT_EXECUTION_PATH_FRAGMENT;
-
-  private static final ArtifactLocationDecoder FAKE_ARTIFACT_DECODER = new ArtifactLocationDecoder(
-    new BlazeRoots(
-      new File("/"),
-      ImmutableList.of(),
-      new ExecutionRootPath("out/crosstool/bin"),
-      new ExecutionRootPath("out/crosstool/gen")
-    ),
-    null
-  );
-
-  private static final BlazeImportSettings DUMMY_IMPORT_SETTINGS = new BlazeImportSettings("", "", "", "", "", BuildSystem.Blaze);
-
-  private static class JdepsMock implements JdepsMap {
-    Map<Label, List<String>> jdeps = Maps.newHashMap();
-
-    @Nullable
-    @Override
-    public List<String> getDependenciesForRule(@NotNull Label label) {
-      return jdeps.get(label);
-    }
-
-    JdepsMock put(Label label, List<String> values) {
-      jdeps.put(label, values);
-      return this;
-    }
-  }
-
-  private BlazeContext context;
-  private ErrorCollector errorCollector = new ErrorCollector();
-  private final JdepsMock jdepsMap = new JdepsMock();
-  private JavaWorkingSet workingSet = null;
-  private MockExperimentService experimentService;
-
-  @Override
-  protected void initTest(@NotNull Container applicationServices, @NotNull Container projectServices) {
-    experimentService = new MockExperimentService();
-    applicationServices.register(ExperimentService.class, experimentService);
-
-    BlazeExecutor blazeExecutor = new MockBlazeExecutor();
-    applicationServices.register(BlazeExecutor.class, blazeExecutor);
-    projectServices.register(BlazeImportSettingsManager.class, new BlazeImportSettingsManager(project));
-    BlazeImportSettingsManager.getInstance(getProject()).setImportSettings(DUMMY_IMPORT_SETTINGS);
-
-    // will silently fall back to FilePathJavaPackageReader
-    applicationServices.register(
-      JavaSourcePackageReader.class,
-      new JavaSourcePackageReader() {
-        @Nullable
-        @Override
-        public String getDeclaredPackageOfJavaFile(@NotNull BlazeContext context, @NotNull SourceArtifact sourceArtifact) {
-          return null;
-        }
-      }
-    );
-    applicationServices.register(PackageManifestReader.class, new PackageManifestReader());
-    applicationServices.register(PrefetchService.class, new MockPrefetchService());
-
-    context = new BlazeContext();
-    context.addOutputSink(IssueOutput.class, errorCollector);
-  }
-
-  BlazeJavaImportResult importWorkspace(
-    WorkspaceRoot workspaceRoot,
-    RuleMapBuilder ruleMapBuilder,
-    ProjectView projectView) {
-
-    ProjectViewSet projectViewSet = ProjectViewSet.builder().add(projectView).build();
-
-    BlazeJavaWorkspaceImporter blazeWorkspaceImporter = new BlazeJavaWorkspaceImporter(
-      project,
-      workspaceRoot,
-      projectViewSet,
-      ruleMapBuilder.build(),
-      jdepsMap,
-      workingSet,
-      FAKE_ARTIFACT_DECODER
-    );
-
-    return blazeWorkspaceImporter.importWorkspace(context);
-  }
-
-  /**
-   * Ensure an empty response results in an empty import result.
-   */
-  @Test
-  public void testEmptyProject() {
-    BlazeJavaImportResult result = importWorkspace(
-      workspaceRoot,
-      RuleMapBuilder.builder(),
-      ProjectView.builder().build()
-    );
-    errorCollector.assertNoIssues();
-    assertTrue(result.contentEntries.isEmpty());
-  }
-
-  @Test
-  public void testSingleModule() {
-    ProjectView projectView = ProjectView.builder()
-      .put(ListSection.builder(DirectorySection.KEY)
-             .add(DirectoryEntry.include(new WorkspacePath("java/com/google/android/apps/example"))))
-      .build();
-
-    RuleMapBuilder ruleMapBuilder = RuleMapBuilder.builder()
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//java/com/google/android/apps/example:example_debug")
-          .setBuildFile(sourceRoot("java/com/google/android/apps/example/BUILD"))
-          .setKind("android_binary")
-          .addSource(sourceRoot("java/com/google/android/apps/example/MainActivity.java"))
-          .addSource(sourceRoot("java/com/google/android/apps/example/subdir/SubdirHelper.java"))
-          .setAndroidInfo(AndroidRuleIdeInfo.builder()
-                            .setManifestFile(sourceRoot("java/com/google/android/apps/example/AndroidManifest.xml"))
-                            .addResource(sourceRoot("java/com/google/android/apps/example/res"))
-                            .setGenerateResourceClass(true)
-                            .setResourceJavaPackage("com.google.android.apps.example"))
-          .setJavaInfo(JavaRuleIdeInfo.builder()
-                         .addJar(LibraryArtifact.builder()
-                                   .setJar(genRoot("java/com/google/android/apps/example/example_debug-ijar.jar"))
-                                   .setRuntimeJar(genRoot("java/com/google/android/apps/example/example_debug.jar")))));
-
-    BlazeJavaImportResult result = importWorkspace(
-      workspaceRoot,
-      ruleMapBuilder,
-      projectView
-    );
-    errorCollector.assertNoIssues();
-
-    assertEquals(1, result.buildOutputJars.size());
-    File compilerOutputLib = result.buildOutputJars.iterator().next();
-    assertNotNull(compilerOutputLib);
-    assertTrue(compilerOutputLib.getPath().endsWith("example_debug.jar"));
-
-    assertThat(result.contentEntries).containsExactly(
-      BlazeContentEntry.builder("/root/java/com/google/android/apps/example")
-        .addSource(BlazeSourceDirectory.builder("/root/java/com/google/android/apps/example")
-                     .setPackagePrefix("com.google.android.apps.example")
-                     .build())
-        .build()
-    );
-
-    assertThat(result.javaSourceFiles).containsExactly(
-      sourceRoot("java/com/google/android/apps/example/MainActivity.java").getFile(),
-      sourceRoot("java/com/google/android/apps/example/subdir/SubdirHelper.java").getFile()
-    );
-  }
-
-  @Test
-  public void testGeneratedLibrariesIncluded() {
-    ProjectView projectView = ProjectView.builder()
-      .put(ListSection.builder(DirectorySection.KEY)
-             .add(DirectoryEntry.include(new WorkspacePath("java/com/google/example"))))
-             .build();
-
-    RuleMapBuilder ruleMapBuilder = RuleMapBuilder.builder()
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//java/com/google/example:lib")
-          .setBuildFile(sourceRoot("java/com/google/example/BUILD"))
-          .setKind("java_library")
-          .addSource(sourceRoot("java/com/google/example/Test.java"))
-          .setJavaInfo(JavaRuleIdeInfo.builder()
-                         .addJar(LibraryArtifact.builder()
-                                   .setJar(genRoot("java/com/google/example/lib-ijar.jar"))
-                                   .setRuntimeJar(genRoot("java/com/google/example/lib.jar")))
-                         .addGeneratedJar(LibraryArtifact.builder()
-                                            .setJar(genRoot("java/com/google/example/lib-gen.jar"))
-                                            .setRuntimeJar(genRoot("java/com/google/example/lib-gen.jar")))));
-
-    BlazeJavaImportResult result = importWorkspace(
-      workspaceRoot,
-      ruleMapBuilder,
-      projectView
-    );
-    assertThat(result.libraries.values().stream().map(BlazeJavaWorkspaceImporterTest::libraryFileName).collect(Collectors.toList()))
-      .containsExactly("lib-gen.jar");
-  }
-
-
-  /**
-   * Imports two binaries and a library. Only one binary should pass the package filter.
-   */
-  @Test
-  public void testImportFilter() {
-    ProjectView projectView = ProjectView.builder()
-      .put(ListSection.builder(DirectorySection.KEY)
-             .add(DirectoryEntry.include(new WorkspacePath("java/com/google/android/apps/example"))))
-      .build();
-
-    RuleMapBuilder ruleMapBuilder = RuleMapBuilder.builder()
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//java/com/google/android/apps/example:example_debug")
-          .setBuildFile(sourceRoot("java/com/google/android/apps/example/BUILD"))
-          .setKind("android_binary")
-          .addSource(sourceRoot("java/com/google/android/apps/example/MainActivity.java"))
-          .setAndroidInfo(AndroidRuleIdeInfo.builder()
-                            .setManifestFile(sourceRoot("java/com/google/android/apps/example/AndroidManifest.xml"))
-                            .addResource(sourceRoot("java/com/google/android/apps/example/res"))
-                            .setGenerateResourceClass(true)
-                            .setResourceJavaPackage("com.google.android.apps.example"))
-          .addDependency("//java/com/google/android/libraries/example:example")
-          .setJavaInfo(JavaRuleIdeInfo.builder()
-                         .addJar(LibraryArtifact.builder()
-                                   .setJar(genRoot("java/com/google/android/apps/example/example_debug.jar"))
-                                   .setRuntimeJar(genRoot("java/com/google/android/apps/example/example_debug.jar")))))
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//java/com/google/android/libraries/example:example")
-          .setBuildFile(sourceRoot("java/com/google/android/libraries/example/BUILD"))
-          .setKind("android_library")
-          .addSource(sourceRoot("java/com/google/android/libraries/example/SharedActivity.java"))
-          .setAndroidInfo(
-            AndroidRuleIdeInfo.builder()
-              .setManifestFile(sourceRoot("java/com/google/android/libraries/example/AndroidManifest.xml"))
-              .addResource(sourceRoot("java/com/google/android/libraries/example/res"))
-              .setGenerateResourceClass(true)
-              .setResourceJavaPackage("com.google.android.libraries.example"))
-          .setJavaInfo(JavaRuleIdeInfo.builder()
-                         .addJar(LibraryArtifact.builder()
-                                   .setJar(genRoot("java/com/google/android/libraries/example/example.jar"))
-                                   .setRuntimeJar(genRoot("java/com/google/android/libraries/example/example.jar")))))
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//java/com/dontimport:example_debug")
-          .setBuildFile(sourceRoot("java/com/dontimport/BUILD"))
-          .setKind("android_binary")
-          .addSource(sourceRoot("java/com/dontimport/MainActivity.java"))
-          .setAndroidInfo(AndroidRuleIdeInfo.builder()
-                            .setManifestFile(sourceRoot("java/com/dontimport/AndroidManifest.xml"))
-                            .addResource(sourceRoot("java/com/dontimport/res"))
-                            .setGenerateResourceClass(true)
-                            .setResourceJavaPackage("com.dontimport"))
-          .addDependency("//java/com/dontimport:sometarget")
-          .setJavaInfo(JavaRuleIdeInfo.builder()
-                         .addJar(LibraryArtifact.builder()
-                                   .setJar(genRoot("java/com/dontimport/example_debug.jar"))
-                                   .setRuntimeJar(genRoot("java/com/dontimport/example_debug.jar")))));
-
-    BlazeJavaImportResult result = importWorkspace(
-      workspaceRoot,
-      ruleMapBuilder,
-      projectView
-    );
-    errorCollector.assertNoIssues();
-
-    assertThat(result.contentEntries).containsExactly(
-      BlazeContentEntry.builder("/root/java/com/google/android/apps/example")
-        .addSource(BlazeSourceDirectory.builder("/root/java/com/google/android/apps/example")
-                     .setPackagePrefix("com.google.android.apps.example")
-                     .build())
-        .build()
-    );
-    assertThat(result.javaSourceFiles).containsExactly(
-      sourceRoot("java/com/google/android/apps/example/MainActivity.java").getFile()
-    );
-  }
-
-  /**
-   * Import a project and its tests
-   */
-  @Test
-  public void testProjectAndTests() {
-    ProjectView projectView = ProjectView.builder()
-      .put(ListSection.builder(DirectorySection.KEY)
-             .add(DirectoryEntry.include(new WorkspacePath("java/com/google/android/apps/example")))
-             .add(DirectoryEntry.include(new WorkspacePath("javatests/com/google/android/apps/example"))))
-      .put(ListSection.builder(TestSourceSection.KEY).add(new Glob("javatests/*")))
-      .build();
-
-    RuleMapBuilder ruleMapBuilder = RuleMapBuilder.builder()
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//java/com/google/android/apps/example:example_debug")
-          .setBuildFile(sourceRoot("java/com/google/android/apps/example/BUILD"))
-          .setKind("android_binary")
-          .addSource(sourceRoot("java/com/google/android/apps/example/MainActivity.java"))
-          .addSource(sourceRoot("java/com/google/android/apps/example/subdir/SubdirHelper.java"))
-          .setAndroidInfo(AndroidRuleIdeInfo.builder()
-                            .setManifestFile(sourceRoot("java/com/google/android/apps/example/AndroidManifest.xml"))
-                            .addResource(sourceRoot("java/com/google/android/apps/example/res"))
-                            .setGenerateResourceClass(true)
-                            .setResourceJavaPackage("com.google.android.apps.example"))
-          .setJavaInfo(JavaRuleIdeInfo.builder()
-                         .addJar(LibraryArtifact.builder()
-                                   .setJar(genRoot("java/com/google/android/apps/example/example_debug.jar"))
-                                   .setRuntimeJar(genRoot("java/com/google/android/apps/example/example_debug.jar")))))
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//javatests/com/google/android/apps/example:example")
-          .setBuildFile(sourceRoot("javatests/com/google/android/apps/example/BUILD"))
-          .setKind("android_test")
-          .addSource(sourceRoot("javatests/com/google/android/apps/example/ExampleTests.java"))
-          .setAndroidInfo(AndroidRuleIdeInfo.builder()
-                            .setResourceJavaPackage("com.google.android.apps.example"))
-          .addDependency("//java/com/google/android/apps/example:example_debug")
-          .setJavaInfo(JavaRuleIdeInfo.builder()
-                         .addJar(LibraryArtifact.builder()
-                                   .setJar(genRoot("javatests/com/google/android/apps/example/example.jar"))
-                                   .setRuntimeJar(genRoot("javatests/com/google/android/apps/example/example.jar")))));
-
-    BlazeJavaImportResult result = importWorkspace(
-      workspaceRoot,
-      ruleMapBuilder,
-      projectView
-    );
-    errorCollector.assertNoIssues();
-
-    assertThat(result.contentEntries).containsExactly(
-      BlazeContentEntry.builder("/root/java/com/google/android/apps/example")
-        .addSource(BlazeSourceDirectory.builder("/root/java/com/google/android/apps/example")
-                     .setPackagePrefix("com.google.android.apps.example")
-                     .build())
-        .build(),
-      BlazeContentEntry.builder("/root/javatests/com/google/android/apps/example")
-        .addSource(BlazeSourceDirectory.builder("/root/javatests/com/google/android/apps/example")
-                     .setPackagePrefix("com.google.android.apps.example")
-                     .setTest(true)
-                     .build())
-        .build()
-    );
-  }
-
-  /**
-   * Test library with a source jar
-   */
-  @Test
-  public void testLibraryWithSourceJar() {
-    ProjectView projectView = ProjectView.builder()
-      .put(ListSection.builder(DirectorySection.KEY)
-             .add(DirectoryEntry.include(new WorkspacePath("java/com/google/android/apps/example")))
-             .add(DirectoryEntry.include(new WorkspacePath("javatests/com/google/android/apps/example"))))
-      .build();
-
-    RuleMapBuilder ruleMapBuilder = RuleMapBuilder.builder()
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//java/com/google/android/apps/example:example_debug")
-          .setBuildFile(sourceRoot("java/com/google/android/apps/example/BUILD"))
-          .setKind("android_binary")
-          .addSource(sourceRoot("java/com/google/android/apps/example/MainActivity.java"))
-          .addSource(sourceRoot("java/com/google/android/apps/example/subdir/SubdirHelper.java"))
-          .setAndroidInfo(AndroidRuleIdeInfo.builder()
-                            .setManifestFile(sourceRoot("java/com/google/android/apps/example/AndroidManifest.xml"))
-                            .addResource(genRoot("java/com/google/android/apps/example/res"))
-                            .setGenerateResourceClass(true)
-                            .setResourceJavaPackage("com.google.android.apps.example"))
-          .addDependency("//thirdparty/some/library:library")
-          .setJavaInfo(JavaRuleIdeInfo.builder()
-                         .addJar(LibraryArtifact.builder()
-                                   .setJar(genRoot("java/com/google/android/apps/example/example_debug.jar"))
-                                   .setRuntimeJar(genRoot("java/com/google/android/apps/example/example_debug.jar")))))
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//thirdparty/some/library:library")
-          .setBuildFile(sourceRoot("/thirdparty/some/library/BUILD"))
-          .setKind("java_import")
-          .setJavaInfo(JavaRuleIdeInfo.builder()
-                         .addJar(LibraryArtifact.builder()
-                                   .setJar(genRoot("thirdparty/some/library.jar"))
-                                   .setRuntimeJar(genRoot("thirdparty/some/library.jar"))
-                                   .setSourceJar(genRoot("thirdparty/some/library.srcjar")))));
-
-    BlazeJavaImportResult result = importWorkspace(
-      workspaceRoot,
-      ruleMapBuilder,
-      projectView
-    );
-    errorCollector.assertNoIssues();
-
-    BlazeLibrary library = findLibrary(result.libraries, "library.jar");
-    assertNotNull(library);
-    assertNotNull(library.getLibraryArtifact().sourceJar);
-  }
-
-  /**
-   * Test a project with a java test rule
-   */
-  @Test
-  public void testJavaTestRule() {
-    ProjectView projectView = ProjectView.builder()
-      .put(ListSection.builder(DirectorySection.KEY)
-             .add(DirectoryEntry.include(new WorkspacePath("java/com/google/android/apps/example")))
-             .add(DirectoryEntry.include(new WorkspacePath("javatests/com/google/android/apps/example"))))
-      .put(ListSection.builder(TestSourceSection.KEY).add(new Glob("javatests/*")))
-      .build();
-
-    RuleMapBuilder ruleMapBuilder = RuleMapBuilder.builder()
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//java/com/google/android/apps/example:example_debug")
-          .setBuildFile(sourceRoot("java/com/google/android/apps/example/BUILD"))
-          .setKind("android_binary")
-          .addSource(sourceRoot("java/com/google/android/apps/example/MainActivity.java"))
-          .addSource(sourceRoot("java/com/google/android/apps/example/subdir/SubdirHelper.java"))
-          .setAndroidInfo(AndroidRuleIdeInfo.builder()
-                            .setManifestFile(sourceRoot("java/com/google/android/apps/example/AndroidManifest.xml"))
-                            .addResource(sourceRoot("java/com/google/android/apps/example/res"))
-                            .setGenerateResourceClass(true)
-                            .setResourceJavaPackage("com.google.android.apps.example"))
-          .setJavaInfo(JavaRuleIdeInfo.builder()
-                         .addJar(LibraryArtifact.builder()
-                                   .setJar(genRoot("java/com/google/android/apps/example/example_debug.jar"))
-                                   .setRuntimeJar(genRoot("java/com/google/android/apps/example/example_debug.jar")))))
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//javatests/com/google/android/apps/example:example")
-          .setBuildFile(sourceRoot("javatests/com/google/android/apps/example/BUILD"))
-          .setKind("java_test")
-          .addSource(sourceRoot("javatests/com/google/android/apps/example/ExampleTests.java"))
-          .addDependency("//java/com/google/android/apps/example:example_debug")
-          .setJavaInfo(JavaRuleIdeInfo.builder()
-                         .addJar(LibraryArtifact.builder()
-                                   .setJar(genRoot("javatests/com/google/android/apps/example/example.jar"))
-                                   .setRuntimeJar(genRoot("javatests/com/google/android/apps/example/example.jar")))));
-
-    BlazeJavaImportResult result = importWorkspace(
-      workspaceRoot,
-      ruleMapBuilder,
-      projectView
-    );
-    errorCollector.assertNoIssues();
-
-    assertThat(result.contentEntries).containsExactly(
-      BlazeContentEntry.builder("/root/java/com/google/android/apps/example")
-        .addSource(BlazeSourceDirectory.builder("/root/java/com/google/android/apps/example")
-                     .setPackagePrefix("com.google.android.apps.example")
-                     .build())
-        .build(),
-      BlazeContentEntry.builder("/root/javatests/com/google/android/apps/example")
-        .addSource(BlazeSourceDirectory.builder("/root/javatests/com/google/android/apps/example")
-                     .setPackagePrefix("com.google.android.apps.example")
-                     .setTest(true)
-                     .build())
-        .build()
-    );
-  }
-
-
-  /*
-   * Test that the non-android libraries can be imported.
-   */
-  @Test
-  public void testNormalJavaLibraryPackage() {
-    ProjectView projectView = ProjectView.builder()
-      .put(ListSection.builder(DirectorySection.KEY)
-             .add(DirectoryEntry.include(new WorkspacePath("java/com/google/android/apps/example")))
-             .add(DirectoryEntry.include(new WorkspacePath("java/com/google/library/something"))))
-      .build();
-
-    RuleMapBuilder ruleMapBuilder = RuleMapBuilder.builder()
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//java/com/google/android/apps/example:example_debug")
-          .setBuildFile(sourceRoot("java/com/google/android/apps/example/BUILD"))
-          .setKind("android_binary")
-          .addSource(sourceRoot("java/com/google/android/apps/example/MainActivity.java"))
-          .addSource(sourceRoot("java/com/google/android/apps/example/subdir/SubdirHelper.java"))
-          .setJavaInfo(JavaRuleIdeInfo.builder())
-          .setAndroidInfo(AndroidRuleIdeInfo.builder()
-                            .setManifestFile(sourceRoot("java/com/google/android/apps/example/AndroidManifest.xml"))
-                            .addResource(sourceRoot("java/com/google/android/apps/example/res"))
-                            .setGenerateResourceClass(true)
-                            .setResourceJavaPackage("com.google.android.apps.example"))
-          .addDependency("//java/com/google/library/something:something")
-      )
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//java/com/google/library/something:something")
-          .setBuildFile(sourceRoot("java/com/google/library/something/BUILD"))
-          .setKind("java_library")
-          .addSource(sourceRoot("java/com/google/library/something/SomeJavaFile.java"))
-          .setJavaInfo(JavaRuleIdeInfo.builder()
-                         .addJar(LibraryArtifact.builder()
-                                   .setJar(genRoot("java/com/google/library/something/something.jar"))
-                                   .setRuntimeJar(genRoot("java/com/google/library/something/something.jar")))));
-
-    BlazeJavaImportResult result = importWorkspace(
-      workspaceRoot,
-      ruleMapBuilder,
-      projectView
-    );
-    errorCollector.assertNoIssues();
-
-    assertThat(result.contentEntries).containsExactly(
-      BlazeContentEntry.builder("/root/java/com/google/android/apps/example")
-        .addSource(BlazeSourceDirectory.builder("/root/java/com/google/android/apps/example")
-                     .setPackagePrefix("com.google.android.apps.example")
-                     .build())
-        .build(),
-      BlazeContentEntry.builder("/root/java/com/google/library/something")
-        .addSource(BlazeSourceDirectory.builder("/root/java/com/google/library/something")
-                     .setPackagePrefix("com.google.library.something")
-                     .build())
-        .build()
-    );
-  }
-
-  @Test
-  public void testImportTargetOutputTag() {
-    ProjectView projectView = ProjectView.builder()
-      .put(ListSection.builder(DirectorySection.KEY)
-             .add(DirectoryEntry.include(new WorkspacePath("lib")))
-             .add(DirectoryEntry.include(new WorkspacePath("lib2"))))
-      .build();
-
-    RuleMapBuilder response = RuleMapBuilder.builder()
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//lib:lib")
-          .setBuildFile(sourceRoot("lib/BUILD"))
-          .setKind("java_library")
-          .addDependency("//lib2:lib2")
-          .setJavaInfo(JavaRuleIdeInfo.builder()
-                         .addJar(LibraryArtifact.builder()
-                                   .setJar(genRoot("lib/lib.jar"))
-                                   .setRuntimeJar(genRoot("lib/lib.jar")))))
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//lib2:lib2")
-          .setBuildFile(sourceRoot("lib2/BUILD"))
-          .setKind("java_library")
-          .addTag("intellij-import-target-output")
-          .setJavaInfo(JavaRuleIdeInfo.builder()
-                         .addJar(LibraryArtifact.builder()
-                                   .setJar(genRoot("lib2/lib2.jar"))
-                                   .setRuntimeJar(genRoot("lib2/lib2.jar")))));
-
-    BlazeJavaImportResult result = importWorkspace(
-      workspaceRoot,
-      response,
-      projectView
-    );
-    errorCollector.assertNoIssues();
-    assertEquals(1, result.libraries.size());
-  }
-
-  @Test
-  public void testImportAsLibraryTagLegacy() {
-    ProjectView projectView = ProjectView.builder()
-      .put(ListSection.builder(DirectorySection.KEY)
-             .add(DirectoryEntry.include(new WorkspacePath("lib")))
-             .add(DirectoryEntry.include(new WorkspacePath("lib2"))))
-      .build();
-
-    RuleMapBuilder response = RuleMapBuilder.builder()
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//lib:lib")
-          .setBuildFile(sourceRoot("lib/BUILD"))
-          .setKind("java_library")
-          .addDependency("//lib2:lib2")
-          .setJavaInfo(JavaRuleIdeInfo.builder()
-                         .addJar(LibraryArtifact.builder()
-                                   .setJar(genRoot("lib/lib.jar"))
-                                   .setRuntimeJar(genRoot("lib/lib.jar")))))
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//lib2:lib2")
-          .setBuildFile(sourceRoot("lib2/BUILD"))
-          .setKind("java_library")
-          .addTag("aswb-import-as-library")
-          .setJavaInfo(JavaRuleIdeInfo.builder()
-                         .addJar(LibraryArtifact.builder()
-                                   .setJar(genRoot("lib2/lib2.jar"))
-                                   .setRuntimeJar(genRoot("lib2/lib2.jar")))));
-
-    BlazeJavaImportResult result = importWorkspace(
-      workspaceRoot,
-      response,
-      projectView
-    );
-    errorCollector.assertNoIssues();
-
-    assertEquals(1, result.libraries.size());
-  }
-
-  @Test
-  public void testMultipleImportOfJarsGetMerged() {
-    ProjectView projectView = ProjectView.builder()
-      .put(ListSection.builder(DirectorySection.KEY)
-             .add(DirectoryEntry.include(new WorkspacePath("lib"))))
-      .build();
-
-    RuleMapBuilder response = RuleMapBuilder.builder()
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//lib:libsource")
-          .setBuildFile(sourceRoot("lib/BUILD"))
-          .setKind("java_library")
-          .setJavaInfo(JavaRuleIdeInfo.builder())
-          .addDependency("//lib:lib0")
-          .addDependency("//lib:lib1"))
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//lib:lib0")
-          .setBuildFile(sourceRoot("lib/BUILD"))
-          .setKind("java_import")
-          .setJavaInfo(JavaRuleIdeInfo.builder()
-                         .addJar(LibraryArtifact.builder()
-                                   .setJar(sourceRoot("lib/lib.jar"))
-                                   .setRuntimeJar(sourceRoot("lib/lib.jar")))))
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//lib:lib1")
-          .setBuildFile(sourceRoot("lib/BUILD"))
-          .setKind("java_import")
-          .setJavaInfo(JavaRuleIdeInfo.builder()
-                         .addJar(LibraryArtifact.builder()
-                                   .setJar(sourceRoot("lib/lib.jar"))
-                                   .setRuntimeJar(sourceRoot("lib/lib.jar")))));
-
-    BlazeJavaImportResult result = importWorkspace(
-      workspaceRoot,
-      response,
-      projectView
-    );
-    errorCollector.assertNoIssues();
-    assertEquals(1, result.libraries.size()); // The libraries were merged
-  }
-
-  @Test
-  public void testRuleWithOnlyGeneratedSourcesIsAddedAsLibrary() {
-    ProjectView projectView = ProjectView.builder()
-      .put(ListSection.builder(DirectorySection.KEY)
-             .add(DirectoryEntry.include(new WorkspacePath("import"))))
-      .build();
-
-    RuleMapBuilder response = RuleMapBuilder.builder()
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//import:lib")
-          .setBuildFile(sourceRoot("import/BUILD"))
-          .setKind("android_library")
-          .setJavaInfo(JavaRuleIdeInfo.builder())
-          .addDependency("//import:import")
-          .addDependency("//import:import_android"))
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//import:import")
-          .setBuildFile(sourceRoot("import/BUILD"))
-          .addSource(genRoot("import/GenSource.java"))
-          .setKind("java_library")
-          .setJavaInfo(JavaRuleIdeInfo.builder()
-                         .addJar(LibraryArtifact.builder()
-                                   .setJar(genRoot("import/import.jar"))
-                                   .setRuntimeJar(genRoot("import/import.jar")))))
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//import:import_android")
-          .setBuildFile(sourceRoot("import/BUILD"))
-          .addSource(genRoot("import/GenSource.java"))
-          .setKind("android_library")
-          .setJavaInfo(JavaRuleIdeInfo.builder()
-                         .addJar(LibraryArtifact.builder()
-                                   .setJar(genRoot("import/import_android.jar"))
-                                   .setRuntimeJar(genRoot("import/import_android.jar")))));
-
-    BlazeJavaImportResult result = importWorkspace(
-      workspaceRoot,
-      response,
-      projectView
-    );
-    errorCollector.assertNoIssues();
-
-    assertThat(findLibrary(result.libraries, "import.jar")).isNotNull();
-    assertThat(findLibrary(result.libraries, "import_android.jar")).isNotNull();
-  }
-
-  @Test
-  public void testImportTargetOutput() {
-    ProjectView projectView = ProjectView.builder()
-      .put(ListSection.builder(DirectorySection.KEY)
-             .add(DirectoryEntry.include(new WorkspacePath("import"))))
-      .put(ListSection.builder(ImportTargetOutputSection.KEY)
-             .add(new Label("//import:import")))
-      .build();
-
-    RuleMapBuilder response = RuleMapBuilder.builder()
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//import:lib")
-          .setBuildFile(sourceRoot("import/BUILD"))
-          .setKind("java_library")
-          .setJavaInfo(JavaRuleIdeInfo.builder())
-          .addDependency("//import:import"))
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//import:import")
-          .setBuildFile(sourceRoot("import/BUILD"))
-          .setKind("java_import")
-          .setJavaInfo(JavaRuleIdeInfo.builder()
-                         .addJar(LibraryArtifact.builder()
-                                   .setJar(genRoot("import/import.jar"))
-                                   .setRuntimeJar(genRoot("import/import.jar")))));
-
-    BlazeJavaImportResult result = importWorkspace(
-      workspaceRoot,
-      response,
-      projectView
-    );
-    errorCollector.assertNoIssues();
-
-    assertThat(result.libraries).isNotEmpty();
-  }
-
-  private RuleMapBuilder ruleMapForJdepsSuite() {
-    return RuleMapBuilder.builder()
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//java/com/google/android/apps/example:example_debug")
-          .setBuildFile(sourceRoot("java/com/google/android/apps/example/BUILD"))
-          .addSource(sourceRoot("java/com/google/android/apps/example/Test.java"))
-          .setKind("java_library")
-          .setJavaInfo(JavaRuleIdeInfo.builder())
-          .addDependency("//thirdparty/a:a"))
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//thirdparty/a:a")
-          .setKind("java_library")
-          .setBuildFile(sourceRoot("third_party/a/BUILD"))
-          .addDependency("//thirdparty/b:b")
-          .setJavaInfo(JavaRuleIdeInfo.builder()
-                         .addJar(LibraryArtifact.builder()
-                                   .setJar(genRoot("thirdparty/a.jar"))
-                                   .setRuntimeJar(genRoot("thirdparty/a.jar"))
-                                   .setSourceJar(genRoot("thirdparty/a.srcjar")))))
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//thirdparty/b:b")
-          .setKind("java_library")
-          .setBuildFile(sourceRoot("third_party/b/BUILD"))
-          .addDependency("//thirdparty/c:c")
-          .setJavaInfo(JavaRuleIdeInfo.builder()
-                         .addJar(LibraryArtifact.builder()
-                                   .setJar(genRoot("thirdparty/b.jar"))
-                                   .setRuntimeJar(genRoot("thirdparty/b.jar"))
-                                   .setSourceJar(genRoot("thirdparty/b.srcjar")))))
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//thirdparty/c:c")
-          .setKind("java_library")
-          .setBuildFile(sourceRoot("third_party/c/BUILD"))
-          .setJavaInfo(JavaRuleIdeInfo.builder()
-                         .addJar(LibraryArtifact.builder()
-                                   .setJar(genRoot("thirdparty/c.jar"))
-                                   .setRuntimeJar(genRoot("thirdparty/c.jar"))
-                                   .setSourceJar(genRoot("thirdparty/c.srcjar")))));
-  }
-
-  @Test
-  public void testLibraryDependenciesWithJdepsSet() {
-    ProjectView projectView = ProjectView.builder()
-      .put(ListSection.builder(DirectorySection.KEY)
-             .add(DirectoryEntry.include(new WorkspacePath("java/com/google/android/apps/example")))
-             .add(DirectoryEntry.include(new WorkspacePath("javatests/com/google/android/apps/example"))))
-      .build();
-    RuleMapBuilder ruleMapBuilder = ruleMapForJdepsSuite();
-    jdepsMap.put(new Label("//java/com/google/android/apps/example:example_debug"), Lists.newArrayList(
-      jdepsPath("thirdparty/a.jar"),
-      jdepsPath("thirdparty/c.jar"))
-    );
-
-    BlazeJavaImportResult result = importWorkspace(
-      workspaceRoot,
-      ruleMapBuilder,
-      projectView
-    );
-    assertThat(result.libraries.values().stream().map(BlazeJavaWorkspaceImporterTest::libraryFileName).collect(Collectors.toList()))
-      .containsExactly("a.jar", "c.jar");
-  }
-
-  @Test
-  public void testLibraryDependenciesWithJdepsReportingNothingShouldStillIncludeDirectDepsIfInWorkingSet() {
-    ProjectView projectView = ProjectView.builder()
-      .put(ListSection.builder(DirectorySection.KEY)
-             .add(DirectoryEntry.include(new WorkspacePath("java/com/google/android/apps/example")))
-             .add(DirectoryEntry.include(new WorkspacePath("javatests/com/google/android/apps/example"))))
-      .build();
-    RuleMapBuilder ruleMapBuilder = ruleMapForJdepsSuite();
-    workingSet = new JavaWorkingSet(workspaceRoot, new WorkingSet(
-      ImmutableList.of(new WorkspacePath("java/com/google/android/apps/example/Test.java")),
-      ImmutableList.of(),
-      ImmutableList.of()
-    ));
-
-    BlazeJavaImportResult result = importWorkspace(
-      workspaceRoot,
-      ruleMapBuilder,
-      projectView
-    );
-    assertThat(result.libraries.values().stream().map(BlazeJavaWorkspaceImporterTest::libraryFileName).collect(Collectors.toList()))
-      .containsExactly("a.jar");
-  }
-
-  @Test
-  public void testLibraryDependenciesWithJdepsReportingNothingShouldNotIncludeDirectDepsIfNotInWorkingSet() {
-    ProjectView projectView = ProjectView.builder()
-      .put(ListSection.builder(DirectorySection.KEY)
-             .add(DirectoryEntry.include(new WorkspacePath("java/com/google/android/apps/example")))
-             .add(DirectoryEntry.include(new WorkspacePath("javatests/com/google/android/apps/example"))))
-      .build();
-    RuleMapBuilder ruleMapBuilder = ruleMapForJdepsSuite();
-    workingSet = new JavaWorkingSet(workspaceRoot, new WorkingSet(
-      ImmutableList.of(),
-      ImmutableList.of(),
-      ImmutableList.of()
-    ));
-
-    BlazeJavaImportResult result = importWorkspace(
-      workspaceRoot,
-      ruleMapBuilder,
-      projectView
-    );
-    assertThat(result.libraries.values().stream().map(BlazeJavaWorkspaceImporterTest::libraryFileName).collect(Collectors.toList()))
-      .isEmpty();
-  }
-
-  /*
-   * Test the exclude_target section
-   */
-  @Test
-  public void testExcludeTarget() {
-    ProjectView projectView = ProjectView.builder()
-      .put(ListSection.builder(DirectorySection.KEY))
-      .put(ListSection.builder(ExcludeTargetSection.KEY)
-             .add(new Label("//java/com/google/android/apps/example:example_debug")))
-      .build();
-
-    RuleMapBuilder ruleMapBuilder = RuleMapBuilder.builder()
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//java/com/google/android/apps/example:example_debug")
-          .setBuildFile(sourceRoot("java/com/google/android/apps/example/BUILD"))
-          .setKind("android_binary")
-          .addSource(sourceRoot("java/com/google/android/apps/example/MainActivity.java"))
-          .addSource(sourceRoot("java/com/google/android/apps/example/subdir/SubdirHelper.java"))
-          .setAndroidInfo(AndroidRuleIdeInfo.builder()
-                            .setManifestFile(sourceRoot("java/com/google/android/apps/example/AndroidManifest.xml"))
-                            .addResource(sourceRoot("java/com/google/android/apps/example/res"))
-                            .setGenerateResourceClass(true)
-                            .setResourceJavaPackage("com.google.android.apps.example"))
-          .addDependency("//java/com/google/library/something:something")
-      );
-
-    BlazeJavaImportResult result = importWorkspace(
-      workspaceRoot,
-      ruleMapBuilder,
-      projectView
-    );
-    errorCollector.assertNoIssues();
-
-    assertThat(result.libraries).isEmpty();
-  }
-
-  /**
-   * Test legacy proto_library jars, complete with overrides and everything.
-   */
-  @Test
-  public void testLegacyProtoLibraryInfo() {
-    ProjectView projectView = ProjectView.builder()
-      .put(ListSection.builder(DirectorySection.KEY)
-             .add(DirectoryEntry.include(new WorkspacePath("java/com/google/example"))))
-      .build();
-
-    RuleMapBuilder ruleMapBuilder = RuleMapBuilder.builder()
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//java/com/google/example:liba")
-          .setBuildFile(sourceRoot("java/com/google/example/BUILD"))
-          .setKind("java_library")
-          .setJavaInfo(JavaRuleIdeInfo.builder())
-          .addDependency("//thirdparty/proto/a:a"))
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//java/com/google/example:libb")
-          .setBuildFile(sourceRoot("java/com/google/example/BUILD"))
-          .setKind("java_library")
-          .setJavaInfo(JavaRuleIdeInfo.builder())
-          .addDependency("//thirdparty/proto/b:b"))
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//thirdparty/proto/a:a")
-          .setBuildFile(sourceRoot("/thirdparty/a/BUILD"))
-          .setKind("proto_library")
-          .setProtoLibraryLegacyInfo(ProtoLibraryLegacyInfo.builder(ProtoLibraryLegacyInfo.ApiFlavor.IMMUTABLE)
-                                       .addJarV1(LibraryArtifact.builder().setJar(genRoot("thirdparty/proto/a/liba-1-ijar.jar")))
-                                       .addJarImmutable(LibraryArtifact.builder().setJar(genRoot("thirdparty/proto/a/liba-ijar.jar"))))
-          .addDependency("//thirdparty/proto/b:b")
-          .addDependency("//thirdparty/proto/c:c"))
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//thirdparty/proto/b:b")
-          .setBuildFile(sourceRoot("/thirdparty/b/BUILD"))
-          .setKind("proto_library")
-          .setProtoLibraryLegacyInfo(ProtoLibraryLegacyInfo.builder(ProtoLibraryLegacyInfo.ApiFlavor.VERSION_1)
-                                       .addJarV1(LibraryArtifact.builder().setJar(genRoot("thirdparty/proto/b/libb-ijar.jar")))
-                                       .addJarImmutable(LibraryArtifact.builder().setJar(genRoot("thirdparty/proto/b/libb-2-ijar.jar"))))
-          .addDependency("//thirdparty/proto/d:d"))
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//thirdparty/proto/c:c")
-          .setBuildFile(sourceRoot("/thirdparty/c/BUILD"))
-          .setKind("proto_library")
-          .setProtoLibraryLegacyInfo(ProtoLibraryLegacyInfo.builder(ProtoLibraryLegacyInfo.ApiFlavor.IMMUTABLE)
-                                       .addJarV1(LibraryArtifact.builder().setJar(genRoot("thirdparty/proto/c/libc-1-ijar.jar")))
-                                       .addJarImmutable(LibraryArtifact.builder().setJar(genRoot("thirdparty/proto/c/libc-ijar.jar"))))
-          .addDependency("//thirdparty/proto/d:d"))
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//thirdparty/proto/d:d")
-          .setBuildFile(sourceRoot("/thirdparty/d/BUILD"))
-          .setKind("proto_library")
-          .setProtoLibraryLegacyInfo(ProtoLibraryLegacyInfo.builder(ProtoLibraryLegacyInfo.ApiFlavor.VERSION_1)
-                                       .addJarV1(LibraryArtifact.builder().setJar(genRoot("thirdparty/proto/d/libd-ijar.jar")))
-                                       .addJarImmutable(LibraryArtifact.builder().setJar(genRoot("thirdparty/proto/d/libd-2-ijar.jar")))));
-
-    workingSet = new JavaWorkingSet(workspaceRoot, new WorkingSet(ImmutableList.of(), ImmutableList.of(), ImmutableList.of()));
-
-    // First test - make sure that jdeps is working
-    jdepsMap.put(new Label("//java/com/google/example:liba"), Lists.newArrayList(jdepsPath("thirdparty/proto/a/liba-ijar.jar")));
-    BlazeJavaImportResult result = importWorkspace(
-      workspaceRoot,
-      ruleMapBuilder,
-      projectView
-    );
-    errorCollector.assertNoIssues();
-    assertThat(result.libraries).hasSize(1);
-    assertThat(findLibrary(result.libraries, "liba-ijar.jar")).isNotNull();
-
-
-    // Second test - put everything in the working set, which should expand to the full transitive closure
-    workingSet = new JavaWorkingSet(workspaceRoot, new WorkingSet(
-      ImmutableList.of(new WorkspacePath("java/com/google/example/BUILD")),
-      ImmutableList.of(),
-      ImmutableList.of()
-    ));
-
-    result = importWorkspace(
-      workspaceRoot,
-      ruleMapBuilder,
-      projectView
-    );
-    errorCollector.assertNoIssues();
-
-    assertThat(result.libraries).hasSize(6);
-    assertThat(findLibrary(result.libraries, "liba-ijar.jar")).isNotNull();
-    assertThat(findLibrary(result.libraries, "libb-ijar.jar")).isNotNull();
-    assertThat(findLibrary(result.libraries, "libb-2-ijar.jar")).isNotNull();
-    assertThat(findLibrary(result.libraries, "libc-ijar.jar")).isNotNull();
-    assertThat(findLibrary(result.libraries, "libd-ijar.jar")).isNotNull();
-    assertThat(findLibrary(result.libraries, "libd-2-ijar.jar")).isNotNull();
-  }
-
-  /*
- * Test that the non-android libraries can be imported.
- */
-  @Test
-  public void testImporterWorksWithWorkspaceRootDirectoryIncluded() {
-    ProjectView projectView = ProjectView.builder()
-      .put(ListSection.builder(DirectorySection.KEY)
-        .add(DirectoryEntry.include(new WorkspacePath(""))))
-      .build();
-
-    RuleMapBuilder ruleMapBuilder = RuleMapBuilder.builder()
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//java/com/google/android/apps/example:example_debug")
-          .setBuildFile(sourceRoot("java/com/google/android/apps/example/BUILD"))
-          .setKind("android_binary")
-          .addSource(sourceRoot("java/com/google/android/apps/example/MainActivity.java"))
-          .addSource(sourceRoot("java/com/google/android/apps/example/subdir/SubdirHelper.java"))
-          .setJavaInfo(JavaRuleIdeInfo.builder())
-          .setAndroidInfo(AndroidRuleIdeInfo.builder()
-            .setManifestFile(sourceRoot("java/com/google/android/apps/example/AndroidManifest.xml"))
-            .addResource(sourceRoot("java/com/google/android/apps/example/res"))
-            .setGenerateResourceClass(true)
-            .setResourceJavaPackage("com.google.android.apps.example"))
-          .addDependency("//java/com/google/library/something:something")
-      )
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//java/com/google/library/something:something")
-          .setBuildFile(sourceRoot("java/com/google/library/something/BUILD"))
-          .setKind("java_library")
-          .addSource(sourceRoot("java/com/google/library/something/SomeJavaFile.java"))
-          .setJavaInfo(JavaRuleIdeInfo.builder()
-            .addJar(LibraryArtifact.builder()
-              .setJar(genRoot("java/com/google/library/something/something.jar"))
-              .setRuntimeJar(genRoot("java/com/google/library/something/something.jar")))));
-
-    BlazeJavaImportResult result = importWorkspace(
-      workspaceRoot,
-      ruleMapBuilder,
-      projectView
-    );
-    errorCollector.assertNoIssues();
-
-    assertThat(result.contentEntries).containsExactly(
-      BlazeContentEntry.builder("/root")
-        .addSource(BlazeSourceDirectory.builder("/root")
-          .build())
-        .addSource(BlazeSourceDirectory.builder("/root/java")
-                     .build())
-        .build()
-    );
-  }
-
-  @Test
-  public void testLanguageLevelIsReadFromToolchain() {
-    ProjectView projectView = ProjectView.builder()
-      .build();
-
-    RuleMapBuilder ruleMapBuilder = RuleMapBuilder.builder()
-      .addRule(
-        RuleIdeInfo.builder()
-          .setLabel("//java/com/google:toolchain")
-          .setBuildFile(sourceRoot("java/com/google/BUILD"))
-          .setKind("java_toolchain")
-          .setJavaToolchainIdeInfo(JavaToolchainIdeInfo.builder()
-                                     .setSourceVersion("8")
-                                     .setTargetVersion("8")));
-
-    BlazeJavaImportResult result = importWorkspace(
-      workspaceRoot,
-      ruleMapBuilder,
-      projectView
-    );
-    assertThat(result.sourceVersion).isEqualTo("8");
-  }
-
-  /* Utility methods */
-
-  private static String libraryFileName(BlazeLibrary library) {
-    return new File(library.getLibraryArtifact().jar.getRelativePath()).getName();
-  }
-
-  @Nullable
-  private static BlazeLibrary findLibrary(Map<LibraryKey, BlazeLibrary> libraries, String libraryName) {
-    for (BlazeLibrary library : libraries.values()) {
-      if (library.getLibraryArtifact().jar.getFile().getPath().endsWith(libraryName)) {
-        return library;
-      }
-    }
-    return null;
-  }
-
-  private ArtifactLocation sourceRoot(String relativePath) {
-    return ArtifactLocation.builder()
-      .setRootPath(FAKE_WORKSPACE_ROOT)
-      .setRelativePath(relativePath)
-      .setIsSource(true)
-      .build();
-  }
-
-  private static ArtifactLocation genRoot(String relativePath) {
-    return ArtifactLocation.builder()
-      .setRootPath(FAKE_GEN_ROOT)
-      .setRootExecutionPathFragment(FAKE_GEN_ROOT_EXECUTION_PATH_FRAGMENT)
-      .setRelativePath(relativePath)
-      .setIsSource(false)
-      .build();
-  }
-
-  private static String jdepsPath(String relativePath) {
-    return FAKE_GEN_ROOT_EXECUTION_PATH_FRAGMENT + "/" + relativePath;
-  }
-}
diff --git a/blaze-java/tests/unittests/com/google/idea/blaze/java/sync/source/SourceDirectoryCalculatorTest.java b/blaze-java/tests/unittests/com/google/idea/blaze/java/sync/source/SourceDirectoryCalculatorTest.java
deleted file mode 100644
index 2442f33..0000000
--- a/blaze-java/tests/unittests/com/google/idea/blaze/java/sync/source/SourceDirectoryCalculatorTest.java
+++ /dev/null
@@ -1,1102 +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.source;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Lists;
-import com.google.common.util.concurrent.MoreExecutors;
-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.experiments.ExperimentService;
-import com.google.idea.blaze.base.experiments.MockExperimentService;
-import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
-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;
-import com.google.idea.blaze.base.model.primitives.Label;
-import com.google.idea.blaze.base.model.primitives.WorkspacePath;
-import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
-import com.google.idea.blaze.base.prefetch.MockPrefetchService;
-import com.google.idea.blaze.base.prefetch.PrefetchService;
-import com.google.idea.blaze.base.projectview.ProjectViewSet;
-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.projectview.SourceTestConfig;
-import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
-import com.google.idea.blaze.base.sync.workspace.BlazeRoots;
-import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolverImpl;
-import com.google.idea.blaze.java.sync.model.BlazeContentEntry;
-import com.google.idea.blaze.java.sync.model.BlazeSourceDirectory;
-import com.google.repackaged.devtools.build.lib.ideinfo.androidstudio.PackageManifestOuterClass;
-import com.google.repackaged.devtools.build.lib.ideinfo.androidstudio.PackageManifestOuterClass.JavaSourcePackage;
-import com.google.repackaged.devtools.build.lib.ideinfo.androidstudio.PackageManifestOuterClass.PackageManifest;
-import com.intellij.util.containers.HashMap;
-import org.jetbrains.annotations.NotNull;
-import org.junit.Test;
-
-import java.io.*;
-import java.nio.file.Paths;
-import java.util.List;
-import java.util.Map;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.fail;
-
-
-/**
- * Test cases for {@link SourceDirectoryCalculator}.
- */
-public class SourceDirectoryCalculatorTest extends BlazeTestCase {
-
-  private static final ImmutableMap<Label, ArtifactLocation> NO_MANIFESTS = ImmutableMap.of();
-  private static final Label LABEL = new Label("//fake:label");
-
-  private MockInputStreamProvider mockInputStreamProvider;
-  private SourceDirectoryCalculator sourceDirectoryCalculator;
-
-  private BlazeContext context = new BlazeContext();
-  private ErrorCollector issues = new ErrorCollector();
-  private MockExperimentService experimentService;
-
-  private WorkspaceRoot workspaceRoot = new WorkspaceRoot(new File("/root"));
-  private ArtifactLocationDecoder decoder = new ArtifactLocationDecoder(
-    new BlazeRoots(
-      new File("/"),
-      Lists.newArrayList(new File("/usr/local/code")),
-      new ExecutionRootPath("out/crosstool/bin"),
-      new ExecutionRootPath("out/crosstool/gen")
-    ),
-    null
-  );
-
-  final static class TestSourceImportConfig extends SourceTestConfig {
-    final boolean isTest;
-
-    public TestSourceImportConfig(boolean isTest) {
-      super(ProjectViewSet.builder().build());
-      this.isTest = isTest;
-    }
-
-    @Override
-    public boolean isTestSource(String relativePath) {
-      return isTest;
-    }
-  }
-
-  @Override
-  protected void initTest(
-    @NotNull Container applicationServices,
-    @NotNull Container projectServices) {
-    super.initTest(applicationServices, projectServices);
-
-    mockInputStreamProvider = new MockInputStreamProvider();
-    applicationServices.register(
-      InputStreamProvider.class,
-      mockInputStreamProvider
-    );
-    applicationServices.register(JavaSourcePackageReader.class, new JavaSourcePackageReader());
-    applicationServices.register(PackageManifestReader.class, new PackageManifestReader());
-    applicationServices.register(FileAttributeProvider.class, new MockFileAttributeProvider());
-
-    context.addOutputSink(IssueOutput.class, issues);
-    sourceDirectoryCalculator = new SourceDirectoryCalculator();
-
-    BlazeExecutor blazeExecutor = new MockBlazeExecutor();
-    applicationServices.register(BlazeExecutor.class, blazeExecutor);
-
-    experimentService = new MockExperimentService();
-    applicationServices.register(ExperimentService.class, experimentService);
-
-    applicationServices.register(PrefetchService.class, new MockPrefetchService());
-  }
-
-  @Test
-  public void testWorkspacePathIsAddedWithoutSources() throws Exception {
-    List<SourceArtifact> sourceArtifacts = ImmutableList.of();
-    ImmutableList<BlazeContentEntry> result = sourceDirectoryCalculator.calculateContentEntries(
-      context,
-      workspaceRoot,
-      new TestSourceImportConfig(false /* isTest */),
-      decoder,
-      ImmutableList.of(new WorkspacePath("java/com/google/app")),
-      sourceArtifacts,
-      NO_MANIFESTS
-    );
-    issues.assertNoIssues();
-    assertThat(result).containsExactly(
-      BlazeContentEntry.builder("/root/java/com/google/app")
-        .addSource(BlazeSourceDirectory.builder("/root/java/com/google/app")
-                     .setPackagePrefix("com.google.app")
-                     .build())
-        .build()
-    );
-  }
-
-  @Test
-  public void testCalculatesPackageForSimpleCase() throws Exception {
-    mockInputStreamProvider
-      .addFile("/root/java/com/google/Bla.java",
-               "package com.google;\n public class Bla {}");
-    List<SourceArtifact> sourceArtifacts = ImmutableList.of(
-      SourceArtifact.builder(LABEL)
-        .setArtifactLocation(ArtifactLocation.builder()
-                               .setRelativePath("java/com/google/Bla.java")
-                               .setRootPath("/root")
-                               .setIsSource(true))
-        .build());
-    ImmutableList<BlazeContentEntry> result = sourceDirectoryCalculator.calculateContentEntries(
-      context,
-      workspaceRoot,
-      new TestSourceImportConfig(false),
-      decoder,
-      ImmutableList.of(new WorkspacePath("java/com/google")),
-      sourceArtifacts,
-      NO_MANIFESTS
-    );
-    assertThat(result).containsExactly(
-      BlazeContentEntry.builder("/root/java/com/google")
-      .addSource(BlazeSourceDirectory.builder("/root/java/com/google")
-                 .setPackagePrefix("com.google")
-                 .build())
-      .build()
-    );
-    issues.assertNoIssues();
-  }
-
-  @Test
-  public void testSourcesToSourceDirectories_testReturnsTest() throws Exception {
-    mockInputStreamProvider
-      .addFile("/root/java/com/google/Bla.java",
-               "package com.google;\n public class Bla {}");
-    List<SourceArtifact> sourceArtifacts = ImmutableList.of(
-      SourceArtifact.builder(LABEL)
-        .setArtifactLocation(ArtifactLocation.builder()
-                               .setRelativePath("java/com/google/Bla.java")
-                               .setRootPath("/root")
-                               .setIsSource(true))
-        .build());
-    ImmutableList<BlazeContentEntry> result = sourceDirectoryCalculator.calculateContentEntries(
-      context,
-      workspaceRoot,
-      new TestSourceImportConfig(true),
-      decoder,
-      ImmutableList.of(new WorkspacePath("java/com/google")),
-      sourceArtifacts,
-      NO_MANIFESTS
-    );
-    issues.assertNoIssues();
-    assertThat(result).containsExactly(
-      BlazeContentEntry.builder("/root/java/com/google")
-      .addSource(BlazeSourceDirectory.builder("/root/java/com/google")
-                 .setPackagePrefix("com.google")
-                 .setTest(true)
-                 .build())
-      .build()
-    );
-  }
-
-  @Test
-  public void testSourcesToSourceDirectories_multipleMatchingPackagesAreMerged() throws Exception {
-    mockInputStreamProvider
-      .addFile("/root/java/com/google/Bla.java",
-               "package com.google;\n public class Bla {}")
-      .addFile("/root/java/com/google/subpackage/Bla.java",
-               "package com.google.subpackage;\n public class Bla {}");
-    List<SourceArtifact> sourceArtifacts = ImmutableList.of(
-      SourceArtifact.builder(LABEL)
-        .setArtifactLocation(ArtifactLocation.builder()
-                               .setRelativePath("java/com/google/Bla.java")
-                               .setRootPath("/root")
-                               .setIsSource(true))
-        .build(),
-      SourceArtifact.builder(LABEL)
-        .setArtifactLocation(ArtifactLocation.builder()
-                               .setRelativePath("java/com/google/subpackage/Bla.java")
-                               .setRootPath("/root")
-                               .setIsSource(true))
-        .build()
-    );
-    ImmutableList<BlazeContentEntry> result = sourceDirectoryCalculator.calculateContentEntries(
-      context,
-      workspaceRoot,
-      new TestSourceImportConfig(false),
-      decoder,
-      ImmutableList.of(new WorkspacePath("java/com/google")),
-      sourceArtifacts,
-      NO_MANIFESTS
-    );
-    issues.assertNoIssues();
-    assertThat(result).containsExactly(
-      BlazeContentEntry.builder("/root/java/com/google")
-      .addSource(BlazeSourceDirectory.builder("/root/java/com/google")
-                 .setPackagePrefix("com.google")
-                 .build())
-      .build()
-    );
-  }
-
-  @Test
-  public void testMultipleDirectoriesAreMergedWithDirectoryRootAsWorkspaceRoot() throws Exception {
-    mockInputStreamProvider
-      .addFile("/root/java/com/google/idea/blaze/plugin/run/Run.java",
-               "package com.google.idea.blaze.plugin.run;\n public class run {}")
-      .addFile("/root/java/com/google/idea/blaze/plugin/sync/Sync.java",
-               "package com.google.idea.blaze.plugin.sync;\n public class Sync {}")
-      .addFile("/root/java/com/google/idea/blaze/plugin/Plugin.java",
-               "package com.google.idea.blaze.plugin;\n public class Plugin {}")
-    ;
-    List<SourceArtifact> sourceArtifacts = ImmutableList.of(
-      SourceArtifact.builder(LABEL)
-        .setArtifactLocation(ArtifactLocation.builder()
-                               .setRelativePath("java/com/google/idea/blaze/plugin/run/Run.java")
-                               .setRootPath("/root")
-                               .setIsSource(true))
-        .build(),
-      SourceArtifact.builder(LABEL)
-        .setArtifactLocation(ArtifactLocation.builder()
-                               .setRelativePath("java/com/google/idea/blaze/plugin/sync/Sync.java")
-                               .setRootPath("/root")
-                               .setIsSource(true))
-        .build(),
-      SourceArtifact.builder(LABEL)
-        .setArtifactLocation(ArtifactLocation.builder()
-                               .setRelativePath("java/com/google/idea/blaze/plugin/Plugin.java")
-                               .setRootPath("/root")
-                               .setIsSource(true))
-        .build()
-    );
-    ImmutableList<BlazeContentEntry> result = sourceDirectoryCalculator.calculateContentEntries(
-      context,
-      workspaceRoot,
-      new TestSourceImportConfig(false),
-      decoder,
-      ImmutableList.of(new WorkspacePath("")),
-      sourceArtifacts,
-      NO_MANIFESTS
-    );
-    issues.assertNoIssues();
-    assertThat(result).containsExactly(
-      BlazeContentEntry.builder("/root")
-        .addSource(BlazeSourceDirectory.builder("/root")
-                     .setPackagePrefix("")
-                     .build())
-        .addSource(BlazeSourceDirectory.builder("/root/java")
-                     .setPackagePrefix("")
-                     .build())
-        .build()
-    );
-  }
-
-  @Test
-  public void testIncorrectPackageInMiddleOfTreeCausesMergePointHigherUp() throws Exception {
-    mockInputStreamProvider
-      .addFile("/root/java/com/google/idea/blaze/plugin/run/Run.java",
-               "package com.google.idea.blaze.plugin.run;\n public class run {}")
-      .addFile("/root/java/com/google/idea/blaze/plugin/sync/Sync.java",
-               "package com.google.idea.blaze.plugin.sync;\n public class Sync {}")
-      .addFile("/root/java/com/google/idea/blaze/Incorrect.java",
-               "package com.google.idea.blaze.incorrect;\n public class Incorrect {}")
-    ;
-    List<SourceArtifact> sourceArtifacts = ImmutableList.of(
-      SourceArtifact.builder(LABEL)
-        .setArtifactLocation(ArtifactLocation.builder()
-                               .setRelativePath("java/com/google/idea/blaze/plugin/run/Run.java")
-                               .setRootPath("/root")
-                               .setIsSource(true))
-        .build(),
-      SourceArtifact.builder(LABEL)
-        .setArtifactLocation(ArtifactLocation.builder()
-                               .setRelativePath("java/com/google/idea/blaze/plugin/sync/Sync.java")
-                               .setRootPath("/root")
-                               .setIsSource(true))
-        .build(),
-      SourceArtifact.builder(LABEL)
-        .setArtifactLocation(ArtifactLocation.builder()
-                               .setRelativePath("java/com/google/idea/blaze/Incorrect.java")
-                               .setRootPath("/root")
-                               .setIsSource(true))
-        .build()
-    );
-    ImmutableList<BlazeContentEntry> result = sourceDirectoryCalculator.calculateContentEntries(
-      context,
-      workspaceRoot,
-      new TestSourceImportConfig(false),
-      decoder,
-      ImmutableList.of(new WorkspacePath("")),
-      sourceArtifacts,
-      NO_MANIFESTS
-    );
-    issues.assertNoIssues();
-    assertThat(result).containsExactly(
-      BlazeContentEntry.builder("/root")
-        .addSource(BlazeSourceDirectory.builder("/root")
-                     .setPackagePrefix("")
-                     .build())
-        .addSource(BlazeSourceDirectory.builder("/root/java/com/google/idea/blaze")
-                     .setPackagePrefix("com.google.idea.blaze.incorrect")
-                     .build())
-        .addSource(BlazeSourceDirectory.builder("/root/java/com/google/idea/blaze/plugin")
-                     .setPackagePrefix("com.google.idea.blaze.plugin")
-                     .build())
-        .build()
-    );
-  }
-
-  @Test
-  public void testSourcesToSourceDirectories_multipleNonMatchingPackagesAreNotMerged() throws Exception {
-    mockInputStreamProvider
-      .addFile("/root/java/com/google/Bla.java",
-               "package com.google;\n public class Bla {}")
-      .addFile("/root/java/com/google/subpackage/Bla.java",
-               "package com.google.different;\n public class Bla {}");
-    List<SourceArtifact> sourceArtifacts = ImmutableList.of(
-      SourceArtifact.builder(LABEL)
-        .setArtifactLocation(ArtifactLocation.builder()
-                               .setRelativePath("java/com/google/Bla.java")
-                               .setRootPath("/root")
-                               .setIsSource(true))
-        .build(),
-      SourceArtifact.builder(LABEL)
-        .setArtifactLocation(ArtifactLocation.builder()
-                               .setRelativePath("java/com/google/subpackage/Bla.java")
-                               .setRootPath("/root")
-                               .setIsSource(true))
-        .build()
-    );
-    ImmutableList<BlazeContentEntry> result = sourceDirectoryCalculator.calculateContentEntries(
-      context,
-      workspaceRoot,
-      new TestSourceImportConfig(false),
-      decoder,
-      ImmutableList.of(new WorkspacePath("java/com/google")),
-      sourceArtifacts,
-      NO_MANIFESTS
-    );
-    issues.assertNoIssues();
-    assertThat(result).containsExactly(
-      BlazeContentEntry.builder("/root/java/com/google")
-      .addSource(BlazeSourceDirectory.builder("/root/java/com/google")
-                 .setPackagePrefix("com.google")
-                 .build())
-      .addSource(BlazeSourceDirectory.builder("/root/java/com/google/subpackage")
-        .setPackagePrefix("com.google.different")
-                 .build())
-      .build()
-    );
-  }
-
-  @Test
-  public void testSourcesToSourceDirectories_childMatchesPathButParentDoesnt() throws Exception {
-    mockInputStreamProvider
-      .addFile("/root/java/com/google/Bla.java",
-               "package com.facebook;\n public class Bla {}")
-      .addFile("/root/java/com/google/subpackage/Bla.java",
-               "package com.google.subpackage;\n public class Bla {}");
-    List<SourceArtifact> sourceArtifacts = ImmutableList.of(
-      SourceArtifact.builder(LABEL)
-        .setArtifactLocation(ArtifactLocation.builder()
-                               .setRelativePath("java/com/google/Bla.java")
-                               .setRootPath("/root")
-                               .setIsSource(true))
-        .build(),
-      SourceArtifact.builder(LABEL)
-        .setArtifactLocation(ArtifactLocation.builder()
-                               .setRelativePath("java/com/google/subpackage/Bla.java")
-                               .setRootPath("/root")
-                               .setIsSource(true))
-        .build()
-    );
-    ImmutableList<BlazeContentEntry> result = sourceDirectoryCalculator.calculateContentEntries(
-      context,
-      workspaceRoot,
-      new TestSourceImportConfig(false),
-      decoder,
-      ImmutableList.of(new WorkspacePath("java/com/google")),
-      sourceArtifacts,
-      NO_MANIFESTS
-    );
-    issues.assertNoIssues();
-    assertThat(result).containsExactly(
-      BlazeContentEntry.builder("/root/java/com/google")
-        .addSource(BlazeSourceDirectory.builder("/root/java/com/google")
-                     .setPackagePrefix("com.facebook")
-                     .build())
-        .addSource(BlazeSourceDirectory.builder("/root/java/com/google/subpackage")
-                     .setPackagePrefix("com.google.subpackage")
-                     .build())
-        .build()
-    );
-  }
-
-  @Test
-  public void testSourcesToSourceDirectories_orderIsIrrelevant() throws Exception {
-    mockInputStreamProvider
-      .addFile("/root/java/com/google/Bla.java",
-               "package com.google;\n public class Bla {}")
-      .addFile("/root/java/com/google/subpackage/Bla.java",
-               "package com.google.different;\n public class Bla {}");
-    List<SourceArtifact> sourceArtifacts = ImmutableList.of(
-      SourceArtifact.builder(LABEL)
-        .setArtifactLocation(ArtifactLocation.builder()
-                               .setRelativePath("java/com/google/subpackage/Bla.java")
-                               .setRootPath("/root")
-                               .setIsSource(true))
-        .build(),
-      SourceArtifact.builder(LABEL)
-        .setArtifactLocation(ArtifactLocation.builder()
-                               .setRelativePath("java/com/google/Bla.java")
-                               .setRootPath("/root")
-                               .setIsSource(true))
-        .build()
-    );
-    ImmutableList<BlazeContentEntry> result = sourceDirectoryCalculator.calculateContentEntries(
-      context,
-      workspaceRoot,
-      new TestSourceImportConfig(false),
-      decoder,
-      ImmutableList.of(new WorkspacePath("java/com/google")),
-      sourceArtifacts,
-      NO_MANIFESTS
-    );
-    issues.assertNoIssues();
-    assertThat(result).containsExactly(
-      BlazeContentEntry.builder("/root/java/com/google")
-      .addSource(BlazeSourceDirectory.builder("/root/java/com/google")
-                   .setPackagePrefix("com.google")
-                 .build())
-      .addSource(BlazeSourceDirectory.builder("/root/java/com/google/subpackage")
-                   .setPackagePrefix("com.google.different")
-                   .build())
-      .build()
-    );
-  }
-
-  @Test
-  public void testSourcesToSourceDirectories_packagesMatchPath() throws Exception {
-    mockInputStreamProvider
-      .addFile("/root/java/com/google/Bla.java",
-               "package com.google;\n public class Bla {}");
-    List<SourceArtifact> sourceArtifacts = ImmutableList.of(
-      SourceArtifact.builder(LABEL)
-        .setArtifactLocation(ArtifactLocation.builder()
-                               .setRelativePath("java/com/google/Bla.java")
-                               .setRootPath("/root")
-                               .setIsSource(true))
-        .build());
-    ImmutableList<BlazeContentEntry> result = sourceDirectoryCalculator.calculateContentEntries(
-      context,
-      workspaceRoot,
-      new TestSourceImportConfig(false),
-      decoder,
-      ImmutableList.of(new WorkspacePath("java/com/google")),
-      sourceArtifacts,
-      NO_MANIFESTS
-    );
-    issues.assertNoIssues();
-    assertThat(result).containsExactly(
-      BlazeContentEntry.builder("/root/java/com/google")
-        .addSource(BlazeSourceDirectory.builder("/root/java/com/google")
-                     .setPackagePrefix("com.google")
-                     .build())
-        .build()
-    );
-  }
-
-  @Test
-  public void testSourcesToSourceDirectories_packagesDoNotMatchPath() throws Exception {
-    mockInputStreamProvider
-      .addFile("/root/java/com/google/Bla.java",
-               "package com.facebook;\n public class Bla {}");
-    List<SourceArtifact> sourceArtifacts = ImmutableList.of(
-      SourceArtifact.builder(LABEL)
-        .setArtifactLocation(ArtifactLocation.builder()
-                               .setRelativePath("java/com/google/Bla.java")
-                               .setRootPath("/root")
-                               .setIsSource(true))
-        .build());
-    ImmutableList<BlazeContentEntry> result = sourceDirectoryCalculator.calculateContentEntries(
-      context,
-      workspaceRoot,
-      new TestSourceImportConfig(false),
-      decoder,
-      ImmutableList.of(new WorkspacePath("java/com/google")),
-      sourceArtifacts,
-      NO_MANIFESTS
-    );
-    issues.assertNoIssues();
-    assertThat(result).containsExactly(
-      BlazeContentEntry.builder("/root/java/com/google")
-        .addSource(BlazeSourceDirectory.builder("/root/java/com/google")
-                     .setPackagePrefix("com.facebook")
-                     .build())
-        .build()
-    );
-  }
-
-  @Test
-  public void testSourcesToSourceDirectories_completePackagePathMismatch() throws Exception {
-    mockInputStreamProvider
-      .addFile("/root/java/com/org/foo/Bla.java",
-               "package com.facebook;\n public class Bla {}");
-    List<SourceArtifact> sourceArtifacts = ImmutableList.of(
-      SourceArtifact.builder(LABEL)
-        .setArtifactLocation(ArtifactLocation.builder()
-                               .setRelativePath("java/com/org/foo/Bla.java")
-                               .setRootPath("/root")
-                               .setIsSource(true))
-        .build());
-    ImmutableList<BlazeContentEntry> result = sourceDirectoryCalculator.calculateContentEntries(
-      context,
-      workspaceRoot,
-      new TestSourceImportConfig(false),
-      decoder,
-      ImmutableList.of(new WorkspacePath("java/com/org")),
-      sourceArtifacts,
-      NO_MANIFESTS
-    );
-    assertThat(result).containsExactly(
-      BlazeContentEntry.builder("/root/java/com/org")
-        .addSource(BlazeSourceDirectory.builder("/root/java/com/org")
-                     .setPackagePrefix("com.org")
-                     .build())
-        .addSource(BlazeSourceDirectory.builder("/root/java/com/org/foo")
-                     .setPackagePrefix("com.facebook")
-                     .build())
-        .build()
-    );
-  }
-
-  @Test
-  public void testSourcesToSourceDirectories_sourcesOutsideOfModuleGeneratesIssue() throws Exception {
-    mockInputStreamProvider
-      .addFile("/root/java/com/facebook/Bla.java",
-               "package com.facebook;\n public class Bla {}");
-    List<SourceArtifact> sourceArtifacts = ImmutableList.of(
-      SourceArtifact.builder(LABEL)
-        .setArtifactLocation(ArtifactLocation.builder()
-                               .setRelativePath("java/com/facebook/Bla.java")
-                               .setRootPath("/root")
-                               .setIsSource(true))
-        .build());
-    ImmutableList<BlazeContentEntry> result = sourceDirectoryCalculator.calculateContentEntries(
-      context,
-      workspaceRoot,
-      new TestSourceImportConfig(false),
-      decoder,
-      ImmutableList.of(new WorkspacePath("java/com/google")),
-      sourceArtifacts,
-      NO_MANIFESTS
-    );
-
-    issues.assertIssueContaining("Did not add");
-  }
-
-  @Test
-  public void testSourcesToSourceDirectories_generatedSourcesOutsideOfModuleGeneratesNoIssue() throws Exception {
-    mockInputStreamProvider
-      .addFile("/root/java/com/facebook/Bla.java",
-               "package com.facebook;\n public class Bla {}");
-    List<SourceArtifact> sourceArtifacts = ImmutableList.of(
-      SourceArtifact.builder(LABEL)
-        .setArtifactLocation(ArtifactLocation.builder()
-                               .setRelativePath("java/com/facebook/Bla.java")
-                               .setRootPath("/root")
-                               .setIsSource(false))
-        .build());
-    ImmutableList<BlazeContentEntry> result = sourceDirectoryCalculator.calculateContentEntries(
-      context,
-      workspaceRoot,
-      new TestSourceImportConfig(false),
-      decoder,
-      ImmutableList.of(new WorkspacePath("java/com/google/my")),
-      sourceArtifacts,
-      NO_MANIFESTS
-    );
-    issues.assertNoIssues();
-  }
-
-  @Test
-  public void testSourcesToSourceDirectories_missingPackageDeclaration() throws Exception {
-    mockInputStreamProvider
-      .addFile("/root/java/com/google/Bla.java",
-               "public class Bla {}");
-    List<SourceArtifact> sourceArtifacts = ImmutableList.of(
-      SourceArtifact.builder(LABEL)
-        .setArtifactLocation(ArtifactLocation.builder()
-                               .setRelativePath("java/com/google/Bla.java")
-                               .setRootPath("/root")
-                               .setIsSource(true))
-        .build());
-    ImmutableList<BlazeContentEntry> result = sourceDirectoryCalculator.calculateContentEntries(
-      context,
-      workspaceRoot,
-      new TestSourceImportConfig(false),
-      decoder,
-      ImmutableList.of(new WorkspacePath("java/com/google")),
-      sourceArtifacts,
-      NO_MANIFESTS
-    );
-
-    issues.assertIssueContaining("No package name string found");
-  }
-
-  @Test
-  public void testCompetingPackageDeclarationPicksMajority() throws Exception {
-    mockInputStreamProvider
-      .addFile("/root/java/com/google/Foo.java",
-               "package com.google.different;\n public class Foo {}")
-      .addFile("/root/java/com/google/Bla.java",
-               "package com.google;\n public class Bla {}")
-      .addFile("/root/java/com/google/Bla2.java",
-               "package com.google;\n public class Bla2 {}")
-      .addFile("/root/java/com/google/Bla3.java",
-               "package com.google;\n public class Bla3 {}")
-    ;
-    List<SourceArtifact> sourceArtifacts = ImmutableList.of(
-      SourceArtifact.builder(LABEL)
-        .setArtifactLocation(ArtifactLocation.builder()
-                               .setRelativePath("java/com/google/Bla.java")
-                               .setRootPath("/root")
-                               .setIsSource(true))
-        .build(),
-      SourceArtifact.builder(LABEL)
-        .setArtifactLocation(ArtifactLocation.builder()
-                               .setRelativePath("java/com/google/Bla2.java")
-                               .setRootPath("/root")
-                               .setIsSource(true))
-        .build(),
-      SourceArtifact.builder(LABEL)
-        .setArtifactLocation(ArtifactLocation.builder()
-                               .setRelativePath("java/com/google/Bla3.java")
-                               .setRootPath("/root")
-                               .setIsSource(true))
-        .build(),
-      SourceArtifact.builder(LABEL)
-        .setArtifactLocation(ArtifactLocation.builder()
-                               .setRelativePath("java/com/google/Foo.java")
-                               .setRootPath("/root")
-                               .setIsSource(true))
-        .build()
-    );
-    ImmutableList<BlazeContentEntry> result = sourceDirectoryCalculator.calculateContentEntries(
-      context,
-      workspaceRoot,
-      new TestSourceImportConfig(false),
-      decoder,
-      ImmutableList.of(new WorkspacePath("java/com/google")),
-      sourceArtifacts,
-      NO_MANIFESTS
-    );
-    issues.assertNoIssues();
-    assertThat(result).containsExactly(
-      BlazeContentEntry.builder("/root/java/com/google")
-        .addSource(BlazeSourceDirectory.builder("/root/java/com/google")
-                     .setPackagePrefix("com.google")
-                     .build())
-        .build()
-    );
-  }
-
-  @Test
-  public void testSourcesToSourceDirectories_packagesMatchPathButNotAtRoot() throws Exception {
-    mockInputStreamProvider
-      .addFile("/root/java/com/google/Bla.java",
-               "package com.google.different;\n public class Bla {}")
-      .addFile("/root/java/com/google/subpackage/Bla.java",
-               "package com.google.subpackage;\n public class Bla {}")
-      .addFile("/root/java/com/google/subpackage/subsubpackage/Bla.java",
-               "package com.google.subpackage.subsubpackage;\n public class Bla {}")
-    ;
-    List<SourceArtifact> sourceArtifacts = ImmutableList.of(
-      SourceArtifact.builder(LABEL)
-        .setArtifactLocation(ArtifactLocation.builder()
-                               .setRelativePath("java/com/google/Bla.java")
-                               .setRootPath("/root")
-                               .setIsSource(true))
-        .build(),
-      SourceArtifact.builder(LABEL)
-        .setArtifactLocation(ArtifactLocation.builder()
-                               .setRelativePath("java/com/google/subpackage/Bla.java")
-                               .setRootPath("/root")
-                               .setIsSource(true))
-        .build(),
-      SourceArtifact.builder(LABEL)
-        .setArtifactLocation(ArtifactLocation.builder()
-                               .setRelativePath("java/com/google/subpackage/subsubpackage/Bla.java")
-                               .setRootPath("/root")
-                               .setIsSource(true))
-        .build()
-    );
-    ImmutableList<BlazeContentEntry> result = sourceDirectoryCalculator.calculateContentEntries(
-      context,
-      workspaceRoot,
-      new TestSourceImportConfig(false),
-      decoder,
-      ImmutableList.of(new WorkspacePath("java/com/google")),
-      sourceArtifacts,
-      NO_MANIFESTS
-    );
-    issues.assertNoIssues();
-    assertThat(result).containsExactly(
-      BlazeContentEntry.builder("/root/java/com/google")
-        .addSource(BlazeSourceDirectory.builder("/root/java/com/google")
-                     .setPackagePrefix("com.google.different")
-                     .build())
-        .addSource(BlazeSourceDirectory.builder("/root/java/com/google/subpackage")
-                     .setPackagePrefix("com.google.subpackage")
-                     .build())
-        .build()
-    );
-  }
-
-  @Test
-  public void testSourcesToSourceDirectories_multipleSubdirectoriesAreNotMerged() throws Exception {
-    mockInputStreamProvider
-      .addFile("/root/java/com/google/package0/Bla.java",
-               "package com.google.packagewrong0;\n public class Bla {}")
-      .addFile("/root/java/com/google/package1/Bla.java",
-               "package com.google.packagewrong1;\n public class Bla {}");
-    List<SourceArtifact> sourceArtifacts = ImmutableList.of(
-      SourceArtifact.builder(LABEL)
-        .setArtifactLocation(ArtifactLocation.builder()
-                               .setRelativePath("java/com/google/package0/Bla.java")
-                               .setRootPath("/root")
-                               .setIsSource(true))
-        .build(),
-      SourceArtifact.builder(LABEL)
-        .setArtifactLocation(ArtifactLocation.builder()
-                               .setRelativePath("java/com/google/package1/Bla.java")
-                               .setRootPath("/root")
-                               .setIsSource(true))
-        .build()
-    );
-    ImmutableList<BlazeContentEntry> result = sourceDirectoryCalculator.calculateContentEntries(
-      context,
-      workspaceRoot,
-      new TestSourceImportConfig(false),
-      decoder,
-      ImmutableList.of(new WorkspacePath("java/com/google")),
-      sourceArtifacts,
-      NO_MANIFESTS
-    );
-    issues.assertNoIssues();
-    assertThat(result).containsExactly(
-      BlazeContentEntry.builder("/root/java/com/google")
-        .addSource(BlazeSourceDirectory.builder("/root/java/com/google")
-                     .setPackagePrefix("com.google")
-                     .build())
-        .addSource(BlazeSourceDirectory.builder("/root/java/com/google/package0")
-                     .setPackagePrefix("com.google.packagewrong0")
-                     .build())
-        .addSource(BlazeSourceDirectory.builder("/root/java/com/google/package1")
-                     .setPackagePrefix("com.google.packagewrong1")
-                     .build())
-        .build()
-    );
-  }
-
-  @Test
-  public void testOldFormatManifest() throws Exception {
-    setOldFormatPackageManifest(
-      "/root/java/com/test.manifest",
-      ImmutableList.of("/root/java/com/google/Bla.java"),
-      ImmutableList.of("com.google")
-    );
-    ImmutableMap<Label, ArtifactLocation> manifests = ImmutableMap.<Label, ArtifactLocation>builder()
-      .put(LABEL, ArtifactLocation.builder()
-        .setRelativePath("java/com/test.manifest")
-        .setRootPath("/root")
-        .setIsSource(true)
-        .build())
-      .build();
-    Map<Label, Map<String, String>> manifestMap = readPackageManifestFiles(manifests, getDecoder("/root"));
-
-    assertThat(manifestMap.get(LABEL)).containsEntry(
-      "/root/java/com/google/Bla.java",
-      "com.google");
-  }
-
-  @Test
-  public void testNewFormatManifest() throws Exception {
-    setNewFormatPackageManifest(
-      "/root/java/com/test.manifest",
-      ImmutableList.of(
-        PackageManifestOuterClass.ArtifactLocation.newBuilder()
-          .setRelativePath("java/com/google/Bla.java")
-          .setIsSource(true)
-          .build()),
-      ImmutableList.of("com.google")
-    );
-    ImmutableMap<Label, ArtifactLocation> manifests = ImmutableMap.<Label, ArtifactLocation>builder()
-      .put(LABEL, ArtifactLocation.builder()
-        .setRelativePath("java/com/test.manifest")
-        .setRootPath("/root")
-        .setIsSource(true)
-        .build())
-      .build();
-    Map<Label, Map<String, String>> manifestMap = readPackageManifestFiles(manifests, getDecoder("/root"));
-
-    assertThat(manifestMap.get(LABEL)).containsEntry(
-      "/root/java/com/google/Bla.java",
-      "com.google");
-  }
-
-  @Test
-  public void testManifestSingleFile() throws Exception {
-    setPackageManifest(
-      "/root",
-      "/root/java/com/test.manifest",
-      ImmutableList.of("java/com/google/Bla.java"),
-      ImmutableList.of("com.google")
-    );
-    ImmutableMap<Label, ArtifactLocation> manifests = ImmutableMap.<Label, ArtifactLocation>builder()
-      .put(LABEL, ArtifactLocation.builder()
-        .setRelativePath("java/com/test.manifest")
-        .setRootPath("/root")
-        .setIsSource(true)
-        .build())
-      .build();
-    Map<Label, Map<String, String>> manifestMap = readPackageManifestFiles(manifests, getDecoder("/root"));
-
-    assertThat(manifestMap.get(LABEL)).containsEntry(
-      "/root/java/com/google/Bla.java",
-      "com.google");
-  }
-
-  @Test
-  public void testManifestRepeatedSources() throws Exception {
-    setPackageManifest(
-      "/root",
-      "/root/java/com/test.manifest",
-      ImmutableList.of("java/com/google/Bla.java",
-                    "java/com/google/Foo.java"),
-      ImmutableList.of("com.google", "com.google.subpackage")
-    );
-    setPackageManifest(
-      "/root",
-      "/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<Label, ArtifactLocation> manifests = ImmutableMap.<Label, ArtifactLocation>builder()
-      .put(new Label("//a:a"), ArtifactLocation.builder()
-        .setRelativePath("java/com/test.manifest")
-        .setRootPath("/root")
-        .setIsSource(true)
-        .build())
-      .put(new Label("//b:b"), ArtifactLocation.builder()
-        .setRelativePath("java/com/test2.manifest")
-        .setRootPath("/root")
-        .setIsSource(true)
-        .build())
-      .build();
-    Map<Label, Map<String, String>> manifestMap = readPackageManifestFiles(manifests, getDecoder("/root"));
-
-    assertThat(manifestMap).hasSize(2);
-
-    assertThat(manifestMap.get(new Label("//a:a"))).containsEntry(
-      "/root/java/com/google/Bla.java",
-      "com.google");
-    assertThat(manifestMap.get(new Label("//a:a"))).containsEntry(
-      "/root/java/com/google/Foo.java",
-      "com.google.subpackage");
-    assertThat(manifestMap.get(new Label("//b:b"))).containsEntry(
-      "/root/java/com/google/other/Temp.java",
-      "com.google.other");
-  }
-
-  @Test
-  public void testManifestMissingSourcesFallback() throws Exception {
-    setPackageManifest(
-      "/root",
-      "/root/java/com/test.manifest",
-      ImmutableList.of("java/com/google/Bla.java",
-                    "java/com/google/Foo.java"),
-      ImmutableList.of("com.google", "com.google")
-    );
-
-    mockInputStreamProvider.addFile("/root/java/com/google/subpackage/Bla.java",
-                                    "package com.google.different;\n public class Bla {}");
-
-    ImmutableMap<Label, ArtifactLocation> manifests = ImmutableMap.<Label, ArtifactLocation>builder()
-      .put(LABEL,
-           ArtifactLocation.builder()
-             .setRelativePath("java/com/test.manifest")
-             .setRootPath("/root")
-             .setIsSource(true)
-             .build())
-      .build();
-
-    List<SourceArtifact> sourceArtifacts = ImmutableList.of(
-      SourceArtifact.builder(LABEL)
-        .setArtifactLocation(ArtifactLocation.builder()
-                               .setRelativePath("java/com/google/Bla.java")
-                               .setRootPath("/root")
-                               .setIsSource(true))
-        .build(),
-      SourceArtifact.builder(LABEL)
-        .setArtifactLocation(ArtifactLocation.builder()
-                               .setRelativePath("java/com/google/Foo.java")
-                               .setRootPath("/root")
-                               .setIsSource(true))
-        .build(),
-      SourceArtifact.builder(LABEL)
-        .setArtifactLocation(ArtifactLocation.builder()
-                               .setRelativePath("java/com/google/subpackage/Bla.java")
-                               .setRootPath("/root")
-                               .setIsSource(true))
-        .build());
-
-    ImmutableList<BlazeContentEntry> result = sourceDirectoryCalculator.calculateContentEntries(
-      context,
-      workspaceRoot,
-      new TestSourceImportConfig(false),
-      getDecoder("/root"),
-      ImmutableList.of(new WorkspacePath("java/com/google")),
-      sourceArtifacts,
-      manifests
-    );
-
-    issues.assertNoIssues();
-    assertThat(result).containsExactly(
-      BlazeContentEntry.builder("/root/java/com/google")
-        .addSource(BlazeSourceDirectory.builder("/root/java/com/google")
-                     .setPackagePrefix("com.google")
-                     .build())
-        .addSource(BlazeSourceDirectory.builder("/root/java/com/google/subpackage")
-                     .setPackagePrefix("com.google.different")
-                     .build())
-        .build()
-    );
-  }
-
-  @Test
-  public void testCandidateRootIterator() {
-    WorkspacePath directoryRoot = new WorkspacePath("com/google");
-    assertThat(new SourceDirectoryCalculator.CandidateRoots(directoryRoot, new SourceDirectoryCalculator.SourceRoot(
-      new WorkspacePath("com/google/a/b/c"), "com.google.a.b.c"))
-    ).containsExactly(
-      new SourceDirectoryCalculator.SourceRoot(new WorkspacePath("com/google/a/b"), "com.google.a.b"),
-      new SourceDirectoryCalculator.SourceRoot(new WorkspacePath("com/google/a"), "com.google.a"),
-      new SourceDirectoryCalculator.SourceRoot(new WorkspacePath("com/google"), "com.google")
-    ).inOrder();
-    assertThat(new SourceDirectoryCalculator.CandidateRoots(directoryRoot, new SourceDirectoryCalculator.SourceRoot(
-      new WorkspacePath("com/google/directory"), "com.google.different"))
-    ).isEmpty();
-  }
-
-  private void setPackageManifest(String rootPath,
-                                  String manifestPath,
-                                  List<String> sourceRelativePaths,
-                                  List<String> packages) {
-    PackageManifest.Builder manifest = PackageManifest.newBuilder();
-    for (int i = 0; i < sourceRelativePaths.size(); i++) {
-      String sourceRelativePath = sourceRelativePaths.get(i);
-      String absPath = Paths.get(rootPath, sourceRelativePath).toString();
-      PackageManifestOuterClass.ArtifactLocation source = PackageManifestOuterClass.ArtifactLocation.newBuilder()
-        .setRootPath(rootPath)
-        .setRelativePath(sourceRelativePath)
-        .setIsSource(true)
-        .build();
-      manifest.addSources(JavaSourcePackage.newBuilder()
-                            .setAbsolutePath(absPath)
-                            .setArtifactLocation(source)
-                            .setPackageString(packages.get(i)));
-    }
-    mockInputStreamProvider.addFile(manifestPath, manifest.build().toByteArray());
-  }
-
-  private void setOldFormatPackageManifest(String manifestPath, List<String> sourcePaths, List<String> packages) {
-    PackageManifest.Builder manifest = PackageManifest.newBuilder();
-    for (int i = 0; i < sourcePaths.size(); i++) {
-      manifest.addSources(JavaSourcePackage.newBuilder()
-                            .setAbsolutePath(sourcePaths.get(i))
-                            .setPackageString(packages.get(i)));
-    }
-    mockInputStreamProvider.addFile(manifestPath, manifest.build().toByteArray());
-  }
-
-  private void setNewFormatPackageManifest(String manifestPath,
-                                           List<PackageManifestOuterClass.ArtifactLocation> sources,
-                                           List<String> packages) {
-    PackageManifest.Builder manifest = PackageManifest.newBuilder();
-    for (int i = 0; i < sources.size(); i++) {
-      manifest.addSources(JavaSourcePackage.newBuilder()
-                            .setArtifactLocation(sources.get(i))
-                            .setPackageString(packages.get(i)));
-    }
-    mockInputStreamProvider.addFile(manifestPath, manifest.build().toByteArray());
-  }
-
-  private static ArtifactLocationDecoder getDecoder(String rootPath) {
-    File root = new File(rootPath);
-    WorkspaceRoot workspaceRoot = new WorkspaceRoot(root);
-    BlazeRoots roots = new BlazeRoots(
-      root,
-      ImmutableList.of(root),
-      new ExecutionRootPath("out/crosstool/bin"),
-      new ExecutionRootPath("out/crosstool/gen")
-    );
-    return new ArtifactLocationDecoder(roots, new WorkspacePathResolverImpl(workspaceRoot, roots));
-  }
-
-  private static class MockInputStreamProvider implements InputStreamProvider {
-
-    private final Map<String, InputStream> javaFiles = new HashMap<String, InputStream>();
-
-    public MockInputStreamProvider addFile(String filePath, String javaSrc) {
-      try {
-        addFile(filePath, javaSrc.getBytes("UTF-8"));
-      }
-      catch (UnsupportedEncodingException e) {
-        fail(e.getMessage());
-      }
-      return this;
-    }
-
-    public MockInputStreamProvider addFile(String filePath, byte[] contents) {
-      javaFiles.put(filePath, new ByteArrayInputStream(contents));
-      return this;
-    }
-
-    @Override
-    public InputStream getFile(@NotNull File path) throws FileNotFoundException {
-      final InputStream inputStream = javaFiles.get(path.getPath());
-      if (inputStream == null) {
-        throw new FileNotFoundException(
-          path + " has not been mapped into MockInputStreamProvider.");
-      }
-      return inputStream;
-    }
-  }
-
-  private Map<Label, Map<String, String>> readPackageManifestFiles(
-    Map<Label, ArtifactLocation> manifests,
-    ArtifactLocationDecoder decoder) {
-    return PackageManifestReader.getInstance().readPackageManifestFiles(context, decoder, manifests, MoreExecutors.sameThreadExecutor());
-  }
-
-  static class MockFileAttributeProvider extends FileAttributeProvider {
-    @Override
-    public long getFileModifiedTime(@NotNull File file) {
-      return 0;
-    }
-  }
-}
\ No newline at end of file
diff --git a/blaze-plugin-dev/BUILD b/blaze-plugin-dev/BUILD
deleted file mode 100644
index 0377443..0000000
--- a/blaze-plugin-dev/BUILD
+++ /dev/null
@@ -1,42 +0,0 @@
-package(default_visibility = ["//visibility:public"])
-
-java_library(
-    name = "blaze-plugin-dev",
-    srcs = glob(["src/**/*.java"]),
-    deps = [
-        "//blaze-base",
-        "//blaze-java",
-        "//intellij-platform-sdk:devkit",
-        "//intellij-platform-sdk:plugin_api",
-        "//third_party:jsr305",
-    ],
-)
-
-filegroup(
-    name = "plugin_xml",
-    srcs = ["src/META-INF/blaze-plugin-dev.xml"],
-)
-
-load(
-    "//intellij_test:test_defs.bzl",
-    "intellij_test",
-)
-
-intellij_test(
-    name = "integration_tests",
-    srcs = glob(["tests/integrationtests/**/*.java"]),
-    integration_tests = True,
-    required_plugins = "com.google.idea.blaze.ijwb",
-    test_package_root = "com.google.idea.blaze.plugin",
-    deps = [
-        ":blaze-plugin-dev",
-        "//blaze-base",
-        "//blaze-base:integration_test_utils",
-        "//blaze-base:unit_test_utils",
-        "//ijwb:ijwb_bazel",
-        "//intellij-platform-sdk:plugin_api_for_tests",
-        "//intellij_test:lib",
-        "//third_party:jsr305",
-        "//third_party:test_lib",
-    ],
-)
diff --git a/blaze-plugin-dev/src/com/google/idea/blaze/plugin/IntellijPluginRule.java b/blaze-plugin-dev/src/com/google/idea/blaze/plugin/IntellijPluginRule.java
deleted file mode 100644
index 29f2271..0000000
--- a/blaze-plugin-dev/src/com/google/idea/blaze/plugin/IntellijPluginRule.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.plugin;
-
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.model.primitives.Kind;
-
-/**
- * Utility methods for intellij_plugin blaze rules
- */
-public class IntellijPluginRule {
-
-  public static final String RULE_TAG_IJ_PLUGIN = "intellij-plugin";
-
-  public static boolean isPluginRule(RuleIdeInfo rule) {
-    return rule.kindIsOneOf(Kind.JAVA_IMPORT) && rule.tags.contains(RULE_TAG_IJ_PLUGIN);
-  }
-}
diff --git a/blaze-plugin-dev/src/com/google/idea/blaze/plugin/run/BlazeIntellijPluginConfiguration.java b/blaze-plugin-dev/src/com/google/idea/blaze/plugin/run/BlazeIntellijPluginConfiguration.java
deleted file mode 100644
index eff1164..0000000
--- a/blaze-plugin-dev/src/com/google/idea/blaze/plugin/run/BlazeIntellijPluginConfiguration.java
+++ /dev/null
@@ -1,466 +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.plugin.run;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Ordering;
-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.LibraryArtifact;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-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.BlazeRunConfiguration;
-import com.google.idea.blaze.base.run.rulefinder.RuleFinder;
-import com.google.idea.blaze.base.settings.Blaze;
-import com.google.idea.blaze.base.ui.UiUtil;
-import com.google.idea.blaze.plugin.IntellijPluginRule;
-import com.intellij.execution.ExecutionException;
-import com.intellij.execution.Executor;
-import com.intellij.execution.configurations.*;
-import com.intellij.execution.process.OSProcessHandler;
-import com.intellij.execution.process.ProcessAdapter;
-import com.intellij.execution.process.ProcessEvent;
-import com.intellij.execution.runners.ExecutionEnvironment;
-import com.intellij.ide.plugins.IdeaPluginDescriptor;
-import com.intellij.ide.plugins.PluginManagerCore;
-import com.intellij.openapi.application.JetBrainsProtocolHandler;
-import com.intellij.openapi.module.Module;
-import com.intellij.openapi.options.ConfigurationException;
-import com.intellij.openapi.options.SettingsEditor;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.projectRoots.ProjectJdkTable;
-import com.intellij.openapi.projectRoots.Sdk;
-import com.intellij.openapi.roots.ProjectRootManager;
-import com.intellij.openapi.roots.ui.configuration.JdkComboBox;
-import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel;
-import com.intellij.openapi.ui.ComboBox;
-import com.intellij.openapi.ui.LabeledComponent;
-import com.intellij.openapi.util.BuildNumber;
-import com.intellij.openapi.util.InvalidDataException;
-import com.intellij.openapi.util.WriteExternalException;
-import com.intellij.ui.ListCellRendererWrapper;
-import com.intellij.ui.RawCommandLineEditor;
-import com.intellij.util.PlatformUtils;
-import com.intellij.util.execution.ParametersListUtil;
-import org.jdom.Element;
-
-import javax.annotation.Nullable;
-import javax.swing.*;
-import java.awt.*;
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.StandardCopyOption;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * A run configuration that builds a plugin jar via blaze, copies it to the
- * SDK sandbox, then runs IJ with the plugin loaded.
- */
-public class BlazeIntellijPluginConfiguration extends LocatableConfigurationBase implements BlazeRunConfiguration, ModuleRunConfiguration {
-
-  private static final String TARGET_TAG = "blaze-target";
-  private static final String USER_BLAZE_FLAG_TAG = "blaze-user-flag";
-  private static final String USER_EXE_FLAG_TAG = "blaze-user-exe-flag";
-  private static final String SDK_ATTR = "blaze-plugin-sdk";
-  private static final String VM_PARAMS_ATTR = "blaze-vm-params";
-  private static final String PROGRAM_PARAMS_ATTR = "blaze-program-params";
-
-  private final String buildSystem;
-
-  @Nullable private Label target;
-  private ImmutableList<String> blazeFlags = ImmutableList.of();
-  private ImmutableList<String> exeFlags = ImmutableList.of();
-  @Nullable private Sdk pluginSdk;
-  @Nullable private String vmParameters;
-  @Nullable private String programParameters;
-
-  public BlazeIntellijPluginConfiguration(
-    Project project,
-    ConfigurationFactory factory,
-    String name,
-    @Nullable RuleIdeInfo initialRule) {
-    super(project, factory, name);
-    this.buildSystem = Blaze.buildSystemName(project);
-    if (initialRule != null) {
-      target = initialRule.label;
-    }
-    Sdk projectSdk = ProjectRootManager.getInstance(project).getProjectSdk();
-    if (IdeaJdkHelper.isIdeaJdk(projectSdk)) {
-      pluginSdk = projectSdk;
-    }
-  }
-
-  @Override
-  @Nullable
-  public Label getTarget() {
-    return target;
-  }
-
-  public void setTarget(Label target) {
-    this.target = target;
-  }
-
-  private File findPluginJar() throws ExecutionException {
-    RuleIdeInfo rule = RuleFinder.getInstance().ruleForTarget(getProject(), getTarget());
-    if (rule == null) {
-      throw new ExecutionException(buildSystem + " rule '" + getTarget() + "' not imported during sync");
-    }
-    JavaRuleIdeInfo javaRuleIdeInfo = rule.javaRuleIdeInfo;
-    if (javaRuleIdeInfo == null) {
-      throw new ExecutionException(buildSystem + " rule '" + getTarget() + "' is not a valid intellij_plugin rule");
-    }
-    Collection<LibraryArtifact> jars = javaRuleIdeInfo.jars;
-    if (javaRuleIdeInfo.jars.size() > 1) {
-      throw new ExecutionException("Invalid IntelliJ plugin rule: it has multiple output jars");
-    }
-    LibraryArtifact artifact = jars.isEmpty() ? null : jars.iterator().next();
-    if (artifact == null || artifact.runtimeJar == null) {
-      throw new ExecutionException("No output plugin jar found for '" + getTarget() + "'");
-    }
-    return artifact.runtimeJar.getFile();
-  }
-
-  /**
-   * Plugin jar has been previously created via blaze build. This method:
-   *  - copies jar to sandbox environment
-   *  - cracks open jar and finds plugin.xml (with ID, etc., needed for JVM args)
-   *  - sets up the SDK, etc. (use project SDK?)
-   *  - sets up the JVM, and returns a JavaCommandLineState
-   */
-  @Nullable
-  @Override
-  public RunProfileState getState(Executor executor, ExecutionEnvironment env) throws ExecutionException {
-    final Sdk ideaJdk = pluginSdk;
-    if (!IdeaJdkHelper.isIdeaJdk(ideaJdk)) {
-      throw new ExecutionException("Choose an IntelliJ Platform Plugin SDK");
-    }
-    String sandboxHome = IdeaJdkHelper.getSandboxHome(ideaJdk);
-    if (sandboxHome == null){
-      throw new ExecutionException("No sandbox specified for IntelliJ Platform Plugin SDK");
-    }
-
-    try {
-      sandboxHome = new File(sandboxHome).getCanonicalPath();
-    }
-    catch (IOException e) {
-      throw new ExecutionException("No sandbox specified for IntelliJ Platform Plugin SDK");
-    }
-    final String canonicalSandbox = sandboxHome;
-    final File pluginJar = findPluginJar();
-    if (!pluginJar.exists()) {
-      throw new ExecutionException("No plugin jar found. Did the " + buildSystem + " build fail?");
-    }
-    final File pluginJarDestination = new File(canonicalSandbox, "plugins/" + pluginJar.getName());
-
-    // copy license from running instance of idea
-    IdeaJdkHelper.copyIDEALicense(sandboxHome);
-
-    final JavaCommandLineState state = new JavaCommandLineState(env) {
-      @Override
-      protected JavaParameters createJavaParameters() throws ExecutionException {
-
-        // copy plugin jar to sandbox
-        IdeaPluginDescriptor pluginDescriptor = PluginManagerCore.loadDescriptor(pluginJar, "plugin.xml");
-        String buildNumber = IdeaJdkHelper.getBuildNumber(ideaJdk);
-        if (PluginManagerCore.isIncompatible(pluginDescriptor, BuildNumber.fromString(buildNumber))) {
-          throw new ExecutionException(
-            String.format("Plugin SDK version '%s' is incompatible with this plugin (since: '%s', until: '%s')",
-                          buildNumber, pluginDescriptor.getSinceBuild(), pluginDescriptor.getUntilBuild()));
-        }
-
-        try {
-          pluginJarDestination.getParentFile().mkdirs();
-          Files.copy(pluginJar.toPath(), pluginJarDestination.toPath(), StandardCopyOption.REPLACE_EXISTING);
-        }
-        catch (IOException e) {
-          throw new ExecutionException("Error copying plugin jar to sandbox", e);
-        }
-
-        final JavaParameters params = new JavaParameters();
-
-        ParametersList vm = params.getVMParametersList();
-
-        fillParameterList(vm, vmParameters);
-        fillParameterList(params.getProgramParametersList(), programParameters);
-
-        IntellijWithPluginClasspathHelper.addRequiredVmParams(params, ideaJdk);
-
-        vm.defineProperty(JetBrainsProtocolHandler.REQUIRED_PLUGINS_KEY, pluginDescriptor.getPluginId().toString());
-
-        if (!vm.hasProperty(PlatformUtils.PLATFORM_PREFIX_KEY) && buildNumber != null) {
-          String prefix = IdeaJdkHelper.getPlatformPrefix(buildNumber);
-          if (prefix != null) {
-            vm.defineProperty(PlatformUtils.PLATFORM_PREFIX_KEY, prefix);
-          }
-        }
-        return params;
-      }
-
-      @Override
-      protected OSProcessHandler startProcess() throws ExecutionException {
-        final OSProcessHandler handler = super.startProcess();
-        handler.addProcessListener(new ProcessAdapter() {
-          @Override
-          public void processTerminated(ProcessEvent event) {
-            pluginJarDestination.delete();
-          }
-        });
-        return handler;
-      }
-    };
-    return state;
-  }
-
-  private static void fillParameterList(ParametersList list, @Nullable String value) {
-    if (value == null) return;
-
-    for (String parameter : value.split(" ")) {
-      if (parameter != null && parameter.length() > 0) {
-        list.add(parameter);
-      }
-    }
-  }
-
-  @Override
-  public Module[] getModules() {
-    return Module.EMPTY_ARRAY;
-  }
-
-  @Override
-  public void checkConfiguration() throws RuntimeConfigurationException {
-    super.checkConfiguration();
-
-    Label target = getTarget();
-    if (target == null) {
-      throw new RuntimeConfigurationError("Select a target to run");
-    }
-    RuleIdeInfo rule = RuleFinder.getInstance().ruleForTarget(getProject(), target);
-    if (rule == null) {
-      throw new RuntimeConfigurationError("The selected target does not exist.");
-    }
-    if (!IntellijPluginRule.isPluginRule(rule)) {
-      throw new RuntimeConfigurationError(
-        "The selected target is not an intellij_plugin");
-    }
-    if (!IdeaJdkHelper.isIdeaJdk(pluginSdk)) {
-      throw new RuntimeConfigurationError("Select an IntelliJ Platform Plugin SDK");
-    }
-  }
-
-  @Override
-  public void readExternal(Element element) throws InvalidDataException {
-    super.readExternal(element);
-    // Target is persisted as a tag to permit multiple targets in the future.
-    Element targetElement = element.getChild(TARGET_TAG);
-    if (targetElement != null && !Strings.isNullOrEmpty(targetElement.getTextTrim())) {
-      target = (Label) TargetExpression.fromString(targetElement.getTextTrim());
-    }
-    else {
-      target = null;
-    }
-    blazeFlags = loadUserFlags(element, USER_BLAZE_FLAG_TAG);
-    exeFlags = loadUserFlags(element, USER_EXE_FLAG_TAG);
-
-    String sdkName = element.getAttributeValue(SDK_ATTR);
-    if (!Strings.isNullOrEmpty(sdkName)) {
-      pluginSdk = ProjectJdkTable.getInstance().findJdk(sdkName);
-    }
-    vmParameters = Strings.emptyToNull(element.getAttributeValue(VM_PARAMS_ATTR));
-    programParameters = Strings.emptyToNull(element.getAttributeValue(PROGRAM_PARAMS_ATTR));
-  }
-
-  private static ImmutableList<String> loadUserFlags(Element root,String tag) {
-    ImmutableList.Builder<String> flagsBuilder = ImmutableList.builder();
-    for (Element e : root.getChildren(tag)) {
-      String flag = e.getTextTrim();
-      if (flag != null && !flag.isEmpty()) {
-        flagsBuilder.add(flag);
-      }
-    }
-    return flagsBuilder.build();
-  }
-
-  private static void saveUserFlags(Element root, List<String> flags, String tag) {
-    for (String flag : flags) {
-      Element child = new Element(tag);
-      child.setText(flag);
-      root.addContent(child);
-    }
-  }
-
-  @Override
-  public void writeExternal(Element element) throws WriteExternalException {
-    super.writeExternal(element);
-    if (target != null) {
-      // Target is persisted as a tag to permit multiple targets in the future.
-      Element targetElement = new Element(TARGET_TAG);
-      targetElement.setText(target.toString());
-      element.addContent(targetElement);
-    }
-    saveUserFlags(element, blazeFlags, USER_BLAZE_FLAG_TAG);
-    saveUserFlags(element, exeFlags, USER_EXE_FLAG_TAG);
-    if (pluginSdk != null) {
-      element.setAttribute(SDK_ATTR, pluginSdk.getName());
-    }
-    if (vmParameters != null) {
-      element.setAttribute(VM_PARAMS_ATTR, vmParameters);
-    }
-    if (programParameters != null) {
-      element.setAttribute(PROGRAM_PARAMS_ATTR, programParameters);
-    }
-  }
-
-  @Override
-  public BlazeIntellijPluginConfiguration clone() {
-    final BlazeIntellijPluginConfiguration configuration = (BlazeIntellijPluginConfiguration) super.clone();
-    configuration.target = target;
-    configuration.blazeFlags = blazeFlags;
-    configuration.exeFlags = exeFlags;
-    configuration.pluginSdk = pluginSdk;
-    configuration.vmParameters = vmParameters;
-    configuration.programParameters = programParameters;
-    return configuration;
-  }
-
-  protected BlazeCommand buildBlazeCommand(Project project, ProjectViewSet projectViewSet) {
-    BlazeCommand.Builder command = BlazeCommand.builder(Blaze.getBuildSystem(getProject()), BlazeCommandName.BUILD)
-      .addTargets(getTarget())
-      .addBlazeFlags(BlazeFlags.buildFlags(project, projectViewSet))
-      .addBlazeFlags(blazeFlags)
-      .addExeFlags(exeFlags)
-      ;
-    return command.build();
-  }
-
-  @Override
-  public BlazeIntellijPluginConfigurationSettingsEditor getConfigurationEditor() {
-    List<RuleIdeInfo> javaRules = RuleFinder.getInstance().findRules(getProject(), IntellijPluginRule::isPluginRule);
-    List<Label> javaLabels = Lists.newArrayList();
-    for (RuleIdeInfo rule : javaRules) {
-      javaLabels.add(rule.label);
-    }
-    return new BlazeIntellijPluginConfigurationSettingsEditor(buildSystem, javaLabels);
-  }
-
-  @Override
-  @Nullable
-  public String suggestedName() {
-    Label target = getTarget();
-    if (target == null) {
-      return null;
-    }
-    return target.ruleName().toString();
-  }
-
-  @VisibleForTesting
-  static class BlazeIntellijPluginConfigurationSettingsEditor extends SettingsEditor<BlazeIntellijPluginConfiguration> {
-    private final String buildSystemName;
-    private final ComboBox targetCombo;
-    private final JTextArea blazeFlagsField = new JTextArea(5, 0);
-    private final JTextArea exeFlagsField = new JTextArea(5, 0);
-    private final JdkComboBox sdkCombo;
-    private final LabeledComponent<RawCommandLineEditor> vmParameters = new LabeledComponent<>();
-    private final LabeledComponent<RawCommandLineEditor> programParameters = new LabeledComponent<>();
-
-    public BlazeIntellijPluginConfigurationSettingsEditor(String buildSystemName, List<Label> javaLabels) {
-      this.buildSystemName = buildSystemName;
-      targetCombo = new ComboBox(new DefaultComboBoxModel(Ordering.usingToString().sortedCopy(javaLabels).toArray()));
-      targetCombo.setRenderer(new ListCellRendererWrapper<Label>() {
-        @Override
-        public void customize(JList list, @Nullable Label value, int index, boolean selected, boolean hasFocus) {
-          setText(value == null ? null : value.toString());
-        }
-      });
-
-      ProjectSdksModel sdksModel = new ProjectSdksModel();
-      sdksModel.reset(null);
-      sdkCombo = new JdkComboBox(sdksModel, IdeaJdkHelper::isIdeaJdkType);
-    }
-
-    @VisibleForTesting
-    @Override
-    public void resetEditorFrom(BlazeIntellijPluginConfiguration s) {
-      targetCombo.setSelectedItem(s.getTarget());
-      blazeFlagsField.setText(ParametersListUtil.join(s.blazeFlags));
-      exeFlagsField.setText(ParametersListUtil.join(s.exeFlags));
-      if (s.pluginSdk != null) {
-        sdkCombo.setSelectedJdk(s.pluginSdk);
-      } else {
-        s.pluginSdk = sdkCombo.getSelectedJdk();
-      }
-      if (s.vmParameters != null) {
-        vmParameters.getComponent().setText(s.vmParameters);
-      }
-      if (s.programParameters != null) {
-        programParameters.getComponent().setText(s.programParameters);
-      }
-    }
-
-    @VisibleForTesting
-    @Override
-    public void applyEditorTo(BlazeIntellijPluginConfiguration s) throws ConfigurationException {
-      try {
-        s.target = (Label)targetCombo.getSelectedItem();
-      }
-      catch (ClassCastException e) {
-        throw new ConfigurationException("Invalid label specified.");
-      }
-      s.blazeFlags = ImmutableList.copyOf(ParametersListUtil.parse(Strings.nullToEmpty(blazeFlagsField.getText())));
-      s.exeFlags = ImmutableList.copyOf(ParametersListUtil.parse(Strings.nullToEmpty(exeFlagsField.getText())));
-      s.pluginSdk = sdkCombo.getSelectedJdk();
-      s.vmParameters = vmParameters.getComponent().getText();
-      s.programParameters = programParameters.getComponent().getText();
-    }
-
-    @Override
-    protected JComponent createEditor() {
-      vmParameters.setText("VM options:");
-      vmParameters.setComponent(new RawCommandLineEditor());
-      vmParameters.getComponent().setDialogCaption(vmParameters.getRawText());
-      vmParameters.setLabelLocation(BorderLayout.WEST);
-
-      programParameters.setText("Program arguments");
-      programParameters.setComponent(new RawCommandLineEditor());
-      programParameters.getComponent().setDialogCaption(programParameters.getRawText());
-      programParameters.setLabelLocation(BorderLayout.WEST);
-
-      return UiUtil.createBox(
-        new JLabel("Target:"),
-        targetCombo,
-        new JLabel("Plugin SDK"),
-        sdkCombo,
-        vmParameters.getLabel(),
-        vmParameters.getComponent(),
-        programParameters.getLabel(),
-        programParameters.getComponent(),
-        new JLabel(buildSystemName + " flags:"),
-        blazeFlagsField,
-        new JLabel("Executable flags:"),
-        exeFlagsField
-      );
-    }
-  }
-}
-
diff --git a/blaze-plugin-dev/src/com/google/idea/blaze/plugin/run/BlazeIntellijPluginConfigurationType.java b/blaze-plugin-dev/src/com/google/idea/blaze/plugin/run/BlazeIntellijPluginConfigurationType.java
deleted file mode 100644
index 0b8356f..0000000
--- a/blaze-plugin-dev/src/com/google/idea/blaze/plugin/run/BlazeIntellijPluginConfigurationType.java
+++ /dev/null
@@ -1,133 +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.plugin.run;
-
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.model.primitives.WorkspaceType;
-import com.google.idea.blaze.base.run.BlazeRuleConfigurationFactory;
-import com.google.idea.blaze.base.run.rulefinder.RuleFinder;
-import com.google.idea.blaze.base.settings.Blaze;
-import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
-import com.google.idea.blaze.plugin.IntellijPluginRule;
-import com.intellij.execution.BeforeRunTask;
-import com.intellij.execution.RunManager;
-import com.intellij.execution.RunnerAndConfigurationSettings;
-import com.intellij.execution.configurations.ConfigurationFactory;
-import com.intellij.execution.configurations.ConfigurationType;
-import com.intellij.execution.configurations.ConfigurationTypeUtil;
-import com.intellij.icons.AllIcons;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.Key;
-
-import javax.swing.*;
-
-/**
- * A type for run configurations that build an IntelliJ plugin jar via blaze,
- * then load them in an IntelliJ application
- */
-public class BlazeIntellijPluginConfigurationType implements ConfigurationType {
-
-  private final BlazeIntellijPluginConfigurationFactory factory = new BlazeIntellijPluginConfigurationFactory(this);
-
-  public static class BlazeIntellijPluginRuleConfigurationFactory implements BlazeRuleConfigurationFactory {
-    @Override
-    public boolean handlesRule(WorkspaceLanguageSettings workspaceLanguageSettings, RuleIdeInfo rule) {
-      return workspaceLanguageSettings.isWorkspaceType(WorkspaceType.INTELLIJ_PLUGIN)
-             && IntellijPluginRule.isPluginRule(rule);
-    }
-
-    @Override
-    public RunnerAndConfigurationSettings createForRule(RunManager runManager, RuleIdeInfo rule) {
-      return getInstance().factory.createForRule(runManager, rule);
-    }
-  }
-
-  public static class BlazeIntellijPluginConfigurationFactory extends ConfigurationFactory {
-
-    protected BlazeIntellijPluginConfigurationFactory(ConfigurationType type) {
-      super(type);
-    }
-
-    @Override
-    public boolean isApplicable(Project project) {
-      return Blaze.isBlazeProject(project);
-    }
-
-    @Override
-    public BlazeIntellijPluginConfiguration createTemplateConfiguration(Project project) {
-      return new BlazeIntellijPluginConfiguration(
-        project,
-        this,
-        "Unnamed",
-        RuleFinder.getInstance().findFirstRule(project, IntellijPluginRule::isPluginRule)
-      );
-    }
-
-    @Override
-    public void configureBeforeRunTaskDefaults(
-      Key<? extends BeforeRunTask> providerID, BeforeRunTask task) {
-      task.setEnabled(providerID.equals(BuildPluginBeforeRunTaskProvider.ID));
-    }
-
-    public RunnerAndConfigurationSettings createForRule(RunManager runManager, RuleIdeInfo rule) {
-      final RunnerAndConfigurationSettings settings =
-        runManager.createRunConfiguration(rule.label.toString(), this);
-      final BlazeIntellijPluginConfiguration configuration =
-        (BlazeIntellijPluginConfiguration) settings.getConfiguration();
-      configuration.setTarget(rule.label);
-      return settings;
-    }
-
-    @Override
-    public boolean isConfigurationSingletonByDefault() {
-      return true;
-    }
-  }
-
-  public static BlazeIntellijPluginConfigurationType getInstance() {
-    return ConfigurationTypeUtil.findConfigurationType(BlazeIntellijPluginConfigurationType.class);
-  }
-
-  @Override
-  public String getDisplayName() {
-    return Blaze.defaultBuildSystemName() + " IntelliJ Plugin";
-  }
-
-  @Override
-  public String getConfigurationTypeDescription() {
-    return "Configuration for launching an IntelliJ plugin in a sandbox environment.";
-  }
-
-  @Override
-  public Icon getIcon() {
-    return AllIcons.Nodes.Plugin;
-  }
-
-  @Override
-  public String getId() {
-    return "BlazeIntellijPluginConfigurationType";
-  }
-
-  @Override
-  public BlazeIntellijPluginConfigurationFactory[] getConfigurationFactories() {
-    return new BlazeIntellijPluginConfigurationFactory[] {factory};
-  }
-
-  public BlazeIntellijPluginConfigurationFactory getFactory() {
-    return factory;
-  }
-
-}
diff --git a/blaze-plugin-dev/src/com/google/idea/blaze/plugin/run/BuildPluginBeforeRunTaskProvider.java b/blaze-plugin-dev/src/com/google/idea/blaze/plugin/run/BuildPluginBeforeRunTaskProvider.java
deleted file mode 100644
index 216621c..0000000
--- a/blaze-plugin-dev/src/com/google/idea/blaze/plugin/run/BuildPluginBeforeRunTaskProvider.java
+++ /dev/null
@@ -1,199 +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.plugin.run;
-
-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.async.process.ExternalTask;
-import com.google.idea.blaze.base.async.process.LineProcessingOutputStream;
-import com.google.idea.blaze.base.command.BlazeCommand;
-import com.google.idea.blaze.base.experiments.ExperimentScope;
-import com.google.idea.blaze.base.issueparser.IssueOutputLineProcessor;
-import com.google.idea.blaze.base.metrics.Action;
-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.scope.BlazeContext;
-import com.google.idea.blaze.base.scope.Scope;
-import com.google.idea.blaze.base.scope.ScopedTask;
-import com.google.idea.blaze.base.scope.output.IssueOutput;
-import com.google.idea.blaze.base.scope.scopes.BlazeConsoleScope;
-import com.google.idea.blaze.base.scope.scopes.IssuesScope;
-import com.google.idea.blaze.base.scope.scopes.LoggedTimingScope;
-import com.google.idea.blaze.base.settings.Blaze;
-import com.google.idea.blaze.base.settings.BlazeUserSettings;
-import com.google.idea.blaze.base.util.SaveUtil;
-import com.intellij.execution.BeforeRunTask;
-import com.intellij.execution.BeforeRunTaskProvider;
-import com.intellij.execution.configurations.RunConfiguration;
-import com.intellij.execution.runners.ExecutionEnvironment;
-import com.intellij.openapi.actionSystem.DataContext;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.Key;
-import com.intellij.openapi.vfs.LocalFileSystem;
-import icons.BlazeIcons;
-
-import javax.annotation.Nullable;
-import javax.swing.*;
-import java.util.concurrent.CancellationException;
-import java.util.concurrent.ExecutionException;
-
-/**
- * Builds the intellij_plugin jar via 'blaze build', for Blaze Intellij Plugin run configurations
- */
-public final class BuildPluginBeforeRunTaskProvider extends BeforeRunTaskProvider<BuildPluginBeforeRunTaskProvider.Task> {
-  public static final Key<Task> ID = Key.create("Blaze.Intellij.Plugin.BeforeRunTask");
-
-  public static class Task extends BeforeRunTask<Task> {
-    private Task() {
-      super(ID);
-      setEnabled(true);
-    }
-  }
-
-  private final Project project;
-
-  public BuildPluginBeforeRunTaskProvider(Project project) {
-    this.project = project;
-  }
-
-  @Override
-  public Icon getIcon() {
-    return BlazeIcons.Blaze;
-  }
-
-  @Override
-  public Icon getTaskIcon(Task task) {
-    return BlazeIcons.Blaze;
-  }
-
-  @Override
-  public boolean isConfigurable() {
-    return false;
-  }
-
-  @Override
-  public boolean configureTask(RunConfiguration runConfiguration, Task task) {
-    return false;
-  }
-
-  @Override
-  public Key<Task> getId() {
-    return ID;
-  }
-
-  @Override
-  public String getName() {
-    return taskName();
-  }
-
-  @Override
-  public String getDescription(Task task) {
-    return taskName();
-  }
-
-  private String taskName() {
-    return Blaze.buildSystemName(project) + " build plugin before-run task";
-  }
-
-  @Override
-  public final boolean canExecuteTask(RunConfiguration configuration, Task task) {
-    return isValidConfiguration(configuration);
-  }
-
-  @Nullable
-  @Override
-  public Task createTask(RunConfiguration runConfiguration) {
-    if (isValidConfiguration(runConfiguration)) {
-      return new Task();
-    }
-    return null;
-  }
-
-  private static boolean isValidConfiguration(RunConfiguration runConfiguration) {
-    return runConfiguration instanceof BlazeIntellijPluginConfiguration;
-  }
-
-  @Override
-  public final boolean executeTask(
-    final DataContext dataContext,
-    final RunConfiguration configuration,
-    final ExecutionEnvironment env,
-    Task task) {
-    if (!canExecuteTask(configuration, task)) {
-      return false;
-    }
-    boolean suppressConsole = BlazeUserSettings.getInstance().getSuppressConsoleForRunAction();
-    return Scope.root(context -> {
-      context
-        .push(new ExperimentScope())
-        .push(new IssuesScope(project))
-        .push(new BlazeConsoleScope.Builder(project).setSuppressConsole(suppressConsole).build())
-      ;
-
-      final ProjectViewSet projectViewSet = ProjectViewManager.getInstance(project).getProjectViewSet();
-      if (projectViewSet == null) {
-        IssueOutput.error("Could not load project view. Please resync project").submit(context);
-        return false;
-      }
-
-      final ScopedTask buildTask = new ScopedTask(context) {
-        @Override
-        protected void execute(BlazeContext context) {
-          BlazeIntellijPluginConfiguration config = (BlazeIntellijPluginConfiguration) configuration;
-          BlazeCommand command = config.buildBlazeCommand(project, projectViewSet);
-          if (command == null || context.hasErrors() || context.isCancelled()) {
-            return;
-          }
-          SaveUtil.saveAllFiles();
-          WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProject(project);
-          int retVal = ExternalTask.builder(workspaceRoot, command)
-            .context(context)
-            .stderr(LineProcessingOutputStream.of(new IssueOutputLineProcessor(project, context, workspaceRoot)))
-            .build()
-            .run(new LoggedTimingScope(project, Action.BLAZE_BUILD));
-          if (retVal != 0) {
-            context.setHasError();
-          }
-          LocalFileSystem.getInstance().refresh(true);
-        }
-      };
-
-      ListenableFuture<Void> buildFuture = BlazeExecutor.submitTask(
-        project,
-        "Executing blaze build for IntelliJ plugin jar",
-        buildTask
-      );
-
-      try {
-        Futures.get(buildFuture, ExecutionException.class);
-      }
-      catch (ExecutionException e) {
-        context.setHasError();
-      }
-      catch (CancellationException e) {
-        context.setCancelled();
-      }
-
-      if (context.hasErrors() || context.isCancelled()) {
-        return false;
-      }
-      return true;
-    });
-  }
-
-}
diff --git a/blaze-plugin-dev/src/com/google/idea/blaze/plugin/run/IdeaJdkHelper.java b/blaze-plugin-dev/src/com/google/idea/blaze/plugin/run/IdeaJdkHelper.java
deleted file mode 100644
index 3b9b152..0000000
--- a/blaze-plugin-dev/src/com/google/idea/blaze/plugin/run/IdeaJdkHelper.java
+++ /dev/null
@@ -1,67 +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.plugin.run;
-
-import com.intellij.openapi.projectRoots.Sdk;
-import com.intellij.openapi.projectRoots.SdkTypeId;
-import org.jetbrains.idea.devkit.projectRoots.IdeaJdk;
-import org.jetbrains.idea.devkit.projectRoots.IntelliJPlatformProduct;
-import org.jetbrains.idea.devkit.projectRoots.Sandbox;
-import org.jetbrains.idea.devkit.run.IdeaLicenseHelper;
-
-import javax.annotation.Nullable;
-
-/**
- * Contains all dependencies on devkit (not included in plugin SDK)
- */
-public class IdeaJdkHelper {
-
-  public static boolean isIdeaJdk(@Nullable Sdk sdk) {
-    return sdk != null && isIdeaJdkType(sdk.getSdkType());
-  }
-
-  public static boolean isIdeaJdkType(SdkTypeId type) {
-    // return IdeaJdk.getInstance().equals(type);
-
-    // gross hack: SdkType.findInstance uses Class object equality, and for IJwB
-    // there are currently two copies of devkit classes...
-    return type.getName().equals("IDEA JDK");
-  }
-
-  @Nullable
-  public static String getBuildNumber(Sdk sdk) {
-    return IdeaJdk.getBuildNumber(sdk.getHomePath());
-  }
-
-  public static void copyIDEALicense(final String sandboxHome) {
-    IdeaLicenseHelper.copyIDEALicense(sandboxHome);
-  }
-
-  /**
-   * @throws RuntimeException if input Sdk is not an IdeaJdk
-   */
-  public static String getSandboxHome(Sdk sdk) {
-    if (!isIdeaJdk(sdk))
-      throw new RuntimeException("Invalid SDK type: " + sdk.getSdkType());
-    return ((Sandbox) sdk.getSdkAdditionalData()).getSandboxHome();
-  }
-
-  @Nullable
-  public static String getPlatformPrefix(String buildNumber) {
-    return IntelliJPlatformProduct.fromBuildNumber(buildNumber).getPlatformPrefix();
-  }
-
-}
diff --git a/blaze-plugin-dev/src/com/google/idea/blaze/plugin/run/IntellijWithPluginClasspathHelper.java b/blaze-plugin-dev/src/com/google/idea/blaze/plugin/run/IntellijWithPluginClasspathHelper.java
deleted file mode 100644
index 7d88630..0000000
--- a/blaze-plugin-dev/src/com/google/idea/blaze/plugin/run/IntellijWithPluginClasspathHelper.java
+++ /dev/null
@@ -1,88 +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.plugin.run;
-
-import com.google.common.collect.Lists;
-import com.intellij.execution.configurations.JavaParameters;
-import com.intellij.execution.configurations.ParametersList;
-import com.intellij.openapi.projectRoots.JavaSdkType;
-import com.intellij.openapi.projectRoots.Sdk;
-import com.intellij.openapi.util.SystemInfo;
-import com.intellij.util.PathsList;
-import org.jetbrains.annotations.NonNls;
-
-import java.io.File;
-import java.util.List;
-
-/**
- * Boilerplate for running an IJ application with an additional plugin,
- * copied from org.jetbrains.idea.devkit.run.PluginRunConfiguration
- */
-public class IntellijWithPluginClasspathHelper {
-
-  private static final List<String> IJ_LIBRARIES = Lists.newArrayList(
-    "log4j.jar",
-    "trove4j.jar",
-    "openapi.jar",
-    "util.jar",
-    "extensions.jar",
-    "bootstrap.jar",
-    "idea.jar",
-    "idea_rt.jar"
-  );
-
-  private static void addIntellijLibraries(JavaParameters params,
-                                           Sdk ideaJdk) {
-    @NonNls String libPath = ideaJdk.getHomePath() + File.separator + "lib";
-    PathsList list = params.getClassPath();
-    for (String lib : IJ_LIBRARIES) {
-      list.addFirst(libPath + File.separator + lib);
-    }
-    list.addFirst(((JavaSdkType) ideaJdk.getSdkType()).getToolsPath(ideaJdk));
-  }
-
-  public static void addRequiredVmParams(JavaParameters params,
-                                         Sdk ideaJdk) {
-    String canonicalSandbox = IdeaJdkHelper.getSandboxHome(ideaJdk);
-    ParametersList vm = params.getVMParametersList();
-
-    @NonNls String libPath = ideaJdk.getHomePath() + File.separator + "lib";
-    vm.add("-Xbootclasspath/a:" + libPath + File.separator + "boot.jar");
-
-    vm.defineProperty("idea.config.path", canonicalSandbox + File.separator + "config");
-    vm.defineProperty("idea.system.path", canonicalSandbox + File.separator + "system");
-    vm.defineProperty("idea.plugins.path", canonicalSandbox + File.separator + "plugins");
-    vm.defineProperty("idea.classpath.index.enabled", "false");
-
-    if (SystemInfo.isMac) {
-      vm.defineProperty("idea.smooth.progress", "false");
-      vm.defineProperty("apple.laf.useScreenMenuBar", "true");
-    }
-
-    if (SystemInfo.isXWindow) {
-      if (!vm.hasProperty("sun.awt.disablegrab")) {
-        vm.defineProperty("sun.awt.disablegrab", "true"); // See http://devnet.jetbrains.net/docs/DOC-1142
-      }
-    }
-
-    params.setWorkingDirectory(ideaJdk.getHomePath() + File.separator + "bin" + File.separator);
-    params.setJdk(ideaJdk);
-
-    addIntellijLibraries(params, ideaJdk);
-
-    params.setMainClass("com.intellij.idea.Main");
-  }
-}
diff --git a/blaze-plugin-dev/src/com/google/idea/blaze/plugin/sync/IntellijPluginSyncPlugin.java b/blaze-plugin-dev/src/com/google/idea/blaze/plugin/sync/IntellijPluginSyncPlugin.java
deleted file mode 100644
index bf3d14b..0000000
--- a/blaze-plugin-dev/src/com/google/idea/blaze/plugin/sync/IntellijPluginSyncPlugin.java
+++ /dev/null
@@ -1,84 +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.plugin.sync;
-
-import com.google.common.collect.ImmutableSet;
-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.WorkspaceType;
-import com.google.idea.blaze.base.projectview.ProjectViewSet;
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.google.idea.blaze.base.sync.BlazeSyncPlugin;
-import com.google.idea.blaze.java.sync.JavaLanguageLevelHelper;
-import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.module.ModuleType;
-import com.intellij.openapi.module.StdModuleTypes;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.roots.LanguageLevelProjectExtension;
-import com.intellij.pom.java.LanguageLevel;
-import com.intellij.util.ui.UIUtil;
-
-import javax.annotation.Nullable;
-import java.util.Set;
-
-/**
- * Development environment support for intellij plugin projects.
- * Prevents the project SDK being reset during sync
- */
-public class IntellijPluginSyncPlugin extends BlazeSyncPlugin.Adapter {
-
-  @Nullable
-  @Override
-  public WorkspaceType getDefaultWorkspaceType() {
-    return WorkspaceType.INTELLIJ_PLUGIN;
-  }
-
-  @Nullable
-  @Override
-  public ModuleType getWorkspaceModuleType(WorkspaceType workspaceType) {
-    if (workspaceType == WorkspaceType.INTELLIJ_PLUGIN) {
-      return StdModuleTypes.JAVA;
-    }
-    return null;
-  }
-
-  @Override
-  public Set<LanguageClass> getSupportedLanguagesInWorkspace(WorkspaceType workspaceType) {
-    if (workspaceType == WorkspaceType.INTELLIJ_PLUGIN) {
-      return ImmutableSet.of(LanguageClass.JAVA);
-    }
-    return ImmutableSet.of();
-  }
-
-  @Override
-  public void updateSdk(Project project,
-                        BlazeContext context,
-                        ProjectViewSet projectViewSet,
-                        BlazeProjectData blazeProjectData) {
-    if (!blazeProjectData.workspaceLanguageSettings.isWorkspaceType(WorkspaceType.INTELLIJ_PLUGIN)) {
-      return;
-    }
-
-    LanguageLevel javaLanguageLevel = JavaLanguageLevelHelper
-      .getJavaLanguageLevel(projectViewSet, blazeProjectData, LanguageLevel.JDK_1_7);
-
-    // Leave the SDK, but set the language level
-    UIUtil.invokeAndWaitIfNeeded((Runnable)() -> ApplicationManager.getApplication().runWriteAction(() -> {
-      LanguageLevelProjectExtension ext = LanguageLevelProjectExtension.getInstance(project);
-      ext.setLanguageLevel(javaLanguageLevel);
-    }));
-  }
-}
diff --git a/blaze-plugin-dev/tests/integrationtests/com/google/idea/blaze/plugin/sync/PluginDevSyncTest.java b/blaze-plugin-dev/tests/integrationtests/com/google/idea/blaze/plugin/sync/PluginDevSyncTest.java
deleted file mode 100644
index 941947b..0000000
--- a/blaze-plugin-dev/tests/integrationtests/com/google/idea/blaze/plugin/sync/PluginDevSyncTest.java
+++ /dev/null
@@ -1,94 +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.plugin.sync;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.ideinfo.RuleMapBuilder;
-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.sync.BlazeSyncIntegrationTestCase;
-import com.google.idea.blaze.base.sync.actions.IncrementalSyncProjectAction;
-import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
-import com.google.idea.blaze.plugin.run.BlazeIntellijPluginConfiguration;
-import com.intellij.execution.RunManager;
-import com.intellij.execution.configurations.RunConfiguration;
-
-import java.util.List;
-
-import static com.google.common.truth.Truth.assertThat;
-
-/**
- * Plugin-dev specific sync integration test.
- */
-public class PluginDevSyncTest extends BlazeSyncIntegrationTestCase {
-  
-  public void ignore_testRunConfigurationCreatedDuringSync() throws Exception {
-    setProjectView(
-      "directories:",
-      "  java/com/google",
-      "targets:",
-      "  //java/com/google:lib",
-      "  //java/com/google:plugin",
-      "workspace_type: intellij_plugin"
-    );
-
-    createFile(
-      "java/com/google/ClassWithUniqueName1.java",
-      "package com.google;",
-      "public class ClassWithUniqueName1 {}"
-    );
-
-    createFile(
-      "java/com/google/ClassWithUniqueName2.java",
-      "package com.google;",
-      "public class ClassWithUniqueName2 {}"
-    );
-
-    ImmutableMap<Label, RuleIdeInfo> ruleMap = RuleMapBuilder.builder()
-      .addRule(RuleIdeInfo.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()
-                 .setBuildFile(sourceRoot("java/com/google/BUILD"))
-                 .setLabel("//java/com/google:plugin")
-                 .setKind("java_import")
-                 .addTag("intellij-plugin")
-      )
-      .build();
-
-    setRuleMap(ruleMap);
-
-    runBlazeSync(IncrementalSyncProjectAction.manualSyncParams);
-
-    assertNoErrors();
-
-    BlazeProjectData blazeProjectData = BlazeProjectDataManager.getInstance(getProject()).getBlazeProjectData();
-    assertThat(blazeProjectData).isNotNull();
-    assertThat(blazeProjectData.ruleMap).isEqualTo(ruleMap);
-    assertThat(blazeProjectData.workspaceLanguageSettings.getWorkspaceType())
-      .isEqualTo(WorkspaceType.INTELLIJ_PLUGIN);
-
-    List<RunConfiguration> runConfigs = RunManager.getInstance(getProject()).getAllConfigurationsList();
-    assertThat(runConfigs).hasSize(1);
-    assertThat(runConfigs.get(0)).isInstanceOf(BlazeIntellijPluginConfiguration.class);
-  }
-
-}
diff --git a/build_defs/BUILD b/build_defs/BUILD
index 47e9af1..0988550 100644
--- a/build_defs/BUILD
+++ b/build_defs/BUILD
@@ -1 +1,5 @@
-package(default_visibility=["//visibility:public"])
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache 2.0
+
+exports_files(["LICENSE"])
diff --git a/build_defs/build_defs.bzl b/build_defs/build_defs.bzl
index ada1bb5..974b857 100644
--- a/build_defs/build_defs.bzl
+++ b/build_defs/build_defs.bzl
@@ -1,65 +1,136 @@
-# 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.
-
-""" Description: Custom build macros for plugin.xml handling """
-#
+"""Custom build macros for plugin.xml handling.
+"""
 
 load("//build_defs/shared:build_defs.bzl",
      "merged_plugin_xml_impl",
      "stamped_plugin_xml_impl",
-     "intellij_plugin_impl")
+     "product_build_txt_impl",
+     "intellij_plugin_impl",
+     "plugin_bundle_impl")
 
-def merged_plugin_xml(name, srcs):
-    """Merges N plugin.xml files together
-    """
-    merged_plugin_xml_impl(
-        name = name,
-        srcs = srcs,
-        merge_tool = "//build_defs/shared:merge_xml",
-    )
+def merged_plugin_xml(name, srcs, **kwargs):
+  """Merges N plugin.xml files together."""
+  merged_plugin_xml_impl(
+      name = name,
+      srcs = srcs,
+      merge_tool = "//build_defs/shared:merge_xml",
+      **kwargs)
 
-def stamped_plugin_xml(name, plugin_xml,
+def stamped_plugin_xml(name,
+                       plugin_xml,
+                       plugin_id=None,
+                       plugin_name=None,
                        stamp_since_build=False,
                        stamp_until_build=False,
+                       include_product_code_in_stamp=False,
                        version_file=None,
                        changelog_file=None,
-                       include_product_code_in_stamp=False):
-    """Stamps a plugin xml file with the IJ build number.
-      stamp_since_build -- Add build number to idea-version since-build.
-      stamp_until_build -- Add build number to idea-version until-build.
-      version_file -- A file with the version number to be included.
-      changelog_file -- A file with changelog to be included.
-      include_product_code_in_stamp -- Whether the product code (eg. "IC")
-        is included in since-build and until-build.
-    """
-    stamped_plugin_xml_impl(
-        name = name,
-        build_txt = "//intellij-platform-sdk:build_number",
-        stamp_tool = "//build_defs/shared:stamp_plugin_xml",
-        plugin_xml = plugin_xml,
-        stamp_since_build = stamp_since_build,
-        version_file = version_file,
-        changelog_file = changelog_file,
-        include_product_code_in_stamp = include_product_code_in_stamp
-    )
+                       **kwargs):
+  """Stamps a plugin xml file with the IJ build number.
 
-def intellij_plugin(name, plugin_xml, deps):
-    """ Creates an intellij plugin from the given deps and plugin.xml """
-    intellij_plugin_impl(
-        name = name,
-        plugin_xml = plugin_xml,
-        zip_tool = "//tools/zip",
-        deps = deps,
-    )
+  Args:
+    name: name of this target
+    plugin_xml: target plugin_xml to stamp
+    plugin_id: the plugin ID to stamp
+    plugin_name: the plugin name to stamp
+    stamp_since_build: Add build number to idea-version since-build.
+    stamp_until_build: Add build number to idea-version until-build.
+    include_product_code_in_stamp: Whether the product code (eg. "IC")
+      is included in since-build and until-build.
+    version_file: A file with the version number to be included.
+    changelog_file: A file with changelog to be included.
+    **kwargs: Any additional arguments to pass to the final target.
+  """
+  stamped_plugin_xml_impl(
+      name = name,
+      application_info_jar = "//intellij_platform_sdk:application_info_jar",
+      application_info_name = "//intellij_platform_sdk:application_info_name",
+      plugin_id = plugin_id,
+      plugin_name = plugin_name,
+      stamp_tool = "//build_defs/shared:stamp_plugin_xml",
+      plugin_xml = plugin_xml,
+      stamp_since_build = stamp_since_build,
+      stamp_until_build = stamp_until_build,
+      include_product_code_in_stamp = include_product_code_in_stamp,
+      version_file = version_file,
+      changelog_file = changelog_file,
+      **kwargs)
 
+def product_build_txt(name, **kwargs):
+  """Produces a product-build.txt file with the build number."""
+  product_build_txt_impl(
+      name = name,
+      application_info_jar = "//intellij_platform_sdk:application_info_jar",
+      application_info_name = "//intellij_platform_sdk:application_info_name",
+      product_build_txt_tool = "//build_defs/shared:product_build_txt",
+      **kwargs)
+
+
+def intellij_plugin(name, plugin_xml, deps, meta_inf_files=[], **kwargs):
+  """Creates an intellij plugin from the given deps and plugin.xml."""
+  intellij_plugin_impl(
+      name = name,
+      plugin_xml = plugin_xml,
+      zip_tool = "//third_party:zip",
+      deps = deps,
+      meta_inf_files = meta_inf_files,
+      **kwargs)
+
+def repackage_jar(name, src_rule, out,
+                  rules = [
+                      "com.google.common.** com.google.repackaged.common.@1",
+                      "com.google.gson.** com.google.repackaged.gson.@1",
+                      "com.google.protobuf.** com.google.repackaged.protobuf.@1",
+                      "com.google.thirdparty.** com.google.repackaged.thirdparty.@1",
+                  ], **kwargs):
+  """Repackages classes in a jar, to avoid collisions in the classpath.
+
+  Args:
+    name: the name of this target
+    src_rule: a java_binary label with the create_executable attribute set to 0
+    out: the output jarfile
+    rules: the rules to apply in the repackaging
+        Only repackage some of com.google.** from proto_deps.jar.
+        We do not repackage:
+        - com.google.net.** because that has JNI files which use
+          FindClass(JNIEnv *, const char *) with hard-coded native string
+          literals that jarjar doesn't rewrite.
+        - com.google.errorprone packages (rewriting will throw off blaze build).
+    **kwargs: Any additional arguments to pass to the final target.
+  """
+  repackage_tool = "@jarjar//jar"
+  deploy_jar = "{src_rule}_deploy.jar".format(src_rule=src_rule)
+  script_lines = []
+  script_lines.append("echo >> /tmp/repackaged_rule.txt")
+  for rule in rules:
+    script_lines.append("echo 'rule {rule}' >> /tmp/repackaged_rule.txt;".format(rule=rule))
+  script_lines.append(" ".join([
+      "$(location {repackage_tool})",
+      "process /tmp/repackaged_rule.txt",
+      "$(location {deploy_jar})",
+      "$@",
+  ]).format(
+      repackage_tool = repackage_tool,
+      deploy_jar = deploy_jar,
+  ))
+  genrule_name = name + "_gen"
+  native.genrule(
+      name = genrule_name,
+      srcs = [deploy_jar],
+      outs = [out],
+      tools = [repackage_tool],
+      cmd = "\n".join(script_lines),
+  )
+  native.java_import(
+      name = name,
+      jars = [out],
+      **kwargs)
+
+def plugin_bundle(name, plugins):
+  """Communicates to IJwB a set of plugins which should be loaded together in a run configuration.
+
+  Args:
+    name: the name of this target
+    plugins: the 'intellij_plugin' targets to be bundled
+  """
+  plugin_bundle_impl(name, plugins)
diff --git a/build_defs/shared/BUILD b/build_defs/shared/BUILD
index 60a6eaf..bb4815c 100644
--- a/build_defs/shared/BUILD
+++ b/build_defs/shared/BUILD
@@ -4,6 +4,8 @@
 
 package(default_visibility = ["//visibility:public"])
 
+licenses(["notice"])  # Apache 2.0
+
 py_binary(
     name = "merge_xml",
     srcs = ["merge_xml.py"],
@@ -13,3 +15,8 @@
     name = "stamp_plugin_xml",
     srcs = ["stamp_plugin_xml.py"],
 )
+
+py_binary(
+    name = "product_build_txt",
+    srcs = ["product_build_txt.py"],
+)
diff --git a/build_defs/shared/build_defs.bzl b/build_defs/shared/build_defs.bzl
index 4d5712b..5c282ee 100644
--- a/build_defs/shared/build_defs.bzl
+++ b/build_defs/shared/build_defs.bzl
@@ -1,21 +1,7 @@
-# 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.
-
 """Custom build macros for IntelliJ plugin handling.
 """
 
-def merged_plugin_xml_impl(name, srcs, merge_tool):
+def merged_plugin_xml_impl(name, srcs, merge_tool, **kwargs):
   """Merges N plugin.xml files together."""
   native.genrule(
       name = name,
@@ -25,43 +11,58 @@
           merge_tool=merge_tool,
       ),
       tools = [merge_tool],
-  )
+      **kwargs)
 
 def _optstr(name, value):
   return ("--" + name) if value else ""
 
 def stamped_plugin_xml_impl(name,
                             plugin_xml,
-                            build_txt,
+                            application_info_jar,
+                            application_info_name,
                             stamp_tool,
+                            plugin_id = None,
+                            plugin_name = None,
                             stamp_since_build=False,
                             stamp_until_build=False,
+                            include_product_code_in_stamp=False,
                             version_file=None,
                             changelog_file=None,
-                            include_product_code_in_stamp=False):
+                            **kwargs):
   """Stamps a plugin xml file with the IJ build number.
 
   Args:
-    name: name of this rule
+    name: name of this target
     plugin_xml: target plugin_xml to stamp
-    build_txt: the file containing the build number
+    application_info_jar: the jar containing the application info
+    application_info_name: a file with the name of the application info
     stamp_tool: the tool to use to stamp the version
+    plugin_id: the plugin ID to stamp
+    plugin_name: the plugin name to stamp
     stamp_since_build: Add build number to idea-version since-build.
     stamp_until_build: Add build number to idea-version until-build.
-    version_file: A file with the version number to be included.
-    changelog_file: A file with the changelog to be included.
     include_product_code_in_stamp: Whether the product code (eg. "IC")
         is included in since-build and until-build.
+    version_file: A file with the version number to be included.
+    changelog_file: A file with the changelog to be included.
+    **kwargs: Any additional arguments to pass to the final target.
   """
   args = [
       "./$(location {stamp_tool})",
       "--plugin_xml=$(location {plugin_xml})",
-      "--build_txt=$(location {build_txt})",
+      "--application_info_jar=$(location {application_info_jar})",
+      "--application_info_name=$(location {application_info_name})",
       "{stamp_since_build}",
       "{stamp_until_build}",
       "{include_product_code_in_stamp}",
   ]
-  srcs = [plugin_xml, build_txt]
+  srcs = [plugin_xml, application_info_jar, application_info_name]
+
+  if plugin_id:
+    args.append("--plugin_id=%s" % plugin_id)
+
+  if plugin_name:
+    args.append("--plugin_name='%s'" % plugin_name)
 
   if version_file:
     args.append("--version_file=$(location {version_file})")
@@ -72,19 +73,20 @@
     srcs.append(changelog_file)
 
   cmd = " ".join(args).format(
-          plugin_xml=plugin_xml,
-          build_txt=build_txt,
-          stamp_tool=stamp_tool,
-          stamp_since_build=_optstr("stamp_since_build",
-                                    stamp_since_build),
-          stamp_until_build=_optstr("stamp_until_build",
-                                    stamp_until_build),
-          version_file=version_file,
-          changelog_file=changelog_file,
-          include_product_code_in_stamp=_optstr(
-              "include_product_code_in_stamp",
-              include_product_code_in_stamp)
-      ) + "> $@"
+      plugin_xml=plugin_xml,
+      application_info_jar=application_info_jar,
+      application_info_name=application_info_name,
+      stamp_tool=stamp_tool,
+      stamp_since_build=_optstr("stamp_since_build",
+                                stamp_since_build),
+      stamp_until_build=_optstr("stamp_until_build",
+                                stamp_until_build),
+      include_product_code_in_stamp=_optstr(
+          "include_product_code_in_stamp",
+          include_product_code_in_stamp),
+      version_file=version_file,
+      changelog_file=changelog_file,
+  ) + "> $@"
 
   native.genrule(
       name = name,
@@ -92,9 +94,46 @@
       outs = [name + ".xml"],
       cmd = cmd,
       tools = [stamp_tool],
-  )
+      **kwargs)
 
-def intellij_plugin_impl(name, plugin_xml, zip_tool, deps):
+def product_build_txt_impl(name,
+                           product_build_txt_tool,
+                           application_info_jar,
+                           application_info_name,
+                           **kwargs):
+  """Produces a product-build.txt file with the build number.
+
+  Args:
+    name: name of this target
+    product_build_txt_tool: the product build tool
+    application_info_jar: the jar containing the application info
+    application_info_name: a file with the name of the application info
+    **kwargs: Any additional arguments to pass to the final target.
+  """
+  args = [
+      "./$(location {product_build_txt_tool})",
+      "--application_info_jar=$(location {application_info_jar})",
+      "--application_info_name=$(location {application_info_name})",
+  ]
+  cmd = " ".join(args).format(
+      application_info_jar=application_info_jar,
+      application_info_name=application_info_name,
+      product_build_txt_tool=product_build_txt_tool,
+  ) + "> $@"
+  native.genrule(
+      name = name,
+      srcs = [application_info_jar, application_info_name],
+      outs = ["product-build.txt"],
+      cmd = cmd,
+      tools = [product_build_txt_tool],
+      **kwargs)
+
+def intellij_plugin_impl(name,
+                         deps,
+                         plugin_xml,
+                         zip_tool,
+                         meta_inf_files=[],
+                         **kwargs):
   """Creates an intellij plugin from the given deps and plugin.xml."""
   binary_name = name + "_binary"
   deploy_jar = binary_name + "_deploy.jar"
@@ -103,22 +142,34 @@
       runtime_deps = deps,
       create_executable = 0,
   )
+  cmd = [
+      "cp $(location {deploy_jar}) $@".format(deploy_jar=deploy_jar),
+      "chmod +w $@",
+      "mkdir -p META-INF",
+      "cp $(location {plugin_xml}) META-INF/plugin.xml".format(plugin_xml=plugin_xml),
+      "$(location {zip_tool}) -u $@ META-INF/plugin.xml >/dev/null".format(zip_tool=zip_tool),
+  ]
+  srcs = [
+      plugin_xml,
+      deploy_jar,
+  ]
+
+  for meta_inf_file in meta_inf_files:
+    cmd.append("cp $(location {meta_inf_file}) META-INF/$$(basename $(location {meta_inf_file}))".format(
+        meta_inf_file=meta_inf_file,
+    ))
+    cmd.append("$(location {zip_tool}) -u $@ META-INF/$$(basename $(location {meta_inf_file})) >/dev/null".format(
+        zip_tool=zip_tool,
+        meta_inf_file=meta_inf_file,
+    ))
+    srcs.append(meta_inf_file)
+
   native.genrule(
       name = name + "_genrule",
-      srcs = [plugin_xml, deploy_jar],
+      srcs = srcs,
       tools = [zip_tool],
       outs = [name + ".jar"],
-      cmd = " ; ".join([
-          "cp $(location {deploy_jar}) $@",
-          "chmod +w $@",
-          "mkdir -p META-INF",
-          "cp $(location {plugin_xml}) META-INF/plugin.xml",
-          "$(location {zip_tool}) -u $@ META-INF/plugin.xml >/dev/null",
-      ]).format(
-          deploy_jar=deploy_jar,
-          plugin_xml=plugin_xml,
-          zip_tool=zip_tool,
-      ),
+      cmd = " ; ".join(cmd),
   )
 
   # included (with tag) as a hack so that IJwB can recognize this is an intellij plugin
@@ -126,6 +177,17 @@
       name = name,
       jars = [name + ".jar"],
       tags = ["intellij-plugin"],
-      visibility = ["//visibility:public"],
-  )
+      **kwargs)
 
+def plugin_bundle_impl(name, plugins):
+  """Communicates to IJwB a set of plugins which should be loaded together in a run configuration.
+
+  Args:
+    name: the name of this target
+    plugins: the 'intellij_plugin' targets to be bundled
+  """
+  native.java_library(
+      name = name,
+      exports = plugins,
+      tags = ["intellij-plugin-bundle"],
+  )
diff --git a/build_defs/shared/merge_xml.py b/build_defs/shared/merge_xml.py
index d7332f6..b5840e6 100755
--- a/build_defs/shared/merge_xml.py
+++ b/build_defs/shared/merge_xml.py
@@ -1,17 +1,3 @@
-# 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.
-
 """Merges multiple xml files with the same top element tags into a single file.
 """
 
@@ -35,9 +21,9 @@
   file_dom = parse(filepath)
 
   if file_dom.documentElement.tagName != tree.documentElement.tagName:
-    raise RuntimeError("Incompatible top-level tags: '%s' vs. '%s'"
-                       % (file_dom.documentElement.tagName,
-                          tree.documentElement.tagName))
+    raise RuntimeError("Incompatible top-level tags: '%s' vs. '%s'" %
+                       (file_dom.documentElement.tagName,
+                        tree.documentElement.tagName))
 
   for node in file_dom.documentElement.childNodes:
     tree.documentElement.appendChild(tree.importNode(node, True))
diff --git a/build_defs/shared/product_build_txt.py b/build_defs/shared/product_build_txt.py
new file mode 100755
index 0000000..f7ac953
--- /dev/null
+++ b/build_defs/shared/product_build_txt.py
@@ -0,0 +1,72 @@
+"""Produces a product-build.txt with the product build."""
+
+import argparse
+import re
+from xml.dom.minidom import parseString
+import zipfile
+
+parser = argparse.ArgumentParser()
+
+parser.add_argument(
+    "--application_info_jar",
+    help="The jar file containing the application info xml",
+    required=True,)
+parser.add_argument(
+    "--application_info_name",
+    help="A .txt file containing the application info xml name",
+    required=True,)
+
+
+def _parse_build_number(build_number):
+  """Parses the build number.
+
+  Args:
+    build_number: The build number as text.
+  Returns:
+    build_number, build_number_without_product_code.
+  Raises:
+    ValueError: if the build number is invalid.
+  """
+  match = re.match(r"^([A-Z]+-)?([0-9]+)(\.[0-9]+)?", build_number)
+  if match is None:
+    raise ValueError("Invalid build number: " + build_number)
+
+  build_number = match.group(1) + match.group(2) + match.group(3)
+  build_number_without_product_code = match.group(2) + match.group(3)
+  return build_number, build_number_without_product_code
+
+
+def main():
+
+  args = parser.parse_args()
+
+  with open(args.application_info_name) as f:
+    application_info_name = f.read().strip()
+
+  with zipfile.ZipFile(args.application_info_jar, "r") as zf:
+    try:
+      data = zf.read(application_info_name)
+    except:
+      raise ValueError("Could not read application info file: " +
+                       application_info_name)
+    component = parseString(data)
+
+    build_elements = component.getElementsByTagName("build")
+    if not build_elements:
+      raise ValueError("Could not find <build> element.")
+    if len(build_elements) > 1:
+      raise ValueError("Ambiguous <build> element.")
+    build_element = build_elements[0]
+    attrs = build_element.attributes
+    build_number_attr = attrs.get("number")
+
+  if not build_number_attr:
+    raise ValueError("Could not find build number in application info")
+
+  build_number, _ = _parse_build_number(build_number_attr.value)
+
+  print build_number
+
+
+if __name__ == "__main__":
+  main()
diff --git a/build_defs/shared/stamp_plugin_xml.py b/build_defs/shared/stamp_plugin_xml.py
index 96c384a..b4fdfb6 100755
--- a/build_defs/shared/stamp_plugin_xml.py
+++ b/build_defs/shared/stamp_plugin_xml.py
@@ -1,22 +1,10 @@
-# 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.
-
 """Stamps a plugin xml with build information."""
 
 import argparse
 import re
 from xml.dom.minidom import parse
+from xml.dom.minidom import parseString
+import zipfile
 
 parser = argparse.ArgumentParser()
 
@@ -26,8 +14,13 @@
     required=True,
 )
 parser.add_argument(
-    "--build_txt",
-    help="The build.txt file containing the build number, e.g. IC-144.1818",
+    "--application_info_jar",
+    help="The jar file containing the application info xml",
+    required=True,
+)
+parser.add_argument(
+    "--application_info_name",
+    help="A .txt file containing the application info xml name",
     required=True,
 )
 parser.add_argument(
@@ -41,6 +34,14 @@
     help="Stamp until-build with the build number",
 )
 parser.add_argument(
+    "--plugin_id",
+    help="plugin ID to stamp into the plugin.xml",
+)
+parser.add_argument(
+    "--plugin_name",
+    help="plugin name to stamp into the plugin.xml",
+)
+parser.add_argument(
     "--version_file",
     help="Version file to stamp into the plugin.xml",
 )
@@ -54,11 +55,30 @@
     help="Include the product code in the stamp",
 )
 
+
 def _read_changelog(changelog_file):
-  """Reads the changelog and transforms it into trivial HTML"""
+  """Reads the changelog and transforms it into trivial HTML."""
   with open(changelog_file) as f:
-    lines = ["<p>" + line + "</p>" for line in f.readlines()]
-    return "\n".join(lines)
+    return "\n".join("<p>" + line + "</p>" for line in f.readlines())
+
+
+def _parse_build_number(build_number):
+  """Parses the build number.
+
+  Args:
+    build_number: The build number as text.
+  Returns:
+    build_number, build_number_without_product_code.
+  Raises:
+    ValueError: if the build number is invalid.
+  """
+  match = re.match(r"^([A-Z]+-)?([0-9]+)(\.[0-9]+)?", build_number)
+  if match is None:
+    raise ValueError("Invalid build number: " + build_number)
+
+  build_number = match.group(1) + match.group(2) + match.group(3)
+  build_number_without_product_code = match.group(2) + match.group(3)
+  return build_number, build_number_without_product_code
 
 
 def main():
@@ -67,20 +87,40 @@
 
   dom = parse(args.plugin_xml)
 
-  with open(args.build_txt) as f:
-    build_number = f.read()
+  with open(args.application_info_name) as f:
+    application_info_name = f.read().strip()
+
+  with zipfile.ZipFile(args.application_info_jar, "r") as zf:
+    try:
+      data = zf.read(application_info_name)
+    except:
+      raise ValueError("Could not read application info file: " +
+                       application_info_name)
+    component = parseString(data)
+
+    build_elements = component.getElementsByTagName("build")
+    if not build_elements:
+      raise ValueError("Could not find <build> element.")
+    if len(build_elements) > 1:
+      raise ValueError("Ambiguous <build> element.")
+    build_element = build_elements[0]
+
+    attrs = build_element.attributes
+    if attrs.has_key("apiVersion"):
+      api_version_attr = attrs.get("apiVersion")
+    else:
+      api_version_attr = attrs.get("number")
+
+  if not api_version_attr:
+    raise ValueError("Could not find api version in application info")
+
+  api_version, api_version_without_product_code = _parse_build_number(
+      api_version_attr.value)
 
   new_elements = []
 
   idea_plugin = dom.documentElement
 
-  match = re.match(r"^([A-Z]+-)?([0-9]+)(\.[0-9]+)?", build_number)
-  if match is None:
-    raise ValueError("Invalid build number: " + build_number)
-
-  build_number = match.group(1) + match.group(2) + match.group(3)
-  build_number_without_product_code = match.group(2) + match.group(3)
-
   version_element = None
   version_elements = idea_plugin.getElementsByTagName("version")
   if len(version_elements) > 1:
@@ -103,9 +143,9 @@
     if idea_plugin.getElementsByTagName("idea-version"):
       raise ValueError("idea-version element already present")
 
-    idea_version_build_element = (build_number
+    idea_version_build_element = (api_version
                                   if args.include_product_code_in_stamp else
-                                  build_number_without_product_code)
+                                  api_version_without_product_code)
 
     idea_version_element = dom.createElement("idea-version")
     new_elements.append(idea_version_element)
@@ -121,10 +161,27 @@
     if idea_plugin.getElementsByTagName("change-notes"):
       raise ValueError("change-notes element already in plugin.xml")
     changelog_element = dom.createElement("change-notes")
-    changelog_text = dom.createCDATASection(_read_changelog(args.changelog_file))
-    changelog_element.appendChild(changelog_text)
+    changelog_text = _read_changelog(args.changelog_file)
+    changelog_cdata = dom.createCDATASection(changelog_text)
+    changelog_element.appendChild(changelog_cdata)
     new_elements.append(changelog_element)
 
+  if args.plugin_id:
+    if idea_plugin.getElementsByTagName("id"):
+      raise ValueError("id element already in plugin.xml")
+    id_element = dom.createElement("id")
+    new_elements.append(id_element)
+    id_text = dom.createTextNode(args.plugin_id)
+    id_element.appendChild(id_text)
+
+  if args.plugin_name:
+    if idea_plugin.getElementsByTagName("name"):
+      raise ValueError("name element already in plugin.xml")
+    name_element = dom.createElement("name")
+    new_elements.append(name_element)
+    name_text = dom.createTextNode(args.plugin_name)
+    name_element.appendChild(name_text)
+
   for new_element in new_elements:
     idea_plugin.appendChild(new_element)
 
diff --git a/common/experiments/BUILD b/common/experiments/BUILD
new file mode 100644
index 0000000..b15de1f
--- /dev/null
+++ b/common/experiments/BUILD
@@ -0,0 +1,39 @@
+licenses(["notice"])  # Apache 2.0
+
+java_library(
+    name = "experiments",
+    srcs = glob(["src/**/*.java"]),
+    visibility = ["//visibility:public"],
+    deps = [
+        "//intellij_platform_sdk:plugin_api",
+        "@jsr305_annotations//jar",
+    ],
+)
+
+java_library(
+    name = "unit_test_utils",
+    testonly = 1,
+    srcs = glob(["tests/utils/unit/**/*.java"]),
+    visibility = ["//visibility:public"],
+    deps = [
+        ":experiments",
+        "//intellij_platform_sdk:plugin_api",
+        "@jsr305_annotations//jar",
+    ],
+)
+
+load(
+    "//intellij_test: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.common.experiments",
+    deps = [
+        ":experiments",
+        "//intellij_platform_sdk:plugin_api_for_tests",
+        "@junit//jar",
+    ],
+)
diff --git a/common/experiments/src/com/google/idea/common/experiments/BoolExperiment.java b/common/experiments/src/com/google/idea/common/experiments/BoolExperiment.java
new file mode 100644
index 0000000..b07019a
--- /dev/null
+++ b/common/experiments/src/com/google/idea/common/experiments/BoolExperiment.java
@@ -0,0 +1,30 @@
+/*
+ * 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.common.experiments;
+
+/** Boolean-valued experiment. */
+public class BoolExperiment extends Experiment {
+  private final boolean defaultValue;
+
+  public BoolExperiment(String key, boolean defaultValue) {
+    super(key);
+    this.defaultValue = defaultValue;
+  }
+
+  public boolean getValue() {
+    return ExperimentService.getInstance().getExperiment(key, defaultValue);
+  }
+}
diff --git a/common/experiments/src/com/google/idea/common/experiments/DevOverride.java b/common/experiments/src/com/google/idea/common/experiments/DevOverride.java
new file mode 100644
index 0000000..5f3b4ce
--- /dev/null
+++ b/common/experiments/src/com/google/idea/common/experiments/DevOverride.java
@@ -0,0 +1,30 @@
+/*
+ * 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.common.experiments;
+
+/** Developer override for string experiments. */
+public class DevOverride extends Experiment {
+  private final String defaultValue;
+
+  public DevOverride(String key, String defaultValue) {
+    super(key);
+    this.defaultValue = defaultValue;
+  }
+
+  public String getValue() {
+    return ExperimentService.getInstance().getExperimentString(key, defaultValue);
+  }
+}
diff --git a/common/experiments/src/com/google/idea/common/experiments/DeveloperFlag.java b/common/experiments/src/com/google/idea/common/experiments/DeveloperFlag.java
new file mode 100644
index 0000000..04e3790
--- /dev/null
+++ b/common/experiments/src/com/google/idea/common/experiments/DeveloperFlag.java
@@ -0,0 +1,25 @@
+/*
+ * 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.common.experiments;
+
+/**
+ * This is an experiment that always defaults to off. Use to document that this is for devs only.
+ */
+public class DeveloperFlag extends BoolExperiment {
+  public DeveloperFlag(String key) {
+    super(key, false);
+  }
+}
diff --git a/common/experiments/src/com/google/idea/common/experiments/Experiment.java b/common/experiments/src/com/google/idea/common/experiments/Experiment.java
new file mode 100644
index 0000000..135dee5
--- /dev/null
+++ b/common/experiments/src/com/google/idea/common/experiments/Experiment.java
@@ -0,0 +1,29 @@
+/*
+ * 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.common.experiments;
+
+/** Experiment class. */
+public abstract class Experiment {
+  protected final String key;
+
+  Experiment(String key) {
+    this.key = key;
+  }
+
+  public String getKey() {
+    return key;
+  }
+}
diff --git a/common/experiments/src/com/google/idea/common/experiments/ExperimentLoader.java b/common/experiments/src/com/google/idea/common/experiments/ExperimentLoader.java
new file mode 100644
index 0000000..a051872
--- /dev/null
+++ b/common/experiments/src/com/google/idea/common/experiments/ExperimentLoader.java
@@ -0,0 +1,31 @@
+/*
+ * 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.common.experiments;
+
+import java.util.Map;
+
+interface ExperimentLoader {
+
+  /**
+   * Load the map of experiment names to values. Experiment names must be hashed from their
+   * canonical value with SHA-512.
+   *
+   * @see HashingExperimentLoader
+   */
+  Map<String, String> getExperiments();
+
+  void initialize();
+}
diff --git a/common/experiments/src/com/google/idea/common/experiments/ExperimentService.java b/common/experiments/src/com/google/idea/common/experiments/ExperimentService.java
new file mode 100644
index 0000000..ebf3b4a
--- /dev/null
+++ b/common/experiments/src/com/google/idea/common/experiments/ExperimentService.java
@@ -0,0 +1,43 @@
+/*
+ * 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.common.experiments;
+
+import com.intellij.openapi.application.ApplicationManager;
+import javax.annotation.Nullable;
+
+/** Reads experiments. */
+public interface ExperimentService {
+
+  static ExperimentService getInstance() {
+    return ApplicationManager.getApplication().getComponent(ExperimentService.class);
+  }
+
+  /** Returns an experiment if it exists, else defaultValue */
+  boolean getExperiment(String key, boolean defaultValue);
+
+  /** Returns a string-valued experiment if it exists, else defaultValue. */
+  @Nullable
+  String getExperimentString(String key, @Nullable String defaultValue);
+
+  /** Returns an int-valued experiment if it exists, else defaultValue. */
+  int getExperimentInt(String key, int defaultValue);
+
+  /** Starts an experiment scope. During an experiment scope, experiments won't be reloaded. */
+  void startExperimentScope();
+
+  /** Ends an experiment scope. */
+  void endExperimentScope();
+}
diff --git a/common/experiments/src/com/google/idea/common/experiments/ExperimentServiceImpl.java b/common/experiments/src/com/google/idea/common/experiments/ExperimentServiceImpl.java
new file mode 100644
index 0000000..6b908c1
--- /dev/null
+++ b/common/experiments/src/com/google/idea/common/experiments/ExperimentServiceImpl.java
@@ -0,0 +1,120 @@
+/*
+ * 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.common.experiments;
+
+import static com.google.idea.common.experiments.ExperimentsUtil.hashExperimentName;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import com.intellij.openapi.components.ApplicationComponent;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.util.SystemProperties;
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
+
+/**
+ * An experiment service that delegates to {@link ExperimentLoader ExperimentLoaders}, in a specific
+ * order.
+ *
+ * <p>It will check system properties first, then an experiment file in the user's home directory,
+ * then finally all files specified by the system property blaze.experiments.file.
+ */
+public class ExperimentServiceImpl extends ApplicationComponent.Adapter
+    implements ExperimentService {
+  private static final Logger logger = Logger.getInstance(ExperimentServiceImpl.class);
+
+  private static final String USER_EXPERIMENT_OVERRIDES_FILE =
+      SystemProperties.getUserHome() + File.separator + ".intellij-experiments";
+
+  private final List<ExperimentLoader> services;
+  private Map<String, String> experiments;
+  private int experimentScopeCounter = 0;
+
+  public ExperimentServiceImpl(String pluginName) {
+    this(
+        new SystemPropertyExperimentLoader(),
+        new FileExperimentLoader(USER_EXPERIMENT_OVERRIDES_FILE),
+        new WebExperimentLoader(pluginName));
+  }
+
+  @VisibleForTesting
+  ExperimentServiceImpl(ExperimentLoader... loaders) {
+    services = ImmutableList.copyOf(loaders);
+  }
+
+  @Override
+  public void initComponent() {
+    services.forEach(ExperimentLoader::initialize);
+  }
+
+  @Override
+  public boolean getExperiment(String key, boolean defaultValue) {
+    String property = getExperiment(key);
+    return property != null ? property.equals("1") : defaultValue;
+  }
+
+  @Override
+  public String getExperimentString(String key, @Nullable String defaultValue) {
+    String property = getExperiment(key);
+    return property != null ? property : defaultValue;
+  }
+
+  @Override
+  public int getExperimentInt(String key, int defaultValue) {
+    String property = getExperiment(key);
+    try {
+      return property != null ? Integer.parseInt(property) : defaultValue;
+    } catch (NumberFormatException e) {
+      logger.warn("Could not parse int for experiment: " + key, e);
+      return defaultValue;
+    }
+  }
+
+  @Override
+  public synchronized void startExperimentScope() {
+    if (++experimentScopeCounter > 0) {
+      experiments = getAllExperiments();
+    }
+  }
+
+  @Override
+  public synchronized void endExperimentScope() {
+    if (--experimentScopeCounter <= 0) {
+      logger.assertTrue(experimentScopeCounter == 0);
+      experiments = null;
+    }
+  }
+
+  private Map<String, String> getAllExperiments() {
+    Map<String, String> experiments = this.experiments;
+    if (experiments != null) {
+      return experiments;
+    } else {
+      return services
+          .stream()
+          .flatMap(service -> service.getExperiments().entrySet().stream())
+          .collect(
+              Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (first, second) -> first));
+    }
+  }
+
+  private String getExperiment(String key) {
+    return getAllExperiments().get(hashExperimentName(key));
+  }
+}
diff --git a/common/experiments/src/com/google/idea/common/experiments/ExperimentsUtil.java b/common/experiments/src/com/google/idea/common/experiments/ExperimentsUtil.java
new file mode 100644
index 0000000..30820f7
--- /dev/null
+++ b/common/experiments/src/com/google/idea/common/experiments/ExperimentsUtil.java
@@ -0,0 +1,31 @@
+/*
+ * 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.common.experiments;
+
+import com.google.common.hash.HashFunction;
+import com.google.common.hash.Hashing;
+import java.nio.charset.StandardCharsets;
+
+final class ExperimentsUtil {
+
+  private static final HashFunction HASHER = Hashing.sha512();
+
+  private ExperimentsUtil() {}
+
+  static String hashExperimentName(String name) {
+    return HASHER.hashString(name, StandardCharsets.UTF_8).toString();
+  }
+}
diff --git a/common/experiments/src/com/google/idea/common/experiments/FileExperimentLoader.java b/common/experiments/src/com/google/idea/common/experiments/FileExperimentLoader.java
new file mode 100644
index 0000000..d913f70
--- /dev/null
+++ b/common/experiments/src/com/google/idea/common/experiments/FileExperimentLoader.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.common.experiments;
+
+import com.google.common.collect.ImmutableMap;
+import com.intellij.openapi.diagnostic.Logger;
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileTime;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+
+/** Reads experiments from a property file. */
+final class FileExperimentLoader extends HashingExperimentLoader {
+
+  private static final Logger logger = Logger.getInstance(FileExperimentLoader.class);
+
+  private final String filename;
+  private Map<String, String> experiments = ImmutableMap.of();
+  private FileTime lastModified = FileTime.fromMillis(0);
+
+  FileExperimentLoader(String filename) {
+    this.filename = filename;
+  }
+
+  @SuppressWarnings("unchecked") // Properties is Map<Object, Object>, we cast to strings
+  @Override
+  Map<String, String> getUnhashedExperiments() {
+    Properties properties = new Properties();
+
+    File file = new File(filename);
+    if (!file.exists()) {
+      experiments = ImmutableMap.of();
+      return experiments;
+    }
+
+    try {
+      FileTime lastModified =
+          Files.readAttributes(file.toPath(), BasicFileAttributes.class).lastModifiedTime();
+      if (Objects.equals(lastModified, this.lastModified)) {
+        return experiments;
+      }
+
+      try (InputStream fis = new FileInputStream(filename);
+          BufferedInputStream bis = new BufferedInputStream(fis)) {
+        properties.load(bis);
+        experiments = ImmutableMap.copyOf((Map) properties);
+        this.lastModified = lastModified;
+      }
+    } catch (IOException e) {
+      logger.warn("Could not load experiments from file: " + filename, e);
+    }
+
+    return experiments;
+  }
+
+  @Override
+  public void initialize() {
+    // Reads the file into memory.
+    getUnhashedExperiments();
+  }
+}
diff --git a/common/experiments/src/com/google/idea/common/experiments/HashingExperimentLoader.java b/common/experiments/src/com/google/idea/common/experiments/HashingExperimentLoader.java
new file mode 100644
index 0000000..cab45c9
--- /dev/null
+++ b/common/experiments/src/com/google/idea/common/experiments/HashingExperimentLoader.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.common.experiments;
+
+import static com.google.idea.common.experiments.ExperimentsUtil.hashExperimentName;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * An experiment loader that handles hashing the experiment names, for sources that store the data
+ * unhashed.
+ */
+abstract class HashingExperimentLoader implements ExperimentLoader {
+
+  @Override
+  public Map<String, String> getExperiments() {
+    return getUnhashedExperiments()
+        .entrySet()
+        .stream()
+        .collect(Collectors.toMap(e -> hashExperimentName(e.getKey()), Map.Entry::getValue));
+  }
+
+  abstract Map<String, String> getUnhashedExperiments();
+}
diff --git a/common/experiments/src/com/google/idea/common/experiments/IntExperiment.java b/common/experiments/src/com/google/idea/common/experiments/IntExperiment.java
new file mode 100644
index 0000000..15eaebd
--- /dev/null
+++ b/common/experiments/src/com/google/idea/common/experiments/IntExperiment.java
@@ -0,0 +1,30 @@
+/*
+ * 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.common.experiments;
+
+/** Integer valued experiment. */
+public class IntExperiment extends Experiment {
+  private final int defaultValue;
+
+  public IntExperiment(String key, int defaultValue) {
+    super(key);
+    this.defaultValue = defaultValue;
+  }
+
+  public int getValue() {
+    return ExperimentService.getInstance().getExperimentInt(key, defaultValue);
+  }
+}
diff --git a/common/experiments/src/com/google/idea/common/experiments/SerializationUtil.java b/common/experiments/src/com/google/idea/common/experiments/SerializationUtil.java
new file mode 100644
index 0000000..53cd842
--- /dev/null
+++ b/common/experiments/src/com/google/idea/common/experiments/SerializationUtil.java
@@ -0,0 +1,60 @@
+/*
+ * 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.common.experiments;
+
+import com.intellij.CommonBundle;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import javax.annotation.Nullable;
+
+/** Utils for serialization. */
+final class SerializationUtil {
+
+  private SerializationUtil() {}
+
+  static void saveToDisk(File file, Serializable serializable) throws IOException {
+    ensureExists(file.getParentFile());
+    try (FileOutputStream fos = new FileOutputStream(file);
+        ObjectOutputStream oos = new ObjectOutputStream(fos)) {
+      oos.writeObject(serializable);
+    }
+  }
+
+  @Nullable
+  static Object loadFromDisk(File file) throws IOException {
+    if (!file.exists()) {
+      return null;
+    }
+    try (FileInputStream fis = new FileInputStream(file);
+        ObjectInputStream ois = new ObjectInputStream(fis)) {
+      return ois.readObject();
+    } catch (ClassNotFoundException e) {
+      throw new IOException(e);
+    }
+  }
+
+  private static void ensureExists(File dir) throws IOException {
+    if (!dir.exists() && !dir.mkdirs()) {
+      throw new IOException(
+          CommonBundle.message("exception.directory.can.not.create", dir.getPath()));
+    }
+  }
+}
diff --git a/common/experiments/src/com/google/idea/common/experiments/StringExperiment.java b/common/experiments/src/com/google/idea/common/experiments/StringExperiment.java
new file mode 100644
index 0000000..0fcd903
--- /dev/null
+++ b/common/experiments/src/com/google/idea/common/experiments/StringExperiment.java
@@ -0,0 +1,31 @@
+/*
+ * 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.common.experiments;
+
+import javax.annotation.Nullable;
+
+/** String-valued experiment. */
+public class StringExperiment extends Experiment {
+
+  public StringExperiment(String key) {
+    super(key);
+  }
+
+  @Nullable
+  public String getValue() {
+    return ExperimentService.getInstance().getExperimentString(key, null);
+  }
+}
diff --git a/common/experiments/src/com/google/idea/common/experiments/SystemPropertyExperimentLoader.java b/common/experiments/src/com/google/idea/common/experiments/SystemPropertyExperimentLoader.java
new file mode 100644
index 0000000..96ebeba
--- /dev/null
+++ b/common/experiments/src/com/google/idea/common/experiments/SystemPropertyExperimentLoader.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.common.experiments;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+
+final class SystemPropertyExperimentLoader extends HashingExperimentLoader {
+  private static final String BLAZE_EXPERIMENT_OVERRIDE = "blaze.experiment.";
+
+  @Override
+  public Map<String, String> getUnhashedExperiments() {
+    return System.getProperties()
+        .stringPropertyNames()
+        .stream()
+        .filter(name -> name.startsWith(BLAZE_EXPERIMENT_OVERRIDE))
+        .collect(
+            Collectors.toMap(
+                name -> name.substring(BLAZE_EXPERIMENT_OVERRIDE.length()), System::getProperty));
+  }
+
+  @Override
+  public void initialize() {
+    // Nothing to do.
+  }
+}
diff --git a/common/experiments/src/com/google/idea/common/experiments/WebExperimentLoader.java b/common/experiments/src/com/google/idea/common/experiments/WebExperimentLoader.java
new file mode 100644
index 0000000..0f27c15
--- /dev/null
+++ b/common/experiments/src/com/google/idea/common/experiments/WebExperimentLoader.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.common.experiments;
+
+import java.util.Map;
+
+final class WebExperimentLoader implements ExperimentLoader {
+
+  private final WebExperimentSyncer syncer;
+
+  WebExperimentLoader(String pluginName) {
+    syncer = new WebExperimentSyncer(pluginName);
+  }
+
+  @Override
+  public Map<String, String> getExperiments() {
+    return syncer.getExperimentValues();
+  }
+
+  @Override
+  public void initialize() {
+
+    syncer.initialize();
+  }
+}
diff --git a/common/experiments/src/com/google/idea/common/experiments/WebExperimentSyncer.java b/common/experiments/src/com/google/idea/common/experiments/WebExperimentSyncer.java
new file mode 100644
index 0000000..464a62d
--- /dev/null
+++ b/common/experiments/src/com/google/idea/common/experiments/WebExperimentSyncer.java
@@ -0,0 +1,174 @@
+/*
+ * 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.common.experiments;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListenableScheduledFuture;
+import com.google.common.util.concurrent.ListeningScheduledExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.intellij.openapi.application.PathManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.util.concurrency.AppExecutorUtil;
+import com.intellij.util.io.HttpRequests;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import org.jetbrains.io.JsonReaderEx;
+
+/**
+ * A singleton class that retrieves the experiments from the experiments service</a>.
+ *
+ * <p>The first time {@link #getExperimentValues()} is called, fresh data will be retrieved in the
+ * current thread. Thereafter, data will be retrieved every 5 minutes in a background thread. If
+ * there is a failure retrieving data, new attempts will be made every minute.
+ */
+final class WebExperimentSyncer {
+
+  private static final Logger logger = Logger.getInstance(WebExperimentSyncer.class);
+
+  private static final String DEFAULT_EXPERIMENT_URL =
+      "https://intellij-experiments.appspot.com/api/experiments/";
+  private static final String EXPERIMENTS_URL_PROPERTY = "intellij.experiments.url";
+
+  private static final int SUCESSFUL_DOWNLOAD_DELAY_MINUTES = 5;
+  private static final int DOWNLOAD_FAILURE_DELAY_MINUTES = 1;
+
+  private final File cacheFile;
+  private final String pluginName;
+
+  // null indicates no fetch attempt has been made. After the first attempt, this will always be a
+  // (possibly empty) map.
+  private Map<String, String> experimentValues = null;
+
+  private final ListeningScheduledExecutorService executor =
+      MoreExecutors.listeningDecorator(AppExecutorUtil.getAppScheduledExecutorService());
+
+  WebExperimentSyncer(String pluginName) {
+    this.pluginName = pluginName;
+    cacheFile =
+        Paths.get(PathManager.getSystemPath(), "blaze", pluginName + ".experiments.cache.dat")
+            .toFile();
+  }
+
+  /**
+   * Get the last-retrieved set of experiment values.
+   *
+   * <p>The first time this method is called, an attempt to retrieve the values will take place.
+   * Thereafter, the values will be retrieved every five minutes on a background thread and this
+   * method will return the most recent successfully retrieved values.
+   */
+  synchronized Map<String, String> getExperimentValues() {
+    if (experimentValues == null) {
+      initialize();
+    }
+
+    return experimentValues;
+  }
+
+  private synchronized void setExperimentValues(HashMap<String, String> experimentValues) {
+    this.experimentValues = experimentValues;
+    saveCache(experimentValues);
+  }
+
+  /** Fetch and process the experiments on the current thread. */
+  void initialize() {
+    experimentValues = loadCache();
+    ListenableFuture<String> response = executor.submit(new WebExperimentsDownloader());
+    response.addListener(
+        new WebExperimentsResultProcessor(response), MoreExecutors.sameThreadExecutor());
+  }
+
+  private void scheduleNextRefresh(boolean refreshWasSuccessful) {
+    int delayInMinutes =
+        refreshWasSuccessful ? SUCESSFUL_DOWNLOAD_DELAY_MINUTES : DOWNLOAD_FAILURE_DELAY_MINUTES;
+    ListenableScheduledFuture<String> refreshResults =
+        executor.schedule(new WebExperimentsDownloader(), delayInMinutes, TimeUnit.MINUTES);
+    refreshResults.addListener(
+        new WebExperimentsResultProcessor(refreshResults), MoreExecutors.sameThreadExecutor());
+  }
+
+  private class WebExperimentsDownloader implements Callable<String> {
+
+    @Override
+    public String call() throws Exception {
+      logger.debug("About to fetch experiments.");
+      return HttpRequests.request(
+              System.getProperty(EXPERIMENTS_URL_PROPERTY, DEFAULT_EXPERIMENT_URL) + pluginName)
+          .readString(null /* progress indicator */);
+    }
+  }
+
+  private class WebExperimentsResultProcessor implements Runnable {
+
+    private final Future<String> resultFuture;
+
+    private WebExperimentsResultProcessor(Future<String> resultFuture) {
+      this.resultFuture = resultFuture;
+    }
+
+    @Override
+    public void run() {
+      logger.debug("Experiments fetched. Processing results.");
+      try {
+        HashMap<String, String> mapBuilder = Maps.newHashMap();
+        String result = resultFuture.get();
+        try (JsonReaderEx reader = new JsonReaderEx(result)) {
+          reader.beginObject();
+          while (reader.hasNext()) {
+            String experimentName = reader.nextName();
+            String experimentValue = reader.nextString();
+            mapBuilder.put(experimentName, experimentValue);
+          }
+        }
+        setExperimentValues(mapBuilder);
+
+        logger.debug("Successfully fetched experiments: " + getExperimentValues());
+        scheduleNextRefresh(true /* refreshWasSuccessful */);
+      } catch (InterruptedException | ExecutionException | RuntimeException e) {
+        logger.debug("Error fetching experiments", e);
+        scheduleNextRefresh(false /* refreshWasSuccessful */);
+      }
+    }
+  }
+
+  private void saveCache(HashMap<String, String> experiments) {
+    try {
+      SerializationUtil.saveToDisk(cacheFile, experiments);
+    } catch (IOException e) {
+      logger.warn("Could not save experiments cache to disk: " + cacheFile);
+    }
+  }
+
+  @SuppressWarnings("unchecked")
+  private Map<String, String> loadCache() {
+    try {
+      Map<String, String> loaded = (Map<String, String>) SerializationUtil.loadFromDisk(cacheFile);
+      return loaded != null ? loaded : ImmutableMap.of();
+    } catch (IOException e) {
+      // This is normal, we might be offline and have never loaded the cache.
+      logger.info("Could not load experiments file: " + cacheFile);
+    }
+    return ImmutableMap.of();
+  }
+}
diff --git a/common/experiments/tests/unittests/com/google/idea/common/experiments/ExperimentServiceImplTest.java b/common/experiments/tests/unittests/com/google/idea/common/experiments/ExperimentServiceImplTest.java
new file mode 100644
index 0000000..cd33e99
--- /dev/null
+++ b/common/experiments/tests/unittests/com/google/idea/common/experiments/ExperimentServiceImplTest.java
@@ -0,0 +1,199 @@
+/*
+ * 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.common.experiments;
+
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.truth.Truth.assertThat;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link ExperimentServiceImpl}. */
+@RunWith(JUnit4.class)
+public class ExperimentServiceImplTest {
+
+  @Test
+  public void testBooleanPropertyTrue() {
+    ExperimentService experimentService =
+        new ExperimentServiceImpl(new MapExperimentLoader("test.property", "1"));
+    assertThat(experimentService.getExperiment("test.property", false)).isTrue();
+  }
+
+  @Test
+  public void testBooleanPropertyFalse() {
+    ExperimentService experimentService =
+        new ExperimentServiceImpl(new MapExperimentLoader("test.property", "0"));
+    assertThat(experimentService.getExperiment("test.property", true)).isFalse();
+  }
+
+  @Test
+  public void testBooleanPropertyReturnsDefaultWhenMissing() {
+    ExperimentService experimentService = new ExperimentServiceImpl(new MapExperimentLoader());
+    assertThat(experimentService.getExperiment("test.notthere", true)).isTrue();
+  }
+
+  @Test
+  public void testStringProperty() {
+    ExperimentService experimentService =
+        new ExperimentServiceImpl(new MapExperimentLoader("test.property", "hi"));
+    assertThat(experimentService.getExperimentString("test.property", null)).isEqualTo("hi");
+  }
+
+  @Test
+  public void testStringPropertyReturnsDefaultWhenMissing() {
+    ExperimentService experimentService = new ExperimentServiceImpl(new MapExperimentLoader());
+    assertThat(experimentService.getExperimentString("test.property", "bye")).isEqualTo("bye");
+  }
+
+  @Test
+  public void testFirstLoaderOverridesSecond() {
+    ExperimentService experimentService =
+        new ExperimentServiceImpl(
+            new MapExperimentLoader("test.property", "1"),
+            new MapExperimentLoader("test.property", "0"));
+    assertThat(experimentService.getExperiment("test.property", false)).isTrue();
+  }
+
+  @Test
+  public void testOnlyInSecondLoader() {
+    ExperimentService experimentService =
+        new ExperimentServiceImpl(
+            new MapExperimentLoader(), new MapExperimentLoader("test.property", "1"));
+    assertThat(experimentService.getExperiment("test.property", false)).isTrue();
+  }
+
+  @Test
+  public void testIntProperty() {
+    ExperimentService experimentService =
+        new ExperimentServiceImpl(new MapExperimentLoader("test.property", "10"));
+    assertThat(experimentService.getExperimentInt("test.property", 0)).isEqualTo(10);
+  }
+
+  @Test
+  public void testIntPropertyDefaultValue() {
+    ExperimentService experimentService = new ExperimentServiceImpl(new MapExperimentLoader());
+    assertThat(experimentService.getExperimentInt("test.property", 100)).isEqualTo(100);
+  }
+
+  @Test
+  public void testIntPropertyThatDoesntParseReturnsDefaultValue() {
+    ExperimentService experimentService =
+        new ExperimentServiceImpl(new MapExperimentLoader("test.property", "hello"));
+    assertThat(experimentService.getExperimentInt("test.property", 111)).isEqualTo(111);
+  }
+
+  @Test
+  public void testDataIsReloadedWhenNotInAScope() throws Exception {
+    MapExperimentLoader experimentLoader = new MapExperimentLoader();
+    ExperimentService experimentService = new ExperimentServiceImpl(experimentLoader);
+    assertThat(experimentService.getExperimentString("test.property", "default"))
+        .isEqualTo("default");
+    experimentLoader.map.put("test.property", "hello");
+    assertThat(experimentService.getExperimentString("test.property", "default"))
+        .isEqualTo("hello");
+  }
+
+  @Test
+  public void testDataIsFrozenWheninAScope() throws Exception {
+    MapExperimentLoader experimentLoader = new MapExperimentLoader();
+    ExperimentService experimentService = new ExperimentServiceImpl(experimentLoader);
+    experimentService.startExperimentScope();
+    assertThat(experimentService.getExperimentString("test.property", "default"))
+        .isEqualTo("default");
+    experimentLoader.map.put("test.property", "hello");
+    assertThat(experimentService.getExperimentString("test.property", "default"))
+        .isEqualTo("default");
+  }
+
+  @Test
+  public void testDataIsReloadedAgainWhenLeavingAScope() throws Exception {
+    MapExperimentLoader experimentLoader = new MapExperimentLoader();
+    ExperimentService experimentService = new ExperimentServiceImpl(experimentLoader);
+    experimentService.startExperimentScope();
+    assertThat(experimentService.getExperimentString("test.property", "default"))
+        .isEqualTo("default");
+    experimentLoader.map.put("test.property", "hello");
+    experimentService.endExperimentScope();
+    assertThat(experimentService.getExperimentString("test.property", "default"))
+        .isEqualTo("hello");
+  }
+
+  @Test
+  public void testEnterTwoScopesButOnlyLeaveOne() throws Exception {
+    MapExperimentLoader experimentLoader = new MapExperimentLoader();
+    ExperimentService experimentService = new ExperimentServiceImpl(experimentLoader);
+    experimentService.startExperimentScope();
+    assertThat(experimentService.getExperimentString("test.property", "default"))
+        .isEqualTo("default");
+    experimentService.startExperimentScope();
+    experimentService.endExperimentScope();
+    experimentLoader.map.put("test.property", "hello");
+    assertThat(experimentService.getExperimentString("test.property", "default"))
+        .isEqualTo("default");
+  }
+
+  @Test
+  public void testEnterAndLeaveTwoScopes() throws Exception {
+    MapExperimentLoader experimentLoader = new MapExperimentLoader();
+    ExperimentService experimentService = new ExperimentServiceImpl(experimentLoader);
+    experimentService.startExperimentScope();
+    assertThat(experimentService.getExperimentString("test.property", "default"))
+        .isEqualTo("default");
+    experimentService.startExperimentScope();
+    experimentService.endExperimentScope();
+    experimentService.endExperimentScope();
+    experimentLoader.map.put("test.property", "hello");
+    assertThat(experimentService.getExperimentString("test.property", "default"))
+        .isEqualTo("hello");
+  }
+
+  @Test
+  public void testLeaveAndEnterRefreshes() throws Exception {
+    MapExperimentLoader experimentLoader = new MapExperimentLoader();
+    experimentLoader.map.put("test.property", "one");
+    ExperimentService experimentService = new ExperimentServiceImpl(experimentLoader);
+    experimentService.startExperimentScope();
+    assertThat(experimentService.getExperimentString("test.property", "default")).isEqualTo("one");
+    experimentLoader.map.put("test.property", "two");
+    experimentService.endExperimentScope();
+    experimentService.startExperimentScope();
+    assertThat(experimentService.getExperimentString("test.property", "default")).isEqualTo("two");
+  }
+
+  private static class MapExperimentLoader extends HashingExperimentLoader {
+
+    private final Map<String, String> map;
+
+    private MapExperimentLoader(String... keysAndValues) {
+      checkState(keysAndValues.length % 2 == 0);
+      map = new HashMap<>();
+      for (int i = 0; i < keysAndValues.length; i += 2) {
+        map.put(keysAndValues[i], keysAndValues[i + 1]);
+      }
+    }
+
+    @Override
+    public Map<String, String> getUnhashedExperiments() {
+      return map;
+    }
+
+    @Override
+    public void initialize() {}
+  }
+}
diff --git a/common/experiments/tests/unittests/com/google/idea/common/experiments/SystemPropertyExperimentLoaderTest.java b/common/experiments/tests/unittests/com/google/idea/common/experiments/SystemPropertyExperimentLoaderTest.java
new file mode 100644
index 0000000..2b3aace
--- /dev/null
+++ b/common/experiments/tests/unittests/com/google/idea/common/experiments/SystemPropertyExperimentLoaderTest.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.common.experiments;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.idea.common.experiments.ExperimentsUtil.hashExperimentName;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Test for the system property experiment loader. */
+@RunWith(JUnit4.class)
+public class SystemPropertyExperimentLoaderTest {
+
+  private static final String EXPERIMENT = "test.foo";
+  private static final String PROPERTY = "blaze.experiment.test.foo";
+  private static final String VALUE = "true";
+
+  @Before
+  public void setUp() {
+    System.setProperty(PROPERTY, VALUE);
+  }
+
+  @After
+  public void tearDown() {
+    System.clearProperty(PROPERTY);
+  }
+
+  @Test
+  public void testGetExperiment() {
+    ExperimentLoader loader = new SystemPropertyExperimentLoader();
+    assertThat(loader.getExperiments().get(hashExperimentName(EXPERIMENT))).isEqualTo(VALUE);
+  }
+}
diff --git a/common/experiments/tests/utils/unit/com/google/idea/common/experiments/MockExperimentService.java b/common/experiments/tests/utils/unit/com/google/idea/common/experiments/MockExperimentService.java
new file mode 100644
index 0000000..c144c70
--- /dev/null
+++ b/common/experiments/tests/utils/unit/com/google/idea/common/experiments/MockExperimentService.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.common.experiments;
+
+import java.util.HashMap;
+import java.util.Map;
+import javax.annotation.Nullable;
+
+/** Used for tests. */
+public class MockExperimentService implements ExperimentService {
+
+  private Map<String, Object> experiments = new HashMap<>();
+
+  @Override
+  public void startExperimentScope() {}
+
+  @Override
+  public void endExperimentScope() {}
+
+  public void setExperiment(BoolExperiment experiment, boolean value) {
+    experiments.put(experiment.getKey(), value);
+  }
+
+  @Override
+  public boolean getExperiment(String key, boolean defaultValue) {
+    if (experiments.containsKey(key)) {
+      return (Boolean) experiments.get(key);
+    }
+    return defaultValue;
+  }
+
+  @Override
+  @Nullable
+  public String getExperimentString(String key, @Nullable String defaultValue) {
+    if (experiments.containsKey(key)) {
+      return (String) experiments.get(key);
+    }
+    return defaultValue;
+  }
+
+  @Override
+  public int getExperimentInt(String key, int defaultValue) {
+    if (experiments.containsKey(key)) {
+      return (Integer) experiments.get(key);
+    }
+    return defaultValue;
+  }
+}
diff --git a/cpp/BUILD b/cpp/BUILD
new file mode 100644
index 0000000..8b23007
--- /dev/null
+++ b/cpp/BUILD
@@ -0,0 +1,56 @@
+licenses(["notice"])  # Apache 2.0
+
+java_library(
+    name = "cpp",
+    srcs = glob(
+        ["src/**/*.java"],
+        exclude = [
+            "src/com/google/idea/blaze/cpp/versioned/**",
+        ],
+    ) + select({
+        "//intellij_platform_sdk:android-studio-latest": [":api_v145_sources"],
+        "//conditions:default": [":api_v162_sources"],
+    }),
+    visibility = ["//visibility:public"],
+    deps = [
+        "//base",
+        "//intellij_platform_sdk:plugin_api",
+        "@jsr305_annotations//jar",
+    ],
+)
+
+filegroup(
+    name = "plugin_xml",
+    srcs = ["src/META-INF/blaze-cpp.xml"],
+    visibility = ["//visibility:public"],
+)
+
+filegroup(
+    name = "api_v145_sources",
+    srcs = glob(["src/com/google/idea/blaze/cpp/versioned/v145/**"]),
+    visibility = ["//visibility:private"],
+)
+
+filegroup(
+    name = "api_v162_sources",
+    srcs = glob(["src/com/google/idea/blaze/cpp/versioned/v162/**"]),
+    visibility = ["//visibility:private"],
+)
+
+load(
+    "//intellij_test: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.cpp",
+    deps = [
+        ":cpp",
+        "//base:unit_test_utils",
+        "//intellij_platform_sdk:plugin_api_for_tests",
+        "@jsr305_annotations//jar",
+        "@junit//jar",
+    ],
+)
diff --git a/cpp/src/META-INF/blaze-cpp.xml b/cpp/src/META-INF/blaze-cpp.xml
new file mode 100644
index 0000000..1cb99ea
--- /dev/null
+++ b/cpp/src/META-INF/blaze-cpp.xml
@@ -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.
+  -->
+<idea-plugin>
+  <depends>com.intellij.modules.cidr.lang</depends>
+  <depends>com.intellij.modules.cidr.debugger</depends>
+
+  <extensions defaultExtensionNs="com.google.idea.blaze">
+    <SyncPlugin implementation="com.google.idea.blaze.cpp.BlazeCSyncPlugin"/>
+    <PrefetchFileSource implementation="com.google.idea.blaze.cpp.CPrefetchFileSource"/>
+  </extensions>
+
+  <extensions defaultExtensionNs="cidr.lang">
+    <languageKindHelper implementation="com.google.idea.blaze.cpp.BlazeLanguageKindCalculatorHelper"/>
+    <autoImportHelper implementation="com.google.idea.blaze.cpp.BlazeCppAutoImportHelper"/>
+  </extensions>
+  <extensions defaultExtensionNs="com.intellij">
+    <projectService serviceImplementation="com.google.idea.blaze.cpp.BlazeCWorkspace"/>
+  </extensions>
+</idea-plugin>
diff --git a/cpp/src/com/google/idea/blaze/cpp/BlazeCSyncPlugin.java b/cpp/src/com/google/idea/blaze/cpp/BlazeCSyncPlugin.java
new file mode 100644
index 0000000..c066fc4
--- /dev/null
+++ b/cpp/src/com/google/idea/blaze/cpp/BlazeCSyncPlugin.java
@@ -0,0 +1,72 @@
+/*
+ * 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.cpp;
+
+import com.google.common.collect.ImmutableSet;
+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.WorkspaceRoot;
+import com.google.idea.blaze.base.model.primitives.WorkspaceType;
+import com.google.idea.blaze.base.projectview.ProjectViewSet;
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.scope.Scope;
+import com.google.idea.blaze.base.scope.scopes.TimingScope;
+import com.google.idea.blaze.base.sync.BlazeSyncPlugin;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ModifiableRootModel;
+import com.jetbrains.cidr.lang.workspace.OCWorkspace;
+import com.jetbrains.cidr.lang.workspace.OCWorkspaceManager;
+import java.util.Set;
+import javax.annotation.Nullable;
+
+final class BlazeCSyncPlugin extends BlazeSyncPlugin.Adapter {
+  @Override
+  public Set<LanguageClass> getSupportedLanguagesInWorkspace(WorkspaceType workspaceType) {
+    if (workspaceType == WorkspaceType.C) {
+      return ImmutableSet.of(LanguageClass.C);
+    }
+    return ImmutableSet.of();
+  }
+
+  @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.C)) {
+      return;
+    }
+
+    Scope.push(
+        context,
+        childContext -> {
+          childContext.push(new TimingScope("Setup C Workspace"));
+
+          OCWorkspace workspace = OCWorkspaceManager.getWorkspace(project);
+          if (workspace instanceof BlazeCWorkspace) {
+            BlazeCWorkspace blazeCWorkspace = (BlazeCWorkspace) workspace;
+            blazeCWorkspace.update(childContext, blazeProjectData);
+          }
+        });
+  }
+}
diff --git a/cpp/src/com/google/idea/blaze/cpp/BlazeCWorkspace.java b/cpp/src/com/google/idea/blaze/cpp/BlazeCWorkspace.java
new file mode 100644
index 0000000..84fc646
--- /dev/null
+++ b/cpp/src/com/google/idea/blaze/cpp/BlazeCWorkspace.java
@@ -0,0 +1,142 @@
+/*
+ * 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.cpp;
+
+import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.settings.Blaze;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.jetbrains.cidr.lang.symbols.OCSymbol;
+import com.jetbrains.cidr.lang.workspace.OCResolveConfiguration;
+import com.jetbrains.cidr.lang.workspace.OCWorkspace;
+import com.jetbrains.cidr.lang.workspace.OCWorkspaceModificationTrackers;
+import java.util.Collection;
+import java.util.List;
+import javax.annotation.Nullable;
+
+/** Main entry point for C/CPP configuration data. */
+public final class BlazeCWorkspace implements OCWorkspace {
+  private static final Logger LOG = Logger.getInstance(BlazeCWorkspace.class);
+
+  @Nullable private final Project project;
+  @Nullable private final OCWorkspaceModificationTrackers modTrackers;
+
+  @Nullable private BlazeConfigurationResolver configurationResolver;
+
+  private BlazeCWorkspace(Project project) {
+    if (Blaze.isBlazeProject(project)) {
+      this.project = project;
+      this.modTrackers = new OCWorkspaceModificationTrackers(project);
+      this.configurationResolver = new BlazeConfigurationResolver(project);
+    } else {
+      this.project = null;
+      this.modTrackers = null;
+    }
+  }
+
+  public static BlazeCWorkspace getInstance(Project project) {
+    return ServiceManager.getService(project, BlazeCWorkspace.class);
+  }
+
+  public void update(BlazeContext context, BlazeProjectData blazeProjectData) {
+    LOG.assertTrue(project != null);
+    LOG.assertTrue(modTrackers != null);
+    LOG.assertTrue(configurationResolver != null);
+
+    long start = System.currentTimeMillis();
+    // Non-incremental update to our c configurations.
+    configurationResolver.update(context, blazeProjectData);
+    long end = System.currentTimeMillis();
+
+    LOG.info(String.format("Blaze OCWorkspace update took: %d ms", (end - start)));
+
+    ApplicationManager.getApplication()
+        .runReadAction(
+            () -> {
+              if (project.isDisposed()) {
+                return;
+              }
+
+              // TODO(salguarnieri) Avoid bumping all of these trackers; figure out what has changed
+              modTrackers.getProjectFilesListTracker().incModificationCount();
+              modTrackers.getSourceFilesListTracker().incModificationCount();
+              modTrackers.getBuildConfigurationChangesTracker().incModificationCount();
+              modTrackers.getBuildSettingsChangesTracker().incModificationCount();
+            });
+  }
+
+  @Override
+  public Collection<VirtualFile> getLibraryFilesToBuildSymbols() {
+    // This method should return all the header files themselves, not the head file directories.
+    // (And not header files in the project; just the ones in the SDK and in any dependencies)
+    return ImmutableList.of();
+  }
+
+  @Override
+  public boolean areFromSameProject(@Nullable VirtualFile a, @Nullable VirtualFile b) {
+    return false;
+  }
+
+  @Override
+  public boolean areFromSamePackage(@Nullable VirtualFile a, @Nullable VirtualFile b) {
+    return false;
+  }
+
+  @Override
+  public boolean isInSDK(@Nullable VirtualFile file) {
+    return false;
+  }
+
+  @Override
+  public boolean isFromWrongSDK(OCSymbol symbol, @Nullable VirtualFile contextFile) {
+    return false;
+  }
+
+  @Nullable
+  @Override
+  public OCResolveConfiguration getSelectedResolveConfiguration() {
+    return null;
+  }
+
+  @Override
+  public OCWorkspaceModificationTrackers getModificationTrackers() {
+    LOG.assertTrue(modTrackers != null);
+    return modTrackers;
+  }
+
+  @Override
+  public List<? extends OCResolveConfiguration> getConfigurations() {
+    return configurationResolver == null
+        ? ImmutableList.of()
+        : configurationResolver.getAllConfigurations();
+  }
+
+  @Override
+  public List<? extends OCResolveConfiguration> getConfigurationsForFile(
+      @Nullable VirtualFile sourceFile) {
+    if (sourceFile == null || !sourceFile.isValid() || configurationResolver == null) {
+      return ImmutableList.of();
+    }
+    OCResolveConfiguration config = configurationResolver.getConfigurationForFile(sourceFile);
+    return config == null ? ImmutableList.of() : ImmutableList.of(config);
+  }
+}
diff --git a/cpp/src/com/google/idea/blaze/cpp/BlazeCompilerMacros.java b/cpp/src/com/google/idea/blaze/cpp/BlazeCompilerMacros.java
new file mode 100644
index 0000000..28eba21
--- /dev/null
+++ b/cpp/src/com/google/idea/blaze/cpp/BlazeCompilerMacros.java
@@ -0,0 +1,87 @@
+/*
+ * 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.cpp;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiFile;
+import com.jetbrains.cidr.lang.preprocessor.OCInclusionContext;
+import com.jetbrains.cidr.lang.preprocessor.OCInclusionContextUtil;
+import com.jetbrains.cidr.lang.workspace.compiler.CidrCompilerResult;
+import com.jetbrains.cidr.lang.workspace.compiler.OCCompilerMacros;
+import com.jetbrains.cidr.lang.workspace.compiler.OCCompilerSettings;
+import com.jetbrains.cidr.toolchains.CompilerInfoCache;
+import java.util.Map;
+
+final class BlazeCompilerMacros extends OCCompilerMacros {
+  private final CompilerInfoCache compilerInfoCache;
+  private final ImmutableCollection<String> globalDefines;
+  private final ImmutableMap<String, String> globalFeatures;
+  private final OCCompilerSettings compilerSettings;
+  private final Project project;
+
+  public BlazeCompilerMacros(
+      Project project,
+      CompilerInfoCache compilerInfoCache,
+      OCCompilerSettings compilerSettings,
+      ImmutableCollection<String> defines,
+      ImmutableMap<String, String> features) {
+    this.project = project;
+    this.compilerInfoCache = compilerInfoCache;
+    this.compilerSettings = compilerSettings;
+    this.globalDefines = defines;
+    this.globalFeatures = features;
+  }
+
+  @Override
+  protected void fillFileMacros(OCInclusionContext context, PsiFile sourceFile) {
+    // Get the default compiler info for this file.
+    VirtualFile vf = OCInclusionContextUtil.getVirtualFile(sourceFile);
+    CidrCompilerResult<CompilerInfoCache.Entry> compilerInfoProvider =
+        compilerInfoCache.getCompilerInfoCache(
+            project, compilerSettings, context.getLanguageKind(), vf);
+    CompilerInfoCache.Entry compilerInfo = compilerInfoProvider.getResult();
+
+    // Combine the info we got from Blaze with the info we get from IntelliJ's methods.
+    ImmutableSet.Builder<String> allDefinesBuilder = ImmutableSet.builder();
+    // IntelliJ expects a string of "#define [VAR_NAME]\n#define [VAR_NAME2]\n..."
+    for (String globalDefine : globalDefines) {
+      allDefinesBuilder.add("#define " + globalDefine);
+    }
+    if (compilerInfo != null) {
+      String[] split = compilerInfo.defines.split("\n");
+      for (String s : split) {
+        allDefinesBuilder.add(s);
+      }
+    }
+    final String allDefines = Joiner.on("\n").join(allDefinesBuilder.build());
+
+    Map<String, String> allFeatures = Maps.newHashMap();
+    allFeatures.putAll(globalFeatures);
+    if (compilerInfo != null) {
+      allFeatures.putAll(compilerInfo.features);
+    }
+
+    fillSubstitutions(context, allDefines);
+    enableClangFeatures(context, allFeatures);
+    enableClangExtensions(context, allFeatures);
+  }
+}
diff --git a/cpp/src/com/google/idea/blaze/cpp/BlazeCompilerSettings.java b/cpp/src/com/google/idea/blaze/cpp/BlazeCompilerSettings.java
new file mode 100644
index 0000000..966b37c
--- /dev/null
+++ b/cpp/src/com/google/idea/blaze/cpp/BlazeCompilerSettings.java
@@ -0,0 +1,113 @@
+/*
+ * 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.cpp;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.jetbrains.cidr.lang.OCLanguageKind;
+import com.jetbrains.cidr.lang.toolchains.CidrCompilerSwitches;
+import com.jetbrains.cidr.lang.toolchains.CidrSwitchBuilder;
+import com.jetbrains.cidr.lang.toolchains.CidrToolEnvironment;
+import com.jetbrains.cidr.lang.toolchains.DefaultCidrToolEnvironment;
+import com.jetbrains.cidr.lang.workspace.compiler.OCCompilerKind;
+import com.jetbrains.cidr.lang.workspace.compiler.OCCompilerSettings;
+import java.io.File;
+import java.util.List;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
+
+final class BlazeCompilerSettings extends OCCompilerSettings {
+  private final CidrToolEnvironment toolEnvironment = new DefaultCidrToolEnvironment();
+
+  private final Project project;
+  @Nullable private final File cCompiler;
+  @Nullable private final File cppCompiler;
+  private final CidrCompilerSwitches cCompilerSwitches;
+  private final CidrCompilerSwitches cppCompilerSwitches;
+
+  BlazeCompilerSettings(
+      Project project,
+      @Nullable File cCompiler,
+      @Nullable File cppCompiler,
+      ImmutableList<String> cFlags,
+      ImmutableList<String> cppFlags) {
+    this.project = project;
+    this.cCompiler = cCompiler;
+    this.cppCompiler = cppCompiler;
+    this.cCompilerSwitches = getCompilerSwitches(cFlags);
+    this.cppCompilerSwitches = getCompilerSwitches(cppFlags);
+  }
+
+  @Override
+  public OCCompilerKind getCompiler(OCLanguageKind languageKind) {
+    return null;
+  }
+
+  @Override
+  public File getCompilerExecutable(OCLanguageKind lang) {
+    if (lang == OCLanguageKind.C) {
+      return cCompiler;
+    } else if (lang == OCLanguageKind.CPP) {
+      return cppCompiler;
+    }
+    // We don't support objective c/c++.
+    return null;
+  }
+
+  @Override
+  public File getCompilerWorkingDir() {
+    return WorkspaceRoot.fromProject(project).directory();
+  }
+
+  @Override
+  public CidrToolEnvironment getEnvironment() {
+    return toolEnvironment;
+  }
+
+  @Override
+  public CidrCompilerSwitches getCompilerSwitches(
+      OCLanguageKind lang, @Nullable VirtualFile sourceFile) {
+    if (lang == OCLanguageKind.C) {
+      return cCompilerSwitches;
+    }
+    if (lang == OCLanguageKind.CPP) {
+      return cppCompilerSwitches;
+    }
+    return new CidrSwitchBuilder().build();
+  }
+
+  private static CidrCompilerSwitches getCompilerSwitches(List<String> allCompilerFlags) {
+    // Explanation of hack:
+    // - this list of switches is currently only used in one place -- GCCCompiler.tryRunGCC.
+    // - list is written to an argument file, whitespace-separated, then passed as a @file arg to
+    // clang.
+    // In this context, escaped whitespace within a single arg is not handled.
+    // Currently, the only way (short of using reflection) to ensure unescaped whitespace
+    // is to have CidrSwitchBuilder treat whitespace as a delimiter between args.
+    allCompilerFlags =
+        allCompilerFlags
+            .stream()
+            .map(flag -> flag.replace("\\ ", " "))
+            .collect(Collectors.toList());
+
+    return new CidrSwitchBuilder()
+        .addAll(Joiner.on(" ").join(allCompilerFlags), CidrSwitchBuilder.Format.FILE_ARGS)
+        .build();
+  }
+}
diff --git a/cpp/src/com/google/idea/blaze/cpp/BlazeConfigurationResolver.java b/cpp/src/com/google/idea/blaze/cpp/BlazeConfigurationResolver.java
new file mode 100644
index 0000000..925f873
--- /dev/null
+++ b/cpp/src/com/google/idea/blaze/cpp/BlazeConfigurationResolver.java
@@ -0,0 +1,274 @@
+/*
+ * 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.cpp;
+
+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.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.model.BlazeProjectData;
+import com.google.idea.blaze.base.model.primitives.Label;
+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.scopes.TimingScope;
+import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.VfsUtilCore;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.jetbrains.cidr.lang.workspace.OCResolveConfiguration;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ExecutionException;
+import javax.annotation.Nullable;
+
+final class BlazeConfigurationResolver {
+  private static final class MapEntry {
+    public final Label label;
+    public final BlazeResolveConfiguration configuration;
+
+    public MapEntry(Label label, BlazeResolveConfiguration configuration) {
+      this.label = label;
+      this.configuration = configuration;
+    }
+  }
+
+  private static final Logger LOG = Logger.getInstance(BlazeConfigurationResolver.class);
+  private final Project project;
+
+  private ImmutableMap<Label, BlazeResolveConfiguration> resolveConfigurations = ImmutableMap.of();
+
+  public BlazeConfigurationResolver(Project project) {
+    this.project = project;
+  }
+
+  public void update(BlazeContext context, BlazeProjectData blazeProjectData) {
+    WorkspacePathResolver workspacePathResolver = blazeProjectData.workspacePathResolver;
+    ImmutableMap<Label, CToolchainIdeInfo> toolchainLookupMap =
+        BlazeResolveConfiguration.buildToolchainLookupMap(
+            context, blazeProjectData.ruleMap, blazeProjectData.reverseDependencies);
+    resolveConfigurations =
+        buildBlazeConfigurationMap(
+            context, blazeProjectData, toolchainLookupMap, workspacePathResolver);
+  }
+
+  private ImmutableMap<Label, BlazeResolveConfiguration> buildBlazeConfigurationMap(
+      BlazeContext parentContext,
+      BlazeProjectData blazeProjectData,
+      ImmutableMap<Label, CToolchainIdeInfo> toolchainLookupMap,
+      WorkspacePathResolver workspacePathResolver) {
+    // Type specification needed to avoid incorrect type inference during command line build.
+    return Scope.push(
+        parentContext,
+        (ScopedFunction<ImmutableMap<Label, 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) {
+                  ListenableFuture<MapEntry> future =
+                      submit(
+                          () ->
+                              createResolveConfiguration(
+                                  rule,
+                                  toolchainLookupMap,
+                                  compilerWrapperCache,
+                                  workspacePathResolver,
+                                  blazeProjectData));
+                  mapEntryFutures.add(future);
+                }
+              }
+
+              ImmutableMap.Builder<Label, BlazeResolveConfiguration> newResolveConfigurations =
+                  ImmutableMap.builder();
+              List<MapEntry> mapEntries;
+              try {
+                mapEntries = Futures.allAsList(mapEntryFutures).get();
+              } catch (InterruptedException | ExecutionException e) {
+                Thread.currentThread().interrupt();
+                LOG.warn("Could not build C resolve configurations", e);
+                context.setCancelled();
+                return ImmutableMap.of();
+              }
+
+              for (MapEntry mapEntry : mapEntries) {
+                // Skip over labels that don't have C configuration data.
+                if (mapEntry != null) {
+                  newResolveConfigurations.put(mapEntry.label, mapEntry.configuration);
+                }
+              }
+              return newResolveConfigurations.build();
+            });
+  }
+
+  private static ListenableFuture<MapEntry> submit(Callable<MapEntry> callable) {
+    return BlazeExecutor.getInstance().submit(callable);
+  }
+
+  @Nullable
+  private MapEntry createResolveConfiguration(
+      RuleIdeInfo rule,
+      ImmutableMap<Label, CToolchainIdeInfo> toolchainLookupMap,
+      ConcurrentMap<CToolchainIdeInfo, File> compilerWrapperCache,
+      WorkspacePathResolver workspacePathResolver,
+      BlazeProjectData blazeProjectData) {
+    Label label = rule.label;
+    LOG.info("Resolving " + label.toString());
+    CToolchainIdeInfo toolchainIdeInfo = toolchainLookupMap.get(label);
+    if (toolchainIdeInfo != null) {
+      File compilerWrapper =
+          findOrCreateCompilerWrapperScript(
+              compilerWrapperCache, toolchainIdeInfo, workspacePathResolver, rule.label);
+      if (compilerWrapper != null) {
+        BlazeResolveConfiguration config =
+            BlazeResolveConfiguration.createConfigurationForTarget(
+                project,
+                workspacePathResolver,
+                blazeProjectData.ruleMap.get(label),
+                toolchainIdeInfo,
+                compilerWrapper);
+        if (config != null) {
+          return new MapEntry(label, config);
+        }
+      }
+    }
+    return null;
+  }
+
+  @Nullable
+  private static File findOrCreateCompilerWrapperScript(
+      Map<CToolchainIdeInfo, File> compilerWrapperCache,
+      CToolchainIdeInfo toolchainIdeInfo,
+      WorkspacePathResolver workspacePathResolver,
+      Label label) {
+    File compilerWrapper = compilerWrapperCache.get(toolchainIdeInfo);
+    if (compilerWrapper == null) {
+      File cppExecutable = toolchainIdeInfo.cppExecutable.getAbsoluteOrRelativeFile();
+      if (cppExecutable != null && !cppExecutable.isAbsolute()) {
+        cppExecutable = workspacePathResolver.resolveToFile(cppExecutable.getPath());
+      }
+      if (cppExecutable == null) {
+        String errorMessage =
+            String.format(
+                "Unable to find compiler executable: %s for rule %s",
+                toolchainIdeInfo.cppExecutable.toString(), label.toString());
+        LOG.warn(errorMessage);
+        compilerWrapper = null;
+      } else {
+        compilerWrapper = createCompilerExecutableWrapper(cppExecutable);
+        if (compilerWrapper != null) {
+          compilerWrapperCache.put(toolchainIdeInfo, compilerWrapper);
+        }
+      }
+    }
+    return compilerWrapper;
+  }
+
+  /**
+   * Create a wrapper script that transforms the CLion compiler invocation into a safe invocation of
+   * the compiler script that blaze uses.
+   *
+   * <p>CLion passes arguments to the compiler in an arguments file. The c toolchain compiler
+   * wrapper script doesn't handle arguments files, so we need to move the compiler arguments from
+   * the file to the command line.
+   *
+   * @param blazeCompilerExecutableFile blaze compiler wrapper
+   * @return The wrapper script that CLion can call.
+   */
+  @Nullable
+  private static File createCompilerExecutableWrapper(File blazeCompilerExecutableFile) {
+    try {
+      File blazeCompilerWrapper =
+          FileUtil.createTempFile("blaze_compiler", ".sh", true /* deleteOnExit */);
+      if (!blazeCompilerWrapper.setExecutable(true)) {
+        return null;
+      }
+      ImmutableList<String> compilerWrapperScriptLines =
+          ImmutableList.of(
+              "#!/bin/bash",
+              "",
+              "# The c toolchain compiler wrapper script doesn't handle arguments files, so we",
+              "# need to move the compiler arguments from the file to the command line.",
+              "",
+              "if [ $# -ne 2 ]; then",
+              "  echo \"Usage: $0 @arg-file compile-file\"",
+              "  exit 2;",
+              "fi",
+              "",
+              "if [[ $1 != @* ]]; then",
+              "  echo \"Usage: $0 @arg-file compile-file\"",
+              "  exit 3;",
+              "fi",
+              "",
+              " # Remove the @ before the arguments file path",
+              "ARG_FILE=${1#@}",
+              "# The actual compiler wrapper script we get from blaze",
+              "EXE=" + blazeCompilerExecutableFile.getPath(),
+              "# Read in the arguments file so we can pass the arguments on the command line.",
+              "ARGS=`cat $ARG_FILE`",
+              "$EXE $ARGS $2");
+
+      try (PrintWriter pw = new PrintWriter(blazeCompilerWrapper)) {
+        compilerWrapperScriptLines.forEach(pw::println);
+      }
+      return blazeCompilerWrapper;
+    } catch (IOException e) {
+      return null;
+    }
+  }
+
+  @Nullable
+  public OCResolveConfiguration getConfigurationForFile(VirtualFile sourceFile) {
+    SourceToRuleMap sourceToRuleMap = SourceToRuleMap.getInstance(project);
+    List<Label> targetsForSourceFile =
+        Lists.newArrayList(
+            sourceToRuleMap.getTargetsForSourceFile(VfsUtilCore.virtualToIoFile(sourceFile)));
+    if (targetsForSourceFile.isEmpty()) {
+      return null;
+    }
+
+    // If a source file is in two different targets,
+    // 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()));
+    Label target = Iterables.getFirst(targetsForSourceFile, null);
+    assert (target != null);
+
+    return resolveConfigurations.get(target);
+  }
+
+  public List<? extends OCResolveConfiguration> getAllConfigurations() {
+    return ImmutableList.copyOf(resolveConfigurations.values());
+  }
+}
diff --git a/cpp/src/com/google/idea/blaze/cpp/BlazeCppAutoImportHelper.java b/cpp/src/com/google/idea/blaze/cpp/BlazeCppAutoImportHelper.java
new file mode 100644
index 0000000..4a63ee9
--- /dev/null
+++ b/cpp/src/com/google/idea/blaze/cpp/BlazeCppAutoImportHelper.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.cpp;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VfsUtilCore;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiFileSystemItem;
+import com.intellij.util.Processor;
+import com.jetbrains.cidr.lang.autoImport.OCDefaultAutoImportHelper;
+import com.jetbrains.cidr.lang.workspace.OCResolveRootAndConfiguration;
+import com.jetbrains.cidr.lang.workspace.headerRoots.IncludedHeadersRoot;
+import javax.annotation.Nullable;
+
+/**
+ * CLion's auto-import suggestions result in include paths relative to the current file (CPP-7593).
+ * Instead, we want paths relative to the header search root (e.g. the relevant blaze/bazel package
+ * path). Presumably this will be fixed in a future CLwB release, but in the meantime, fix it
+ * ourselves.
+ */
+public class BlazeCppAutoImportHelper extends OCDefaultAutoImportHelper {
+
+  @Override
+  public boolean supports(OCResolveRootAndConfiguration rootAndConfiguration) {
+    return rootAndConfiguration.getConfiguration()
+        instanceof com.google.idea.blaze.cpp.BlazeResolveConfiguration;
+  }
+
+  /**
+   * Search in project header roots only. All other cases are covered by CLion's default
+   * implementation.
+   */
+  @Override
+  public boolean processPathSpecificationToInclude(
+      Project project,
+      @Nullable VirtualFile targetFile,
+      final VirtualFile fileToImport,
+      OCResolveRootAndConfiguration rootAndConfiguration,
+      Processor<ImportSpecification> processor) {
+    String name = fileToImport.getName();
+    String path = fileToImport.getPath();
+
+    VirtualFile targetFileParent = targetFile != null ? targetFile.getParent() : null;
+
+    if (targetFileParent != null && targetFileParent.equals(fileToImport.getParent())) {
+      if (!processor.process(
+          new ImportSpecification(name, ImportSpecification.Kind.PROJECT_HEADER))) {
+        return false;
+      }
+    }
+
+    for (PsiFileSystemItem root : rootAndConfiguration.getProjectHeadersRoots().getRoots()) {
+      if (!(root instanceof IncludedHeadersRoot)) {
+        continue;
+      }
+      VirtualFile rootBase = root.getVirtualFile();
+      String relativePath = VfsUtilCore.getRelativePath(fileToImport, rootBase);
+      if (relativePath == null) {
+        continue;
+      }
+      if (!processor.process(
+          new ImportSpecification(relativePath, ImportSpecification.Kind.PROJECT_HEADER))) {
+        return false;
+      }
+    }
+    return true;
+  }
+}
diff --git a/cpp/src/com/google/idea/blaze/cpp/BlazeLanguageKindCalculatorHelper.java b/cpp/src/com/google/idea/blaze/cpp/BlazeLanguageKindCalculatorHelper.java
new file mode 100644
index 0000000..254c94a
--- /dev/null
+++ b/cpp/src/com/google/idea/blaze/cpp/BlazeLanguageKindCalculatorHelper.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.cpp;
+
+import com.google.idea.blaze.base.settings.Blaze;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.io.FileUtilRt;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.jetbrains.cidr.lang.OCLanguageKind;
+import com.jetbrains.cidr.lang.workspace.OCLanguageKindCalculatorHelper;
+import javax.annotation.Nullable;
+
+final class BlazeLanguageKindCalculatorHelper implements OCLanguageKindCalculatorHelper {
+  @Nullable
+  @Override
+  public OCLanguageKind getSpecifiedLanguage(Project project, VirtualFile file) {
+    return null;
+  }
+
+  @Nullable
+  @Override
+  public OCLanguageKind getLanguageByExtension(Project project, String name) {
+    if (Blaze.isBlazeProject(project)) {
+      String extension = FileUtilRt.getExtension(name);
+      if (extension.equalsIgnoreCase("c")) {
+        return OCLanguageKind.C;
+      }
+      if (extension.equalsIgnoreCase("cc")) {
+        return OCLanguageKind.CPP;
+      }
+    }
+    return null;
+  }
+}
diff --git a/cpp/src/com/google/idea/blaze/cpp/BlazeResolveConfigurationTemporaryBase.java b/cpp/src/com/google/idea/blaze/cpp/BlazeResolveConfigurationTemporaryBase.java
new file mode 100644
index 0000000..a710707
--- /dev/null
+++ b/cpp/src/com/google/idea/blaze/cpp/BlazeResolveConfigurationTemporaryBase.java
@@ -0,0 +1,375 @@
+/*
+ * 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.cpp;
+
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMultimap;
+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.CToolchainIdeInfo;
+import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+import com.google.idea.blaze.base.model.RuleMap;
+import com.google.idea.blaze.base.model.primitives.ExecutionRootPath;
+import com.google.idea.blaze.base.model.primitives.Label;
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.scope.Scope;
+import com.google.idea.blaze.base.scope.scopes.TimingScope;
+import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver;
+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;
+import com.jetbrains.cidr.lang.preprocessor.OCImportGraph;
+import com.jetbrains.cidr.lang.workspace.OCLanguageKindCalculator;
+import com.jetbrains.cidr.lang.workspace.OCResolveConfiguration;
+import com.jetbrains.cidr.lang.workspace.OCResolveRootAndConfiguration;
+import com.jetbrains.cidr.lang.workspace.OCWorkspaceUtil;
+import com.jetbrains.cidr.lang.workspace.compiler.CidrCompilerResult;
+import com.jetbrains.cidr.lang.workspace.compiler.OCCompilerMacros;
+import com.jetbrains.cidr.lang.workspace.compiler.OCCompilerSettings;
+import com.jetbrains.cidr.lang.workspace.headerRoots.HeaderRoots;
+import com.jetbrains.cidr.lang.workspace.headerRoots.HeadersSearchRoot;
+import com.jetbrains.cidr.lang.workspace.headerRoots.IncludedHeadersRoot;
+import com.jetbrains.cidr.toolchains.CompilerInfoCache;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import javax.annotation.Nullable;
+
+/**
+ * This is a temporary base class to deal with API changes between v145 (Android Studio) and v162
+ * (CLion). Once Android Studio's API has caught up, the features in versioned/v162 can be merged,
+ * this class be renamed BlazeResolveConfiguration, and it can be made final.
+ */
+abstract class BlazeResolveConfigurationTemporaryBase extends UserDataHolderBase
+    implements OCResolveConfiguration {
+
+  public static final Logger LOG = Logger.getInstance(BlazeResolveConfiguration.class);
+
+  private final WorkspacePathResolver workspacePathResolver;
+
+  /* project, label are protected instead of private just so v145 can access */
+  protected final Project project;
+  protected final Label label;
+
+  private final ImmutableList<HeadersSearchRoot> cLibraryIncludeRoots;
+  private final ImmutableList<HeadersSearchRoot> cppLibraryIncludeRoots;
+  private final HeaderRoots projectIncludeRoots;
+
+  private final CompilerInfoCache compilerInfoCache;
+  private final BlazeCompilerMacros compilerMacros;
+  private final BlazeCompilerSettings compilerSettings;
+
+  @Nullable
+  public static BlazeResolveConfiguration createConfigurationForTarget(
+      Project project,
+      WorkspacePathResolver workspacePathResolver,
+      RuleIdeInfo ruleIdeInfo,
+      CToolchainIdeInfo toolchainIdeInfo,
+      File compilerWrapper) {
+    CRuleIdeInfo cRuleIdeInfo = ruleIdeInfo.cRuleIdeInfo;
+    if (cRuleIdeInfo == null) {
+      return null;
+    }
+
+    ImmutableSet.Builder<ExecutionRootPath> systemIncludesBuilder = ImmutableSet.builder();
+    systemIncludesBuilder.addAll(cRuleIdeInfo.transitiveSystemIncludeDirectories);
+    systemIncludesBuilder.addAll(toolchainIdeInfo.builtInIncludeDirectories);
+    systemIncludesBuilder.addAll(toolchainIdeInfo.unfilteredToolchainSystemIncludes);
+
+    ImmutableSet.Builder<ExecutionRootPath> userIncludesBuilder = ImmutableSet.builder();
+    userIncludesBuilder.addAll(cRuleIdeInfo.transitiveIncludeDirectories);
+
+    ImmutableSet.Builder<ExecutionRootPath> userQuoteIncludesBuilder = ImmutableSet.builder();
+    userQuoteIncludesBuilder.addAll(cRuleIdeInfo.transitiveQuoteIncludeDirectories);
+
+    ImmutableList.Builder<String> cFlagsBuilder = ImmutableList.builder();
+    cFlagsBuilder.addAll(toolchainIdeInfo.baseCompilerOptions);
+    cFlagsBuilder.addAll(toolchainIdeInfo.cCompilerOptions);
+    cFlagsBuilder.addAll(toolchainIdeInfo.unfilteredCompilerOptions);
+
+    ImmutableList.Builder<String> cppFlagsBuilder = ImmutableList.builder();
+    cppFlagsBuilder.addAll(toolchainIdeInfo.baseCompilerOptions);
+    cppFlagsBuilder.addAll(toolchainIdeInfo.cppCompilerOptions);
+    cppFlagsBuilder.addAll(toolchainIdeInfo.unfilteredCompilerOptions);
+
+    ImmutableMap<String, String> features = ImmutableMap.of();
+
+    return new BlazeResolveConfiguration(
+        project,
+        workspacePathResolver,
+        ruleIdeInfo.label,
+        systemIncludesBuilder.build(),
+        systemIncludesBuilder.build(),
+        userQuoteIncludesBuilder.build(),
+        userIncludesBuilder.build(),
+        userIncludesBuilder.build(),
+        cRuleIdeInfo.transitiveDefines,
+        features,
+        compilerWrapper,
+        compilerWrapper,
+        cFlagsBuilder.build(),
+        cppFlagsBuilder.build());
+  }
+
+  public static ImmutableMap<Label, CToolchainIdeInfo> buildToolchainLookupMap(
+      BlazeContext context, RuleMap ruleMap, ImmutableMultimap<Label, Label> reverseDependencies) {
+    return Scope.push(
+        context,
+        childContext -> {
+          childContext.push(new TimingScope("Build toolchain lookup map"));
+
+          List<Label> seeds = Lists.newArrayList();
+          for (RuleIdeInfo rule : ruleMap.rules()) {
+            Label label = rule.label;
+            CToolchainIdeInfo cToolchainIdeInfo = rule.cToolchainIdeInfo;
+            if (cToolchainIdeInfo != null) {
+              seeds.add(label);
+            }
+          }
+
+          Map<Label, CToolchainIdeInfo> lookupTable = Maps.newHashMap();
+          for (Label seed : seeds) {
+            CToolchainIdeInfo toolchainInfo = ruleMap.get(seed).cToolchainIdeInfo;
+            LOG.assertTrue(toolchainInfo != null);
+            List<Label> worklist = Lists.newArrayList(reverseDependencies.get(seed));
+            while (!worklist.isEmpty()) {
+              // We should never see a label depend on two different toolchains.
+              Label l = worklist.remove(0);
+              CToolchainIdeInfo previousValue = lookupTable.putIfAbsent(l, toolchainInfo);
+              // Don't propagate the toolchain twice.
+              if (previousValue == null) {
+                worklist.addAll(reverseDependencies.get(l));
+              } else {
+                LOG.assertTrue(previousValue.equals(toolchainInfo));
+              }
+            }
+          }
+          return ImmutableMap.copyOf(lookupTable);
+        });
+  }
+
+  public BlazeResolveConfigurationTemporaryBase(
+      Project project,
+      WorkspacePathResolver workspacePathResolver,
+      Label label,
+      ImmutableCollection<ExecutionRootPath> cSystemIncludeDirs,
+      ImmutableCollection<ExecutionRootPath> cppSystemIncludeDirs,
+      ImmutableCollection<ExecutionRootPath> quoteIncludeDirs,
+      ImmutableCollection<ExecutionRootPath> cIncludeDirs,
+      ImmutableCollection<ExecutionRootPath> cppIncludeDirs,
+      ImmutableCollection<String> defines,
+      ImmutableMap<String, String> features,
+      File cCompilerExecutable,
+      File cppCompilerExecutable,
+      ImmutableList<String> cCompilerFlags,
+      ImmutableList<String> cppCompilerFlags) {
+    this.workspacePathResolver = workspacePathResolver;
+    this.project = project;
+    this.label = label;
+
+    ImmutableList.Builder<HeadersSearchRoot> cIncludeRootsBuilder = ImmutableList.builder();
+    collectHeaderRoots(cIncludeRootsBuilder, cIncludeDirs, true /* isUserHeader */);
+    collectHeaderRoots(cIncludeRootsBuilder, cSystemIncludeDirs, false /* isUserHeader */);
+    this.cLibraryIncludeRoots = cIncludeRootsBuilder.build();
+
+    ImmutableList.Builder<HeadersSearchRoot> cppIncludeRootsBuilder = ImmutableList.builder();
+    collectHeaderRoots(cppIncludeRootsBuilder, cppIncludeDirs, true /* isUserHeader */);
+    collectHeaderRoots(cppIncludeRootsBuilder, cppSystemIncludeDirs, false /* isUserHeader */);
+    this.cppLibraryIncludeRoots = cppIncludeRootsBuilder.build();
+
+    ImmutableList.Builder<HeadersSearchRoot> quoteIncludeRootsBuilder = ImmutableList.builder();
+    collectHeaderRoots(quoteIncludeRootsBuilder, quoteIncludeDirs, true /* isUserHeader */);
+    this.projectIncludeRoots = new HeaderRoots(quoteIncludeRootsBuilder.build());
+
+    this.compilerSettings =
+        new BlazeCompilerSettings(
+            project, cCompilerExecutable, cppCompilerExecutable, cCompilerFlags, cppCompilerFlags);
+
+    this.compilerInfoCache = new CompilerInfoCache();
+    this.compilerMacros =
+        new BlazeCompilerMacros(project, compilerInfoCache, compilerSettings, defines, features);
+  }
+
+  @Override
+  public Project getProject() {
+    return project;
+  }
+
+  @Override
+  public String getDisplayName(boolean shorten) {
+    return label.toString();
+  }
+
+  @Nullable
+  @Override
+  public VirtualFile getPrecompiledHeader() {
+    return null;
+  }
+
+  @Nullable
+  @Override
+  public OCLanguageKind getDeclaredLanguageKind(VirtualFile sourceOrHeaderFile) {
+    String fileName = sourceOrHeaderFile.getName();
+    if (OCFileTypeHelpers.isSourceFile(fileName)) {
+      return getLanguageKind(sourceOrHeaderFile);
+    }
+
+    if (OCFileTypeHelpers.isHeaderFile(fileName)) {
+      return getLanguageKind(getSourceFileForHeaderFile(sourceOrHeaderFile));
+    }
+
+    return null;
+  }
+
+  private OCLanguageKind getLanguageKind(@Nullable VirtualFile sourceFile) {
+    OCLanguageKind kind = OCLanguageKindCalculator.tryFileTypeAndExtension(project, sourceFile);
+    return kind != null ? kind : getMaximumLanguageKind();
+  }
+
+  @Nullable
+  private VirtualFile getSourceFileForHeaderFile(VirtualFile headerFile) {
+    ArrayList<VirtualFile> roots =
+        new ArrayList<>(OCImportGraph.getAllHeaderRoots(project, headerFile));
+
+    final String headerNameWithoutExtension = headerFile.getNameWithoutExtension();
+    for (VirtualFile root : roots) {
+      if (root.getNameWithoutExtension().equals(headerNameWithoutExtension)) {
+        return root;
+      }
+    }
+    return null;
+  }
+
+  @Override
+  public OCLanguageKind getPrecompiledLanguageKind() {
+    return getMaximumLanguageKind();
+  }
+
+  @Override
+  public OCLanguageKind getMaximumLanguageKind() {
+    return OCLanguageKind.CPP;
+  }
+
+  @Override
+  public HeaderRoots getProjectHeadersRoots() {
+    return projectIncludeRoots;
+  }
+
+  @Override
+  public HeaderRoots getLibraryHeadersRoots(OCResolveRootAndConfiguration headerContext) {
+    OCLanguageKind languageKind = headerContext.getKind();
+    VirtualFile sourceFile = headerContext.getRootFile();
+    if (languageKind == null) {
+      languageKind = getLanguageKind(sourceFile);
+    }
+
+    ImmutableSet.Builder<HeadersSearchRoot> roots = ImmutableSet.builder();
+    if (languageKind == OCLanguageKind.C) {
+      roots.addAll(cLibraryIncludeRoots);
+    } else {
+      roots.addAll(cppLibraryIncludeRoots);
+    }
+
+    CidrCompilerResult<CompilerInfoCache.Entry> compilerInfoCacheHolder =
+        compilerInfoCache.getCompilerInfoCache(project, compilerSettings, languageKind, sourceFile);
+    CompilerInfoCache.Entry compilerInfo = compilerInfoCacheHolder.getResult();
+    if (compilerInfo != null) {
+      roots.addAll(compilerInfo.headerSearchPaths);
+    }
+    return new HeaderRoots(roots.build().asList());
+  }
+
+  private void collectHeaderRoots(
+      ImmutableList.Builder<HeadersSearchRoot> roots,
+      ImmutableCollection<ExecutionRootPath> paths,
+      boolean isUserHeader) {
+    for (ExecutionRootPath executionRootPath : paths) {
+      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 {
+          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;
+  }
+
+  @Override
+  public OCCompilerSettings getCompilerSettings() {
+    return compilerSettings;
+  }
+
+  @Nullable
+  @Override
+  public Object getIndexingCluster() {
+    return null;
+  }
+
+  @Override
+  public int compareTo(OCResolveConfiguration other) {
+    return OCWorkspaceUtil.compareConfigurations(this, other);
+  }
+
+  @Override
+  public int hashCode() {
+    // There should only be one configuration per label.
+    return Objects.hash(label);
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+
+    if (!(obj instanceof BlazeResolveConfiguration)) {
+      return false;
+    }
+
+    BlazeResolveConfiguration that = (BlazeResolveConfiguration) obj;
+    return compareTo(that) == 0;
+  }
+}
diff --git a/cpp/src/com/google/idea/blaze/cpp/CPrefetchFileSource.java b/cpp/src/com/google/idea/blaze/cpp/CPrefetchFileSource.java
new file mode 100644
index 0000000..ff0b0ed
--- /dev/null
+++ b/cpp/src/com/google/idea/blaze/cpp/CPrefetchFileSource.java
@@ -0,0 +1,36 @@
+/*
+ * 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.cpp;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.prefetch.PrefetchFileSource;
+import com.intellij.openapi.project.Project;
+import java.io.File;
+import java.util.Collection;
+import java.util.Set;
+
+/** Causes C files to become prefetched. */
+public class CPrefetchFileSource implements PrefetchFileSource {
+  @Override
+  public void addFilesToPrefetch(
+      Project project, BlazeProjectData blazeProjectData, Collection<File> files) {}
+
+  @Override
+  public Set<String> prefetchSrcFileExtensions() {
+    return ImmutableSet.of("c", "cc", "cpp", "h", "hh", "hpp");
+  }
+}
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
new file mode 100644
index 0000000..7704f4f
--- /dev/null
+++ b/cpp/src/com/google/idea/blaze/cpp/versioned/v145/BlazeResolveConfiguration.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.cpp;
+
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.idea.blaze.base.model.primitives.ExecutionRootPath;
+import com.google.idea.blaze.base.model.primitives.Label;
+import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver;
+import com.intellij.openapi.project.Project;
+import com.jetbrains.cidr.execution.CidrBuildTarget;
+import com.jetbrains.cidr.execution.CidrBuildTargetWithConfigurations;
+import com.jetbrains.cidr.execution.CidrTargetHolder;
+import java.io.File;
+import java.util.List;
+import javax.annotation.Nullable;
+import javax.swing.Icon;
+
+final class BlazeResolveConfiguration extends BlazeResolveConfigurationTemporaryBase
+    implements CidrTargetHolder {
+
+  public BlazeResolveConfiguration(
+      Project project,
+      WorkspacePathResolver workspacePathResolver,
+      Label label,
+      ImmutableCollection<ExecutionRootPath> cSystemIncludeDirs,
+      ImmutableCollection<ExecutionRootPath> cppSystemIncludeDirs,
+      ImmutableCollection<ExecutionRootPath> quoteIncludeDirs,
+      ImmutableCollection<ExecutionRootPath> cIncludeDirs,
+      ImmutableCollection<ExecutionRootPath> cppIncludeDirs,
+      ImmutableCollection<String> defines,
+      ImmutableMap<String, String> features,
+      File cCompilerExecutable,
+      File cppCompilerExecutable,
+      ImmutableList<String> cCompilerFlags,
+      ImmutableList<String> cppCompilerFlags) {
+    super(
+        project,
+        workspacePathResolver,
+        label,
+        cSystemIncludeDirs,
+        cppSystemIncludeDirs,
+        quoteIncludeDirs,
+        cIncludeDirs,
+        cppIncludeDirs,
+        defines,
+        features,
+        cCompilerExecutable,
+        cppCompilerExecutable,
+        cCompilerFlags,
+        cppCompilerFlags);
+  }
+
+  /** Workaround for b/30301958. TODO: Remove this once we move to CLion 162.1531.1 or later */
+  @Override
+  public CidrBuildTarget getTarget() {
+    return new CidrBuildTargetWithConfigurations() {
+      @Override
+      public String getName() {
+        return label.toString();
+      }
+
+      @Override
+      public String getProjectName() {
+        return project.getName();
+      }
+
+      @Nullable
+      @Override
+      public Icon getIcon() {
+        return null;
+      }
+
+      @Override
+      public boolean isExecutable() {
+        return false;
+      }
+
+      @Override
+      public List getBuildConfigurations() {
+        return ImmutableList.of();
+      }
+    };
+  }
+}
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
new file mode 100644
index 0000000..c5c5e86
--- /dev/null
+++ b/cpp/src/com/google/idea/blaze/cpp/versioned/v162/BlazeResolveConfiguration.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.cpp;
+
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.idea.blaze.base.model.primitives.ExecutionRootPath;
+import com.google.idea.blaze.base.model.primitives.Label;
+import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver;
+import com.intellij.openapi.project.Project;
+import com.jetbrains.cidr.modulemap.ModuleMapModules;
+import java.io.File;
+import org.jetbrains.annotations.NotNull;
+
+final class BlazeResolveConfiguration extends BlazeResolveConfigurationTemporaryBase {
+
+  public BlazeResolveConfiguration(
+      Project project,
+      WorkspacePathResolver workspacePathResolver,
+      Label label,
+      ImmutableCollection<ExecutionRootPath> cSystemIncludeDirs,
+      ImmutableCollection<ExecutionRootPath> cppSystemIncludeDirs,
+      ImmutableCollection<ExecutionRootPath> quoteIncludeDirs,
+      ImmutableCollection<ExecutionRootPath> cIncludeDirs,
+      ImmutableCollection<ExecutionRootPath> cppIncludeDirs,
+      ImmutableCollection<String> defines,
+      ImmutableMap<String, String> features,
+      File cCompilerExecutable,
+      File cppCompilerExecutable,
+      ImmutableList<String> cCompilerFlags,
+      ImmutableList<String> cppCompilerFlags) {
+    super(
+        project,
+        workspacePathResolver,
+        label,
+        cSystemIncludeDirs,
+        cppSystemIncludeDirs,
+        quoteIncludeDirs,
+        cIncludeDirs,
+        cppIncludeDirs,
+        defines,
+        features,
+        cCompilerExecutable,
+        cppCompilerExecutable,
+        cCompilerFlags,
+        cppCompilerFlags);
+  }
+
+  @NotNull
+  @Override
+  public ModuleMapModules getModules() {
+    return ModuleMapModules.Companion.getEMPTY();
+  }
+}
diff --git a/cpp/tests/unittests/com/google/idea/blaze/cpp/BlazeCompilerSettingsTest.java b/cpp/tests/unittests/com/google/idea/blaze/cpp/BlazeCompilerSettingsTest.java
new file mode 100644
index 0000000..317d838
--- /dev/null
+++ b/cpp/tests/unittests/com/google/idea/blaze/cpp/BlazeCompilerSettingsTest.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.cpp;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.base.BlazeTestCase;
+import com.jetbrains.cidr.lang.OCLanguageKind;
+import com.jetbrains.cidr.lang.toolchains.CidrCompilerSwitches;
+import java.io.File;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link BlazeCompilerSettings}. */
+@RunWith(JUnit4.class)
+public class BlazeCompilerSettingsTest extends BlazeTestCase {
+
+  @Test
+  public void testCompilerSwitchesSimple() {
+    File cppExe = new File("bin/cpp");
+    ImmutableList<String> cFlags = ImmutableList.of("-fast", "-slow");
+    BlazeCompilerSettings settings =
+        new BlazeCompilerSettings(getProject(), cppExe, cppExe, cFlags, cFlags);
+
+    CidrCompilerSwitches compilerSwitches = settings.getCompilerSwitches(OCLanguageKind.C, null);
+    List<String> commandLineArgs = compilerSwitches.getFileArgs();
+    assertThat(commandLineArgs).containsExactly("-fast", "-slow");
+  }
+
+  @Test
+  public void testCompilerSwitchesWithUnescapedSpaces() {
+    File cppExe = new File("bin/cpp");
+    ImmutableList<String> cFlags = ImmutableList.of("-f ast", "-slo w");
+    BlazeCompilerSettings settings =
+        new BlazeCompilerSettings(getProject(), cppExe, cppExe, cFlags, cFlags);
+
+    CidrCompilerSwitches compilerSwitches = settings.getCompilerSwitches(OCLanguageKind.C, null);
+    List<String> fileArgs = compilerSwitches.getFileArgs();
+    assertThat(fileArgs).containsExactly("-f", "ast", "-slo", "w");
+  }
+
+  @Test
+  public void testCompilerSwitchesWithEscapedSpaces() {
+    File cppExe = new File("bin/cpp");
+    ImmutableList<String> cFlags = ImmutableList.of("-f\\ ast", "-slo\\ w");
+    BlazeCompilerSettings settings =
+        new BlazeCompilerSettings(getProject(), cppExe, cppExe, cFlags, cFlags);
+
+    CidrCompilerSwitches compilerSwitches = settings.getCompilerSwitches(OCLanguageKind.C, null);
+    List<String> fileArgs = compilerSwitches.getFileArgs();
+    assertThat(fileArgs).containsExactly("-f", "ast", "-slo", "w");
+  }
+
+  @Test
+  public void testCompilerSwitchesWithUnescapedAndEscapedSpaces() {
+    File cppExe = new File("bin/cpp");
+    ImmutableList<String> cFlags = ImmutableList.of("-f ast", "-slo\\ w");
+    BlazeCompilerSettings settings =
+        new BlazeCompilerSettings(getProject(), cppExe, cppExe, cFlags, cFlags);
+
+    CidrCompilerSwitches compilerSwitches = settings.getCompilerSwitches(OCLanguageKind.C, null);
+    List<String> fileArgs = compilerSwitches.getFileArgs();
+    assertThat(fileArgs).containsExactly("-f", "ast", "-slo", "w");
+  }
+}
diff --git a/ijwb/.bazelproject b/ijwb/.bazelproject
index 3dc3303..f066e1b 100644
--- a/ijwb/.bazelproject
+++ b/ijwb/.bazelproject
@@ -1,6 +1,7 @@
 directories:
   .
   -aswb
+  -aswb-google3
   -clwb
   -blaze-cpp
 
diff --git a/ijwb/BUILD b/ijwb/BUILD
index c681b39..fdc10ee 100644
--- a/ijwb/BUILD
+++ b/ijwb/BUILD
@@ -2,25 +2,28 @@
 # Description: Builds ijwb
 #
 
+licenses(["notice"])  # Apache 2.0
+
 load(
     "//build_defs:build_defs.bzl",
+    "intellij_plugin",
     "merged_plugin_xml",
     "stamped_plugin_xml",
-    "intellij_plugin",
 )
 
 merged_plugin_xml(
     name = "merged_plugin_xml_common",
     srcs = [
         "src/META-INF/ijwb.xml",
-        "//blaze-base:plugin_xml",
-        "//blaze-java:plugin_xml",
-        "//blaze-plugin-dev:plugin_xml",
+        "//base:plugin_xml",
+        "//java:plugin_xml",
+        "//plugin_dev:plugin_xml",
     ],
+    visibility = ["//visibility:public"],
 )
 
 merged_plugin_xml(
-    name = "merged_plugin_xml_bazel",
+    name = "merged_plugin_xml",
     srcs = [
         "src/META-INF/ijwb_bazel.xml",
         ":merged_plugin_xml_common",
@@ -28,9 +31,11 @@
 )
 
 stamped_plugin_xml(
-    name = "stamped_plugin_xml_bazel",
+    name = "stamped_plugin_xml",
     include_product_code_in_stamp = True,
-    plugin_xml = ":merged_plugin_xml_bazel",
+    plugin_id = "com.google.idea.bazel.ijwb",
+    plugin_name = "IntelliJ with Bazel",
+    plugin_xml = ":merged_plugin_xml",
     stamp_since_build = True,
     version_file = "//:version",
 )
@@ -38,20 +43,21 @@
 java_library(
     name = "ijwb_lib",
     srcs = glob(["src/**/*.java"]),
+    visibility = ["//visibility:public"],
     exports = [
-        "//blaze-plugin-dev",
+        "//plugin_dev",
     ],
     deps = [
-        "//blaze-base",
-        "//blaze-java",
-        "//intellij-platform-sdk:plugin_api",
-        "//third_party:jsr305",
+        "//base",
+        "//intellij_platform_sdk:plugin_api",
+        "//java",
+        "@jsr305_annotations//jar",
     ],
 )
 
 intellij_plugin(
     name = "ijwb_bazel",
-    plugin_xml = ":stamped_plugin_xml_bazel",
+    plugin_xml = ":stamped_plugin_xml",
     deps = [
         ":ijwb_lib",
     ],
diff --git a/ijwb/plugin/META-INF/.gitignore b/ijwb/plugin/META-INF/.gitignore
new file mode 100644
index 0000000..3a1130b
--- /dev/null
+++ b/ijwb/plugin/META-INF/.gitignore
@@ -0,0 +1 @@
+plugin.xml
\ No newline at end of file
diff --git a/ijwb/src/META-INF/ijwb.xml b/ijwb/src/META-INF/ijwb.xml
index 988e14e..b5fcb39 100644
--- a/ijwb/src/META-INF/ijwb.xml
+++ b/ijwb/src/META-INF/ijwb.xml
@@ -14,7 +14,6 @@
   ~ limitations under the License.
   -->
 <idea-plugin>
-  <id>com.google.idea.blaze.ijwb</id>
   <vendor>Google</vendor>
 
   <extensions defaultExtensionNs="com.intellij">
diff --git a/ijwb/src/META-INF/ijwb_bazel.xml b/ijwb/src/META-INF/ijwb_bazel.xml
index a258cab..2068ad9 100644
--- a/ijwb/src/META-INF/ijwb_bazel.xml
+++ b/ijwb/src/META-INF/ijwb_bazel.xml
@@ -15,10 +15,25 @@
   -->
 <idea-plugin>
 
-  <name>IntelliJ with Bazel</name>
-
   <description>
-    Provides the ability to import Bazel Java projects in IntelliJ.
+    <![CDATA[
+      <a href="http://bazel.io">Bazel</a> support for IntelliJ.
+
+      Features:
+        <ul>
+        <li>Import BUILD files into the IDE.</li>
+        <li>BUILD file custom language support.</li>
+        <li>Support for blaze run configurations for certain rule classes.</li>
+        </ul>
+
+      Usage instructions at <a href="http://ij.bazel.io">ij.bazel.io</a>
+      ]]>
   </description>
 
+  <application-components>
+    <component>
+      <implementation-class>com.google.idea.blaze.ijwb.plugin.MigrateBazelPluginDependency</implementation-class>
+    </component>
+  </application-components>
+
 </idea-plugin>
\ No newline at end of file
diff --git a/ijwb/src/META-INF/ijwb_blaze.xml b/ijwb/src/META-INF/ijwb_blaze.xml
deleted file mode 100644
index eaef611..0000000
--- a/ijwb/src/META-INF/ijwb_blaze.xml
+++ /dev/null
@@ -1,24 +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.
-  -->
-<idea-plugin>
-
-  <name>IntelliJ with Blaze</name>
-
-  <description>
-    Provides the ability to import Blaze Java projects in IntelliJ.
-  </description>
-
-</idea-plugin>
\ No newline at end of file
diff --git a/ijwb/src/com/google/idea/blaze/ijwb/android/BlazeAndroidLiteImportResult.java b/ijwb/src/com/google/idea/blaze/ijwb/android/BlazeAndroidLiteImportResult.java
deleted file mode 100644
index 05c2b10..0000000
--- a/ijwb/src/com/google/idea/blaze/ijwb/android/BlazeAndroidLiteImportResult.java
+++ /dev/null
@@ -1,37 +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.android;
-
-import com.google.common.collect.ImmutableCollection;
-import com.google.idea.blaze.java.sync.model.BlazeLibrary;
-
-import javax.annotation.concurrent.Immutable;
-import java.io.Serializable;
-
-/**
- * The result of a blaze import operation.
- */
-@Immutable
-public class BlazeAndroidLiteImportResult implements Serializable {
-  private static final long serialVersionUID = 1L;
-
-  public final ImmutableCollection<BlazeLibrary> libraries;
-
-  public BlazeAndroidLiteImportResult(
-    ImmutableCollection<BlazeLibrary> libraries) {
-    this.libraries = libraries;
-  }
-}
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 fabfb7d..0d864b8 100644
--- a/ijwb/src/com/google/idea/blaze/ijwb/android/BlazeAndroidLiteJavaSyncAugmenter.java
+++ b/ijwb/src/com/google/idea/blaze/ijwb/android/BlazeAndroidLiteJavaSyncAugmenter.java
@@ -15,34 +15,40 @@
  */
 package com.google.idea.blaze.ijwb.android;
 
-import com.google.common.collect.ImmutableList;
-import com.google.idea.blaze.base.model.BlazeProjectData;
-import com.google.idea.blaze.base.projectview.section.Glob;
+import com.google.idea.blaze.base.ideinfo.AndroidRuleIdeInfo;
+import com.google.idea.blaze.base.ideinfo.LibraryArtifact;
+import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+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 com.google.idea.blaze.java.sync.model.BlazeLibrary;
-
+import com.google.idea.blaze.java.sync.model.BlazeJarLibrary;
 import java.util.Collection;
 
-/**
- * Augments the java sync process with Android lite support.
- */
-public class BlazeAndroidLiteJavaSyncAugmenter implements BlazeJavaSyncAugmenter {
+/** Augments the java sync process with Android lite support. */
+public class BlazeAndroidLiteJavaSyncAugmenter extends BlazeJavaSyncAugmenter.Adapter {
 
   @Override
-  public void addLibraryFilter(Glob.GlobSet excludedLibraries) {
+  public boolean isActive(WorkspaceLanguageSettings workspaceLanguageSettings) {
+    return workspaceLanguageSettings.isLanguageActive(LanguageClass.ANDROID);
   }
 
   @Override
-  public Collection<BlazeLibrary> getAdditionalLibraries(BlazeProjectData blazeProjectData) {
-    BlazeAndroidLiteSyncData syncData = blazeProjectData.syncState.get(BlazeAndroidLiteSyncData.class);
-    if (syncData == null) {
-      return ImmutableList.of();
+  public void addJarsForSourceRule(
+      RuleIdeInfo rule, Collection<BlazeJarLibrary> jars, Collection<BlazeJarLibrary> genJars) {
+    AndroidRuleIdeInfo androidRuleIdeInfo = rule.androidRuleIdeInfo;
+    if (androidRuleIdeInfo == null) {
+      return;
     }
-    return syncData.importResult.libraries;
-  }
 
-  @Override
-  public Collection<String> getExternallyAddedLibraries(BlazeProjectData blazeProjectData) {
-    return ImmutableList.of();
+    // Add R.java jars
+    LibraryArtifact resourceJar = androidRuleIdeInfo.resourceJar;
+    if (resourceJar != null) {
+      jars.add(new BlazeJarLibrary(resourceJar, rule.label));
+    }
+
+    LibraryArtifact idlJar = androidRuleIdeInfo.idlJar;
+    if (idlJar != null) {
+      genJars.add(new BlazeJarLibrary(idlJar, rule.label));
+    }
   }
 }
diff --git a/ijwb/src/com/google/idea/blaze/ijwb/android/BlazeAndroidLiteSyncData.java b/ijwb/src/com/google/idea/blaze/ijwb/android/BlazeAndroidLiteSyncData.java
deleted file mode 100644
index b510f05..0000000
--- a/ijwb/src/com/google/idea/blaze/ijwb/android/BlazeAndroidLiteSyncData.java
+++ /dev/null
@@ -1,33 +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.android;
-
-import javax.annotation.concurrent.Immutable;
-import java.io.Serializable;
-
-/**
- * Sync data for the Android lite plugin.
- */
-@Immutable
-public class BlazeAndroidLiteSyncData implements Serializable {
-  private static final long serialVersionUID = 1L;
-
-  public final BlazeAndroidLiteImportResult importResult;
-
-  public BlazeAndroidLiteSyncData(BlazeAndroidLiteImportResult importResult) {
-    this.importResult = importResult;
-  }
-}
diff --git a/ijwb/src/com/google/idea/blaze/ijwb/android/BlazeAndroidLiteSyncPlugin.java b/ijwb/src/com/google/idea/blaze/ijwb/android/BlazeAndroidLiteSyncPlugin.java
index a799d7e..4a9612d 100644
--- a/ijwb/src/com/google/idea/blaze/ijwb/android/BlazeAndroidLiteSyncPlugin.java
+++ b/ijwb/src/com/google/idea/blaze/ijwb/android/BlazeAndroidLiteSyncPlugin.java
@@ -15,34 +15,14 @@
  */
 package com.google.idea.blaze.ijwb.android;
 
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.model.SyncState;
-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.WorkspaceRoot;
 import com.google.idea.blaze.base.model.primitives.WorkspaceType;
-import com.google.idea.blaze.base.projectview.ProjectViewSet;
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.google.idea.blaze.base.scope.Scope;
-import com.google.idea.blaze.base.scope.scopes.TimingScope;
 import com.google.idea.blaze.base.sync.BlazeSyncPlugin;
-import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
-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.intellij.openapi.project.Project;
-
-import javax.annotation.Nullable;
-import java.io.File;
 import java.util.Set;
 
-/**
- * Rudimentary support for android in IntelliJ.
- */
+/** Rudimentary support for android in IntelliJ. */
 public class BlazeAndroidLiteSyncPlugin extends BlazeSyncPlugin.Adapter {
-
   @Override
   public Set<LanguageClass> getSupportedLanguagesInWorkspace(WorkspaceType workspaceType) {
     switch (workspaceType) {
@@ -53,36 +33,4 @@
         return ImmutableSet.of();
     }
   }
-
-  @Override
-  public void updateSyncState(Project project,
-                              BlazeContext context,
-                              WorkspaceRoot workspaceRoot,
-                              ProjectViewSet projectViewSet,
-                              WorkspaceLanguageSettings workspaceLanguageSettings,
-                              BlazeRoots blazeRoots,
-                              @Nullable WorkingSet workingSet,
-                              WorkspacePathResolver workspacePathResolver,
-                              ImmutableMap<Label, RuleIdeInfo> ruleMap,
-                              @Deprecated @Nullable File androidPlatformDirectory,
-                              SyncState.Builder syncStateBuilder,
-                              @Nullable SyncState previousSyncState) {
-    if (!workspaceLanguageSettings.isLanguageActive(LanguageClass.ANDROID)) {
-      return;
-    }
-
-    BlazeAndroidLiteWorkspaceImporter workspaceImporter = new BlazeAndroidLiteWorkspaceImporter(
-      project,
-      workspaceRoot,
-      context,
-      projectViewSet,
-      ruleMap
-    );
-    BlazeAndroidLiteImportResult importResult = Scope.push(context, childContext -> {
-      childContext.push(new TimingScope("AndroidLiteWorkspaceImporter"));
-      return workspaceImporter.importWorkspace();
-    });
-    BlazeAndroidLiteSyncData syncData = new BlazeAndroidLiteSyncData(importResult);
-    syncStateBuilder.put(BlazeAndroidLiteSyncData.class, syncData);
-  }
 }
diff --git a/ijwb/src/com/google/idea/blaze/ijwb/android/BlazeAndroidLiteWorkspaceImporter.java b/ijwb/src/com/google/idea/blaze/ijwb/android/BlazeAndroidLiteWorkspaceImporter.java
deleted file mode 100644
index c5fd4eb..0000000
--- a/ijwb/src/com/google/idea/blaze/ijwb/android/BlazeAndroidLiteWorkspaceImporter.java
+++ /dev/null
@@ -1,106 +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.android;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.idea.blaze.base.ideinfo.AndroidRuleIdeInfo;
-import com.google.idea.blaze.base.ideinfo.LibraryArtifact;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-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.WorkspaceRoot;
-import com.google.idea.blaze.base.projectview.ProjectViewSet;
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.google.idea.blaze.base.sync.projectview.ProjectViewRuleImportFilter;
-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 java.util.List;
-import java.util.stream.Collectors;
-
-/**
- * Builds a BlazeWorkspace.
- */
-public final class BlazeAndroidLiteWorkspaceImporter {
-  private static final Logger LOG = Logger.getInstance(BlazeAndroidLiteWorkspaceImporter.class);
-
-  private final Project project;
-  private final BlazeContext context;
-  private final ImmutableMap<Label, RuleIdeInfo> ruleMap;
-  private final ProjectViewRuleImportFilter importFilter;
-
-  public BlazeAndroidLiteWorkspaceImporter(
-    Project project,
-    WorkspaceRoot workspaceRoot,
-    BlazeContext context,
-    ProjectViewSet projectViewSet,
-    ImmutableMap<Label, RuleIdeInfo> ruleMap) {
-    this.project = project;
-    this.context = context;
-    this.ruleMap = ruleMap;
-    this.importFilter = new ProjectViewRuleImportFilter(project, workspaceRoot, projectViewSet);
-  }
-
-  public BlazeAndroidLiteImportResult importWorkspace() {
-    List<RuleIdeInfo> rules = ruleMap.values()
-      .stream()
-      .filter(importFilter::isSourceRule)
-      .filter(rule -> rule.kind.getLanguageClass() == LanguageClass.ANDROID)
-      .filter(rule -> !importFilter.excludeTarget(rule))
-      .collect(Collectors.toList());
-
-    WorkspaceBuilder workspaceBuilder = new WorkspaceBuilder();
-
-    for (RuleIdeInfo rule : rules) {
-      addRuleAsSource(
-        workspaceBuilder,
-        rule
-      );
-    }
-
-    return new BlazeAndroidLiteImportResult(
-      workspaceBuilder.libraries.build()
-    );
-  }
-
-  private void addRuleAsSource(
-    WorkspaceBuilder workspaceBuilder,
-    RuleIdeInfo rule) {
-
-    AndroidRuleIdeInfo androidRuleIdeInfo = rule.androidRuleIdeInfo;
-    if (androidRuleIdeInfo != null) {
-      // Add R.java jars
-      LibraryArtifact resourceJar = androidRuleIdeInfo.resourceJar;
-      if (resourceJar != null) {
-        BlazeLibrary library1 = new BlazeLibrary(LibraryKey.fromJarFile(resourceJar.jar.getFile()), resourceJar);
-        workspaceBuilder.libraries.add(library1);
-      }
-
-      LibraryArtifact idlJar = androidRuleIdeInfo.idlJar;
-      if (idlJar != null) {
-        BlazeLibrary library = new BlazeLibrary(LibraryKey.fromJarFile(idlJar.jar.getFile()), idlJar);
-        workspaceBuilder.libraries.add(library);
-      }
-    }
-  }
-
-  static class WorkspaceBuilder {
-    ImmutableList.Builder<BlazeLibrary> libraries = ImmutableList.builder();
-  }
-}
diff --git a/ijwb/src/com/google/idea/blaze/ijwb/dart/BlazeDartJavaSyncAugmenter.java b/ijwb/src/com/google/idea/blaze/ijwb/dart/BlazeDartJavaSyncAugmenter.java
index 0de1f96..98f0f65 100644
--- a/ijwb/src/com/google/idea/blaze/ijwb/dart/BlazeDartJavaSyncAugmenter.java
+++ b/ijwb/src/com/google/idea/blaze/ijwb/dart/BlazeDartJavaSyncAugmenter.java
@@ -18,30 +18,19 @@
 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.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.BlazeLibrary;
-
 import java.util.Collection;
 
-/**
- * Prevents garbage collection of the Dart SDK library.
- */
-public class BlazeDartJavaSyncAugmenter implements BlazeJavaSyncAugmenter {
+/** Prevents garbage collection of the Dart SDK library. */
+public class BlazeDartJavaSyncAugmenter extends BlazeJavaSyncAugmenter.Adapter {
   @Override
-  public void addLibraryFilter(Glob.GlobSet excludedLibraries) {
-  }
-
-  @Override
-  public Collection<BlazeLibrary> getAdditionalLibraries(BlazeProjectData blazeProjectData) {
-    return ImmutableList.of();
+  public boolean isActive(WorkspaceLanguageSettings workspaceLanguageSettings) {
+    return workspaceLanguageSettings.isLanguageActive(LanguageClass.DART);
   }
 
   @Override
   public Collection<String> getExternallyAddedLibraries(BlazeProjectData blazeProjectData) {
-    if (blazeProjectData.workspaceLanguageSettings.isLanguageActive(LanguageClass.DART)) {
-      return ImmutableList.of(BlazeDartSyncPlugin.DART_SDK_LIBRARY_NAME);
-    }
-    return ImmutableList.of();
+    return ImmutableList.of(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 81fbf2b..d9245cd 100644
--- a/ijwb/src/com/google/idea/blaze/ijwb/dart/BlazeDartSyncPlugin.java
+++ b/ijwb/src/com/google/idea/blaze/ijwb/dart/BlazeDartSyncPlugin.java
@@ -31,13 +31,10 @@
 import com.intellij.openapi.roots.ModifiableRootModel;
 import com.intellij.openapi.roots.impl.libraries.ApplicationLibraryTable;
 import com.intellij.openapi.roots.libraries.Library;
-
-import javax.annotation.Nullable;
 import java.util.Set;
+import javax.annotation.Nullable;
 
-/**
- * Supports dart.
- */
+/** Supports dart. */
 public class BlazeDartSyncPlugin extends BlazeSyncPlugin.Adapter {
 
   static final String DART_SDK_LIBRARY_NAME = "Dart SDK";
@@ -49,36 +46,40 @@
   }
 
   @Override
-  public void updateProjectStructure(Project project,
-                                     BlazeContext context,
-                                     WorkspaceRoot workspaceRoot,
-                                     ProjectViewSet projectViewSet,
-                                     BlazeProjectData blazeProjectData,
-                                     @Nullable BlazeProjectData oldBlazeProjectData,
-                                     ModuleEditor moduleEditor,
-                                     Module workspaceModule,
-                                     ModifiableRootModel workspaceModifiableModel) {
+  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.DART)) {
       return;
     }
 
-    Library dartSdkLibrary = ApplicationLibraryTable.getApplicationTable().getLibraryByName(DART_SDK_LIBRARY_NAME);
+    Library dartSdkLibrary =
+        ApplicationLibraryTable.getApplicationTable().getLibraryByName(DART_SDK_LIBRARY_NAME);
     if (dartSdkLibrary != null) {
       if (workspaceModifiableModel.findLibraryOrderEntry(dartSdkLibrary) == null) {
         workspaceModifiableModel.addLibraryEntry(dartSdkLibrary);
       }
     } else {
-      IssueOutput
-        .error("Dart language support is requested, but the Dart SDK was not found. "
-               + "You must manually enable Dart support from File > Settings > Languages & Frameworks > Dart.")
-        .submit(context);
+      IssueOutput.error(
+              "Dart language support is requested, but the Dart SDK was not found. "
+                  + "You must manually enable Dart support from "
+                  + "File > Settings > Languages & Frameworks > Dart.")
+          .submit(context);
     }
   }
 
   @Override
-  public boolean validateProjectView(BlazeContext context,
-                                     ProjectViewSet projectViewSet,
-                                     WorkspaceLanguageSettings workspaceLanguageSettings) {
+  public boolean validateProjectView(
+      BlazeContext context,
+      ProjectViewSet projectViewSet,
+      WorkspaceLanguageSettings workspaceLanguageSettings) {
     if (!workspaceLanguageSettings.isLanguageActive(LanguageClass.DART)) {
       return true;
     }
diff --git a/ijwb/src/com/google/idea/blaze/ijwb/ide/IdeCheck.java b/ijwb/src/com/google/idea/blaze/ijwb/ide/IdeCheck.java
index 33a94ec..617b9a7 100644
--- a/ijwb/src/com/google/idea/blaze/ijwb/ide/IdeCheck.java
+++ b/ijwb/src/com/google/idea/blaze/ijwb/ide/IdeCheck.java
@@ -19,9 +19,7 @@
 import com.intellij.ide.plugins.PluginManager;
 import com.intellij.openapi.extensions.PluginId;
 
-/**
- * IDE and plugin checks.
- */
+/** IDE and plugin checks. */
 public class IdeCheck {
   public static boolean isPluginEnabled(String pluginIdString) {
     PluginId pluginId = PluginId.getId(pluginIdString);
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 b41596e..282a249 100644
--- a/ijwb/src/com/google/idea/blaze/ijwb/javascript/BlazeJavascriptSyncPlugin.java
+++ b/ijwb/src/com/google/idea/blaze/ijwb/javascript/BlazeJavascriptSyncPlugin.java
@@ -33,14 +33,11 @@
 import com.intellij.openapi.roots.ContentEntry;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.util.PlatformUtils;
-
-import javax.annotation.Nullable;
 import java.util.Collection;
 import java.util.Set;
+import javax.annotation.Nullable;
 
-/**
- * Allows people to use a javascript-only workspace.
- */
+/** Allows people to use a javascript-only workspace. */
 public class BlazeJavascriptSyncPlugin extends BlazeSyncPlugin.Adapter {
 
   @Nullable
@@ -58,12 +55,13 @@
   }
 
   @Override
-  public void updateContentEntries(Project project,
-                                   BlazeContext context,
-                                   WorkspaceRoot workspaceRoot,
-                                   ProjectViewSet projectViewSet,
-                                   BlazeProjectData blazeProjectData,
-                                   Collection<ContentEntry> contentEntries) {
+  public void updateContentEntries(
+      Project project,
+      BlazeContext context,
+      WorkspaceRoot workspaceRoot,
+      ProjectViewSet projectViewSet,
+      BlazeProjectData blazeProjectData,
+      Collection<ContentEntry> contentEntries) {
     if (!blazeProjectData.workspaceLanguageSettings.isWorkspaceType(WorkspaceType.JAVASCRIPT)) {
       return;
     }
@@ -84,9 +82,10 @@
   }
 
   @Override
-  public boolean validateProjectView(BlazeContext context,
-                                     ProjectViewSet projectViewSet,
-                                     WorkspaceLanguageSettings workspaceLanguageSettings) {
+  public boolean validateProjectView(
+      BlazeContext context,
+      ProjectViewSet projectViewSet,
+      WorkspaceLanguageSettings workspaceLanguageSettings) {
     if (!workspaceLanguageSettings.isLanguageActive(LanguageClass.JAVASCRIPT)) {
       return true;
     }
diff --git a/ijwb/src/com/google/idea/blaze/ijwb/plugin/IjwbPluginId.java b/ijwb/src/com/google/idea/blaze/ijwb/plugin/IjwbPluginId.java
index bfaba31..a1187f6 100644
--- a/ijwb/src/com/google/idea/blaze/ijwb/plugin/IjwbPluginId.java
+++ b/ijwb/src/com/google/idea/blaze/ijwb/plugin/IjwbPluginId.java
@@ -15,17 +15,23 @@
  */
 package com.google.idea.blaze.ijwb.plugin;
 
+import com.google.idea.blaze.base.bazel.BuildSystemProvider;
 import com.google.idea.blaze.base.plugin.BlazePluginId;
+import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
 
-/**
- * IJwB plugin configuration information.
- */
+/** IJwB plugin configuration information. */
 public class IjwbPluginId implements BlazePluginId {
 
-  private static final String PLUGIN_ID = "com.google.idea.blaze.ijwb";  // Please keep up-to-date with plugin.xml
+  // Please keep these up-to-date with plugin xmls
+  static final String BLAZE_PLUGIN_ID = "com.google.idea.blaze.ijwb";
+  static final String BAZEL_PLUGIN_ID = "com.google.idea.bazel.ijwb";
 
   @Override
   public String getPluginId() {
-    return PLUGIN_ID;
+    BuildSystem type = BuildSystemProvider.defaultBuildSystem().buildSystem();
+    if (type == BuildSystem.Blaze) {
+      return BLAZE_PLUGIN_ID;
+    }
+    return BAZEL_PLUGIN_ID;
   }
 }
diff --git a/ijwb/src/com/google/idea/blaze/ijwb/plugin/MigrateBazelPluginDependency.java b/ijwb/src/com/google/idea/blaze/ijwb/plugin/MigrateBazelPluginDependency.java
new file mode 100644
index 0000000..4231e03
--- /dev/null
+++ b/ijwb/src/com/google/idea/blaze/ijwb/plugin/MigrateBazelPluginDependency.java
@@ -0,0 +1,48 @@
+/*
+ * 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.plugin;
+
+import com.google.idea.blaze.base.plugin.dependency.PluginDependencyHelper;
+import com.google.idea.blaze.base.settings.Blaze;
+import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
+import com.intellij.openapi.components.ApplicationComponent;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.ProjectManager;
+import com.intellij.openapi.project.ProjectManagerAdapter;
+
+/**
+ * Temporary migration code following the IntelliJ-with-Bazel plugin ID change. We can't prevent an
+ * initial, spurious error that a required plugin is missing, however this will at least prevent the
+ * error on subsequent project loads.
+ */
+public class MigrateBazelPluginDependency extends ApplicationComponent.Adapter {
+
+  @Override
+  public void initComponent() {
+    ProjectManager projectManager = ProjectManager.getInstance();
+    projectManager.addProjectManagerListener(
+        new ProjectManagerAdapter() {
+          @Override
+          public void projectOpened(Project project) {
+            if (Blaze.isBlazeProject(project)
+                && Blaze.getBuildSystem(project) == BuildSystem.Bazel) {
+              PluginDependencyHelper.removeDependencyOnOldPlugin(
+                  project, IjwbPluginId.BLAZE_PLUGIN_ID);
+            }
+          }
+        });
+  }
+}
diff --git a/ijwb/src/com/google/idea/blaze/ijwb/typescript/BlazeTypescriptJavaSyncAugmenter.java b/ijwb/src/com/google/idea/blaze/ijwb/typescript/BlazeTypescriptJavaSyncAugmenter.java
index 251b04d..a39c63e 100644
--- a/ijwb/src/com/google/idea/blaze/ijwb/typescript/BlazeTypescriptJavaSyncAugmenter.java
+++ b/ijwb/src/com/google/idea/blaze/ijwb/typescript/BlazeTypescriptJavaSyncAugmenter.java
@@ -18,30 +18,19 @@
 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.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.BlazeLibrary;
-
 import java.util.Collection;
 
-/**
- * Prevents garbage collection of "tsconfig$roots"
- */
-public class BlazeTypescriptJavaSyncAugmenter implements BlazeJavaSyncAugmenter {
+/** Prevents garbage collection of "tsconfig$roots" */
+public class BlazeTypescriptJavaSyncAugmenter extends BlazeJavaSyncAugmenter.Adapter {
   @Override
-  public void addLibraryFilter(Glob.GlobSet excludedLibraries) {
-  }
-
-  @Override
-  public Collection<BlazeLibrary> getAdditionalLibraries(BlazeProjectData blazeProjectData) {
-    return ImmutableList.of();
+  public boolean isActive(WorkspaceLanguageSettings workspaceLanguageSettings) {
+    return workspaceLanguageSettings.isLanguageActive(LanguageClass.TYPESCRIPT);
   }
 
   @Override
   public Collection<String> getExternallyAddedLibraries(BlazeProjectData blazeProjectData) {
-    if (blazeProjectData.workspaceLanguageSettings.isLanguageActive(LanguageClass.TYPESCRIPT)) {
-      return ImmutableList.of(BlazeTypescriptSyncPlugin.TSCONFIG_LIBRARY_NAME);
-    }
-    return ImmutableList.of();
+    return ImmutableList.of(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 7e5c3e7..3659a42 100644
--- a/ijwb/src/com/google/idea/blaze/ijwb/typescript/BlazeTypescriptSyncPlugin.java
+++ b/ijwb/src/com/google/idea/blaze/ijwb/typescript/BlazeTypescriptSyncPlugin.java
@@ -16,16 +16,15 @@
 package com.google.idea.blaze.ijwb.typescript;
 
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.idea.blaze.base.async.process.ExternalTask;
 import com.google.idea.blaze.base.async.process.LineProcessingOutputStream;
 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.RuleIdeInfo;
 import com.google.idea.blaze.base.issueparser.IssueOutputLineProcessor;
 import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.model.RuleMap;
 import com.google.idea.blaze.base.model.SyncState;
 import com.google.idea.blaze.base.model.primitives.Label;
 import com.google.idea.blaze.base.model.primitives.LanguageClass;
@@ -36,7 +35,7 @@
 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;
-import com.google.idea.blaze.base.scope.output.PrintOutput;
+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.sync.BlazeSyncPlugin;
@@ -44,23 +43,17 @@
 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.intellij.openapi.module.Module;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.roots.ModifiableRootModel;
 import com.intellij.openapi.roots.impl.libraries.ProjectLibraryTable;
 import com.intellij.openapi.roots.libraries.Library;
 import com.intellij.util.PlatformUtils;
-
-import java.io.File;
 import java.util.Collection;
 import java.util.Set;
-
 import javax.annotation.Nullable;
 
-/**
- * Supports typescript.
- */
+/** Supports typescript. */
 public class BlazeTypescriptSyncPlugin extends BlazeSyncPlugin.Adapter {
 
   static final String TSCONFIG_LIBRARY_NAME = "tsconfig$roots";
@@ -71,66 +64,73 @@
   }
 
   @Override
-  public void updateSyncState(Project project,
-                              BlazeContext context,
-                              WorkspaceRoot workspaceRoot,
-                              ProjectViewSet projectViewSet,
-                              WorkspaceLanguageSettings workspaceLanguageSettings,
-                              BlazeRoots blazeRoots,
-                              @Nullable WorkingSet workingSet,
-                              WorkspacePathResolver workspacePathResolver,
-                              ImmutableMap<Label, RuleIdeInfo> ruleMap,
-                              @Deprecated @Nullable File androidPlatformDirectory,
-                              SyncState.Builder syncStateBuilder,
-                              @Nullable SyncState previousSyncState) {
+  public void updateSyncState(
+      Project project,
+      BlazeContext context,
+      WorkspaceRoot workspaceRoot,
+      ProjectViewSet projectViewSet,
+      WorkspaceLanguageSettings workspaceLanguageSettings,
+      BlazeRoots blazeRoots,
+      @Nullable WorkingSet workingSet,
+      WorkspacePathResolver workspacePathResolver,
+      RuleMap ruleMap,
+      SyncState.Builder syncStateBuilder,
+      @Nullable SyncState previousSyncState) {
     if (!workspaceLanguageSettings.isLanguageActive(LanguageClass.TYPESCRIPT)) {
       return;
     }
 
-    Label tsConfig = projectViewSet.getSectionValue(TsConfigRuleSection.KEY);
+    Label tsConfig = projectViewSet.getScalarValue(TsConfigRuleSection.KEY);
     if (tsConfig == null) {
       invalidProjectViewError(context);
       return;
     }
 
-    Scope.push(context, (childContext) -> {
-      childContext.push(new TimingScope("TsConfig"));
-      childContext.output(PrintOutput.output("Updating tsconfig..."));
+    Scope.push(
+        context,
+        (childContext) -> {
+          childContext.push(new TimingScope("TsConfig"));
+          childContext.output(new StatusOutput("Updating tsconfig..."));
 
-      BlazeCommand command = BlazeCommand.builder(Blaze.getBuildSystem(project), BlazeCommandName.RUN)
-        .addTargets(tsConfig)
-        .addBlazeFlags(BlazeFlags.buildFlags(project, projectViewSet))
-        .build();
+          BlazeCommand command =
+              BlazeCommand.builder(Blaze.getBuildSystem(project), BlazeCommandName.RUN)
+                  .addTargets(tsConfig)
+                  .addBlazeFlags(BlazeFlags.buildFlags(project, projectViewSet))
+                  .build();
 
-      int retVal = ExternalTask.builder(workspaceRoot, command)
-        .context(childContext)
-        .stderr(LineProcessingOutputStream.of(
-          new IssueOutputLineProcessor(project, childContext, workspaceRoot)
-        ))
-        .build()
-        .run();
+          int retVal =
+              ExternalTask.builder(workspaceRoot)
+                  .addBlazeCommand(command)
+                  .context(childContext)
+                  .stderr(
+                      LineProcessingOutputStream.of(
+                          new IssueOutputLineProcessor(project, childContext, workspaceRoot)))
+                  .build()
+                  .run();
 
-      if (retVal != 0) {
-        childContext.setHasError();
-      }
-    });
+          if (retVal != 0) {
+            childContext.setHasError();
+          }
+        });
   }
 
   @Override
-  public void updateProjectStructure(Project project,
-                                     BlazeContext context,
-                                     WorkspaceRoot workspaceRoot,
-                                     ProjectViewSet projectViewSet,
-                                     BlazeProjectData blazeProjectData,
-                                     @Nullable BlazeProjectData oldBlazeProjectData,
-                                     ModuleEditor moduleEditor,
-                                     Module workspaceModule,
-                                     ModifiableRootModel workspaceModifiableModel) {
+  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.TYPESCRIPT)) {
       return;
     }
 
-    Library tsConfigLibrary = ProjectLibraryTable.getInstance(project).getLibraryByName(TSCONFIG_LIBRARY_NAME);
+    Library tsConfigLibrary =
+        ProjectLibraryTable.getInstance(project).getLibraryByName(TSCONFIG_LIBRARY_NAME);
     if (tsConfigLibrary != null) {
       if (workspaceModifiableModel.findLibraryOrderEntry(tsConfigLibrary) == null) {
         workspaceModifiableModel.addLibraryEntry(tsConfigLibrary);
@@ -139,9 +139,10 @@
   }
 
   @Override
-  public boolean validateProjectView(BlazeContext context,
-                                     ProjectViewSet projectViewSet,
-                                     WorkspaceLanguageSettings workspaceLanguageSettings) {
+  public boolean validateProjectView(
+      BlazeContext context,
+      ProjectViewSet projectViewSet,
+      WorkspaceLanguageSettings workspaceLanguageSettings) {
     boolean typescriptActive = workspaceLanguageSettings.isLanguageActive(LanguageClass.TYPESCRIPT);
 
     if (typescriptActive && !PlatformUtils.isIdeaUltimate()) {
@@ -150,7 +151,7 @@
     }
 
     // Must have either both typescript and ts_config_rule or neither
-    Label tsConfig = projectViewSet.getSectionValue(TsConfigRuleSection.KEY);
+    Label tsConfig = projectViewSet.getScalarValue(TsConfigRuleSection.KEY);
     if (typescriptActive ^ (tsConfig != null)) {
       invalidProjectViewError(context);
       return false;
@@ -160,9 +161,10 @@
   }
 
   private void invalidProjectViewError(BlazeContext context) {
-    IssueOutput
-      .error("For Typescript support you must add both additional_languages: typescript and the ts_config_rule attribute.")
-      .submit(context);
+    IssueOutput.error(
+            "For Typescript support you must add both additional_languages: "
+                + "typescript and the ts_config_rule attribute.")
+        .submit(context);
   }
 
   @Override
diff --git a/ijwb/src/com/google/idea/blaze/ijwb/typescript/TsConfigRuleSection.java b/ijwb/src/com/google/idea/blaze/ijwb/typescript/TsConfigRuleSection.java
index ea476fd..aa3caff 100644
--- a/ijwb/src/com/google/idea/blaze/ijwb/typescript/TsConfigRuleSection.java
+++ b/ijwb/src/com/google/idea/blaze/ijwb/typescript/TsConfigRuleSection.java
@@ -24,13 +24,10 @@
 import com.google.idea.blaze.base.projectview.section.SectionKey;
 import com.google.idea.blaze.base.projectview.section.SectionParser;
 import com.google.idea.blaze.base.ui.BlazeValidationError;
-
-import javax.annotation.Nullable;
 import java.util.List;
+import javax.annotation.Nullable;
 
-/**
- * Points to the ts_config rule.
- */
+/** Points to the ts_config rule. */
 public class TsConfigRuleSection {
   public static final SectionKey<Label, ScalarSection<Label>> KEY = SectionKey.of("ts_config_rule");
   public static final SectionParser PARSER = new TsConfigRuleSectionParser();
diff --git a/intellij-platform-sdk/BUILD b/intellij-platform-sdk/BUILD
deleted file mode 100644
index 617aa82..0000000
--- a/intellij-platform-sdk/BUILD
+++ /dev/null
@@ -1,100 +0,0 @@
-#
-# Description: IntelliJ plugin SDKs required to build the plugin jars.
-#
-
-config_setting(
-    name = "intellij-latest",
-    values = {
-        "define": "ij_product=intellij-latest",
-    },
-)
-
-config_setting(
-    name = "clion-latest",
-    values = {
-        "define": "ij_product=clion-latest",
-    },
-)
-
-config_setting(
-    name = "android-studio-latest",
-    values = {
-        "define": "ij_product=android-studio-latest",
-    },
-)
-
-java_library(
-    name = "plugin_api_internal",
-    exports = select({
-        ":intellij-latest": ["@intellij_latest//:plugin_api"],
-        ":clion-latest": ["@clion_latest//:plugin_api"],
-        ":android-studio-latest": ["//intellij-platform-sdk/AI-145.971.21:plugin_api"],
-        "//conditions:default": ["@intellij_latest//:plugin_api"],
-    }),
-)
-
-# The outward facing plugin api
-java_library(
-    name = "plugin_api",
-    neverlink = 1,
-    visibility = ["//visibility:public"],
-    exports = [":plugin_api_internal"],
-)
-
-# for tests, we need the IJ API at runtime,
-# so can't use the neverlink rule
-java_library(
-    name = "plugin_api_for_tests",
-    testonly = 1,
-    visibility = ["//visibility:public"],
-    exports = [":plugin_api_internal"],
-)
-
-# The dev kit is only for IntelliJ since you only develop plugins in Java.
-java_library(
-    name = "devkit",
-    neverlink = 1,
-    visibility = ["//visibility:public"],
-    exports = select({
-        ":intellij-latest": ["@intellij_latest//:devkit"],
-        "//conditions:default": ["@intellij_latest//:devkit"],
-    }),
-)
-
-filegroup(
-    name = "build_number",
-    srcs = select({
-        ":intellij-latest": ["@intellij_latest//:build_number"],
-        ":clion-latest": ["@clion_latest//:build_number"],
-        ":android-studio-latest": ["//intellij-platform-sdk/AI-145.971.21:build_number"],
-        "//conditions:default": ["@intellij_latest//:build_number"],
-    }),
-    visibility = ["//visibility:public"],
-)
-
-# Plugins bundled with the SDK which are required for compilation and/or integration tests
-java_library(
-    name = "bundled_plugins_internal",
-    exports = select({
-        ":intellij-latest": ["@intellij_latest//:bundled_plugins"],
-        ":clion-latest": ["@clion_latest//:bundled_plugins"],
-        ":android-studio-latest": ["//intellij-platform-sdk/AI-145.971.21:bundled_plugins"],
-        "//conditions:default": ["@intellij_latest//:bundled_plugins"],
-    }),
-)
-
-java_library(
-    name = "bundled_plugins",
-    neverlink = 1,
-    visibility = ["//visibility:public"],
-    exports = [":bundled_plugins_internal"],
-)
-
-# for tests, we include the bundled plugins at runtime,
-# so can't use the neverlink rule
-java_library(
-    name = "bundled_plugins_for_tests",
-    testonly = 1,
-    visibility = ["//visibility:public"],
-    exports = [":bundled_plugins_internal"],
-)
diff --git a/intellij_platform_sdk/BUILD b/intellij_platform_sdk/BUILD
new file mode 100644
index 0000000..a733a02
--- /dev/null
+++ b/intellij_platform_sdk/BUILD
@@ -0,0 +1,104 @@
+#
+# Description: IntelliJ plugin SDKs required to build the plugin jars.
+#
+
+package(default_visibility = ["//visibility:public"])
+
+config_setting(
+    name = "intellij-latest",
+    values = {
+        "define": "ij_product=intellij-latest",
+    },
+)
+
+config_setting(
+    name = "clion-latest",
+    values = {
+        "define": "ij_product=clion-latest",
+    },
+)
+
+config_setting(
+    name = "android-studio-latest",
+    values = {
+        "define": "ij_product=android-studio-latest",
+    },
+)
+
+java_library(
+    name = "plugin_api_internal",
+    visibility = ["//visibility:private"],
+    exports = select({
+        ":intellij-latest": ["@intellij_latest//:plugin_api"],
+        ":clion-latest": ["@clion_latest//:plugin_api"],
+        ":android-studio-latest": [
+            "@android_studio_latest//:plugin_api",
+            "@android_studio_latest//:android_plugin",
+        ],
+        "//conditions:default": ["@intellij_latest//:plugin_api"],
+    }),
+)
+
+# The outward facing plugin api
+java_library(
+    name = "plugin_api",
+    neverlink = 1,
+    exports = [":plugin_api_internal"],
+)
+
+# for tests, we need the IJ API at runtime,
+# so can't use the neverlink rule
+java_library(
+    name = "plugin_api_for_tests",
+    testonly = 1,
+    exports = [
+        ":plugin_api_internal",
+        "@mockito//jar",
+        "@objenesis//jar",
+        "@truth//jar",
+    ],
+)
+
+# The dev kit is only for IntelliJ since you only develop plugins in Java.
+java_library(
+    name = "devkit",
+    neverlink = 1,
+    exports = select({
+        ":intellij-latest": ["@intellij_latest//:devkit"],
+        ":android-studio-latest": [],
+        ":clion-latest": [],
+        "//conditions:default": ["@intellij_latest//:devkit"],
+    }),
+)
+
+# Bundled plugins required by integration tests
+java_library(
+    name = "bundled_plugins",
+    testonly = 1,
+    runtime_deps = select({
+        ":intellij-latest": ["@intellij_latest//:bundled_plugins"],
+        ":clion-latest": ["@clion_latest//:bundled_plugins"],
+        ":android-studio-latest": ["@android_studio_latest//:bundled_plugins"],
+        "//conditions:default": ["@intellij_latest//:bundled_plugins"],
+    }),
+)
+
+filegroup(
+    name = "application_info_jar",
+    srcs = select({
+        ":intellij-latest": ["@intellij_latest//:application_info_jar"],
+        ":clion-latest": ["@clion_latest//:application_info_jar"],
+        ":android-studio-latest": ["@android_studio_latest//:application_info_jar"],
+        "//conditions:default": ["@intellij_latest//:application_info_jar"],
+    }),
+)
+
+filegroup(
+    name = "application_info_name",
+    srcs = select({
+        ":intellij-latest": ["intellij_application_info_name.txt"],
+        ":clion-latest": ["clion_application_info_name.txt"],
+        ":android-studio-latest": ["android_studio_application_info_name.txt"],
+        "//conditions:default": ["intellij_application_info_name.txt"],
+    }),
+)
diff --git a/intellij_platform_sdk/BUILD.android_studio b/intellij_platform_sdk/BUILD.android_studio
new file mode 100644
index 0000000..7a4839a
--- /dev/null
+++ b/intellij_platform_sdk/BUILD.android_studio
@@ -0,0 +1,40 @@
+# Description:
+#
+# Plugin source jars for Android Studio, accessed remotely.
+
+package(default_visibility = ["//visibility:public"])
+
+java_import(
+    name = "plugin_api",
+    jars = glob([
+        "android-studio/lib/*.jar",
+    ]),
+    tags = ["intellij-provided-by-sdk"],
+)
+
+java_import(
+    name = "android_plugin",
+    jars = glob([
+        "android-studio/plugins/android/lib/*.jar",
+        "android-studio/plugins/android-ndk/lib/*.jar",
+    ]),
+)
+
+# The plugins required by ASwB. We need to include them
+# when running integration tests.
+java_import(
+    name = "bundled_plugins",
+    jars = glob([
+        "android-studio/plugins/gradle/lib/*.jar",
+        "android-studio/plugins/Groovy/lib/*.jar",
+        "android-studio/plugins/java-i18n/lib/*.jar",
+        "android-studio/plugins/junit/lib/*.jar",
+        "android-studio/plugins/properties/lib/*.jar",
+    ]),
+    tags = ["intellij-provided-by-sdk"],
+)
+
+filegroup(
+    name = "application_info_jar",
+    srcs = ["android-studio/lib/resources.jar"],
+)
diff --git a/intellij_platform_sdk/BUILD.clion b/intellij_platform_sdk/BUILD.clion
new file mode 100644
index 0000000..c32194f
--- /dev/null
+++ b/intellij_platform_sdk/BUILD.clion
@@ -0,0 +1,24 @@
+# Description:
+#
+# Plugin source jars for CLion, accessed remotely.
+
+package(default_visibility = ["//visibility:public"])
+
+java_import(
+    name = "plugin_api",
+    jars = glob(["clion-*/lib/*.jar"]),
+    tags = ["intellij-provided-by-sdk"],
+)
+
+# The plugins required by CLwB. Presumably there will be some, when we write
+# some integration tests.
+java_import(
+    name = "bundled_plugins",
+    jars = [],
+    tags = ["intellij-provided-by-sdk"],
+)
+
+filegroup(
+    name = "application_info_jar",
+    srcs = glob(["clion-*/lib/clion.jar"]),
+)
\ No newline at end of file
diff --git a/intellij_platform_sdk/BUILD.idea b/intellij_platform_sdk/BUILD.idea
new file mode 100644
index 0000000..251b620
--- /dev/null
+++ b/intellij_platform_sdk/BUILD.idea
@@ -0,0 +1,34 @@
+# Description:
+#
+# Plugin source jars for IntelliJ CE, accessed remotely.
+
+package(default_visibility = ["//visibility:public"])
+
+java_import(
+    name = "plugin_api",
+    jars = glob(["idea-IC-*/lib/*.jar"]),
+    tags = ["intellij-provided-by-sdk"],
+)
+
+java_import(
+    name = "devkit",
+    jars = glob(["idea-IC-*/plugins/devkit/lib/devkit.jar"]),
+)
+
+# The plugins required by IJwB. We need to include them
+# when running integration tests.
+java_import(
+    name = "bundled_plugins",
+    jars = glob([
+        "idea-IC-*/plugins/devkit/lib/*.jar",
+        "idea-IC-*/plugins/java-i18n/lib/*.jar",
+        "idea-IC-*/plugins/junit/lib/*.jar",
+        "idea-IC-*/plugins/properties/lib/*.jar",
+    ]),
+    tags = ["intellij-provided-by-sdk"],
+)
+
+filegroup(
+    name = "application_info_jar",
+    srcs = glob(["idea-IC-*/lib/resources.jar"]),
+)
diff --git a/intellij_platform_sdk/android_studio_application_info_name.txt b/intellij_platform_sdk/android_studio_application_info_name.txt
new file mode 100644
index 0000000..d962cc0
--- /dev/null
+++ b/intellij_platform_sdk/android_studio_application_info_name.txt
@@ -0,0 +1 @@
+idea/AndroidStudioApplicationInfo.xml
diff --git a/intellij_platform_sdk/clion_application_info_name.txt b/intellij_platform_sdk/clion_application_info_name.txt
new file mode 100644
index 0000000..0cf4569
--- /dev/null
+++ b/intellij_platform_sdk/clion_application_info_name.txt
@@ -0,0 +1 @@
+idea/CLionApplicationInfo.xml
diff --git a/intellij_platform_sdk/intellij_application_info_name.txt b/intellij_platform_sdk/intellij_application_info_name.txt
new file mode 100644
index 0000000..687d0ea
--- /dev/null
+++ b/intellij_platform_sdk/intellij_application_info_name.txt
@@ -0,0 +1 @@
+idea/IdeaApplicationInfo.xml
diff --git a/intellij_test/BUILD b/intellij_test/BUILD
index cfd25b4..8b2600e 100644
--- a/intellij_test/BUILD
+++ b/intellij_test/BUILD
@@ -4,12 +4,15 @@
 
 package(default_visibility = ["//visibility:public"])
 
+licenses(["notice"])  # Apache 2.0
+
 java_library(
     name = "lib",
+    testonly = 1,
     srcs = glob(["src/**/*.java"]),
     deps = [
-        "//intellij-platform-sdk:plugin_api_for_tests",
-        "//third_party:jsr305",
-        "//third_party:test_lib",
+        "//intellij_platform_sdk:plugin_api_for_tests",
+        "@jsr305_annotations//jar",
+        "@junit//jar",
     ],
 )
diff --git a/intellij_test/src/com/google/idea/blaze/base/BlazeTestSystemProperties.java b/intellij_test/src/com/google/idea/blaze/base/BlazeTestSystemProperties.java
index 2a19f58..c8a3351 100644
--- a/intellij_test/src/com/google/idea/blaze/base/BlazeTestSystemProperties.java
+++ b/intellij_test/src/com/google/idea/blaze/base/BlazeTestSystemProperties.java
@@ -17,33 +17,28 @@
 
 import com.google.common.base.Joiner;
 import com.google.common.collect.Lists;
-
+import com.intellij.openapi.application.Application;
+import com.intellij.openapi.application.PathManager;
 import com.intellij.openapi.vfs.newvfs.impl.VfsRootAccess;
 import com.intellij.util.PlatformUtils;
-
 import java.io.File;
 import java.io.IOException;
 import java.net.URL;
 import java.util.Enumeration;
 import java.util.List;
+import javax.annotation.Nullable;
 
-/**
- * Test utilities specific to running in a blaze/bazel environment.
- */
+/** Test utilities specific to running in a blaze/bazel environment. */
 public class BlazeTestSystemProperties {
 
-  /**
-   * The absolute path to the runfiles directory.
-   */
+  /** The absolute path to the runfiles directory. */
   private static final String RUNFILES_PATH = getUserValue("TEST_SRCDIR");
 
   public static boolean isRunThroughBlaze() {
     return System.getenv("JAVA_RUNFILES") != null;
   }
 
-  /**
-   * Sets up the necessary system properties for running IntelliJ tests via blaze/bazel.
-   */
+  /** Sets up the necessary system properties for running IntelliJ tests via blaze/bazel. */
   public static void configureSystemProperties() throws IOException {
     if (!isRunThroughBlaze()) {
       return;
@@ -56,11 +51,18 @@
     setIfEmpty(PlatformUtils.PLATFORM_PREFIX_KEY, "Idea");
     setIfEmpty("idea.classpath.index.enabled", "false");
 
+    // Tests fail if they access files outside of the project roots and other system directories.
+    // Ensure runfiles and platform api are whitelisted.
     VfsRootAccess.allowRootAccess(RUNFILES_PATH);
+    String platformApi = getPlatformApiPath();
+    if (platformApi != null) {
+      VfsRootAccess.allowRootAccess(platformApi);
+    }
 
     List<String> pluginJars = Lists.newArrayList();
     try {
-      Enumeration<URL> urls = BlazeTestSystemProperties.class.getClassLoader().getResources("META-INF/plugin.xml");
+      Enumeration<URL> urls =
+          BlazeTestSystemProperties.class.getClassLoader().getResources("META-INF/plugin.xml");
       while (urls.hasMoreElements()) {
         URL url = urls.nextElement();
         addArchiveFile(url, pluginJars);
@@ -73,6 +75,17 @@
     setIfEmpty("idea.plugins.path", Joiner.on(File.pathSeparator).join(pluginJars));
   }
 
+  @Nullable
+  private static String getPlatformApiPath() {
+    String platformJar = PathManager.getJarPathForClass(Application.class);
+    if (platformJar == null) {
+      return null;
+    }
+    File jarFile = new File(platformJar).getAbsoluteFile();
+    File libDir = jarFile.getParentFile();
+    return libDir != null ? libDir.getParent() : null;
+  }
+
   private static void addArchiveFile(URL url, List<String> files) {
     if ("jar".equals(url.getProtocol())) {
       String path = url.getPath();
@@ -100,8 +113,8 @@
   /**
    * Gets directory that should be used for all files created during testing.
    *
-   * <p>This method will return a directory that's common to all tests run
-   * within the same <i>build target</i>.
+   * <p>This method will return a directory that's common to all tests run within the same <i>build
+   * target</i>.
    *
    * @return standard file, for example the File representing "/tmp/zogjones/foo_unittest/".
    */
@@ -134,9 +147,8 @@
   }
 
   /**
-   * Returns the value for system property <code>name</code>, or if that is
-   * not found the value of the user's environment variable <code>name</code>.
-   * If neither is found, null is returned.
+   * Returns the value for system property <code>name</code>, or if that is not found the value of
+   * the user's environment variable <code>name</code>. If neither is found, null is returned.
    *
    * @param name the name of property to get
    * @return the value of the property or null if it is not found
@@ -148,5 +160,4 @@
     }
     return propValue;
   }
-
 }
diff --git a/intellij_test/src/com/google/idea/blaze/base/suite/TestAggregator.java b/intellij_test/src/com/google/idea/blaze/base/suite/TestAggregator.java
index af80f29..dbae268 100644
--- a/intellij_test/src/com/google/idea/blaze/base/suite/TestAggregator.java
+++ b/intellij_test/src/com/google/idea/blaze/base/suite/TestAggregator.java
@@ -27,5 +27,4 @@
  */
 @Retention(RetentionPolicy.RUNTIME)
 @Target({ElementType.TYPE})
-public @interface TestAggregator {
-}
+public @interface TestAggregator {}
diff --git a/intellij_test/src/com/google/idea/blaze/base/suite/TestAll.java b/intellij_test/src/com/google/idea/blaze/base/suite/TestAll.java
index fbd2a21..b40fafe 100644
--- a/intellij_test/src/com/google/idea/blaze/base/suite/TestAll.java
+++ b/intellij_test/src/com/google/idea/blaze/base/suite/TestAll.java
@@ -23,13 +23,6 @@
 import com.intellij.testFramework.TestLoggerFactory;
 import com.intellij.testFramework.TestRunnerUtil;
 import com.intellij.util.ArrayUtil;
-
-import junit.framework.JUnit4TestAdapter;
-import junit.framework.Test;
-import junit.framework.TestCase;
-import junit.framework.TestResult;
-import junit.framework.TestSuite;
-
 import java.io.File;
 import java.io.IOException;
 import java.lang.reflect.Method;
@@ -39,12 +32,14 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
-
 import javax.annotation.Nullable;
+import junit.framework.JUnit4TestAdapter;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestResult;
+import junit.framework.TestSuite;
 
-/**
- * A cut-down version of {@link com.intellij.TestAll} which supports test classes inside jars.
- */
+/** A cut-down version of {@link com.intellij.TestAll} which supports test classes inside jars. */
 @TestAggregator
 public class TestAll implements Test {
 
@@ -58,7 +53,8 @@
     this(packageRoot, getClassRoots());
   }
 
-  public TestAll(String packageRoot, String... classRoots) throws IOException, ClassNotFoundException {
+  public TestAll(String packageRoot, String... classRoots)
+      throws IOException, ClassNotFoundException {
     testCaseLoader = new TestCaseLoader("");
     fillTestCases(testCaseLoader, packageRoot, classRoots);
   }
@@ -66,7 +62,7 @@
   public static String[] getClassRoots() {
     final ClassLoader loader = TestAll.class.getClassLoader();
     if (loader instanceof URLClassLoader) {
-      return getClassRoots(((URLClassLoader)loader).getURLs());
+      return getClassRoots(((URLClassLoader) loader).getURLs());
     }
     final Class<? extends ClassLoader> loaderClass = loader.getClass();
     if (loaderClass.getName().equals("com.intellij.util.lang.UrlClassLoader")) {
@@ -75,6 +71,7 @@
         final List<URL> urls = (List<URL>) declaredMethod.invoke(loader);
         return getClassRoots(urls.toArray(new URL[urls.size()]));
       } catch (Throwable ignore) {
+        // Do nothing
       }
     }
     return System.getProperty("java.class.path").split(File.pathSeparator);
@@ -82,16 +79,17 @@
 
   private static String[] getClassRoots(URL[] urls) {
     return Arrays.stream(urls)
-      .map(VfsUtilCore::convertFromUrl)
-      .map(VfsUtilCore::urlToPath)
-      .toArray(String[]::new);
+        .map(VfsUtilCore::convertFromUrl)
+        .map(VfsUtilCore::urlToPath)
+        .toArray(String[]::new);
   }
 
   private static boolean isIntellijPlatformJar(String classRoot) {
     return classRoot.contains("intellij-platform-sdk");
   }
 
-  public static void fillTestCases(TestCaseLoader testCaseLoader, String packageRoot, String... classRoots) throws IOException {
+  public static void fillTestCases(
+      TestCaseLoader testCaseLoader, String packageRoot, String... classRoots) throws IOException {
     long before = System.currentTimeMillis();
     for (String classRoot : classRoots) {
       if (isIntellijPlatformJar(classRoot)) {
@@ -103,7 +101,8 @@
       testCaseLoader.loadTestCases(classRootFile.getName(), classes);
       int newCount = testCaseLoader.getClasses().size();
       if (newCount != oldCount) {
-        System.out.println("Loaded " + (newCount - oldCount) + " tests from class root " + classRoot);
+        System.out.println(
+            "Loaded " + (newCount - oldCount) + " tests from class root " + classRoot);
       }
     }
 
@@ -112,8 +111,12 @@
     }
     long after = System.currentTimeMillis();
 
-    String message = "Number of test classes found: " + testCaseLoader.getClasses().size()
-                      + " time to load: " + (after - before) / 1000 + "s.";
+    String message =
+        "Number of test classes found: "
+            + testCaseLoader.getClasses().size()
+            + " time to load: "
+            + (after - before) / 1000
+            + "s.";
     System.out.println(message);
     log(message);
   }
@@ -122,7 +125,7 @@
   public int countTestCases() {
     int count = 0;
     for (Object aClass : testCaseLoader.getClasses()) {
-      Test test = getTest((Class)aClass);
+      Test test = getTest((Class) aClass);
       if (test != null) {
         count += test.countTestCases();
       }
@@ -175,30 +178,29 @@
       }
 
       final int[] testsCount = {0};
-      TestSuite suite = new TestSuite(testCaseClass) {
-        @Override
-        public void addTest(Test test) {
-          if (!(test instanceof TestCase)) {
-            doAddTest(test);
-          }
-          else {
-            String name = ((TestCase)test).getName();
-            if ("warning".equals(name)) {
-              return; // Mute TestSuite's "no tests found" warning
+      TestSuite suite =
+          new TestSuite(testCaseClass) {
+            @Override
+            public void addTest(Test test) {
+              if (!(test instanceof TestCase)) {
+                doAddTest(test);
+              } else {
+                String name = ((TestCase) test).getName();
+                if ("warning".equals(name)) {
+                  return; // Mute TestSuite's "no tests found" warning
+                }
+                doAddTest(test);
+              }
             }
-            doAddTest(test);
-          }
-        }
 
-        private void doAddTest(Test test) {
-          testsCount[0]++;
-          super.addTest(test);
-        }
-      };
+            private void doAddTest(Test test) {
+              testsCount[0]++;
+              super.addTest(test);
+            }
+          };
 
       return testsCount[0] > 0 ? suite : null;
-    }
-    catch (Throwable t) {
+    } catch (Throwable t) {
       System.err.println("Failed to load test: " + testCaseClass.getName());
       t.printStackTrace(System.err);
       return null;
@@ -209,8 +211,7 @@
   private static Method safeFindMethod(Class<?> klass, String name) {
     try {
       return klass.getMethod(name);
-    }
-    catch (NoSuchMethodException e) {
+    } catch (NoSuchMethodException e) {
       return null;
     }
   }
@@ -218,5 +219,4 @@
   private static void log(String message) {
     TeamCityLogger.info(message);
   }
-
 }
diff --git a/intellij_test/src/com/google/idea/blaze/base/suite/TestClassFinder.java b/intellij_test/src/com/google/idea/blaze/base/suite/TestClassFinder.java
index 8df1ed3..40633ed 100644
--- a/intellij_test/src/com/google/idea/blaze/base/suite/TestClassFinder.java
+++ b/intellij_test/src/com/google/idea/blaze/base/suite/TestClassFinder.java
@@ -18,7 +18,6 @@
 import com.google.common.collect.Sets;
 import com.intellij.ClassFinder;
 import com.intellij.openapi.util.text.StringUtil;
-
 import java.io.File;
 import java.io.IOException;
 import java.util.Enumeration;
@@ -26,18 +25,15 @@
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 
-/**
- * Finds all valid test classes inside a given directory or jar.
- */
+/** Finds all valid test classes inside a given directory or jar. */
 @TestAggregator
 public class TestClassFinder {
 
   private static final String CLASS_EXTENSION = ".class";
 
-  /**
-   * Returns all top-level test classes underneath the specified classpath and package roots.
-   */
-  public static SortedSet<String> findTestClasses(File classRootFile, String packageRoot) throws IOException {
+  /** Returns all top-level test classes underneath the specified classpath and package roots. */
+  public static SortedSet<String> findTestClasses(File classRootFile, String packageRoot)
+      throws IOException {
     if (isJar(classRootFile.getPath())) {
       return findTestClassesInJar(classRootFile, packageRoot);
     }
@@ -45,7 +41,8 @@
     return Sets.newTreeSet(finder.getClasses());
   }
 
-  private static SortedSet<String> findTestClassesInJar(File classPathRoot, String packageRoot) throws IOException {
+  private static SortedSet<String> findTestClassesInJar(File classPathRoot, String packageRoot)
+      throws IOException {
     packageRoot = packageRoot.replace('.', File.separatorChar);
     SortedSet<String> classNames = Sets.newTreeSet();
     ZipFile zipFile = new ZipFile(classPathRoot.getPath());
@@ -55,7 +52,9 @@
     Enumeration<? extends ZipEntry> entries = zipFile.entries();
     while (entries.hasMoreElements()) {
       String entryName = entries.nextElement().getName();
-      if (entryName.endsWith(CLASS_EXTENSION) && isTopLevelClass(entryName) && entryName.startsWith(packageRoot)) {
+      if (entryName.endsWith(CLASS_EXTENSION)
+          && isTopLevelClass(entryName)
+          && entryName.startsWith(packageRoot)) {
         classNames.add(getClassName(entryName));
       }
     }
@@ -70,9 +69,7 @@
     return fileName.indexOf('$') < 0;
   }
 
-  /**
-   * Given the absolute path of a class file, return the class name.
-   */
+  /** Given the absolute path of a class file, return the class name. */
   private static String getClassName(String className) {
     return StringUtil.trimEnd(className, CLASS_EXTENSION).replace(File.separatorChar, '.');
   }
diff --git a/intellij_test/src/com/google/idea/blaze/base/suite/TestSuiteBuilder.java b/intellij_test/src/com/google/idea/blaze/base/suite/TestSuiteBuilder.java
index 78c384c..9d918a1 100644
--- a/intellij_test/src/com/google/idea/blaze/base/suite/TestSuiteBuilder.java
+++ b/intellij_test/src/com/google/idea/blaze/base/suite/TestSuiteBuilder.java
@@ -17,11 +17,14 @@
 
 import com.google.common.base.Strings;
 import com.google.idea.blaze.base.BlazeTestSystemProperties;
-
 import junit.framework.Test;
 import junit.framework.TestSuite;
+import org.junit.runner.RunWith;
+import org.junit.runners.AllTests;
 
+/** Simple JUnit3 style test suite builder. */
 @TestAggregator
+@RunWith(AllTests.class)
 public class TestSuiteBuilder {
   public static Test suite() throws Throwable {
 
@@ -34,5 +37,4 @@
     suite.addTest(new TestAll(packageRoot));
     return suite;
   }
-
 }
diff --git a/intellij_test/test_defs.bzl b/intellij_test/test_defs.bzl
index 72029a8..52205c4 100644
--- a/intellij_test/test_defs.bzl
+++ b/intellij_test/test_defs.bzl
@@ -1,55 +1,101 @@
-# 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.
-
 """Custom rule for creating IntelliJ plugin tests.
 """
 
-# The JVM flags common to all test rules
-JVM_FLAGS_FOR_TESTS = [
-    "-Didea.classpath.index.enabled=false",
-    "-Djava.awt.headless=true",
-]
+def intellij_unit_test_suite(name, srcs, test_package_root, **kwargs):
+  """Creates a java_test rule comprising all valid test classes in the specified srcs.
 
-def intellij_test(name,
-                  srcs,
-                  test_package_root,
-                  deps,
-                  platform_prefix="Idea",
-                  required_plugins=None,
-                  integration_tests=False):
-  """Creates a java_test rule comprising all valid test classes
-  in the specified srcs.
+  Args:
+    name: name of this rule.
+    srcs: the test classes.
+    test_package_root: only tests under this package root will be run.
+    **kwargs: Any other args to be passed to the java_test.
+  """
+  test_srcs = [test for test in srcs if test.endswith("Test.java")]
+  test_classes = [_get_test_class(test_src, test_package_root) for test_src in test_srcs]
+  suite_class_name = name + "TestSuite"
+  suite_class = test_package_root + "." + suite_class_name
+  _generate_test_suite(
+      name = suite_class_name,
+      test_package_root = test_package_root,
+      test_classes = test_classes,
+  )
+  native.java_test(
+      name = name,
+      srcs = srcs + [suite_class_name],
+      test_class = suite_class,
+      **kwargs)
+
+def _generate_test_suite(name, test_package_root, test_classes):
+  """Generates a JUnit test suite pulling in all the referenced classes."""
+  lines = []
+  lines.append("package %s;" % test_package_root)
+  lines.append("")
+  lines.append("import org.junit.runner.RunWith;")
+  lines.append("import org.junit.runners.Suite;")
+  lines.append("")
+  for test_class in test_classes:
+    lines.append("import %s;" % test_class)
+  lines.append("")
+  lines.append("@RunWith(Suite.class)")
+  lines.append("@Suite.SuiteClasses({")
+  for test_class in test_classes:
+    lines.append("    %s.class," % test_class.split(".")[-1])
+  lines.append("})")
+  lines.append("class %s {}" % name)
+
+  contents = "\\n".join(lines)
+  native.genrule(
+      name = name,
+      cmd = "printf '%s' > $@" % contents,
+      outs = [name + ".java"],
+  )
+
+
+def _get_test_class(test_src, test_package_root):
+  """Returns the test class of the source relative to the given root."""
+  temp = test_src[:-5]
+  temp = temp.replace("/", ".")
+  i = temp.rfind(test_package_root)
+  if i < 0:
+    fail("Test source '%s' not under package root '%s'" % (test_src, test_package_root))
+  test_class = temp[i:]
+  return test_class
+
+def intellij_integration_test_suite(
+    name,
+    srcs,
+    test_package_root,
+    deps,
+    runtime_deps = [],
+    platform_prefix="Idea",
+    required_plugins=None,
+    **kwargs):
+  """Creates a java_test rule comprising all valid test classes in the specified srcs.
 
   Args:
     name: name of this rule.
     srcs: the test classes.
     test_package_root: only tests under this package root will be run.
     deps: the required deps.
-    plugin_jar: a target building the plugin to be tested. This will be added to the classpath.
+    runtime_deps: the required runtime_deps.
     platform_prefix: Specifies the JetBrains product these tests are run against. Examples are
         'Idea' (IJ CE), 'idea' (IJ UE), 'CLion', 'AndroidStudio'. See
         com.intellij.util.PlatformUtils for other options.
     required_plugins: optional comma-separated list of plugin IDs. Integration tests will fail if
         these plugins aren't loaded at runtime.
-    integration_tests: if true, bundled IJ core plugins will be added to the classpath.
+    **kwargs: Any other args to be passed to the java_test.
   """
 
-  if integration_tests:
-    deps.append("//intellij-platform-sdk:bundled_plugins")
+  runtime_deps = list(runtime_deps)
+  runtime_deps.extend([
+      "//intellij_test:lib",
+      "//intellij_platform_sdk:bundled_plugins",
+      "//third_party:jdk8_tools",
+  ])
 
-
-  jvm_flags = JVM_FLAGS_FOR_TESTS + [
+  jvm_flags = [
+      "-Didea.classpath.index.enabled=false",
+      "-Djava.awt.headless=true",
       "-Didea.platform.prefix=" + platform_prefix,
       "-Didea.test.package.root=" + test_package_root,
   ]
@@ -61,7 +107,8 @@
       name = name,
       srcs = srcs,
       deps = deps,
-      size = "medium" if integration_tests else "small",
+      runtime_deps = runtime_deps,
+      size = "medium",
       jvm_flags = jvm_flags,
       test_class = "com.google.idea.blaze.base.suite.TestSuiteBuilder",
-  )
\ No newline at end of file
+      **kwargs)
diff --git a/java/BUILD b/java/BUILD
new file mode 100644
index 0000000..4ba94eb
--- /dev/null
+++ b/java/BUILD
@@ -0,0 +1,93 @@
+licenses(["notice"])  # Apache 2.0
+
+java_library(
+    name = "java",
+    srcs = glob(["src/**/*.java"]),
+    visibility = ["//visibility:public"],
+    deps = [
+        "//base",
+        "//common/experiments",
+        "//intellij_platform_sdk:plugin_api",
+        "//proto_deps",
+        "@jsr305_annotations//jar",
+    ],
+)
+
+filegroup(
+    name = "plugin_xml",
+    srcs = ["src/META-INF/blaze-java.xml"],
+    visibility = ["//visibility:public"],
+)
+
+load(
+    "//build_defs:build_defs.bzl",
+    "merged_plugin_xml",
+    "stamped_plugin_xml",
+    "intellij_plugin",
+)
+
+merged_plugin_xml(
+    name = "merged_plugin_xml",
+    srcs = [
+        "//base:plugin_xml",
+    ] + [
+        ":plugin_xml",
+    ],
+)
+
+stamped_plugin_xml(
+    name = "java_plugin_xml",
+    plugin_id = "com.google.idea.blaze.java",
+    plugin_name = "com.google.idea.blaze.java",
+    plugin_xml = "merged_plugin_xml",
+)
+
+intellij_plugin(
+    name = "java_integration_test_plugin",
+    testonly = 1,
+    plugin_xml = ":java_plugin_xml",
+    deps = [
+        ":java",
+    ],
+)
+
+load(
+    "//intellij_test:test_defs.bzl",
+    "intellij_integration_test_suite",
+    "intellij_unit_test_suite",
+)
+
+intellij_unit_test_suite(
+    name = "unit_tests",
+    srcs = glob(["tests/unittests/**/*.java"]),
+    test_package_root = "com.google.idea.blaze.java",
+    deps = [
+        ":java",
+        "//base",
+        "//base:unit_test_utils",
+        "//common/experiments",
+        "//common/experiments:unit_test_utils",
+        "//intellij_platform_sdk:plugin_api_for_tests",
+        "//proto_deps",
+        "@jsr305_annotations//jar",
+        "@junit//jar",
+    ],
+)
+
+intellij_integration_test_suite(
+    name = "integration_tests",
+    srcs = glob(["tests/integrationtests/**/*.java"]),
+    required_plugins = "com.google.idea.blaze.java",
+    test_package_root = "com.google.idea.blaze.java",
+    runtime_deps = [
+        ":java_integration_test_plugin",
+    ],
+    deps = [
+        ":java",
+        "//base",
+        "//base:integration_test_utils",
+        "//base:unit_test_utils",
+        "//intellij_platform_sdk:plugin_api_for_tests",
+        "@jsr305_annotations//jar",
+    ],
+)
diff --git a/java/src/META-INF/blaze-java.xml b/java/src/META-INF/blaze-java.xml
new file mode 100644
index 0000000..321e5e4
--- /dev/null
+++ b/java/src/META-INF/blaze-java.xml
@@ -0,0 +1,96 @@
+<!--
+  ~ 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.
+  -->
+<idea-plugin>
+  <depends>com.intellij.modules.java</depends>
+  <depends>JUnit</depends>
+
+  <actions>
+    <action class="com.google.idea.blaze.java.libraries.ExcludeLibraryAction"
+            id="Blaze.ExcludeLibraryAction"
+            icon="BlazeIcons.Blaze"
+            text="Exclude Library and Resync">
+      <add-to-group group-id="Blaze.ProjectViewPopupMenu"/>
+    </action>
+    <action class="com.google.idea.blaze.java.libraries.AttachSourceJarAction"
+            id="Blaze.AttachSourceJarAction"
+            icon="BlazeIcons.Blaze"
+            text="Attach Source Jar">
+      <add-to-group group-id="Blaze.ProjectViewPopupMenu"/>
+    </action>
+    <action class="com.google.idea.blaze.java.libraries.AddLibraryRuleDirectoryToProjectViewAction"
+            id="Blaze.AddLibraryRuleDirectoryToProjectView"
+            icon="BlazeIcons.Blaze"
+            text="Add Library Rule Directory To Project View">
+      <add-to-group group-id="Blaze.ProjectViewPopupMenu"/>
+    </action>
+
+    <!-- IntelliJ specific actions -->
+
+    <action id="Blaze.ImportProject2" class="com.google.idea.blaze.java.wizard2.BlazeImportProjectAction" icon="BlazeIcons.Blaze">
+      <add-to-group group-id="WelcomeScreen.QuickStart" />
+      <add-to-group group-id="OpenProjectGroup" relative-to-action="ImportProject" anchor="after"/>
+    </action>
+
+    <!-- End IntelliJ specific actions -->
+
+  </actions>
+
+  <extensions defaultExtensionNs="com.google.idea.blaze">
+    <SyncPlugin implementation="com.google.idea.blaze.java.sync.BlazeJavaSyncPlugin"/>
+    <PsiFileProvider implementation="com.google.idea.blaze.java.psi.JavaPsiFileProvider" />
+    <BlazeCommandRunConfigurationHandlerProvider implementation="com.google.idea.blaze.java.run.BlazeJavaRunConfigurationHandlerProvider"/>
+    <RuleConfigurationFactory implementation="com.google.idea.blaze.java.run.BlazeJavaRuleConfigurationFactory"/>
+    <RuleConfigurationFactory implementation="com.google.idea.blaze.java.run.BlazeJavaTestRuleConfigurationFactory"/>
+    <BlazeUserSettingsContributor implementation="com.google.idea.blaze.java.settings.BlazeJavaUserSettingsContributor$BlazeJavaUserSettingsProvider"/>
+    <FileCache implementation="com.google.idea.blaze.java.libraries.JarCache$FileCacheAdapter"/>
+    <PrefetchFileSource implementation="com.google.idea.blaze.java.sync.JavaPrefetchFileSource"/>
+  </extensions>
+
+  <extensions defaultExtensionNs="com.intellij">
+    <runConfigurationProducer
+        implementation="com.google.idea.blaze.java.run.producers.BlazeJavaMainClassRunConfigurationProducer"
+        order="first"/>
+    <runConfigurationProducer
+        implementation="com.google.idea.blaze.java.run.producers.BlazeJavaTestClassConfigurationProducer"
+        order="first"/>
+    <runConfigurationProducer
+        implementation="com.google.idea.blaze.java.run.producers.BlazeJavaTestMethodConfigurationProducer"
+        order="first"/>
+    <projectViewNodeDecorator implementation="com.google.idea.blaze.java.syncstatus.BlazeJavaSyncStatusClassNodeDecorator"/>
+    <editorTabColorProvider implementation="com.google.idea.blaze.java.syncstatus.BlazeJavaSyncStatusEditorTabColorProvider"/>
+    <editorTabTitleProvider implementation="com.google.idea.blaze.java.syncstatus.BlazeJavaSyncStatusEditorTabTitleProvider"/>
+    <applicationService serviceInterface="com.google.idea.blaze.java.sync.source.JavaSourcePackageReader"
+                        serviceImplementation="com.google.idea.blaze.java.sync.source.JavaSourcePackageReader"/>
+    <applicationService serviceInterface="com.google.idea.blaze.java.sync.source.PackageManifestReader"
+                        serviceImplementation="com.google.idea.blaze.java.sync.source.PackageManifestReader"/>
+    <programRunner implementation="com.google.idea.blaze.java.run.BlazeJavaDebuggerRunner"/>
+    <projectService serviceInterface="com.google.idea.blaze.base.ui.BlazeProblemsView"
+                    serviceImplementation="com.google.idea.blaze.java.ui.BlazeIntelliJProblemsView"/>
+    <projectService serviceImplementation="com.google.idea.blaze.java.libraries.SourceJarManager"/>
+    <refactoring.safeDeleteProcessor id="build_file_safe_delete" order="before javaProcessor"
+                                     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.BlazeAttachSourceProvider"/>
+    <applicationService serviceImplementation="com.google.idea.blaze.java.settings.BlazeJavaUserSettings"/>
+  </extensions>
+
+  <extensionPoints>
+    <extensionPoint qualifiedName="com.google.idea.blaze.java.JavaSyncAugmenter"
+                    interface="com.google.idea.blaze.java.sync.BlazeJavaSyncAugmenter"/>
+  </extensionPoints>
+</idea-plugin>
diff --git a/java/src/com/google/idea/blaze/java/lang/build/BuildFileSafeDeleteProcessor.java b/java/src/com/google/idea/blaze/java/lang/build/BuildFileSafeDeleteProcessor.java
new file mode 100644
index 0000000..390ce7b
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/lang/build/BuildFileSafeDeleteProcessor.java
@@ -0,0 +1,155 @@
+/*
+ * 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.lang.build;
+
+import com.google.idea.blaze.base.lang.buildfile.references.GlobReference;
+import com.google.idea.blaze.base.lang.buildfile.search.BlazePackage;
+import com.google.idea.blaze.base.lang.buildfile.search.ResolveUtil;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFileSystemItem;
+import com.intellij.refactoring.safeDelete.JavaSafeDeleteProcessor;
+import com.intellij.refactoring.safeDelete.NonCodeUsageSearchInfo;
+import com.intellij.refactoring.safeDelete.usageInfo.SafeDeleteUsageInfo;
+import com.intellij.usageView.UsageInfo;
+import com.intellij.util.IncorrectOperationException;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Removes glob references which don't refer directly to the item(s) being deleted (b/28979434)
+ * (e.g. indirect references from glob([*.java])).
+ *
+ * <p>Runs before JavaSafeDeleteProcessor, and delegates to it, removing indirect glob references.
+ * Only the first valid SafeDeleteProcessorDelegate is used in almost all cases*, so this class
+ * effectively replaces JavaSafeDeleteProcessor (*in the situations where all processors are used,
+ * this class has no effect).
+ */
+public class BuildFileSafeDeleteProcessor extends JavaSafeDeleteProcessor {
+
+  /**
+   * Delegates to JavaSafeDeleteProcessor, then removes indirect glob references which we don't want
+   * to block safe delete.
+   */
+  @Nullable
+  @Override
+  public NonCodeUsageSearchInfo findUsages(
+      @NotNull PsiElement element,
+      @NotNull PsiElement[] allElementsToDelete,
+      @NotNull List<UsageInfo> result) {
+    NonCodeUsageSearchInfo superResult = super.findUsages(element, allElementsToDelete, result);
+    Iterator<UsageInfo> iter = result.iterator();
+    while (iter.hasNext()) {
+      if (ignoreUsage(iter.next())) {
+        iter.remove();
+      }
+    }
+    return superResult;
+  }
+
+  /**
+   * We keep globs which reference the file directly (i.e. without wildcards), and remove all
+   * indirect references for the purposes of the 'safe delete' action.
+   */
+  private static boolean ignoreUsage(UsageInfo usage) {
+    if (usage.getReference() instanceof GlobReference && usage instanceof SafeDeleteUsageInfo) {
+      PsiElement referencedElement = ((SafeDeleteUsageInfo) usage).getReferencedElement();
+      PsiFileSystemItem file = ResolveUtil.asFileSystemItemSearch(referencedElement);
+      String relativePath = getBlazePackageRelativePathToFile(file);
+      if (relativePath == null) {
+        return false;
+      }
+      return !((GlobReference) usage.getReference())
+          .matchesDirectly(relativePath, file.isDirectory());
+    }
+    return false;
+  }
+
+  @Nullable
+  private static String getBlazePackageRelativePathToFile(@Nullable PsiFileSystemItem file) {
+    if (file == null) {
+      return null;
+    }
+    BlazePackage containingPackage = BlazePackage.getContainingPackage(file);
+    if (containingPackage == null) {
+      return null;
+    }
+    return containingPackage.getRelativePathToChild(file.getVirtualFile());
+  }
+
+  @Override
+  public boolean handlesElement(PsiElement element) {
+    return super.handlesElement(element);
+  }
+
+  @Nullable
+  @Override
+  public Collection<? extends PsiElement> getElementsToSearch(
+      @NotNull PsiElement element,
+      @Nullable Module module,
+      @NotNull Collection<PsiElement> allElementsToDelete) {
+    return super.getElementsToSearch(element, module, allElementsToDelete);
+  }
+
+  @Nullable
+  @Override
+  public Collection<PsiElement> getAdditionalElementsToDelete(
+      @NotNull PsiElement element,
+      @NotNull Collection<PsiElement> allElementsToDelete,
+      boolean askUser) {
+    return super.getAdditionalElementsToDelete(element, allElementsToDelete, askUser);
+  }
+
+  @Nullable
+  @Override
+  public Collection<String> findConflicts(
+      @NotNull PsiElement element, @NotNull PsiElement[] allElementsToDelete) {
+    return super.findConflicts(element, allElementsToDelete);
+  }
+
+  @Nullable
+  @Override
+  public UsageInfo[] preprocessUsages(Project project, UsageInfo[] usages) {
+    return usages;
+  }
+
+  @Override
+  public void prepareForDeletion(PsiElement element) throws IncorrectOperationException {}
+
+  @Override
+  public boolean isToSearchInComments(PsiElement element) {
+    return super.isToSearchInComments(element);
+  }
+
+  @Override
+  public void setToSearchInComments(PsiElement element, boolean enabled) {
+    super.setToSearchInComments(element, enabled);
+  }
+
+  @Override
+  public boolean isToSearchForTextOccurrences(PsiElement element) {
+    return super.isToSearchForTextOccurrences(element);
+  }
+
+  @Override
+  public void setToSearchForTextOccurrences(PsiElement element, boolean enabled) {
+    super.setToSearchForTextOccurrences(element, enabled);
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/libraries/AddLibraryRuleDirectoryToProjectViewAction.java b/java/src/com/google/idea/blaze/java/libraries/AddLibraryRuleDirectoryToProjectViewAction.java
new file mode 100644
index 0000000..ac34691
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/libraries/AddLibraryRuleDirectoryToProjectViewAction.java
@@ -0,0 +1,162 @@
+/*
+ * 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.libraries;
+
+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.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.WorkspacePath;
+import com.google.idea.blaze.base.projectview.ProjectViewEdit;
+import com.google.idea.blaze.base.projectview.ProjectViewManager;
+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.sections.DirectoryEntry;
+import com.google.idea.blaze.base.projectview.section.sections.DirectorySection;
+import com.google.idea.blaze.base.settings.BlazeUserSettings;
+import com.google.idea.blaze.base.sync.BlazeSyncManager;
+import com.google.idea.blaze.base.sync.BlazeSyncParams;
+import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
+import com.google.idea.blaze.java.sync.model.BlazeJarLibrary;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.Presentation;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.libraries.Library;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.util.io.FileUtil;
+import java.io.File;
+import java.util.List;
+import java.util.Set;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+class AddLibraryRuleDirectoryToProjectViewAction extends BlazeAction {
+  @Override
+  public void actionPerformed(AnActionEvent e) {
+    Project project = e.getProject();
+    assert project != null;
+    Library library = LibraryActionHelper.findLibraryForAction(e);
+    if (library != null) {
+      addDirectoriesToProjectView(project, ImmutableList.of(library));
+    }
+  }
+
+  @Override
+  protected void doUpdate(@NotNull AnActionEvent e) {
+    Presentation presentation = e.getPresentation();
+    boolean visible = false;
+    boolean enabled = false;
+    Project project = e.getProject();
+    if (project != null) {
+      Library library = LibraryActionHelper.findLibraryForAction(e);
+      if (library != null) {
+        visible = true;
+        if (getDirectoryToAddForLibrary(project, library) != null) {
+          enabled = true;
+        }
+      }
+    }
+    presentation.setVisible(visible);
+    presentation.setEnabled(enabled);
+  }
+
+  @Nullable
+  static WorkspacePath getDirectoryToAddForLibrary(Project project, Library library) {
+    BlazeJarLibrary blazeLibrary =
+        LibraryActionHelper.findLibraryFromIntellijLibrary(project, library);
+    if (blazeLibrary == null) {
+      return null;
+    }
+    Label originatingRule = blazeLibrary.originatingRule;
+    if (originatingRule == null) {
+      return null;
+    }
+    BlazeProjectData blazeProjectData =
+        BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
+    if (blazeProjectData == null) {
+      return null;
+    }
+    RuleIdeInfo rule = blazeProjectData.ruleMap.get(originatingRule);
+    if (rule == 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)) {
+      return null;
+    }
+    if (rule.buildFile == null) {
+      return null;
+    }
+    File buildFile = new File(rule.buildFile.getRelativePath());
+    WorkspacePath workspacePath = new WorkspacePath(buildFile.getParent());
+    ProjectViewSet projectViewSet = ProjectViewManager.getInstance(project).getProjectViewSet();
+    if (projectViewSet == null) {
+      return null;
+    }
+    boolean exists =
+        projectViewSet
+            .listItems(DirectorySection.KEY)
+            .stream()
+            .anyMatch(
+                entry ->
+                    FileUtil.isAncestor(
+                        entry.directory.relativePath(), workspacePath.relativePath(), false));
+    if (exists) {
+      return null;
+    }
+    return workspacePath;
+  }
+
+  static void addDirectoriesToProjectView(Project project, List<Library> libraries) {
+    Set<WorkspacePath> workspacePaths = Sets.newHashSet();
+    for (Library library : libraries) {
+      WorkspacePath workspacePath = getDirectoryToAddForLibrary(project, library);
+      if (workspacePath != null) {
+        workspacePaths.add(workspacePath);
+      }
+    }
+    ProjectViewEdit edit =
+        ProjectViewEdit.editLocalProjectView(
+            project,
+            builder -> {
+              ListSection<DirectoryEntry> existingSection = builder.getLast(DirectorySection.KEY);
+              ListSection.Builder<DirectoryEntry> directoryBuilder =
+                  ListSection.update(DirectorySection.KEY, existingSection);
+              for (WorkspacePath workspacePath : workspacePaths) {
+                directoryBuilder.add(new DirectoryEntry(workspacePath, true));
+              }
+              builder.replace(existingSection, directoryBuilder);
+              return true;
+            });
+    if (edit == null) {
+      Messages.showErrorDialog(
+          "Could not modify project view. Check for errors in your project view and try again",
+          "Error");
+      return;
+    }
+    edit.apply();
+    BlazeSyncManager.getInstance(project)
+        .requestProjectSync(
+            new BlazeSyncParams.Builder("Adding Library", BlazeSyncParams.SyncMode.INCREMENTAL)
+                .addProjectViewTargets(true)
+                .addWorkingSet(BlazeUserSettings.getInstance().getExpandSyncToWorkingSet())
+                .build());
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/libraries/AddLibraryRuleDirectoryToProjectViewAttachSourcesProvider.java b/java/src/com/google/idea/blaze/java/libraries/AddLibraryRuleDirectoryToProjectViewAttachSourcesProvider.java
new file mode 100644
index 0000000..03aaf3b
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/libraries/AddLibraryRuleDirectoryToProjectViewAttachSourcesProvider.java
@@ -0,0 +1,83 @@
+/*
+ * 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.libraries;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
+import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
+import com.intellij.codeInsight.AttachSourcesProvider;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.LibraryOrderEntry;
+import com.intellij.openapi.roots.libraries.Library;
+import com.intellij.openapi.util.ActionCallback;
+import com.intellij.psi.PsiFile;
+import java.util.Collection;
+import java.util.List;
+import org.jetbrains.annotations.NotNull;
+
+/** @author Sergey Evdokimov */
+public class AddLibraryRuleDirectoryToProjectViewAttachSourcesProvider
+    implements AttachSourcesProvider {
+
+  @NotNull
+  @Override
+  public Collection<AttachSourcesAction> getActions(
+      List<LibraryOrderEntry> orderEntries, final PsiFile psiFile) {
+    Project project = psiFile.getProject();
+    BlazeProjectData blazeProjectData =
+        BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
+    if (blazeProjectData == null) {
+      return ImmutableList.of();
+    }
+
+    List<Library> librariesToAttachSourceTo = Lists.newArrayList();
+    for (LibraryOrderEntry orderEntry : orderEntries) {
+      Library library = orderEntry.getLibrary();
+      WorkspacePath workspacePath =
+          AddLibraryRuleDirectoryToProjectViewAction.getDirectoryToAddForLibrary(project, library);
+      if (workspacePath == null) {
+        continue;
+      }
+      librariesToAttachSourceTo.add(library);
+    }
+
+    if (librariesToAttachSourceTo.isEmpty()) {
+      return ImmutableList.of();
+    }
+
+    return ImmutableList.of(
+        new AttachSourcesAction() {
+          @Override
+          public String getName() {
+            return "Add Source Directories To Project View";
+          }
+
+          @Override
+          public String getBusyText() {
+            return "Adding directories...";
+          }
+
+          @Override
+          public ActionCallback perform(List<LibraryOrderEntry> orderEntriesContainingFile) {
+            AddLibraryRuleDirectoryToProjectViewAction.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
new file mode 100644
index 0000000..ca216ac
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/libraries/AttachSourceJarAction.java
@@ -0,0 +1,93 @@
+/*
+ * 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.libraries;
+
+import com.google.idea.blaze.base.actions.BlazeAction;
+import com.google.idea.blaze.base.ideinfo.LibraryArtifact;
+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;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.impl.libraries.ProjectLibraryTable;
+import com.intellij.openapi.roots.libraries.Library;
+import com.intellij.openapi.roots.libraries.LibraryTable;
+import com.intellij.openapi.ui.Messages;
+import org.jetbrains.annotations.NotNull;
+
+class AttachSourceJarAction extends BlazeAction {
+  @Override
+  public void actionPerformed(AnActionEvent e) {
+    Project project = e.getProject();
+    assert project != null;
+    Library library = LibraryActionHelper.findLibraryForAction(e);
+    if (library != null) {
+      BlazeJarLibrary blazeLibrary =
+          LibraryActionHelper.findLibraryFromIntellijLibrary(project, library);
+      if (blazeLibrary == null) {
+        Messages.showErrorDialog(
+            project, "Could not find this library in the project.", CommonBundle.getErrorTitle());
+        return;
+      }
+
+      final LibraryArtifact libraryArtifact = blazeLibrary.libraryArtifact;
+      if (libraryArtifact.sourceJar == null) {
+        return;
+      }
+      SourceJarManager sourceJarManager = SourceJarManager.getInstance(project);
+      boolean attachSourceJar = !sourceJarManager.hasSourceJarAttached(blazeLibrary.key);
+      sourceJarManager.setHasSourceJarAttached(blazeLibrary.key, attachSourceJar);
+
+      ApplicationManager.getApplication()
+          .runWriteAction(
+              () -> {
+                LibraryTable libraryTable = ProjectLibraryTable.getInstance(project);
+                LibraryTable.ModifiableModel libraryTableModel = libraryTable.getModifiableModel();
+                LibraryEditor.updateLibrary(project, libraryTable, libraryTableModel, blazeLibrary);
+                libraryTableModel.commit();
+              });
+    }
+  }
+
+  @Override
+  protected void doUpdate(@NotNull AnActionEvent e) {
+    Presentation presentation = e.getPresentation();
+    String text = "Attach Source Jar";
+    boolean visible = false;
+    boolean enabled = false;
+    Project project = e.getProject();
+    if (project != null) {
+      Library library = LibraryActionHelper.findLibraryForAction(e);
+      if (library != null) {
+        visible = true;
+
+        BlazeJarLibrary blazeLibrary =
+            LibraryActionHelper.findLibraryFromIntellijLibrary(e.getProject(), library);
+        if (blazeLibrary != null && blazeLibrary.libraryArtifact.sourceJar != null) {
+          enabled = true;
+          if (SourceJarManager.getInstance(project).hasSourceJarAttached(blazeLibrary.key)) {
+            text = "Detach Source Jar";
+          }
+        }
+      }
+    }
+    presentation.setVisible(visible);
+    presentation.setEnabled(enabled);
+    presentation.setText(text);
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/libraries/BlazeAttachSourceProvider.java b/java/src/com/google/idea/blaze/java/libraries/BlazeAttachSourceProvider.java
new file mode 100644
index 0000000..b70ba2f
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/libraries/BlazeAttachSourceProvider.java
@@ -0,0 +1,136 @@
+/*
+ * 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.libraries;
+
+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.ideinfo.LibraryArtifact;
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
+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.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.LibraryOrderEntry;
+import com.intellij.openapi.roots.impl.libraries.ProjectLibraryTable;
+import com.intellij.openapi.roots.libraries.Library;
+import com.intellij.openapi.roots.libraries.LibraryTable;
+import com.intellij.openapi.util.ActionCallback;
+import com.intellij.psi.PsiFile;
+import com.intellij.util.ui.UIUtil;
+import java.util.Collection;
+import java.util.List;
+import org.jetbrains.annotations.NotNull;
+
+/** @author Sergey Evdokimov */
+public class BlazeAttachSourceProvider implements AttachSourcesProvider {
+  private static final Logger LOG = Logger.getInstance(BlazeAttachSourceProvider.class);
+
+  @NotNull
+  @Override
+  public Collection<AttachSourcesAction> getActions(
+      List<LibraryOrderEntry> orderEntries, final PsiFile psiFile) {
+    Project project = psiFile.getProject();
+    BlazeProjectData blazeProjectData =
+        BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
+    if (blazeProjectData == null) {
+      return ImmutableList.of();
+    }
+
+    List<BlazeLibrary> librariesToAttachSourceTo = Lists.newArrayList();
+    for (LibraryOrderEntry orderEntry : orderEntries) {
+      Library library = orderEntry.getLibrary();
+      if (library == null) {
+        continue;
+      }
+      LibraryKey libraryKey = LibraryKey.fromIntelliJLibrary(library);
+      if (SourceJarManager.getInstance(project).hasSourceJarAttached(libraryKey)) {
+        continue;
+      }
+      BlazeJarLibrary blazeLibrary =
+          LibraryActionHelper.findLibraryFromIntellijLibrary(project, library);
+      if (blazeLibrary == null) {
+        continue;
+      }
+      LibraryArtifact libraryArtifact = blazeLibrary.libraryArtifact;
+      ArtifactLocation artifactLocation = libraryArtifact.sourceJar;
+      if (artifactLocation == null) {
+        continue;
+      }
+      librariesToAttachSourceTo.add(blazeLibrary);
+    }
+
+    if (librariesToAttachSourceTo.isEmpty()) {
+      return ImmutableList.of();
+    }
+
+    /**
+     * Semi-hack: When sources are requested and we have them, we attach them automatically if the
+     * corresponding user setting is active.
+     */
+    if (BlazeJavaUserSettings.getInstance().getAttachSourcesOnDemand()) {
+      UIUtil.invokeLaterIfNeeded(
+          () -> {
+            attachSources(project, librariesToAttachSourceTo);
+          });
+      return ImmutableList.of();
+    }
+
+    return ImmutableList.of(
+        new AttachSourcesAction() {
+          @Override
+          public String getName() {
+            return "Attach Blaze Source Jars";
+          }
+
+          @Override
+          public String getBusyText() {
+            return "Attaching source jars...";
+          }
+
+          @Override
+          public ActionCallback perform(List<LibraryOrderEntry> orderEntriesContainingFile) {
+            attachSources(project, librariesToAttachSourceTo);
+            return ActionCallback.DONE;
+          }
+        });
+  }
+
+  static void attachSources(Project project, Collection<BlazeLibrary> librariesToAttachSourceTo) {
+    ApplicationManager.getApplication()
+        .runWriteAction(
+            () -> {
+              LibraryTable libraryTable = ProjectLibraryTable.getInstance(project);
+              LibraryTable.ModifiableModel libraryTableModel = libraryTable.getModifiableModel();
+              for (BlazeLibrary blazeLibrary : librariesToAttachSourceTo) {
+                // Make sure we don't do it twice
+                if (SourceJarManager.getInstance(project).hasSourceJarAttached(blazeLibrary.key)) {
+                  continue;
+                }
+                SourceJarManager.getInstance(project)
+                    .setHasSourceJarAttached(blazeLibrary.key, true);
+                LibraryEditor.updateLibrary(project, libraryTable, libraryTableModel, blazeLibrary);
+              }
+              libraryTableModel.commit();
+            });
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/libraries/ExcludeLibraryAction.java b/java/src/com/google/idea/blaze/java/libraries/ExcludeLibraryAction.java
new file mode 100644
index 0000000..8d2776e
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/libraries/ExcludeLibraryAction.java
@@ -0,0 +1,89 @@
+/*
+ * 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.libraries;
+
+import com.google.idea.blaze.base.actions.BlazeAction;
+import com.google.idea.blaze.base.ideinfo.LibraryArtifact;
+import com.google.idea.blaze.base.projectview.ProjectViewEdit;
+import com.google.idea.blaze.base.projectview.section.Glob;
+import com.google.idea.blaze.base.projectview.section.ListSection;
+import com.google.idea.blaze.base.settings.BlazeUserSettings;
+import com.google.idea.blaze.base.sync.BlazeSyncManager;
+import com.google.idea.blaze.base.sync.BlazeSyncParams;
+import com.google.idea.blaze.java.projectview.ExcludeLibrarySection;
+import com.google.idea.blaze.java.sync.model.BlazeJarLibrary;
+import com.intellij.CommonBundle;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.Presentation;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.libraries.Library;
+import com.intellij.openapi.ui.Messages;
+import org.jetbrains.annotations.NotNull;
+
+class ExcludeLibraryAction extends BlazeAction {
+  @Override
+  public void actionPerformed(AnActionEvent e) {
+    Project project = e.getProject();
+    assert project != null;
+    Library library = LibraryActionHelper.findLibraryForAction(e);
+    if (library != null) {
+      BlazeJarLibrary blazeLibrary =
+          LibraryActionHelper.findLibraryFromIntellijLibrary(project, library);
+      if (blazeLibrary == null) {
+        Messages.showErrorDialog(
+            project, "Could not find this library in the project.", CommonBundle.getErrorTitle());
+        return;
+      }
+
+      final LibraryArtifact libraryArtifact = blazeLibrary.libraryArtifact;
+      final String path = libraryArtifact.jarForIntellijLibrary().getRelativePath();
+
+      ProjectViewEdit edit =
+          ProjectViewEdit.editLocalProjectView(
+              project,
+              builder -> {
+                ListSection<Glob> existingSection = builder.getLast(ExcludeLibrarySection.KEY);
+                builder.replace(
+                    existingSection,
+                    ListSection.update(ExcludeLibrarySection.KEY, existingSection)
+                        .add(new Glob(path)));
+                return true;
+              });
+      if (edit == null) {
+        Messages.showErrorDialog(
+            "Could not modify project view. Check for errors in your project view and try again",
+            "Error");
+        return;
+      }
+      edit.apply();
+
+      BlazeSyncManager.getInstance(project)
+          .requestProjectSync(
+              new BlazeSyncParams.Builder("Sync", BlazeSyncParams.SyncMode.INCREMENTAL)
+                  .addProjectViewTargets(true)
+                  .addWorkingSet(BlazeUserSettings.getInstance().getExpandSyncToWorkingSet())
+                  .build());
+    }
+  }
+
+  @Override
+  protected void doUpdate(@NotNull AnActionEvent e) {
+    Presentation presentation = e.getPresentation();
+    boolean enabled = LibraryActionHelper.findLibraryForAction(e) != null;
+    presentation.setVisible(enabled);
+    presentation.setEnabled(enabled);
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/libraries/JarCache.java b/java/src/com/google/idea/blaze/java/libraries/JarCache.java
new file mode 100644
index 0000000..33b5cad
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/libraries/JarCache.java
@@ -0,0 +1,336 @@
+/*
+ * 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.libraries;
+
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+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.common.util.concurrent.ListeningExecutorService;
+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.BlazeProjectData;
+import com.google.idea.blaze.base.prefetch.FetchExecutor;
+import com.google.idea.blaze.base.projectview.ProjectViewSet;
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.scope.output.PrintOutput;
+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.data.BlazeDataStorage;
+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;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.io.FileUtil;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
+
+/** 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;
+  private final File cacheDir;
+  private boolean enabled;
+  @Nullable private BiMap<File, String> sourceFileToCacheKey = null;
+
+  public static JarCache getInstance(Project project) {
+    return ServiceManager.getService(project, JarCache.class);
+  }
+
+  public JarCache(Project project) {
+    this.project = project;
+    this.importSettings = BlazeImportSettingsManager.getInstance(project).getImportSettings();
+    this.cacheDir = getCacheDir();
+  }
+
+  public void onSync(
+      BlazeContext context, BlazeProjectData projectData, BlazeSyncParams.SyncMode syncMode) {
+    Collection<BlazeLibrary> libraries = BlazeLibraryCollector.getLibraries(projectData);
+    boolean fullRefresh = syncMode == BlazeSyncParams.SyncMode.FULL;
+    boolean enabled = updateEnabled();
+
+    if (!enabled || fullRefresh) {
+      clearCache();
+    }
+    if (!enabled) {
+      return;
+    }
+
+    boolean attachAllSourceJars = BlazeJavaUserSettings.getInstance().getAttachSourcesByDefault();
+    SourceJarManager sourceJarManager = SourceJarManager.getInstance(project);
+
+    List<BlazeJarLibrary> jarLibraries =
+        libraries
+            .stream()
+            .filter(library -> library instanceof BlazeJarLibrary)
+            .map(library -> (BlazeJarLibrary) library)
+            .collect(Collectors.toList());
+
+    BiMap<File, String> sourceFileToCacheKey = HashBiMap.create(jarLibraries.size());
+    for (BlazeJarLibrary library : jarLibraries) {
+      File jarFile = library.libraryArtifact.jarForIntellijLibrary().getFile();
+      sourceFileToCacheKey.put(jarFile, cacheKeyForJar(jarFile));
+
+      boolean attachSourceJar =
+          attachAllSourceJars || sourceJarManager.hasSourceJarAttached(library.key);
+      if (attachSourceJar && library.libraryArtifact.sourceJar != null) {
+        File srcJarFile = library.libraryArtifact.sourceJar.getFile();
+        sourceFileToCacheKey.put(srcJarFile, cacheKeyForSourceJar(srcJarFile));
+      }
+    }
+
+    this.sourceFileToCacheKey = sourceFileToCacheKey;
+    refresh(context, true);
+  }
+
+  public boolean isEnabled() {
+    return enabled;
+  }
+
+  private boolean updateEnabled() {
+    this.enabled =
+        BlazeJavaUserSettings.getInstance().getUseJarCache()
+            && ENABLE_JAR_CACHE.getValue()
+            && !ApplicationManager.getApplication().isUnitTestMode();
+    return enabled;
+  }
+
+  /** Refreshes any updated files in the cache. Does not add or removes any files */
+  public void refresh() {
+    refresh(null, false);
+  }
+
+  private void refresh(@Nullable BlazeContext context, boolean removeMissingFiles) {
+    if (!enabled || sourceFileToCacheKey == null) {
+      return;
+    }
+
+    // Ensure the cache dir exists
+    if (!cacheDir.exists()) {
+      if (!cacheDir.mkdirs()) {
+        LOG.error("Could not create jar cache directory");
+        return;
+      }
+    }
+
+    // Discover state of source jars
+    ImmutableMap<File, Long> sourceFileTimestamps =
+        FileDiffer.readFileState(sourceFileToCacheKey.keySet());
+    if (sourceFileTimestamps == null) {
+      return;
+    }
+    ImmutableMap.Builder<String, Long> sourceFileCacheKeyToTimestamp = ImmutableMap.builder();
+    for (Map.Entry<File, Long> entry : sourceFileTimestamps.entrySet()) {
+      String cacheKey = sourceFileToCacheKey.get(entry.getKey());
+      sourceFileCacheKeyToTimestamp.put(cacheKey, entry.getValue());
+    }
+
+    // Discover current on-disk cache state
+    File[] cacheFiles = cacheDir.listFiles();
+    assert cacheFiles != null;
+    ImmutableMap<File, Long> cacheFileTimestamps =
+        FileDiffer.readFileState(Lists.newArrayList(cacheFiles));
+    if (cacheFileTimestamps == null) {
+      return;
+    }
+    ImmutableMap.Builder<String, Long> cachedFileCacheKeyToTimestamp = ImmutableMap.builder();
+    for (Map.Entry<File, Long> entry : cacheFileTimestamps.entrySet()) {
+      String cacheKey = entry.getKey().getName(); // Cache key == file name
+      cachedFileCacheKeyToTimestamp.put(cacheKey, entry.getValue());
+    }
+
+    List<String> updatedFiles = Lists.newArrayList();
+    List<String> removedFiles = Lists.newArrayList();
+    FileDiffer.diffState(
+        cachedFileCacheKeyToTimestamp.build(),
+        sourceFileCacheKeyToTimestamp.build(),
+        updatedFiles,
+        removedFiles);
+
+    ListeningExecutorService executor = FetchExecutor.EXECUTOR;
+    List<ListenableFuture<?>> futures = Lists.newArrayList();
+    Map<String, File> cacheKeyToSourceFile = sourceFileToCacheKey.inverse();
+    for (String cacheKey : updatedFiles) {
+      File sourceFile = cacheKeyToSourceFile.get(cacheKey);
+      File cacheFile = cacheFileForKey(cacheKey);
+      futures.add(
+          executor.submit(
+              () -> {
+                try {
+                  Files.copy(
+                      Paths.get(sourceFile.getPath()),
+                      Paths.get(cacheFile.getPath()),
+                      StandardCopyOption.REPLACE_EXISTING,
+                      StandardCopyOption.COPY_ATTRIBUTES);
+                } catch (IOException e) {
+                  LOG.warn(e);
+                }
+              }));
+    }
+
+    if (removeMissingFiles) {
+      for (String cacheKey : removedFiles) {
+        File cacheFile = cacheFileForKey(cacheKey);
+        futures.add(
+            executor.submit(
+                () -> {
+                  try {
+                    Files.deleteIfExists(Paths.get(cacheFile.getPath()));
+                  } catch (IOException e) {
+                    LOG.warn(e);
+                  }
+                }));
+      }
+    }
+
+    try {
+      Futures.allAsList(futures).get();
+    } catch (InterruptedException e) {
+      Thread.currentThread().interrupt();
+      LOG.warn(e);
+    } catch (ExecutionException e) {
+      LOG.error(e);
+    }
+    if (context != null && updatedFiles.size() > 0) {
+      context.output(PrintOutput.log(String.format("Copied %d jars", updatedFiles.size())));
+    }
+    if (context != null && removedFiles.size() > 0 && removeMissingFiles) {
+      context.output(PrintOutput.log(String.format("Removed %d jars", removedFiles.size())));
+    }
+    if (context != null) {
+      try {
+        File[] finalCacheFiles = cacheDir.listFiles();
+        assert finalCacheFiles != null;
+        ImmutableMap<File, Long> cacheFileSizes =
+            FileSizeScanner.readFilesizes(Lists.newArrayList(finalCacheFiles));
+        Long total =
+            cacheFileSizes.values().stream().reduce((size1, size2) -> size1 + size2).orElse(0L);
+        context.output(
+            PrintOutput.log(
+                String.format(
+                    "Total Jar Cache size: %d kB (%d files)",
+                    total / 1024, finalCacheFiles.length)));
+      } catch (Exception e) {
+        LOG.warn("Could not determine cache size", e);
+      }
+    }
+  }
+
+  private void clearCache() {
+    if (cacheDir.exists()) {
+      File[] cacheFiles = cacheDir.listFiles();
+      if (cacheFiles != null) {
+        FileUtil.asyncDelete(Lists.newArrayList(cacheFiles));
+      }
+    }
+    sourceFileToCacheKey = null;
+  }
+
+  /** Gets the cached file for a jar. If it doesn't exist, we return the file from the library. */
+  public File getCachedJar(BlazeJarLibrary library) {
+    File file = library.libraryArtifact.jarForIntellijLibrary().getFile();
+    if (!enabled || sourceFileToCacheKey == null) {
+      return file;
+    }
+    String cacheKey = sourceFileToCacheKey.get(file);
+    if (cacheKey == null) {
+      return file;
+    }
+    return cacheFileForKey(cacheKey);
+  }
+
+  /** Gets the cached file for a source jar. */
+  @Nullable
+  public File getCachedSourceJar(BlazeJarLibrary library) {
+    if (library.libraryArtifact.sourceJar == null) {
+      return null;
+    }
+    File file = library.libraryArtifact.sourceJar.getFile();
+    if (!enabled || sourceFileToCacheKey == null) {
+      return file;
+    }
+    String cacheKey = sourceFileToCacheKey.get(file);
+    if (cacheKey == null) {
+      return file;
+    }
+    return cacheFileForKey(cacheKey);
+  }
+
+  private static String cacheKeyInternal(File jar) {
+    int parentHash = jar.getParent().hashCode();
+    return FileUtil.getNameWithoutExtension(jar) + "_" + Integer.toHexString(parentHash);
+  }
+
+  private static String cacheKeyForJar(File jar) {
+    return cacheKeyInternal(jar) + ".jar";
+  }
+
+  private static String cacheKeyForSourceJar(File srcjar) {
+    return cacheKeyInternal(srcjar) + "-src.jar";
+  }
+
+  private File cacheFileForKey(String key) {
+    return new File(cacheDir, key);
+  }
+
+  private File getCacheDir() {
+    return new File(BlazeDataStorage.getProjectDataDir(importSettings), "libraries");
+  }
+
+  static class FileCacheAdapter implements FileCache {
+    @Override
+    public String getName() {
+      return "Jar Cache";
+    }
+
+    @Override
+    public void onSync(
+        Project project,
+        BlazeContext context,
+        ProjectViewSet projectViewSet,
+        BlazeProjectData projectData,
+        BlazeSyncParams.SyncMode syncMode) {
+      getInstance(project).onSync(context, projectData, syncMode);
+    }
+
+    @Override
+    public void refreshFiles(Project project) {
+      getInstance(project).refresh();
+    }
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/libraries/LibraryActionHelper.java b/java/src/com/google/idea/blaze/java/libraries/LibraryActionHelper.java
new file mode 100644
index 0000000..3b3132c
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/libraries/LibraryActionHelper.java
@@ -0,0 +1,87 @@
+/*
+ * 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.libraries;
+
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
+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;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.impl.libraries.ProjectLibraryTable;
+import com.intellij.openapi.roots.libraries.Library;
+import com.intellij.openapi.roots.libraries.LibraryTable;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.pom.Navigatable;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+class LibraryActionHelper {
+
+  static BlazeJarLibrary findLibraryFromIntellijLibrary(Project project, Library library) {
+    LibraryKey libraryKey = LibraryKey.fromIntelliJLibrary(library);
+    BlazeProjectData projectData =
+        BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
+    if (projectData == null) {
+      return null;
+    }
+    BlazeJavaSyncData syncData = projectData.syncState.get(BlazeJavaSyncData.class);
+    if (syncData == null) {
+      Messages.showErrorDialog(project, "Project isn't synced. Please resync project.", "Error");
+      return null;
+    }
+
+    BlazeLibrary blazeLibrary = syncData.importResult.libraries.get(libraryKey);
+    if (!(blazeLibrary instanceof BlazeJarLibrary)) {
+      return null;
+    }
+    return (BlazeJarLibrary) blazeLibrary;
+  }
+
+  @Nullable
+  public static Library findLibraryForAction(@NotNull AnActionEvent e) {
+    Project project = e.getProject();
+    if (project != null) {
+      NamedLibraryElementNode node = findLibraryNode(e.getDataContext());
+      if (node != null) {
+        String libraryName = node.getName();
+        if (StringUtil.isNotEmpty(libraryName)) {
+          LibraryTable libraryTable = ProjectLibraryTable.getInstance(project);
+          return libraryTable.getLibraryByName(libraryName);
+        }
+      }
+    }
+    return null;
+  }
+
+  @Nullable
+  private static NamedLibraryElementNode findLibraryNode(@NotNull DataContext dataContext) {
+    Navigatable[] navigatables = CommonDataKeys.NAVIGATABLE_ARRAY.getData(dataContext);
+    if (navigatables != null && navigatables.length == 1) {
+      Navigatable navigatable = navigatables[0];
+      if (navigatable instanceof NamedLibraryElementNode) {
+        return (NamedLibraryElementNode) navigatable;
+      }
+    }
+    return null;
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/libraries/SourceJarManager.java b/java/src/com/google/idea/blaze/java/libraries/SourceJarManager.java
new file mode 100644
index 0000000..10ea861
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/libraries/SourceJarManager.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.java.libraries;
+
+import com.google.common.collect.Sets;
+import com.google.idea.blaze.java.sync.model.LibraryKey;
+import com.intellij.openapi.components.PersistentStateComponent;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.components.State;
+import com.intellij.openapi.components.Storage;
+import com.intellij.openapi.components.StoragePathMacros;
+import com.intellij.openapi.project.Project;
+import java.util.Set;
+import org.jdom.Element;
+
+/** Keeps track of which libraries have source jars attached. */
+@State(name = "BlazeSourceJarManager", storages = @Storage(StoragePathMacros.WORKSPACE_FILE))
+public class SourceJarManager implements PersistentStateComponent<Element> {
+  private Set<LibraryKey> librariesWithSourceJarsAttached = Sets.newHashSet();
+
+  public static SourceJarManager getInstance(Project project) {
+    return ServiceManager.getService(project, SourceJarManager.class);
+  }
+
+  public boolean hasSourceJarAttached(LibraryKey libraryKey) {
+    return librariesWithSourceJarsAttached.contains(libraryKey);
+  }
+
+  public void setHasSourceJarAttached(LibraryKey libraryKey, boolean hasSourceJar) {
+    if (hasSourceJar) {
+      librariesWithSourceJarsAttached.add(libraryKey);
+    } else {
+      librariesWithSourceJarsAttached.remove(libraryKey);
+    }
+  }
+
+  @Override
+  public Element getState() {
+    Element element = new Element("state");
+    for (LibraryKey libraryKey : librariesWithSourceJarsAttached) {
+      Element libElement = new Element("library");
+      libElement.setText(libraryKey.getIntelliJLibraryName());
+      element.addContent(libElement);
+    }
+    return element;
+  }
+
+  @Override
+  public void loadState(Element state) {
+    Set<LibraryKey> librariesWithSourceJars = Sets.newHashSet();
+    for (Element libElement : state.getChildren()) {
+      LibraryKey libraryKey = LibraryKey.fromIntelliJLibraryName(libElement.getText());
+      librariesWithSourceJars.add(libraryKey);
+    }
+    this.librariesWithSourceJarsAttached = librariesWithSourceJars;
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/projectview/ExcludeLibrarySection.java b/java/src/com/google/idea/blaze/java/projectview/ExcludeLibrarySection.java
new file mode 100644
index 0000000..e1c466d
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/projectview/ExcludeLibrarySection.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.java.projectview;
+
+import com.google.idea.blaze.base.projectview.section.Glob;
+import com.google.idea.blaze.base.projectview.section.GlobSectionParser;
+import com.google.idea.blaze.base.projectview.section.ListSection;
+import com.google.idea.blaze.base.projectview.section.SectionKey;
+import com.google.idea.blaze.base.projectview.section.SectionParser;
+
+/** Section for excluding libraries. */
+public class ExcludeLibrarySection {
+  public static final SectionKey<Glob, ListSection<Glob>> KEY = SectionKey.of("exclude_library");
+  public static final SectionParser PARSER = new GlobSectionParser(KEY);
+}
diff --git a/java/src/com/google/idea/blaze/java/projectview/ExcludedLibrarySection.java b/java/src/com/google/idea/blaze/java/projectview/ExcludedLibrarySection.java
new file mode 100644
index 0000000..908d48d
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/projectview/ExcludedLibrarySection.java
@@ -0,0 +1,35 @@
+/*
+ * 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.projectview;
+
+import com.google.idea.blaze.base.projectview.section.Glob;
+import com.google.idea.blaze.base.projectview.section.GlobSectionParser;
+import com.google.idea.blaze.base.projectview.section.ListSection;
+import com.google.idea.blaze.base.projectview.section.SectionKey;
+import com.google.idea.blaze.base.projectview.section.SectionParser;
+
+/** Section for excluding libraries. */
+@Deprecated
+public class ExcludedLibrarySection {
+  public static final SectionKey<Glob, ListSection<Glob>> KEY = SectionKey.of("excluded_libraries");
+  public static final SectionParser PARSER =
+      new GlobSectionParser(KEY) {
+        @Override
+        public boolean isDeprecated() {
+          return true;
+        }
+      };
+}
diff --git a/java/src/com/google/idea/blaze/java/projectview/JavaLanguageLevelSection.java b/java/src/com/google/idea/blaze/java/projectview/JavaLanguageLevelSection.java
new file mode 100644
index 0000000..09cd3b4
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/projectview/JavaLanguageLevelSection.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.java.projectview;
+
+import com.google.idea.blaze.base.projectview.ProjectViewSet;
+import com.google.idea.blaze.base.projectview.parser.ParseContext;
+import com.google.idea.blaze.base.projectview.parser.ProjectViewParser;
+import com.google.idea.blaze.base.projectview.section.ScalarSection;
+import com.google.idea.blaze.base.projectview.section.ScalarSectionParser;
+import com.google.idea.blaze.base.projectview.section.SectionKey;
+import com.google.idea.blaze.base.projectview.section.SectionParser;
+import com.intellij.pom.java.LanguageLevel;
+import javax.annotation.Nullable;
+
+/** Section to force the java language level used */
+public class JavaLanguageLevelSection {
+  public static final SectionKey<Integer, ScalarSection<Integer>> KEY =
+      SectionKey.of("java_language_level");
+  public static final SectionParser PARSER = new JavaLanguageLevelParser();
+
+  public static LanguageLevel getLanguageLevel(
+      ProjectViewSet projectViewSet, LanguageLevel defaultValue) {
+    Integer level = projectViewSet.getScalarValue(KEY, null);
+    if (level == null) {
+      return defaultValue;
+    }
+    return getLanguageLevel(level, defaultValue);
+  }
+
+  @Nullable
+  private static LanguageLevel getLanguageLevel(
+      Integer level, @Nullable LanguageLevel defaultValue) {
+    switch (level) {
+      case 3:
+        return LanguageLevel.JDK_1_3;
+      case 4:
+        return LanguageLevel.JDK_1_4;
+      case 5:
+        return LanguageLevel.JDK_1_5;
+      case 6:
+        return LanguageLevel.JDK_1_6;
+      case 7:
+        return LanguageLevel.JDK_1_7;
+      case 8:
+        return LanguageLevel.JDK_1_8;
+      case 9:
+        return LanguageLevel.JDK_1_9;
+      default:
+        return defaultValue;
+    }
+  }
+
+  private static class JavaLanguageLevelParser extends ScalarSectionParser<Integer> {
+    public JavaLanguageLevelParser() {
+      super(KEY, ':');
+    }
+
+    @Nullable
+    @Override
+    protected Integer parseItem(ProjectViewParser parser, ParseContext parseContext, String rest) {
+      try {
+        Integer value = Integer.parseInt(rest);
+        if (getLanguageLevel(value, null) != null) {
+          return value;
+        }
+        // Fall through to error handler
+      } catch (NumberFormatException e) {
+        // Fall through to error handler
+      }
+      parseContext.addError("Illegal java language level: " + rest);
+      return null;
+    }
+
+    @Override
+    protected void printItem(StringBuilder sb, Integer value) {
+      sb.append(value.toString());
+    }
+
+    @Override
+    public ItemType getItemType() {
+      return ItemType.Other;
+    }
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/psi/JavaPsiFileProvider.java b/java/src/com/google/idea/blaze/java/psi/JavaPsiFileProvider.java
new file mode 100644
index 0000000..2e0d282
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/psi/JavaPsiFileProvider.java
@@ -0,0 +1,35 @@
+/*
+ * 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.psi;
+
+import com.google.idea.blaze.base.lang.buildfile.search.PsiFileProvider;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import javax.annotation.Nullable;
+
+/** Replaces top-level java classes with their corresponding PsiFile */
+public class JavaPsiFileProvider implements PsiFileProvider {
+
+  @Nullable
+  @Override
+  public PsiFile asFileSearch(PsiElement elementToSearch) {
+    if (elementToSearch instanceof PsiClass) {
+      elementToSearch = elementToSearch.getParent();
+    }
+    return elementToSearch instanceof PsiFile ? (PsiFile) elementToSearch : null;
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/run/BlazeJavaDebuggerRunner.java b/java/src/com/google/idea/blaze/java/run/BlazeJavaDebuggerRunner.java
new file mode 100644
index 0000000..55f9900
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/run/BlazeJavaDebuggerRunner.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.java.run;
+
+import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
+import com.intellij.debugger.impl.GenericDebuggerRunner;
+import com.intellij.execution.ExecutionException;
+import com.intellij.execution.configurations.JavaParameters;
+import com.intellij.execution.configurations.RemoteConnection;
+import com.intellij.execution.configurations.RunProfile;
+import com.intellij.execution.configurations.RunProfileState;
+import com.intellij.execution.configurations.RunnerSettings;
+import com.intellij.execution.executors.DefaultDebugExecutor;
+import com.intellij.execution.runners.ExecutionEnvironment;
+import com.intellij.execution.ui.RunContentDescriptor;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/** A runner that adapts the GenericDebuggerRunner to work with Blaze run configurations. */
+public class BlazeJavaDebuggerRunner extends GenericDebuggerRunner {
+  @Override
+  @NotNull
+  public String getRunnerId() {
+    return "Blaze-Debug";
+  }
+
+  @Override
+  public boolean canRun(@NotNull final String executorId, @NotNull final RunProfile profile) {
+    if (executorId.equals(DefaultDebugExecutor.EXECUTOR_ID)
+        && profile instanceof BlazeCommandRunConfiguration) {
+      BlazeCommandRunConfiguration configuration = (BlazeCommandRunConfiguration) profile;
+      RuleIdeInfo rule = configuration.getRuleForTarget();
+      return rule != null && BlazeJavaRunConfigurationHandlerProvider.supportsKind(rule.kind);
+    }
+    return false;
+  }
+
+  @Override
+  public void patch(
+      JavaParameters javaParameters,
+      RunnerSettings runnerSettings,
+      RunProfile runProfile,
+      final boolean beforeExecution) {
+    // We don't want to support Java run configuration patching.
+  }
+
+  @Override
+  @Nullable
+  public RunContentDescriptor createContentDescriptor(
+      @NotNull RunProfileState state, @NotNull ExecutionEnvironment environment)
+      throws ExecutionException {
+    if (!(state instanceof BlazeJavaRunProfileState)) {
+      return null;
+    }
+    BlazeJavaRunProfileState blazeState = (BlazeJavaRunProfileState) state;
+    RemoteConnection connection = blazeState.getRemoteConnection();
+    return attachVirtualMachine(state, environment, connection, true /* pollConnection */);
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/run/BlazeJavaRuleConfigurationFactory.java b/java/src/com/google/idea/blaze/java/run/BlazeJavaRuleConfigurationFactory.java
new file mode 100644
index 0000000..2ed1ca8
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/run/BlazeJavaRuleConfigurationFactory.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.run;
+
+import com.google.idea.blaze.base.command.BlazeCommandName;
+import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+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;
+import com.google.idea.blaze.base.run.BlazeRuleConfigurationFactory;
+import com.google.idea.blaze.base.run.confighandler.BlazeCommandGenericRunConfigurationHandler;
+import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
+import com.intellij.execution.configurations.ConfigurationFactory;
+import com.intellij.execution.configurations.RunConfiguration;
+
+/** Creates run configurations for java_binary. */
+public class BlazeJavaRuleConfigurationFactory extends BlazeRuleConfigurationFactory {
+  @Override
+  public boolean handlesRule(
+      WorkspaceLanguageSettings workspaceLanguageSettings, RuleIdeInfo rule) {
+    return rule.kind == Kind.JAVA_BINARY;
+  }
+
+  @Override
+  protected ConfigurationFactory getConfigurationFactory() {
+    return BlazeCommandRunConfigurationType.getInstance().getFactory();
+  }
+
+  @Override
+  public void setupConfiguration(RunConfiguration configuration, RuleIdeInfo rule) {
+    final BlazeCommandRunConfiguration blazeConfig = (BlazeCommandRunConfiguration) configuration;
+    blazeConfig.setTarget(rule.label);
+
+    BlazeCommandGenericRunConfigurationHandler handler =
+        (BlazeCommandGenericRunConfigurationHandler) blazeConfig.getHandler();
+    handler.setCommand(BlazeCommandName.RUN);
+
+    blazeConfig.setGeneratedName();
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/run/BlazeJavaRunConfigurationHandlerProvider.java b/java/src/com/google/idea/blaze/java/run/BlazeJavaRunConfigurationHandlerProvider.java
new file mode 100644
index 0000000..966fe91
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/run/BlazeJavaRunConfigurationHandlerProvider.java
@@ -0,0 +1,82 @@
+/*
+ * 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.run;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.idea.blaze.base.model.primitives.Kind;
+import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
+import com.google.idea.blaze.base.run.confighandler.BlazeCommandGenericRunConfigurationHandler;
+import com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationHandler;
+import com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationHandlerProvider;
+import com.intellij.execution.Executor;
+import com.intellij.execution.configurations.RunProfileState;
+import com.intellij.execution.executors.DefaultDebugExecutor;
+import com.intellij.execution.runners.ExecutionEnvironment;
+
+/** Java-specific handler for {@link BlazeCommandRunConfiguration}s. */
+public class BlazeJavaRunConfigurationHandlerProvider
+    implements BlazeCommandRunConfigurationHandlerProvider {
+
+  private static final ImmutableSet<Kind> RELEVANT_RULE_KINDS =
+      ImmutableSet.of(Kind.ANDROID_ROBOLECTRIC_TEST, Kind.JAVA_TEST, Kind.JAVA_BINARY);
+
+  static boolean supportsKind(Kind kind) {
+    return RELEVANT_RULE_KINDS.contains(kind);
+  }
+
+  @Override
+  public boolean canHandleKind(Kind kind) {
+    return supportsKind(kind);
+  }
+
+  @Override
+  public BlazeCommandRunConfigurationHandler createHandler(BlazeCommandRunConfiguration config) {
+    return new BlazeJavaRunConfigurationHandler(config);
+  }
+
+  @Override
+  public String getId() {
+    return "BlazeJavaRunConfigurationHandlerProvider";
+  }
+
+  private static class BlazeJavaRunConfigurationHandler
+      extends BlazeCommandGenericRunConfigurationHandler {
+
+    BlazeJavaRunConfigurationHandler(BlazeCommandRunConfiguration configuration) {
+      super(configuration);
+    }
+
+    private BlazeJavaRunConfigurationHandler(
+        BlazeJavaRunConfigurationHandler other, BlazeCommandRunConfiguration configuration) {
+      super(other, configuration);
+    }
+
+    @Override
+    public BlazeJavaRunConfigurationHandler cloneFor(BlazeCommandRunConfiguration configuration) {
+      return new BlazeJavaRunConfigurationHandler(this, configuration);
+    }
+
+    @Override
+    public RunProfileState getState(Executor executor, ExecutionEnvironment environment) {
+      return new BlazeJavaRunProfileState(environment, executor instanceof DefaultDebugExecutor);
+    }
+
+    @Override
+    public String getHandlerName() {
+      return "Java Handler";
+    }
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/run/BlazeJavaRunProfileState.java b/java/src/com/google/idea/blaze/java/run/BlazeJavaRunProfileState.java
new file mode 100644
index 0000000..935a1e0
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/run/BlazeJavaRunProfileState.java
@@ -0,0 +1,158 @@
+/*
+ * 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.run;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.base.async.process.LineProcessingOutputStream;
+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.RuleIdeInfo;
+import com.google.idea.blaze.base.issueparser.IssueOutputLineProcessor;
+import com.google.idea.blaze.base.metrics.Action;
+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.ProjectViewManager;
+import com.google.idea.blaze.base.projectview.ProjectViewSet;
+import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
+import com.google.idea.blaze.base.run.confighandler.BlazeCommandGenericRunConfigurationHandler;
+import com.google.idea.blaze.base.run.processhandler.LineProcessingProcessAdapter;
+import com.google.idea.blaze.base.run.processhandler.ScopedBlazeProcessHandler;
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.scope.scopes.IdeaLogScope;
+import com.google.idea.blaze.base.scope.scopes.IssuesScope;
+import com.google.idea.blaze.base.scope.scopes.LoggedTimingScope;
+import com.google.idea.blaze.base.settings.Blaze;
+import com.google.idea.blaze.base.settings.BlazeImportSettings;
+import com.google.idea.blaze.base.settings.BlazeImportSettingsManager;
+import com.intellij.execution.ExecutionException;
+import com.intellij.execution.configurations.CommandLineState;
+import com.intellij.execution.configurations.RemoteConnection;
+import com.intellij.execution.configurations.RemoteState;
+import com.intellij.execution.configurations.RunProfile;
+import com.intellij.execution.process.ProcessHandler;
+import com.intellij.execution.process.ProcessListener;
+import com.intellij.execution.runners.ExecutionEnvironment;
+import com.intellij.openapi.project.Project;
+
+/**
+ * A Blaze run configuration set up with a an executor, program runner, and other settings, ready to
+ * be executed. This class creates a command line for Blaze and exposes debug connection information
+ * when using a debug executor.
+ */
+final class BlazeJavaRunProfileState extends CommandLineState implements RemoteState {
+  // Blaze seems to always use this port for --java_debug.
+  // TODO(joshgiles): Look at manually identifying and setting port.
+  private static final int DEBUG_PORT = 5005;
+  private static final String DEBUG_HOST_NAME = "localhost";
+
+  private final BlazeCommandRunConfiguration configuration;
+  private final boolean debug;
+
+  public BlazeJavaRunProfileState(ExecutionEnvironment environment, boolean debug) {
+    super(environment);
+    RunProfile runProfile = environment.getRunProfile();
+    assert runProfile instanceof BlazeCommandRunConfiguration;
+    configuration = (BlazeCommandRunConfiguration) runProfile;
+    this.debug = debug;
+  }
+
+  @Override
+  protected ProcessHandler startProcess() throws ExecutionException {
+    Project project = configuration.getProject();
+    BlazeImportSettings importSettings =
+        BlazeImportSettingsManager.getInstance(project).getImportSettings();
+    assert importSettings != null;
+
+    ProjectViewSet projectViewSet = ProjectViewManager.getInstance(project).getProjectViewSet();
+    assert projectViewSet != null;
+
+    BlazeCommand blazeCommand = getBlazeCommand(project, configuration, projectViewSet, debug);
+    WorkspaceRoot workspaceRoot = WorkspaceRoot.fromImportSettings(importSettings);
+    return new ScopedBlazeProcessHandler(
+        project,
+        blazeCommand,
+        workspaceRoot,
+        new ScopedBlazeProcessHandler.ScopedProcessHandlerDelegate() {
+          @Override
+          public void onBlazeContextStart(BlazeContext context) {
+            context
+                .push(new LoggedTimingScope(project, Action.BLAZE_COMMAND_USAGE))
+                .push(new IssuesScope(project))
+                .push(new IdeaLogScope());
+          }
+
+          @Override
+          public ImmutableList<ProcessListener> createProcessListeners(BlazeContext context) {
+            LineProcessingOutputStream outputStream =
+                LineProcessingOutputStream.of(
+                    new IssueOutputLineProcessor(project, context, workspaceRoot));
+            return ImmutableList.of(new LineProcessingProcessAdapter(outputStream));
+          }
+        });
+  }
+
+  @Override
+  public RemoteConnection getRemoteConnection() {
+    if (!debug) {
+      return null;
+    }
+    return new RemoteConnection(
+        true /* useSockets */,
+        DEBUG_HOST_NAME,
+        Integer.toString(DEBUG_PORT),
+        false /* serverMode */);
+  }
+
+  @VisibleForTesting
+  static BlazeCommand getBlazeCommand(
+      Project project,
+      BlazeCommandRunConfiguration configuration,
+      ProjectViewSet projectViewSet,
+      boolean debug) {
+
+    BlazeCommandGenericRunConfigurationHandler handler =
+        configuration.getHandlerIfType(BlazeCommandGenericRunConfigurationHandler.class);
+    assert handler != null;
+
+    BlazeCommandName blazeCommand = handler.getCommand();
+    assert blazeCommand != null;
+    BlazeCommand.Builder command =
+        BlazeCommand.builder(Blaze.getBuildSystem(project), blazeCommand)
+            .setBlazeBinary(handler.getBlazeBinary())
+            .addTargets(configuration.getTarget())
+            .addBlazeFlags(BlazeFlags.buildFlags(project, projectViewSet))
+            .addBlazeFlags(handler.getAllBlazeFlags());
+
+    if (debug) {
+      boolean isJavaBinary = false;
+      RuleIdeInfo rule = configuration.getRuleForTarget();
+      if (rule != null && (rule.kind == Kind.JAVA_BINARY)) {
+        isJavaBinary = true;
+      }
+
+      if (isJavaBinary) {
+        command.addExeFlags(BlazeFlags.JAVA_BINARY_DEBUG);
+      } else {
+        command.addBlazeFlags(BlazeFlags.JAVA_TEST_DEBUG);
+      }
+    }
+
+    command.addExeFlags(handler.getAllExeFlags());
+    return command.build();
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/run/BlazeJavaTestRuleConfigurationFactory.java b/java/src/com/google/idea/blaze/java/run/BlazeJavaTestRuleConfigurationFactory.java
new file mode 100644
index 0000000..42c6256
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/run/BlazeJavaTestRuleConfigurationFactory.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.run;
+
+import com.google.idea.blaze.base.command.BlazeCommandName;
+import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+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;
+import com.google.idea.blaze.base.run.BlazeRuleConfigurationFactory;
+import com.google.idea.blaze.base.run.confighandler.BlazeCommandGenericRunConfigurationHandler;
+import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
+import com.intellij.execution.configurations.ConfigurationFactory;
+import com.intellij.execution.configurations.RunConfiguration;
+
+/** Creates run configurations for java_test and android_robolectric_test. */
+public class BlazeJavaTestRuleConfigurationFactory extends BlazeRuleConfigurationFactory {
+  @Override
+  public boolean handlesRule(
+      WorkspaceLanguageSettings workspaceLanguageSettings, RuleIdeInfo rule) {
+    return rule.kindIsOneOf(Kind.JAVA_TEST, Kind.ANDROID_ROBOLECTRIC_TEST);
+  }
+
+  @Override
+  protected ConfigurationFactory getConfigurationFactory() {
+    return BlazeCommandRunConfigurationType.getInstance().getFactory();
+  }
+
+  @Override
+  public void setupConfiguration(RunConfiguration configuration, RuleIdeInfo rule) {
+    final BlazeCommandRunConfiguration blazeConfig = (BlazeCommandRunConfiguration) configuration;
+    blazeConfig.setTarget(rule.label);
+
+    BlazeCommandGenericRunConfigurationHandler handler =
+        (BlazeCommandGenericRunConfigurationHandler) blazeConfig.getHandler();
+    handler.setCommand(BlazeCommandName.TEST);
+
+    blazeConfig.setGeneratedName();
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/run/RunUtil.java b/java/src/com/google/idea/blaze/java/run/RunUtil.java
new file mode 100644
index 0000000..fab9497
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/run/RunUtil.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.java.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.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.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiFile;
+import java.io.File;
+import java.util.Collection;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/** Utility methods for finding rules and Android facets. */
+public final class RunUtil {
+
+  private RunUtil() {}
+
+  /**
+   * @return The Blaze test rule containing the target test class. In the case of multiple
+   *     containing rules, the first rule sorted alphabetically by label.
+   */
+  @Nullable
+  public static RuleIdeInfo ruleForTestClass(
+      @NotNull Project project,
+      @NotNull PsiClass testClass,
+      @Nullable TestIdeInfo.TestSize testSize) {
+    File testFile = getFileForClass(testClass);
+    if (testFile == null) {
+      return null;
+    }
+    Collection<RuleIdeInfo> rules =
+        TestRuleFinder.getInstance(project).testTargetsForSourceFile(testFile);
+    Label testLabel = TestRuleHeuristic.chooseTestTargetForSourceFile(testFile, rules, testSize);
+    if (testLabel == null) {
+      return null;
+    }
+    return RuleFinder.getInstance().ruleForTarget(project, testLabel);
+  }
+
+  /**
+   * Returns an instance of {@link java.io.File} related to the containing file of the given class.
+   * It returns {@code null} if the given class is not contained in a file and only exists in
+   * memory.
+   */
+  @Nullable
+  public static File getFileForClass(@NotNull PsiClass aClass) {
+    PsiFile containingFile = aClass.getContainingFile();
+    if (containingFile == null) {
+      return null;
+    }
+
+    VirtualFile virtualFile = containingFile.getVirtualFile();
+    if (virtualFile == null) {
+      return null;
+    }
+
+    return new File(virtualFile.getPath());
+  }
+}
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
new file mode 100644
index 0000000..72c35f6
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/run/producers/BlazeJavaMainClassRunConfigurationProducer.java
@@ -0,0 +1,147 @@
+/*
+ * 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.run.producers;
+
+import com.google.common.collect.ImmutableCollection;
+import com.google.idea.blaze.base.command.BlazeCommandName;
+import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.model.RuleMap;
+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.confighandler.BlazeCommandGenericRunConfigurationHandler;
+import com.google.idea.blaze.base.run.producers.BlazeRunConfigurationProducer;
+import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
+import com.google.idea.blaze.java.run.RunUtil;
+import com.intellij.execution.JavaExecutionUtil;
+import com.intellij.execution.Location;
+import com.intellij.execution.actions.ConfigurationContext;
+import com.intellij.execution.application.ApplicationConfigurationType;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Ref;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiMethod;
+import com.intellij.psi.util.PsiMethodUtil;
+import java.io.File;
+import java.util.Objects;
+import org.jetbrains.annotations.Nullable;
+
+/** Creates run configurations for Java main classes sourced by java_binary targets. */
+public class BlazeJavaMainClassRunConfigurationProducer
+    extends BlazeRunConfigurationProducer<BlazeCommandRunConfiguration> {
+
+  public BlazeJavaMainClassRunConfigurationProducer() {
+    super(BlazeCommandRunConfigurationType.getInstance());
+  }
+
+  @Override
+  protected boolean doSetupConfigFromContext(
+      BlazeCommandRunConfiguration configuration,
+      ConfigurationContext context,
+      Ref<PsiElement> sourceElement) {
+    PsiClass mainClass = getMainClass(context);
+    if (mainClass == null) {
+      return false;
+    }
+    // Try setting source element to a main method so ApplicationConfigurationProducer
+    // can't override our configuration by producing a more specific one.
+    PsiMethod mainMethod = PsiMethodUtil.findMainMethod(mainClass);
+    if (mainMethod == null) {
+      sourceElement.set(mainClass);
+    } else {
+      sourceElement.set(mainMethod);
+    }
+
+    Label label = getRuleLabel(context.getProject(), mainClass);
+    if (label == null) {
+      return false;
+    }
+    configuration.setTarget(label);
+    BlazeCommandGenericRunConfigurationHandler handler =
+        configuration.getHandlerIfType(BlazeCommandGenericRunConfigurationHandler.class);
+    if (handler == null) {
+      return false;
+    }
+    handler.setCommand(BlazeCommandName.RUN);
+    configuration.setGeneratedName();
+    return true;
+  }
+
+  @Override
+  protected boolean doIsConfigFromContext(
+      BlazeCommandRunConfiguration configuration, ConfigurationContext context) {
+    BlazeCommandGenericRunConfigurationHandler handler =
+        configuration.getHandlerIfType(BlazeCommandGenericRunConfigurationHandler.class);
+    if (handler == null) {
+      return false;
+    }
+    if (!Objects.equals(handler.getCommand(), BlazeCommandName.RUN)) {
+      return false;
+    }
+    PsiClass mainClass = getMainClass(context);
+    if (mainClass == null) {
+      return false;
+    }
+    Label label = getRuleLabel(context.getProject(), mainClass);
+    if (label == null) {
+      return false;
+    }
+    return Objects.equals(configuration.getTarget(), label);
+  }
+
+  @Nullable
+  private static PsiClass getMainClass(ConfigurationContext context) {
+    Location location = context.getLocation();
+    if (location == null) {
+      return null;
+    }
+    location = JavaExecutionUtil.stepIntoSingleClass(location);
+    if (location == null) {
+      return null;
+    }
+    PsiElement element = location.getPsiElement();
+    if (!element.isPhysical()) {
+      return null;
+    }
+    return ApplicationConfigurationType.getMainClass(element);
+  }
+
+  @Nullable
+  private static Label getRuleLabel(Project project, PsiClass mainClass) {
+    File mainClassFile = RunUtil.getFileForClass(mainClass);
+    ImmutableCollection<Label> labels =
+        SourceToRuleMap.getInstance(project).getTargetsForSourceFile(mainClassFile);
+    BlazeProjectData blazeProjectData =
+        BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
+    if (blazeProjectData == null) {
+      return null;
+    }
+    RuleMap ruleMap = blazeProjectData.ruleMap;
+    for (Label label : labels) {
+      RuleIdeInfo rule = ruleMap.get(label);
+      if (rule.kind == Kind.JAVA_BINARY) {
+        // 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 label;
+      }
+    }
+    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
new file mode 100644
index 0000000..c9c4c3e
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/run/producers/BlazeJavaTestClassConfigurationProducer.java
@@ -0,0 +1,147 @@
+/*
+ * 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.run.producers;
+
+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.TestIdeInfo;
+import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
+import com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType;
+import com.google.idea.blaze.base.run.BlazeConfigurationNameBuilder;
+import com.google.idea.blaze.base.run.confighandler.BlazeCommandGenericRunConfigurationHandler;
+import com.google.idea.blaze.base.run.producers.BlazeRunConfigurationProducer;
+import com.google.idea.blaze.java.run.RunUtil;
+import com.intellij.execution.JavaExecutionUtil;
+import com.intellij.execution.Location;
+import com.intellij.execution.actions.ConfigurationContext;
+import com.intellij.execution.junit.JUnitUtil;
+import com.intellij.openapi.util.Ref;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiMethod;
+import java.util.List;
+import java.util.Objects;
+import org.jetbrains.annotations.NotNull;
+
+/** Producer for run configurations related to Java test classes in Blaze. */
+public class BlazeJavaTestClassConfigurationProducer
+    extends BlazeRunConfigurationProducer<BlazeCommandRunConfiguration> {
+
+  public BlazeJavaTestClassConfigurationProducer() {
+    super(BlazeCommandRunConfigurationType.getInstance());
+  }
+
+  @Override
+  protected boolean doSetupConfigFromContext(
+      @NotNull BlazeCommandRunConfiguration configuration,
+      @NotNull ConfigurationContext context,
+      @NotNull Ref<PsiElement> sourceElement) {
+
+    final Location contextLocation = context.getLocation();
+    assert contextLocation != null;
+    final Location location = JavaExecutionUtil.stepIntoSingleClass(contextLocation);
+    if (location == null) {
+      return false;
+    }
+
+    if (JUnitConfigurationUtil.isMultipleElementsSelected(context)) {
+      return false;
+    }
+
+    PsiClass testClass = JUnitUtil.getTestClass(location);
+    if (testClass == null) {
+      return false;
+    }
+    sourceElement.set(testClass);
+
+    TestIdeInfo.TestSize testSize = TestSizeAnnotationMap.getTestSize(testClass);
+    RuleIdeInfo rule = RunUtil.ruleForTestClass(context.getProject(), testClass, testSize);
+    if (rule == null) {
+      return false;
+    }
+
+    configuration.setTarget(rule.label);
+    BlazeCommandGenericRunConfigurationHandler handler =
+        configuration.getHandlerIfType(BlazeCommandGenericRunConfigurationHandler.class);
+    if (handler == null) {
+      return false;
+    }
+    handler.setCommand(BlazeCommandName.TEST);
+
+    ImmutableList.Builder<String> flags = ImmutableList.builder();
+
+    String qualifiedName = testClass.getQualifiedName();
+    if (qualifiedName != null) {
+      flags.add(BlazeFlags.testFilterFlagForClass(qualifiedName));
+    }
+
+    flags.add(BlazeFlags.TEST_OUTPUT_STREAMED);
+    flags.addAll(handler.getAllBlazeFlags());
+
+    handler.setBlazeFlags(flags.build());
+
+    BlazeConfigurationNameBuilder nameBuilder = new BlazeConfigurationNameBuilder(configuration);
+    nameBuilder.setTargetString(testClass.getName());
+    configuration.setName(nameBuilder.build());
+
+    return true;
+  }
+
+  @Override
+  protected boolean doIsConfigFromContext(
+      @NotNull BlazeCommandRunConfiguration configuration, @NotNull ConfigurationContext context) {
+
+    final Location contextLocation = context.getLocation();
+    assert contextLocation != null;
+    final Location location = JavaExecutionUtil.stepIntoSingleClass(contextLocation);
+    if (location == null) {
+      return false;
+    }
+
+    if (JUnitConfigurationUtil.isMultipleElementsSelected(context)) {
+      return false;
+    }
+
+    Location<PsiMethod> methodLocation = ProducerUtils.getMethodLocation(contextLocation);
+    if (methodLocation != null) {
+      return false;
+    }
+
+    PsiClass testClass = JUnitUtil.getTestClass(location);
+    if (testClass == null) {
+      return false;
+    }
+
+    return checkIfAttributesAreTheSame(configuration, testClass);
+  }
+
+  private boolean checkIfAttributesAreTheSame(
+      @NotNull BlazeCommandRunConfiguration configuration, @NotNull PsiClass testClass) {
+    BlazeCommandGenericRunConfigurationHandler handler =
+        configuration.getHandlerIfType(BlazeCommandGenericRunConfigurationHandler.class);
+    if (handler == null) {
+      return false;
+    }
+    if (!Objects.equals(handler.getCommand(), BlazeCommandName.TEST)) {
+      return false;
+    }
+    List<String> flags = handler.getAllBlazeFlags();
+
+    return flags.contains(BlazeFlags.testFilterFlagForClass(testClass.getQualifiedName()));
+  }
+}
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
new file mode 100644
index 0000000..4b67696
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/run/producers/BlazeJavaTestMethodConfigurationProducer.java
@@ -0,0 +1,171 @@
+/*
+ * 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.run.producers;
+
+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.TestIdeInfo;
+import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
+import com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType;
+import com.google.idea.blaze.base.run.BlazeConfigurationNameBuilder;
+import com.google.idea.blaze.base.run.confighandler.BlazeCommandGenericRunConfigurationHandler;
+import com.google.idea.blaze.base.run.producers.BlazeRunConfigurationProducer;
+import com.google.idea.blaze.java.run.RunUtil;
+import com.intellij.execution.actions.ConfigurationContext;
+import com.intellij.execution.junit.JUnitUtil;
+import com.intellij.openapi.util.Ref;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiMethod;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import org.jetbrains.annotations.NotNull;
+
+/** Producer for run configurations related to Java test methods in Blaze. */
+public class BlazeJavaTestMethodConfigurationProducer
+    extends BlazeRunConfigurationProducer<BlazeCommandRunConfiguration> {
+
+  private static class SelectedMethodInfo {
+    private final PsiMethod firstMethod;
+    private final PsiClass containingClass;
+    private final List<String> methodNames;
+    private final String testFilterFlag;
+
+    public SelectedMethodInfo(
+        PsiMethod firstMethod,
+        PsiClass containingClass,
+        List<String> methodNames,
+        String testFilterFlag) {
+      this.firstMethod = firstMethod;
+      this.containingClass = containingClass;
+      this.methodNames = methodNames;
+      this.testFilterFlag = testFilterFlag;
+    }
+  }
+
+  public BlazeJavaTestMethodConfigurationProducer() {
+    super(BlazeCommandRunConfigurationType.getInstance());
+  }
+
+  @Override
+  protected boolean doSetupConfigFromContext(
+      @NotNull BlazeCommandRunConfiguration configuration,
+      @NotNull ConfigurationContext context,
+      @NotNull Ref<PsiElement> sourceElement) {
+
+    SelectedMethodInfo methodInfo = getSelectedMethodInfo(context);
+    if (methodInfo == null) {
+      return false;
+    }
+
+    // PatternConfigurationProducer also chooses the first method as its source element.
+    // As long as we choose an element at the same PSI hierarchy level,
+    // PatternConfigurationProducer won't override our configuration.
+    sourceElement.set(methodInfo.firstMethod);
+
+    TestIdeInfo.TestSize testSize = TestSizeAnnotationMap.getTestSize(methodInfo.firstMethod);
+    RuleIdeInfo rule =
+        RunUtil.ruleForTestClass(context.getProject(), methodInfo.containingClass, testSize);
+    if (rule == null) {
+      return false;
+    }
+
+    configuration.setTarget(rule.label);
+    BlazeCommandGenericRunConfigurationHandler handler =
+        configuration.getHandlerIfType(BlazeCommandGenericRunConfigurationHandler.class);
+    if (handler == null) {
+      return false;
+    }
+    handler.setCommand(BlazeCommandName.TEST);
+
+    ImmutableList.Builder<String> flags = ImmutableList.builder();
+    flags.add(methodInfo.testFilterFlag);
+    flags.add(BlazeFlags.TEST_OUTPUT_STREAMED);
+    flags.addAll(handler.getAllBlazeFlags());
+
+    handler.setBlazeFlags(flags.build());
+
+    BlazeConfigurationNameBuilder nameBuilder = new BlazeConfigurationNameBuilder(configuration);
+    nameBuilder.setTargetString(
+        String.format(
+            "%s.%s",
+            methodInfo.containingClass.getName(), String.join(",", methodInfo.methodNames)));
+    configuration.setName(nameBuilder.build());
+
+    return true;
+  }
+
+  @Override
+  protected boolean doIsConfigFromContext(
+      @NotNull BlazeCommandRunConfiguration configuration, @NotNull ConfigurationContext context) {
+    BlazeCommandGenericRunConfigurationHandler handler =
+        configuration.getHandlerIfType(BlazeCommandGenericRunConfigurationHandler.class);
+    if (handler == null) {
+      return false;
+    }
+    if (!Objects.equals(handler.getCommand(), BlazeCommandName.TEST)) {
+      return false;
+    }
+
+    SelectedMethodInfo methodInfo = getSelectedMethodInfo(context);
+    if (methodInfo == null) {
+      return false;
+    }
+
+    List<String> flags = handler.getAllBlazeFlags();
+    return flags.contains(methodInfo.testFilterFlag);
+  }
+
+  private static SelectedMethodInfo getSelectedMethodInfo(ConfigurationContext context) {
+    final List<PsiMethod> selectedMethods = TestMethodSelectionUtil.getSelectedMethods(context);
+    if (selectedMethods == null) {
+      return null;
+    }
+    assert selectedMethods.size() > 0;
+    final PsiMethod firstMethod = selectedMethods.get(0);
+
+    final PsiClass containingClass = firstMethod.getContainingClass();
+    if (containingClass == null) {
+      return null;
+    }
+    for (PsiMethod method : selectedMethods) {
+      if (!containingClass.equals(method.getContainingClass())) {
+        return null;
+      }
+    }
+
+    final List<String> methodNames = new ArrayList<>();
+    for (PsiMethod method : selectedMethods) {
+      methodNames.add(method.getName());
+    }
+    // Sort so multiple configurations created with different selection orders are the same.
+    Collections.sort(methodNames);
+
+    final String qualifiedName = containingClass.getQualifiedName();
+    if (qualifiedName == null) {
+      return null;
+    }
+    final boolean isJUnit3Class = JUnitUtil.isJUnit3TestClass(containingClass);
+    final String testFilterFlag =
+        BlazeFlags.testFilterFlagForClassAndMethods(qualifiedName, methodNames, isJUnit3Class);
+
+    return new SelectedMethodInfo(firstMethod, containingClass, methodNames, testFilterFlag);
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/run/producers/JUnitConfigurationUtil.java b/java/src/com/google/idea/blaze/java/run/producers/JUnitConfigurationUtil.java
new file mode 100644
index 0000000..b994e36
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/run/producers/JUnitConfigurationUtil.java
@@ -0,0 +1,194 @@
+/*
+ * 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.run.producers;
+
+import com.intellij.execution.Location;
+import com.intellij.execution.actions.ConfigurationContext;
+import com.intellij.execution.junit.JUnitUtil;
+import com.intellij.execution.junit2.PsiMemberParameterizedLocation;
+import com.intellij.execution.junit2.info.MethodLocation;
+import com.intellij.execution.testframework.TestsUIUtil;
+import com.intellij.openapi.actionSystem.CommonDataKeys;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.LangDataKeys;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.JavaDirectoryService;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiClassOwner;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiManager;
+import com.intellij.psi.PsiMember;
+import com.intellij.psi.PsiMethod;
+import com.intellij.psi.PsiModifier;
+import com.intellij.psi.PsiPackage;
+import com.intellij.psi.search.PsiElementProcessor;
+import com.intellij.psi.util.ClassUtil;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+
+/**
+ * Cloned from PatternConfigurationProducer, stripped down to only contain
+ * isMultipleElementsSelected.
+ */
+public class JUnitConfigurationUtil {
+  protected static boolean isTestClass(PsiClass psiClass) {
+    return JUnitUtil.isTestClass(psiClass);
+  }
+
+  protected static boolean isTestMethod(boolean checkAbstract, PsiElement psiElement) {
+    return JUnitUtil.getTestMethod(psiElement, checkAbstract) != null;
+  }
+
+  public static boolean isMultipleElementsSelected(ConfigurationContext context) {
+    final DataContext dataContext = context.getDataContext();
+    if (TestsUIUtil.isMultipleSelectionImpossible(dataContext)) {
+      return false;
+    }
+    final LinkedHashSet<String> classes = new LinkedHashSet<String>();
+    final PsiElementProcessor.CollectElementsWithLimit<PsiElement> processor =
+        new PsiElementProcessor.CollectElementsWithLimit<PsiElement>(2);
+    final PsiElement[] locationElements = collectLocationElements(classes, dataContext);
+    if (locationElements != null) {
+      collectTestMembers(locationElements, false, false, processor);
+    } else {
+      collectContextElements(dataContext, false, false, classes, processor);
+    }
+    return processor.getCollection().size() > 1;
+  }
+
+  public static void collectTestMembers(
+      PsiElement[] psiElements,
+      boolean checkAbstract,
+      boolean checkIsTest,
+      PsiElementProcessor.CollectElements<PsiElement> collectingProcessor) {
+    for (PsiElement psiElement : psiElements) {
+      if (psiElement instanceof PsiClassOwner) {
+        final PsiClass[] classes = ((PsiClassOwner) psiElement).getClasses();
+        for (PsiClass aClass : classes) {
+          if ((!checkIsTest && aClass.hasModifierProperty(PsiModifier.PUBLIC)
+                  || checkIsTest && isTestClass(aClass))
+              && !collectingProcessor.execute(aClass)) {
+            return;
+          }
+        }
+      } else if (psiElement instanceof PsiClass) {
+        if ((!checkIsTest && ((PsiClass) psiElement).hasModifierProperty(PsiModifier.PUBLIC)
+                || checkIsTest && isTestClass((PsiClass) psiElement))
+            && !collectingProcessor.execute(psiElement)) {
+          return;
+        }
+      } else if (psiElement instanceof PsiMethod) {
+        if (checkIsTest
+            && isTestMethod(checkAbstract, psiElement)
+            && !collectingProcessor.execute(psiElement)) {
+          return;
+        }
+        if (!checkIsTest) {
+          final PsiClass containingClass = ((PsiMethod) psiElement).getContainingClass();
+          if (containingClass != null
+              && containingClass.hasModifierProperty(PsiModifier.PUBLIC)
+              && !collectingProcessor.execute(psiElement)) {
+            return;
+          }
+        }
+      } else if (psiElement instanceof PsiDirectory) {
+        final PsiPackage aPackage =
+            JavaDirectoryService.getInstance().getPackage((PsiDirectory) psiElement);
+        if (aPackage != null && !collectingProcessor.execute(aPackage)) {
+          return;
+        }
+      }
+    }
+  }
+
+  private static boolean collectContextElements(
+      DataContext dataContext,
+      boolean checkAbstract,
+      boolean checkIsTest,
+      LinkedHashSet<String> classes,
+      PsiElementProcessor.CollectElements<PsiElement> processor) {
+    PsiElement[] elements = LangDataKeys.PSI_ELEMENT_ARRAY.getData(dataContext);
+    if (elements != null) {
+      collectTestMembers(elements, checkAbstract, checkIsTest, processor);
+      for (PsiElement psiClass : processor.getCollection()) {
+        classes.add(getQName(psiClass));
+      }
+      return true;
+    } else {
+      final VirtualFile[] files = CommonDataKeys.VIRTUAL_FILE_ARRAY.getData(dataContext);
+      if (files != null) {
+        Project project = CommonDataKeys.PROJECT.getData(dataContext);
+        if (project != null) {
+          final PsiManager psiManager = PsiManager.getInstance(project);
+          for (VirtualFile file : files) {
+            final PsiFile psiFile = psiManager.findFile(file);
+            if (psiFile instanceof PsiClassOwner) {
+              collectTestMembers(
+                  ((PsiClassOwner) psiFile).getClasses(), checkAbstract, checkIsTest, processor);
+              for (PsiElement psiMember : processor.getCollection()) {
+                classes.add(((PsiClass) psiMember).getQualifiedName());
+              }
+            }
+          }
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  private static PsiElement[] collectLocationElements(
+      LinkedHashSet<String> classes, DataContext dataContext) {
+    final Location<?>[] locations = Location.DATA_KEYS.getData(dataContext);
+    if (locations != null) {
+      List<PsiElement> elements = new ArrayList<PsiElement>();
+      for (Location<?> location : locations) {
+        final PsiElement psiElement = location.getPsiElement();
+        classes.add(getQName(psiElement, location));
+        elements.add(psiElement);
+      }
+      return elements.toArray(new PsiElement[elements.size()]);
+    }
+    return null;
+  }
+
+  public static String getQName(PsiElement psiMember) {
+    return getQName(psiMember, null);
+  }
+
+  public static String getQName(PsiElement psiMember, Location location) {
+    if (psiMember instanceof PsiClass) {
+      return ClassUtil.getJVMClassName((PsiClass) psiMember);
+    } else if (psiMember instanceof PsiMember) {
+      final PsiClass containingClass =
+          location instanceof MethodLocation
+              ? ((MethodLocation) location).getContainingClass()
+              : location instanceof PsiMemberParameterizedLocation
+                  ? ((PsiMemberParameterizedLocation) location).getContainingClass()
+                  : ((PsiMember) psiMember).getContainingClass();
+      assert containingClass != null;
+      return ClassUtil.getJVMClassName(containingClass) + "," + ((PsiMember) psiMember).getName();
+    } else if (psiMember instanceof PsiPackage) {
+      return ((PsiPackage) psiMember).getQualifiedName();
+    }
+    assert false;
+    return null;
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/run/producers/ProducerUtils.java b/java/src/com/google/idea/blaze/java/run/producers/ProducerUtils.java
new file mode 100644
index 0000000..b1b40ab
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/run/producers/ProducerUtils.java
@@ -0,0 +1,66 @@
+/*
+ * 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.run.producers;
+
+import com.intellij.execution.Location;
+import com.intellij.execution.junit.JUnitUtil;
+import com.intellij.execution.junit2.PsiMemberParameterizedLocation;
+import com.intellij.execution.junit2.info.MethodLocation;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiMethod;
+import java.util.Iterator;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Copy of {@link org.jetbrains.plugins.gradle.execution.test.runner.TestRunnerUtils}.
+ *
+ * <p>
+ *
+ * <p>Do not modify.
+ */
+public class ProducerUtils {
+  @Nullable
+  public static Location<PsiMethod> getMethodLocation(@NotNull Location contextLocation) {
+    Location<PsiMethod> methodLocation = getTestMethod(contextLocation);
+    if (methodLocation == null) {
+      return null;
+    }
+
+    if (contextLocation instanceof PsiMemberParameterizedLocation) {
+      PsiClass containingClass =
+          ((PsiMemberParameterizedLocation) contextLocation).getContainingClass();
+      if (containingClass != null) {
+        methodLocation =
+            MethodLocation.elementInClass(methodLocation.getPsiElement(), containingClass);
+      }
+    }
+    return methodLocation;
+  }
+
+  @Nullable
+  public static Location<PsiMethod> getTestMethod(final Location<?> location) {
+    for (Iterator<Location<PsiMethod>> iterator = location.getAncestors(PsiMethod.class, false);
+        iterator.hasNext();
+        ) {
+      final Location<PsiMethod> methodLocation = iterator.next();
+      if (JUnitUtil.isTestMethod(methodLocation, false)) {
+        return methodLocation;
+      }
+    }
+    return null;
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/run/producers/TestMethodSelectionUtil.java b/java/src/com/google/idea/blaze/java/run/producers/TestMethodSelectionUtil.java
new file mode 100644
index 0000000..e19f9bf
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/run/producers/TestMethodSelectionUtil.java
@@ -0,0 +1,150 @@
+/*
+ * 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.run.producers;
+
+import com.intellij.execution.Location;
+import com.intellij.execution.PsiLocation;
+import com.intellij.execution.actions.ConfigurationContext;
+import com.intellij.execution.junit.JUnitUtil;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.LangDataKeys;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiMethod;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/** Helper functions for getting selected test methods. */
+public class TestMethodSelectionUtil {
+
+  /**
+   * Get all test methods directly or indirectly selected in the given context. This includes
+   * methods selected in the Structure panel, as well as methods the context location is inside of.
+   *
+   * @param context The context to get selected test methods from.
+   * @param allMustMatch If true, will return null if any selected elements are not test methods.
+   * @return A list of test methods (with at least one element), or null if:
+   *     <ul>
+   *     <li>There are no selected test methods
+   *     <li>{@code allMustMatch} is true, but elements other than test methods are selected
+   *     </ul>
+   *
+   * @see #getDirectlySelectedMethods(ConfigurationContext, boolean)
+   * @see #getIndirectlySelectedMethod(ConfigurationContext)
+   */
+  @Nullable
+  public static List<PsiMethod> getSelectedMethods(
+      @NotNull ConfigurationContext context, boolean allMustMatch) {
+    List<PsiMethod> directlySelectedMethods = getDirectlySelectedMethods(context, allMustMatch);
+    if (directlySelectedMethods != null && directlySelectedMethods.size() > 0) {
+      return directlySelectedMethods;
+    }
+    if (allMustMatch && JUnitConfigurationUtil.isMultipleElementsSelected(context)) {
+      return null;
+    }
+    PsiMethod indirectlySelectedMethod = getIndirectlySelectedMethod(context);
+    if (indirectlySelectedMethod != null) {
+      return Collections.singletonList(indirectlySelectedMethod);
+    }
+    return null;
+  }
+
+  /**
+   * Get all test methods directly or indirectly selected in the given context. This includes
+   * methods selected in the Structure panel, as well as methods the context location is inside of.
+   *
+   * @param context The context to get selected test methods from.
+   * @return A list of test methods (with at least one element), or null if:
+   *     <ul>
+   *     <li>There are no selected test methods
+   *     <li>Any elements other than test methods are selected
+   *     </ul>
+   *
+   * @see #getDirectlySelectedMethods(ConfigurationContext, boolean)
+   * @see #getIndirectlySelectedMethod(ConfigurationContext)
+   */
+  @Nullable
+  public static List<PsiMethod> getSelectedMethods(@NotNull ConfigurationContext context) {
+    return getSelectedMethods(context, true);
+  }
+
+  /**
+   * Get all test methods directly selected in the given context. This includes, for example,
+   * methods selected from the Structure panel. It does not include methods the context location is
+   * inside of. Note that methods may belong to different classes (possible if methods are selected
+   * from the Project panel with "Show Members" checked), and methods in abstract classes are not
+   * returned.
+   *
+   * @param context The context to get selected test methods from.
+   * @param allMustMatch If true, will return null if any selected elements are not test methods.
+   * @return A list of test methods (possibly empty), or null if:
+   *     <ul>
+   *     <li>There is no selection
+   *     <li>{@code allMustMatch} is true, but elements other than test methods are selected
+   *     </ul>
+   */
+  @Nullable
+  public static List<PsiMethod> getDirectlySelectedMethods(
+      @NotNull ConfigurationContext context, boolean allMustMatch) {
+    final DataContext dataContext = context.getDataContext();
+    PsiElement[] elements = LangDataKeys.PSI_ELEMENT_ARRAY.getData(dataContext);
+    if (elements == null) {
+      return null;
+    }
+    List<PsiMethod> methods = new ArrayList<>();
+    for (PsiElement element : elements) {
+      if (element instanceof PsiMethod) {
+        PsiMethod method = (PsiMethod) element;
+        if (JUnitUtil.isTestMethod(PsiLocation.fromPsiElement(method))) {
+          methods.add(method);
+        } else if (allMustMatch) {
+          return null;
+        }
+      } else if (allMustMatch) {
+        return null;
+      }
+    }
+    return methods;
+  }
+
+  /**
+   * Get a test method which is considered selected in the given context, belonging to a
+   * non-abstract class. The context location may be the method itself, or anywhere inside the
+   * method.
+   *
+   * @param context The context to search for a test method in.
+   * @return A test method, or null if none are found.
+   */
+  @Nullable
+  public static PsiMethod getIndirectlySelectedMethod(@NotNull ConfigurationContext context) {
+    final Location<?> contextLocation = context.getLocation();
+    if (contextLocation == null) {
+      return null;
+    }
+    Iterator<Location<PsiMethod>> locationIterator =
+        contextLocation.getAncestors(PsiMethod.class, false);
+    while (locationIterator.hasNext()) {
+      Location<PsiMethod> methodLocation = locationIterator.next();
+      if (JUnitUtil.isTestMethod(methodLocation)) {
+        return methodLocation.getPsiElement();
+      }
+    }
+    return null;
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/run/producers/TestSizeAnnotationMap.java b/java/src/com/google/idea/blaze/java/run/producers/TestSizeAnnotationMap.java
new file mode 100644
index 0000000..13062b3
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/run/producers/TestSizeAnnotationMap.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.java.run.producers;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.idea.blaze.base.ideinfo.TestIdeInfo;
+import com.intellij.psi.PsiAnnotation;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiMethod;
+import com.intellij.psi.PsiModifierList;
+import javax.annotation.Nullable;
+
+/** Maps method and class annotations to our test size enumeration. */
+public class TestSizeAnnotationMap {
+  private static ImmutableMap<String, TestIdeInfo.TestSize> ANNOTATION_TO_TEST_SIZE =
+      ImmutableMap.<String, TestIdeInfo.TestSize>builder()
+          .put("com.google.testing.testsize.SmallTest", TestIdeInfo.TestSize.SMALL)
+          .put("com.google.testing.testsize.MediumTest", TestIdeInfo.TestSize.MEDIUM)
+          .put("com.google.testing.testsize.LargeTest", TestIdeInfo.TestSize.LARGE)
+          .put("com.google.testing.testsize.EnormousTest", TestIdeInfo.TestSize.ENORMOUS)
+          .build();
+
+  @Nullable
+  public static TestIdeInfo.TestSize getTestSize(PsiMethod psiMethod) {
+    PsiAnnotation[] annotations = psiMethod.getModifierList().getAnnotations();
+    TestIdeInfo.TestSize testSize = getTestSize(annotations);
+    if (testSize != null) {
+      return testSize;
+    }
+    return getTestSize(psiMethod.getContainingClass());
+  }
+
+  @Nullable
+  public static TestIdeInfo.TestSize getTestSize(PsiClass psiClass) {
+    PsiModifierList psiModifierList = psiClass.getModifierList();
+    if (psiModifierList == null) {
+      return null;
+    }
+    PsiAnnotation[] annotations = psiModifierList.getAnnotations();
+    TestIdeInfo.TestSize testSize = getTestSize(annotations);
+    if (testSize == null) {
+      return null;
+    }
+    return testSize;
+  }
+
+  @Nullable
+  private static TestIdeInfo.TestSize getTestSize(PsiAnnotation[] annotations) {
+    for (PsiAnnotation annotation : annotations) {
+      String qualifiedName = annotation.getQualifiedName();
+      TestIdeInfo.TestSize testSize = ANNOTATION_TO_TEST_SIZE.get(qualifiedName);
+      if (testSize != null) {
+        return testSize;
+      }
+    }
+    return null;
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/settings/BlazeJavaUserSettings.java b/java/src/com/google/idea/blaze/java/settings/BlazeJavaUserSettings.java
new file mode 100644
index 0000000..d9b9d57
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/settings/BlazeJavaUserSettings.java
@@ -0,0 +1,104 @@
+/*
+ * 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.settings;
+
+import com.google.idea.blaze.base.bazel.BuildSystemProvider;
+import com.google.idea.blaze.base.settings.Blaze;
+import com.google.idea.blaze.base.settings.BlazeUserSettings;
+import com.intellij.openapi.components.PersistentStateComponent;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.components.State;
+import com.intellij.openapi.components.Storage;
+import com.intellij.util.xmlb.XmlSerializerUtil;
+import org.jetbrains.annotations.NotNull;
+
+/** Java-specific user settings. */
+@State(name = "BlazeJavaUserSettings", storages = @Storage("blaze.java.user.settings.xml"))
+public class BlazeJavaUserSettings implements PersistentStateComponent<BlazeJavaUserSettings> {
+  private boolean useJarCache = getDefaultJarCacheValue();
+  private boolean attachSourcesByDefault = false;
+  private boolean attachSourcesOnDemand = false;
+  private boolean migrated;
+
+  public static BlazeJavaUserSettings getInstance() {
+    BlazeJavaUserSettings settings = ServiceManager.getService(BlazeJavaUserSettings.class);
+    settings.migrate();
+    return settings;
+  }
+
+  private static boolean getDefaultJarCacheValue() {
+    return BuildSystemProvider.defaultBuildSystem().buildSystem() == Blaze.BuildSystem.Blaze;
+  }
+
+  @Override
+  @NotNull
+  public BlazeJavaUserSettings getState() {
+    return this;
+  }
+
+  @Override
+  public void loadState(BlazeJavaUserSettings state) {
+    XmlSerializerUtil.copyBean(state, this);
+    migrate();
+  }
+
+  /**
+   * Added in 1.8, can be removed ~2.2. When this is removed, java settings can no longer be
+   * migrated. (This is non-catastrophic though -- the settings will just reset)
+   */
+  private void migrate() {
+    if (!migrated) {
+      BlazeUserSettings userSettings = BlazeUserSettings.getInstance();
+      this.attachSourcesByDefault = userSettings.getAttachSourcesByDefault();
+      this.attachSourcesOnDemand = userSettings.getAttachSourcesOnDemand();
+      this.migrated = true;
+    }
+  }
+
+  public boolean getUseJarCache() {
+    return useJarCache;
+  }
+
+  public void setUseJarCache(boolean useJarCache) {
+    this.useJarCache = useJarCache;
+  }
+
+  public boolean getAttachSourcesByDefault() {
+    return attachSourcesByDefault;
+  }
+
+  public void setAttachSourcesByDefault(boolean attachSourcesByDefault) {
+    this.attachSourcesByDefault = attachSourcesByDefault;
+  }
+
+  public boolean getAttachSourcesOnDemand() {
+    return attachSourcesOnDemand;
+  }
+
+  public void setAttachSourcesOnDemand(boolean attachSourcesOnDemand) {
+    this.attachSourcesOnDemand = attachSourcesOnDemand;
+  }
+
+  @SuppressWarnings("unused") // Used by bean serialization
+  public boolean getMigrated() {
+    return migrated;
+  }
+
+  @SuppressWarnings("unused") // Used by bean serialization
+  public void setMigrated(boolean migrated) {
+    this.migrated = migrated;
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/settings/BlazeJavaUserSettingsContributor.java b/java/src/com/google/idea/blaze/java/settings/BlazeJavaUserSettingsContributor.java
new file mode 100644
index 0000000..bf5bad9
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/settings/BlazeJavaUserSettingsContributor.java
@@ -0,0 +1,136 @@
+/*
+ * 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.settings;
+
+import com.google.common.base.Objects;
+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;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+
+/** Contributes java-specific settings. */
+public class BlazeJavaUserSettingsContributor implements BlazeUserSettingsContributor {
+  private JCheckBox useJarCache;
+  private JCheckBox attachSourcesByDefault;
+  private JCheckBox attachSourcesOnDemand;
+  private final ImmutableList<JComponent> components;
+
+  BlazeJavaUserSettingsContributor() {
+    useJarCache = new JCheckBox();
+    useJarCache.setText(
+        String.format(
+            "Use a local jar cache. More robust, but we can miss %s changes made outside the IDE.",
+            Blaze.defaultBuildSystemName()));
+
+    attachSourcesByDefault = new JCheckBox();
+    attachSourcesByDefault.setSelected(false);
+    attachSourcesByDefault.setText(
+        "Automatically attach sources on project sync (WARNING: increases index time by 100%+)");
+
+    attachSourcesByDefault.addActionListener(
+        (event) -> {
+          BlazeJavaUserSettings settings = BlazeJavaUserSettings.getInstance();
+          if (attachSourcesByDefault.isSelected() && !settings.getAttachSourcesByDefault()) {
+            int result =
+                Messages.showOkCancelDialog(
+                    "You are turning on source jars by default. "
+                        + "This setting increases indexing time by "
+                        + ">100%, can cost ~1GB RAM, and will increase "
+                        + "project reopen time significantly. "
+                        + "Are you sure you want to proceed?",
+                    "Turn On Sources By Default?", null);
+            if (result != Messages.OK) {
+              attachSourcesByDefault.setSelected(false);
+            }
+          }
+        });
+
+    attachSourcesOnDemand = new JCheckBox();
+    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();
+  }
+
+  @Override
+  public void apply() {
+    BlazeJavaUserSettings settings = BlazeJavaUserSettings.getInstance();
+    settings.setUseJarCache(useJarCache.isSelected());
+    settings.setAttachSourcesByDefault(attachSourcesByDefault.isSelected());
+    settings.setAttachSourcesOnDemand(attachSourcesOnDemand.isSelected());
+  }
+
+  @Override
+  public void reset() {
+    BlazeJavaUserSettings settings = BlazeJavaUserSettings.getInstance();
+    useJarCache.setSelected(settings.getUseJarCache());
+    attachSourcesByDefault.setSelected(settings.getAttachSourcesByDefault());
+    attachSourcesOnDemand.setSelected(settings.getAttachSourcesOnDemand());
+  }
+
+  @Override
+  public boolean isModified() {
+    BlazeJavaUserSettings settings = BlazeJavaUserSettings.getInstance();
+    return !Objects.equal(useJarCache.isSelected(), settings.getUseJarCache())
+        || !Objects.equal(attachSourcesByDefault.isSelected(), settings.getAttachSourcesByDefault())
+        || !Objects.equal(attachSourcesOnDemand.isSelected(), settings.getAttachSourcesOnDemand());
+  }
+
+  @Override
+  public int getRowCount() {
+    return components.size();
+  }
+
+  @Override
+  public int addComponents(JPanel panel, int rowi) {
+    for (JComponent contributedComponent : components) {
+      panel.add(
+          contributedComponent,
+          new GridConstraints(
+              rowi++,
+              0,
+              1,
+              2,
+              GridConstraints.ANCHOR_NORTHWEST,
+              GridConstraints.FILL_NONE,
+              GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
+              GridConstraints.SIZEPOLICY_FIXED,
+              null,
+              null,
+              null,
+              0,
+              false));
+    }
+    return rowi;
+  }
+
+  static class BlazeJavaUserSettingsProvider implements BlazeUserSettingsContributor.Provider {
+    @Override
+    public BlazeUserSettingsContributor getContributor() {
+      return new BlazeJavaUserSettingsContributor();
+    }
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/sync/BlazeJavaSyncAugmenter.java b/java/src/com/google/idea/blaze/java/sync/BlazeJavaSyncAugmenter.java
new file mode 100644
index 0000000..7bd32d9
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/sync/BlazeJavaSyncAugmenter.java
@@ -0,0 +1,91 @@
+/*
+ * 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.RuleIdeInfo;
+import com.google.idea.blaze.base.model.BlazeProjectData;
+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.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();
+    }
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/sync/BlazeJavaSyncPlugin.java b/java/src/com/google/idea/blaze/java/sync/BlazeJavaSyncPlugin.java
new file mode 100644
index 0000000..9d6eb17
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/sync/BlazeJavaSyncPlugin.java
@@ -0,0 +1,311 @@
+/*
+ * 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.ImmutableSet;
+import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
+import com.google.idea.blaze.base.ideinfo.LibraryArtifact;
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.model.RuleMap;
+import com.google.idea.blaze.base.model.SyncState;
+import com.google.idea.blaze.base.model.primitives.LanguageClass;
+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.ProjectViewSet;
+import com.google.idea.blaze.base.projectview.section.Glob;
+import com.google.idea.blaze.base.projectview.section.SectionParser;
+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;
+import com.google.idea.blaze.base.scope.output.PerformanceWarning;
+import com.google.idea.blaze.base.scope.scopes.TimingScope;
+import com.google.idea.blaze.base.sync.BlazeSyncPlugin;
+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;
+import com.google.idea.blaze.base.sync.workspace.WorkingSet;
+import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver;
+import com.google.idea.blaze.java.projectview.ExcludeLibrarySection;
+import com.google.idea.blaze.java.projectview.ExcludedLibrarySection;
+import com.google.idea.blaze.java.projectview.JavaLanguageLevelSection;
+import com.google.idea.blaze.java.sync.importer.BlazeJavaWorkspaceImporter;
+import com.google.idea.blaze.java.sync.jdeps.JdepsFileReader;
+import com.google.idea.blaze.java.sync.jdeps.JdepsMap;
+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.diagnostic.Logger;
+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;
+
+/** Sync support for Java. */
+public class BlazeJavaSyncPlugin extends BlazeSyncPlugin.Adapter {
+  private static final Logger LOG = Logger.getInstance(BlazeJavaSyncPlugin.class);
+  private final JdepsFileReader jdepsFileReader = new JdepsFileReader();
+
+  @Nullable
+  @Override
+  public WorkspaceType getDefaultWorkspaceType() {
+    return WorkspaceType.JAVA;
+  }
+
+  @Override
+  public Set<LanguageClass> getSupportedLanguagesInWorkspace(WorkspaceType workspaceType) {
+    if (workspaceType == WorkspaceType.JAVA) {
+      return ImmutableSet.of(LanguageClass.JAVA);
+    }
+    return ImmutableSet.of();
+  }
+
+  @Nullable
+  @Override
+  public ModuleType getWorkspaceModuleType(WorkspaceType workspaceType) {
+    if (workspaceType == WorkspaceType.JAVA) {
+      return StdModuleTypes.JAVA;
+    }
+    return null;
+  }
+
+  @Override
+  public void updateSyncState(
+      Project project,
+      BlazeContext context,
+      WorkspaceRoot workspaceRoot,
+      ProjectViewSet projectViewSet,
+      WorkspaceLanguageSettings workspaceLanguageSettings,
+      BlazeRoots blazeRoots,
+      @Nullable WorkingSet workingSet,
+      WorkspacePathResolver workspacePathResolver,
+      RuleMap ruleMap,
+      SyncState.Builder syncStateBuilder,
+      @Nullable SyncState previousSyncState) {
+    JavaWorkingSet javaWorkingSet = null;
+    if (workingSet != null) {
+      javaWorkingSet = new JavaWorkingSet(workspaceRoot, workingSet);
+    }
+
+    JdepsMap jdepsMap =
+        jdepsFileReader.loadJdepsFiles(
+            project, context, ruleMap, syncStateBuilder, previousSyncState);
+    if (context.isCancelled()) {
+      return;
+    }
+
+    BlazeJavaWorkspaceImporter blazeJavaWorkspaceImporter =
+        new BlazeJavaWorkspaceImporter(
+            project,
+            context,
+            workspaceRoot,
+            projectViewSet,
+            workspaceLanguageSettings,
+            ruleMap,
+            jdepsMap,
+            javaWorkingSet,
+            new ArtifactLocationDecoder(blazeRoots, workspacePathResolver));
+    BlazeJavaImportResult importResult =
+        Scope.push(
+            context,
+            (childContext) -> {
+              childContext.push(new TimingScope("JavaWorkspaceImporter"));
+              return blazeJavaWorkspaceImporter.importWorkspace(childContext);
+            });
+    Glob.GlobSet excludedLibraries =
+        new Glob.GlobSet(
+            ImmutableList.<Glob>builder()
+                .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);
+  }
+
+  @Override
+  public void updateSdk(
+      Project project,
+      BlazeContext context,
+      ProjectViewSet projectViewSet,
+      BlazeProjectData blazeProjectData) {
+    if (!blazeProjectData.workspaceLanguageSettings.isWorkspaceType(WorkspaceType.JAVA)) {
+      return;
+    }
+    updateJdk(project, context, projectViewSet, blazeProjectData);
+  }
+
+  @Override
+  public void updateContentEntries(
+      Project project,
+      BlazeContext context,
+      WorkspaceRoot workspaceRoot,
+      ProjectViewSet projectViewSet,
+      BlazeProjectData blazeProjectData,
+      Collection<ContentEntry> contentEntries) {
+    if (!blazeProjectData.workspaceLanguageSettings.isLanguageActive(LanguageClass.JAVA)) {
+      return;
+    }
+    BlazeJavaSyncData syncData = blazeProjectData.syncState.get(BlazeJavaSyncData.class);
+    if (syncData == null) {
+      return;
+    }
+
+    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,
+      ProjectViewSet projectViewSet,
+      BlazeProjectData blazeProjectData) {
+
+    LanguageLevel javaLanguageLevel =
+        JavaLanguageLevelHelper.getJavaLanguageLevel(
+            projectViewSet, blazeProjectData, LanguageLevel.JDK_1_7);
+
+    final Sdk sdk = Jdks.chooseOrCreateJavaSdk(javaLanguageLevel);
+    if (sdk == null) {
+      String msg =
+          String.format(
+              "Unable to find a JDK %1$s installed.\n", javaLanguageLevel.getPresentableText());
+      msg +=
+          "After configuring a suitable JDK in the \"Project Structure\" dialog, "
+              + "sync the project again.";
+      IssueOutput.error(msg).submit(context);
+      return;
+    }
+    setProjectSdkAndLanguageLevel(project, sdk, javaLanguageLevel);
+  }
+
+  private static void setProjectSdkAndLanguageLevel(
+      final Project project, final Sdk sdk, final LanguageLevel javaLanguageLevel) {
+    UIUtil.invokeAndWaitIfNeeded(
+        (Runnable)
+            () ->
+                ApplicationManager.getApplication()
+                    .runWriteAction(
+                        () -> {
+                          ProjectRootManagerEx rootManager =
+                              ProjectRootManagerEx.getInstanceEx(project);
+                          rootManager.setProjectSdk(sdk);
+                          LanguageLevelProjectExtension ext =
+                              LanguageLevelProjectExtension.getInstance(project);
+                          ext.setLanguageLevel(javaLanguageLevel);
+                        }));
+  }
+
+  @Override
+  public boolean validate(
+      Project project, BlazeContext context, BlazeProjectData blazeProjectData) {
+    BlazeJavaSyncData syncData = blazeProjectData.syncState.get(BlazeJavaSyncData.class);
+    if (syncData == null) {
+      return true;
+    }
+    warnAboutDeployJars(context, syncData);
+    return true;
+  }
+
+  @Override
+  public Collection<SectionParser> getSections() {
+    return ImmutableList.of(
+        ExcludedLibrarySection.PARSER,
+        ExcludeLibrarySection.PARSER,
+        JavaLanguageLevelSection.PARSER);
+  }
+
+  @Override
+  public boolean requiresResolveIdeArtifacts() {
+    return true;
+  }
+
+  /**
+   * 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
+   * project.
+   */
+  private static void warnAboutDeployJars(BlazeContext context, BlazeJavaSyncData syncData) {
+    for (BlazeLibrary library : syncData.importResult.libraries.values()) {
+      if (!(library instanceof BlazeJarLibrary)) {
+        continue;
+      }
+      BlazeJarLibrary jarLibrary = (BlazeJarLibrary) library;
+      LibraryArtifact libraryArtifact = jarLibrary.libraryArtifact;
+      ArtifactLocation artifactLocation = libraryArtifact.jarForIntellijLibrary();
+      if (artifactLocation.getRelativePath().endsWith("deploy.jar")
+          || artifactLocation.getRelativePath().endsWith("deploy-ijar.jar")
+          || artifactLocation.getRelativePath().endsWith("deploy-hjar.jar")) {
+        context.output(
+            new PerformanceWarning(
+                "Performance warning: You have added a deploy jar as a library. "
+                    + "This can lead to poor indexing performance, and the debugger may "
+                    + "become confused and step into the deploy jar instead of your code. "
+                    + "Consider redoing the rule to not use deploy jars, exclude the target "
+                    + "from your .blazeproject, or exclude the library.\n"
+                    + "Library path: "
+                    + artifactLocation.getRelativePath()));
+      }
+    }
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/sync/BlazeLibraryCollector.java b/java/src/com/google/idea/blaze/java/sync/BlazeLibraryCollector.java
new file mode 100644
index 0000000..99cd0b5
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/sync/BlazeLibraryCollector.java
@@ -0,0 +1,62 @@
+/*
+ * 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
new file mode 100644
index 0000000..9357346
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/sync/DuplicateSourceDetector.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.java.sync;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Lists;
+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.model.primitives.Label;
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.scope.output.PerformanceWarning;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/** Detects and reports duplicate sources */
+public class DuplicateSourceDetector {
+  Multimap<ArtifactLocation, Label> artifacts = ArrayListMultimap.create();
+
+  public void add(Label label, ArtifactLocation artifactLocation) {
+    artifacts.put(artifactLocation, label);
+  }
+
+  static class Duplicate {
+    final ArtifactLocation artifactLocation;
+    final Collection<Label> labels;
+
+    public Duplicate(ArtifactLocation artifactLocation, Collection<Label> labels) {
+      this.artifactLocation = artifactLocation;
+      this.labels = labels;
+    }
+  }
+
+  public void reportDuplicates(BlazeContext context) {
+    List<Duplicate> duplicates = Lists.newArrayList();
+    for (ArtifactLocation key : artifacts.keySet()) {
+      Collection<Label> 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<Label> labelSet = Sets.newHashSet(labels);
+        if (labelSet.size() > 1) {
+          duplicates.add(new Duplicate(key, labelSet));
+        }
+      }
+    }
+
+    if (duplicates.isEmpty()) {
+      return;
+    }
+
+    Collections.sort(
+        duplicates,
+        (lhs, rhs) ->
+            lhs.artifactLocation
+                .getRelativePath()
+                .compareTo(rhs.artifactLocation.getRelativePath()));
+
+    context.output(new PerformanceWarning("Duplicate sources detected:"));
+    for (Duplicate duplicate : duplicates) {
+      ArtifactLocation artifactLocation = duplicate.artifactLocation;
+      context.output(new PerformanceWarning("  Source: " + artifactLocation.getRelativePath()));
+      context.output(new PerformanceWarning("  Consumed by rules:"));
+      for (Label label : duplicate.labels) {
+        context.output(new PerformanceWarning("    " + label));
+      }
+      context.output(new PerformanceWarning("")); // Newline
+    }
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/sync/JavaLanguageLevelHelper.java b/java/src/com/google/idea/blaze/java/sync/JavaLanguageLevelHelper.java
new file mode 100644
index 0000000..c0c3dd3
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/sync/JavaLanguageLevelHelper.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.java.sync;
+
+import com.google.common.base.Strings;
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.projectview.ProjectViewSet;
+import com.google.idea.blaze.java.projectview.JavaLanguageLevelSection;
+import com.google.idea.blaze.java.sync.model.BlazeJavaSyncData;
+import com.intellij.pom.java.LanguageLevel;
+
+/** Called by sync plugins to determine the appropriate java language level. */
+public class JavaLanguageLevelHelper {
+
+  public static LanguageLevel getJavaLanguageLevel(
+      ProjectViewSet projectViewSet,
+      BlazeProjectData blazeProjectData,
+      LanguageLevel defaultLanguageLevel) {
+
+    defaultLanguageLevel = getLanguageLevelFromToolchain(blazeProjectData, defaultLanguageLevel);
+    return JavaLanguageLevelSection.getLanguageLevel(projectViewSet, defaultLanguageLevel);
+  }
+
+  private static LanguageLevel getLanguageLevelFromToolchain(
+      BlazeProjectData blazeProjectData, LanguageLevel defaultLanguageLevel) {
+    BlazeJavaSyncData blazeJavaSyncData = blazeProjectData.syncState.get(BlazeJavaSyncData.class);
+    if (blazeJavaSyncData != null) {
+      String sourceVersion = blazeJavaSyncData.importResult.sourceVersion;
+      if (!Strings.isNullOrEmpty(sourceVersion)) {
+        switch (sourceVersion) {
+          case "6":
+            return LanguageLevel.JDK_1_6;
+          case "7":
+            return LanguageLevel.JDK_1_7;
+          case "8":
+            return LanguageLevel.JDK_1_8;
+          case "9":
+            return LanguageLevel.JDK_1_9;
+        }
+      }
+    }
+    return defaultLanguageLevel;
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/sync/JavaPrefetchFileSource.java b/java/src/com/google/idea/blaze/java/sync/JavaPrefetchFileSource.java
new file mode 100644
index 0000000..b77741a
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/sync/JavaPrefetchFileSource.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.java.sync;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.prefetch.PrefetchFileSource;
+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;
+import java.util.Set;
+
+/** Adds the jars to prefetch. */
+public class JavaPrefetchFileSource implements PrefetchFileSource {
+  @Override
+  public void addFilesToPrefetch(
+      Project project, BlazeProjectData blazeProjectData, Collection<File> files) {
+    BlazeJavaSyncData syncData = blazeProjectData.syncState.get(BlazeJavaSyncData.class);
+    if (syncData == null) {
+      return;
+    }
+    // If we have a local jar cache we don't need to prefetch anything
+    if (JarCache.getInstance(project).isEnabled()) {
+      return;
+    }
+    boolean attachSourcesByDefault =
+        BlazeJavaUserSettings.getInstance().getAttachSourcesByDefault();
+    SourceJarManager sourceJarManager = SourceJarManager.getInstance(project);
+    Collection<BlazeLibrary> libraries = BlazeLibraryCollector.getLibraries(blazeProjectData);
+    for (BlazeLibrary library : libraries) {
+      if (!(library instanceof BlazeJarLibrary)) {
+        continue;
+      }
+      BlazeJarLibrary jarLibrary = (BlazeJarLibrary) library;
+      files.add(jarLibrary.libraryArtifact.jarForIntellijLibrary().getFile());
+
+      boolean attachSourceJar =
+          attachSourcesByDefault || sourceJarManager.hasSourceJarAttached(jarLibrary.key);
+      if (attachSourceJar && jarLibrary.libraryArtifact.sourceJar != null) {
+        files.add(jarLibrary.libraryArtifact.sourceJar.getFile());
+      }
+    }
+  }
+
+  @Override
+  public Set<String> prefetchSrcFileExtensions() {
+    return ImmutableSet.of("java");
+  }
+}
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
new file mode 100644
index 0000000..61d7155
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/sync/importer/BlazeJavaWorkspaceImporter.java
@@ -0,0 +1,472 @@
+/*
+ * 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.importer;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+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.LibraryArtifact;
+import com.google.idea.blaze.base.ideinfo.ProtoLibraryLegacyInfo;
+import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+import com.google.idea.blaze.base.model.RuleMap;
+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.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.PrintOutput;
+import com.google.idea.blaze.base.settings.Blaze;
+import com.google.idea.blaze.base.sync.projectview.ImportRoots;
+import com.google.idea.blaze.base.sync.projectview.ProjectViewRuleImportFilter;
+import com.google.idea.blaze.base.sync.projectview.SourceTestConfig;
+import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
+import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
+import com.google.idea.blaze.java.sync.BlazeJavaSyncAugmenter;
+import com.google.idea.blaze.java.sync.DuplicateSourceDetector;
+import com.google.idea.blaze.java.sync.jdeps.JdepsMap;
+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.google.idea.common.experiments.BoolExperiment;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
+
+/** Builds a BlazeWorkspace. */
+public final class BlazeJavaWorkspaceImporter {
+  private static final Logger LOG = Logger.getInstance(BlazeJavaWorkspaceImporter.class);
+
+  private static final BoolExperiment NO_EMPTY_SOURCE_RULES =
+      new BoolExperiment("no.empty.source.rules", true);
+
+  private final Project project;
+  private final BlazeContext context;
+  private final WorkspaceRoot workspaceRoot;
+  private final ImportRoots importRoots;
+  private final RuleMap ruleMap;
+  private final SourceTestConfig sourceTestConfig;
+  private final JdepsMap jdepsMap;
+  @Nullable private final JavaWorkingSet workingSet;
+  private final ArtifactLocationDecoder artifactLocationDecoder;
+  private final ProjectViewRuleImportFilter importFilter;
+  private final DuplicateSourceDetector duplicateSourceDetector = new DuplicateSourceDetector();
+  private final Collection<BlazeJavaSyncAugmenter> augmenters;
+
+  public BlazeJavaWorkspaceImporter(
+      Project project,
+      BlazeContext context,
+      WorkspaceRoot workspaceRoot,
+      ProjectViewSet projectViewSet,
+      WorkspaceLanguageSettings workspaceLanguageSettings,
+      RuleMap ruleMap,
+      JdepsMap jdepsMap,
+      @Nullable JavaWorkingSet workingSet,
+      ArtifactLocationDecoder artifactLocationDecoder) {
+    this.project = project;
+    this.context = context;
+    this.workspaceRoot = workspaceRoot;
+    this.importRoots =
+        ImportRoots.builder(workspaceRoot, Blaze.getBuildSystem(project))
+            .add(projectViewSet)
+            .build();
+    this.ruleMap = ruleMap;
+    this.jdepsMap = jdepsMap;
+    this.workingSet = workingSet;
+    this.artifactLocationDecoder = artifactLocationDecoder;
+    this.importFilter = new ProjectViewRuleImportFilter(project, workspaceRoot, projectViewSet);
+    this.sourceTestConfig = new SourceTestConfig(projectViewSet);
+    this.augmenters = BlazeJavaSyncAugmenter.getActiveSyncAgumenters(workspaceLanguageSettings);
+  }
+
+  public BlazeJavaImportResult importWorkspace(BlazeContext context) {
+    List<RuleIdeInfo> includedRules =
+        ruleMap
+            .rules()
+            .stream()
+            .filter(rule -> !importFilter.excludeTarget(rule))
+            .collect(Collectors.toList());
+
+    List<RuleIdeInfo> javaRules =
+        includedRules
+            .stream()
+            .filter(rule -> rule.javaRuleIdeInfo != null)
+            .collect(Collectors.toList());
+
+    Map<Label, Collection<ArtifactLocation>> ruleToJavaSources = Maps.newHashMap();
+    for (RuleIdeInfo rule : javaRules) {
+      List<ArtifactLocation> javaSources =
+          rule.sources
+              .stream()
+              .filter(source -> source.getRelativePath().endsWith(".java"))
+              .collect(Collectors.toList());
+      ruleToJavaSources.put(rule.label, javaSources);
+    }
+
+    boolean noEmptySourceRules = NO_EMPTY_SOURCE_RULES.getValue();
+    List<RuleIdeInfo> sourceRules = Lists.newArrayList();
+    List<RuleIdeInfo> libraryRules = Lists.newArrayList();
+    for (RuleIdeInfo rule : javaRules) {
+      boolean importAsSource =
+          importFilter.isSourceRule(rule)
+              && canImportAsSource(rule)
+              && (noEmptySourceRules
+                  ? anyNonGeneratedSources(ruleToJavaSources.get(rule.label))
+                  : !allSourcesGenerated(ruleToJavaSources.get(rule.label)));
+
+      if (importAsSource) {
+        sourceRules.add(rule);
+      } else {
+        libraryRules.add(rule);
+      }
+    }
+
+    List<RuleIdeInfo> protoLibraries =
+        includedRules
+            .stream()
+            .filter(rule -> rule.kind == Kind.PROTO_LIBRARY)
+            .collect(Collectors.toList());
+
+    WorkspaceBuilder workspaceBuilder = new WorkspaceBuilder();
+    for (RuleIdeInfo rule : sourceRules) {
+      addRuleAsSource(workspaceBuilder, rule, ruleToJavaSources.get(rule.label));
+    }
+
+    SourceDirectoryCalculator sourceDirectoryCalculator = new SourceDirectoryCalculator();
+    ImmutableList<BlazeContentEntry> contentEntries =
+        sourceDirectoryCalculator.calculateContentEntries(
+            project,
+            context,
+            workspaceRoot,
+            sourceTestConfig,
+            artifactLocationDecoder,
+            importRoots.rootDirectories(),
+            workspaceBuilder.sourceArtifacts,
+            workspaceBuilder.javaPackageManifests);
+
+    int totalContentEntryCount = 0;
+    for (BlazeContentEntry contentEntry : contentEntries) {
+      totalContentEntryCount += contentEntry.sources.size();
+    }
+    context.output(PrintOutput.log("Java content entry count: " + totalContentEntryCount));
+
+    ImmutableMap<LibraryKey, BlazeJarLibrary> libraries =
+        buildLibraries(workspaceBuilder, ruleMap, libraryRules, protoLibraries);
+
+    duplicateSourceDetector.reportDuplicates(context);
+
+    String sourceVersion = findSourceVersion(ruleMap);
+
+    return new BlazeJavaImportResult(
+        contentEntries,
+        libraries,
+        ImmutableList.copyOf(
+            workspaceBuilder.buildOutputJars.stream().sorted().collect(Collectors.toList())),
+        ImmutableSet.copyOf(workspaceBuilder.addedSourceFiles),
+        sourceVersion);
+  }
+
+  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 anyNonGeneratedSources(Collection<ArtifactLocation> sources) {
+    return sources.stream().anyMatch(ArtifactLocation::isSource);
+  }
+
+  private ImmutableMap<LibraryKey, BlazeJarLibrary> buildLibraries(
+      WorkspaceBuilder workspaceBuilder,
+      RuleMap ruleMap,
+      List<RuleIdeInfo> libraryRules,
+      List<RuleIdeInfo> protoLibraries) {
+    // Build library maps
+    Multimap<Label, BlazeJarLibrary> labelToLibrary = ArrayListMultimap.create();
+    Map<String, BlazeJarLibrary> jdepsPathToLibrary = Maps.newHashMap();
+
+    // Add any output jars from source rules
+    for (Label label : workspaceBuilder.outputJarsFromSourceRules.keySet()) {
+      Collection<BlazeJarLibrary> jars = workspaceBuilder.outputJarsFromSourceRules.get(label);
+      labelToLibrary.putAll(label, jars);
+      for (BlazeJarLibrary library : jars) {
+        addLibraryToJdeps(jdepsPathToLibrary, library);
+      }
+    }
+
+    for (RuleIdeInfo rule : libraryRules) {
+      JavaRuleIdeInfo javaRuleIdeInfo = rule.javaRuleIdeInfo;
+      if (javaRuleIdeInfo == null) {
+        continue;
+      }
+      List<LibraryArtifact> allJars = Lists.newArrayList();
+      allJars.addAll(javaRuleIdeInfo.jars);
+      Collection<BlazeJarLibrary> libraries =
+          allJars
+              .stream()
+              .map(library -> new BlazeJarLibrary(library, rule.label))
+              .collect(Collectors.toList());
+
+      labelToLibrary.putAll(rule.label, libraries);
+      for (BlazeJarLibrary library : libraries) {
+        addLibraryToJdeps(jdepsPathToLibrary, library);
+      }
+    }
+
+    // proto legacy jdeps support
+    for (RuleIdeInfo rule : protoLibraries) {
+      ProtoLibraryLegacyInfo protoLibraryLegacyInfo = rule.protoLibraryLegacyInfo;
+      if (protoLibraryLegacyInfo == null) {
+        continue;
+      }
+      for (LibraryArtifact libraryArtifact :
+          Iterables.concat(
+              protoLibraryLegacyInfo.jarsV1,
+              protoLibraryLegacyInfo.jarsMutable,
+              protoLibraryLegacyInfo.jarsImmutable)) {
+        addLibraryToJdeps(jdepsPathToLibrary, new BlazeJarLibrary(libraryArtifact, rule.label));
+      }
+    }
+
+    Map<LibraryKey, BlazeJarLibrary> result = Maps.newHashMap();
+
+    // Collect jars from jdep references
+    for (String jdepsPath : workspaceBuilder.jdeps) {
+      BlazeJarLibrary library = jdepsPathToLibrary.get(jdepsPath);
+      if (library != null) {
+        result.put(library.key, library);
+      }
+    }
+
+    // Collect jars referenced by direct deps from your working set
+    for (Label deps : workspaceBuilder.directDeps) {
+      for (BlazeJarLibrary library : labelToLibrary.get(deps)) {
+        result.put(library.key, library);
+      }
+    }
+
+    // Collect legacy proto libraries from direct deps
+    addProtoLegacyLibrariesFromDirectDeps(workspaceBuilder, ruleMap, result);
+
+    // Collect generated jars from source rules
+    for (BlazeJarLibrary library : workspaceBuilder.generatedJarsFromSourceRules) {
+      result.put(library.key, library);
+    }
+
+    return ImmutableMap.copyOf(result);
+  }
+
+  private void addProtoLegacyLibrariesFromDirectDeps(
+      WorkspaceBuilder workspaceBuilder, RuleMap ruleMap, Map<LibraryKey, BlazeJarLibrary> result) {
+    List<Label> version1Roots = Lists.newArrayList();
+    List<Label> immutableRoots = Lists.newArrayList();
+    List<Label> mutableRoots = Lists.newArrayList();
+    for (Label label : workspaceBuilder.directDeps) {
+      RuleIdeInfo rule = ruleMap.get(label);
+      if (rule == null) {
+        continue;
+      }
+      ProtoLibraryLegacyInfo protoLibraryLegacyInfo = rule.protoLibraryLegacyInfo;
+      if (protoLibraryLegacyInfo == null) {
+        continue;
+      }
+      switch (protoLibraryLegacyInfo.apiFlavor) {
+        case VERSION_1:
+          version1Roots.add(label);
+          break;
+        case IMMUTABLE:
+          immutableRoots.add(label);
+          break;
+        case MUTABLE:
+          mutableRoots.add(label);
+          break;
+        case BOTH:
+          mutableRoots.add(label);
+          immutableRoots.add(label);
+          break;
+        default:
+          // Can't happen
+          break;
+      }
+    }
+
+    addProtoLegacyLibrariesFromDirectDepsForFlavor(
+        ruleMap, ProtoLibraryLegacyInfo.ApiFlavor.VERSION_1, version1Roots, result);
+    addProtoLegacyLibrariesFromDirectDepsForFlavor(
+        ruleMap, ProtoLibraryLegacyInfo.ApiFlavor.IMMUTABLE, immutableRoots, result);
+    addProtoLegacyLibrariesFromDirectDepsForFlavor(
+        ruleMap, ProtoLibraryLegacyInfo.ApiFlavor.MUTABLE, mutableRoots, result);
+  }
+
+  private void addProtoLegacyLibrariesFromDirectDepsForFlavor(
+      RuleMap ruleMap,
+      ProtoLibraryLegacyInfo.ApiFlavor apiFlavor,
+      List<Label> roots,
+      Map<LibraryKey, BlazeJarLibrary> result) {
+    Set<Label> seen = Sets.newHashSet();
+    while (!roots.isEmpty()) {
+      Label label = roots.remove(roots.size() - 1);
+      if (!seen.add(label)) {
+        continue;
+      }
+      RuleIdeInfo rule = ruleMap.get(label);
+      if (rule == null) {
+        continue;
+      }
+      ProtoLibraryLegacyInfo protoLibraryLegacyInfo = rule.protoLibraryLegacyInfo;
+      if (protoLibraryLegacyInfo == null) {
+        continue;
+      }
+      final Collection<LibraryArtifact> libraries;
+      switch (apiFlavor) {
+        case VERSION_1:
+          libraries = protoLibraryLegacyInfo.jarsV1;
+          break;
+        case MUTABLE:
+          libraries = protoLibraryLegacyInfo.jarsMutable;
+          break;
+        case IMMUTABLE:
+          libraries = protoLibraryLegacyInfo.jarsImmutable;
+          break;
+        default:
+          // Can't happen
+          libraries = null;
+          break;
+      }
+
+      if (libraries != null) {
+        for (LibraryArtifact libraryArtifact : libraries) {
+          BlazeJarLibrary library = new BlazeJarLibrary(libraryArtifact, label);
+          result.put(library.key, library);
+        }
+      }
+
+      roots.addAll(rule.dependencies);
+    }
+  }
+
+  private void addLibraryToJdeps(
+      Map<String, BlazeJarLibrary> jdepsPathToLibrary, BlazeJarLibrary library) {
+    LibraryArtifact libraryArtifact = library.libraryArtifact;
+    ArtifactLocation interfaceJar = libraryArtifact.interfaceJar;
+    if (interfaceJar != null) {
+      jdepsPathToLibrary.put(interfaceJar.getExecutionRootRelativePath(), library);
+    }
+    ArtifactLocation classJar = libraryArtifact.classJar;
+    if (classJar != null) {
+      jdepsPathToLibrary.put(classJar.getExecutionRootRelativePath(), library);
+    }
+  }
+
+  private void addRuleAsSource(
+      WorkspaceBuilder workspaceBuilder,
+      RuleIdeInfo rule,
+      Collection<ArtifactLocation> javaSources) {
+    JavaRuleIdeInfo javaRuleIdeInfo = rule.javaRuleIdeInfo;
+    if (javaRuleIdeInfo == null) {
+      return;
+    }
+
+    Label label = rule.label;
+    Collection<String> jars = jdepsMap.getDependenciesForRule(label);
+    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)) {
+      workspaceBuilder.directDeps.add(
+          label); // Add self, so we pick up our own gen jars if in working set
+      workspaceBuilder.directDeps.addAll(rule.dependencies);
+    }
+
+    for (ArtifactLocation artifactLocation : javaSources) {
+      if (artifactLocation.isSource()) {
+        duplicateSourceDetector.add(label, artifactLocation);
+        workspaceBuilder.sourceArtifacts.add(new SourceArtifact(label, artifactLocation));
+        workspaceBuilder.addedSourceFiles.add(artifactLocation.getFile());
+      }
+    }
+
+    ArtifactLocation manifest = javaRuleIdeInfo.packageManifest;
+    if (manifest != null) {
+      workspaceBuilder.javaPackageManifests.put(label, manifest);
+    }
+    for (LibraryArtifact libraryArtifact : javaRuleIdeInfo.jars) {
+      ArtifactLocation classJar = libraryArtifact.classJar;
+      if (classJar != null) {
+        workspaceBuilder.buildOutputJars.add(classJar.getFile());
+      }
+    }
+    workspaceBuilder.generatedJarsFromSourceRules.addAll(
+        javaRuleIdeInfo
+            .generatedJars
+            .stream()
+            .map(libraryArtifact -> new BlazeJarLibrary(libraryArtifact, label))
+            .collect(Collectors.toList()));
+    if (javaRuleIdeInfo.filteredGenJar != null) {
+      workspaceBuilder.generatedJarsFromSourceRules.add(
+          new BlazeJarLibrary(javaRuleIdeInfo.filteredGenJar, label));
+    }
+
+    for (BlazeJavaSyncAugmenter augmenter : augmenters) {
+      augmenter.addJarsForSourceRule(
+          rule,
+          workspaceBuilder.outputJarsFromSourceRules.get(label),
+          workspaceBuilder.generatedJarsFromSourceRules);
+    }
+  }
+
+  @Nullable
+  private String findSourceVersion(RuleMap ruleMap) {
+    for (RuleIdeInfo rule : ruleMap.rules()) {
+      if (rule.javaToolchainIdeInfo != null) {
+        return rule.javaToolchainIdeInfo.sourceVersion;
+      }
+    }
+    return null;
+  }
+
+  static class WorkspaceBuilder {
+    Set<String> jdeps = Sets.newHashSet();
+    Set<Label> directDeps = Sets.newHashSet();
+    Set<File> addedSourceFiles = Sets.newHashSet();
+    Multimap<Label, BlazeJarLibrary> outputJarsFromSourceRules = ArrayListMultimap.create();
+    List<BlazeJarLibrary> generatedJarsFromSourceRules = Lists.newArrayList();
+    List<File> buildOutputJars = Lists.newArrayList();
+    List<SourceArtifact> sourceArtifacts = Lists.newArrayList();
+    Map<Label, ArtifactLocation> javaPackageManifests = Maps.newHashMap();
+  }
+}
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
new file mode 100644
index 0000000..4570965
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/sync/jdeps/JdepsFileReader.java
@@ -0,0 +1,195 @@
+/*
+ * 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.jdeps;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.idea.blaze.base.async.FutureUtil;
+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.model.RuleMap;
+import com.google.idea.blaze.base.model.SyncState;
+import com.google.idea.blaze.base.model.primitives.Label;
+import com.google.idea.blaze.base.prefetch.PrefetchService;
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.scope.Scope;
+import com.google.idea.blaze.base.scope.output.PrintOutput;
+import com.google.idea.blaze.base.scope.scopes.TimingScope;
+import com.google.repackaged.devtools.build.lib.view.proto.Deps;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicLong;
+import javax.annotation.Nullable;
+
+/** Reads jdeps from the ide info result. */
+public class JdepsFileReader {
+  private static final Logger LOG = Logger.getInstance(JdepsFileReader.class);
+
+  static class JdepsState implements Serializable {
+    private static final long serialVersionUID = 3L;
+    private ImmutableMap<File, Long> fileState = null;
+    private Map<File, Label> fileToLabelMap = Maps.newHashMap();
+    private Map<Label, List<String>> labelToJdeps = Maps.newHashMap();
+  }
+
+  private static class Result {
+    File file;
+    Label label;
+    List<String> dependencies;
+
+    public Result(File file, Label label, List<String> dependencies) {
+      this.file = file;
+      this.label = label;
+      this.dependencies = dependencies;
+    }
+  }
+
+  /** Loads any updated jdeps files since the last invocation of this method. */
+  @Nullable
+  public JdepsMap loadJdepsFiles(
+      Project project,
+      BlazeContext parentContext,
+      RuleMap ruleMap,
+      SyncState.Builder syncStateBuilder,
+      @Nullable SyncState previousSyncState) {
+    JdepsState oldState =
+        previousSyncState != null ? previousSyncState.get(JdepsState.class) : null;
+    JdepsState jdepsState =
+        Scope.push(
+            parentContext,
+            (context) -> {
+              context.push(new TimingScope("LoadJdepsFiles"));
+              return doLoadJdepsFiles(project, context, oldState, ruleMap);
+            });
+    if (jdepsState == null) {
+      return null;
+    }
+    syncStateBuilder.put(JdepsState.class, jdepsState);
+    return label -> jdepsState.labelToJdeps.get(label);
+  }
+
+  private JdepsState doLoadJdepsFiles(
+      Project project, BlazeContext context, @Nullable JdepsState oldState, RuleMap ruleMap) {
+    JdepsState state = new JdepsState();
+    if (oldState != null) {
+      state.labelToJdeps = Maps.newHashMap(oldState.labelToJdeps);
+      state.fileToLabelMap = Maps.newHashMap(oldState.fileToLabelMap);
+    }
+
+    List<File> files = Lists.newArrayList();
+    for (RuleIdeInfo ruleIdeInfo : ruleMap.rules()) {
+      JavaRuleIdeInfo javaRuleIdeInfo = ruleIdeInfo.javaRuleIdeInfo;
+      if (javaRuleIdeInfo != null) {
+        ArtifactLocation jdepsFile = javaRuleIdeInfo.jdepsFile;
+        if (jdepsFile != null) {
+          files.add(jdepsFile.getFile());
+        }
+      }
+    }
+
+    List<File> updatedFiles = Lists.newArrayList();
+    List<File> removedFiles = Lists.newArrayList();
+    state.fileState =
+        FileDiffer.updateFiles(
+            oldState != null ? oldState.fileState : null, files, updatedFiles, removedFiles);
+
+    ListenableFuture<?> fetchFuture =
+        PrefetchService.getInstance().prefetchFiles(project, updatedFiles);
+    if (!FutureUtil.waitForFuture(context, fetchFuture)
+        .timed("FetchJdeps")
+        .withProgressMessage("Reading jdeps files...")
+        .run()
+        .success()) {
+      return null;
+    }
+
+    for (File removedFile : removedFiles) {
+      Label label = state.fileToLabelMap.remove(removedFile);
+      if (label != null) {
+        state.labelToJdeps.remove(label);
+      }
+    }
+
+    AtomicLong totalSizeLoaded = new AtomicLong(0);
+
+    List<ListenableFuture<Result>> futures = Lists.newArrayList();
+    for (File updatedFile : updatedFiles) {
+      futures.add(
+          submit(
+              () -> {
+                totalSizeLoaded.addAndGet(updatedFile.length());
+                try (InputStream inputStream = new FileInputStream(updatedFile)) {
+                  Deps.Dependencies dependencies = Deps.Dependencies.parseFrom(inputStream);
+                  if (dependencies != null) {
+                    if (dependencies.hasRuleLabel()) {
+                      Label label = new Label(dependencies.getRuleLabel());
+                      List<String> dependencyStringList = Lists.newArrayList();
+                      for (Deps.Dependency dependency : dependencies.getDependencyList()) {
+                        // We only want explicit or implicit deps that were
+                        // actually resolved by the compiler, not ones that are
+                        // available for use in the same package
+                        if (dependency.getKind() == Deps.Dependency.Kind.EXPLICIT
+                            || dependency.getKind() == Deps.Dependency.Kind.IMPLICIT) {
+                          dependencyStringList.add(dependency.getPath());
+                        }
+                      }
+                      return new Result(updatedFile, label, dependencyStringList);
+                    }
+                  }
+                } catch (FileNotFoundException e) {
+                  LOG.info("Could not open jdeps file: " + updatedFile);
+                }
+                return null;
+              }));
+    }
+    try {
+      for (Result result : Futures.allAsList(futures).get()) {
+        if (result != null) {
+          state.fileToLabelMap.put(result.file, result.label);
+          state.labelToJdeps.put(result.label, result.dependencies);
+        }
+      }
+      context.output(
+          PrintOutput.log(
+              String.format(
+                  "Loaded %d jdeps files, total size %dkB",
+                  updatedFiles.size(), totalSizeLoaded.get() / 1024)));
+    } catch (InterruptedException | ExecutionException e) {
+      LOG.error(e);
+      return null;
+    }
+    return state;
+  }
+
+  private static <T> ListenableFuture<T> submit(Callable<T> callable) {
+    return BlazeExecutor.getInstance().submit(callable);
+  }
+}
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
new file mode 100644
index 0000000..73ae6a2
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/sync/jdeps/JdepsMap.java
@@ -0,0 +1,35 @@
+/*
+ * 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.jdeps;
+
+import com.google.idea.blaze.base.model.primitives.Label;
+import java.util.List;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/** Map of rule -> jdeps dependencies. */
+public interface JdepsMap {
+  /**
+   * For a given rule, returns workspace root relative paths of artifacts that were used during
+   * compilation.
+   *
+   * <p>It's not specified whether jars or ijars are used during compilation.
+   *
+   * <p>If the rule doesn't have source or otherwise wasn't instrumented, null is returned.
+   */
+  @Nullable
+  List<String> getDependenciesForRule(@NotNull Label label);
+}
diff --git a/java/src/com/google/idea/blaze/java/sync/model/BlazeContentEntry.java b/java/src/com/google/idea/blaze/java/sync/model/BlazeContentEntry.java
new file mode 100644
index 0000000..996265d
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/sync/model/BlazeContentEntry.java
@@ -0,0 +1,96 @@
+/*
+ * 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.model;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import java.io.File;
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.List;
+import javax.annotation.concurrent.Immutable;
+
+/** Corresponds to an IntelliJ content entry. */
+@Immutable
+public class BlazeContentEntry implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  public final File contentRoot;
+  public final ImmutableList<BlazeSourceDirectory> sources;
+
+  public BlazeContentEntry(File contentRoot, ImmutableList<BlazeSourceDirectory> sources) {
+    this.contentRoot = contentRoot;
+    this.sources = sources;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    BlazeContentEntry that = (BlazeContentEntry) o;
+    return Objects.equal(contentRoot, that.contentRoot) && Objects.equal(sources, that.sources);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(contentRoot, sources);
+  }
+
+  @Override
+  public String toString() {
+    return "BlazeContentEntry {\n"
+        + "  contentRoot: "
+        + contentRoot
+        + "\n"
+        + "  sources: "
+        + sources
+        + "\n"
+        + '}';
+  }
+
+  public static Builder builder(String contentRoot) {
+    return new Builder(new File(contentRoot));
+  }
+
+  public static Builder builder(File contentRoot) {
+    return new Builder(contentRoot);
+  }
+
+  /** Builder for content entries */
+  public static class Builder {
+    File contentRoot;
+    List<BlazeSourceDirectory> sources = Lists.newArrayList();
+
+    public Builder(File contentRoot) {
+      this.contentRoot = contentRoot;
+    }
+
+    public Builder addSource(BlazeSourceDirectory sourceDirectory) {
+      this.sources.add(sourceDirectory);
+      return this;
+    }
+
+    public BlazeContentEntry build() {
+      Collections.sort(sources, BlazeSourceDirectory.COMPARATOR);
+      return new BlazeContentEntry(contentRoot, ImmutableList.copyOf(sources));
+    }
+  }
+}
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
new file mode 100644
index 0000000..2470e63
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/sync/model/BlazeJarLibrary.java
@@ -0,0 +1,83 @@
+/*
+ * 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.model;
+
+import com.google.common.base.Objects;
+import com.google.idea.blaze.base.ideinfo.LibraryArtifact;
+import com.google.idea.blaze.base.model.primitives.Label;
+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.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.OrderRootType;
+import com.intellij.openapi.roots.libraries.Library;
+import java.io.File;
+import javax.annotation.concurrent.Immutable;
+
+/** An immutable reference to a .jar required by a rule. */
+@Immutable
+public final class BlazeJarLibrary extends BlazeLibrary {
+  private static final long serialVersionUID = 1L;
+
+  public final LibraryArtifact libraryArtifact;
+
+  public final Label originatingRule;
+
+  public BlazeJarLibrary(LibraryArtifact libraryArtifact, Label originatingRule) {
+    super(LibraryKey.fromJarFile(libraryArtifact.jarForIntellijLibrary().getFile()));
+    this.libraryArtifact = libraryArtifact;
+    this.originatingRule = originatingRule;
+  }
+
+  @Override
+  public void modifyLibraryModel(Project project, Library.ModifiableModel libraryModel) {
+    JarCache jarCache = JarCache.getInstance(project);
+    File jar = jarCache.getCachedJar(this);
+    libraryModel.addRoot(pathToUrl(jar), OrderRootType.CLASSES);
+
+    boolean attachSourcesByDefault =
+        BlazeJavaUserSettings.getInstance().getAttachSourcesByDefault();
+    SourceJarManager sourceJarManager = SourceJarManager.getInstance(project);
+    boolean attachSourceJar = attachSourcesByDefault || sourceJarManager.hasSourceJarAttached(key);
+    if (attachSourceJar && libraryArtifact.sourceJar != null) {
+      File sourceJar = jarCache.getCachedSourceJar(this);
+      if (sourceJar != null) {
+        libraryModel.addRoot(pathToUrl(sourceJar), OrderRootType.SOURCES);
+      }
+    }
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(super.hashCode(), libraryArtifact, originatingRule);
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    if (this == other) {
+      return true;
+    }
+    if (!(other instanceof BlazeJarLibrary)) {
+      return false;
+    }
+
+    BlazeJarLibrary that = (BlazeJarLibrary) other;
+
+    return super.equals(other)
+        && Objects.equal(libraryArtifact, that.libraryArtifact)
+        && Objects.equal(originatingRule, that.originatingRule);
+  }
+}
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
new file mode 100644
index 0000000..f033772
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/sync/model/BlazeJavaImportResult.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.java.sync.model;
+
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import java.io.File;
+import java.io.Serializable;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+/** The result of a blaze import operation. */
+@Immutable
+public class BlazeJavaImportResult implements Serializable {
+  private static final long serialVersionUID = 3L;
+
+  public final ImmutableList<BlazeContentEntry> contentEntries;
+  public final ImmutableMap<LibraryKey, BlazeJarLibrary> libraries;
+  public final ImmutableCollection<File> buildOutputJars;
+  public final ImmutableSet<File> javaSourceFiles;
+  @Nullable public final String sourceVersion;
+
+  public BlazeJavaImportResult(
+      ImmutableList<BlazeContentEntry> contentEntries,
+      ImmutableMap<LibraryKey, BlazeJarLibrary> libraries,
+      ImmutableCollection<File> buildOutputJars,
+      ImmutableSet<File> javaSourceFiles,
+      @Nullable String sourceVersion) {
+    this.contentEntries = contentEntries;
+    this.libraries = libraries;
+    this.buildOutputJars = buildOutputJars;
+    this.javaSourceFiles = javaSourceFiles;
+    this.sourceVersion = sourceVersion;
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/sync/model/BlazeJavaSyncData.java b/java/src/com/google/idea/blaze/java/sync/model/BlazeJavaSyncData.java
new file mode 100644
index 0000000..25e71c6
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/sync/model/BlazeJavaSyncData.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.java.sync.model;
+
+import com.google.idea.blaze.base.projectview.section.Glob;
+import java.io.Serializable;
+
+/** Sync data for the java plugin. */
+public class BlazeJavaSyncData implements Serializable {
+  private static final long serialVersionUID = 3L;
+
+  public final BlazeJavaImportResult importResult;
+  public final Glob.GlobSet excludedLibraries;
+
+  public BlazeJavaSyncData(BlazeJavaImportResult importResult, Glob.GlobSet excludedLibraries) {
+    this.importResult = importResult;
+    this.excludedLibraries = excludedLibraries;
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/sync/model/BlazeLibrary.java b/java/src/com/google/idea/blaze/java/sync/model/BlazeLibrary.java
new file mode 100644
index 0000000..f84fc4c
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/sync/model/BlazeLibrary.java
@@ -0,0 +1,80 @@
+/*
+ * 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.model;
+
+import com.google.common.base.Objects;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.libraries.Library;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.io.FileUtilRt;
+import com.intellij.openapi.vfs.StandardFileSystems;
+import com.intellij.openapi.vfs.VirtualFileManager;
+import com.intellij.util.io.URLUtil;
+import java.io.File;
+import java.io.Serializable;
+import javax.annotation.concurrent.Immutable;
+
+/** A model object for something that will map to an IntelliJ library. */
+@Immutable
+public abstract class BlazeLibrary implements Serializable {
+  private static final long serialVersionUID = 8L;
+
+  public final LibraryKey key;
+
+  protected BlazeLibrary(LibraryKey key) {
+    this.key = key;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(key);
+  }
+
+  @Override
+  public String toString() {
+    return key.toString();
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    if (this == other) {
+      return true;
+    }
+    if (!(other instanceof BlazeLibrary)) {
+      return false;
+    }
+
+    BlazeLibrary that = (BlazeLibrary) other;
+    return Objects.equal(key, that.key);
+  }
+
+  public abstract void modifyLibraryModel(Project project, Library.ModifiableModel libraryModel);
+
+  protected static String pathToUrl(File path) {
+    String name = path.getName();
+    boolean isJarFile =
+        FileUtilRt.extensionEquals(name, "jar") || FileUtilRt.extensionEquals(name, "zip");
+    // .jar files require an URL with "jar" protocol.
+    String protocol =
+        isJarFile ? StandardFileSystems.JAR_PROTOCOL : StandardFileSystems.FILE_PROTOCOL;
+    String filePath = FileUtil.toSystemIndependentName(path.getPath());
+    String url = VirtualFileManager.constructUrl(protocol, filePath);
+    if (isJarFile) {
+      url += URLUtil.JAR_SEPARATOR;
+    }
+    return url;
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/sync/model/BlazeSourceDirectory.java b/java/src/com/google/idea/blaze/java/sync/model/BlazeSourceDirectory.java
new file mode 100644
index 0000000..f39a2e0
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/sync/model/BlazeSourceDirectory.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.java.sync.model;
+
+import com.google.common.base.Objects;
+import java.io.File;
+import java.io.Serializable;
+import java.util.Comparator;
+import javax.annotation.concurrent.Immutable;
+import org.jetbrains.annotations.NotNull;
+
+/** A source directory. */
+@Immutable
+public final class BlazeSourceDirectory implements Serializable {
+  private static final long serialVersionUID = 2L;
+
+  public static final Comparator<BlazeSourceDirectory> COMPARATOR =
+      (o1, o2) ->
+          String.CASE_INSENSITIVE_ORDER.compare(
+              o1.getDirectory().getPath(), o2.getDirectory().getPath());
+
+  @NotNull private final File directory;
+  private final boolean isTest;
+  private final boolean isGenerated;
+  private final boolean isResource;
+  @NotNull private final String packagePrefix;
+
+  /** Bulider for source directory */
+  public static class Builder {
+    @NotNull private final File directory;
+    @NotNull private String packagePrefix = "";
+    private boolean isTest;
+    private boolean isResource;
+    private boolean isGenerated;
+
+    private Builder(@NotNull File directory) {
+      this.directory = directory;
+    }
+
+    public Builder setPackagePrefix(@NotNull String packagePrefix) {
+      this.packagePrefix = packagePrefix;
+      return this;
+    }
+
+    public Builder setTest(boolean isTest) {
+      this.isTest = isTest;
+      return this;
+    }
+
+    public Builder setResource(boolean isResource) {
+      this.isResource = isResource;
+      return this;
+    }
+
+    public Builder setGenerated(boolean isGenerated) {
+      this.isGenerated = isGenerated;
+      return this;
+    }
+
+    public BlazeSourceDirectory build() {
+      return new BlazeSourceDirectory(directory, isTest, isResource, isGenerated, packagePrefix);
+    }
+  }
+
+  @NotNull
+  public static Builder builder(@NotNull String directory) {
+    return new Builder(new File(directory));
+  }
+
+  @NotNull
+  public static Builder builder(@NotNull File directory) {
+    return new Builder(directory);
+  }
+
+  private BlazeSourceDirectory(
+      @NotNull File directory,
+      boolean isTest,
+      boolean isResource,
+      boolean isGenerated,
+      @NotNull String packagePrefix) {
+    this.directory = directory;
+    this.isTest = isTest;
+    this.isResource = isResource;
+    this.isGenerated = isGenerated;
+    this.packagePrefix = packagePrefix;
+  }
+
+  /** Returns the full path name of the root of a source directory. */
+  @NotNull
+  public File getDirectory() {
+    return directory;
+  }
+
+  /** Returns {@code true} if the directory contains test sources. */
+  public boolean getIsTest() {
+    return isTest;
+  }
+
+  /** Returns {@code true} if the directory contains resources. */
+  public boolean getIsResource() {
+    return isResource;
+  }
+
+  /** Returns {@code true} if the directory contains generated files. */
+  public boolean getIsGenerated() {
+    return isGenerated;
+  }
+
+  /**
+   * Returns the package prefix for the directory. If the directory is a source root, such as a
+   * "src" directory, then this returns an empty string.
+   */
+  @NotNull
+  public String getPackagePrefix() {
+    return packagePrefix;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(directory, isTest, isResource, packagePrefix, isGenerated);
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    if (this == other) {
+      return true;
+    }
+    if (!(other instanceof BlazeSourceDirectory)) {
+      return false;
+    }
+    BlazeSourceDirectory that = (BlazeSourceDirectory) other;
+    return directory.equals(that.directory)
+        && packagePrefix.equals(that.packagePrefix)
+        && isResource == that.isResource
+        && isTest == that.isTest
+        && isGenerated == that.isGenerated;
+  }
+
+  @Override
+  public String toString() {
+    return "BlazeSourceDirectory {\n"
+        + "  directory: "
+        + directory
+        + "\n"
+        + "  isTest: "
+        + isTest
+        + "\n"
+        + "  isGenerated: "
+        + isGenerated
+        + "\n"
+        + "  isResource: "
+        + isResource
+        + "\n"
+        + "  packagePrefix: "
+        + packagePrefix
+        + "\n"
+        + '}';
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/sync/model/LibraryKey.java b/java/src/com/google/idea/blaze/java/sync/model/LibraryKey.java
new file mode 100644
index 0000000..8496a31
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/sync/model/LibraryKey.java
@@ -0,0 +1,88 @@
+/*
+ * 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.model;
+
+import com.intellij.openapi.roots.libraries.Library;
+import com.intellij.openapi.util.io.FileUtil;
+import java.io.File;
+import java.io.Serializable;
+import javax.annotation.concurrent.Immutable;
+import org.jetbrains.annotations.NotNull;
+
+/** Uniquely identifies a library as imported into IntellJ. */
+@Immutable
+public final class LibraryKey implements Serializable {
+  public static final long serialVersionUID = 1L;
+
+  @NotNull private final String name;
+
+  @NotNull
+  public static LibraryKey fromJarFile(@NotNull File jarFile) {
+    int parentHash = jarFile.getParent().hashCode();
+    String name = FileUtil.getNameWithoutExtension(jarFile) + "_" + Integer.toHexString(parentHash);
+    return new LibraryKey(name);
+  }
+
+  @NotNull
+  public static LibraryKey forResourceLibrary() {
+    return new LibraryKey("external_resources_library");
+  }
+
+  @NotNull
+  public static LibraryKey fromIntelliJLibrary(@NotNull Library library) {
+    String name = library.getName();
+    if (name == null) {
+      throw new IllegalArgumentException("Null library name");
+    }
+    return fromIntelliJLibraryName(name);
+  }
+
+  @NotNull
+  public static LibraryKey fromIntelliJLibraryName(@NotNull String name) {
+    return new LibraryKey(name);
+  }
+
+  LibraryKey(@NotNull String name) {
+    this.name = name;
+  }
+
+  public String getIntelliJLibraryName() {
+    return name;
+  }
+
+  @Override
+  public String toString() {
+    return name;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+
+    LibraryKey that = (LibraryKey) o;
+    return name.equals(that.name);
+  }
+
+  @Override
+  public int hashCode() {
+    return name.hashCode();
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/sync/projectstructure/Jdks.java b/java/src/com/google/idea/blaze/java/sync/projectstructure/Jdks.java
new file mode 100644
index 0000000..e80c98b
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/sync/projectstructure/Jdks.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.java.sync.projectstructure;
+
+import static com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil.createAndAddSDK;
+import static com.intellij.openapi.util.io.FileUtil.notNullize;
+import static com.intellij.openapi.util.text.StringUtil.isNotEmpty;
+import static java.util.Collections.emptyList;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.model.primitives.LanguageClass;
+import com.google.idea.blaze.base.sync.sdk.DefaultSdkProvider;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.projectRoots.JavaSdk;
+import com.intellij.openapi.projectRoots.JavaSdkVersion;
+import com.intellij.openapi.projectRoots.ProjectJdkTable;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.pom.java.LanguageLevel;
+import com.intellij.util.SystemProperties;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/** Utility methods related to IDEA JDKs. */
+public class Jdks {
+  @NonNls private static final LanguageLevel DEFAULT_LANG_LEVEL = LanguageLevel.JDK_1_7;
+
+  @Nullable
+  public static Sdk chooseOrCreateJavaSdk(LanguageLevel langLevel) {
+    for (Sdk sdk : ProjectJdkTable.getInstance().getAllJdks()) {
+      if (isApplicableJdk(sdk, langLevel)) {
+        return sdk;
+      }
+    }
+    String jdkHomePath = null;
+    for (DefaultSdkProvider defaultSdkProvider : DefaultSdkProvider.EP_NAME.getExtensions()) {
+      File sdk = defaultSdkProvider.provideSdkForLanguage(LanguageClass.JAVA);
+      if (sdk != null) {
+        jdkHomePath = sdk.getPath();
+        break;
+      }
+    }
+
+    if (jdkHomePath == null) {
+      jdkHomePath = getJdkHomePath(langLevel);
+    }
+
+    if (jdkHomePath == null) {
+      return null;
+    }
+
+    return createJdk(jdkHomePath);
+  }
+
+  public static boolean isApplicableJdk(@NotNull Sdk jdk, @Nullable LanguageLevel langLevel) {
+    if (!(jdk.getSdkType() instanceof JavaSdk)) {
+      return false;
+    }
+    if (langLevel == null) {
+      langLevel = DEFAULT_LANG_LEVEL;
+    }
+    JavaSdkVersion version = JavaSdk.getInstance().getVersion(jdk);
+    if (version != null) {
+      //noinspection TestOnlyProblems
+      return hasMatchingLangLevel(version, langLevel);
+    }
+    return false;
+  }
+
+  @Nullable
+  public static String getJdkHomePath(@NotNull LanguageLevel langLevel) {
+    Collection<String> jdkHomePaths =
+        new ArrayList<String>(JavaSdk.getInstance().suggestHomePaths());
+    if (jdkHomePaths.isEmpty()) {
+      return null;
+    }
+    // prefer jdk path of getJavaHome(), since we have to allow access to it in tests
+    // see AndroidProjectDataServiceTest#testImportData()
+    final List<String> list = new ArrayList<String>();
+    String javaHome = SystemProperties.getJavaHome();
+
+    if (javaHome != null && !javaHome.isEmpty()) {
+      for (Iterator<String> it = jdkHomePaths.iterator(); it.hasNext(); ) {
+        final String path = it.next();
+
+        if (path != null && javaHome.startsWith(path)) {
+          it.remove();
+          list.add(path);
+        }
+      }
+    }
+    list.addAll(jdkHomePaths);
+    return getBestJdkHomePath(list, langLevel);
+  }
+
+  @VisibleForTesting
+  @Nullable
+  static String getBestJdkHomePath(
+      @NotNull Collection<String> jdkHomePaths, @NotNull LanguageLevel langLevel) {
+    // Search for JDKs in both the suggest folder and all its sub folders.
+    List<String> roots = Lists.newArrayList();
+    for (String jdkHomePath : jdkHomePaths) {
+      if (isNotEmpty(jdkHomePath)) {
+        roots.add(jdkHomePath);
+        roots.addAll(getChildrenPaths(jdkHomePath));
+      }
+    }
+    return getBestJdk(roots, langLevel);
+  }
+
+  @NotNull
+  private static List<String> getChildrenPaths(@NotNull String dirPath) {
+    File dir = new File(dirPath);
+    if (!dir.isDirectory()) {
+      return emptyList();
+    }
+    List<String> childrenPaths = Lists.newArrayList();
+    for (File child : notNullize(dir.listFiles())) {
+      boolean directory = child.isDirectory();
+      if (directory) {
+        childrenPaths.add(child.getAbsolutePath());
+      }
+    }
+    return childrenPaths;
+  }
+
+  @Nullable
+  private static String getBestJdk(
+      @NotNull List<String> jdkRoots, @NotNull LanguageLevel langLevel) {
+    String bestJdk = null;
+    for (String jdkRoot : jdkRoots) {
+      if (JavaSdk.getInstance().isValidSdkHome(jdkRoot)) {
+        if (bestJdk == null && hasMatchingLangLevel(jdkRoot, langLevel)) {
+          bestJdk = jdkRoot;
+        } else if (bestJdk != null) {
+          bestJdk = selectJdk(bestJdk, jdkRoot, langLevel);
+        }
+      }
+    }
+    return bestJdk;
+  }
+
+  @Nullable
+  private static String selectJdk(
+      @NotNull String jdk1, @NotNull String jdk2, @NotNull LanguageLevel langLevel) {
+    if (hasMatchingLangLevel(jdk1, langLevel)) {
+      return jdk1;
+    }
+    if (hasMatchingLangLevel(jdk2, langLevel)) {
+      return jdk2;
+    }
+    return null;
+  }
+
+  private static boolean hasMatchingLangLevel(
+      @NotNull String jdkRoot, @NotNull LanguageLevel langLevel) {
+    JavaSdkVersion version = getVersion(jdkRoot);
+    return hasMatchingLangLevel(version, langLevel);
+  }
+
+  @VisibleForTesting
+  static boolean hasMatchingLangLevel(
+      @NotNull JavaSdkVersion jdkVersion, @NotNull LanguageLevel langLevel) {
+    LanguageLevel max = jdkVersion.getMaxLanguageLevel();
+    return max.isAtLeast(langLevel);
+  }
+
+  @NotNull
+  private static JavaSdkVersion getVersion(@NotNull String jdkRoot) {
+    String version = JavaSdk.getInstance().getVersionString(jdkRoot);
+    if (version == null) {
+      return JavaSdkVersion.JDK_1_0;
+    }
+    JavaSdkVersion sdkVersion = JavaSdk.getInstance().getVersion(version);
+    return sdkVersion == null ? JavaSdkVersion.JDK_1_0 : sdkVersion;
+  }
+
+  @Nullable
+  public static Sdk createJdk(@NotNull String jdkHomePath) {
+    Sdk jdk = createAndAddSDK(jdkHomePath, JavaSdk.getInstance());
+    if (jdk == null) {
+      String msg = String.format("Unable to create JDK from path '%1$s'", jdkHomePath);
+      Logger.getInstance(Jdks.class).error(msg);
+    }
+    return jdk;
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/sync/projectstructure/LibraryEditor.java b/java/src/com/google/idea/blaze/java/sync/projectstructure/LibraryEditor.java
new file mode 100644
index 0000000..ea197ed
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/sync/projectstructure/LibraryEditor.java
@@ -0,0 +1,133 @@
+/*
+ * 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.projectstructure;
+
+import com.google.common.collect.Sets;
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.scope.output.PrintOutput;
+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;
+import com.intellij.openapi.roots.OrderRootType;
+import com.intellij.openapi.roots.impl.libraries.ProjectLibraryTable;
+import com.intellij.openapi.roots.libraries.Library;
+import com.intellij.openapi.roots.libraries.LibraryTable;
+import java.util.Collection;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/** Edits IntelliJ libraries */
+public class LibraryEditor {
+  private static final Logger LOG = Logger.getInstance(LibraryEditor.class);
+
+  public static void updateProjectLibraries(
+      Project project,
+      BlazeContext context,
+      BlazeProjectData blazeProjectData,
+      Collection<BlazeLibrary> libraries) {
+    Set<LibraryKey> intelliJLibraryState = Sets.newHashSet();
+    for (Library library : ProjectLibraryTable.getInstance(project).getLibraries()) {
+      String name = library.getName();
+      if (name != null) {
+        intelliJLibraryState.add(LibraryKey.fromIntelliJLibraryName(name));
+      }
+    }
+    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 {
+      for (BlazeLibrary library : libraries) {
+        updateLibrary(project, libraryTable, libraryTableModel, library);
+      }
+
+      // Garbage collect unused libraries
+      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)) {
+          Library library = libraryTable.getLibraryByName(libraryIntellijName);
+          if (library != null) {
+            libraryTableModel.removeLibrary(library);
+          }
+        }
+      }
+    } finally {
+      libraryTableModel.commit();
+    }
+  }
+
+  public static void updateLibrary(
+      Project project,
+      LibraryTable libraryTable,
+      LibraryTable.ModifiableModel libraryTableModel,
+      BlazeLibrary blazeLibrary) {
+    String libraryName = blazeLibrary.key.getIntelliJLibraryName();
+
+    Library library = libraryTable.getLibraryByName(libraryName);
+    boolean libraryExists = library != null;
+    if (!libraryExists) {
+      library = libraryTableModel.createLibrary(libraryName);
+    }
+    Library.ModifiableModel libraryModel = library.getModifiableModel();
+    if (libraryExists) {
+      for (String url : libraryModel.getUrls(OrderRootType.CLASSES)) {
+        libraryModel.removeRoot(url, OrderRootType.CLASSES);
+      }
+      for (String url : libraryModel.getUrls(OrderRootType.SOURCES)) {
+        libraryModel.removeRoot(url, OrderRootType.SOURCES);
+      }
+    }
+    try {
+      blazeLibrary.modifyLibraryModel(project, libraryModel);
+    } finally {
+      libraryModel.commit();
+    }
+  }
+
+  public static void configureDependencies(
+      ModifiableRootModel modifiableRootModel, Collection<BlazeLibrary> libraries) {
+    for (BlazeLibrary library : libraries) {
+      updateLibraryDependency(modifiableRootModel, library.key);
+    }
+  }
+
+  private static void updateLibraryDependency(ModifiableRootModel model, LibraryKey libraryKey) {
+    LibraryTable libraryTable = ProjectLibraryTable.getInstance(model.getProject());
+    Library library = libraryTable.getLibraryByName(libraryKey.getIntelliJLibraryName());
+    if (library == null) {
+      LOG.error(
+          "Library missing: "
+              + libraryKey.getIntelliJLibraryName()
+              + ". Please resync project to resolve.");
+      return;
+    }
+    model.addLibraryEntry(library);
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/sync/projectstructure/SourceFolderEditor.java b/java/src/com/google/idea/blaze/java/sync/projectstructure/SourceFolderEditor.java
new file mode 100644
index 0000000..0d88c4e
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/sync/projectstructure/SourceFolderEditor.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.java.sync.projectstructure;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Maps;
+import com.google.idea.blaze.java.sync.model.BlazeContentEntry;
+import com.google.idea.blaze.java.sync.model.BlazeJavaImportResult;
+import com.google.idea.blaze.java.sync.model.BlazeSourceDirectory;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.roots.ContentEntry;
+import com.intellij.openapi.roots.SourceFolder;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.VfsUtilCore;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.io.URLUtil;
+import java.io.File;
+import java.util.Collection;
+import java.util.Map;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.jps.model.JpsElement;
+import org.jetbrains.jps.model.java.JavaResourceRootType;
+import org.jetbrains.jps.model.java.JavaSourceRootProperties;
+import org.jetbrains.jps.model.module.JpsModuleSourceRoot;
+
+/** Edits source folders in IntelliJ content entries */
+public class SourceFolderEditor {
+  private static final Logger LOG = Logger.getInstance(SourceFolderEditor.class);
+
+  public static void modifyContentEntries(
+      BlazeJavaImportResult importResult, Collection<ContentEntry> contentEntries) {
+
+    Map<File, BlazeContentEntry> contentEntryMap = Maps.newHashMap();
+    for (BlazeContentEntry contentEntry : importResult.contentEntries) {
+      contentEntryMap.put(contentEntry.contentRoot, contentEntry);
+    }
+
+    for (ContentEntry contentEntry : contentEntries) {
+      VirtualFile virtualFile = contentEntry.getFile();
+      if (virtualFile == null) {
+        continue;
+      }
+
+      File contentRoot = new File(virtualFile.getPath());
+      BlazeContentEntry javaContentEntry = contentEntryMap.get(contentRoot);
+      if (javaContentEntry != null) {
+        for (BlazeSourceDirectory sourceDirectory : javaContentEntry.sources) {
+          addSourceFolderToContentEntry(contentEntry, sourceDirectory);
+        }
+      }
+    }
+  }
+
+  private static void addSourceFolderToContentEntry(
+      ContentEntry contentEntry, BlazeSourceDirectory sourceDirectory) {
+    File sourceDir = sourceDirectory.getDirectory();
+
+    // Create the source folder
+    SourceFolder sourceFolder;
+    if (sourceDirectory.getIsResource()) {
+      JavaResourceRootType resourceRootType =
+          sourceDirectory.getIsTest()
+              ? JavaResourceRootType.TEST_RESOURCE
+              : JavaResourceRootType.RESOURCE;
+      sourceFolder = contentEntry.addSourceFolder(pathToUrl(sourceDir.getPath()), resourceRootType);
+    } else {
+      sourceFolder =
+          contentEntry.addSourceFolder(pathToUrl(sourceDir.getPath()), sourceDirectory.getIsTest());
+    }
+    JpsModuleSourceRoot sourceRoot = sourceFolder.getJpsElement();
+    JpsElement properties = sourceRoot.getProperties();
+    if (properties instanceof JavaSourceRootProperties) {
+      JavaSourceRootProperties rootProperties = (JavaSourceRootProperties) properties;
+      if (sourceDirectory.getIsGenerated()) {
+        rootProperties.setForGeneratedSources(true);
+      }
+      String packagePrefix = sourceDirectory.getPackagePrefix();
+      if (!Strings.isNullOrEmpty(packagePrefix)) {
+        rootProperties.setPackagePrefix(packagePrefix);
+      }
+    }
+  }
+
+  @NotNull
+  private static String pathToUrl(@NotNull String filePath) {
+    filePath = FileUtil.toSystemIndependentName(filePath);
+    if (filePath.endsWith(".srcjar") || filePath.endsWith(".jar")) {
+      return URLUtil.JAR_PROTOCOL + URLUtil.SCHEME_SEPARATOR + filePath + URLUtil.JAR_SEPARATOR;
+    } else if (filePath.contains("src.jar!")) {
+      return URLUtil.JAR_PROTOCOL + URLUtil.SCHEME_SEPARATOR + filePath;
+    } else {
+      return VfsUtilCore.pathToUrl(filePath);
+    }
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/sync/source/FilePathJavaPackageReader.java b/java/src/com/google/idea/blaze/java/sync/source/FilePathJavaPackageReader.java
new file mode 100644
index 0000000..99e3c66
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/sync/source/FilePathJavaPackageReader.java
@@ -0,0 +1,33 @@
+/*
+ * 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.source;
+
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.util.PackagePrefixCalculator;
+
+/** Gets the package from a java file by its file path alone (i.e. without opening the file). */
+public final class FilePathJavaPackageReader extends JavaPackageReader {
+  @Override
+  public String getDeclaredPackageOfJavaFile(BlazeContext context, SourceArtifact sourceArtifact) {
+    String directory = sourceArtifact.artifactLocation.getRelativePath();
+    int i = directory.lastIndexOf('/');
+    if (i >= 0) {
+      directory = directory.substring(0, i);
+    }
+    return PackagePrefixCalculator.packagePrefixOf(new WorkspacePath(directory));
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/sync/source/JavaPackageReader.java b/java/src/com/google/idea/blaze/java/sync/source/JavaPackageReader.java
new file mode 100644
index 0000000..9c1f037
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/sync/source/JavaPackageReader.java
@@ -0,0 +1,25 @@
+/*
+ * 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.source;
+
+import com.google.idea.blaze.base.scope.BlazeContext;
+import javax.annotation.Nullable;
+
+/** Reads java packages from files. */
+public abstract class JavaPackageReader {
+  @Nullable
+  abstract String getDeclaredPackageOfJavaFile(BlazeContext context, SourceArtifact sourceArtifact);
+}
diff --git a/java/src/com/google/idea/blaze/java/sync/source/JavaSourcePackageReader.java b/java/src/com/google/idea/blaze/java/sync/source/JavaSourcePackageReader.java
new file mode 100644
index 0000000..0f3eca0
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/sync/source/JavaSourcePackageReader.java
@@ -0,0 +1,77 @@
+/*
+ * 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.source;
+
+import com.google.idea.blaze.base.io.InputStreamProvider;
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.scope.output.IssueOutput;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.diagnostic.Logger;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.annotation.Nullable;
+
+/** Parse package string directly from java source */
+public class JavaSourcePackageReader extends JavaPackageReader {
+
+  public static JavaSourcePackageReader getInstance() {
+    return ServiceManager.getService(JavaSourcePackageReader.class);
+  }
+
+  private static final Logger LOG = Logger.getInstance(SourceDirectoryCalculator.class);
+
+  private static final Pattern JAVA_PACKAGE_PATTERN =
+      Pattern.compile("^\\s*package\\s+([\\w\\.]+);");
+
+  @Override
+  @Nullable
+  public String getDeclaredPackageOfJavaFile(BlazeContext context, SourceArtifact sourceArtifact) {
+    if (sourceArtifact.artifactLocation.isGenerated()) {
+      return null;
+    }
+    InputStreamProvider inputStreamProvider = InputStreamProvider.getInstance();
+    File sourceFile = sourceArtifact.artifactLocation.getFile();
+    try (InputStream javaInputStream = inputStreamProvider.getFile(sourceFile)) {
+      BufferedReader javaReader = new BufferedReader(new InputStreamReader(javaInputStream));
+      String javaLine;
+
+      while ((javaLine = javaReader.readLine()) != null) {
+        Matcher packageMatch = JAVA_PACKAGE_PATTERN.matcher(javaLine);
+        if (packageMatch.find()) {
+          return packageMatch.group(1);
+        }
+      }
+      IssueOutput.warn("No package name string found in java source file: " + sourceFile)
+          .inFile(sourceFile)
+          .submit(context);
+      return null;
+    } catch (FileNotFoundException e) {
+      IssueOutput.warn("No source file found for: " + sourceFile)
+          .inFile(sourceFile)
+          .submit(context);
+      return null;
+    } catch (IOException e) {
+      LOG.error(e);
+      return null;
+    }
+  }
+}
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
new file mode 100644
index 0000000..726d60a
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/sync/source/ManifestFilePackageReader.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.java.sync.source;
+
+import com.google.idea.blaze.base.model.primitives.Label;
+import com.google.idea.blaze.base.scope.BlazeContext;
+import java.util.Map;
+import javax.annotation.Nullable;
+
+class ManifestFilePackageReader extends JavaPackageReader {
+
+  private final Map<Label, Map<String, String>> manifestMap;
+
+  public ManifestFilePackageReader(Map<Label, Map<String, String>> manifestMap) {
+    this.manifestMap = manifestMap;
+  }
+
+  @Nullable
+  @Override
+  String getDeclaredPackageOfJavaFile(BlazeContext context, SourceArtifact sourceArtifact) {
+    Map<String, String> manifestMapForRule = manifestMap.get(sourceArtifact.originatingRule);
+    if (manifestMapForRule != null) {
+      return manifestMapForRule.get(sourceArtifact.artifactLocation.getFile().getPath());
+    }
+    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
new file mode 100644
index 0000000..61ec202
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/sync/source/PackageManifestReader.java
@@ -0,0 +1,149 @@
+/*
+ * 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.source;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+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.io.InputStreamProvider;
+import com.google.idea.blaze.base.model.primitives.Label;
+import com.google.idea.blaze.base.prefetch.PrefetchService;
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
+import com.google.repackaged.devtools.build.lib.ideinfo.androidstudio.PackageManifestOuterClass.JavaSourcePackage;
+import com.google.repackaged.devtools.build.lib.ideinfo.androidstudio.PackageManifestOuterClass.PackageManifest;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import javax.annotation.Nullable;
+
+/** Reads package manifests. */
+public class PackageManifestReader {
+  private static final Logger LOG = Logger.getInstance(SourceDirectoryCalculator.class);
+
+  public static PackageManifestReader getInstance() {
+    return ServiceManager.getService(PackageManifestReader.class);
+  }
+
+  private ImmutableMap<File, Long> fileDiffState;
+
+  private Map<File, Label> fileToLabelMap = Maps.newHashMap();
+  private final Map<Label, Map<String, String>> manifestMap = Maps.newConcurrentMap();
+
+  /** @return A map from java source absolute file path to declared package string. */
+  public Map<Label, Map<String, String>> readPackageManifestFiles(
+      Project project,
+      BlazeContext context,
+      ArtifactLocationDecoder decoder,
+      Map<Label, ArtifactLocation> javaPackageManifests,
+      ListeningExecutorService executorService) {
+
+    Map<File, Label> fileToLabelMap = Maps.newHashMap();
+    for (Map.Entry<Label, ArtifactLocation> entry : javaPackageManifests.entrySet()) {
+      Label label = entry.getKey();
+      File file = entry.getValue().getFile();
+      fileToLabelMap.put(file, label);
+    }
+    List<File> updatedFiles = Lists.newArrayList();
+    List<File> removedFiles = Lists.newArrayList();
+    fileDiffState =
+        FileDiffer.updateFiles(fileDiffState, fileToLabelMap.keySet(), updatedFiles, removedFiles);
+
+    ListenableFuture<?> fetchFuture =
+        PrefetchService.getInstance().prefetchFiles(project, updatedFiles);
+    if (!FutureUtil.waitForFuture(context, fetchFuture)
+        .timed("FetchPackageManifests")
+        .withProgressMessage("Reading package manifests...")
+        .run()
+        .success()) {
+      return null;
+    }
+
+    List<ListenableFuture<Void>> futures = Lists.newArrayList();
+    for (File file : updatedFiles) {
+      futures.add(
+          executorService.submit(
+              () -> {
+                Map<String, String> manifest = parseManifestFile(decoder, file);
+                manifestMap.put(fileToLabelMap.get(file), manifest);
+                return null;
+              }));
+    }
+    for (File file : removedFiles) {
+      Label label = this.fileToLabelMap.get(file);
+      if (label != null) {
+        manifestMap.remove(label);
+      }
+    }
+    this.fileToLabelMap = fileToLabelMap;
+
+    try {
+      Futures.allAsList(futures).get();
+    } catch (ExecutionException | InterruptedException e) {
+      LOG.error(e);
+      throw new IllegalStateException("Could not read sources");
+    }
+    return manifestMap;
+  }
+
+  protected Map<String, String> parseManifestFile(
+      ArtifactLocationDecoder decoder, File packageManifest) {
+    Map<String, String> outputMap = Maps.newHashMap();
+    InputStreamProvider inputStreamProvider = InputStreamProvider.getInstance();
+
+    try (InputStream input = inputStreamProvider.getFile(packageManifest)) {
+      try (BufferedInputStream bufferedInputStream = new BufferedInputStream(input)) {
+        PackageManifest proto = PackageManifest.parseFrom(bufferedInputStream);
+        for (JavaSourcePackage source : proto.getSourcesList()) {
+          String absPath = getAbsolutePath(decoder, source);
+          if (absPath != null) {
+            outputMap.put(absPath, source.getPackageString());
+          }
+        }
+      }
+      return outputMap;
+    } catch (IOException e) {
+      LOG.error(e);
+      return outputMap;
+    }
+  }
+
+  /**
+   * Returns null if the artifact location file can't be found, presumably because it's been removed
+   * from the file system since the blaze build.
+   */
+  @Nullable
+  private static String getAbsolutePath(ArtifactLocationDecoder decoder, JavaSourcePackage source) {
+    ArtifactLocation location = decoder.decode(source.getArtifactLocation());
+    if (location == null) {
+      return null;
+    }
+    return location.getFile().getAbsolutePath();
+  }
+}
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
new file mode 100644
index 0000000..37e35bd
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/sync/source/SourceArtifact.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.java.sync.source;
+
+import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
+import com.google.idea.blaze.base.model.primitives.Label;
+
+/** Pairing of rule and source artifact. */
+public class SourceArtifact {
+  public final Label originatingRule;
+  public final ArtifactLocation artifactLocation;
+
+  public SourceArtifact(Label originatingRule, ArtifactLocation artifactLocation) {
+    this.originatingRule = originatingRule;
+    this.artifactLocation = artifactLocation;
+  }
+
+  public static Builder builder(Label originatingRule) {
+    return new Builder(originatingRule);
+  }
+
+  static class Builder {
+    Label originatingRule;
+    ArtifactLocation artifactLocation;
+
+    Builder(Label originatingRule) {
+      this.originatingRule = originatingRule;
+    }
+
+    public Builder setArtifactLocation(ArtifactLocation artifactLocation) {
+      this.artifactLocation = artifactLocation;
+      return this;
+    }
+
+    public Builder setArtifactLocation(ArtifactLocation.Builder artifactLocation) {
+      return setArtifactLocation(artifactLocation.build());
+    }
+
+    public SourceArtifact build() {
+      return new SourceArtifact(originatingRule, 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
new file mode 100644
index 0000000..c4d992c
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/sync/source/SourceDirectoryCalculator.java
@@ -0,0 +1,525 @@
+/*
+ * 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.source;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Objects;
+import com.google.common.base.Splitter;
+import com.google.common.base.Strings;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.HashMultiset;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Multiset;
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+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.model.primitives.Label;
+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;
+import com.google.idea.blaze.base.scope.Scope;
+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.projectview.SourceTestConfig;
+import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
+import com.google.idea.blaze.base.util.PackagePrefixCalculator;
+import com.google.idea.blaze.java.sync.model.BlazeContentEntry;
+import com.google.idea.blaze.java.sync.model.BlazeSourceDirectory;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import java.io.File;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
+
+/**
+ * This is a utility class for calculating the java sources and their package prefixes given a
+ * module and its Blaze {@link ArtifactLocation} list.
+ */
+public final class SourceDirectoryCalculator {
+
+  private static final Logger LOG = Logger.getInstance(SourceDirectoryCalculator.class);
+
+  private static final Splitter PACKAGE_SPLITTER = Splitter.on('.');
+  private static final Splitter PATH_SPLITTER = Splitter.on('/');
+  private static final Joiner PACKAGE_JOINER = Joiner.on('.');
+  private static final Joiner PATH_JOINER = Joiner.on('/');
+
+  private static final JavaPackageReader generatedFileJavaPackageReader =
+      new FilePathJavaPackageReader();
+  private final ListeningExecutorService executorService = MoreExecutors.sameThreadExecutor();
+  private final ListeningExecutorService packageReaderExecutorService =
+      MoreExecutors.listeningDecorator(new TransientExecutor(16));
+
+  public ImmutableList<BlazeContentEntry> calculateContentEntries(
+      Project project,
+      BlazeContext context,
+      WorkspaceRoot workspaceRoot,
+      SourceTestConfig sourceTestConfig,
+      ArtifactLocationDecoder artifactLocationDecoder,
+      Collection<WorkspacePath> rootDirectories,
+      Collection<SourceArtifact> sources,
+      Map<Label, ArtifactLocation> javaPackageManifests) {
+
+    ManifestFilePackageReader manifestFilePackageReader =
+        Scope.push(
+            context,
+            (childContext) -> {
+              childContext.push(new TimingScope("ReadPackageManifests"));
+              Map<Label, Map<String, String>> manifestMap =
+                  PackageManifestReader.getInstance()
+                      .readPackageManifestFiles(
+                          project,
+                          childContext,
+                          artifactLocationDecoder,
+                          javaPackageManifests,
+                          packageReaderExecutorService);
+              return new ManifestFilePackageReader(manifestMap);
+            });
+
+    final List<JavaPackageReader> javaPackageReaders =
+        Lists.newArrayList(
+            manifestFilePackageReader,
+            JavaSourcePackageReader.getInstance(),
+            generatedFileJavaPackageReader);
+
+    Collection<SourceArtifact> nonGeneratedSources = filterGeneratedArtifacts(sources);
+
+    // Sort artifacts and excludes into their respective workspace paths
+    Multimap<WorkspacePath, SourceArtifact> sourcesUnderDirectoryRoot =
+        sortArtifactLocationsByRootDirectory(context, rootDirectories, nonGeneratedSources);
+
+    List<BlazeContentEntry> result = Lists.newArrayList();
+    Scope.push(
+        context,
+        (childContext) -> {
+          childContext.push(new TimingScope("CalculateSourceDirectories"));
+          for (WorkspacePath workspacePath : rootDirectories) {
+            File contentRoot = workspaceRoot.fileForPath(workspacePath);
+            ImmutableList<BlazeSourceDirectory> sourceDirectories =
+                calculateSourceDirectoriesForContentRoot(
+                    context,
+                    sourceTestConfig,
+                    workspaceRoot,
+                    workspacePath,
+                    sourcesUnderDirectoryRoot.get(workspacePath),
+                    javaPackageReaders);
+            if (!sourceDirectories.isEmpty()) {
+              result.add(new BlazeContentEntry(contentRoot, sourceDirectories));
+            }
+          }
+          Collections.sort(result, (lhs, rhs) -> lhs.contentRoot.compareTo(rhs.contentRoot));
+        });
+    return ImmutableList.copyOf(result);
+  }
+
+  private Collection<SourceArtifact> filterGeneratedArtifacts(
+      Collection<SourceArtifact> artifactLocations) {
+    return artifactLocations
+        .stream()
+        .filter(sourceArtifact -> sourceArtifact.artifactLocation.isSource())
+        .collect(Collectors.toList());
+  }
+
+  private static Multimap<WorkspacePath, SourceArtifact> sortArtifactLocationsByRootDirectory(
+      BlazeContext context,
+      Collection<WorkspacePath> rootDirectories,
+      Collection<SourceArtifact> sources) {
+
+    Multimap<WorkspacePath, SourceArtifact> result = ArrayListMultimap.create();
+
+    for (SourceArtifact sourceArtifact : sources) {
+      WorkspacePath foundWorkspacePath =
+          rootDirectories
+              .stream()
+              .filter(
+                  rootDirectory ->
+                      isUnderRootDirectory(
+                          rootDirectory, sourceArtifact.artifactLocation.getRelativePath()))
+              .findFirst()
+              .orElse(null);
+
+      if (foundWorkspacePath != null) {
+        result.put(foundWorkspacePath, sourceArtifact);
+      } else if (sourceArtifact.artifactLocation.isSource()) {
+        File sourceFile = sourceArtifact.artifactLocation.getFile();
+        String message =
+            String.format(
+                "Did not add %s. You're probably using a java file from outside the workspace"
+                    + " that has been exported using export_files. Don't do that.",
+                sourceFile);
+        IssueOutput.warn(message).inFile(sourceFile).submit(context);
+      }
+    }
+    return result;
+  }
+
+  private static boolean isUnderRootDirectory(WorkspacePath rootDirectory, String relativePath) {
+    if (rootDirectory.isWorkspaceRoot()) {
+      return true;
+    }
+    String rootDirectoryString = rootDirectory.toString();
+    return relativePath.startsWith(rootDirectoryString)
+        && (relativePath.length() == rootDirectoryString.length()
+            || (relativePath.charAt(rootDirectoryString.length()) == '/'));
+  }
+
+  /** Calculates all source directories for a single content root. */
+  private ImmutableList<BlazeSourceDirectory> calculateSourceDirectoriesForContentRoot(
+      BlazeContext context,
+      SourceTestConfig sourceTestConfig,
+      WorkspaceRoot workspaceRoot,
+      WorkspacePath directoryRoot,
+      Collection<SourceArtifact> sourceArtifacts,
+      Collection<JavaPackageReader> javaPackageReaders) {
+
+    // Split out java files
+    List<SourceArtifact> javaArtifacts = Lists.newArrayList();
+    for (SourceArtifact sourceArtifact : sourceArtifacts) {
+      if (isJavaFile(sourceArtifact.artifactLocation)) {
+        javaArtifacts.add(sourceArtifact);
+      }
+    }
+
+    List<BlazeSourceDirectory> result = Lists.newArrayList();
+
+    // Add java source directories
+    calculateJavaSourceDirectories(
+        context,
+        workspaceRoot,
+        directoryRoot,
+        sourceTestConfig,
+        javaArtifacts,
+        javaPackageReaders,
+        result);
+
+    Collections.sort(result, BlazeSourceDirectory.COMPARATOR);
+    return ImmutableList.copyOf(result);
+  }
+
+  /** Adds the java source directories. */
+  private void calculateJavaSourceDirectories(
+      BlazeContext context,
+      WorkspaceRoot workspaceRoot,
+      WorkspacePath directoryRoot,
+      SourceTestConfig sourceTestConfig,
+      Collection<SourceArtifact> javaArtifacts,
+      Collection<JavaPackageReader> javaPackageReaders,
+      Collection<BlazeSourceDirectory> result) {
+
+    List<SourceRoot> sourceRootsPerFile = Lists.newArrayList();
+
+    // Get java sources
+    List<ListenableFuture<SourceRoot>> sourceRootFutures = Lists.newArrayList();
+    for (final SourceArtifact sourceArtifact : javaArtifacts) {
+      ListenableFuture<SourceRoot> future =
+          executorService.submit(
+              () -> sourceRootForJavaSource(context, sourceArtifact, javaPackageReaders));
+      sourceRootFutures.add(future);
+    }
+    try {
+      for (SourceRoot sourceRoot : Futures.allAsList(sourceRootFutures).get()) {
+        if (sourceRoot != null) {
+          sourceRootsPerFile.add(sourceRoot);
+        }
+      }
+    } catch (ExecutionException | InterruptedException e) {
+      LOG.error(e);
+      throw new IllegalStateException("Could not read sources");
+    }
+
+    // Sort source roots into their respective directories
+    Multimap<WorkspacePath, SourceRoot> sourceDirectoryToSourceRoots = HashMultimap.create();
+    for (SourceRoot sourceRoot : sourceRootsPerFile) {
+      sourceDirectoryToSourceRoots.put(sourceRoot.workspacePath, sourceRoot);
+    }
+
+    // Create a mapping from directory to package prefix
+    Map<WorkspacePath, SourceRoot> workspacePathToSourceRoot = Maps.newHashMap();
+    for (WorkspacePath workspacePath : sourceDirectoryToSourceRoots.keySet()) {
+      Collection<SourceRoot> sources = sourceDirectoryToSourceRoots.get(workspacePath);
+      Multiset<String> packages = HashMultiset.create();
+
+      for (SourceRoot source : sources) {
+        packages.add(source.packagePrefix);
+      }
+
+      final String directoryPackagePrefix;
+      // Common case -- all source files agree on a single package
+      if (packages.elementSet().size() == 1) {
+        directoryPackagePrefix = packages.elementSet().iterator().next();
+      } else {
+        String preferredPackagePrefix = PackagePrefixCalculator.packagePrefixOf(workspacePath);
+        directoryPackagePrefix = pickMostFrequentlyOccurring(packages, preferredPackagePrefix);
+      }
+
+      SourceRoot candidateRoot = new SourceRoot(workspacePath, directoryPackagePrefix);
+      workspacePathToSourceRoot.put(workspacePath, candidateRoot);
+    }
+
+    // Add content entry base if it doesn't exist
+    if (!workspacePathToSourceRoot.containsKey(directoryRoot)) {
+      SourceRoot candidateRoot =
+          new SourceRoot(directoryRoot, PackagePrefixCalculator.packagePrefixOf(directoryRoot));
+      workspacePathToSourceRoot.put(directoryRoot, candidateRoot);
+    }
+
+    // First, create a graph of the directory structure from root to each source file
+    Map<WorkspacePath, SourceRootDirectoryNode> sourceRootDirectoryNodeMap = Maps.newHashMap();
+    SourceRootDirectoryNode rootNode = new SourceRootDirectoryNode(directoryRoot, null);
+    sourceRootDirectoryNodeMap.put(directoryRoot, rootNode);
+    for (SourceRoot sourceRoot : workspacePathToSourceRoot.values()) {
+      final String sourcePathRelativeToDirectoryRoot =
+          sourcePathRelativeToDirectoryRoot(directoryRoot, sourceRoot.workspacePath);
+      List<String> pathComponents =
+          !Strings.isNullOrEmpty(sourcePathRelativeToDirectoryRoot)
+              ? PATH_SPLITTER.splitToList(sourcePathRelativeToDirectoryRoot)
+              : ImmutableList.of();
+      SourceRootDirectoryNode previousNode = rootNode;
+      for (int i = 0; i < pathComponents.size(); ++i) {
+        final WorkspacePath workspacePath =
+            getWorkspacePathFromPathComponents(directoryRoot, pathComponents, i + 1);
+        SourceRootDirectoryNode node = sourceRootDirectoryNodeMap.get(workspacePath);
+        if (node == null) {
+          node = new SourceRootDirectoryNode(workspacePath, pathComponents.get(i));
+          sourceRootDirectoryNodeMap.put(workspacePath, node);
+          previousNode.children.add(node);
+        }
+        previousNode = node;
+      }
+    }
+
+    // Add package prefix votes at each directory node
+    for (SourceRoot sourceRoot : workspacePathToSourceRoot.values()) {
+      final String sourcePathRelativeToDirectoryRoot =
+          sourcePathRelativeToDirectoryRoot(directoryRoot, sourceRoot.workspacePath);
+
+      List<String> packageComponents = PACKAGE_SPLITTER.splitToList(sourceRoot.packagePrefix);
+      List<String> pathComponents =
+          !Strings.isNullOrEmpty(sourcePathRelativeToDirectoryRoot)
+              ? PATH_SPLITTER.splitToList(sourcePathRelativeToDirectoryRoot)
+              : ImmutableList.of();
+      int packageIndex = packageComponents.size();
+      int pathIndex = pathComponents.size();
+      while (pathIndex >= 0 && packageIndex >= 0) {
+        final WorkspacePath workspacePath =
+            getWorkspacePathFromPathComponents(directoryRoot, pathComponents, pathIndex);
+
+        SourceRootDirectoryNode node = sourceRootDirectoryNodeMap.get(workspacePath);
+
+        String packagePrefix = PACKAGE_JOINER.join(packageComponents.subList(0, packageIndex));
+
+        // If this is the source root containing Java files, we *have* to pick its package prefix
+        // Otherwise just add a vote
+        if (sourceRoot.workspacePath.equals(workspacePath)) {
+          node.forcedPackagePrefix = packagePrefix;
+        } else {
+          node.packagePrefixVotes.add(packagePrefix);
+        }
+
+        String pathComponent = pathIndex > 0 ? pathComponents.get(pathIndex - 1) : "";
+        String packageComponent = packageIndex > 0 ? packageComponents.get(packageIndex - 1) : "";
+        if (!pathComponent.equals(packageComponent)) {
+          break;
+        }
+
+        --packageIndex;
+        --pathIndex;
+      }
+    }
+
+    Map<WorkspacePath, SourceRoot> sourceRoots = Maps.newHashMap();
+    SourceRootDirectoryNode root = sourceRootDirectoryNodeMap.get(directoryRoot);
+    visitDirectoryNode(sourceRoots, root, null);
+
+    for (SourceRoot sourceRoot : sourceRoots.values()) {
+      result.add(
+          BlazeSourceDirectory.builder(workspaceRoot.fileForPath(sourceRoot.workspacePath))
+              .setPackagePrefix(sourceRoot.packagePrefix)
+              .setTest(sourceTestConfig.isTestSource(sourceRoot.workspacePath.relativePath()))
+              .setGenerated(false)
+              .build());
+    }
+  }
+
+  private static String sourcePathRelativeToDirectoryRoot(
+      WorkspacePath directoryRoot, WorkspacePath workspacePath) {
+    int directoryRootLength = directoryRoot.relativePath().length();
+    String relativePath = workspacePath.relativePath();
+    final String relativeSourcePath;
+    if (relativePath.length() > directoryRootLength) {
+      if (directoryRootLength > 0) {
+        relativeSourcePath = relativePath.substring(directoryRootLength + 1);
+      } else {
+        relativeSourcePath = relativePath;
+      }
+    } else {
+      relativeSourcePath = "";
+    }
+    return relativeSourcePath;
+  }
+
+  private static WorkspacePath getWorkspacePathFromPathComponents(
+      WorkspacePath directoryRoot, List<String> pathComponents, int pathIndex) {
+    String directoryRootRelativePath = PATH_JOINER.join(pathComponents.subList(0, pathIndex));
+    final WorkspacePath workspacePath;
+    if (directoryRootRelativePath.isEmpty()) {
+      workspacePath = directoryRoot;
+    } else if (directoryRoot.isWorkspaceRoot()) {
+      workspacePath = new WorkspacePath(directoryRootRelativePath);
+    } else {
+      workspacePath =
+          new WorkspacePath(
+              PATH_JOINER.join(directoryRoot.relativePath(), directoryRootRelativePath));
+    }
+    return workspacePath;
+  }
+
+  private static void visitDirectoryNode(
+      Map<WorkspacePath, SourceRoot> sourceRoots,
+      SourceRootDirectoryNode node,
+      @Nullable String parentCompatiblePackagePrefix) {
+    String packagePrefix =
+        node.forcedPackagePrefix != null
+            ? node.forcedPackagePrefix
+            : pickMostFrequentlyOccurring(
+                node.packagePrefixVotes,
+                PackagePrefixCalculator.packagePrefixOf(node.workspacePath));
+    packagePrefix = packagePrefix != null ? packagePrefix : parentCompatiblePackagePrefix;
+    if (packagePrefix != null && !packagePrefix.equals(parentCompatiblePackagePrefix)) {
+      sourceRoots.put(node.workspacePath, new SourceRoot(node.workspacePath, packagePrefix));
+    }
+    for (SourceRootDirectoryNode child : node.children) {
+      String compatiblePackagePrefix = null;
+      if (packagePrefix != null) {
+        compatiblePackagePrefix =
+            Strings.isNullOrEmpty(packagePrefix)
+                ? child.directoryName
+                : packagePrefix + "." + child.directoryName;
+      }
+      visitDirectoryNode(sourceRoots, child, compatiblePackagePrefix);
+    }
+  }
+
+  @Nullable
+  private static <T> T pickMostFrequentlyOccurring(Multiset<T> set, String prefer) {
+    T best = null;
+    int bestCount = 0;
+
+    for (T candidate : set.elementSet()) {
+      int candidateCount = set.count(candidate);
+      if (candidateCount > bestCount || (candidateCount == bestCount && candidate.equals(prefer))) {
+        best = candidate;
+        bestCount = candidateCount;
+      }
+    }
+    return best;
+  }
+
+  @Nullable
+  private static SourceRoot sourceRootForJavaSource(
+      BlazeContext context,
+      SourceArtifact sourceArtifact,
+      Collection<JavaPackageReader> javaPackageReaders) {
+
+    File javaFile = sourceArtifact.artifactLocation.getFile();
+
+    String declaredPackage = null;
+    for (JavaPackageReader reader : javaPackageReaders) {
+      declaredPackage = reader.getDeclaredPackageOfJavaFile(context, sourceArtifact);
+      if (declaredPackage != null) {
+        break;
+      }
+    }
+    if (declaredPackage == null) {
+      IssueOutput.warn("Failed to inspect the package name of java source file: " + javaFile)
+          .inFile(javaFile)
+          .submit(context);
+      return null;
+    }
+    return new SourceRoot(
+        new WorkspacePath(new File(sourceArtifact.artifactLocation.getRelativePath()).getParent()),
+        declaredPackage);
+  }
+
+  static class SourceRoot {
+    final WorkspacePath workspacePath;
+    final String packagePrefix;
+
+    public SourceRoot(WorkspacePath workspacePath, String packagePrefix) {
+      this.workspacePath = workspacePath;
+      this.packagePrefix = packagePrefix;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      SourceRoot that = (SourceRoot) o;
+      return Objects.equal(workspacePath, that.workspacePath)
+          && Objects.equal(packagePrefix, that.packagePrefix);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hashCode(workspacePath, packagePrefix);
+    }
+
+    @Override
+    public String toString() {
+      return "SourceRoot {"
+          + '\n'
+          + "  workspacePath: "
+          + workspacePath
+          + '\n'
+          + "  packagePrefix: "
+          + packagePrefix
+          + '\n'
+          + '}';
+    }
+  }
+
+  static class SourceRootDirectoryNode {
+    final WorkspacePath workspacePath;
+    @Nullable final String directoryName;
+    final Set<SourceRootDirectoryNode> children = Sets.newHashSet();
+    final Multiset<String> packagePrefixVotes = HashMultiset.create();
+    String forcedPackagePrefix;
+
+    public SourceRootDirectoryNode(WorkspacePath workspacePath, @Nullable String directoryName) {
+      this.workspacePath = workspacePath;
+      this.directoryName = directoryName;
+    }
+  }
+
+  private static boolean isJavaFile(ArtifactLocation artifactLocation) {
+    return artifactLocation.getRelativePath().endsWith(".java");
+  }
+}
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
new file mode 100644
index 0000000..0f40445
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/sync/workingset/JavaWorkingSet.java
@@ -0,0 +1,84 @@
+/*
+ * 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.workingset;
+
+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.model.primitives.WorkspacePath;
+import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
+import com.google.idea.blaze.base.sync.workspace.WorkingSet;
+import java.util.Set;
+
+/**
+ * Computes the working set of files of directories from source control.
+ *
+ * <p>The working set is: - All new untracked directories (git only) - All modified BUILD files -
+ * All modified java files
+ *
+ * <p>A rule is considered part of the working set if any of the following is true: - Its BUILD file
+ * is modified - Its BUILD file is under a new directory - Any of its java files are modified - Any
+ * of its java files are under a new directory
+ *
+ * <p>Rules in the working set get an expanded classpath of their direct deps, i.e. they temporarily
+ * defeat classpath reduction.
+ */
+public class JavaWorkingSet {
+  private final Set<String> modifiedBuildFileRelativePaths;
+  private final Set<String> modifiedJavaFileRelativePaths;
+
+  public JavaWorkingSet(WorkspaceRoot workspaceRoot, WorkingSet workingSet) {
+    Set<String> modifiedBuildFileRelativePaths = Sets.newHashSet();
+    Set<String> modifiedJavaFileRelativePaths = Sets.newHashSet();
+
+    for (WorkspacePath workspacePath :
+        Iterables.concat(workingSet.addedFiles, workingSet.modifiedFiles)) {
+      if (workspaceRoot.fileForPath(workspacePath).getName().equals("BUILD")) {
+        modifiedBuildFileRelativePaths.add(workspacePath.relativePath());
+      } else if (workspacePath.relativePath().endsWith(".java")) {
+        modifiedJavaFileRelativePaths.add(workspacePath.relativePath());
+      }
+    }
+
+    this.modifiedBuildFileRelativePaths = modifiedBuildFileRelativePaths;
+    this.modifiedJavaFileRelativePaths = modifiedJavaFileRelativePaths;
+  }
+
+  public boolean isRuleInWorkingSet(RuleIdeInfo ruleIdeInfo) {
+    ArtifactLocation buildFile = ruleIdeInfo.buildFile;
+    if (buildFile != null) {
+      if (modifiedBuildFileRelativePaths.contains(buildFile.getRelativePath())) {
+        return true;
+      }
+    }
+
+    for (ArtifactLocation artifactLocation : ruleIdeInfo.sources) {
+      if (isInWorkingSet(artifactLocation)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  public boolean isInWorkingSet(ArtifactLocation artifactLocation) {
+    return isInWorkingSet(artifactLocation.getRelativePath());
+  }
+
+  boolean isInWorkingSet(String relativePath) {
+    return modifiedJavaFileRelativePaths.contains(relativePath);
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/syncstatus/BlazeJavaSyncStatusClassNodeDecorator.java b/java/src/com/google/idea/blaze/java/syncstatus/BlazeJavaSyncStatusClassNodeDecorator.java
new file mode 100644
index 0000000..6ffabc2
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/syncstatus/BlazeJavaSyncStatusClassNodeDecorator.java
@@ -0,0 +1,60 @@
+/*
+ * 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.syncstatus;
+
+import com.intellij.ide.projectView.PresentationData;
+import com.intellij.ide.projectView.ProjectViewNode;
+import com.intellij.ide.projectView.ProjectViewNodeDecorator;
+import com.intellij.ide.projectView.impl.nodes.ClassTreeNode;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packageDependencies.ui.PackageDependenciesNode;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiFile;
+import com.intellij.ui.ColoredTreeCellRenderer;
+import com.intellij.ui.SimpleTextAttributes;
+
+/** Grays out any unreachable java classes. */
+public class BlazeJavaSyncStatusClassNodeDecorator implements ProjectViewNodeDecorator {
+  @Override
+  public void decorate(ProjectViewNode node, PresentationData data) {
+    if (!(node instanceof ClassTreeNode)) {
+      return;
+    }
+    PsiClass psiClass = ((ClassTreeNode) node).getPsiClass();
+    if (psiClass == null) {
+      return;
+    }
+    PsiFile psiFile = psiClass.getContainingFile();
+    if (psiFile == null) {
+      return;
+    }
+    VirtualFile virtualFile = psiFile.getVirtualFile();
+    if (virtualFile == null) {
+      return;
+    }
+
+    Project project = node.getProject();
+    if (SyncStatusHelper.isUnsynced(project, virtualFile)) {
+      data.clearText();
+      data.addText(psiClass.getName(), SimpleTextAttributes.GRAY_ATTRIBUTES);
+      data.addText(" (unsynced)", SimpleTextAttributes.GRAY_ATTRIBUTES);
+    }
+  }
+
+  @Override
+  public void decorate(PackageDependenciesNode node, ColoredTreeCellRenderer cellRenderer) {}
+}
diff --git a/java/src/com/google/idea/blaze/java/syncstatus/BlazeJavaSyncStatusEditorTabColorProvider.java b/java/src/com/google/idea/blaze/java/syncstatus/BlazeJavaSyncStatusEditorTabColorProvider.java
new file mode 100644
index 0000000..ad8e396
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/syncstatus/BlazeJavaSyncStatusEditorTabColorProvider.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.java.syncstatus;
+
+import com.intellij.openapi.fileEditor.impl.EditorTabColorProvider;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.ui.JBColor;
+import java.awt.Color;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/** Changes the color for unsynced files. */
+public class BlazeJavaSyncStatusEditorTabColorProvider implements EditorTabColorProvider {
+  private static final JBColor UNSYNCED_COLOR =
+      new JBColor(new Color(252, 234, 234), new Color(121, 105, 105));
+
+  @Nullable
+  @Override
+  public Color getEditorTabColor(@NotNull Project project, @NotNull VirtualFile file) {
+    if (file.getName().endsWith(".java") && SyncStatusHelper.isUnsynced(project, file)) {
+      return UNSYNCED_COLOR;
+    }
+    return null;
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/syncstatus/BlazeJavaSyncStatusEditorTabTitleProvider.java b/java/src/com/google/idea/blaze/java/syncstatus/BlazeJavaSyncStatusEditorTabTitleProvider.java
new file mode 100644
index 0000000..fce5c89
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/syncstatus/BlazeJavaSyncStatusEditorTabTitleProvider.java
@@ -0,0 +1,33 @@
+/*
+ * 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.syncstatus;
+
+import com.intellij.openapi.fileEditor.impl.EditorTabTitleProvider;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.Nullable;
+
+/** Changes the tab title for unsynced files. */
+public class BlazeJavaSyncStatusEditorTabTitleProvider implements EditorTabTitleProvider {
+  @Nullable
+  @Override
+  public String getEditorTabTitle(Project project, VirtualFile file) {
+    if (file.getName().endsWith("java") && SyncStatusHelper.isUnsynced(project, file)) {
+      return file.getPresentableName() + " (unsynced)";
+    }
+    return null;
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/syncstatus/SyncStatusHelper.java b/java/src/com/google/idea/blaze/java/syncstatus/SyncStatusHelper.java
new file mode 100644
index 0000000..2ea5884
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/syncstatus/SyncStatusHelper.java
@@ -0,0 +1,43 @@
+/*
+ * 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.syncstatus;
+
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
+import com.google.idea.blaze.java.sync.model.BlazeJavaSyncData;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import java.io.File;
+
+class SyncStatusHelper {
+  static boolean isUnsynced(Project project, VirtualFile virtualFile) {
+    BlazeProjectData blazeProjectData =
+        BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
+    if (blazeProjectData == null) {
+      return false;
+    }
+    BlazeJavaSyncData syncData = blazeProjectData.syncState.get(BlazeJavaSyncData.class);
+    if (syncData == null) {
+      return false;
+    }
+    if (!virtualFile.isInLocalFileSystem()) {
+      return false;
+    }
+
+    File file = new File(virtualFile.getPath());
+    return !syncData.importResult.javaSourceFiles.contains(file);
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/ui/BlazeIntelliJProblemsView.java b/java/src/com/google/idea/blaze/java/ui/BlazeIntelliJProblemsView.java
new file mode 100644
index 0000000..a2f0582
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/ui/BlazeIntelliJProblemsView.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.java.ui;
+
+import com.google.idea.blaze.base.scope.output.IssueOutput;
+import com.google.idea.blaze.base.ui.BlazeProblemsView;
+import com.intellij.compiler.CompilerMessageImpl;
+import com.intellij.compiler.ProblemsView;
+import com.intellij.openapi.compiler.CompilerMessageCategory;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import java.util.UUID;
+
+class BlazeIntelliJProblemsView implements BlazeProblemsView {
+  private final Project project;
+
+  private BlazeIntelliJProblemsView(Project project) {
+    this.project = project;
+  }
+
+  @Override
+  public void clearOldMessages(UUID sessionId) {
+    ProblemsView.SERVICE.getInstance(project).clearOldMessages(null, sessionId);
+  }
+
+  @Override
+  public void addMessage(IssueOutput issue, UUID sessionId) {
+    VirtualFile virtualFile =
+        issue.getFile() != null
+            ? VfsUtil.findFileByIoFile(issue.getFile(), true /* refresh */)
+            : null;
+    CompilerMessageCategory category =
+        issue.getCategory() == IssueOutput.Category.ERROR
+            ? CompilerMessageCategory.ERROR
+            : CompilerMessageCategory.WARNING;
+    CompilerMessageImpl message =
+        new CompilerMessageImpl(
+            project,
+            category,
+            issue.getMessage(),
+            virtualFile,
+            issue.getLine(),
+            issue.getColumn(),
+            issue.getNavigatable());
+    ProblemsView.SERVICE.getInstance(project).addMessage(message, sessionId);
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/wizard2/BlazeEditProjectViewImportWizardStep.java b/java/src/com/google/idea/blaze/java/wizard2/BlazeEditProjectViewImportWizardStep.java
new file mode 100644
index 0000000..4a6bc6a
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/wizard2/BlazeEditProjectViewImportWizardStep.java
@@ -0,0 +1,102 @@
+/*
+ * 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.wizard2;
+
+import com.google.idea.blaze.base.ui.BlazeValidationResult;
+import com.google.idea.blaze.base.wizard2.BlazeNewProjectBuilder;
+import com.google.idea.blaze.base.wizard2.BlazeProjectCommitException;
+import com.google.idea.blaze.base.wizard2.ui.BlazeEditProjectViewControl;
+import com.intellij.ide.util.projectWizard.WizardContext;
+import com.intellij.ide.wizard.CommitStepException;
+import com.intellij.openapi.options.ConfigurationException;
+import com.intellij.projectImport.ProjectImportWizardStep;
+import java.awt.BorderLayout;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import org.jetbrains.annotations.NotNull;
+
+/** Shows the edit project view screen. */
+class BlazeEditProjectViewImportWizardStep extends ProjectImportWizardStep {
+
+  private final JPanel component = new JPanel(new BorderLayout());
+  private BlazeEditProjectViewControl control;
+  private boolean settingsInitialised;
+
+  public BlazeEditProjectViewImportWizardStep(@NotNull WizardContext context) {
+    super(context);
+  }
+
+  @Override
+  public JComponent getComponent() {
+    return component;
+  }
+
+  @Override
+  public void updateStep() {
+    if (!settingsInitialised) {
+      init();
+    } else {
+      control.update(getProjectBuilder());
+    }
+  }
+
+  private void init() {
+    control =
+        new BlazeEditProjectViewControl(getProjectBuilder(), getWizardContext().getDisposable());
+    this.component.add(control.getUiComponent());
+    settingsInitialised = true;
+  }
+
+  @Override
+  public boolean validate() throws ConfigurationException {
+    BlazeValidationResult validationResult = control.validate();
+    if (validationResult.error != null) {
+      throw new ConfigurationException(validationResult.error.getError());
+    }
+    return validationResult.success;
+  }
+
+  @Override
+  public void updateDataModel() {
+    BlazeNewProjectBuilder builder = getProjectBuilder();
+    control.updateBuilder(builder);
+
+    WizardContext wizardContext = getWizardContext();
+    wizardContext.setProjectName(builder.getProjectName());
+    wizardContext.setProjectFileDirectory(builder.getProjectDataDirectory());
+  }
+
+  @Override
+  public void onWizardFinished() throws CommitStepException {
+    try {
+      getProjectBuilder().commit();
+    } catch (BlazeProjectCommitException e) {
+      throw new CommitStepException(e.getMessage());
+    }
+  }
+
+  @Override
+  public String getHelpId() {
+    return "docs/project-views";
+  }
+
+  private BlazeNewProjectBuilder getProjectBuilder() {
+    BlazeProjectImportBuilder builder =
+        (BlazeProjectImportBuilder) getWizardContext().getProjectBuilder();
+    assert builder != null;
+    return builder.builder();
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/wizard2/BlazeImportProjectAction.java b/java/src/com/google/idea/blaze/java/wizard2/BlazeImportProjectAction.java
new file mode 100644
index 0000000..273a4ff
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/wizard2/BlazeImportProjectAction.java
@@ -0,0 +1,42 @@
+/*
+ * 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.wizard2;
+
+import com.google.idea.blaze.base.settings.Blaze;
+import com.intellij.ide.impl.NewProjectUtil;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+
+class BlazeImportProjectAction extends AnAction {
+  @Override
+  public void actionPerformed(AnActionEvent e) {
+    BlazeNewProjectWizard wizard =
+        new BlazeNewProjectWizard(
+            new BlazeNewProjectImportProvider(new BlazeProjectImportBuilder()));
+    if (!wizard.showAndGet()) {
+      return;
+    }
+    //noinspection ConstantConditions
+    NewProjectUtil.createFromWizard(wizard, null);
+  }
+
+  @Override
+  public void update(AnActionEvent e) {
+    super.update(e);
+    e.getPresentation()
+        .setText(String.format("Import %s Project...", Blaze.defaultBuildSystemName()));
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/wizard2/BlazeNewProjectImportProvider.java b/java/src/com/google/idea/blaze/java/wizard2/BlazeNewProjectImportProvider.java
new file mode 100644
index 0000000..d73e681
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/wizard2/BlazeNewProjectImportProvider.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.java.wizard2;
+
+import com.intellij.ide.util.projectWizard.ModuleWizardStep;
+import com.intellij.ide.util.projectWizard.WizardContext;
+import com.intellij.projectImport.ProjectImportProvider;
+
+/** The import provider for the Blaze plugin. */
+class BlazeNewProjectImportProvider extends ProjectImportProvider {
+
+  public BlazeNewProjectImportProvider(BlazeProjectImportBuilder builder) {
+    super(builder);
+  }
+
+  @Override
+  public ModuleWizardStep[] createSteps(WizardContext context) {
+    return new ModuleWizardStep[] {
+      new BlazeSelectWorkspaceImportWizardStep(context),
+      new BlazeSelectBuildSystemBinaryStep(context),
+      new BlazeSelectProjectViewImportWizardStep(context),
+      new BlazeEditProjectViewImportWizardStep(context)
+    };
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/wizard2/BlazeNewProjectWizard.java b/java/src/com/google/idea/blaze/java/wizard2/BlazeNewProjectWizard.java
new file mode 100644
index 0000000..62be22e
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/wizard2/BlazeNewProjectWizard.java
@@ -0,0 +1,54 @@
+/*
+ * 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.wizard2;
+
+import com.google.idea.blaze.base.help.BlazeHelpHandler;
+import com.intellij.ide.util.newProjectWizard.AddModuleWizard;
+import com.intellij.projectImport.ProjectImportProvider;
+import java.awt.event.ActionListener;
+import org.jetbrains.annotations.Nullable;
+
+final class BlazeNewProjectWizard extends AddModuleWizard {
+  public BlazeNewProjectWizard(ProjectImportProvider provider) {
+    super(null, null, provider);
+  }
+
+  @Override
+  protected String getDimensionServiceKey() {
+    return null; // No dimension service
+  }
+
+  @Override
+  protected void helpAction() {
+    doHelpAction();
+  }
+
+  @Override
+  protected void doHelpAction() {
+    String helpId = getHelpID();
+    BlazeHelpHandler helpHandler = BlazeHelpHandler.getInstance();
+    if (helpId != null && helpHandler != null) {
+      helpHandler.handleHelp(helpId);
+    }
+  }
+
+  // Swallow the escape key
+  @Nullable
+  @Override
+  protected ActionListener createCancelAction() {
+    return null;
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/wizard2/BlazeProjectImportBuilder.java b/java/src/com/google/idea/blaze/java/wizard2/BlazeProjectImportBuilder.java
new file mode 100644
index 0000000..30445a4
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/wizard2/BlazeProjectImportBuilder.java
@@ -0,0 +1,83 @@
+/*
+ * 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.wizard2;
+
+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.openapi.module.ModifiableModuleModel;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.projectRoots.JavaSdk;
+import com.intellij.openapi.projectRoots.SdkTypeId;
+import com.intellij.openapi.roots.ui.configuration.ModulesProvider;
+import com.intellij.packaging.artifacts.ModifiableArtifactModel;
+import com.intellij.projectImport.ProjectImportBuilder;
+import icons.BlazeIcons;
+import java.util.List;
+import javax.swing.Icon;
+import org.jetbrains.annotations.NotNull;
+
+/** Wrapper around a {@link BlazeNewProjectBuilder} to fit into IntelliJ's import framework. */
+class BlazeProjectImportBuilder extends ProjectImportBuilder<Void> {
+  private BlazeNewProjectBuilder builder = new BlazeNewProjectBuilder();
+
+  @NotNull
+  @Override
+  public String getName() {
+    return Blaze.defaultBuildSystemName();
+  }
+
+  @Override
+  public Icon getIcon() {
+    return BlazeIcons.Blaze;
+  }
+
+  @Override
+  public boolean isSuitableSdkType(SdkTypeId sdk) {
+    return sdk == JavaSdk.getInstance();
+  }
+
+  @Override
+  public List<Void> getList() {
+    return ImmutableList.of();
+  }
+
+  @Override
+  public boolean isMarked(Void element) {
+    return true;
+  }
+
+  @Override
+  public void setList(List<Void> gradleProjects) {}
+
+  @Override
+  public void setOpenProjectSettingsAfter(boolean on) {}
+
+  @Override
+  public List<Module> commit(
+      final Project project,
+      ModifiableModuleModel model,
+      ModulesProvider modulesProvider,
+      ModifiableArtifactModel artifactModel) {
+    builder.commitToProject(project);
+    return ImmutableList.of();
+  }
+
+  public BlazeNewProjectBuilder builder() {
+    return builder;
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/wizard2/BlazeSelectBuildSystemBinaryStep.java b/java/src/com/google/idea/blaze/java/wizard2/BlazeSelectBuildSystemBinaryStep.java
new file mode 100644
index 0000000..7b50624
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/wizard2/BlazeSelectBuildSystemBinaryStep.java
@@ -0,0 +1,93 @@
+/*
+ * 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.wizard2;
+
+import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
+import com.google.idea.blaze.base.settings.BlazeUserSettings;
+import com.google.idea.blaze.base.ui.BlazeValidationResult;
+import com.google.idea.blaze.base.wizard2.BlazeNewProjectBuilder;
+import com.google.idea.blaze.base.wizard2.ui.SelectBazelBinaryControl;
+import com.intellij.ide.util.projectWizard.WizardContext;
+import com.intellij.ide.wizard.CommitStepException;
+import com.intellij.openapi.options.ConfigurationException;
+import com.intellij.projectImport.ProjectImportWizardStep;
+import java.awt.BorderLayout;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import org.jetbrains.annotations.NotNull;
+
+class BlazeSelectBuildSystemBinaryStep extends ProjectImportWizardStep {
+
+  private final JPanel component = new JPanel(new BorderLayout());
+  private SelectBazelBinaryControl control;
+  private boolean settingsInitialized = false;
+
+  public BlazeSelectBuildSystemBinaryStep(@NotNull WizardContext context) {
+    super(context);
+  }
+
+  @Override
+  public boolean isStepVisible() {
+    updateStep();
+    if (control.builder.getBuildSystem() != BuildSystem.Bazel) {
+      return false;
+    }
+    String currentBinaryPath = BlazeUserSettings.getInstance().getBazelBinaryPath();
+    return currentBinaryPath == null;
+  }
+
+  @Override
+  public JComponent getComponent() {
+    return component;
+  }
+
+  @Override
+  public void updateStep() {
+    if (!settingsInitialized) {
+      init();
+    }
+  }
+
+  private void init() {
+    control = new SelectBazelBinaryControl(getProjectBuilder());
+    component.add(control.getUiComponent());
+    settingsInitialized = true;
+  }
+
+  @Override
+  public boolean validate() throws ConfigurationException {
+    BlazeValidationResult result = control.validate();
+    if (!result.success) {
+      throw new ConfigurationException(result.error.getError());
+    }
+    return true;
+  }
+
+  @Override
+  public void updateDataModel() {}
+
+  @Override
+  public void onWizardFinished() throws CommitStepException {
+    control.commit();
+  }
+
+  private BlazeNewProjectBuilder getProjectBuilder() {
+    BlazeProjectImportBuilder builder =
+        (BlazeProjectImportBuilder) getWizardContext().getProjectBuilder();
+    assert builder != null;
+    return builder.builder();
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/wizard2/BlazeSelectProjectViewImportWizardStep.java b/java/src/com/google/idea/blaze/java/wizard2/BlazeSelectProjectViewImportWizardStep.java
new file mode 100644
index 0000000..9978f07
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/wizard2/BlazeSelectProjectViewImportWizardStep.java
@@ -0,0 +1,90 @@
+/*
+ * 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.wizard2;
+
+import com.google.idea.blaze.base.ui.BlazeValidationResult;
+import com.google.idea.blaze.base.wizard2.BlazeNewProjectBuilder;
+import com.google.idea.blaze.base.wizard2.ui.BlazeSelectProjectViewControl;
+import com.intellij.ide.util.projectWizard.WizardContext;
+import com.intellij.ide.wizard.CommitStepException;
+import com.intellij.openapi.options.ConfigurationException;
+import com.intellij.projectImport.ProjectImportWizardStep;
+import java.awt.BorderLayout;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import org.jetbrains.annotations.NotNull;
+
+class BlazeSelectProjectViewImportWizardStep extends ProjectImportWizardStep {
+
+  private final JPanel component = new JPanel(new BorderLayout());
+  private BlazeSelectProjectViewControl control;
+  private boolean settingsInitialised;
+
+  public BlazeSelectProjectViewImportWizardStep(@NotNull WizardContext context) {
+    super(context);
+  }
+
+  @Override
+  public JComponent getComponent() {
+    return component;
+  }
+
+  @Override
+  public void updateStep() {
+    if (!settingsInitialised) {
+      init();
+    } else {
+      control.update(getProjectBuilder());
+    }
+  }
+
+  private void init() {
+    control = new BlazeSelectProjectViewControl(getProjectBuilder());
+    this.component.add(control.getUiComponent());
+    settingsInitialised = true;
+  }
+
+  @Override
+  public boolean validate() throws ConfigurationException {
+    BlazeValidationResult result = control.validate();
+    if (!result.success) {
+      throw new ConfigurationException(result.error.getError());
+    }
+    return true;
+  }
+
+  @Override
+  public void updateDataModel() {
+    control.updateBuilder(getProjectBuilder());
+  }
+
+  @Override
+  public void onWizardFinished() throws CommitStepException {
+    control.commit();
+  }
+
+  @Override
+  public String getHelpId() {
+    return "docs/project-views";
+  }
+
+  private BlazeNewProjectBuilder getProjectBuilder() {
+    BlazeProjectImportBuilder builder =
+        (BlazeProjectImportBuilder) getWizardContext().getProjectBuilder();
+    assert builder != null;
+    return builder.builder();
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/wizard2/BlazeSelectWorkspaceImportWizardStep.java b/java/src/com/google/idea/blaze/java/wizard2/BlazeSelectWorkspaceImportWizardStep.java
new file mode 100644
index 0000000..b5b5b6a
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/wizard2/BlazeSelectWorkspaceImportWizardStep.java
@@ -0,0 +1,88 @@
+/*
+ * 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.wizard2;
+
+import com.google.idea.blaze.base.ui.BlazeValidationResult;
+import com.google.idea.blaze.base.wizard2.BlazeNewProjectBuilder;
+import com.google.idea.blaze.base.wizard2.ui.BlazeSelectWorkspaceControl;
+import com.intellij.ide.util.projectWizard.WizardContext;
+import com.intellij.ide.wizard.CommitStepException;
+import com.intellij.openapi.options.ConfigurationException;
+import com.intellij.projectImport.ProjectImportWizardStep;
+import java.awt.BorderLayout;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import org.jetbrains.annotations.NotNull;
+
+class BlazeSelectWorkspaceImportWizardStep extends ProjectImportWizardStep {
+
+  private final JPanel component = new JPanel(new BorderLayout());
+  private BlazeSelectWorkspaceControl control;
+  private boolean settingsInitialised;
+
+  public BlazeSelectWorkspaceImportWizardStep(@NotNull WizardContext context) {
+    super(context);
+  }
+
+  @Override
+  public JComponent getComponent() {
+    return component;
+  }
+
+  @Override
+  public void updateStep() {
+    if (!settingsInitialised) {
+      init();
+    }
+  }
+
+  private void init() {
+    control = new BlazeSelectWorkspaceControl(getProjectBuilder());
+    this.component.add(control.getUiComponent());
+    settingsInitialised = true;
+  }
+
+  @Override
+  public boolean validate() throws ConfigurationException {
+    BlazeValidationResult result = control.validate();
+    if (!result.success) {
+      throw new ConfigurationException(result.error.getError());
+    }
+    return true;
+  }
+
+  @Override
+  public void updateDataModel() {
+    control.updateBuilder(getProjectBuilder());
+  }
+
+  @Override
+  public void onWizardFinished() throws CommitStepException {
+    control.commit();
+  }
+
+  @Override
+  public String getHelpId() {
+    return "docs/import-project";
+  }
+
+  private BlazeNewProjectBuilder getProjectBuilder() {
+    BlazeProjectImportBuilder builder =
+        (BlazeProjectImportBuilder) getWizardContext().getProjectBuilder();
+    assert builder != null;
+    return builder.builder();
+  }
+}
diff --git a/blaze-java/src/com/google/idea/blaze/java/wizard2/README b/java/src/com/google/idea/blaze/java/wizard2/README
similarity index 100%
rename from blaze-java/src/com/google/idea/blaze/java/wizard2/README
rename to java/src/com/google/idea/blaze/java/wizard2/README
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
new file mode 100644
index 0000000..7a1a64d
--- /dev/null
+++ b/java/tests/integrationtests/com/google/idea/blaze/java/lang/build/JavaClassRenameTest.java
@@ -0,0 +1,42 @@
+/*
+ * 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.lang.build;
+
+import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
+import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
+import com.intellij.psi.PsiJavaFile;
+import com.intellij.refactoring.rename.RenameProcessor;
+
+/** Tests that BUILD file references are correctly updated when performing rename refactors. */
+public class JavaClassRenameTest extends BuildFileIntegrationTestCase {
+
+  public void testRenameJavaClass() {
+    PsiJavaFile javaFile =
+        (PsiJavaFile)
+            createPsiFile(
+                "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 RenameProcessor(getProject(), javaFile.getClasses()[0], "NewName", false, false).run();
+
+    assertFileContents(buildFile, "java_library(name = \"ref2\", srcs = [\"NewName.java\"])");
+  }
+}
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
new file mode 100644
index 0000000..535932a
--- /dev/null
+++ b/java/tests/integrationtests/com/google/idea/blaze/java/lang/build/SafeDeleteTest.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.java.lang.build;
+
+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.util.PsiUtils;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.refactoring.BaseRefactoringProcessor;
+import com.intellij.refactoring.safeDelete.SafeDeleteHandler;
+
+/** Tests for the safe delete action which aren't covered by existing tests. */
+public class SafeDeleteTest extends BuildFileIntegrationTestCase {
+
+  public void testIndirectGlobReferencesNotIncluded() {
+    PsiFile javaFile =
+        createPsiFile("com/google/Test.java", "package com.google;", "public class Test {}");
+
+    PsiClass javaClass = PsiUtils.findFirstChildOfClassRecursive(javaFile, PsiClass.class);
+
+    BuildFile buildFile =
+        createBuildFile(
+            "com/google/BUILD",
+            "java_library(",
+            "    name = 'lib'",
+            "    srcs = glob(['*.java'])",
+            ")");
+
+    try {
+      SafeDeleteHandler.invoke(getProject(), new PsiElement[] {javaClass}, true);
+    } catch (BaseRefactoringProcessor.ConflictsInTestsException e) {
+      fail("Glob reference was incorrectly included");
+      return;
+    }
+  }
+
+  public void testDirectGlobReferencesIncluded() {
+    PsiFile javaFile =
+        createPsiFile("com/google/Test.java", "package com.google;", "public class Test {}");
+
+    PsiClass javaClass = PsiUtils.findFirstChildOfClassRecursive(javaFile, PsiClass.class);
+
+    BuildFile buildFile =
+        createBuildFile(
+            "com/google/BUILD",
+            "java_library(",
+            "    name = 'lib'",
+            "    srcs = glob(['Test.java'])",
+            ")");
+
+    try {
+      SafeDeleteHandler.invoke(getProject(), new PsiElement[] {javaClass}, true);
+    } catch (BaseRefactoringProcessor.ConflictsInTestsException expected) {
+      return;
+    }
+    fail("Expected an unsafe usage to be found");
+  }
+}
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
new file mode 100644
index 0000000..76dc9e1
--- /dev/null
+++ b/java/tests/integrationtests/com/google/idea/blaze/java/sync/JavaSyncTest.java
@@ -0,0 +1,139 @@
+/*
+ * 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 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.RuleMapBuilder;
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.model.RuleMap;
+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;
+import com.google.idea.blaze.base.sync.BlazeSyncParams.SyncMode;
+import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
+import com.google.idea.blaze.java.sync.model.BlazeContentEntry;
+import com.google.idea.blaze.java.sync.model.BlazeJavaSyncData;
+import com.google.idea.blaze.java.sync.model.BlazeSourceDirectory;
+import java.util.List;
+
+/** Java-specific sync integration tests. */
+public class JavaSyncTest extends BlazeSyncIntegrationTestCase {
+
+  public void testJavaClassesPresentInClassPath() throws Exception {
+    setProjectView(
+        "directories:",
+        "  java/com/google",
+        "targets:",
+        "  //java/com/google:lib",
+        "workspace_type: java");
+
+    createWorkspaceFile(
+        "java/com/google/ClassWithUniqueName1.java",
+        "package com.google;",
+        "public class ClassWithUniqueName1 {}");
+
+    createWorkspaceFile(
+        "java/com/google/ClassWithUniqueName2.java",
+        "package com.google;",
+        "public class ClassWithUniqueName2 {}");
+
+    RuleMap ruleMap =
+        RuleMapBuilder.builder()
+            .addRule(
+                RuleIdeInfo.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()))
+            .build();
+
+    setRuleMap(ruleMap);
+
+    BlazeSyncParams syncParams =
+        new BlazeSyncParams.Builder("Full Sync", BlazeSyncParams.SyncMode.FULL)
+            .addProjectViewTargets(true)
+            .build();
+    runBlazeSync(syncParams);
+
+    assertNoErrors();
+
+    BlazeProjectData blazeProjectData =
+        BlazeProjectDataManager.getInstance(getProject()).getBlazeProjectData();
+    assertThat(blazeProjectData).isNotNull();
+    assertThat(blazeProjectData.ruleMap).isEqualTo(ruleMap);
+    assertThat(blazeProjectData.workspaceLanguageSettings.getWorkspaceType())
+        .isEqualTo(WorkspaceType.JAVA);
+
+    BlazeJavaSyncData javaSyncData = blazeProjectData.syncState.get(BlazeJavaSyncData.class);
+    List<BlazeContentEntry> contentEntries = javaSyncData.importResult.contentEntries;
+    assertThat(contentEntries).hasSize(1);
+
+    BlazeContentEntry contentEntry = contentEntries.get(0);
+    assertThat(contentEntry.contentRoot.getPath())
+        .isEqualTo(tempDirectory.getPath() + "/java/com/google");
+    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");
+  }
+
+  public void testSimpleSync() throws Exception {
+    setProjectView(
+        "directories:",
+        "  java/com/google",
+        "targets:",
+        "  //java/com/google:lib",
+        "workspace_type: java");
+
+    createFile("java/com/google/Source.java", "package com.google;", "public class Source {}");
+
+    createFile("java/com/google/Other.java", "package com.google;", "public class Other {}");
+
+    RuleMap ruleMap =
+        RuleMapBuilder.builder()
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setBuildFile(sourceRoot("java/com/google/BUILD"))
+                    .setLabel("//java/com/google:lib")
+                    .setKind("java_library")
+                    .addSource(sourceRoot("java/com/google/Source.java"))
+                    .addSource(sourceRoot("java/com/google/Other.java")))
+            .build();
+
+    setRuleMap(ruleMap);
+
+    runBlazeSync(
+        new BlazeSyncParams.Builder("Sync", SyncMode.INCREMENTAL)
+            .addProjectViewTargets(true)
+            .build());
+
+    assertNoErrors();
+
+    BlazeProjectData blazeProjectData =
+        BlazeProjectDataManager.getInstance(getProject()).getBlazeProjectData();
+    assertThat(blazeProjectData).isNotNull();
+    assertThat(blazeProjectData.ruleMap).isEqualTo(ruleMap);
+    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
new file mode 100644
index 0000000..b3dd584
--- /dev/null
+++ b/java/tests/unittests/com/google/idea/blaze/java/run/BlazeJavaRunProfileStateTest.java
@@ -0,0 +1,160 @@
+/*
+ * 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.run;
+
+import static com.google.common.truth.Truth.assertThat;
+
+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.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.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.BlazeCommandRunConfiguration;
+import com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType;
+import com.google.idea.blaze.base.run.confighandler.BlazeCommandGenericRunConfigurationHandler;
+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.settings.Blaze.BuildSystem;
+import com.google.idea.blaze.base.settings.BlazeImportSettings;
+import com.google.idea.blaze.base.settings.BlazeImportSettingsManager;
+import com.google.idea.blaze.base.settings.BlazeUserSettings;
+import com.google.idea.common.experiments.ExperimentService;
+import com.google.idea.common.experiments.MockExperimentService;
+import com.intellij.openapi.extensions.impl.ExtensionPointImpl;
+import com.intellij.openapi.project.Project;
+import java.util.List;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link BlazeJavaRunProfileState}. */
+@RunWith(JUnit4.class)
+public class BlazeJavaRunProfileStateTest extends BlazeTestCase {
+
+  private static final BlazeImportSettings DUMMY_IMPORT_SETTINGS =
+      new BlazeImportSettings("", "", "", "", "", BuildSystem.Blaze);
+
+  private BlazeCommandRunConfiguration configuration;
+
+  @Override
+  protected void initTest(
+      @NotNull Container applicationServices, @NotNull Container projectServices) {
+    projectServices.register(
+        BlazeImportSettingsManager.class, new BlazeImportSettingsManager(project));
+    BlazeImportSettingsManager.getInstance(getProject()).setImportSettings(DUMMY_IMPORT_SETTINGS);
+
+    configuration =
+        new BlazeCommandRunConfigurationType().getFactory().createTemplateConfiguration(project);
+
+    ExperimentService experimentService = new MockExperimentService();
+    applicationServices.register(ExperimentService.class, experimentService);
+    applicationServices.register(RuleFinder.class, new MockRuleFinder());
+    applicationServices.register(BlazeUserSettings.class, new BlazeUserSettings());
+    registerExtensionPoint(BuildFlagsProvider.EP_NAME, BuildFlagsProvider.class);
+    ExtensionPointImpl<BlazeCommandRunConfigurationHandlerProvider> handlerProviderEp =
+        registerExtensionPoint(
+            BlazeCommandRunConfigurationHandlerProvider.EP_NAME,
+            BlazeCommandRunConfigurationHandlerProvider.class);
+    handlerProviderEp.registerExtension(new BlazeCommandGenericRunConfigurationHandlerProvider());
+  }
+
+  @Test
+  public void flagsShouldBeAppendedIfPresent() {
+    configuration.setTarget(new Label("//label:rule"));
+    BlazeCommandGenericRunConfigurationHandler handler =
+        (BlazeCommandGenericRunConfigurationHandler) configuration.getHandler();
+    handler.setCommand(BlazeCommandName.fromString("command"));
+    handler.setBlazeFlags(ImmutableList.of("--flag1", "--flag2"));
+    assertThat(
+            BlazeJavaRunProfileState.getBlazeCommand(
+                    project, configuration, ProjectViewSet.builder().build(), false /* debug */)
+                .toList())
+        .isEqualTo(
+            ImmutableList.of(
+                "/usr/bin/blaze",
+                "command",
+                BlazeFlags.getToolTagFlag(),
+                "--flag1",
+                "--flag2",
+                "--",
+                "//label:rule"));
+  }
+
+  @Test
+  public void debugFlagShouldBeIncludedForJavaTest() {
+    configuration.setTarget(new Label("//label:rule"));
+    BlazeCommandGenericRunConfigurationHandler handler =
+        (BlazeCommandGenericRunConfigurationHandler) configuration.getHandler();
+    handler.setCommand(BlazeCommandName.fromString("command"));
+    assertThat(
+            BlazeJavaRunProfileState.getBlazeCommand(
+                    project, configuration, ProjectViewSet.builder().build(), true /* debug */)
+                .toList())
+        .isEqualTo(
+            ImmutableList.of(
+                "/usr/bin/blaze",
+                "command",
+                BlazeFlags.getToolTagFlag(),
+                "--java_debug",
+                "--",
+                "//label:rule"));
+  }
+
+  @Test
+  public void debugFlagShouldBeIncludedForJavaBinary() {
+    configuration.setTarget(new Label("//label:java_binary_rule"));
+    BlazeCommandGenericRunConfigurationHandler handler =
+        (BlazeCommandGenericRunConfigurationHandler) configuration.getHandler();
+    handler.setCommand(BlazeCommandName.fromString("command"));
+    assertThat(
+            BlazeJavaRunProfileState.getBlazeCommand(
+                    project, configuration, ProjectViewSet.builder().build(), true /* debug */)
+                .toList())
+        .isEqualTo(
+            ImmutableList.of(
+                "/usr/bin/blaze",
+                "command",
+                BlazeFlags.getToolTagFlag(),
+                "--",
+                "//label:java_binary_rule",
+                "--debug"));
+  }
+
+  private static class MockRuleFinder extends RuleFinder {
+    @Override
+    public List<RuleIdeInfo> findRules(Project project, Predicate<RuleIdeInfo> 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")) {
+        builder.setKind(Kind.JAVA_BINARY);
+      } else {
+        builder.setKind(Kind.JAVA_TEST);
+      }
+      return builder.build();
+    }
+  }
+}
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
new file mode 100644
index 0000000..9b5aa31
--- /dev/null
+++ b/java/tests/unittests/com/google/idea/blaze/java/sync/importer/BlazeJavaWorkspaceImporterTest.java
@@ -0,0 +1,1424 @@
+/*
+ * 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.importer;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+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.ArtifactLocation;
+import com.google.idea.blaze.base.ideinfo.JavaRuleIdeInfo;
+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.RuleMapBuilder;
+import com.google.idea.blaze.base.ideinfo.Tags;
+import com.google.idea.blaze.base.model.primitives.ExecutionRootPath;
+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.prefetch.MockPrefetchService;
+import com.google.idea.blaze.base.prefetch.PrefetchService;
+import com.google.idea.blaze.base.projectview.ProjectView;
+import com.google.idea.blaze.base.projectview.ProjectViewSet;
+import com.google.idea.blaze.base.projectview.section.Glob;
+import com.google.idea.blaze.base.projectview.section.ListSection;
+import com.google.idea.blaze.base.projectview.section.sections.DirectoryEntry;
+import com.google.idea.blaze.base.projectview.section.sections.DirectorySection;
+import com.google.idea.blaze.base.projectview.section.sections.ExcludeTargetSection;
+import com.google.idea.blaze.base.projectview.section.sections.ImportTargetOutputSection;
+import com.google.idea.blaze.base.projectview.section.sections.TestSourceSection;
+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.settings.BlazeImportSettings;
+import com.google.idea.blaze.base.settings.BlazeImportSettingsManager;
+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;
+import com.google.idea.blaze.base.sync.workspace.WorkingSet;
+import com.google.idea.blaze.java.sync.BlazeJavaSyncAugmenter;
+import com.google.idea.blaze.java.sync.jdeps.JdepsMap;
+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.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;
+import com.google.idea.blaze.java.sync.workingset.JavaWorkingSet;
+import com.google.idea.common.experiments.ExperimentService;
+import com.google.idea.common.experiments.MockExperimentService;
+import com.intellij.openapi.extensions.impl.ExtensionPointImpl;
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for BlazeJavaWorkspaceImporter */
+@RunWith(JUnit4.class)
+public class BlazeJavaWorkspaceImporterTest extends BlazeTestCase {
+
+  private static final String FAKE_WORKSPACE_ROOT = "/root";
+  private WorkspaceRoot workspaceRoot = new WorkspaceRoot(new File(FAKE_WORKSPACE_ROOT));
+
+  private static final String FAKE_GEN_ROOT_EXECUTION_PATH_FRAGMENT =
+      "blaze-out/gcc-4.X.Y-crosstool-v17-hybrid-grtev3-k8-fastbuild/bin";
+
+  private static final String FAKE_GEN_ROOT =
+      "/path/to/8093958afcfde6c33d08b621dfaa4e09/root/" + FAKE_GEN_ROOT_EXECUTION_PATH_FRAGMENT;
+
+  private static final ArtifactLocationDecoder FAKE_ARTIFACT_DECODER =
+      new ArtifactLocationDecoder(
+          new BlazeRoots(
+              new File("/"),
+              ImmutableList.of(),
+              new ExecutionRootPath("out/crosstool/bin"),
+              new ExecutionRootPath("out/crosstool/gen")),
+          null);
+
+  private static final BlazeImportSettings DUMMY_IMPORT_SETTINGS =
+      new BlazeImportSettings("", "", "", "", "", BuildSystem.Blaze);
+  private ExtensionPointImpl<BlazeJavaSyncAugmenter> augmenters;
+
+  private static class JdepsMock implements JdepsMap {
+    Map<Label, List<String>> jdeps = Maps.newHashMap();
+
+    @Nullable
+    @Override
+    public List<String> getDependenciesForRule(@NotNull Label label) {
+      return jdeps.get(label);
+    }
+
+    JdepsMock put(Label label, List<String> values) {
+      jdeps.put(label, values);
+      return this;
+    }
+  }
+
+  private BlazeContext context;
+  private ErrorCollector errorCollector = new ErrorCollector();
+  private final JdepsMock jdepsMap = new JdepsMock();
+  private JavaWorkingSet workingSet = null;
+  private final WorkspaceLanguageSettings workspaceLanguageSettings =
+      new WorkspaceLanguageSettings(WorkspaceType.JAVA, ImmutableSet.of(LanguageClass.JAVA));
+  private MockExperimentService experimentService;
+
+  @Override
+  protected void initTest(
+      @NotNull Container applicationServices, @NotNull Container projectServices) {
+    experimentService = new MockExperimentService();
+    applicationServices.register(ExperimentService.class, experimentService);
+
+    BlazeExecutor blazeExecutor = new MockBlazeExecutor();
+    applicationServices.register(BlazeExecutor.class, blazeExecutor);
+    projectServices.register(
+        BlazeImportSettingsManager.class, new BlazeImportSettingsManager(project));
+    BlazeImportSettingsManager.getInstance(getProject()).setImportSettings(DUMMY_IMPORT_SETTINGS);
+
+    // will silently fall back to FilePathJavaPackageReader
+    applicationServices.register(
+        JavaSourcePackageReader.class,
+        new JavaSourcePackageReader() {
+          @Nullable
+          @Override
+          public String getDeclaredPackageOfJavaFile(
+              @NotNull BlazeContext context, @NotNull SourceArtifact sourceArtifact) {
+            return null;
+          }
+        });
+    applicationServices.register(PackageManifestReader.class, new PackageManifestReader());
+    applicationServices.register(PrefetchService.class, new MockPrefetchService());
+
+    context = new BlazeContext();
+    context.addOutputSink(IssueOutput.class, errorCollector);
+
+    augmenters =
+        registerExtensionPoint(BlazeJavaSyncAugmenter.EP_NAME, BlazeJavaSyncAugmenter.class);
+  }
+
+  BlazeJavaImportResult importWorkspace(
+      WorkspaceRoot workspaceRoot, RuleMapBuilder ruleMapBuilder, ProjectView projectView) {
+
+    ProjectViewSet projectViewSet = ProjectViewSet.builder().add(projectView).build();
+
+    BlazeJavaWorkspaceImporter blazeWorkspaceImporter =
+        new BlazeJavaWorkspaceImporter(
+            project,
+            context,
+            workspaceRoot,
+            projectViewSet,
+            workspaceLanguageSettings,
+            ruleMapBuilder.build(),
+            jdepsMap,
+            workingSet,
+            FAKE_ARTIFACT_DECODER);
+
+    return blazeWorkspaceImporter.importWorkspace(context);
+  }
+
+  /** Ensure an empty response results in an empty import result. */
+  @Test
+  public void testEmptyProject() {
+    BlazeJavaImportResult result =
+        importWorkspace(workspaceRoot, RuleMapBuilder.builder(), ProjectView.builder().build());
+    errorCollector.assertNoIssues();
+    assertTrue(result.contentEntries.isEmpty());
+  }
+
+  @Test
+  public void testSingleModule() {
+    ProjectView projectView =
+        ProjectView.builder()
+            .add(
+                ListSection.builder(DirectorySection.KEY)
+                    .add(DirectoryEntry.include(new WorkspacePath("java/apps/example"))))
+            .build();
+
+    RuleMapBuilder ruleMapBuilder =
+        RuleMapBuilder.builder()
+            .addRule(
+                RuleIdeInfo.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()
+                            .setManifestFile(source("java/apps/example/AndroidManifest.xml"))
+                            .addResource(source("java/apps/example/res"))
+                            .setGenerateResourceClass(true)
+                            .setResourceJavaPackage("com.google.android.apps.example"))
+                    .setJavaInfo(
+                        JavaRuleIdeInfo.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);
+    errorCollector.assertNoIssues();
+
+    assertEquals(1, result.buildOutputJars.size());
+    File compilerOutputLib = result.buildOutputJars.iterator().next();
+    assertNotNull(compilerOutputLib);
+    assertTrue(compilerOutputLib.getPath().endsWith("example_debug.jar"));
+
+    assertThat(result.contentEntries)
+        .containsExactly(
+            BlazeContentEntry.builder("/root/java/apps/example")
+                .addSource(
+                    BlazeSourceDirectory.builder("/root/java/apps/example")
+                        .setPackagePrefix("apps.example")
+                        .build())
+                .build());
+
+    assertThat(result.javaSourceFiles)
+        .containsExactly(
+            source("java/apps/example/MainActivity.java").getFile(),
+            source("java/apps/example/subdir/SubdirHelper.java").getFile());
+  }
+
+  @Test
+  public void testGeneratedLibrariesIncluded() {
+    ProjectView projectView =
+        ProjectView.builder()
+            .add(
+                ListSection.builder(DirectorySection.KEY)
+                    .add(DirectoryEntry.include(new WorkspacePath("java/example"))))
+            .build();
+
+    RuleMapBuilder ruleMapBuilder =
+        RuleMapBuilder.builder()
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setLabel("//java/example:lib")
+                    .setBuildFile(source("java/example/BUILD"))
+                    .setKind("java_library")
+                    .addSource(source("java/example/Test.java"))
+                    .setJavaInfo(
+                        JavaRuleIdeInfo.builder()
+                            .addJar(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(gen("java/example/lib-ijar.jar"))
+                                    .setClassJar(gen("java/example/lib.jar")))
+                            .addGeneratedJar(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(gen("java/example/lib-gen.jar"))
+                                    .setClassJar(gen("java/example/lib-gen.jar")))));
+
+    BlazeJavaImportResult result = importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
+    assertThat(
+            result
+                .libraries
+                .values()
+                .stream()
+                .map(BlazeJavaWorkspaceImporterTest::libraryFileName)
+                .collect(Collectors.toList()))
+        .containsExactly("lib-gen.jar");
+  }
+
+  /** Imports two binaries and a library. Only one binary should pass the package filter. */
+  @Test
+  public void testImportFilter() {
+    ProjectView projectView =
+        ProjectView.builder()
+            .add(
+                ListSection.builder(DirectorySection.KEY)
+                    .add(DirectoryEntry.include(new WorkspacePath("java/apps/example"))))
+            .build();
+
+    RuleMapBuilder ruleMapBuilder =
+        RuleMapBuilder.builder()
+            .addRule(
+                RuleIdeInfo.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()
+                            .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()
+                            .addJar(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(gen("java/apps/example/example_debug.jar"))
+                                    .setClassJar(gen("java/apps/example/example_debug.jar")))))
+            .addRule(
+                RuleIdeInfo.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()
+                            .setManifestFile(source("java/libraries/example/AndroidManifest.xml"))
+                            .addResource(source("java/libraries/example/res"))
+                            .setGenerateResourceClass(true)
+                            .setResourceJavaPackage("com.google.android.libraries.example"))
+                    .setJavaInfo(
+                        JavaRuleIdeInfo.builder()
+                            .addJar(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(gen("java/libraries/example/example.jar"))
+                                    .setClassJar(gen("java/libraries/example/example.jar")))))
+            .addRule(
+                RuleIdeInfo.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()
+                            .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()
+                            .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);
+    errorCollector.assertNoIssues();
+
+    assertThat(result.contentEntries)
+        .containsExactly(
+            BlazeContentEntry.builder("/root/java/apps/example")
+                .addSource(
+                    BlazeSourceDirectory.builder("/root/java/apps/example")
+                        .setPackagePrefix("apps.example")
+                        .build())
+                .build());
+    assertThat(result.javaSourceFiles)
+        .containsExactly(source("java/apps/example/MainActivity.java").getFile());
+  }
+
+  /** Import a project and its tests */
+  @Test
+  public void testProjectAndTests() {
+    ProjectView projectView =
+        ProjectView.builder()
+            .add(
+                ListSection.builder(DirectorySection.KEY)
+                    .add(DirectoryEntry.include(new WorkspacePath("java/apps/example")))
+                    .add(DirectoryEntry.include(new WorkspacePath("javatests/apps/example"))))
+            .add(ListSection.builder(TestSourceSection.KEY).add(new Glob("javatests/*")))
+            .build();
+
+    RuleMapBuilder ruleMapBuilder =
+        RuleMapBuilder.builder()
+            .addRule(
+                RuleIdeInfo.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()
+                            .setManifestFile(source("java/apps/example/AndroidManifest.xml"))
+                            .addResource(source("java/apps/example/res"))
+                            .setGenerateResourceClass(true)
+                            .setResourceJavaPackage("com.google.android.apps.example"))
+                    .setJavaInfo(
+                        JavaRuleIdeInfo.builder()
+                            .addJar(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(gen("java/apps/example/example_debug.jar"))
+                                    .setClassJar(gen("java/apps/example/example_debug.jar")))))
+            .addRule(
+                RuleIdeInfo.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()
+                            .setResourceJavaPackage("com.google.android.apps.example"))
+                    .addDependency("//java/apps/example:example_debug")
+                    .setJavaInfo(
+                        JavaRuleIdeInfo.builder()
+                            .addJar(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(gen("javatests/apps/example/example.jar"))
+                                    .setClassJar(gen("javatests/apps/example/example.jar")))));
+
+    BlazeJavaImportResult result = importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
+    errorCollector.assertNoIssues();
+
+    assertThat(result.contentEntries)
+        .containsExactly(
+            BlazeContentEntry.builder("/root/java/apps/example")
+                .addSource(
+                    BlazeSourceDirectory.builder("/root/java/apps/example")
+                        .setPackagePrefix("apps.example")
+                        .build())
+                .build(),
+            BlazeContentEntry.builder("/root/javatests/apps/example")
+                .addSource(
+                    BlazeSourceDirectory.builder("/root/javatests/apps/example")
+                        .setPackagePrefix("apps.example")
+                        .setTest(true)
+                        .build())
+                .build());
+  }
+
+  /** Test library with a source jar */
+  @Test
+  public void testLibraryWithSourceJar() {
+    ProjectView projectView =
+        ProjectView.builder()
+            .add(
+                ListSection.builder(DirectorySection.KEY)
+                    .add(DirectoryEntry.include(new WorkspacePath("java/apps/example")))
+                    .add(DirectoryEntry.include(new WorkspacePath("javatests/apps/example"))))
+            .build();
+
+    RuleMapBuilder ruleMapBuilder =
+        RuleMapBuilder.builder()
+            .addRule(
+                RuleIdeInfo.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()
+                            .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()
+                            .addJar(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(gen("java/apps/example/example_debug.jar"))
+                                    .setClassJar(gen("java/apps/example/example_debug.jar")))))
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setLabel("//thirdparty/some/library:library")
+                    .setBuildFile(source("/thirdparty/some/library/BUILD"))
+                    .setKind("java_import")
+                    .setJavaInfo(
+                        JavaRuleIdeInfo.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);
+    errorCollector.assertNoIssues();
+
+    BlazeJarLibrary library = findLibrary(result.libraries, "library.jar");
+    assertNotNull(library);
+    assertNotNull(library.libraryArtifact.sourceJar);
+  }
+
+  /** Test a project with a java test rule */
+  @Test
+  public void testJavaTestRule() {
+    ProjectView projectView =
+        ProjectView.builder()
+            .add(
+                ListSection.builder(DirectorySection.KEY)
+                    .add(DirectoryEntry.include(new WorkspacePath("java/apps/example")))
+                    .add(DirectoryEntry.include(new WorkspacePath("javatests/apps/example"))))
+            .add(ListSection.builder(TestSourceSection.KEY).add(new Glob("javatests/*")))
+            .build();
+
+    RuleMapBuilder ruleMapBuilder =
+        RuleMapBuilder.builder()
+            .addRule(
+                RuleIdeInfo.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()
+                            .setManifestFile(source("java/apps/example/AndroidManifest.xml"))
+                            .addResource(source("java/apps/example/res"))
+                            .setGenerateResourceClass(true)
+                            .setResourceJavaPackage("com.google.android.apps.example"))
+                    .setJavaInfo(
+                        JavaRuleIdeInfo.builder()
+                            .addJar(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(gen("java/apps/example/example_debug.jar"))
+                                    .setClassJar(gen("java/apps/example/example_debug.jar")))))
+            .addRule(
+                RuleIdeInfo.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()
+                            .addJar(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(gen("javatests/apps/example/example.jar"))
+                                    .setClassJar(gen("javatests/apps/example/example.jar")))));
+
+    BlazeJavaImportResult result = importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
+    errorCollector.assertNoIssues();
+
+    assertThat(result.contentEntries)
+        .containsExactly(
+            BlazeContentEntry.builder("/root/java/apps/example")
+                .addSource(
+                    BlazeSourceDirectory.builder("/root/java/apps/example")
+                        .setPackagePrefix("apps.example")
+                        .build())
+                .build(),
+            BlazeContentEntry.builder("/root/javatests/apps/example")
+                .addSource(
+                    BlazeSourceDirectory.builder("/root/javatests/apps/example")
+                        .setPackagePrefix("apps.example")
+                        .setTest(true)
+                        .build())
+                .build());
+  }
+
+  /*
+   * Test that the non-android libraries can be imported.
+   */
+  @Test
+  public void testNormalJavaLibraryPackage() {
+    ProjectView projectView =
+        ProjectView.builder()
+            .add(
+                ListSection.builder(DirectorySection.KEY)
+                    .add(DirectoryEntry.include(new WorkspacePath("java/apps/example")))
+                    .add(DirectoryEntry.include(new WorkspacePath("java/library/something"))))
+            .build();
+
+    RuleMapBuilder ruleMapBuilder =
+        RuleMapBuilder.builder()
+            .addRule(
+                RuleIdeInfo.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())
+                    .setAndroidInfo(
+                        AndroidRuleIdeInfo.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()
+                    .setLabel("//java/library/something:something")
+                    .setBuildFile(source("java/library/something/BUILD"))
+                    .setKind("java_library")
+                    .addSource(source("java/library/something/SomeJavaFile.java"))
+                    .setJavaInfo(
+                        JavaRuleIdeInfo.builder()
+                            .addJar(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(gen("java/library/something/something.jar"))
+                                    .setClassJar(gen("java/library/something/something.jar")))));
+
+    BlazeJavaImportResult result = importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
+    errorCollector.assertNoIssues();
+
+    assertThat(result.contentEntries)
+        .containsExactly(
+            BlazeContentEntry.builder("/root/java/apps/example")
+                .addSource(
+                    BlazeSourceDirectory.builder("/root/java/apps/example")
+                        .setPackagePrefix("apps.example")
+                        .build())
+                .build(),
+            BlazeContentEntry.builder("/root/java/library/something")
+                .addSource(
+                    BlazeSourceDirectory.builder("/root/java/library/something")
+                        .setPackagePrefix("library.something")
+                        .build())
+                .build());
+  }
+
+  @Test
+  public void testImportTargetOutputTag() {
+    ProjectView projectView =
+        ProjectView.builder()
+            .add(
+                ListSection.builder(DirectorySection.KEY)
+                    .add(DirectoryEntry.include(new WorkspacePath("lib")))
+                    .add(DirectoryEntry.include(new WorkspacePath("lib2"))))
+            .build();
+
+    RuleMapBuilder response =
+        RuleMapBuilder.builder()
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setLabel("//lib:lib")
+                    .setBuildFile(source("lib/BUILD"))
+                    .setKind("java_library")
+                    .addSource(source("lib/Lib.java"))
+                    .addDependency("//lib2:lib2")
+                    .setJavaInfo(
+                        JavaRuleIdeInfo.builder()
+                            .addJar(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(gen("lib/lib.jar"))
+                                    .setClassJar(gen("lib/lib.jar")))))
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setLabel("//lib2:lib2")
+                    .setBuildFile(source("lib2/BUILD"))
+                    .setKind("java_library")
+                    .addSource(source("lib2/Lib2.java"))
+                    .addTag("intellij-import-target-output")
+                    .setJavaInfo(
+                        JavaRuleIdeInfo.builder()
+                            .addJar(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(gen("lib2/lib2.jar"))
+                                    .setClassJar(gen("lib2/lib2.jar")))));
+
+    BlazeJavaImportResult result = importWorkspace(workspaceRoot, response, projectView);
+    errorCollector.assertNoIssues();
+    assertEquals(1, result.libraries.size());
+  }
+
+  @Test
+  public void testImportAsLibraryTagLegacy() {
+    ProjectView projectView =
+        ProjectView.builder()
+            .add(
+                ListSection.builder(DirectorySection.KEY)
+                    .add(DirectoryEntry.include(new WorkspacePath("lib")))
+                    .add(DirectoryEntry.include(new WorkspacePath("lib2"))))
+            .build();
+
+    RuleMapBuilder response =
+        RuleMapBuilder.builder()
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setLabel("//lib:lib")
+                    .setBuildFile(source("lib/BUILD"))
+                    .setKind("java_library")
+                    .addSource(source("lib/Lib.java"))
+                    .addDependency("//lib2:lib2")
+                    .setJavaInfo(
+                        JavaRuleIdeInfo.builder()
+                            .addJar(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(gen("lib/lib.jar"))
+                                    .setClassJar(gen("lib/lib.jar")))))
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setLabel("//lib2:lib2")
+                    .setBuildFile(source("lib2/BUILD"))
+                    .setKind("java_library")
+                    .addSource(source("lib2/Lib2.java"))
+                    .addTag("aswb-import-as-library")
+                    .setJavaInfo(
+                        JavaRuleIdeInfo.builder()
+                            .addJar(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(gen("lib2/lib2.jar"))
+                                    .setClassJar(gen("lib2/lib2.jar")))));
+
+    BlazeJavaImportResult result = importWorkspace(workspaceRoot, response, projectView);
+    errorCollector.assertNoIssues();
+
+    assertEquals(1, result.libraries.size());
+  }
+
+  @Test
+  public void testMultipleImportOfJarsGetMerged() {
+    ProjectView projectView =
+        ProjectView.builder()
+            .add(
+                ListSection.builder(DirectorySection.KEY)
+                    .add(DirectoryEntry.include(new WorkspacePath("lib"))))
+            .build();
+
+    RuleMapBuilder response =
+        RuleMapBuilder.builder()
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setLabel("//lib:libsource")
+                    .setBuildFile(source("lib/BUILD"))
+                    .setKind("java_library")
+                    .addSource(source("lib/Source.java"))
+                    .setJavaInfo(JavaRuleIdeInfo.builder())
+                    .addDependency("//lib:lib0")
+                    .addDependency("//lib:lib1"))
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setLabel("//lib:lib0")
+                    .setBuildFile(source("lib/BUILD"))
+                    .setKind("java_import")
+                    .setJavaInfo(
+                        JavaRuleIdeInfo.builder()
+                            .addJar(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(source("lib/lib.jar"))
+                                    .setClassJar(source("lib/lib.jar")))))
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setLabel("//lib:lib1")
+                    .setBuildFile(source("lib/BUILD"))
+                    .setKind("java_import")
+                    .setJavaInfo(
+                        JavaRuleIdeInfo.builder()
+                            .addJar(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(source("lib/lib.jar"))
+                                    .setClassJar(source("lib/lib.jar")))));
+
+    BlazeJavaImportResult result = importWorkspace(workspaceRoot, response, projectView);
+    errorCollector.assertNoIssues();
+    assertEquals(1, result.libraries.size()); // The libraries were merged
+  }
+
+  @Test
+  public void testRuleWithOnlyGeneratedSourcesIsAddedAsLibrary() {
+    ProjectView projectView =
+        ProjectView.builder()
+            .add(
+                ListSection.builder(DirectorySection.KEY)
+                    .add(DirectoryEntry.include(new WorkspacePath("import"))))
+            .build();
+
+    RuleMapBuilder response =
+        RuleMapBuilder.builder()
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setLabel("//import:lib")
+                    .setBuildFile(source("import/BUILD"))
+                    .setKind("android_library")
+                    .addSource(source("import/Lib.java"))
+                    .setJavaInfo(JavaRuleIdeInfo.builder())
+                    .addDependency("//import:import")
+                    .addDependency("//import:import_android"))
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setLabel("//import:import")
+                    .setBuildFile(source("import/BUILD"))
+                    .setKind("java_library")
+                    .addSource(gen("import/GenSource.java"))
+                    .setJavaInfo(
+                        JavaRuleIdeInfo.builder()
+                            .addJar(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(gen("import/import.jar"))
+                                    .setClassJar(gen("import/import.jar")))))
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setLabel("//import:import_android")
+                    .setBuildFile(source("import/BUILD"))
+                    .setKind("android_library")
+                    .addSource(gen("import/GenSource.java"))
+                    .setJavaInfo(
+                        JavaRuleIdeInfo.builder()
+                            .addJar(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(gen("import/import_android.jar"))
+                                    .setClassJar(gen("import/import_android.jar")))));
+
+    BlazeJavaImportResult result = importWorkspace(workspaceRoot, response, projectView);
+    errorCollector.assertNoIssues();
+
+    assertThat(findLibrary(result.libraries, "import.jar")).isNotNull();
+    assertThat(findLibrary(result.libraries, "import_android.jar")).isNotNull();
+  }
+
+  @Test
+  public void testRuleWithMixedGeneratedSourcesAddsFilteredGenJar() {
+    ProjectView projectView =
+        ProjectView.builder()
+            .add(
+                ListSection.builder(DirectorySection.KEY)
+                    .add(DirectoryEntry.include(new WorkspacePath("import"))))
+            .build();
+
+    RuleMapBuilder response =
+        RuleMapBuilder.builder()
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setLabel("//import:lib")
+                    .setBuildFile(source("import/BUILD"))
+                    .setKind("java_library")
+                    .addSource(source("import/Import.java"))
+                    .addSource(gen("import/Import.java"))
+                    .setJavaInfo(
+                        JavaRuleIdeInfo.builder()
+                            .setFilteredGenJar(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(gen("import/filtered-gen.jar")))));
+
+    BlazeJavaImportResult result = importWorkspace(workspaceRoot, response, projectView);
+    errorCollector.assertNoIssues();
+    assertThat(findLibrary(result.libraries, "filtered-gen.jar")).isNotNull();
+  }
+
+  @Test
+  public void testRuleWithOnlySourceJarAsSourceAddedAsLibrary() {
+    ProjectView projectView =
+        ProjectView.builder()
+            .add(
+                ListSection.builder(DirectorySection.KEY)
+                    .add(DirectoryEntry.include(new WorkspacePath("import"))))
+            .build();
+
+    RuleMapBuilder response =
+        RuleMapBuilder.builder()
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setLabel("//import:lib")
+                    .setBuildFile(source("import/BUILD"))
+                    .setKind("android_library")
+                    .addSource(source("import/Lib.java"))
+                    .setJavaInfo(JavaRuleIdeInfo.builder())
+                    .addDependency("//import:import")
+                    .addDependency("//import:import_android"))
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setLabel("//import:import")
+                    .setBuildFile(source("import/BUILD"))
+                    .setKind("java_library")
+                    .addSource(gen("import/gen-src.jar"))
+                    .setJavaInfo(
+                        JavaRuleIdeInfo.builder()
+                            .addJar(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(gen("import/import.jar"))
+                                    .setClassJar(gen("import/import.jar")))));
+
+    BlazeJavaImportResult result = importWorkspace(workspaceRoot, response, projectView);
+    errorCollector.assertNoIssues();
+
+    assertThat(findLibrary(result.libraries, "import.jar")).isNotNull();
+  }
+
+  @Test
+  public void testImportTargetOutput() {
+    ProjectView projectView =
+        ProjectView.builder()
+            .add(
+                ListSection.builder(DirectorySection.KEY)
+                    .add(DirectoryEntry.include(new WorkspacePath("import"))))
+            .add(
+                ListSection.builder(ImportTargetOutputSection.KEY)
+                    .add(new Label("//import:import")))
+            .build();
+
+    RuleMapBuilder response =
+        RuleMapBuilder.builder()
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setLabel("//import:lib")
+                    .setBuildFile(source("import/BUILD"))
+                    .setKind("java_library")
+                    .addSource(source("import/Lib.java"))
+                    .setJavaInfo(JavaRuleIdeInfo.builder())
+                    .addDependency("//import:import"))
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setLabel("//import:import")
+                    .setBuildFile(source("import/BUILD"))
+                    .setKind("java_library")
+                    .addSource(source("import/Import.java"))
+                    .setJavaInfo(
+                        JavaRuleIdeInfo.builder()
+                            .addJar(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(gen("import/import.jar"))
+                                    .setClassJar(gen("import/import.jar")))));
+
+    BlazeJavaImportResult result = importWorkspace(workspaceRoot, response, projectView);
+    errorCollector.assertNoIssues();
+
+    assertThat(result.libraries).isNotEmpty();
+  }
+
+  private RuleMapBuilder ruleMapForJdepsSuite() {
+    return RuleMapBuilder.builder()
+        .addRule(
+            RuleIdeInfo.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())
+                .addDependency("//thirdparty/a:a"))
+        .addRule(
+            RuleIdeInfo.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()
+                        .addJar(
+                            LibraryArtifact.builder()
+                                .setInterfaceJar(gen("thirdparty/a.jar"))
+                                .setClassJar(gen("thirdparty/a.jar"))
+                                .setSourceJar(gen("thirdparty/a.srcjar")))))
+        .addRule(
+            RuleIdeInfo.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()
+                        .addJar(
+                            LibraryArtifact.builder()
+                                .setInterfaceJar(gen("thirdparty/b.jar"))
+                                .setClassJar(gen("thirdparty/b.jar"))
+                                .setSourceJar(gen("thirdparty/b.srcjar")))))
+        .addRule(
+            RuleIdeInfo.builder()
+                .setLabel("//thirdparty/c:c")
+                .setKind("java_library")
+                .addSource(source("thirdparty/c/C.java"))
+                .setBuildFile(source("third_party/c/BUILD"))
+                .setJavaInfo(
+                    JavaRuleIdeInfo.builder()
+                        .addJar(
+                            LibraryArtifact.builder()
+                                .setInterfaceJar(gen("thirdparty/c.jar"))
+                                .setClassJar(gen("thirdparty/c.jar"))
+                                .setSourceJar(gen("thirdparty/c.srcjar")))));
+  }
+
+  @Test
+  public void testLibraryDependenciesWithJdepsSet() {
+    ProjectView projectView =
+        ProjectView.builder()
+            .add(
+                ListSection.builder(DirectorySection.KEY)
+                    .add(DirectoryEntry.include(new WorkspacePath("java/apps/example")))
+                    .add(DirectoryEntry.include(new WorkspacePath("javatests/apps/example"))))
+            .build();
+    RuleMapBuilder ruleMapBuilder = ruleMapForJdepsSuite();
+    jdepsMap.put(
+        new Label("//java/apps/example:example_debug"),
+        Lists.newArrayList(jdepsPath("thirdparty/a.jar"), jdepsPath("thirdparty/c.jar")));
+
+    BlazeJavaImportResult result = importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
+    assertThat(
+            result
+                .libraries
+                .values()
+                .stream()
+                .map(BlazeJavaWorkspaceImporterTest::libraryFileName)
+                .collect(Collectors.toList()))
+        .containsExactly("a.jar", "c.jar");
+  }
+
+  @Test
+  public void
+      testLibraryDependenciesWithJdepsReportingNothingShouldStillIncludeDirectDepsIfInWorkingSet() {
+    ProjectView projectView =
+        ProjectView.builder()
+            .add(
+                ListSection.builder(DirectorySection.KEY)
+                    .add(DirectoryEntry.include(new WorkspacePath("java/apps/example")))
+                    .add(DirectoryEntry.include(new WorkspacePath("javatests/apps/example"))))
+            .build();
+    RuleMapBuilder ruleMapBuilder = ruleMapForJdepsSuite();
+    workingSet =
+        new JavaWorkingSet(
+            workspaceRoot,
+            new WorkingSet(
+                ImmutableList.of(new WorkspacePath("java/apps/example/Test.java")),
+                ImmutableList.of(),
+                ImmutableList.of()));
+
+    BlazeJavaImportResult result = importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
+    assertThat(
+            result
+                .libraries
+                .values()
+                .stream()
+                .map(BlazeJavaWorkspaceImporterTest::libraryFileName)
+                .collect(Collectors.toList()))
+        .containsExactly("a.jar");
+  }
+
+  @Test
+  public void testLibraryDepsWithJdepsReportingZeroShouldNotIncludeDirectDepsIfNotInWorkingSet() {
+    ProjectView projectView =
+        ProjectView.builder()
+            .add(
+                ListSection.builder(DirectorySection.KEY)
+                    .add(DirectoryEntry.include(new WorkspacePath("java/apps/example")))
+                    .add(DirectoryEntry.include(new WorkspacePath("javatests/apps/example"))))
+            .build();
+    RuleMapBuilder ruleMapBuilder = ruleMapForJdepsSuite();
+    workingSet =
+        new JavaWorkingSet(
+            workspaceRoot,
+            new WorkingSet(ImmutableList.of(), ImmutableList.of(), ImmutableList.of()));
+
+    BlazeJavaImportResult result = importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
+    assertThat(
+            result
+                .libraries
+                .values()
+                .stream()
+                .map(BlazeJavaWorkspaceImporterTest::libraryFileName)
+                .collect(Collectors.toList()))
+        .isEmpty();
+  }
+
+  /*
+   * Test the exclude_target section
+   */
+  @Test
+  public void testExcludeTarget() {
+    ProjectView projectView =
+        ProjectView.builder()
+            .add(
+                ListSection.builder(DirectorySection.KEY)
+                    .add(DirectoryEntry.include(new WorkspacePath("java/apps/example"))))
+            .add(
+                ListSection.builder(ExcludeTargetSection.KEY)
+                    .add(new Label("//java/apps/example:example")))
+            .build();
+
+    RuleMapBuilder ruleMapBuilder =
+        RuleMapBuilder.builder()
+            .addRule(
+                RuleIdeInfo.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()
+                            .addJar(
+                                LibraryArtifact.builder().setInterfaceJar(gen("example.jar")))));
+
+    BlazeJavaImportResult result = importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
+    errorCollector.assertNoIssues();
+
+    assertThat(result.javaSourceFiles).isEmpty();
+  }
+
+  /*
+   * Test the intellij-exclude-target tag
+   */
+  @Test
+  public void testExcludeTargetTag() {
+    ProjectView projectView =
+        ProjectView.builder()
+            .add(
+                ListSection.builder(DirectorySection.KEY)
+                    .add(DirectoryEntry.include(new WorkspacePath("java/apps/example"))))
+            .build();
+
+    RuleMapBuilder ruleMapBuilder =
+        RuleMapBuilder.builder()
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setLabel("//java/apps/example:example")
+                    .addTag(Tags.RULE_TAG_EXCLUDE_TARGET)
+                    .setBuildFile(source("java/apps/example/BUILD"))
+                    .setKind("java_library")
+                    .addSource(source("java/apps/example/Example.java"))
+                    .setJavaInfo(
+                        JavaRuleIdeInfo.builder()
+                            .addJar(
+                                LibraryArtifact.builder().setInterfaceJar(gen("example.jar")))));
+
+    BlazeJavaImportResult result = importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
+    errorCollector.assertNoIssues();
+
+    assertThat(result.javaSourceFiles).isEmpty();
+  }
+
+  /** Test legacy proto_library jars, complete with overrides and everything. */
+  @Test
+  public void testLegacyProtoLibraryInfo() {
+    ProjectView projectView =
+        ProjectView.builder()
+            .add(
+                ListSection.builder(DirectorySection.KEY)
+                    .add(DirectoryEntry.include(new WorkspacePath("java/example"))))
+            .build();
+
+    RuleMapBuilder ruleMapBuilder =
+        RuleMapBuilder.builder()
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setLabel("//java/example:liba")
+                    .setBuildFile(source("java/example/BUILD"))
+                    .setKind("java_library")
+                    .addSource(source("java/example/Liba.java"))
+                    .setJavaInfo(JavaRuleIdeInfo.builder())
+                    .addDependency("//thirdparty/proto/a:a"))
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setLabel("//java/example:libb")
+                    .setBuildFile(source("java/example/BUILD"))
+                    .setKind("java_library")
+                    .addSource(source("java/example/Libb.java"))
+                    .setJavaInfo(JavaRuleIdeInfo.builder())
+                    .addDependency("//thirdparty/proto/b:b"))
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setLabel("//thirdparty/proto/a:a")
+                    .setBuildFile(source("/thirdparty/a/BUILD"))
+                    .setKind("proto_library")
+                    .setProtoLibraryLegacyInfo(
+                        ProtoLibraryLegacyInfo.builder(ProtoLibraryLegacyInfo.ApiFlavor.IMMUTABLE)
+                            .addJarV1(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(gen("thirdparty/proto/a/liba-1-ijar.jar")))
+                            .addJarImmutable(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(gen("thirdparty/proto/a/liba-ijar.jar"))))
+                    .addDependency("//thirdparty/proto/b:b")
+                    .addDependency("//thirdparty/proto/c:c"))
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setLabel("//thirdparty/proto/b:b")
+                    .setBuildFile(source("/thirdparty/b/BUILD"))
+                    .setKind("proto_library")
+                    .setProtoLibraryLegacyInfo(
+                        ProtoLibraryLegacyInfo.builder(ProtoLibraryLegacyInfo.ApiFlavor.VERSION_1)
+                            .addJarV1(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(gen("thirdparty/proto/b/libb-ijar.jar")))
+                            .addJarImmutable(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(gen("thirdparty/proto/b/libb-2-ijar.jar"))))
+                    .addDependency("//thirdparty/proto/d:d"))
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setLabel("//thirdparty/proto/c:c")
+                    .setBuildFile(source("/thirdparty/c/BUILD"))
+                    .setKind("proto_library")
+                    .setProtoLibraryLegacyInfo(
+                        ProtoLibraryLegacyInfo.builder(ProtoLibraryLegacyInfo.ApiFlavor.IMMUTABLE)
+                            .addJarV1(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(gen("thirdparty/proto/c/libc-1-ijar.jar")))
+                            .addJarImmutable(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(gen("thirdparty/proto/c/libc-ijar.jar"))))
+                    .addDependency("//thirdparty/proto/d:d"))
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setLabel("//thirdparty/proto/d:d")
+                    .setBuildFile(source("/thirdparty/d/BUILD"))
+                    .setKind("proto_library")
+                    .setProtoLibraryLegacyInfo(
+                        ProtoLibraryLegacyInfo.builder(ProtoLibraryLegacyInfo.ApiFlavor.VERSION_1)
+                            .addJarV1(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(gen("thirdparty/proto/d/libd-ijar.jar")))
+                            .addJarImmutable(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(gen("thirdparty/proto/d/libd-2-ijar.jar")))));
+
+    workingSet =
+        new JavaWorkingSet(
+            workspaceRoot,
+            new WorkingSet(ImmutableList.of(), ImmutableList.of(), ImmutableList.of()));
+
+    // First test - make sure that jdeps is working
+    jdepsMap.put(
+        new Label("//java/example:liba"),
+        Lists.newArrayList(jdepsPath("thirdparty/proto/a/liba-ijar.jar")));
+    BlazeJavaImportResult result = importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
+    errorCollector.assertNoIssues();
+    assertThat(result.libraries).hasSize(1);
+    assertThat(findLibrary(result.libraries, "liba-ijar.jar")).isNotNull();
+
+    // Second test
+    // Put everything in the working set, which should expand to the full transitive closure
+    workingSet =
+        new JavaWorkingSet(
+            workspaceRoot,
+            new WorkingSet(
+                ImmutableList.of(new WorkspacePath("java/example/BUILD")),
+                ImmutableList.of(),
+                ImmutableList.of()));
+
+    result = importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
+    errorCollector.assertNoIssues();
+
+    assertThat(result.libraries).hasSize(6);
+    assertThat(findLibrary(result.libraries, "liba-ijar.jar")).isNotNull();
+    assertThat(findLibrary(result.libraries, "libb-ijar.jar")).isNotNull();
+    assertThat(findLibrary(result.libraries, "libb-2-ijar.jar")).isNotNull();
+    assertThat(findLibrary(result.libraries, "libc-ijar.jar")).isNotNull();
+    assertThat(findLibrary(result.libraries, "libd-ijar.jar")).isNotNull();
+    assertThat(findLibrary(result.libraries, "libd-2-ijar.jar")).isNotNull();
+  }
+
+  /** Test that the non-android libraries can be imported. */
+  @Test
+  public void testImporterWorksWithWorkspaceRootDirectoryIncluded() {
+    ProjectView projectView =
+        ProjectView.builder()
+            .add(
+                ListSection.builder(DirectorySection.KEY)
+                    .add(DirectoryEntry.include(new WorkspacePath(""))))
+            .build();
+
+    RuleMapBuilder ruleMapBuilder =
+        RuleMapBuilder.builder()
+            .addRule(
+                RuleIdeInfo.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())
+                    .setAndroidInfo(
+                        AndroidRuleIdeInfo.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()
+                    .setLabel("//java/library/something:something")
+                    .setBuildFile(source("java/library/something/BUILD"))
+                    .setKind("java_library")
+                    .addSource(source("java/library/something/SomeJavaFile.java"))
+                    .setJavaInfo(
+                        JavaRuleIdeInfo.builder()
+                            .addJar(
+                                LibraryArtifact.builder()
+                                    .setInterfaceJar(gen("java/library/something/something.jar"))
+                                    .setClassJar(gen("java/library/something/something.jar")))));
+
+    BlazeJavaImportResult result = importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
+    errorCollector.assertNoIssues();
+
+    assertThat(result.contentEntries)
+        .containsExactly(
+            BlazeContentEntry.builder("/root")
+                .addSource(BlazeSourceDirectory.builder("/root").build())
+                .addSource(BlazeSourceDirectory.builder("/root/java").build())
+                .build());
+  }
+
+  @Test
+  public void testLanguageLevelIsReadFromToolchain() {
+    ProjectView projectView = ProjectView.builder().build();
+
+    RuleMapBuilder ruleMapBuilder =
+        RuleMapBuilder.builder()
+            .addRule(
+                RuleIdeInfo.builder()
+                    .setLabel("//java:toolchain")
+                    .setBuildFile(source("java/BUILD"))
+                    .setKind("java_toolchain")
+                    .setJavaToolchainIdeInfo(
+                        JavaToolchainIdeInfo.builder()
+                            .setSourceVersion("8")
+                            .setTargetVersion("8")));
+
+    BlazeJavaImportResult result = importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
+    assertThat(result.sourceVersion).isEqualTo("8");
+  }
+
+  @Test
+  public void testSyncAugmenter() {
+    augmenters.registerExtension(
+        new BlazeJavaSyncAugmenter.Adapter() {
+          @Override
+          public boolean isActive(WorkspaceLanguageSettings workspaceLanguageSettings) {
+            return true;
+          }
+
+          @Override
+          public void addJarsForSourceRule(
+              RuleIdeInfo rule,
+              Collection<BlazeJarLibrary> jars,
+              Collection<BlazeJarLibrary> genJars) {
+            if (rule.label.equals(new Label("//java/example:source"))) {
+              jars.add(
+                  new BlazeJarLibrary(
+                      LibraryArtifact.builder().setInterfaceJar(gen("source.jar")).build(),
+                      rule.label));
+            }
+          }
+        });
+
+    ProjectView projectView =
+        ProjectView.builder()
+            .add(
+                ListSection.builder(DirectorySection.KEY)
+                    .add(DirectoryEntry.include(new WorkspacePath("java/example"))))
+            .build();
+
+    RuleMapBuilder ruleMapBuilder =
+        RuleMapBuilder.builder()
+            .addRule(
+                RuleIdeInfo.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()
+                    .setLabel("//java/lib:lib")
+                    .setBuildFile(source("java/lib/BUILD"))
+                    .setKind("java_library")
+                    .addSource(source("Lib.java"))
+                    .setJavaInfo(JavaRuleIdeInfo.builder()));
+
+    BlazeJavaImportResult result = importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
+    assertThat(
+            result
+                .libraries
+                .values()
+                .stream()
+                .map(BlazeJavaWorkspaceImporterTest::libraryFileName)
+                .collect(Collectors.toList()))
+        .containsExactly("source.jar");
+  }
+
+  /* Utility methods */
+
+  private static String libraryFileName(BlazeJarLibrary library) {
+    return library.libraryArtifact.jarForIntellijLibrary().getFile().getName();
+  }
+
+  @Nullable
+  private static BlazeJarLibrary findLibrary(
+      Map<LibraryKey, BlazeJarLibrary> libraries, String libraryName) {
+    for (BlazeJarLibrary library : libraries.values()) {
+      if (library
+          .libraryArtifact
+          .jarForIntellijLibrary()
+          .getFile()
+          .getPath()
+          .endsWith(libraryName)) {
+        return library;
+      }
+    }
+    return null;
+  }
+
+  private ArtifactLocation source(String relativePath) {
+    return ArtifactLocation.builder()
+        .setRootPath(FAKE_WORKSPACE_ROOT)
+        .setRelativePath(relativePath)
+        .setIsSource(true)
+        .build();
+  }
+
+  private static ArtifactLocation gen(String relativePath) {
+    return ArtifactLocation.builder()
+        .setRootPath(FAKE_GEN_ROOT)
+        .setRootExecutionPathFragment(FAKE_GEN_ROOT_EXECUTION_PATH_FRAGMENT)
+        .setRelativePath(relativePath)
+        .setIsSource(false)
+        .build();
+  }
+
+  private static String jdepsPath(String relativePath) {
+    return FAKE_GEN_ROOT_EXECUTION_PATH_FRAGMENT + "/" + relativePath;
+  }
+}
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
new file mode 100644
index 0000000..dec8f90
--- /dev/null
+++ b/java/tests/unittests/com/google/idea/blaze/java/sync/source/SourceDirectoryCalculatorTest.java
@@ -0,0 +1,1185 @@
+/*
+ * 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.source;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.MoreExecutors;
+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.ArtifactLocation;
+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;
+import com.google.idea.blaze.base.model.primitives.Label;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
+import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
+import com.google.idea.blaze.base.prefetch.MockPrefetchService;
+import com.google.idea.blaze.base.prefetch.PrefetchService;
+import com.google.idea.blaze.base.projectview.ProjectViewSet;
+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.projectview.SourceTestConfig;
+import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
+import com.google.idea.blaze.base.sync.workspace.BlazeRoots;
+import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolverImpl;
+import com.google.idea.blaze.java.sync.model.BlazeContentEntry;
+import com.google.idea.blaze.java.sync.model.BlazeSourceDirectory;
+import com.google.idea.common.experiments.ExperimentService;
+import com.google.idea.common.experiments.MockExperimentService;
+import com.google.repackaged.devtools.build.lib.ideinfo.androidstudio.PackageManifestOuterClass;
+import com.google.repackaged.devtools.build.lib.ideinfo.androidstudio.PackageManifestOuterClass.JavaSourcePackage;
+import com.google.repackaged.devtools.build.lib.ideinfo.androidstudio.PackageManifestOuterClass.PackageManifest;
+import com.intellij.util.containers.HashMap;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.List;
+import java.util.Map;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Test cases for {@link SourceDirectoryCalculator}. */
+@RunWith(JUnit4.class)
+public class SourceDirectoryCalculatorTest extends BlazeTestCase {
+
+  private static final ImmutableMap<Label, ArtifactLocation> NO_MANIFESTS = ImmutableMap.of();
+  private static final Label LABEL = new Label("//fake:label");
+
+  private MockInputStreamProvider mockInputStreamProvider;
+  private SourceDirectoryCalculator sourceDirectoryCalculator;
+
+  private BlazeContext context = new BlazeContext();
+  private ErrorCollector issues = new ErrorCollector();
+  private MockExperimentService experimentService;
+
+  private WorkspaceRoot workspaceRoot = new WorkspaceRoot(new File("/root"));
+  private ArtifactLocationDecoder decoder =
+      new ArtifactLocationDecoder(
+          new BlazeRoots(
+              new File("/"),
+              Lists.newArrayList(new File("/usr/local/code")),
+              new ExecutionRootPath("out/crosstool/bin"),
+              new ExecutionRootPath("out/crosstool/gen")),
+          null);
+
+  static final class TestSourceImportConfig extends SourceTestConfig {
+    final boolean isTest;
+
+    public TestSourceImportConfig(boolean isTest) {
+      super(ProjectViewSet.builder().build());
+      this.isTest = isTest;
+    }
+
+    @Override
+    public boolean isTestSource(String relativePath) {
+      return isTest;
+    }
+  }
+
+  @Override
+  protected void initTest(
+      @NotNull Container applicationServices, @NotNull Container projectServices) {
+    super.initTest(applicationServices, projectServices);
+
+    mockInputStreamProvider = new MockInputStreamProvider();
+    applicationServices.register(InputStreamProvider.class, mockInputStreamProvider);
+    applicationServices.register(JavaSourcePackageReader.class, new JavaSourcePackageReader());
+    applicationServices.register(PackageManifestReader.class, new PackageManifestReader());
+    applicationServices.register(FileAttributeProvider.class, new MockFileAttributeProvider());
+
+    context.addOutputSink(IssueOutput.class, issues);
+    sourceDirectoryCalculator = new SourceDirectoryCalculator();
+
+    BlazeExecutor blazeExecutor = new MockBlazeExecutor();
+    applicationServices.register(BlazeExecutor.class, blazeExecutor);
+
+    experimentService = new MockExperimentService();
+    applicationServices.register(ExperimentService.class, experimentService);
+
+    applicationServices.register(PrefetchService.class, new MockPrefetchService());
+  }
+
+  @Test
+  public void testWorkspacePathIsAddedWithoutSources() throws Exception {
+    List<SourceArtifact> sourceArtifacts = ImmutableList.of();
+    ImmutableList<BlazeContentEntry> result =
+        sourceDirectoryCalculator.calculateContentEntries(
+            project,
+            context,
+            workspaceRoot,
+            new TestSourceImportConfig(false /* isTest */),
+            decoder,
+            ImmutableList.of(new WorkspacePath("java/com/google/app")),
+            sourceArtifacts,
+            NO_MANIFESTS);
+    issues.assertNoIssues();
+    assertThat(result)
+        .containsExactly(
+            BlazeContentEntry.builder("/root/java/com/google/app")
+                .addSource(
+                    BlazeSourceDirectory.builder("/root/java/com/google/app")
+                        .setPackagePrefix("com.google.app")
+                        .build())
+                .build());
+  }
+
+  @Test
+  public void testCalculatesPackageForSimpleCase() throws Exception {
+    mockInputStreamProvider.addFile(
+        "/root/java/com/google/Bla.java", "package com.google;\n public class Bla {}");
+    List<SourceArtifact> sourceArtifacts =
+        ImmutableList.of(
+            SourceArtifact.builder(LABEL)
+                .setArtifactLocation(
+                    ArtifactLocation.builder()
+                        .setRelativePath("java/com/google/Bla.java")
+                        .setRootPath("/root")
+                        .setIsSource(true))
+                .build());
+    ImmutableList<BlazeContentEntry> result =
+        sourceDirectoryCalculator.calculateContentEntries(
+            project,
+            context,
+            workspaceRoot,
+            new TestSourceImportConfig(false),
+            decoder,
+            ImmutableList.of(new WorkspacePath("java/com/google")),
+            sourceArtifacts,
+            NO_MANIFESTS);
+    assertThat(result)
+        .containsExactly(
+            BlazeContentEntry.builder("/root/java/com/google")
+                .addSource(
+                    BlazeSourceDirectory.builder("/root/java/com/google")
+                        .setPackagePrefix("com.google")
+                        .build())
+                .build());
+    issues.assertNoIssues();
+  }
+
+  @Test
+  public void testSourcesToSourceDirectories_testReturnsTest() throws Exception {
+    mockInputStreamProvider.addFile(
+        "/root/java/com/google/Bla.java", "package com.google;\n public class Bla {}");
+    List<SourceArtifact> sourceArtifacts =
+        ImmutableList.of(
+            SourceArtifact.builder(LABEL)
+                .setArtifactLocation(
+                    ArtifactLocation.builder()
+                        .setRelativePath("java/com/google/Bla.java")
+                        .setRootPath("/root")
+                        .setIsSource(true))
+                .build());
+    ImmutableList<BlazeContentEntry> result =
+        sourceDirectoryCalculator.calculateContentEntries(
+            project,
+            context,
+            workspaceRoot,
+            new TestSourceImportConfig(true),
+            decoder,
+            ImmutableList.of(new WorkspacePath("java/com/google")),
+            sourceArtifacts,
+            NO_MANIFESTS);
+    issues.assertNoIssues();
+    assertThat(result)
+        .containsExactly(
+            BlazeContentEntry.builder("/root/java/com/google")
+                .addSource(
+                    BlazeSourceDirectory.builder("/root/java/com/google")
+                        .setPackagePrefix("com.google")
+                        .setTest(true)
+                        .build())
+                .build());
+  }
+
+  @Test
+  public void testSourcesToSourceDirectories_multipleMatchingPackagesAreMerged() throws Exception {
+    mockInputStreamProvider
+        .addFile("/root/java/com/google/Bla.java", "package com.google;\n public class Bla {}")
+        .addFile(
+            "/root/java/com/google/subpackage/Bla.java",
+            "package com.google.subpackage;\n public class Bla {}");
+    List<SourceArtifact> sourceArtifacts =
+        ImmutableList.of(
+            SourceArtifact.builder(LABEL)
+                .setArtifactLocation(
+                    ArtifactLocation.builder()
+                        .setRelativePath("java/com/google/Bla.java")
+                        .setRootPath("/root")
+                        .setIsSource(true))
+                .build(),
+            SourceArtifact.builder(LABEL)
+                .setArtifactLocation(
+                    ArtifactLocation.builder()
+                        .setRelativePath("java/com/google/subpackage/Bla.java")
+                        .setRootPath("/root")
+                        .setIsSource(true))
+                .build());
+    ImmutableList<BlazeContentEntry> result =
+        sourceDirectoryCalculator.calculateContentEntries(
+            project,
+            context,
+            workspaceRoot,
+            new TestSourceImportConfig(false),
+            decoder,
+            ImmutableList.of(new WorkspacePath("java/com/google")),
+            sourceArtifacts,
+            NO_MANIFESTS);
+    issues.assertNoIssues();
+    assertThat(result)
+        .containsExactly(
+            BlazeContentEntry.builder("/root/java/com/google")
+                .addSource(
+                    BlazeSourceDirectory.builder("/root/java/com/google")
+                        .setPackagePrefix("com.google")
+                        .build())
+                .build());
+  }
+
+  @Test
+  public void testMultipleDirectoriesAreMergedWithDirectoryRootAsWorkspaceRoot() throws Exception {
+    mockInputStreamProvider
+        .addFile(
+            "/root/java/com/google/idea/blaze/plugin/run/Run.java",
+            "package com.google.idea.blaze.plugin.run;\n public class run {}")
+        .addFile(
+            "/root/java/com/google/idea/blaze/plugin/sync/Sync.java",
+            "package com.google.idea.blaze.plugin.sync;\n public class Sync {}")
+        .addFile(
+            "/root/java/com/google/idea/blaze/plugin/Plugin.java",
+            "package com.google.idea.blaze.plugin;\n public class Plugin {}");
+    List<SourceArtifact> sourceArtifacts =
+        ImmutableList.of(
+            SourceArtifact.builder(LABEL)
+                .setArtifactLocation(
+                    ArtifactLocation.builder()
+                        .setRelativePath("java/com/google/idea/blaze/plugin/run/Run.java")
+                        .setRootPath("/root")
+                        .setIsSource(true))
+                .build(),
+            SourceArtifact.builder(LABEL)
+                .setArtifactLocation(
+                    ArtifactLocation.builder()
+                        .setRelativePath("java/com/google/idea/blaze/plugin/sync/Sync.java")
+                        .setRootPath("/root")
+                        .setIsSource(true))
+                .build(),
+            SourceArtifact.builder(LABEL)
+                .setArtifactLocation(
+                    ArtifactLocation.builder()
+                        .setRelativePath("java/com/google/idea/blaze/plugin/Plugin.java")
+                        .setRootPath("/root")
+                        .setIsSource(true))
+                .build());
+    ImmutableList<BlazeContentEntry> result =
+        sourceDirectoryCalculator.calculateContentEntries(
+            project,
+            context,
+            workspaceRoot,
+            new TestSourceImportConfig(false),
+            decoder,
+            ImmutableList.of(new WorkspacePath("")),
+            sourceArtifacts,
+            NO_MANIFESTS);
+    issues.assertNoIssues();
+    assertThat(result)
+        .containsExactly(
+            BlazeContentEntry.builder("/root")
+                .addSource(BlazeSourceDirectory.builder("/root").setPackagePrefix("").build())
+                .addSource(BlazeSourceDirectory.builder("/root/java").setPackagePrefix("").build())
+                .build());
+  }
+
+  @Test
+  public void testIncorrectPackageInMiddleOfTreeCausesMergePointHigherUp() throws Exception {
+    mockInputStreamProvider
+        .addFile(
+            "/root/java/com/google/idea/blaze/plugin/run/Run.java",
+            "package com.google.idea.blaze.plugin.run;\n public class run {}")
+        .addFile(
+            "/root/java/com/google/idea/blaze/plugin/sync/Sync.java",
+            "package com.google.idea.blaze.plugin.sync;\n public class Sync {}")
+        .addFile(
+            "/root/java/com/google/idea/blaze/Incorrect.java",
+            "package com.google.idea.blaze.incorrect;\n public class Incorrect {}");
+    List<SourceArtifact> sourceArtifacts =
+        ImmutableList.of(
+            SourceArtifact.builder(LABEL)
+                .setArtifactLocation(
+                    ArtifactLocation.builder()
+                        .setRelativePath("java/com/google/idea/blaze/plugin/run/Run.java")
+                        .setRootPath("/root")
+                        .setIsSource(true))
+                .build(),
+            SourceArtifact.builder(LABEL)
+                .setArtifactLocation(
+                    ArtifactLocation.builder()
+                        .setRelativePath("java/com/google/idea/blaze/plugin/sync/Sync.java")
+                        .setRootPath("/root")
+                        .setIsSource(true))
+                .build(),
+            SourceArtifact.builder(LABEL)
+                .setArtifactLocation(
+                    ArtifactLocation.builder()
+                        .setRelativePath("java/com/google/idea/blaze/Incorrect.java")
+                        .setRootPath("/root")
+                        .setIsSource(true))
+                .build());
+    ImmutableList<BlazeContentEntry> result =
+        sourceDirectoryCalculator.calculateContentEntries(
+            project,
+            context,
+            workspaceRoot,
+            new TestSourceImportConfig(false),
+            decoder,
+            ImmutableList.of(new WorkspacePath("")),
+            sourceArtifacts,
+            NO_MANIFESTS);
+    issues.assertNoIssues();
+    assertThat(result)
+        .containsExactly(
+            BlazeContentEntry.builder("/root")
+                .addSource(BlazeSourceDirectory.builder("/root").setPackagePrefix("").build())
+                .addSource(BlazeSourceDirectory.builder("/root/java").setPackagePrefix("").build())
+                .addSource(
+                    BlazeSourceDirectory.builder("/root/java/com/google/idea/blaze")
+                        .setPackagePrefix("com.google.idea.blaze.incorrect")
+                        .build())
+                .addSource(
+                    BlazeSourceDirectory.builder("/root/java/com/google/idea/blaze/plugin")
+                        .setPackagePrefix("com.google.idea.blaze.plugin")
+                        .build())
+                .build());
+  }
+
+  @Test
+  public void testSourcesToSourceDirectories_multipleNonMatchingPackagesAreNotMerged()
+      throws Exception {
+    mockInputStreamProvider
+        .addFile("/root/java/com/google/Bla.java", "package com.google;\n public class Bla {}")
+        .addFile(
+            "/root/java/com/google/subpackage/Bla.java",
+            "package com.google.different;\n public class Bla {}");
+    List<SourceArtifact> sourceArtifacts =
+        ImmutableList.of(
+            SourceArtifact.builder(LABEL)
+                .setArtifactLocation(
+                    ArtifactLocation.builder()
+                        .setRelativePath("java/com/google/Bla.java")
+                        .setRootPath("/root")
+                        .setIsSource(true))
+                .build(),
+            SourceArtifact.builder(LABEL)
+                .setArtifactLocation(
+                    ArtifactLocation.builder()
+                        .setRelativePath("java/com/google/subpackage/Bla.java")
+                        .setRootPath("/root")
+                        .setIsSource(true))
+                .build());
+    ImmutableList<BlazeContentEntry> result =
+        sourceDirectoryCalculator.calculateContentEntries(
+            project,
+            context,
+            workspaceRoot,
+            new TestSourceImportConfig(false),
+            decoder,
+            ImmutableList.of(new WorkspacePath("java/com/google")),
+            sourceArtifacts,
+            NO_MANIFESTS);
+    issues.assertNoIssues();
+    assertThat(result)
+        .containsExactly(
+            BlazeContentEntry.builder("/root/java/com/google")
+                .addSource(
+                    BlazeSourceDirectory.builder("/root/java/com/google")
+                        .setPackagePrefix("com.google")
+                        .build())
+                .addSource(
+                    BlazeSourceDirectory.builder("/root/java/com/google/subpackage")
+                        .setPackagePrefix("com.google.different")
+                        .build())
+                .build());
+  }
+
+  @Test
+  public void testSourcesToSourceDirectories_childMatchesPathButParentDoesnt() throws Exception {
+    mockInputStreamProvider
+        .addFile("/root/java/com/google/Bla.java", "package com.facebook;\n public class Bla {}")
+        .addFile(
+            "/root/java/com/google/subpackage/Bla.java",
+            "package com.google.subpackage;\n public class Bla {}");
+    List<SourceArtifact> sourceArtifacts =
+        ImmutableList.of(
+            SourceArtifact.builder(LABEL)
+                .setArtifactLocation(
+                    ArtifactLocation.builder()
+                        .setRelativePath("java/com/google/Bla.java")
+                        .setRootPath("/root")
+                        .setIsSource(true))
+                .build(),
+            SourceArtifact.builder(LABEL)
+                .setArtifactLocation(
+                    ArtifactLocation.builder()
+                        .setRelativePath("java/com/google/subpackage/Bla.java")
+                        .setRootPath("/root")
+                        .setIsSource(true))
+                .build());
+    ImmutableList<BlazeContentEntry> result =
+        sourceDirectoryCalculator.calculateContentEntries(
+            project,
+            context,
+            workspaceRoot,
+            new TestSourceImportConfig(false),
+            decoder,
+            ImmutableList.of(new WorkspacePath("java/com/google")),
+            sourceArtifacts,
+            NO_MANIFESTS);
+    issues.assertNoIssues();
+    assertThat(result)
+        .containsExactly(
+            BlazeContentEntry.builder("/root/java/com/google")
+                .addSource(
+                    BlazeSourceDirectory.builder("/root/java/com/google")
+                        .setPackagePrefix("com.facebook")
+                        .build())
+                .addSource(
+                    BlazeSourceDirectory.builder("/root/java/com/google/subpackage")
+                        .setPackagePrefix("com.google.subpackage")
+                        .build())
+                .build());
+  }
+
+  @Test
+  public void testSourcesToSourceDirectories_orderIsIrrelevant() throws Exception {
+    mockInputStreamProvider
+        .addFile("/root/java/com/google/Bla.java", "package com.google;\n public class Bla {}")
+        .addFile(
+            "/root/java/com/google/subpackage/Bla.java",
+            "package com.google.different;\n public class Bla {}");
+    List<SourceArtifact> sourceArtifacts =
+        ImmutableList.of(
+            SourceArtifact.builder(LABEL)
+                .setArtifactLocation(
+                    ArtifactLocation.builder()
+                        .setRelativePath("java/com/google/subpackage/Bla.java")
+                        .setRootPath("/root")
+                        .setIsSource(true))
+                .build(),
+            SourceArtifact.builder(LABEL)
+                .setArtifactLocation(
+                    ArtifactLocation.builder()
+                        .setRelativePath("java/com/google/Bla.java")
+                        .setRootPath("/root")
+                        .setIsSource(true))
+                .build());
+    ImmutableList<BlazeContentEntry> result =
+        sourceDirectoryCalculator.calculateContentEntries(
+            project,
+            context,
+            workspaceRoot,
+            new TestSourceImportConfig(false),
+            decoder,
+            ImmutableList.of(new WorkspacePath("java/com/google")),
+            sourceArtifacts,
+            NO_MANIFESTS);
+    issues.assertNoIssues();
+    assertThat(result)
+        .containsExactly(
+            BlazeContentEntry.builder("/root/java/com/google")
+                .addSource(
+                    BlazeSourceDirectory.builder("/root/java/com/google")
+                        .setPackagePrefix("com.google")
+                        .build())
+                .addSource(
+                    BlazeSourceDirectory.builder("/root/java/com/google/subpackage")
+                        .setPackagePrefix("com.google.different")
+                        .build())
+                .build());
+  }
+
+  @Test
+  public void testSourcesToSourceDirectories_packagesMatchPath() throws Exception {
+    mockInputStreamProvider.addFile(
+        "/root/java/com/google/Bla.java", "package com.google;\n public class Bla {}");
+    List<SourceArtifact> sourceArtifacts =
+        ImmutableList.of(
+            SourceArtifact.builder(LABEL)
+                .setArtifactLocation(
+                    ArtifactLocation.builder()
+                        .setRelativePath("java/com/google/Bla.java")
+                        .setRootPath("/root")
+                        .setIsSource(true))
+                .build());
+    ImmutableList<BlazeContentEntry> result =
+        sourceDirectoryCalculator.calculateContentEntries(
+            project,
+            context,
+            workspaceRoot,
+            new TestSourceImportConfig(false),
+            decoder,
+            ImmutableList.of(new WorkspacePath("java/com/google")),
+            sourceArtifacts,
+            NO_MANIFESTS);
+    issues.assertNoIssues();
+    assertThat(result)
+        .containsExactly(
+            BlazeContentEntry.builder("/root/java/com/google")
+                .addSource(
+                    BlazeSourceDirectory.builder("/root/java/com/google")
+                        .setPackagePrefix("com.google")
+                        .build())
+                .build());
+  }
+
+  @Test
+  public void testSourcesToSourceDirectories_packagesDoNotMatchPath() throws Exception {
+    mockInputStreamProvider.addFile(
+        "/root/java/com/google/Bla.java", "package com.facebook;\n public class Bla {}");
+    List<SourceArtifact> sourceArtifacts =
+        ImmutableList.of(
+            SourceArtifact.builder(LABEL)
+                .setArtifactLocation(
+                    ArtifactLocation.builder()
+                        .setRelativePath("java/com/google/Bla.java")
+                        .setRootPath("/root")
+                        .setIsSource(true))
+                .build());
+    ImmutableList<BlazeContentEntry> result =
+        sourceDirectoryCalculator.calculateContentEntries(
+            project,
+            context,
+            workspaceRoot,
+            new TestSourceImportConfig(false),
+            decoder,
+            ImmutableList.of(new WorkspacePath("java/com/google")),
+            sourceArtifacts,
+            NO_MANIFESTS);
+    issues.assertNoIssues();
+    assertThat(result)
+        .containsExactly(
+            BlazeContentEntry.builder("/root/java/com/google")
+                .addSource(
+                    BlazeSourceDirectory.builder("/root/java/com/google")
+                        .setPackagePrefix("com.facebook")
+                        .build())
+                .build());
+  }
+
+  @Test
+  public void testSourcesToSourceDirectories_completePackagePathMismatch() throws Exception {
+    mockInputStreamProvider.addFile(
+        "/root/java/com/org/foo/Bla.java", "package com.facebook;\n public class Bla {}");
+    List<SourceArtifact> sourceArtifacts =
+        ImmutableList.of(
+            SourceArtifact.builder(LABEL)
+                .setArtifactLocation(
+                    ArtifactLocation.builder()
+                        .setRelativePath("java/com/org/foo/Bla.java")
+                        .setRootPath("/root")
+                        .setIsSource(true))
+                .build());
+    ImmutableList<BlazeContentEntry> result =
+        sourceDirectoryCalculator.calculateContentEntries(
+            project,
+            context,
+            workspaceRoot,
+            new TestSourceImportConfig(false),
+            decoder,
+            ImmutableList.of(new WorkspacePath("java/com/org")),
+            sourceArtifacts,
+            NO_MANIFESTS);
+    assertThat(result)
+        .containsExactly(
+            BlazeContentEntry.builder("/root/java/com/org")
+                .addSource(
+                    BlazeSourceDirectory.builder("/root/java/com/org")
+                        .setPackagePrefix("com.org")
+                        .build())
+                .addSource(
+                    BlazeSourceDirectory.builder("/root/java/com/org/foo")
+                        .setPackagePrefix("com.facebook")
+                        .build())
+                .build());
+  }
+
+  @Test
+  public void testSourcesToSourceDirectories_sourcesOutsideOfModuleGeneratesIssue()
+      throws Exception {
+    mockInputStreamProvider.addFile(
+        "/root/java/com/facebook/Bla.java", "package com.facebook;\n public class Bla {}");
+    List<SourceArtifact> sourceArtifacts =
+        ImmutableList.of(
+            SourceArtifact.builder(LABEL)
+                .setArtifactLocation(
+                    ArtifactLocation.builder()
+                        .setRelativePath("java/com/facebook/Bla.java")
+                        .setRootPath("/root")
+                        .setIsSource(true))
+                .build());
+    ImmutableList<BlazeContentEntry> result =
+        sourceDirectoryCalculator.calculateContentEntries(
+            project,
+            context,
+            workspaceRoot,
+            new TestSourceImportConfig(false),
+            decoder,
+            ImmutableList.of(new WorkspacePath("java/com/google")),
+            sourceArtifacts,
+            NO_MANIFESTS);
+
+    issues.assertIssueContaining("Did not add");
+  }
+
+  @Test
+  public void testSourcesToSourceDirectories_generatedSourcesOutsideOfModuleGeneratesNoIssue()
+      throws Exception {
+    mockInputStreamProvider.addFile(
+        "/root/java/com/facebook/Bla.java", "package com.facebook;\n public class Bla {}");
+    List<SourceArtifact> sourceArtifacts =
+        ImmutableList.of(
+            SourceArtifact.builder(LABEL)
+                .setArtifactLocation(
+                    ArtifactLocation.builder()
+                        .setRelativePath("java/com/facebook/Bla.java")
+                        .setRootPath("/root")
+                        .setIsSource(false))
+                .build());
+    ImmutableList<BlazeContentEntry> result =
+        sourceDirectoryCalculator.calculateContentEntries(
+            project,
+            context,
+            workspaceRoot,
+            new TestSourceImportConfig(false),
+            decoder,
+            ImmutableList.of(new WorkspacePath("java/com/google/my")),
+            sourceArtifacts,
+            NO_MANIFESTS);
+    issues.assertNoIssues();
+  }
+
+  @Test
+  public void testSourcesToSourceDirectories_missingPackageDeclaration() throws Exception {
+    mockInputStreamProvider.addFile("/root/java/com/google/Bla.java", "public class Bla {}");
+    List<SourceArtifact> sourceArtifacts =
+        ImmutableList.of(
+            SourceArtifact.builder(LABEL)
+                .setArtifactLocation(
+                    ArtifactLocation.builder()
+                        .setRelativePath("java/com/google/Bla.java")
+                        .setRootPath("/root")
+                        .setIsSource(true))
+                .build());
+    ImmutableList<BlazeContentEntry> result =
+        sourceDirectoryCalculator.calculateContentEntries(
+            project,
+            context,
+            workspaceRoot,
+            new TestSourceImportConfig(false),
+            decoder,
+            ImmutableList.of(new WorkspacePath("java/com/google")),
+            sourceArtifacts,
+            NO_MANIFESTS);
+
+    issues.assertIssueContaining("No package name string found");
+  }
+
+  @Test
+  public void testCompetingPackageDeclarationPicksMajority() throws Exception {
+    mockInputStreamProvider
+        .addFile(
+            "/root/java/com/google/Foo.java", "package com.google.different;\n public class Foo {}")
+        .addFile("/root/java/com/google/Bla.java", "package com.google;\n public class Bla {}")
+        .addFile("/root/java/com/google/Bla2.java", "package com.google;\n public class Bla2 {}")
+        .addFile("/root/java/com/google/Bla3.java", "package com.google;\n public class Bla3 {}");
+    List<SourceArtifact> sourceArtifacts =
+        ImmutableList.of(
+            SourceArtifact.builder(LABEL)
+                .setArtifactLocation(
+                    ArtifactLocation.builder()
+                        .setRelativePath("java/com/google/Bla.java")
+                        .setRootPath("/root")
+                        .setIsSource(true))
+                .build(),
+            SourceArtifact.builder(LABEL)
+                .setArtifactLocation(
+                    ArtifactLocation.builder()
+                        .setRelativePath("java/com/google/Bla2.java")
+                        .setRootPath("/root")
+                        .setIsSource(true))
+                .build(),
+            SourceArtifact.builder(LABEL)
+                .setArtifactLocation(
+                    ArtifactLocation.builder()
+                        .setRelativePath("java/com/google/Bla3.java")
+                        .setRootPath("/root")
+                        .setIsSource(true))
+                .build(),
+            SourceArtifact.builder(LABEL)
+                .setArtifactLocation(
+                    ArtifactLocation.builder()
+                        .setRelativePath("java/com/google/Foo.java")
+                        .setRootPath("/root")
+                        .setIsSource(true))
+                .build());
+    ImmutableList<BlazeContentEntry> result =
+        sourceDirectoryCalculator.calculateContentEntries(
+            project,
+            context,
+            workspaceRoot,
+            new TestSourceImportConfig(false),
+            decoder,
+            ImmutableList.of(new WorkspacePath("java/com/google")),
+            sourceArtifacts,
+            NO_MANIFESTS);
+    issues.assertNoIssues();
+    assertThat(result)
+        .containsExactly(
+            BlazeContentEntry.builder("/root/java/com/google")
+                .addSource(
+                    BlazeSourceDirectory.builder("/root/java/com/google")
+                        .setPackagePrefix("com.google")
+                        .build())
+                .build());
+  }
+
+  @Test
+  public void testSourcesToSourceDirectories_packagesMatchPathButNotAtRoot() throws Exception {
+    mockInputStreamProvider
+        .addFile(
+            "/root/java/com/google/Bla.java", "package com.google.different;\n public class Bla {}")
+        .addFile(
+            "/root/java/com/google/subpackage/Bla.java",
+            "package com.google.subpackage;\n public class Bla {}")
+        .addFile(
+            "/root/java/com/google/subpackage/subsubpackage/Bla.java",
+            "package com.google.subpackage.subsubpackage;\n public class Bla {}");
+    List<SourceArtifact> sourceArtifacts =
+        ImmutableList.of(
+            SourceArtifact.builder(LABEL)
+                .setArtifactLocation(
+                    ArtifactLocation.builder()
+                        .setRelativePath("java/com/google/Bla.java")
+                        .setRootPath("/root")
+                        .setIsSource(true))
+                .build(),
+            SourceArtifact.builder(LABEL)
+                .setArtifactLocation(
+                    ArtifactLocation.builder()
+                        .setRelativePath("java/com/google/subpackage/Bla.java")
+                        .setRootPath("/root")
+                        .setIsSource(true))
+                .build(),
+            SourceArtifact.builder(LABEL)
+                .setArtifactLocation(
+                    ArtifactLocation.builder()
+                        .setRelativePath("java/com/google/subpackage/subsubpackage/Bla.java")
+                        .setRootPath("/root")
+                        .setIsSource(true))
+                .build());
+    ImmutableList<BlazeContentEntry> result =
+        sourceDirectoryCalculator.calculateContentEntries(
+            project,
+            context,
+            workspaceRoot,
+            new TestSourceImportConfig(false),
+            decoder,
+            ImmutableList.of(new WorkspacePath("java/com/google")),
+            sourceArtifacts,
+            NO_MANIFESTS);
+    issues.assertNoIssues();
+    assertThat(result)
+        .containsExactly(
+            BlazeContentEntry.builder("/root/java/com/google")
+                .addSource(
+                    BlazeSourceDirectory.builder("/root/java/com/google")
+                        .setPackagePrefix("com.google.different")
+                        .build())
+                .addSource(
+                    BlazeSourceDirectory.builder("/root/java/com/google/subpackage")
+                        .setPackagePrefix("com.google.subpackage")
+                        .build())
+                .build());
+  }
+
+  @Test
+  public void testSourcesToSourceDirectories_multipleSubdirectoriesAreNotMerged() throws Exception {
+    mockInputStreamProvider
+        .addFile(
+            "/root/java/com/google/package0/Bla.java",
+            "package com.google.packagewrong0;\n public class Bla {}")
+        .addFile(
+            "/root/java/com/google/package1/Bla.java",
+            "package com.google.packagewrong1;\n public class Bla {}");
+    List<SourceArtifact> sourceArtifacts =
+        ImmutableList.of(
+            SourceArtifact.builder(LABEL)
+                .setArtifactLocation(
+                    ArtifactLocation.builder()
+                        .setRelativePath("java/com/google/package0/Bla.java")
+                        .setRootPath("/root")
+                        .setIsSource(true))
+                .build(),
+            SourceArtifact.builder(LABEL)
+                .setArtifactLocation(
+                    ArtifactLocation.builder()
+                        .setRelativePath("java/com/google/package1/Bla.java")
+                        .setRootPath("/root")
+                        .setIsSource(true))
+                .build());
+    ImmutableList<BlazeContentEntry> result =
+        sourceDirectoryCalculator.calculateContentEntries(
+            project,
+            context,
+            workspaceRoot,
+            new TestSourceImportConfig(false),
+            decoder,
+            ImmutableList.of(new WorkspacePath("java/com/google")),
+            sourceArtifacts,
+            NO_MANIFESTS);
+    issues.assertNoIssues();
+    assertThat(result)
+        .containsExactly(
+            BlazeContentEntry.builder("/root/java/com/google")
+                .addSource(
+                    BlazeSourceDirectory.builder("/root/java/com/google")
+                        .setPackagePrefix("com.google")
+                        .build())
+                .addSource(
+                    BlazeSourceDirectory.builder("/root/java/com/google/package0")
+                        .setPackagePrefix("com.google.packagewrong0")
+                        .build())
+                .addSource(
+                    BlazeSourceDirectory.builder("/root/java/com/google/package1")
+                        .setPackagePrefix("com.google.packagewrong1")
+                        .build())
+                .build());
+  }
+
+  @Test
+  public void testLowestDirectoryIsPrioritised() throws Exception {
+    mockInputStreamProvider
+        .addFile(
+            "/root/java/com/google/android/chimera/internal/Preconditions.java",
+            "package com.google.android.chimera.container.internal;\n "
+                + "public class Preconditions {}")
+        .addFile(
+            "/root/java/com/google/android/chimera/container/FileApkUtils.java",
+            "package com.google.android.chimera.container;\n public class FileApkUtils {}");
+    List<SourceArtifact> sourceArtifacts =
+        ImmutableList.of(
+            SourceArtifact.builder(LABEL)
+                .setArtifactLocation(
+                    ArtifactLocation.builder()
+                        .setRelativePath(
+                            "java/com/google/android/chimera/internal/Preconditions.java")
+                        .setRootPath("/root")
+                        .setIsSource(true))
+                .build(),
+            SourceArtifact.builder(LABEL)
+                .setArtifactLocation(
+                    ArtifactLocation.builder()
+                        .setRelativePath(
+                            "java/com/google/android/chimera/container/FileApkUtils.java")
+                        .setRootPath("/root")
+                        .setIsSource(true))
+                .build());
+    ImmutableList<BlazeContentEntry> result =
+        sourceDirectoryCalculator.calculateContentEntries(
+            project,
+            context,
+            workspaceRoot,
+            new TestSourceImportConfig(false),
+            decoder,
+            ImmutableList.of(new WorkspacePath("java/com/google/android")),
+            sourceArtifacts,
+            NO_MANIFESTS);
+    issues.assertNoIssues();
+    assertThat(result)
+        .containsExactly(
+            BlazeContentEntry.builder("/root/java/com/google/android")
+                .addSource(
+                    BlazeSourceDirectory.builder("/root/java/com/google/android")
+                        .setPackagePrefix("com.google.android")
+                        .build())
+                .addSource(
+                    BlazeSourceDirectory.builder("/root/java/com/google/android/chimera/internal")
+                        .setPackagePrefix("com.google.android.chimera.container.internal")
+                        .build())
+                .build());
+  }
+
+  @Test
+  public void testNewFormatManifest() throws Exception {
+    setNewFormatPackageManifest(
+        "/root/java/com/test.manifest",
+        ImmutableList.of(
+            PackageManifestOuterClass.ArtifactLocation.newBuilder()
+                .setRelativePath("java/com/google/Bla.java")
+                .setIsSource(true)
+                .build()),
+        ImmutableList.of("com.google"));
+    ImmutableMap<Label, ArtifactLocation> manifests =
+        ImmutableMap.<Label, ArtifactLocation>builder()
+            .put(
+                LABEL,
+                ArtifactLocation.builder()
+                    .setRelativePath("java/com/test.manifest")
+                    .setRootPath("/root")
+                    .setIsSource(true)
+                    .build())
+            .build();
+    Map<Label, Map<String, String>> manifestMap =
+        readPackageManifestFiles(manifests, getDecoder("/root"));
+
+    assertThat(manifestMap.get(LABEL))
+        .containsEntry("/root/java/com/google/Bla.java", "com.google");
+  }
+
+  @Test
+  public void testManifestSingleFile() throws Exception {
+    setPackageManifest(
+        "/root/java/com/test.manifest",
+        ImmutableList.of("java/com/google/Bla.java"),
+        ImmutableList.of("com.google"));
+    ImmutableMap<Label, ArtifactLocation> manifests =
+        ImmutableMap.<Label, ArtifactLocation>builder()
+            .put(
+                LABEL,
+                ArtifactLocation.builder()
+                    .setRelativePath("java/com/test.manifest")
+                    .setRootPath("/root")
+                    .setIsSource(true)
+                    .build())
+            .build();
+    Map<Label, Map<String, String>> manifestMap =
+        readPackageManifestFiles(manifests, getDecoder("/root"));
+
+    assertThat(manifestMap.get(LABEL))
+        .containsEntry("/root/java/com/google/Bla.java", "com.google");
+  }
+
+  @Test
+  public void testManifestRepeatedSources() throws Exception {
+    setPackageManifest(
+        "/root/java/com/test.manifest",
+        ImmutableList.of("java/com/google/Bla.java", "java/com/google/Foo.java"),
+        ImmutableList.of("com.google", "com.google.subpackage"));
+    setPackageManifest(
+        "/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<Label, ArtifactLocation> manifests =
+        ImmutableMap.<Label, ArtifactLocation>builder()
+            .put(
+                new Label("//a:a"),
+                ArtifactLocation.builder()
+                    .setRelativePath("java/com/test.manifest")
+                    .setRootPath("/root")
+                    .setIsSource(true)
+                    .build())
+            .put(
+                new Label("//b:b"),
+                ArtifactLocation.builder()
+                    .setRelativePath("java/com/test2.manifest")
+                    .setRootPath("/root")
+                    .setIsSource(true)
+                    .build())
+            .build();
+    Map<Label, Map<String, String>> manifestMap =
+        readPackageManifestFiles(manifests, getDecoder("/root"));
+
+    assertThat(manifestMap).hasSize(2);
+
+    assertThat(manifestMap.get(new Label("//a:a")))
+        .containsEntry("/root/java/com/google/Bla.java", "com.google");
+    assertThat(manifestMap.get(new Label("//a:a")))
+        .containsEntry("/root/java/com/google/Foo.java", "com.google.subpackage");
+    assertThat(manifestMap.get(new Label("//b:b")))
+        .containsEntry("/root/java/com/google/other/Temp.java", "com.google.other");
+  }
+
+  @Test
+  public void testManifestMissingSourcesFallback() throws Exception {
+    setPackageManifest(
+        "/root/java/com/test.manifest",
+        ImmutableList.of("java/com/google/Bla.java", "java/com/google/Foo.java"),
+        ImmutableList.of("com.google", "com.google"));
+
+    mockInputStreamProvider.addFile(
+        "/root/java/com/google/subpackage/Bla.java",
+        "package com.google.different;\n public class Bla {}");
+
+    ImmutableMap<Label, ArtifactLocation> manifests =
+        ImmutableMap.<Label, ArtifactLocation>builder()
+            .put(
+                LABEL,
+                ArtifactLocation.builder()
+                    .setRelativePath("java/com/test.manifest")
+                    .setRootPath("/root")
+                    .setIsSource(true)
+                    .build())
+            .build();
+
+    List<SourceArtifact> sourceArtifacts =
+        ImmutableList.of(
+            SourceArtifact.builder(LABEL)
+                .setArtifactLocation(
+                    ArtifactLocation.builder()
+                        .setRelativePath("java/com/google/Bla.java")
+                        .setRootPath("/root")
+                        .setIsSource(true))
+                .build(),
+            SourceArtifact.builder(LABEL)
+                .setArtifactLocation(
+                    ArtifactLocation.builder()
+                        .setRelativePath("java/com/google/Foo.java")
+                        .setRootPath("/root")
+                        .setIsSource(true))
+                .build(),
+            SourceArtifact.builder(LABEL)
+                .setArtifactLocation(
+                    ArtifactLocation.builder()
+                        .setRelativePath("java/com/google/subpackage/Bla.java")
+                        .setRootPath("/root")
+                        .setIsSource(true))
+                .build());
+
+    ImmutableList<BlazeContentEntry> result =
+        sourceDirectoryCalculator.calculateContentEntries(
+            project,
+            context,
+            workspaceRoot,
+            new TestSourceImportConfig(false),
+            getDecoder("/root"),
+            ImmutableList.of(new WorkspacePath("java/com/google")),
+            sourceArtifacts,
+            manifests);
+
+    issues.assertNoIssues();
+    assertThat(result)
+        .containsExactly(
+            BlazeContentEntry.builder("/root/java/com/google")
+                .addSource(
+                    BlazeSourceDirectory.builder("/root/java/com/google")
+                        .setPackagePrefix("com.google")
+                        .build())
+                .addSource(
+                    BlazeSourceDirectory.builder("/root/java/com/google/subpackage")
+                        .setPackagePrefix("com.google.different")
+                        .build())
+                .build());
+  }
+
+  private void setPackageManifest(
+      String manifestPath,
+      List<String> sourceRelativePaths,
+      List<String> packages) {
+    PackageManifest.Builder manifest = PackageManifest.newBuilder();
+    for (int i = 0; i < sourceRelativePaths.size(); i++) {
+      String sourceRelativePath = sourceRelativePaths.get(i);
+      PackageManifestOuterClass.ArtifactLocation source =
+          PackageManifestOuterClass.ArtifactLocation.newBuilder()
+              .setRelativePath(sourceRelativePath)
+              .setIsSource(true)
+              .build();
+      manifest.addSources(
+          JavaSourcePackage.newBuilder()
+              .setArtifactLocation(source)
+              .setPackageString(packages.get(i)));
+    }
+    mockInputStreamProvider.addFile(manifestPath, manifest.build().toByteArray());
+  }
+
+  private void setNewFormatPackageManifest(
+      String manifestPath,
+      List<PackageManifestOuterClass.ArtifactLocation> sources,
+      List<String> packages) {
+    PackageManifest.Builder manifest = PackageManifest.newBuilder();
+    for (int i = 0; i < sources.size(); i++) {
+      manifest.addSources(
+          JavaSourcePackage.newBuilder()
+              .setArtifactLocation(sources.get(i))
+              .setPackageString(packages.get(i)));
+    }
+    mockInputStreamProvider.addFile(manifestPath, manifest.build().toByteArray());
+  }
+
+  private static ArtifactLocationDecoder getDecoder(String rootPath) {
+    File root = new File(rootPath);
+    WorkspaceRoot workspaceRoot = new WorkspaceRoot(root);
+    BlazeRoots roots =
+        new BlazeRoots(
+            root,
+            ImmutableList.of(root),
+            new ExecutionRootPath("out/crosstool/bin"),
+            new ExecutionRootPath("out/crosstool/gen"));
+    return new ArtifactLocationDecoder(roots, new WorkspacePathResolverImpl(workspaceRoot, roots));
+  }
+
+  private static class MockInputStreamProvider implements InputStreamProvider {
+
+    private final Map<String, InputStream> javaFiles = new HashMap<String, InputStream>();
+
+    public MockInputStreamProvider addFile(String filePath, String javaSrc) {
+      try {
+        addFile(filePath, javaSrc.getBytes("UTF-8"));
+      } catch (UnsupportedEncodingException e) {
+        fail(e.getMessage());
+      }
+      return this;
+    }
+
+    public MockInputStreamProvider addFile(String filePath, byte[] contents) {
+      javaFiles.put(filePath, new ByteArrayInputStream(contents));
+      return this;
+    }
+
+    @Override
+    public InputStream getFile(@NotNull File path) throws FileNotFoundException {
+      final InputStream inputStream = javaFiles.get(path.getPath());
+      if (inputStream == null) {
+        throw new FileNotFoundException(
+            path + " has not been mapped into MockInputStreamProvider.");
+      }
+      return inputStream;
+    }
+  }
+
+  private Map<Label, Map<String, String>> readPackageManifestFiles(
+      Map<Label, ArtifactLocation> manifests, ArtifactLocationDecoder decoder) {
+    return PackageManifestReader.getInstance()
+        .readPackageManifestFiles(
+            project, context, decoder, manifests, MoreExecutors.sameThreadExecutor());
+  }
+
+  static class MockFileAttributeProvider extends FileAttributeProvider {
+    @Override
+    public long getFileModifiedTime(@NotNull File file) {
+      return 1;
+    }
+  }
+}
diff --git a/plugin_dev/BUILD b/plugin_dev/BUILD
new file mode 100644
index 0000000..0e90f5a
--- /dev/null
+++ b/plugin_dev/BUILD
@@ -0,0 +1,78 @@
+licenses(["notice"])  # Apache 2.0
+
+java_library(
+    name = "plugin_dev",
+    srcs = glob(["src/**/*.java"]),
+    visibility = ["//visibility:public"],
+    deps = [
+        "//base",
+        "//intellij_platform_sdk:devkit",
+        "//intellij_platform_sdk:plugin_api",
+        "//java",
+        "@jsr305_annotations//jar",
+    ],
+)
+
+filegroup(
+    name = "plugin_xml",
+    srcs = ["src/META-INF/blaze-plugin-dev.xml"],
+    visibility = ["//visibility:public"],
+)
+
+load(
+    "//build_defs:build_defs.bzl",
+    "merged_plugin_xml",
+    "stamped_plugin_xml",
+    "intellij_plugin",
+)
+
+merged_plugin_xml(
+    name = "merged_plugin_xml",
+    srcs = [
+        "//base:plugin_xml",
+        "//java:plugin_xml",
+    ] + [
+        ":plugin_xml",
+    ],
+)
+
+stamped_plugin_xml(
+    name = "plugin_dev_plugin_xml",
+    plugin_id = "com.google.idea.blaze.plugin_dev",
+    plugin_name = "com.google.idea.blaze.plugin_dev",
+    plugin_xml = "merged_plugin_xml",
+)
+
+intellij_plugin(
+    name = "plugin_dev_integration_test_plugin",
+    testonly = 1,
+    plugin_xml = ":plugin_dev_plugin_xml",
+    deps = [
+        ":plugin_dev",
+        "//base",
+        "//java",
+    ],
+)
+
+load(
+    "//intellij_test:test_defs.bzl",
+    "intellij_integration_test_suite",
+)
+
+intellij_integration_test_suite(
+    name = "integration_tests",
+    srcs = glob(["tests/integrationtests/**/*.java"]),
+    required_plugins = "com.google.idea.blaze.ijwb",
+    test_package_root = "com.google.idea.blaze.plugin",
+    runtime_deps = [
+        ":plugin_dev_integration_test_plugin",
+    ],
+    deps = [
+        ":plugin_dev",
+        "//base",
+        "//base:integration_test_utils",
+        "//base:unit_test_utils",
+        "//intellij_platform_sdk:plugin_api_for_tests",
+        "@jsr305_annotations//jar",
+    ],
+)
diff --git a/blaze-plugin-dev/src/META-INF/blaze-plugin-dev.xml b/plugin_dev/src/META-INF/blaze-plugin-dev.xml
similarity index 100%
rename from blaze-plugin-dev/src/META-INF/blaze-plugin-dev.xml
rename to plugin_dev/src/META-INF/blaze-plugin-dev.xml
diff --git a/plugin_dev/src/com/google/idea/blaze/plugin/IntellijPluginRule.java b/plugin_dev/src/com/google/idea/blaze/plugin/IntellijPluginRule.java
new file mode 100644
index 0000000..5e5668d
--- /dev/null
+++ b/plugin_dev/src/com/google/idea/blaze/plugin/IntellijPluginRule.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.plugin;
+
+import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+import com.google.idea.blaze.base.model.primitives.Kind;
+
+/** Utility methods for intellij_plugin blaze rules */
+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 boolean isPluginRule(RuleIdeInfo rule) {
+    return isPluginBundle(rule) || isSinglePluginRule(rule);
+  }
+
+  public static boolean isPluginBundle(RuleIdeInfo rule) {
+    return rule.kindIsOneOf(Kind.JAVA_LIBRARY) && rule.tags.contains(RULE_TAG_IJ_PLUGIN_BUNDLE);
+  }
+
+  public static boolean isSinglePluginRule(RuleIdeInfo rule) {
+    return rule.kindIsOneOf(Kind.JAVA_IMPORT) && rule.tags.contains(RULE_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
new file mode 100644
index 0000000..d909032
--- /dev/null
+++ b/plugin_dev/src/com/google/idea/blaze/plugin/run/BlazeIntellijPluginConfiguration.java
@@ -0,0 +1,531 @@
+/*
+ * 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.plugin.run;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Joiner;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Ordering;
+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.LibraryArtifact;
+import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+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.settings.Blaze;
+import com.google.idea.blaze.base.ui.UiUtil;
+import com.google.idea.blaze.plugin.IntellijPluginRule;
+import com.intellij.execution.ExecutionException;
+import com.intellij.execution.Executor;
+import com.intellij.execution.configurations.ConfigurationFactory;
+import com.intellij.execution.configurations.JavaCommandLineState;
+import com.intellij.execution.configurations.JavaParameters;
+import com.intellij.execution.configurations.LocatableConfigurationBase;
+import com.intellij.execution.configurations.ModuleRunConfiguration;
+import com.intellij.execution.configurations.ParametersList;
+import com.intellij.execution.configurations.RunProfileState;
+import com.intellij.execution.configurations.RuntimeConfigurationError;
+import com.intellij.execution.configurations.RuntimeConfigurationException;
+import com.intellij.execution.process.OSProcessHandler;
+import com.intellij.execution.process.ProcessAdapter;
+import com.intellij.execution.process.ProcessEvent;
+import com.intellij.execution.runners.ExecutionEnvironment;
+import com.intellij.ide.plugins.IdeaPluginDescriptor;
+import com.intellij.ide.plugins.PluginManagerCore;
+import com.intellij.openapi.application.JetBrainsProtocolHandler;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.options.ConfigurationException;
+import com.intellij.openapi.options.SettingsEditor;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.projectRoots.ProjectJdkTable;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.roots.ProjectRootManager;
+import com.intellij.openapi.roots.ui.configuration.JdkComboBox;
+import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel;
+import com.intellij.openapi.ui.ComboBox;
+import com.intellij.openapi.ui.LabeledComponent;
+import com.intellij.openapi.util.BuildNumber;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.ui.ListCellRendererWrapper;
+import com.intellij.ui.RawCommandLineEditor;
+import com.intellij.util.PlatformUtils;
+import com.intellij.util.execution.ParametersListUtil;
+import java.awt.BorderLayout;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
+import java.util.Collection;
+import java.util.List;
+import javax.annotation.Nullable;
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JTextArea;
+import org.jdom.Element;
+
+/**
+ * A run configuration that builds a plugin jar via blaze, copies it to the SDK sandbox, then runs
+ * IJ with the plugin loaded.
+ */
+public class BlazeIntellijPluginConfiguration extends LocatableConfigurationBase
+    implements BlazeRunConfiguration, ModuleRunConfiguration {
+
+  private static final String TARGET_TAG = "blaze-target";
+  private static final String USER_BLAZE_FLAG_TAG = "blaze-user-flag";
+  private static final String USER_EXE_FLAG_TAG = "blaze-user-exe-flag";
+  private static final String SDK_ATTR = "blaze-plugin-sdk";
+  private static final String VM_PARAMS_ATTR = "blaze-vm-params";
+  private static final String PROGRAM_PARAMS_ATTR = "blaze-program-params";
+
+  private final String buildSystem;
+
+  @Nullable private Label target;
+  private ImmutableList<String> blazeFlags = ImmutableList.of();
+  private ImmutableList<String> exeFlags = ImmutableList.of();
+  @Nullable private Sdk pluginSdk;
+  @Nullable String vmParameters;
+  @Nullable private String programParameters;
+
+  public BlazeIntellijPluginConfiguration(
+      Project project,
+      ConfigurationFactory factory,
+      String name,
+      @Nullable RuleIdeInfo initialRule) {
+    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;
+    }
+  }
+
+  @Override
+  @Nullable
+  public Label getTarget() {
+    return target;
+  }
+
+  public void setTarget(Label target) {
+    this.target = target;
+  }
+
+  private ImmutableList<File> findPluginJars() throws ExecutionException {
+    RuleIdeInfo rule = RuleFinder.getInstance().ruleForTarget(getProject(), getTarget());
+    if (rule == null) {
+      throw new ExecutionException(
+          buildSystem + " rule '" + getTarget() + "' not imported during sync");
+    }
+    return IntellijPluginRule.isPluginBundle(rule)
+        ? findBundledJars(rule)
+        : ImmutableList.of(findPluginJar(rule));
+  }
+
+  private ImmutableList<File> findBundledJars(RuleIdeInfo rule) 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(depRule));
+      }
+    }
+    return jars.build();
+  }
+
+  private File findPluginJar(RuleIdeInfo rule) throws ExecutionException {
+    JavaRuleIdeInfo javaRuleIdeInfo = rule.javaRuleIdeInfo;
+    if (!IntellijPluginRule.isSinglePluginRule(rule) || javaRuleIdeInfo == null) {
+      throw new ExecutionException(
+          buildSystem + " rule '" + rule.label + "' is not a valid intellij_plugin rule");
+    }
+    Collection<LibraryArtifact> jars = javaRuleIdeInfo.jars;
+    if (javaRuleIdeInfo.jars.size() > 1) {
+      throw new ExecutionException("Invalid IntelliJ plugin rule: 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 + "'");
+    }
+    return artifact.classJar.getFile();
+  }
+
+  /**
+   * Plugin jar has been previously created via blaze build. This method: - copies jar to sandbox
+   * environment - cracks open jar and finds plugin.xml (with ID, etc., needed for JVM args) - sets
+   * up the SDK, etc. (use project SDK?) - sets up the JVM, and returns a JavaCommandLineState
+   */
+  @Nullable
+  @Override
+  public RunProfileState getState(Executor executor, ExecutionEnvironment env)
+      throws ExecutionException {
+    final Sdk ideaJdk = pluginSdk;
+    if (!IdeaJdkHelper.isIdeaJdk(ideaJdk)) {
+      throw new ExecutionException("Choose an IntelliJ Platform Plugin SDK");
+    }
+    String sandboxHome = IdeaJdkHelper.getSandboxHome(ideaJdk);
+    if (sandboxHome == null) {
+      throw new ExecutionException("No sandbox specified for IntelliJ Platform Plugin SDK");
+    }
+
+    try {
+      sandboxHome = new File(sandboxHome).getCanonicalPath();
+    } catch (IOException e) {
+      throw new ExecutionException("No sandbox specified for IntelliJ Platform Plugin SDK");
+    }
+    final String canonicalSandbox = sandboxHome;
+    final ImmutableList<File> pluginJars = findPluginJars();
+    for (File file : pluginJars) {
+      if (!file.exists()) {
+        throw new ExecutionException(
+            String.format(
+                "Plugin jar '%s' not found. Did the %s build fail?", file.getName(), buildSystem));
+      }
+    }
+    // copy license from running instance of idea
+    IdeaJdkHelper.copyIDEALicense(sandboxHome);
+
+    final JavaCommandLineState state =
+        new JavaCommandLineState(env) {
+          @Override
+          protected JavaParameters createJavaParameters() throws ExecutionException {
+            String buildNumber = IdeaJdkHelper.getBuildNumber(ideaJdk);
+            List<String> pluginIds = Lists.newArrayList();
+            for (File jar : pluginJars) {
+              pluginIds.add(copyPluginJarToSandbox(jar, buildNumber, canonicalSandbox));
+            }
+
+            final JavaParameters params = new JavaParameters();
+
+            ParametersList vm = params.getVMParametersList();
+
+            fillParameterList(vm, vmParameters);
+            fillParameterList(params.getProgramParametersList(), programParameters);
+
+            IntellijWithPluginClasspathHelper.addRequiredVmParams(params, ideaJdk);
+
+            vm.defineProperty(
+                JetBrainsProtocolHandler.REQUIRED_PLUGINS_KEY, Joiner.on(',').join(pluginIds));
+
+            if (!vm.hasProperty(PlatformUtils.PLATFORM_PREFIX_KEY) && buildNumber != null) {
+              String prefix = IdeaJdkHelper.getPlatformPrefix(buildNumber);
+              if (prefix != null) {
+                vm.defineProperty(PlatformUtils.PLATFORM_PREFIX_KEY, prefix);
+              }
+            }
+            return params;
+          }
+
+          @Override
+          protected OSProcessHandler startProcess() throws ExecutionException {
+            final OSProcessHandler handler = super.startProcess();
+            handler.addProcessListener(
+                new ProcessAdapter() {
+                  @Override
+                  public void processTerminated(ProcessEvent event) {
+                    for (File jar : pluginJars) {
+                      pluginDestination(jar, canonicalSandbox).delete();
+                    }
+                  }
+                });
+            return handler;
+          }
+        };
+    return state;
+  }
+
+  private static File pluginDestination(File jar, String sandboxPath) {
+    return new File(sandboxPath, "plugins/" + jar.getName());
+  }
+
+  /** Copies the plugin jar to the sandbox, and returns the plugin ID. */
+  private static String copyPluginJarToSandbox(File jar, String buildNumber, String sandboxPath)
+      throws ExecutionException {
+    IdeaPluginDescriptor pluginDescriptor = PluginManagerCore.loadDescriptor(jar, "plugin.xml");
+    if (PluginManagerCore.isIncompatible(pluginDescriptor, BuildNumber.fromString(buildNumber))) {
+      throw new ExecutionException(
+          String.format(
+              "Plugin SDK version '%s' is incompatible with this plugin "
+                  + "(since: '%s', until: '%s')",
+              buildNumber, pluginDescriptor.getSinceBuild(), pluginDescriptor.getUntilBuild()));
+    }
+    File pluginJarDestination = pluginDestination(jar, sandboxPath);
+    try {
+      pluginJarDestination.getParentFile().mkdirs();
+      Files.copy(jar.toPath(), pluginJarDestination.toPath(), StandardCopyOption.REPLACE_EXISTING);
+    } catch (IOException e) {
+      throw new ExecutionException("Error copying plugin jar to sandbox", e);
+    }
+    return pluginDescriptor.getPluginId().getIdString();
+  }
+
+  private static void fillParameterList(ParametersList list, @Nullable String value) {
+    if (value == null) {
+      return;
+    }
+
+    for (String parameter : value.split(" ")) {
+      if (parameter != null && parameter.length() > 0) {
+        list.add(parameter);
+      }
+    }
+  }
+
+  @Override
+  public Module[] getModules() {
+    return Module.EMPTY_ARRAY;
+  }
+
+  @Override
+  public void checkConfiguration() throws RuntimeConfigurationException {
+    super.checkConfiguration();
+
+    Label target = getTarget();
+    if (target == null) {
+      throw new RuntimeConfigurationError("Select a target to run");
+    }
+    RuleIdeInfo rule = RuleFinder.getInstance().ruleForTarget(getProject(), target);
+    if (rule == null) {
+      throw new RuntimeConfigurationError("The selected target does not exist.");
+    }
+    if (!IntellijPluginRule.isPluginRule(rule)) {
+      throw new RuntimeConfigurationError("The selected target is not an intellij_plugin");
+    }
+    if (!IdeaJdkHelper.isIdeaJdk(pluginSdk)) {
+      throw new RuntimeConfigurationError("Select an IntelliJ Platform Plugin SDK");
+    }
+  }
+
+  @Override
+  public void readExternal(Element element) throws InvalidDataException {
+    super.readExternal(element);
+    // Target is persisted as a tag to permit multiple targets in the future.
+    Element targetElement = element.getChild(TARGET_TAG);
+    if (targetElement != null && !Strings.isNullOrEmpty(targetElement.getTextTrim())) {
+      target = (Label) TargetExpression.fromString(targetElement.getTextTrim());
+    } else {
+      target = null;
+    }
+    blazeFlags = loadUserFlags(element, USER_BLAZE_FLAG_TAG);
+    exeFlags = loadUserFlags(element, USER_EXE_FLAG_TAG);
+
+    String sdkName = element.getAttributeValue(SDK_ATTR);
+    if (!Strings.isNullOrEmpty(sdkName)) {
+      pluginSdk = ProjectJdkTable.getInstance().findJdk(sdkName);
+    }
+    vmParameters = Strings.emptyToNull(element.getAttributeValue(VM_PARAMS_ATTR));
+    programParameters = Strings.emptyToNull(element.getAttributeValue(PROGRAM_PARAMS_ATTR));
+  }
+
+  private static ImmutableList<String> loadUserFlags(Element root, String tag) {
+    ImmutableList.Builder<String> flagsBuilder = ImmutableList.builder();
+    for (Element e : root.getChildren(tag)) {
+      String flag = e.getTextTrim();
+      if (flag != null && !flag.isEmpty()) {
+        flagsBuilder.add(flag);
+      }
+    }
+    return flagsBuilder.build();
+  }
+
+  private static void saveUserFlags(Element root, List<String> flags, String tag) {
+    for (String flag : flags) {
+      Element child = new Element(tag);
+      child.setText(flag);
+      root.addContent(child);
+    }
+  }
+
+  @Override
+  public void writeExternal(Element element) throws WriteExternalException {
+    super.writeExternal(element);
+    if (target != null) {
+      // Target is persisted as a tag to permit multiple targets in the future.
+      Element targetElement = new Element(TARGET_TAG);
+      targetElement.setText(target.toString());
+      element.addContent(targetElement);
+    }
+    saveUserFlags(element, blazeFlags, USER_BLAZE_FLAG_TAG);
+    saveUserFlags(element, exeFlags, USER_EXE_FLAG_TAG);
+    if (pluginSdk != null) {
+      element.setAttribute(SDK_ATTR, pluginSdk.getName());
+    }
+    if (vmParameters != null) {
+      element.setAttribute(VM_PARAMS_ATTR, vmParameters);
+    }
+    if (programParameters != null) {
+      element.setAttribute(PROGRAM_PARAMS_ATTR, programParameters);
+    }
+  }
+
+  @Override
+  public BlazeIntellijPluginConfiguration clone() {
+    final BlazeIntellijPluginConfiguration configuration =
+        (BlazeIntellijPluginConfiguration) super.clone();
+    configuration.target = target;
+    configuration.blazeFlags = blazeFlags;
+    configuration.exeFlags = exeFlags;
+    configuration.pluginSdk = pluginSdk;
+    configuration.vmParameters = vmParameters;
+    configuration.programParameters = programParameters;
+    return configuration;
+  }
+
+  protected BlazeCommand buildBlazeCommand(Project project, ProjectViewSet projectViewSet) {
+    BlazeCommand.Builder command =
+        BlazeCommand.builder(Blaze.getBuildSystem(getProject()), BlazeCommandName.BUILD)
+            .addTargets(getTarget())
+            .addBlazeFlags(BlazeFlags.buildFlags(project, projectViewSet))
+            .addBlazeFlags(blazeFlags)
+            .addExeFlags(exeFlags);
+    return command.build();
+  }
+
+  @Override
+  public BlazeIntellijPluginConfigurationSettingsEditor getConfigurationEditor() {
+    List<RuleIdeInfo> javaRules =
+        RuleFinder.getInstance().findRules(getProject(), IntellijPluginRule::isPluginRule);
+    List<Label> javaLabels = Lists.newArrayList();
+    for (RuleIdeInfo rule : javaRules) {
+      javaLabels.add(rule.label);
+    }
+    return new BlazeIntellijPluginConfigurationSettingsEditor(buildSystem, javaLabels);
+  }
+
+  @Override
+  @Nullable
+  public String suggestedName() {
+    Label target = getTarget();
+    if (target == null) {
+      return null;
+    }
+    return new BlazeConfigurationNameBuilder()
+        .setBuildSystemName(getProject())
+        .setCommandName("build")
+        .setTargetString(target)
+        .build();
+  }
+
+  @VisibleForTesting
+  static class BlazeIntellijPluginConfigurationSettingsEditor
+      extends SettingsEditor<BlazeIntellijPluginConfiguration> {
+    private final String buildSystemName;
+    private final ComboBox targetCombo;
+    private final JTextArea blazeFlagsField = new JTextArea(5, 0);
+    private final JTextArea exeFlagsField = new JTextArea(5, 0);
+    private final JdkComboBox sdkCombo;
+    private final LabeledComponent<RawCommandLineEditor> vmParameters = new LabeledComponent<>();
+    private final LabeledComponent<RawCommandLineEditor> programParameters =
+        new LabeledComponent<>();
+
+    public BlazeIntellijPluginConfigurationSettingsEditor(
+        String buildSystemName, List<Label> javaLabels) {
+      this.buildSystemName = buildSystemName;
+      targetCombo =
+          new ComboBox(
+              new DefaultComboBoxModel(Ordering.usingToString().sortedCopy(javaLabels).toArray()));
+      targetCombo.setRenderer(
+          new ListCellRendererWrapper<Label>() {
+            @Override
+            public void customize(
+                JList list, @Nullable Label value, int index, boolean selected, boolean hasFocus) {
+              setText(value == null ? null : value.toString());
+            }
+          });
+
+      ProjectSdksModel sdksModel = new ProjectSdksModel();
+      sdksModel.reset(null);
+      sdkCombo = new JdkComboBox(sdksModel, IdeaJdkHelper::isIdeaJdkType);
+    }
+
+    @VisibleForTesting
+    @Override
+    public void resetEditorFrom(BlazeIntellijPluginConfiguration s) {
+      targetCombo.setSelectedItem(s.getTarget());
+      blazeFlagsField.setText(ParametersListUtil.join(s.blazeFlags));
+      exeFlagsField.setText(ParametersListUtil.join(s.exeFlags));
+      if (s.pluginSdk != null) {
+        sdkCombo.setSelectedJdk(s.pluginSdk);
+      } else {
+        s.pluginSdk = sdkCombo.getSelectedJdk();
+      }
+      if (s.vmParameters != null) {
+        vmParameters.getComponent().setText(s.vmParameters);
+      }
+      if (s.programParameters != null) {
+        programParameters.getComponent().setText(s.programParameters);
+      }
+    }
+
+    @VisibleForTesting
+    @Override
+    public void applyEditorTo(BlazeIntellijPluginConfiguration s) throws ConfigurationException {
+      try {
+        s.target = (Label) targetCombo.getSelectedItem();
+      } catch (ClassCastException e) {
+        throw new ConfigurationException("Invalid label specified.");
+      }
+      s.blazeFlags =
+          ImmutableList.copyOf(
+              ParametersListUtil.parse(Strings.nullToEmpty(blazeFlagsField.getText())));
+      s.exeFlags =
+          ImmutableList.copyOf(
+              ParametersListUtil.parse(Strings.nullToEmpty(exeFlagsField.getText())));
+      s.pluginSdk = sdkCombo.getSelectedJdk();
+      s.vmParameters = vmParameters.getComponent().getText();
+      s.programParameters = programParameters.getComponent().getText();
+    }
+
+    @Override
+    protected JComponent createEditor() {
+      vmParameters.setText("VM options:");
+      vmParameters.setComponent(new RawCommandLineEditor());
+      vmParameters.getComponent().setDialogCaption(vmParameters.getRawText());
+      vmParameters.setLabelLocation(BorderLayout.WEST);
+
+      programParameters.setText("Program arguments");
+      programParameters.setComponent(new RawCommandLineEditor());
+      programParameters.getComponent().setDialogCaption(programParameters.getRawText());
+      programParameters.setLabelLocation(BorderLayout.WEST);
+
+      return UiUtil.createBox(
+          new JLabel("Target:"),
+          targetCombo,
+          new JLabel("Plugin SDK"),
+          sdkCombo,
+          vmParameters.getLabel(),
+          vmParameters.getComponent(),
+          programParameters.getLabel(),
+          programParameters.getComponent(),
+          new JLabel(buildSystemName + " flags:"),
+          blazeFlagsField,
+          new JLabel("Executable flags:"),
+          exeFlagsField);
+    }
+  }
+}
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
new file mode 100644
index 0000000..38baa96
--- /dev/null
+++ b/plugin_dev/src/com/google/idea/blaze/plugin/run/BlazeIntellijPluginConfigurationType.java
@@ -0,0 +1,166 @@
+/*
+ * 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.plugin.run;
+
+import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+import com.google.idea.blaze.base.model.primitives.WorkspaceType;
+import com.google.idea.blaze.base.run.BlazeRuleConfigurationFactory;
+import com.google.idea.blaze.base.run.rulefinder.RuleFinder;
+import com.google.idea.blaze.base.settings.Blaze;
+import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
+import com.google.idea.blaze.plugin.IntellijPluginRule;
+import com.intellij.diagnostic.VMOptions;
+import com.intellij.execution.BeforeRunTask;
+import com.intellij.execution.configurations.ConfigurationFactory;
+import com.intellij.execution.configurations.ConfigurationType;
+import com.intellij.execution.configurations.ConfigurationTypeUtil;
+import com.intellij.execution.configurations.RunConfiguration;
+import com.intellij.icons.AllIcons;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Key;
+import com.intellij.openapi.util.NullableLazyValue;
+import javax.annotation.Nullable;
+import javax.swing.Icon;
+
+/**
+ * A type for run configurations that build an IntelliJ plugin jar via blaze, then load them in an
+ * IntelliJ application
+ */
+public class BlazeIntellijPluginConfigurationType implements ConfigurationType {
+
+  private final BlazeIntellijPluginConfigurationFactory factory =
+      new BlazeIntellijPluginConfigurationFactory(this);
+
+  static class BlazeIntellijPluginRuleConfigurationFactory extends BlazeRuleConfigurationFactory {
+    @Override
+    public boolean handlesRule(
+        WorkspaceLanguageSettings workspaceLanguageSettings, RuleIdeInfo rule) {
+      return workspaceLanguageSettings.isWorkspaceType(WorkspaceType.INTELLIJ_PLUGIN)
+          && IntellijPluginRule.isPluginRule(rule);
+    }
+
+    @Override
+    protected ConfigurationFactory getConfigurationFactory() {
+      return getInstance().factory;
+    }
+
+    @Override
+    public void setupConfiguration(RunConfiguration configuration, RuleIdeInfo rule) {
+      final BlazeIntellijPluginConfiguration pluginConfig =
+          (BlazeIntellijPluginConfiguration) configuration;
+      getInstance().factory.setupConfigurationForRule(pluginConfig, rule);
+    }
+  }
+
+  static class BlazeIntellijPluginConfigurationFactory extends ConfigurationFactory {
+
+    private static NullableLazyValue<String> currentVmOptions =
+        new NullableLazyValue<String>() {
+          @Nullable
+          @Override
+          protected String compute() {
+            return defaultVmOptions();
+          }
+        };
+
+    protected BlazeIntellijPluginConfigurationFactory(ConfigurationType type) {
+      super(type);
+    }
+
+    @Override
+    public boolean isApplicable(Project project) {
+      return Blaze.isBlazeProject(project);
+    }
+
+    @Override
+    public BlazeIntellijPluginConfiguration createTemplateConfiguration(Project project) {
+      BlazeIntellijPluginConfiguration config =
+          new BlazeIntellijPluginConfiguration(
+              project,
+              this,
+              "Unnamed",
+              RuleFinder.getInstance().findFirstRule(project, IntellijPluginRule::isPluginRule));
+      config.vmParameters = currentVmOptions.getValue();
+      return config;
+    }
+
+    @Override
+    public void configureBeforeRunTaskDefaults(
+        Key<? extends BeforeRunTask> providerID, BeforeRunTask task) {
+      task.setEnabled(providerID.equals(BuildPluginBeforeRunTaskProvider.ID));
+    }
+
+    void setupConfigurationForRule(
+        BlazeIntellijPluginConfiguration configuration, RuleIdeInfo rule) {
+      configuration.setTarget(rule.label);
+      configuration.setGeneratedName();
+      if (configuration.vmParameters == null) {
+        configuration.vmParameters = currentVmOptions.getValue();
+      }
+    }
+
+    @Override
+    public boolean isConfigurationSingletonByDefault() {
+      return true;
+    }
+
+    private static String defaultVmOptions() {
+      String vmoptions = VMOptions.read();
+      if (vmoptions == null) {
+        return null;
+      }
+      vmoptions = vmoptions.replaceAll("\\s+", " ").trim();
+      String vmoptionsFile = System.getProperty("jb.vmOptionsFile");
+      if (vmoptionsFile != null) {
+        vmoptions += " -Djb.vmOptionsFile=" + vmoptionsFile;
+      }
+      return vmoptions;
+    }
+  }
+
+  public static BlazeIntellijPluginConfigurationType getInstance() {
+    return ConfigurationTypeUtil.findConfigurationType(BlazeIntellijPluginConfigurationType.class);
+  }
+
+  @Override
+  public String getDisplayName() {
+    return Blaze.defaultBuildSystemName() + " IntelliJ Plugin";
+  }
+
+  @Override
+  public String getConfigurationTypeDescription() {
+    return "Configuration for launching an IntelliJ plugin in a sandbox environment.";
+  }
+
+  @Override
+  public Icon getIcon() {
+    return AllIcons.Nodes.Plugin;
+  }
+
+  @Override
+  public String getId() {
+    return "BlazeIntellijPluginConfigurationType";
+  }
+
+  @Override
+  public BlazeIntellijPluginConfigurationFactory[] getConfigurationFactories() {
+    return new BlazeIntellijPluginConfigurationFactory[] {factory};
+  }
+
+  public BlazeIntellijPluginConfigurationFactory getFactory() {
+    return factory;
+  }
+}
diff --git a/plugin_dev/src/com/google/idea/blaze/plugin/run/BuildPluginBeforeRunTaskProvider.java b/plugin_dev/src/com/google/idea/blaze/plugin/run/BuildPluginBeforeRunTaskProvider.java
new file mode 100644
index 0000000..de99b7a
--- /dev/null
+++ b/plugin_dev/src/com/google/idea/blaze/plugin/run/BuildPluginBeforeRunTaskProvider.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.plugin.run;
+
+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.async.process.ExternalTask;
+import com.google.idea.blaze.base.async.process.LineProcessingOutputStream;
+import com.google.idea.blaze.base.command.BlazeCommand;
+import com.google.idea.blaze.base.experiments.ExperimentScope;
+import com.google.idea.blaze.base.filecache.FileCaches;
+import com.google.idea.blaze.base.issueparser.IssueOutputLineProcessor;
+import com.google.idea.blaze.base.metrics.Action;
+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.scope.BlazeContext;
+import com.google.idea.blaze.base.scope.Scope;
+import com.google.idea.blaze.base.scope.ScopedTask;
+import com.google.idea.blaze.base.scope.output.IssueOutput;
+import com.google.idea.blaze.base.scope.scopes.BlazeConsoleScope;
+import com.google.idea.blaze.base.scope.scopes.IdeaLogScope;
+import com.google.idea.blaze.base.scope.scopes.IssuesScope;
+import com.google.idea.blaze.base.scope.scopes.LoggedTimingScope;
+import com.google.idea.blaze.base.settings.Blaze;
+import com.google.idea.blaze.base.settings.BlazeUserSettings;
+import com.google.idea.blaze.base.util.SaveUtil;
+import com.intellij.execution.BeforeRunTask;
+import com.intellij.execution.BeforeRunTaskProvider;
+import com.intellij.execution.configurations.RunConfiguration;
+import com.intellij.execution.runners.ExecutionEnvironment;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Key;
+import icons.BlazeIcons;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import javax.annotation.Nullable;
+import javax.swing.Icon;
+
+/**
+ * Builds the intellij_plugin jar via 'blaze build', for Blaze Intellij Plugin run configurations
+ */
+public final class BuildPluginBeforeRunTaskProvider
+    extends BeforeRunTaskProvider<BuildPluginBeforeRunTaskProvider.Task> {
+  public static final Key<Task> ID = Key.create("Blaze.Intellij.Plugin.BeforeRunTask");
+
+  static class Task extends BeforeRunTask<Task> {
+    private Task() {
+      super(ID);
+      setEnabled(true);
+    }
+  }
+
+  private final Project project;
+
+  public BuildPluginBeforeRunTaskProvider(Project project) {
+    this.project = project;
+  }
+
+  @Override
+  public Icon getIcon() {
+    return BlazeIcons.Blaze;
+  }
+
+  @Override
+  public Icon getTaskIcon(Task task) {
+    return BlazeIcons.Blaze;
+  }
+
+  @Override
+  public boolean isConfigurable() {
+    return false;
+  }
+
+  @Override
+  public boolean configureTask(RunConfiguration runConfiguration, Task task) {
+    return false;
+  }
+
+  @Override
+  public Key<Task> getId() {
+    return ID;
+  }
+
+  @Override
+  public String getName() {
+    return taskName();
+  }
+
+  @Override
+  public String getDescription(Task task) {
+    return taskName();
+  }
+
+  private String taskName() {
+    return Blaze.buildSystemName(project) + " build plugin before-run task";
+  }
+
+  @Override
+  public final boolean canExecuteTask(RunConfiguration configuration, Task task) {
+    return isValidConfiguration(configuration);
+  }
+
+  @Nullable
+  @Override
+  public Task createTask(RunConfiguration runConfiguration) {
+    if (isValidConfiguration(runConfiguration)) {
+      return new Task();
+    }
+    return null;
+  }
+
+  private static boolean isValidConfiguration(RunConfiguration runConfiguration) {
+    return runConfiguration instanceof BlazeIntellijPluginConfiguration;
+  }
+
+  @Override
+  public final boolean executeTask(
+      final DataContext dataContext,
+      final RunConfiguration configuration,
+      final ExecutionEnvironment env,
+      Task task) {
+    if (!canExecuteTask(configuration, task)) {
+      return false;
+    }
+    boolean suppressConsole = BlazeUserSettings.getInstance().getSuppressConsoleForRunAction();
+    return Scope.root(
+        context -> {
+          context
+              .push(new ExperimentScope())
+              .push(new IssuesScope(project))
+              .push(
+                  new BlazeConsoleScope.Builder(project)
+                      .setSuppressConsole(suppressConsole)
+                      .build())
+              .push(new IdeaLogScope());
+
+          final ProjectViewSet projectViewSet =
+              ProjectViewManager.getInstance(project).getProjectViewSet();
+          if (projectViewSet == null) {
+            IssueOutput.error("Could not load project view. Please resync project").submit(context);
+            return false;
+          }
+
+          final ScopedTask buildTask =
+              new ScopedTask(context) {
+                @Override
+                protected void execute(BlazeContext context) {
+                  BlazeIntellijPluginConfiguration config =
+                      (BlazeIntellijPluginConfiguration) configuration;
+                  BlazeCommand command = config.buildBlazeCommand(project, projectViewSet);
+                  if (command == null || context.hasErrors() || context.isCancelled()) {
+                    return;
+                  }
+                  SaveUtil.saveAllFiles();
+                  WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProject(project);
+                  int retVal =
+                      ExternalTask.builder(workspaceRoot)
+                          .addBlazeCommand(command)
+                          .context(context)
+                          .stderr(
+                              LineProcessingOutputStream.of(
+                                  new IssueOutputLineProcessor(project, context, workspaceRoot)))
+                          .build()
+                          .run(new LoggedTimingScope(project, Action.BLAZE_BUILD));
+                  if (retVal != 0) {
+                    context.setHasError();
+                  }
+                  FileCaches.refresh(project);
+                }
+              };
+
+          ListenableFuture<Void> buildFuture =
+              BlazeExecutor.submitTask(
+                  project, "Executing blaze build for IntelliJ plugin jar", buildTask);
+
+          try {
+            Futures.get(buildFuture, ExecutionException.class);
+          } catch (ExecutionException e) {
+            context.setHasError();
+          } catch (CancellationException e) {
+            context.setCancelled();
+          }
+
+          if (context.hasErrors() || context.isCancelled()) {
+            return false;
+          }
+          return true;
+        });
+  }
+}
diff --git a/plugin_dev/src/com/google/idea/blaze/plugin/run/IdeaJdkHelper.java b/plugin_dev/src/com/google/idea/blaze/plugin/run/IdeaJdkHelper.java
new file mode 100644
index 0000000..74daeee
--- /dev/null
+++ b/plugin_dev/src/com/google/idea/blaze/plugin/run/IdeaJdkHelper.java
@@ -0,0 +1,58 @@
+/*
+ * 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.plugin.run;
+
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.projectRoots.SdkTypeId;
+import javax.annotation.Nullable;
+import org.jetbrains.idea.devkit.projectRoots.IdeaJdk;
+import org.jetbrains.idea.devkit.projectRoots.IntelliJPlatformProduct;
+import org.jetbrains.idea.devkit.projectRoots.Sandbox;
+import org.jetbrains.idea.devkit.run.IdeaLicenseHelper;
+
+/** Contains all dependencies on devkit (not included in plugin SDK) */
+public class IdeaJdkHelper {
+
+  public static boolean isIdeaJdk(@Nullable Sdk sdk) {
+    return sdk != null && isIdeaJdkType(sdk.getSdkType());
+  }
+
+  public static boolean isIdeaJdkType(SdkTypeId type) {
+    return IdeaJdk.getInstance().equals(type);
+  }
+
+  @Nullable
+  public static String getBuildNumber(Sdk sdk) {
+    return IdeaJdk.getBuildNumber(sdk.getHomePath());
+  }
+
+  public static void copyIDEALicense(final String sandboxHome) {
+    IdeaLicenseHelper.copyIDEALicense(sandboxHome);
+  }
+
+  /** @throws RuntimeException if input Sdk is not an IdeaJdk */
+  public static String getSandboxHome(Sdk sdk) {
+    if (!isIdeaJdk(sdk)) {
+      throw new RuntimeException("Invalid SDK type: " + sdk.getSdkType());
+    }
+    return ((Sandbox) sdk.getSdkAdditionalData()).getSandboxHome();
+  }
+
+  @Nullable
+  public static String getPlatformPrefix(String buildNumber) {
+    return IntelliJPlatformProduct.fromBuildNumber(buildNumber).getPlatformPrefix();
+  }
+}
diff --git a/plugin_dev/src/com/google/idea/blaze/plugin/run/IntellijWithPluginClasspathHelper.java b/plugin_dev/src/com/google/idea/blaze/plugin/run/IntellijWithPluginClasspathHelper.java
new file mode 100644
index 0000000..b3d0b2e
--- /dev/null
+++ b/plugin_dev/src/com/google/idea/blaze/plugin/run/IntellijWithPluginClasspathHelper.java
@@ -0,0 +1,86 @@
+/*
+ * 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.plugin.run;
+
+import com.google.common.collect.Lists;
+import com.intellij.execution.configurations.JavaParameters;
+import com.intellij.execution.configurations.ParametersList;
+import com.intellij.openapi.projectRoots.JavaSdkType;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.util.PathsList;
+import java.io.File;
+import java.util.List;
+import org.jetbrains.annotations.NonNls;
+
+/**
+ * Boilerplate for running an IJ application with an additional plugin, copied from
+ * org.jetbrains.idea.devkit.run.PluginRunConfiguration
+ */
+public class IntellijWithPluginClasspathHelper {
+
+  private static final List<String> IJ_LIBRARIES =
+      Lists.newArrayList(
+          "log4j.jar",
+          "trove4j.jar",
+          "openapi.jar",
+          "util.jar",
+          "extensions.jar",
+          "bootstrap.jar",
+          "idea.jar",
+          "idea_rt.jar");
+
+  private static void addIntellijLibraries(JavaParameters params, Sdk ideaJdk) {
+    @NonNls String libPath = ideaJdk.getHomePath() + File.separator + "lib";
+    PathsList list = params.getClassPath();
+    for (String lib : IJ_LIBRARIES) {
+      list.addFirst(libPath + File.separator + lib);
+    }
+    list.addFirst(((JavaSdkType) ideaJdk.getSdkType()).getToolsPath(ideaJdk));
+  }
+
+  public static void addRequiredVmParams(JavaParameters params, Sdk ideaJdk) {
+    String canonicalSandbox = IdeaJdkHelper.getSandboxHome(ideaJdk);
+    ParametersList vm = params.getVMParametersList();
+
+    @NonNls String libPath = ideaJdk.getHomePath() + File.separator + "lib";
+    vm.add("-Xbootclasspath/a:" + libPath + File.separator + "boot.jar");
+
+    vm.defineProperty("idea.config.path", canonicalSandbox + File.separator + "config");
+    vm.defineProperty("idea.system.path", canonicalSandbox + File.separator + "system");
+    vm.defineProperty("idea.plugins.path", canonicalSandbox + File.separator + "plugins");
+    vm.defineProperty("idea.classpath.index.enabled", "false");
+
+    if (SystemInfo.isMac) {
+      vm.defineProperty("idea.smooth.progress", "false");
+      vm.defineProperty("apple.laf.useScreenMenuBar", "true");
+    }
+
+    if (SystemInfo.isXWindow) {
+      if (!vm.hasProperty("sun.awt.disablegrab")) {
+        vm.defineProperty(
+            "sun.awt.disablegrab", "true"); // See http://devnet.jetbrains.net/docs/DOC-1142
+      }
+    }
+
+    params.setWorkingDirectory(ideaJdk.getHomePath() + File.separator + "bin" + File.separator);
+    params.setJdk(ideaJdk);
+
+    addIntellijLibraries(params, ideaJdk);
+
+    params.setMainClass("com.intellij.idea.Main");
+  }
+}
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
new file mode 100644
index 0000000..b4d13c7
--- /dev/null
+++ b/plugin_dev/src/com/google/idea/blaze/plugin/sync/IntellijPluginSyncPlugin.java
@@ -0,0 +1,92 @@
+/*
+ * 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.plugin.sync;
+
+import com.google.common.collect.ImmutableSet;
+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.WorkspaceType;
+import com.google.idea.blaze.base.projectview.ProjectViewSet;
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.sync.BlazeSyncPlugin;
+import com.google.idea.blaze.java.sync.JavaLanguageLevelHelper;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.module.ModuleType;
+import com.intellij.openapi.module.StdModuleTypes;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.LanguageLevelProjectExtension;
+import com.intellij.pom.java.LanguageLevel;
+import com.intellij.util.ui.UIUtil;
+import java.util.Set;
+import javax.annotation.Nullable;
+
+/**
+ * Development environment support for intellij plugin projects. Prevents the project SDK being
+ * reset during sync
+ */
+public class IntellijPluginSyncPlugin extends BlazeSyncPlugin.Adapter {
+
+  @Nullable
+  @Override
+  public WorkspaceType getDefaultWorkspaceType() {
+    return WorkspaceType.INTELLIJ_PLUGIN;
+  }
+
+  @Nullable
+  @Override
+  public ModuleType getWorkspaceModuleType(WorkspaceType workspaceType) {
+    if (workspaceType == WorkspaceType.INTELLIJ_PLUGIN) {
+      return StdModuleTypes.JAVA;
+    }
+    return null;
+  }
+
+  @Override
+  public Set<LanguageClass> getSupportedLanguagesInWorkspace(WorkspaceType workspaceType) {
+    if (workspaceType == WorkspaceType.INTELLIJ_PLUGIN) {
+      return ImmutableSet.of(LanguageClass.JAVA);
+    }
+    return ImmutableSet.of();
+  }
+
+  @Override
+  public void updateSdk(
+      Project project,
+      BlazeContext context,
+      ProjectViewSet projectViewSet,
+      BlazeProjectData blazeProjectData) {
+    if (!blazeProjectData.workspaceLanguageSettings.isWorkspaceType(
+        WorkspaceType.INTELLIJ_PLUGIN)) {
+      return;
+    }
+
+    LanguageLevel javaLanguageLevel =
+        JavaLanguageLevelHelper.getJavaLanguageLevel(
+            projectViewSet, blazeProjectData, LanguageLevel.JDK_1_7);
+
+    // Leave the SDK, but set the language level
+    UIUtil.invokeAndWaitIfNeeded(
+        (Runnable)
+            () ->
+                ApplicationManager.getApplication()
+                    .runWriteAction(
+                        () -> {
+                          LanguageLevelProjectExtension ext =
+                              LanguageLevelProjectExtension.getInstance(project);
+                          ext.setLanguageLevel(javaLanguageLevel);
+                        }));
+  }
+}
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
new file mode 100644
index 0000000..492a6fb
--- /dev/null
+++ b/plugin_dev/tests/integrationtests/com/google/idea/blaze/plugin/sync/PluginDevSyncTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.plugin.sync;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+import com.google.idea.blaze.base.ideinfo.RuleMapBuilder;
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.model.RuleMap;
+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;
+import com.google.idea.blaze.base.sync.BlazeSyncParams.SyncMode;
+import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
+import com.google.idea.blaze.plugin.run.BlazeIntellijPluginConfiguration;
+import com.intellij.execution.RunManager;
+import com.intellij.execution.configurations.RunConfiguration;
+import java.util.List;
+
+/** Plugin-dev specific sync integration test. */
+public class PluginDevSyncTest extends BlazeSyncIntegrationTestCase {
+
+  public void testRunConfigurationCreatedDuringSync() throws Exception {
+    setProjectView(
+        "directories:",
+        "  java/com/google",
+        "targets:",
+        "  //java/com/google:lib",
+        "  //java/com/google:plugin",
+        "workspace_type: intellij_plugin");
+
+    createFile(
+        "java/com/google/ClassWithUniqueName1.java",
+        "package com.google;",
+        "public class ClassWithUniqueName1 {}");
+
+    createFile(
+        "java/com/google/ClassWithUniqueName2.java",
+        "package com.google;",
+        "public class ClassWithUniqueName2 {}");
+
+    RuleMap ruleMap =
+        RuleMapBuilder.builder()
+            .addRule(
+                RuleIdeInfo.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()
+                    .setBuildFile(sourceRoot("java/com/google/BUILD"))
+                    .setLabel("//java/com/google:plugin")
+                    .setKind("java_import")
+                    .addTag("intellij-plugin"))
+            .build();
+
+    setRuleMap(ruleMap);
+
+    runBlazeSync(
+        new BlazeSyncParams.Builder("Sync", SyncMode.INCREMENTAL)
+            .addProjectViewTargets(true)
+            .build());
+
+    assertNoErrors();
+
+    BlazeProjectData blazeProjectData =
+        BlazeProjectDataManager.getInstance(getProject()).getBlazeProjectData();
+    assertThat(blazeProjectData).isNotNull();
+    assertThat(blazeProjectData.ruleMap).isEqualTo(ruleMap);
+    assertThat(blazeProjectData.workspaceLanguageSettings.getWorkspaceType())
+        .isEqualTo(WorkspaceType.INTELLIJ_PLUGIN);
+
+    List<RunConfiguration> runConfigs =
+        RunManager.getInstance(getProject()).getAllConfigurationsList();
+    assertThat(runConfigs).hasSize(1);
+    assertThat(runConfigs.get(0)).isInstanceOf(BlazeIntellijPluginConfiguration.class);
+  }
+}
diff --git a/proto_deps/BUILD b/proto_deps/BUILD
new file mode 100644
index 0000000..f90fbfa
--- /dev/null
+++ b/proto_deps/BUILD
@@ -0,0 +1,12 @@
+#
+# Description:
+# Checked-in copy of the bazel proto deps.
+#
+
+licenses(["notice"])  # Apache 2.0
+
+java_import(
+    name = "proto_deps",
+    jars = ["proto_deps.jar"],
+    visibility = ["//visibility:public"],
+)
diff --git a/proto_deps/proto_deps.jar b/proto_deps/proto_deps.jar
new file mode 100755
index 0000000..80b6046
--- /dev/null
+++ b/proto_deps/proto_deps.jar
Binary files differ
diff --git a/remote_platform_sdks/BUILD.android_studio b/remote_platform_sdks/BUILD.android_studio
deleted file mode 100644
index 3e394c2..0000000
--- a/remote_platform_sdks/BUILD.android_studio
+++ /dev/null
@@ -1,36 +0,0 @@
-# Description:
-#
-# Plugin source jars for Android Studio 2.2. Preview 4, accessed remotely.
-
-package(default_visibility = ["//visibility:public"])
-
-java_import(
-    name = "plugin_api",
-    jars = glob([
-        "android-studio/lib/*.jar",
-        "android-studio/plugins/android/lib/*.jar",
-        "android-studio/plugins/android-ndk/lib/*.jar",
-    ]),
-    tags = ["intellij-provided-by-sdk"],
-)
-
-# The plugins required by ASwB. We need to include them
-# when running integration tests.
-java_import(
-    name = "bundled_plugins",
-    jars = glob([
-        "android-studio/plugins/android/lib/*.jar",
-        "android-studio/plugins/android-ndk/lib/*.jar",
-        "android-studio/plugins/gradle/lib/*.jar",
-        "android-studio/plugins/Groovy/lib/*.jar",
-        "android-studio/plugins/java-i18n/lib/*.jar",
-        "android-studio/plugins/junit/lib/*.jar",
-        "android-studio/plugins/properties/lib/*.jar",
-    ]),
-    tags = ["intellij-provided-by-sdk"],
-)
-
-filegroup(
-    name = "build_number",
-    srcs = ["android-studio/build.txt"],
-)
diff --git a/remote_platform_sdks/BUILD.clion b/remote_platform_sdks/BUILD.clion
deleted file mode 100644
index 48593a8..0000000
--- a/remote_platform_sdks/BUILD.clion
+++ /dev/null
@@ -1,24 +0,0 @@
-# Description:
-#
-# Plugin source jars for CLion 2016.1.3, accessed remotely.
-
-package(default_visibility = ["//visibility:public"])
-
-java_import(
-    name = "plugin_api",
-    jars = glob(["clion-2016.1.3/lib/*.jar"]),
-    tags = ["intellij-provided-by-sdk"],
-)
-
-filegroup(
-    name = "build_number",
-    srcs = ["clion-2016.1.3/build.txt"],
-)
-
-# The plugins required by CLwB. Presumably there will be some, when we write
-# some integration tests.
-java_import(
-    name = "bundled_plugins",
-    jars = [],
-    tags = ["intellij-provided-by-sdk"],
-)
diff --git a/remote_platform_sdks/BUILD.idea b/remote_platform_sdks/BUILD.idea
deleted file mode 100644
index c8106a5..0000000
--- a/remote_platform_sdks/BUILD.idea
+++ /dev/null
@@ -1,35 +0,0 @@
-# Description:
-#
-# Plugin source jars for IntelliJ 2016.1.3, accessed remotely.
-
-package(default_visibility = ["//visibility:public"])
-
-java_import(
-    name = "plugin_api",
-    jars = glob(["idea-IC-145.*/lib/*.jar"]),
-    tags = ["intellij-provided-by-sdk"],
-)
-
-java_import(
-    name = "devkit",
-    jars = glob(["idea-IC-145.*/plugins/devkit/lib/devkit.jar"]),
-    tags = ["intellij-provided-by-sdk"],
-)
-
-# The plugins required by IJwB. We need to include them
-# when running integration tests.
-java_import(
-    name = "bundled_plugins",
-    jars = glob([
-        "idea-IC-145.*/plugins/devkit/lib/*.jar",
-        "idea-IC-145.*/plugins/java-i18n/lib/*.jar",
-        "idea-IC-145.*/plugins/junit/lib/*.jar",
-        "idea-IC-145.*/plugins/properties/lib/*.jar",
-    ]),
-    tags = ["intellij-provided-by-sdk"],
-)
-
-filegroup(
-    name = "build_number",
-    srcs = glob(["idea-IC-145.*/build.txt"]),
-)
diff --git a/third_party/BUILD b/third_party/BUILD
index 37a3284..d7eae12 100644
--- a/third_party/BUILD
+++ b/third_party/BUILD
@@ -1,22 +1,11 @@
 package(default_visibility = ["//visibility:public"])
 
 java_import(
-    name = "trickle",
-    jars = ["trickle/trickle-0.6.1.jar"],
+    name = "jdk8_tools",
+    jars = ["jdk8/tools.jar"],
 )
 
-java_import(
-    name = "jsr305",
-    jars = ["jsr305/jsr305.jar"],
-)
-
-java_import(
-    name = "test_lib",
-    jars = [
-        "jdk8/tools.jar",
-        "truth/truth.jar",
-        "mockito/mockito-core-1.9.5.jar",
-        "junit4/junit-4.12.jar",
-        "objenesis/objenesis-1_3.jar",
-    ],
+sh_binary(
+    name = "zip",
+    srcs = ["zip/zip.sh"],
 )
diff --git a/third_party/README.md b/third_party/README.md
index 0cceaa6..b62d641 100644
--- a/third_party/README.md
+++ b/third_party/README.md
@@ -2,38 +2,7 @@
 author, but ship together with the source so building IJwB requires
 a minimal set of extra dependencies.
 
-
-## [trickle](https://github.com/spotify/trickle/archive/trickle-0.6.1.zip)
-
-* Version: 0.6.1
-* License: Apache License 2.0
-
-## [mockito](http://mockito.googlecode.com/files/mockito-1.9.5.zip)
-
-* Version: 1.9.5
-* License: MIT
-
-## [jsr-305](http://central.maven.org/maven2/com/google/code/findbugs/jsr305/3.0.1/jsr305-3.0.1.jar)
-
-* Version: 3.0.1
-* License: BSD License
-
-## [junit4](https://github.com/junit-team/junit/archive/r4.12.zip)
-
-* Version: 4.12
-* License: Eclipse Public License 1.0
-
-## [objenesis](https://objenesis.googlecode.com/files/objenesis-1.3-bin.zip)
-
-* Version: 1.3
-* License: Apache 2.0
-
 ## [java jdk8](http://hg.openjdk.java.net/jdk8u/jdk8u60)
 
 * Version: jdk8u60-b27 (rev)
 * License: GPLv2 and GPLv2+ClassPath Exception
-
-## [truth](https://github.com/google/truth)
-
-* Version: 0.28
-* License: Apache 2.0
diff --git a/third_party/jdk8/tools.jar b/third_party/jdk8/tools.jar
index 1e18b3e..f9f71d6 100644
--- a/third_party/jdk8/tools.jar
+++ b/third_party/jdk8/tools.jar
Binary files differ
diff --git a/third_party/jsr305/LICENSE b/third_party/jsr305/LICENSE
deleted file mode 100644
index 6736681..0000000
--- a/third_party/jsr305/LICENSE
+++ /dev/null
@@ -1,28 +0,0 @@
-Copyright (c) 2007-2009, JSR305 expert group
-All rights reserved.
-
-http://www.opensource.org/licenses/bsd-license.php
-
-Redistribution and use in source and binary forms, with or without 
-modification, are permitted provided that the following conditions are met:
-
-    * Redistributions of source code must retain the above copyright notice, 
-      this list of conditions and the following disclaimer.
-    * Redistributions in binary form must reproduce the above copyright notice, 
-      this list of conditions and the following disclaimer in the documentation 
-      and/or other materials provided with the distribution.
-    * Neither the name of the JSR305 expert group nor the names of its 
-      contributors may be used to endorse or promote products derived from 
-      this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 
-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
-ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
-LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
-CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
-SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
-INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
-CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
-ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
-POSSIBILITY OF SUCH DAMAGE.
diff --git a/third_party/jsr305/jsr305.jar b/third_party/jsr305/jsr305.jar
deleted file mode 100644
index f973aea..0000000
--- a/third_party/jsr305/jsr305.jar
+++ /dev/null
Binary files differ
diff --git a/third_party/junit4/LICENSE b/third_party/junit4/LICENSE
deleted file mode 100644
index fb68629..0000000
--- a/third_party/junit4/LICENSE
+++ /dev/null
@@ -1,214 +0,0 @@
-JUnit
-
-Eclipse Public License - v 1.0
-
-THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC
-LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM
-CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
-
-1. DEFINITIONS
-
-"Contribution" means:
-
-      a) in the case of the initial Contributor, the initial code and
-         documentation distributed under this Agreement, and
-      b) in the case of each subsequent Contributor:
-
-      i) changes to the Program, and
-
-      ii) additions to the Program;
-
-      where such changes and/or additions to the Program originate from and are
-distributed by that particular Contributor. A Contribution 'originates' from a
-Contributor if it was added to the Program by such Contributor itself or anyone
-acting on such Contributor's behalf. Contributions do not include additions to
-the Program which: (i) are separate modules of software distributed in
-conjunction with the Program under their own license agreement, and (ii) are
-not derivative works of the Program. 
-
-"Contributor" means any person or entity that distributes the Program.
-
-"Licensed Patents " mean patent claims licensable by a Contributor which are
-necessarily infringed by the use or sale of its Contribution alone or when
-combined with the Program.
-
-"Program" means the Contributions distributed in accordance with this Agreement.
-
-"Recipient" means anyone who receives the Program under this Agreement,
-including all Contributors.
-
-2. GRANT OF RIGHTS
-
-      a) Subject to the terms of this Agreement, each Contributor hereby grants
-Recipient a non-exclusive, worldwide, royalty-free copyright license to
-reproduce, prepare derivative works of, publicly display, publicly perform,
-distribute and sublicense the Contribution of such Contributor, if any, and
-such derivative works, in source code and object code form.
-
-      b) Subject to the terms of this Agreement, each Contributor hereby grants
-Recipient a non-exclusive, worldwide, royalty-free patent license under
-Licensed Patents to make, use, sell, offer to sell, import and otherwise
-transfer the Contribution of such Contributor, if any, in source code and
-object code form. This patent license shall apply to the combination of the
-Contribution and the Program if, at the time the Contribution is added by the
-Contributor, such addition of the Contribution causes such combination to be
-covered by the Licensed Patents. The patent license shall not apply to any
-other combinations which include the Contribution. No hardware per se is
-licensed hereunder. 
-
-      c) Recipient understands that although each Contributor grants the
-licenses to its Contributions set forth herein, no assurances are provided by
-any Contributor that the Program does not infringe the patent or other
-intellectual property rights of any other entity. Each Contributor disclaims
-any liability to Recipient for claims brought by any other entity based on
-infringement of intellectual property rights or otherwise. As a condition to
-exercising the rights and licenses granted hereunder, each Recipient hereby
-assumes sole responsibility to secure any other intellectual property rights
-needed, if any. For example, if a third party patent license is required to
-allow Recipient to distribute the Program, it is Recipient's responsibility to
-acquire that license before distributing the Program.
-
-      d) Each Contributor represents that to its knowledge it has sufficient
-copyright rights in its Contribution, if any, to grant the copyright license
-set forth in this Agreement. 
-
-3. REQUIREMENTS
-
-A Contributor may choose to distribute the Program in object code form under
-its own license agreement, provided that:
-
-      a) it complies with the terms and conditions of this Agreement; and
-
-      b) its license agreement:
-
-      i) effectively disclaims on behalf of all Contributors all warranties and
-conditions, express and implied, including warranties or conditions of title
-and non-infringement, and implied warranties or conditions of merchantability
-and fitness for a particular purpose; 
-
-      ii) effectively excludes on behalf of all Contributors all liability for
-damages, including direct, indirect, special, incidental and consequential
-damages, such as lost profits; 
-
-      iii) states that any provisions which differ from this Agreement are
-offered by that Contributor alone and not by any other party; and
-
-      iv) states that source code for the Program is available from such
-Contributor, and informs licensees how to obtain it in a reasonable manner on
-or through a medium customarily used for software exchange. 
-
-When the Program is made available in source code form:
-
-      a) it must be made available under this Agreement; and 
-
-      b) a copy of this Agreement must be included with each copy of the
-Program. 
-
-Contributors may not remove or alter any copyright notices contained within the
-Program.
-
-Each Contributor must identify itself as the originator of its Contribution, if
-any, in a manner that reasonably allows subsequent Recipients to identify the
-originator of the Contribution.
-
-4. COMMERCIAL DISTRIBUTION
-
-Commercial distributors of software may accept certain responsibilities with
-respect to end users, business partners and the like. While this license is
-intended to facilitate the commercial use of the Program, the Contributor who
-includes the Program in a commercial product offering should do so in a manner
-which does not create potential liability for other Contributors. Therefore, if
-a Contributor includes the Program in a commercial product offering, such
-Contributor ("Commercial Contributor") hereby agrees to defend and indemnify
-every other Contributor ("Indemnified Contributor") against any losses, damages
-and costs (collectively "Losses") arising from claims, lawsuits and other legal
-actions brought by a third party against the Indemnified Contributor to the
-extent caused by the acts or omissions of such Commercial Contributor in
-connection with its distribution of the Program in a commercial product
-offering. The obligations in this section do not apply to any claims or Losses
-relating to any actual or alleged intellectual property infringement. In order
-to qualify, an Indemnified Contributor must: a) promptly notify the Commercial
-Contributor in writing of such claim, and b) allow the Commercial Contributor
-to control, and cooperate with the Commercial Contributor in, the defense and
-any related settlement negotiations. The Indemnified Contributor may
-participate in any such claim at its own expense.
-
-For example, a Contributor might include the Program in a commercial product
-offering, Product X. That Contributor is then a Commercial Contributor. If that
-Commercial Contributor then makes performance claims, or offers warranties
-related to Product X, those performance claims and warranties are such
-Commercial Contributor's responsibility alone. Under this section, the
-Commercial Contributor would have to defend claims against the other
-Contributors related to those performance claims and warranties, and if a court
-requires any other Contributor to pay any damages as a result, the Commercial
-Contributor must pay those damages.
-
-5. NO WARRANTY
-
-EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
-IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE,
-NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each
-Recipient is solely responsible for determining the appropriateness of using
-and distributing the Program and assumes all risks associated with its exercise
-of rights under this Agreement, including but not limited to the risks and
-costs of program errors, compliance with applicable laws, damage to or loss of
-data, programs or equipment, and unavailability or interruption of operations.
-
-6. DISCLAIMER OF LIABILITY
-
-EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY
-CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST
-PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
-STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
-WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS
-GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
-
-7. GENERAL
-
-If any provision of this Agreement is invalid or unenforceable under applicable
-law, it shall not affect the validity or enforceability of the remainder of the
-terms of this Agreement, and without further action by the parties hereto, such
-provision shall be reformed to the minimum extent necessary to make such
-provision valid and enforceable.
-
-If Recipient institutes patent litigation against any
-entity (including a cross-claim or counterclaim in a lawsuit) alleging that the
-Program itself (excluding combinations of the Program with other software or
-hardware) infringes such Recipient's patent(s), then such Recipient's rights
-granted under Section 2(b) shall terminate as of the date such litigation is
-filed.
-
-All Recipient's rights under this Agreement shall terminate if it fails to
-comply with any of the material terms or conditions of this Agreement and does
-not cure such failure in a reasonable period of time after becoming aware of
-such noncompliance. If all Recipient's rights under this Agreement terminate,
-Recipient agrees to cease use and distribution of the Program as soon as
-reasonably practicable. However, Recipient's obligations under this Agreement
-and any licenses granted by Recipient relating to the Program shall continue
-and survive.
-
-Everyone is permitted to copy and distribute copies of this Agreement, but in
-order to avoid inconsistency the Agreement is copyrighted and may only be
-modified in the following manner. The Agreement Steward reserves the right to
-publish new versions (including revisions) of this Agreement from time to time.
-No one other than the Agreement Steward has the right to modify this Agreement.
-The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to
-serve as the Agreement Steward to a suitable separate entity. Each new version
-of the Agreement will be given a distinguishing version number. The Program
-(including Contributions) may always be distributed subject to the version of
-the Agreement under which it was received. In addition, after a new version of
-the Agreement is published, Contributor may elect to distribute the Program
-(including its Contributions) under the new version. Except as expressly stated
-in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to
-the intellectual property of any Contributor under this Agreement, whether
-expressly, by implication, estoppel or otherwise. All rights in the Program not
-expressly granted under this Agreement are reserved.
-
-This Agreement is governed by the laws of the State of New York and the
-intellectual property laws of the United States of America. No party to this
-Agreement will bring a legal action under this Agreement more than one year
-after the cause of action arose. Each party waives its rights to a jury trial
-in any resulting litigation. 
-
diff --git a/third_party/junit4/junit-4.12.jar b/third_party/junit4/junit-4.12.jar
deleted file mode 100644
index 3a7fc26..0000000
--- a/third_party/junit4/junit-4.12.jar
+++ /dev/null
Binary files differ
diff --git a/third_party/mockito/LICENSE b/third_party/mockito/LICENSE
deleted file mode 100644
index e0840a4..0000000
--- a/third_party/mockito/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-The MIT License
-
-Copyright (c) 2007 Mockito contributors
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
\ No newline at end of file
diff --git a/third_party/mockito/mockito-core-1.9.5.jar b/third_party/mockito/mockito-core-1.9.5.jar
deleted file mode 100644
index 5de7610..0000000
--- a/third_party/mockito/mockito-core-1.9.5.jar
+++ /dev/null
Binary files differ
diff --git a/third_party/objenesis/LICENSE b/third_party/objenesis/LICENSE
deleted file mode 100644
index d645695..0000000
--- a/third_party/objenesis/LICENSE
+++ /dev/null
@@ -1,202 +0,0 @@
-
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
diff --git a/third_party/objenesis/objenesis-1_3.jar b/third_party/objenesis/objenesis-1_3.jar
deleted file mode 100644
index d56dc2b..0000000
--- a/third_party/objenesis/objenesis-1_3.jar
+++ /dev/null
Binary files differ
diff --git a/third_party/trickle/LICENSE b/third_party/trickle/LICENSE
deleted file mode 100644
index d645695..0000000
--- a/third_party/trickle/LICENSE
+++ /dev/null
@@ -1,202 +0,0 @@
-
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
diff --git a/third_party/trickle/trickle-0.6.1.jar b/third_party/trickle/trickle-0.6.1.jar
deleted file mode 100644
index bb0965e..0000000
--- a/third_party/trickle/trickle-0.6.1.jar
+++ /dev/null
Binary files differ
diff --git a/third_party/truth/LICENSE b/third_party/truth/LICENSE
deleted file mode 100644
index d645695..0000000
--- a/third_party/truth/LICENSE
+++ /dev/null
@@ -1,202 +0,0 @@
-
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
diff --git a/third_party/truth/truth.jar b/third_party/truth/truth.jar
deleted file mode 100755
index e0a5a6e..0000000
--- a/third_party/truth/truth.jar
+++ /dev/null
Binary files differ
diff --git a/tools/zip/zip.sh b/third_party/zip/zip.sh
similarity index 100%
rename from tools/zip/zip.sh
rename to third_party/zip/zip.sh
diff --git a/tools/zip/BUILD b/tools/zip/BUILD
deleted file mode 100644
index 13c4d21..0000000
--- a/tools/zip/BUILD
+++ /dev/null
@@ -1,6 +0,0 @@
-package(default_visibility = ["//visibility:public"])
-
-sh_binary(
-    name = "zip",
-    srcs = ["zip.sh"],
-)
diff --git a/version.bzl b/version.bzl
new file mode 100644
index 0000000..96c8141
--- /dev/null
+++ b/version.bzl
@@ -0,0 +1,3 @@
+"""Version of the blaze plugin."""
+
+VERSION = "1.9.2"