Merge pull request #129 from brendandouglas/master
Import of bazel plugin using copybara
diff --git a/BUILD b/BUILD
index 5e26650..1d898ce 100644
--- a/BUILD
+++ b/BUILD
@@ -30,6 +30,14 @@
],
)
+# UE-specific IJwB tests
+test_suite(
+ name = "ijwb_ue_tests",
+ tests = [
+ "//golang:integration_tests",
+ ],
+)
+
# ASwB tests, run with an Android Studio plugin SDK
test_suite(
name = "aswb_tests",
@@ -38,6 +46,8 @@
"//aswb:unit_tests",
"//base:integration_tests",
"//base:unit_tests",
+ "//cpp:integration_tests",
+ "//cpp:unit_tests",
"//java:integration_tests",
"//java:unit_tests",
],
diff --git a/CHANGELOG b/CHANGELOG
index 32034b6..15ceaa6 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,53 @@
+v2017.08.28
+===========
+* CLion test output: make URLs and bazel targets clickable
+* Retrieve Javadocs for unattached sources
+* CLion: incremental sync retains more caches. Prefill caches on project reload.
+
+v2017.08.14
+===========
+* Fix spurious 'unused' warnings for AutoFactory-annotated classes
+* Python: Test UI support for paramaterized python tests
+* Python: Linkify stack traces in Bazel Console view
+* Test UI: fix timeouts not being marked as failures
+* Go: migrate Go code to the latest JetBrains plugin
+
+v2017.08.01
+===========
+* Add a user setting to suppress the Bazel console during sync
+* Add support for IntelliJ 2017.1.5
+* ASwB: fix generated resources not resolving
+* Fix unresolved references when targets are built with multiple Bazel configurations
+* Python: fix 'argument list too long' errors when debugging
+
+v2017.07.17
+===========
+* Explicitly deprioritize older android/gwt-specific versions of libraries during sync
+* Improve test finder heuristics when creating run configurations
+
+v2017.07.05
+===========
+* CLwB: Show "unsynced" diagnostic file status for C++ files
+
+v2017.06.19
+===========
+* Improve performance when indexing proto_library targets in the working set.
+* Fix incorrectly reusing existing, but different, run configurations.
+* Order BUILD file structure view by target name, not rule type.
+
+v2017.06.05
+===========
+* Add Scala support to IntelliJ.
+* Add 'sync_flags' .blazeproject section, for flags only applied during sync.
+* Android Studio: NDK plugins are now optional.
+* CLion: Improve performance by prefetching required genfiles during sync.
+
+v2017.05.22
+===========
+* TypeScript: Support multiple ts_config rules in .bazelproject
+* Android Studio: Index javac jar for javax.lang classes
+* Show failed test targets in test result UI
+
v2017.05.08
===========
* Add Python support to CLion
diff --git a/README.md b/README.md
index 439b85e..1489497 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# An IntelliJ plugin for [Bazel](http://bazel.build) projects
-This is an early-access version of our Blaze plugins for IntelliJ,
+This is an early-access version of our Bazel plugins for IntelliJ,
Android Studio, and CLion.
This code drop is for educational purposes only and is currently
@@ -13,8 +13,7 @@
## Installation
You can find our plugin in the Jetbrains plugin repository by going to
-`Settings -> Browse Repositories`, and searching for `IntelliJ with Bazel`,
-`Android Studio with Bazel`, or `CLion with Bazel`.
+`Settings -> Browse Repositories`, and searching for `Bazel`.
## Usage
diff --git a/WORKSPACE b/WORKSPACE
index 9a4eaff..7d1168f 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -2,44 +2,40 @@
# Long-lived download links available at: https://www.jetbrains.com/intellij-repository/releases
-# The plugin api for IntelliJ 2017.1.1. This is required to build IJwB,
+# The plugin api for IntelliJ 2017.2. This is required to build IJwB,
# and run integration tests.
new_http_archive(
- name = "intellij_ce_2017_1_1",
+ name = "intellij_ce_2017_2_2",
build_file = "intellij_platform_sdk/BUILD.idea",
- url = "https://www.jetbrains.com/intellij-repository/releases/com/jetbrains/intellij/idea/ideaIC/2017.1.1/ideaIC-2017.1.1.zip",
+ url = "https://www.jetbrains.com/intellij-repository/releases/com/jetbrains/intellij/idea/ideaIC/2017.2.2/ideaIC-2017.2.2.zip",
+ sha256 = "4e14343ecaab00a3a02f2e51995a46cdbea90e20d336f806a1ec40892bbd7e53",
)
-# The plugin api for IntelliJ 2016.3.1. This is required to build IJwB,
+# The plugin api for IntelliJ 2017.1. This is required to build IJwB,
# and run integration tests.
new_http_archive(
- name = "intellij_ce_2016_3_1",
+ name = "intellij_ce_2017_1_5",
build_file = "intellij_platform_sdk/BUILD.idea",
- url = "https://www.jetbrains.com/intellij-repository/releases/com/jetbrains/intellij/idea/ideaIC/2016.3.1/ideaIC-2016.3.1.zip",
+ url = "https://www.jetbrains.com/intellij-repository/releases/com/jetbrains/intellij/idea/ideaIC/2017.1.5/ideaIC-2017.1.5.zip",
+ sha256 = "d11beb116f500ecbf75b0a1098dfaad696bc8a15edceae163f53bc511ab79445"
)
-# The plugin api for IntelliJ 2016.2.4. This is required to build IJwB,
-# and run integration tests.
+# The plugin api for IntelliJ UE 2017.2. This is required to run UE-specific
+# integration tests.
new_http_archive(
- name = "IC_162_2032_8",
+ name = "intellij_ue_2017_2_2",
build_file = "intellij_platform_sdk/BUILD.idea",
- url = "https://www.jetbrains.com/intellij-repository/releases/com/jetbrains/intellij/idea/ideaIC/2016.2.4/ideaIC-2016.2.4.zip",
+ url = "https://www.jetbrains.com/intellij-repository/releases/com/jetbrains/intellij/idea/ideaIU/2017.2.2/ideaIU-2017.2.2.zip",
+ sha256 = "b5a07d41b255799b2cdf2aa8b1c154aa35bf88deac4ae3718b2bae257b68d47d",
)
-# The plugin api for CLion 2016.2.2. This is required to build CLwB,
-# and run integration tests.
+# The plugin api for IntelliJ UE 2017.1. This is required to run UE-specific
+# integration tests.
new_http_archive(
- name = "CL_162_1967_7",
- build_file = "intellij_platform_sdk/BUILD.clion",
- url = "https://download.jetbrains.com/cpp/CLion-2016.2.2.tar.gz",
-)
-
-# The plugin api for CLion 2016.3.2. This is required to build CLwB,
-# and run integration tests.
-new_http_archive(
- name = "clion_2016_3_2",
- build_file = "intellij_platform_sdk/BUILD.clion",
- url = "https://download.jetbrains.com/cpp/CLion-2016.3.2.tar.gz",
+ name = "intellij_ue_2017_1_5",
+ build_file = "intellij_platform_sdk/BUILD.idea",
+ url = "https://www.jetbrains.com/intellij-repository/releases/com/jetbrains/intellij/idea/ideaIU/2017.1.5/ideaIU-2017.1.5.zip",
+ sha256 = "6d887f79ca7853923060499cac7778b30fc82414f112deb8245d59179892bbad"
)
# The plugin api for CLion 2017.1.1. This is required to build CLwB,
@@ -48,6 +44,16 @@
name = "clion_2017_1_1",
build_file = "intellij_platform_sdk/BUILD.clion",
url = "https://download.jetbrains.com/cpp/CLion-2017.1.1.tar.gz",
+ sha256 = "9abd6bd38801ae6cf29db2cd133c700e8da11841093de872312fe33ed51309ae",
+)
+
+# The plugin api for CLion 2017.2.0. This is required to build CLwB,
+# and run integration tests.
+new_http_archive(
+ name = "clion_2017_2_1",
+ build_file = "intellij_platform_sdk/BUILD.clion",
+ url = "https://download.jetbrains.com/cpp/CLion-2017.2.1.tar.gz",
+ sha256 = "acd3d09a37a3fa922a85a48635d1b230d559ea68917e2e7895caf16460d50c13",
)
# The plugin api for Android Studio 2.3.1. This is required to build ASwB,
@@ -56,18 +62,16 @@
name = "android_studio_2_3_1_0",
build_file = "intellij_platform_sdk/BUILD.android_studio",
url = "https://dl.google.com/dl/android/studio/ide-zips/2.3.1.0/android-studio-ide-162.3871768-linux.zip",
+ sha256 = "36520f21678f80298b5df5fe5956db17a5984576f895fdcaa36ab0dbfb408433",
)
-# Python plugin for IntelliJ CE 2016.3. Required at compile-time for python-specific features.
+# The plugin api for Android Studio 3.0 Beta 1. This is required to build ASwB,
+# and run integration tests.
new_http_archive(
- name = "python_2016_3",
- build_file_content = "\n".join([
- "java_import(",
- " name = 'python',",
- " jars = ['python/lib/python.jar'],",
- " visibility = ['//visibility:public'],",
- ")"]),
- url = "https://plugins.jetbrains.com/files/7322/32326/python-community-163.298.zip",
+ name = "android_studio_3_0_0_9",
+ build_file = "intellij_platform_sdk/BUILD.android_studio",
+ url = "https://dl.google.com/dl/android/studio/ide-zips/3.0.0.9/android-studio-ide-171.4243858-linux.zip",
+ sha256 = "422cc6c85ee62186bdb81facd970c0058575dc45ba39f6fd8b326b430269f28c",
)
# Python plugin for IntelliJ CE 2017.1. Required at compile-time for python-specific features.
@@ -79,7 +83,66 @@
" jars = ['python-ce/lib/python-ce.jar'],",
" visibility = ['//visibility:public'],",
")"]),
- url = "https://plugins.jetbrains.com/files/7322/33704/python-ce-2017.1.171.3780.116.zip",
+ url = "https://download.plugins.jetbrains.com/7322/35756/python-ce-2017.1.171.4694.26.zip",
+ sha256 = "640d116ff01bcc2f87c75d20da642f17fcd86ed69929e4e0247ffc0df8aa780f",
+)
+
+# Python plugin for IntelliJ CE 2017.2. Required at compile-time for python-specific features.
+new_http_archive(
+ name = "python_2017_2",
+ build_file_content = "\n".join([
+ "java_import(",
+ " name = 'python',",
+ " jars = ['python-ce/lib/python-ce.jar'],",
+ " visibility = ['//visibility:public'],",
+ ")"]),
+ url = "https://download.plugins.jetbrains.com/7322/37356/python-ce-2017.2.172.3544.31.zip",
+ sha256 = "c7ee48c0bafb29f4a18eaac804b113c4dcdfeaaae174d9003c9ad96e44df6fe0",
+)
+
+# Go plugin for IntelliJ UE and CLion 2017.2.1. Required at compile-time for Bazel integration.
+new_http_archive(
+ name = "go_2017_2",
+ build_file_content = "\n".join([
+ "java_import(",
+ " name = 'go',",
+ " jars = glob(['intellij-go/lib/*.jar']),",
+ " visibility = ['//visibility:public'],",
+ ")"]),
+ url = "https://download.plugins.jetbrains.com/9568/37740/intellij-go-172.3757.46.zip",
+ sha256 = "3e5eb5415a05e6c30e79c263135c2937cc05e310e553889bd69eefa819705f9c",
+)
+
+# Go plugin for IntelliJ UE and CLion 2017.1.5. Required at compile-time for Bazel integration.
+new_http_archive(
+ name = "go_2017_1",
+ build_file_content = "\n".join([
+ "java_import(",
+ " name = 'go',",
+ " jars = glob(['intellij-go/lib/*.jar']),",
+ " visibility = ['//visibility:public'],",
+ ")"]),
+ url = "https://download.plugins.jetbrains.com/9568/36389/intellij-go-171.4694.61.zip",
+ sha256 = "16bf70045360c1c2a056c9ae540626dffa3680b8283cb89febaff3a9499b7101",
+)
+
+# Scala plugin for IntelliJ CE 2017.2 EAP. Required at compile-time for scala-specific features.
+new_http_archive(
+ name = "scala_2017_2",
+ build_file_content = "\n".join([
+ "java_import(",
+ " name = 'scala',",
+ " jars = [",
+ " 'Scala/lib/scala-plugin.jar',",
+ " 'Scala/lib/compiler-settings.jar',",
+ " 'Scala/lib/scala-library.jar',",
+ " 'Scala/lib/scalameta120.jar',",
+ " 'Scala/lib/scalatest-finders-patched.jar',",
+ " ],",
+ " visibility = ['//visibility:public'],",
+ ")"]),
+ url = "https://download.plugins.jetbrains.com/1347/35283/scala-intellij-bin-2017.2.2.zip",
+ sha256 = "1f0eef98da44dbc3f4f22b399a9175897aca448fd80405eca77fd61bd5fb7219",
)
# Scala plugin for IntelliJ CE 2017.1. Required at compile-time for scala-specific features.
@@ -87,17 +150,18 @@
name = "scala_2017_1",
build_file_content = "\n".join([
"java_import(",
- " name = 'scala-library',",
- " jars = ['Scala/lib/scala-library.jar'],",
- ")",
- "",
- "java_import(",
" name = 'scala',",
- " jars = ['Scala/lib/scala-plugin.jar'],",
- " runtime_deps = [':scala-library'],",
+ " jars = [",
+ " 'Scala/lib/scala-plugin.jar',",
+ " 'Scala/lib/compiler-settings.jar',",
+ " 'Scala/lib/scala-library.jar',",
+ " 'Scala/lib/scalameta120.jar',",
+ " 'Scala/lib/scalatest-finders-patched.jar',",
+ " ],",
" visibility = ['//visibility:public'],",
")"]),
url = "https://plugins.jetbrains.com/files/1347/33637/scala-intellij-bin-2017.1.15.zip",
+ sha256 = "b58670a3b52584effc6dd3d014e77fe80e2795b5e5e58716548ecc1452eca6cf",
)
# LICENSE: Common Public License 1.0
@@ -141,3 +205,17 @@
artifact = "com.googlecode.jarjar:jarjar:1.3",
sha1 = "b81c2719c63fa8e6f3eca5b11b8e9b5ad79463db",
)
+
+# LICENSE: The Apache Software License, Version 2.0
+maven_jar(
+ name = "auto_value",
+ artifact = "com.google.auto.value:auto-value:1.3",
+ sha1 = "4961194f62915eb45e21940537d60ac53912c57d",
+)
+
+# LICENSE: The Apache Software License, Version 2.0
+maven_jar(
+ name = "error_prone_annotations",
+ artifact = "com.google.errorprone:error_prone_annotations:2.0.15",
+ sha1 = "822652ed7196d119b35d2e22eb9cd4ffda11e640",
+)
diff --git a/aspect/BUILD b/aspect/BUILD
new file mode 100644
index 0000000..7f19914
--- /dev/null
+++ b/aspect/BUILD
@@ -0,0 +1,29 @@
+#
+# Description: Bazel aspect bundled with the Bazel IntelliJ plugin.
+#
+
+licenses(["notice"]) # Apache 2.0
+
+# the aspect files that will be bundled with the final plugin zip
+filegroup(
+ name = "aspect_files",
+ srcs = [
+ "WORKSPACE",
+ "intellij_info.bzl",
+ "intellij_info_impl.bzl",
+ ":BUILD.bazel",
+ "//aspect/tools:JarFilter_deploy.jar",
+ "//aspect/tools:PackageParser_deploy.jar",
+ ],
+ visibility = ["//visibility:public"],
+)
+
+# BUILD file bundled with the aspect must not override the BUILD file
+# used for development. So we name it BUILD.aspect, and rename prior
+# to bundling with the plugin.
+genrule(
+ name = "rename_files",
+ srcs = ["BUILD.aspect"],
+ outs = ["BUILD.bazel"],
+ cmd = "cp $< $@",
+)
diff --git a/aspect/BUILD.aspect b/aspect/BUILD.aspect
new file mode 100644
index 0000000..c109e12
--- /dev/null
+++ b/aspect/BUILD.aspect
@@ -0,0 +1,30 @@
+#
+# Description:
+# The final form of the BUILD file accessed at runtime as an external WORKSPACE.
+#
+
+licenses(["notice"]) # Apache 2.0
+
+java_binary(
+ name = "JarFilter_bin",
+ main_class = "com.google.idea.blaze.aspect.JarFilter",
+ runtime_deps = [":jar_filter_lib"],
+ visibility = ["//visibility:public"],
+)
+
+java_import(
+ name = "jar_filter_lib",
+ jars = ["JarFilter_deploy.jar"],
+)
+
+java_binary(
+ name = "PackageParser_bin",
+ main_class = "com.google.idea.blaze.aspect.PackageParser",
+ runtime_deps = [":package_parser_lib"],
+ visibility = ["//visibility:public"],
+)
+
+java_import(
+ name = "package_parser_lib",
+ jars = ["PackageParser_deploy.jar"],
+)
\ No newline at end of file
diff --git a/aspect/WORKSPACE b/aspect/WORKSPACE
new file mode 100644
index 0000000..e11b403
--- /dev/null
+++ b/aspect/WORKSPACE
@@ -0,0 +1 @@
+workspace(name = "intellij_aspect")
\ No newline at end of file
diff --git a/aspect/intellij_info.bzl b/aspect/intellij_info.bzl
new file mode 100644
index 0000000..3224efe
--- /dev/null
+++ b/aspect/intellij_info.bzl
@@ -0,0 +1,20 @@
+"""Bazel-specific intellij aspect."""
+
+load(
+ "//:intellij_info_impl.bzl",
+ "make_intellij_info_aspect",
+ "intellij_info_aspect_impl",
+)
+
+def tool_label(tool_name):
+ """Returns a label that points to a tool target in the bundled aspect workspace."""
+ return Label("//:" + tool_name + "_bin")
+
+semantics = struct(
+ tool_label = tool_label,
+)
+
+def _aspect_impl(target, ctx):
+ return intellij_info_aspect_impl(target, ctx, semantics)
+
+intellij_info_aspect = make_intellij_info_aspect(_aspect_impl, semantics)
diff --git a/aspect/intellij_info_impl.bzl b/aspect/intellij_info_impl.bzl
new file mode 100644
index 0000000..c5968e9
--- /dev/null
+++ b/aspect/intellij_info_impl.bzl
@@ -0,0 +1,753 @@
+"""Implementation of IntelliJ-specific information collecting aspect."""
+
+# Compile-time dependency attributes, grouped by type.
+DEPS = [
+ "_cc_toolchain", # From cc rules
+ "_stl", # From cc rules
+ "malloc", # From cc_binary rules
+ "_java_toolchain", # From java rules
+ "deps",
+ "exports",
+ "_robolectric", # From android_robolectric_test
+ "_android_sdk", # from android rules
+ "aidl_lib", # from android_sdk
+ "_scala_toolchain", # From scala rules
+]
+
+# Run-time dependency attributes, grouped by type.
+RUNTIME_DEPS = [
+ "runtime_deps",
+]
+
+PREREQUISITE_DEPS = []
+
+# Dependency type enum
+COMPILE_TIME = 0
+RUNTIME = 1
+
+##### Helpers
+
+def struct_omit_none(**kwargs):
+ """A replacement for standard `struct` function that omits the fields with None value."""
+ d = {name: kwargs[name] for name in kwargs if kwargs[name] != None}
+ return struct(**d)
+
+def artifact_location(f):
+ """Creates an ArtifactLocation proto from a File."""
+ if f == None:
+ return None
+
+ return to_artifact_location(
+ f.path,
+ f.root.path if not f.is_source else "",
+ f.is_source,
+ is_external_artifact(f.owner),
+ )
+
+def to_artifact_location(exec_path, root_exec_path_fragment, is_source, is_external):
+ """Derives workspace path from other path fragments, and creates an ArtifactLocation proto."""
+ # Bazel 0.4.4 has directory structure:
+ # exec_path = (root_fragment)? + (external/repo_name)? + relative_path
+ # Bazel 0.4.5 has planned directory structure:
+ # exec_path = (../repo_name)? + (root_fragment)? + relative_path
+ # Handle both cases by trying to strip the external workspace prefix before and after removing
+ # root_exec_path_fragment.
+ relative_path = strip_external_workspace_prefix(exec_path)
+ relative_path = strip_root_exec_path_fragment(relative_path, root_exec_path_fragment)
+ # Remove this line when Bazel 0.4.4 and earlier no longer need to be supported.
+ relative_path = strip_external_workspace_prefix(relative_path)
+
+ root_exec_path_fragment = exec_path[:-(len("/" + relative_path))]
+
+ return struct_omit_none(
+ relative_path = relative_path,
+ is_source = is_source,
+ is_external = is_external,
+ root_execution_path_fragment = root_exec_path_fragment,
+ is_new_external_version = True,
+ )
+
+def strip_root_exec_path_fragment(path, root_fragment):
+ if root_fragment and path.startswith(root_fragment + "/"):
+ return path[len(root_fragment + "/"):]
+ return path
+
+def strip_external_workspace_prefix(path):
+ """Either 'external/workspace_name/' or '../workspace_name/'."""
+ # Label.EXTERNAL_PATH_PREFIX is due to change from 'external' to '..' in Bazel 0.4.5.
+ # This code is for forwards and backwards compatibility.
+ # Remove the 'external/' check when Bazel 0.4.4 and earlier no longer need to be supported.
+ if path.startswith("../") or path.startswith("external/"):
+ return "/".join(path.split("/")[2:])
+ return path
+
+def is_external_artifact(label):
+ """Determines whether a label corresponds to an external artifact."""
+ # Label.EXTERNAL_PATH_PREFIX is due to change from 'external' to '..' in Bazel 0.4.5.
+ # This code is for forwards and backwards compatibility.
+ # Remove the 'external' check when Bazel 0.4.4 and earlier no longer need to be supported.
+ return label.workspace_root.startswith("external") or label.workspace_root.startswith("..")
+
+def source_directory_tuple(resource_file):
+ """Creates a tuple of (exec_path, root_exec_path_fragment, is_source, is_external)."""
+ relative_path = str(android_common.resource_source_directory(resource_file))
+ root_exec_path_fragment = resource_file.root.path if not resource_file.is_source else None
+ return (
+ relative_path if resource_file.is_source else root_exec_path_fragment + "/" + relative_path,
+ root_exec_path_fragment,
+ resource_file.is_source,
+ is_external_artifact(resource_file.owner)
+ )
+
+def all_unique_source_directories(resources):
+ """Builds a list of unique ArtifactLocation protos."""
+ # Sets can contain tuples, but cannot contain structs.
+ # Use set of tuples to unquify source directories.
+ source_directory_tuples = depset([source_directory_tuple(f) for f in resources])
+ return [to_artifact_location(
+ exec_path,
+ root_path_fragment,
+ is_source,
+ is_external)
+ for (exec_path, root_path_fragment, is_source, is_external) in source_directory_tuples]
+
+def build_file_artifact_location(ctx):
+ """Creates an ArtifactLocation proto representing a location of a given BUILD file."""
+ return to_artifact_location(
+ ctx.build_file_path,
+ ctx.build_file_path,
+ True,
+ is_external_artifact(ctx.label)
+ )
+
+def get_source_jar(output):
+ if hasattr(output, "source_jar"):
+ return output.source_jar
+ return None
+
+def library_artifact(java_output):
+ """Creates a LibraryArtifact representing a given java_output."""
+ if java_output == None or java_output.class_jar == None:
+ return None
+ return struct_omit_none(
+ jar = artifact_location(java_output.class_jar),
+ interface_jar = artifact_location(java_output.ijar),
+ source_jar = artifact_location(get_source_jar(java_output)),
+ )
+
+def annotation_processing_jars(annotation_processing):
+ """Creates a LibraryArtifact representing Java annotation processing jars."""
+ return struct_omit_none(
+ jar = artifact_location(annotation_processing.class_jar),
+ source_jar = artifact_location(annotation_processing.source_jar),
+ )
+
+def jars_from_output(output):
+ """Collect jars for intellij-resolve-files from Java output."""
+ if output == None:
+ return []
+ return [jar
+ for jar in [output.class_jar, output.ijar, get_source_jar(output)]
+ if jar != None and not jar.is_source]
+
+# TODO(salguarnieri) Remove once skylark provides the path safe string from a PathFragment.
+def replace_empty_path_with_dot(path):
+ return path or "."
+
+def sources_from_target(ctx):
+ """Get the list of sources from a target as artifact locations."""
+ return artifacts_from_target_list_attr(ctx, "srcs")
+
+def artifacts_from_target_list_attr(ctx, attr_name):
+ """Converts a list of targets to a list of artifact locations."""
+ return [artifact_location(f)
+ for target in getattr(ctx.rule.attr, attr_name, [])
+ for f in target.files]
+
+def _collect_target_from_attr(rule_attrs, attr_name, result):
+ """Collects the targets from the given attr into the result."""
+ if not hasattr(rule_attrs, attr_name):
+ return
+ attr_value = getattr(rule_attrs, attr_name)
+ type_name = type(attr_value)
+ if type_name == "Target":
+ result.append(attr_value)
+ elif type_name == "list":
+ result.extend(attr_value)
+
+def collect_targets_from_attrs(rule_attrs, attrs):
+ """Returns a list of targets from the given attributes."""
+ result = []
+ for attr_name in attrs:
+ _collect_target_from_attr(rule_attrs, attr_name, result)
+ return [target for target in result if is_valid_aspect_target(target)]
+
+def targets_to_labels(targets):
+ """Returns a set of label strings for the given targets."""
+ return depset([str(target.label) for target in targets])
+
+def list_omit_none(value):
+ """Returns a list of the value, or the empty list if None."""
+ return [value] if value else []
+
+def is_valid_aspect_target(target):
+ """Returns whether the target has had the aspect run on it."""
+ return hasattr(target, "intellij_info")
+
+def get_aspect_ids(ctx, target):
+ """Returns the all aspect ids, filtering out self."""
+ aspect_ids = None
+ if hasattr(ctx, "aspect_ids"):
+ aspect_ids = ctx.aspect_ids
+ elif hasattr(target, "aspect_ids"):
+ aspect_ids = target.aspect_ids
+ else:
+ return None
+ return [aspect_id for aspect_id in aspect_ids if "intellij_info_aspect" not in aspect_id]
+
+def make_target_key(label, aspect_ids):
+ """Returns a TargetKey proto struct from a target."""
+ return struct_omit_none(
+ label = str(label),
+ aspect_ids = tuple(aspect_ids) if aspect_ids else None
+ )
+
+def make_dep(dep, dependency_type):
+ """Returns a Dependency proto struct."""
+ return struct(
+ target = dep.intellij_info.target_key,
+ dependency_type = dependency_type,
+ )
+
+def make_deps(deps, dependency_type):
+ """Returns a list of Dependency proto structs."""
+ return [make_dep(dep, dependency_type) for dep in deps]
+
+def make_dep_from_label(label, dependency_type):
+ """Returns a Dependency proto struct from a label."""
+ return struct(
+ target = struct(label = str(label)),
+ dependency_type = dependency_type,
+ )
+
+def update_set_in_dict(input_dict, key, other_set):
+ """Updates depset in dict, merging it with another depset."""
+ input_dict[key] = input_dict.get(key, depset()) | other_set
+
+##### Builders for individual parts of the aspect output
+
+def collect_py_info(target, ctx, ide_info, ide_info_file, output_groups):
+ """Updates Python-specific output groups, returns false if not a Python target."""
+ if not hasattr(target, "py"):
+ return False
+
+ ide_info["py_ide_info"] = struct_omit_none(
+ sources = sources_from_target(ctx),
+ )
+ transitive_sources = target.py.transitive_sources
+ # TODO(brendandouglas): target to python files only
+ compile_files = target.output_group("files_to_compile_INTERNAL_")
+
+ update_set_in_dict(output_groups, "intellij-info-py", depset([ide_info_file]))
+ update_set_in_dict(output_groups, "intellij-compile-py", compile_files)
+ update_set_in_dict(output_groups, "intellij-resolve-py", transitive_sources)
+
+ # Add to legacy output groups for backwards compatibility
+ update_set_in_dict(output_groups, "intellij-compile", compile_files)
+ update_set_in_dict(output_groups, "intellij-resolve", transitive_sources)
+ return True
+
+def _collect_generated_proto_go_sources(target):
+ """Returns a depset of proto go source files generated by this target."""
+ if not hasattr(target, "proto_go_api_info"):
+ return None
+ files = getattr(target.proto_go_api_info, "files_to_build", [])
+ return depset([f for f in files if f.basename.endswith(".pb.go")])
+
+def collect_go_info(target, ide_info, ide_info_file, output_groups):
+ """Updates Go-specific output groups, returns false if not a recognized Go target."""
+ # currently there's no Go Skylark API, with the only exception being proto_library targets
+ if not hasattr(target, "proto_go_api_info"):
+ return False
+
+ generated_sources = _collect_generated_proto_go_sources(target)
+
+ ide_info["go_ide_info"] = struct_omit_none(
+ generated_sources = [artifact_location(f) for f in generated_sources],
+ )
+
+ update_set_in_dict(output_groups, "intellij-info-go", depset([ide_info_file]))
+ update_set_in_dict(output_groups, "intellij-compile-go", generated_sources)
+ update_set_in_dict(output_groups, "intellij-resolve-go", generated_sources)
+ return True
+
+def collect_cpp_info(target, ctx, ide_info, ide_info_file, output_groups):
+ """Updates C++-specific output groups, returns false if not a C++ target."""
+ if not hasattr(target, "cc"):
+ return False
+
+ sources = artifacts_from_target_list_attr(ctx, "srcs")
+ headers = artifacts_from_target_list_attr(ctx, "hdrs")
+ textual_headers = artifacts_from_target_list_attr(ctx, "textual_hdrs")
+
+ target_includes = []
+ if hasattr(ctx.rule.attr, "includes"):
+ target_includes = ctx.rule.attr.includes
+ target_defines = []
+ if hasattr(ctx.rule.attr, "defines"):
+ target_defines = ctx.rule.attr.defines
+ target_copts = []
+ if hasattr(ctx.rule.attr, "copts"):
+ target_copts = ctx.rule.attr.copts
+
+ cc_provider = target.cc
+
+ c_info = struct_omit_none(
+ source = sources,
+ header = headers,
+ textual_header = textual_headers,
+ target_include = target_includes,
+ target_define = target_defines,
+ target_copt = target_copts,
+ transitive_include_directory = cc_provider.include_directories,
+ transitive_quote_include_directory = cc_provider.quote_include_directories,
+ transitive_define = cc_provider.defines,
+ transitive_system_include_directory = cc_provider.system_include_directories,
+ )
+ ide_info["c_ide_info"] = c_info
+ resolve_files = cc_provider.transitive_headers
+ # TODO(brendandouglas): target to cpp files only
+ compile_files = target.output_group("files_to_compile_INTERNAL_")
+
+ update_set_in_dict(output_groups, "intellij-info-cpp", depset([ide_info_file]))
+ update_set_in_dict(output_groups, "intellij-compile-cpp", compile_files)
+ update_set_in_dict(output_groups, "intellij-resolve-cpp", resolve_files)
+
+ # Add to legacy output groups for backwards compatibility
+ update_set_in_dict(output_groups, "intellij-compile", compile_files)
+ update_set_in_dict(output_groups, "intellij-resolve", resolve_files)
+ return True
+
+def collect_c_toolchain_info(ctx, ide_info, ide_info_file, output_groups):
+ """Updates cc_toolchain-relevant output groups, returns false if not a cc_toolchain target."""
+ if ctx.rule.kind != "cc_toolchain":
+ return False
+
+ # This should exist because we requested it in our aspect definition.
+ cc_fragment = ctx.fragments.cpp
+
+ c_toolchain_info = struct_omit_none(
+ target_name = cc_fragment.target_gnu_system_name,
+ base_compiler_option = cc_fragment.compiler_options(ctx.features),
+ c_option = cc_fragment.c_options,
+ cpp_option = cc_fragment.cxx_options(ctx.features),
+ link_option = cc_fragment.link_options,
+ unfiltered_compiler_option = cc_fragment.unfiltered_compiler_options(ctx.features),
+ preprocessor_executable = replace_empty_path_with_dot(
+ str(cc_fragment.preprocessor_executable)),
+ cpp_executable = str(cc_fragment.compiler_executable),
+ built_in_include_directory = [str(d)
+ for d in cc_fragment.built_in_include_directories],
+ )
+ ide_info["c_toolchain_ide_info"] = c_toolchain_info
+ update_set_in_dict(output_groups, "intellij-info-cpp", depset([ide_info_file]))
+ return True
+
+def get_java_provider(target):
+ if hasattr(target, "proto_java"):
+ return target.proto_java
+ if hasattr(target, "java"):
+ return target.java
+ if hasattr(target, "scala"):
+ return target.scala
+ return None
+
+def get_java_jars(outputs):
+ """Handle both Java (java.outputs.jars list) and Scala (single scala.outputs) targets."""
+ if hasattr(outputs, "jars"):
+ return outputs.jars
+ if hasattr(outputs, "class_jar"):
+ return [outputs]
+ return []
+
+def collect_java_info(target, ctx, semantics, ide_info, ide_info_file, output_groups):
+ """Updates Java-specific output groups, returns false if not a Java target."""
+ java = get_java_provider(target)
+ if not java:
+ return False
+
+ java_semantics = semantics.java if hasattr(semantics, "java") else None
+ if java_semantics and java_semantics.skip_target(target, ctx):
+ return False
+
+ ide_info_files = depset()
+ sources = sources_from_target(ctx)
+ java_jars = get_java_jars(java.outputs)
+ jars = [library_artifact(output) for output in java_jars]
+ class_jars = [output.class_jar for output in java_jars if output and output.class_jar]
+ output_jars = [jar for output in java_jars for jar in jars_from_output(output)]
+ resolve_files = depset(output_jars)
+ compile_files = depset(class_jars)
+
+ gen_jars = []
+ if (hasattr(java, "annotation_processing") and
+ java.annotation_processing and
+ java.annotation_processing.enabled):
+ gen_jars = [annotation_processing_jars(java.annotation_processing)]
+ resolve_files = resolve_files | depset([
+ jar for jar in [java.annotation_processing.class_jar,
+ java.annotation_processing.source_jar]
+ if jar != None and not jar.is_source])
+ compile_files = compile_files | depset([
+ jar for jar in [java.annotation_processing.class_jar]
+ if jar != None and not jar.is_source])
+
+ jdeps = None
+ if hasattr(java.outputs, "jdeps"):
+ jdeps = artifact_location(java.outputs.jdeps)
+
+ java_sources, gen_java_sources, srcjars = divide_java_sources(ctx)
+
+ if java_semantics:
+ srcjars = java_semantics.filter_source_jars(target, ctx, srcjars)
+
+ package_manifest = None
+ if java_sources:
+ package_manifest = build_java_package_manifest(ctx, target, java_sources, ".java-manifest")
+ ide_info_files = ide_info_files | depset([package_manifest])
+
+ filtered_gen_jar = None
+ if java_sources and (gen_java_sources or srcjars):
+ filtered_gen_jar, filtered_gen_resolve_files = build_filtered_gen_jar(
+ ctx,
+ target,
+ java,
+ gen_java_sources,
+ srcjars
+ )
+ resolve_files = resolve_files | filtered_gen_resolve_files
+
+ java_info = struct_omit_none(
+ sources = sources,
+ jars = jars,
+ jdeps = jdeps,
+ generated_jars = gen_jars,
+ package_manifest = artifact_location(package_manifest),
+ filtered_gen_jar = filtered_gen_jar,
+ main_class = ctx.rule.attr.main_class if hasattr(ctx.rule.attr, "main_class") else None,
+ )
+
+ ide_info["java_ide_info"] = java_info
+ ide_info_files += depset([ide_info_file])
+ update_set_in_dict(output_groups, "intellij-info-java", ide_info_files)
+ update_set_in_dict(output_groups, "intellij-compile-java", compile_files)
+ update_set_in_dict(output_groups, "intellij-resolve-java", resolve_files)
+
+ # Add to legacy output groups for backwards compatibility
+ update_set_in_dict(output_groups, "intellij-info-text", ide_info_files)
+ update_set_in_dict(output_groups, "intellij-compile", compile_files)
+ update_set_in_dict(output_groups, "intellij-resolve", resolve_files)
+ return True
+
+def _package_manifest_file_argument(f):
+ artifact = artifact_location(f)
+ is_external = "1" if is_external_artifact(f.owner) else "0"
+ return artifact.root_execution_path_fragment + "," + artifact.relative_path + "," + is_external
+
+def build_java_package_manifest(ctx, target, source_files, suffix):
+ """Builds the java package manifest for the given source files."""
+ output = ctx.new_file(target.label.name + suffix)
+
+ args = []
+ args += ["--output_manifest", output.path]
+ args += ["--sources"]
+ args += [":".join([_package_manifest_file_argument(f) for f in source_files])]
+ argfile = ctx.new_file(ctx.configuration.bin_dir,
+ target.label.name + suffix + ".params")
+ ctx.file_action(output=argfile, content="\n".join(args))
+
+ ctx.action(
+ inputs = source_files + [argfile],
+ outputs = [output],
+ executable = ctx.executable._package_parser,
+ arguments = ["@" + argfile.path],
+ mnemonic = "JavaPackageManifest",
+ progress_message = "Parsing java package strings for " + str(target.label),
+ )
+ return output
+
+def build_filtered_gen_jar(ctx, target, java, gen_java_sources, srcjars):
+ """Filters the passed jar to contain only classes from the given manifest."""
+ jar_artifacts = []
+ source_jar_artifacts = []
+ for jar in java.outputs.jars:
+ if jar.ijar:
+ jar_artifacts.append(jar.ijar)
+ elif jar.class_jar:
+ jar_artifacts.append(jar.class_jar)
+ if jar.source_jar:
+ source_jar_artifacts.append(jar.source_jar)
+
+ filtered_jar = ctx.new_file(target.label.name + "-filtered-gen.jar")
+ filtered_source_jar = ctx.new_file(target.label.name + "-filtered-gen-src.jar")
+ args = []
+ for jar in jar_artifacts:
+ args += ["--filter_jar", jar.path]
+ for jar in source_jar_artifacts:
+ args += ["--filter_source_jar", jar.path]
+ args += ["--filtered_jar", filtered_jar.path]
+ args += ["--filtered_source_jar", filtered_source_jar.path]
+ if gen_java_sources:
+ for java_file in gen_java_sources:
+ args += ["--keep_java_file", java_file.path]
+ if srcjars:
+ for source_jar in srcjars:
+ args += ["--keep_source_jar", source_jar.path]
+ ctx.action(
+ inputs = jar_artifacts + source_jar_artifacts + gen_java_sources + srcjars,
+ outputs = [filtered_jar, filtered_source_jar],
+ executable = ctx.executable._jar_filter,
+ arguments = args,
+ mnemonic = "JarFilter",
+ progress_message = "Filtering generated code for " + str(target.label),
+ )
+ output_jar = struct(
+ jar=artifact_location(filtered_jar),
+ source_jar=artifact_location(filtered_source_jar),
+ )
+ intellij_resolve_files = depset([filtered_jar, filtered_source_jar])
+ return output_jar, intellij_resolve_files
+
+def divide_java_sources(ctx):
+ """Divide sources into plain java, generated java, and srcjars."""
+
+ java_sources = []
+ gen_java_sources = []
+ srcjars = []
+ if hasattr(ctx.rule.attr, "srcs"):
+ srcs = ctx.rule.attr.srcs
+ for src in srcs:
+ for f in src.files:
+ if f.basename.endswith(".java"):
+ if f.is_source:
+ java_sources.append(f)
+ else:
+ gen_java_sources.append(f)
+ elif f.basename.endswith(".srcjar"):
+ srcjars.append(f)
+
+ return java_sources, gen_java_sources, srcjars
+
+def collect_android_info(target, ctx, semantics, ide_info, ide_info_file, output_groups):
+ """Updates Android-specific output groups, returns false if not a Android target."""
+ if not hasattr(target, "android"):
+ return False
+
+ android_semantics = semantics.android if hasattr(semantics, "android") else None
+ extra_ide_info = android_semantics.extra_ide_info(target, ctx) if android_semantics else {}
+
+ android = target.android
+ android_info = struct_omit_none(
+ java_package = android.java_package,
+ idl_import_root = android.idl.import_root if hasattr(android.idl, "import_root") else None,
+ manifest = artifact_location(android.manifest),
+ apk = artifact_location(android.apk),
+ dependency_apk = [artifact_location(apk) for apk in android.apks_under_test],
+ has_idl_sources = android.idl.output != None,
+ idl_jar = library_artifact(android.idl.output),
+ generate_resource_class = android.defines_resources,
+ resources = all_unique_source_directories(android.resources),
+ resource_jar = library_artifact(android.resource_jar),
+ **extra_ide_info
+ )
+ resolve_files = depset(jars_from_output(android.idl.output))
+
+ if android.manifest and not android.manifest.is_source:
+ resolve_files = resolve_files | depset([android.manifest])
+
+ ide_info["android_ide_info"] = android_info
+ update_set_in_dict(output_groups, "intellij-info-android", depset([ide_info_file]))
+ update_set_in_dict(output_groups, "intellij-resolve-android", resolve_files)
+
+ # Add to legacy output groups for backwards compatibility
+ update_set_in_dict(output_groups, "intellij-resolve", resolve_files)
+ return True
+
+def collect_android_sdk_info(ctx, ide_info, ide_info_file, output_groups):
+ """Updates android_sdk-relevant groups, returns false if not an android_sdk target."""
+ if ctx.rule.kind != "android_sdk":
+ return False
+ android_jar_file = list(ctx.rule.attr.android_jar.files)[0]
+ ide_info["android_sdk_ide_info"] = struct(
+ android_jar = artifact_location(android_jar_file),
+ )
+ update_set_in_dict(output_groups, "intellij-info-android", depset([ide_info_file]))
+ return True
+
+def build_test_info(ctx):
+ """Build TestInfo."""
+ if not is_test_rule(ctx):
+ return None
+ return struct_omit_none(
+ size = ctx.rule.attr.size,
+ )
+
+def is_test_rule(ctx):
+ kind_string = ctx.rule.kind
+ return kind_string.endswith("_test")
+
+def collect_java_toolchain_info(target, ide_info, ide_info_file, output_groups):
+ """Updates java_toolchain-relevant output groups, returns false if not a java_toolchain target."""
+ if not hasattr(target, "java_toolchain"):
+ return False
+ toolchain_info = target.java_toolchain
+ javac_jar_file = toolchain_info.javac_jar if hasattr(toolchain_info, "javac_jar") else None
+ ide_info["java_toolchain_ide_info"] = struct_omit_none(
+ source_version = toolchain_info.source_version,
+ target_version = toolchain_info.target_version,
+ javac_jar = artifact_location(javac_jar_file),
+ )
+ update_set_in_dict(output_groups, "intellij-info-java", depset([ide_info_file]))
+ return True
+
+##### Main aspect function
+
+def intellij_info_aspect_impl(target, ctx, semantics):
+ """Aspect implementation function."""
+ tags = ctx.rule.attr.tags
+ if "no-ide" in tags:
+ return struct()
+
+ rule_attrs = ctx.rule.attr
+
+ # Collect direct dependencies
+ direct_dep_targets = collect_targets_from_attrs(
+ rule_attrs, semantics_extra_deps(DEPS, semantics, "extra_deps"))
+ direct_deps = make_deps(direct_dep_targets, COMPILE_TIME)
+
+ # Add exports from direct dependencies
+ exported_deps_from_deps = []
+ for dep in direct_dep_targets:
+ exported_deps_from_deps = exported_deps_from_deps + dep.intellij_info.export_deps
+
+ # Combine into all compile time deps
+ compiletime_deps = direct_deps + exported_deps_from_deps
+
+ # Propagate my own exports
+ export_deps = []
+ if hasattr(target, "java"):
+ transitive_exports = target.java.transitive_exports
+ export_deps = [make_dep_from_label(label, COMPILE_TIME) for label in transitive_exports]
+ # Empty android libraries export all their dependencies.
+ if ctx.rule.kind == "android_library":
+ if not hasattr(rule_attrs, "srcs") or not ctx.rule.attr.srcs:
+ export_deps = export_deps + compiletime_deps
+ export_deps = list(depset(export_deps))
+
+ # runtime_deps
+ runtime_dep_targets = collect_targets_from_attrs(
+ rule_attrs, semantics_extra_deps(RUNTIME_DEPS, semantics, "extra_runtime_deps"))
+ runtime_deps = make_deps(runtime_dep_targets, RUNTIME)
+ all_deps = list(depset(compiletime_deps + runtime_deps))
+
+ # extra prerequisites
+ extra_prerequisite_targets = collect_targets_from_attrs(
+ rule_attrs, semantics_extra_deps(PREREQUISITE_DEPS, semantics, "extra_prerequisites"))
+
+ # Roll up output files from my prerequisites
+ prerequisites = direct_dep_targets + runtime_dep_targets + extra_prerequisite_targets
+ output_groups = dict()
+ for dep in prerequisites:
+ for k, v in dep.intellij_info.output_groups.items():
+ update_set_in_dict(output_groups, k, v)
+
+ # Initialize the ide info dict, and corresponding output file
+ # This will be passed to each language-specific handler to fill in as required
+ file_name = target.label.name
+ aspect_ids = get_aspect_ids(ctx, target)
+ if aspect_ids:
+ aspect_hash = hash(".".join(aspect_ids))
+ file_name = file_name + "-" + str(aspect_hash)
+ file_name = file_name + ".intellij-info.txt"
+ ide_info_file = ctx.new_file(file_name)
+
+ target_key = make_target_key(target.label, aspect_ids)
+ ide_info = dict(
+ key = target_key,
+ kind_string = ctx.rule.kind,
+ deps = list(all_deps),
+ build_file_artifact_location = build_file_artifact_location(ctx),
+ tags = tags,
+ features = ctx.features,
+ )
+ # Collect test info
+ ide_info["test_info"] = build_test_info(ctx)
+
+ handled = False
+ handled = collect_py_info(target, ctx, ide_info, ide_info_file, output_groups) or handled
+ handled = collect_cpp_info(target, ctx, ide_info, ide_info_file, output_groups) or handled
+ handled = collect_c_toolchain_info(ctx, ide_info, ide_info_file, output_groups) or handled
+ handled = collect_go_info(target, ide_info, ide_info_file, output_groups) or handled
+ handled = collect_java_info(target, ctx, semantics, ide_info, ide_info_file, output_groups) or handled
+ handled = collect_java_toolchain_info(target, ide_info, ide_info_file, output_groups) or handled
+ handled = collect_android_info(target, ctx, semantics, ide_info, ide_info_file, output_groups) or handled
+ handled = collect_android_sdk_info(ctx, ide_info, ide_info_file, output_groups) or handled
+
+ # Any extra ide info
+ if hasattr(semantics, "extra_ide_info"):
+ handled = semantics.extra_ide_info(target, ctx, ide_info, ide_info_file, output_groups) or handled
+
+ # Add to generic output group if it's not handled by a language-specific handler
+ if not handled:
+ update_set_in_dict(output_groups, "intellij-info-generic", depset([ide_info_file]))
+
+ # Add to legacy output group for backwards compatibility
+ update_set_in_dict(output_groups, "intellij-info-text", depset([ide_info_file]))
+
+ # Output the ide information file.
+ info = struct_omit_none(**ide_info)
+ ctx.file_action(ide_info_file, info.to_proto())
+
+ # Return providers.
+ return struct_omit_none(
+ output_groups = output_groups,
+ intellij_info = struct(
+ target_key = target_key,
+ output_groups = output_groups,
+ export_deps = export_deps,
+ ),
+ )
+
+def semantics_extra_deps(base, semantics, name):
+ if not hasattr(semantics, name):
+ return base
+ extra_deps = getattr(semantics, name)
+ return base + extra_deps
+
+def make_intellij_info_aspect(aspect_impl, semantics):
+ """Creates the aspect given the semantics."""
+ tool_label = semantics.tool_label
+ deps = semantics_extra_deps(DEPS, semantics, "extra_deps")
+ runtime_deps = semantics_extra_deps(RUNTIME_DEPS, semantics, "extra_runtime_deps")
+ prerequisite_deps = semantics_extra_deps(PREREQUISITE_DEPS, semantics, "extra_prerequisites")
+
+ attr_aspects = deps + runtime_deps + prerequisite_deps
+
+ return aspect(
+ attrs = {
+ "_package_parser": attr.label(
+ default = tool_label("PackageParser"),
+ cfg = "host",
+ executable = True,
+ allow_files = True),
+ "_jar_filter": attr.label(
+ default = tool_label("JarFilter"),
+ cfg = "host",
+ executable = True,
+ allow_files = True),
+ },
+ attr_aspects = attr_aspects,
+ fragments = ["cpp"],
+ implementation = aspect_impl,
+ required_aspect_providers = ["proto_java"],
+ )
diff --git a/aspect/tools/BUILD b/aspect/tools/BUILD
new file mode 100644
index 0000000..fe42712
--- /dev/null
+++ b/aspect/tools/BUILD
@@ -0,0 +1,84 @@
+#
+# Description:
+# Tools needed by the bazel plugin's aspect.
+#
+
+package(default_visibility = ["//aspect:__pkg__"])
+
+licenses(["notice"]) # Apache 2.0
+
+# To prevent versioning conflicts when developing internally, we always use the same
+# guava version bundled with the IntelliJ plugin API.
+java_library(
+ name = "guava",
+ exports = ["//intellij_platform_sdk:guava"],
+)
+
+java_library(
+ name = "lib",
+ srcs = glob(["src/**/*.java"]),
+ deps = [
+ ":guava",
+ "//proto:proto_deps",
+ "@jsr305_annotations//jar",
+ ],
+)
+
+java_binary(
+ name = "JarFilter",
+ main_class = "com.google.idea.blaze.aspect.JarFilter",
+ visibility = ["//visibility:public"],
+ runtime_deps = [":lib"],
+)
+
+java_binary(
+ name = "PackageParser",
+ main_class = "com.google.idea.blaze.aspect.PackageParser",
+ visibility = ["//visibility:public"],
+ runtime_deps = [":lib"],
+)
+
+java_library(
+ name = "test_lib",
+ testonly = 1,
+ exports = [
+ ":guava",
+ ":lib",
+ "//intellij_platform_sdk:truth",
+ "//proto:proto_deps",
+ "@jsr305_annotations//jar",
+ "@junit//jar",
+ ],
+)
+
+java_test(
+ name = "JarFilterTest",
+ size = "medium",
+ srcs = ["tests/unittests/com/google/idea/blaze/aspect/JarFilterTest.java"],
+ test_class = "com.google.idea.blaze.aspect.JarFilterTest",
+ deps = [":test_lib"],
+)
+
+java_test(
+ name = "PackageParserTest",
+ size = "small",
+ srcs = ["tests/unittests/com/google/idea/blaze/aspect/PackageParserTest.java"],
+ test_class = "com.google.idea.blaze.aspect.PackageParserTest",
+ deps = [":test_lib"],
+)
+
+java_test(
+ name = "OptionParserTest",
+ size = "small",
+ srcs = ["tests/unittests/com/google/idea/blaze/aspect/OptionParserTest.java"],
+ test_class = "com.google.idea.blaze.aspect.OptionParserTest",
+ deps = [":test_lib"],
+)
+
+java_test(
+ name = "ArtifactLocationParserTest",
+ size = "small",
+ srcs = ["tests/unittests/com/google/idea/blaze/aspect/ArtifactLocationParserTest.java"],
+ test_class = "com.google.idea.blaze.aspect.ArtifactLocationParserTest",
+ deps = [":test_lib"],
+)
diff --git a/aspect/tools/src/com/google/idea/blaze/aspect/ArtifactLocationParser.java b/aspect/tools/src/com/google/idea/blaze/aspect/ArtifactLocationParser.java
new file mode 100644
index 0000000..6a5088c
--- /dev/null
+++ b/aspect/tools/src/com/google/idea/blaze/aspect/ArtifactLocationParser.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.aspect;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import com.google.repackaged.devtools.intellij.ideinfo.IntellijIdeInfo.ArtifactLocation;
+import java.nio.file.FileSystems;
+import java.nio.file.Path;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+/** Parses an {@link ArtifactLocation} from a comma-separate list of string-encoded fields. */
+@VisibleForTesting
+public final class ArtifactLocationParser {
+ private ArtifactLocationParser() {}
+
+ private static final Splitter SPLITTER = Splitter.on(',');
+
+ @VisibleForTesting
+ static final String INVALID_FORMAT =
+ "Expected format rootExecutionPathFragment,relPath,isExternal";
+
+ private static Path getPath(String pathString) {
+ return FileSystems.getDefault().getPath(pathString);
+ }
+
+ /** Parse a colon-separated list of string-encoded {@link ArtifactLocation}s. */
+ static List<ArtifactLocation> parseList(String input) {
+ ImmutableList.Builder<ArtifactLocation> builder = ImmutableList.builder();
+ for (String piece : input.split(":")) {
+ if (!piece.isEmpty()) {
+ builder.add(parse(piece));
+ }
+ }
+ return builder.build();
+ }
+
+ /** Parse an {@link ArtifactLocation} from a comma-separate list of string-encoded fields. */
+ static ArtifactLocation parse(String input) {
+ Iterator<String> values = SPLITTER.split(input).iterator();
+ try {
+ Path rootExecutionPathFragment = getPath(values.next());
+ Path relPath = getPath(values.next());
+ boolean isExternal = values.next().equals("1");
+ if (values.hasNext()) {
+ throw new IllegalArgumentException(INVALID_FORMAT);
+ }
+
+ boolean isSource = rootExecutionPathFragment.toString().isEmpty();
+ return ArtifactLocation.newBuilder()
+ .setRootExecutionPathFragment(rootExecutionPathFragment.toString())
+ .setRelativePath(relPath.toString())
+ .setIsSource(isSource)
+ .setIsExternal(isExternal)
+ .build();
+
+ } catch (NoSuchElementException e) {
+ throw new IllegalArgumentException(INVALID_FORMAT);
+ }
+ }
+}
diff --git a/aspect/tools/src/com/google/idea/blaze/aspect/JarFilter.java b/aspect/tools/src/com/google/idea/blaze/aspect/JarFilter.java
new file mode 100644
index 0000000..53cb58d
--- /dev/null
+++ b/aspect/tools/src/com/google/idea/blaze/aspect/JarFilter.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.aspect;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.io.Files;
+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 java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.FileSystems;
+import java.nio.file.Path;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executors;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+import javax.annotation.Nullable;
+
+/** Filters a jar, keeping only the classes that are indicated. */
+public final class JarFilter {
+
+ /** The options for a {@link JarFilter} action. */
+ @VisibleForTesting
+ static final class JarFilterOptions {
+ List<Path> filterJars;
+ List<Path> filterSourceJars;
+ List<Path> keepJavaFiles;
+ List<Path> keepSourceJars;
+ Path filteredJar;
+ Path filteredSourceJar;
+ }
+
+ @VisibleForTesting
+ static JarFilterOptions parseArgs(String[] args) {
+ args = parseParamFileIfUsed(args);
+ JarFilterOptions options = new JarFilterOptions();
+ options.filterJars = OptionParser.parseMultiOption(args, "filter_jar", PATH_PARSER);
+ options.filterSourceJars =
+ OptionParser.parseMultiOption(args, "filter_source_jar", PATH_PARSER);
+ options.keepJavaFiles = OptionParser.parseMultiOption(args, "keep_java_file", PATH_PARSER);
+ options.keepSourceJars = OptionParser.parseMultiOption(args, "keep_source_jar", PATH_PARSER);
+ options.filteredJar = OptionParser.parseSingleOption(args, "filtered_jar", PATH_PARSER);
+ options.filteredSourceJar =
+ OptionParser.parseSingleOption(args, "filtered_source_jar", PATH_PARSER);
+ return options;
+ }
+
+ private static final Function<String, Path> PATH_PARSER =
+ string -> FileSystems.getDefault().getPath(string);
+
+ private static final Logger logger = Logger.getLogger(JarFilter.class.getName());
+
+ private static final Pattern JAVA_PACKAGE_PATTERN =
+ Pattern.compile("^\\s*package\\s+([\\w\\.]+);");
+
+ public static void main(String[] args) throws Exception {
+ JarFilterOptions options = parseArgs(args);
+ try {
+ main(options);
+ } catch (Throwable e) {
+ logger.log(Level.SEVERE, "Error filtering jars", e);
+ System.exit(1);
+ }
+ System.exit(0);
+ }
+
+ @VisibleForTesting
+ static void main(JarFilterOptions options) throws Exception {
+ Preconditions.checkNotNull(options.filteredJar);
+
+ if (options.filterJars == null) {
+ options.filterJars = ImmutableList.of();
+ }
+ if (options.filterSourceJars == null) {
+ options.filterSourceJars = ImmutableList.of();
+ }
+
+ final List<String> archiveFileNamePrefixes = Lists.newArrayList();
+ if (options.keepJavaFiles != null) {
+ archiveFileNamePrefixes.addAll(parseJavaFiles(options.keepJavaFiles));
+ }
+ if (options.keepSourceJars != null) {
+ archiveFileNamePrefixes.addAll(parseSrcJars(options.keepSourceJars));
+ }
+
+ filterJars(
+ options.filterJars,
+ options.filteredJar,
+ string -> shouldKeepClass(archiveFileNamePrefixes, string));
+ if (options.filteredSourceJar != null) {
+ filterJars(
+ options.filterSourceJars,
+ options.filteredSourceJar,
+ string -> shouldKeepJavaFile(archiveFileNamePrefixes, string));
+ }
+ }
+
+ private static String[] parseParamFileIfUsed(String[] args) {
+ if (args.length != 1 || !args[0].startsWith("@")) {
+ return args;
+ }
+ File paramFile = new File(args[0].substring(1));
+ try {
+ return Files.readLines(paramFile, StandardCharsets.UTF_8).toArray(new String[0]);
+ } catch (IOException e) {
+ throw new RuntimeException("Error parsing param file: " + args[0], e);
+ }
+ }
+
+ /** Finds the expected jar archive file name prefixes for the java files. */
+ private static List<String> parseJavaFiles(List<Path> javaFiles) throws IOException {
+ ListeningExecutorService executorService =
+ MoreExecutors.listeningDecorator(
+ Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()));
+
+ List<ListenableFuture<String>> futures = Lists.newArrayList();
+ for (final Path javaFile : javaFiles) {
+ futures.add(
+ executorService.submit(
+ () -> {
+ String packageString = getDeclaredPackageOfJavaFile(javaFile);
+ return packageString != null
+ ? getArchiveFileNamePrefix(javaFile.toString(), packageString)
+ : null;
+ }));
+ }
+ try {
+ List<String> archiveFileNamePrefixes = Futures.allAsList(futures).get();
+ List<String> result = Lists.newArrayList();
+ for (String archiveFileNamePrefix : archiveFileNamePrefixes) {
+ if (archiveFileNamePrefix != null) {
+ result.add(archiveFileNamePrefix);
+ }
+ }
+ return result;
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new IOException(e);
+ } catch (ExecutionException e) {
+ throw new IOException(e);
+ }
+ }
+
+ private static List<String> parseSrcJars(List<Path> srcJars) throws IOException {
+ List<String> result = Lists.newArrayList();
+ for (Path srcJar : srcJars) {
+ try (ZipFile sourceZipFile = new ZipFile(srcJar.toFile())) {
+ Enumeration<? extends ZipEntry> entries = sourceZipFile.entries();
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
+ if (!entry.getName().endsWith(".java")) {
+ continue;
+ }
+ try (BufferedReader reader =
+ new BufferedReader(
+ new InputStreamReader(sourceZipFile.getInputStream(entry), UTF_8))) {
+ String packageString = parseDeclaredPackage(reader);
+ if (packageString != null) {
+ String archiveFileNamePrefix =
+ getArchiveFileNamePrefix(entry.getName(), packageString);
+ result.add(archiveFileNamePrefix);
+ }
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ @Nullable
+ private static String getDeclaredPackageOfJavaFile(Path javaFile) {
+ try (BufferedReader reader =
+ java.nio.file.Files.newBufferedReader(javaFile, StandardCharsets.UTF_8)) {
+ return parseDeclaredPackage(reader);
+
+ } catch (IOException e) {
+ logger.log(Level.WARNING, "Error parsing package string from java source: " + javaFile, e);
+ return null;
+ }
+ }
+
+ @Nullable
+ private static String parseDeclaredPackage(BufferedReader reader) throws IOException {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ Matcher packageMatch = JAVA_PACKAGE_PATTERN.matcher(line);
+ if (packageMatch.find()) {
+ return packageMatch.group(1);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Computes the expected archive file name prefix of a java class.
+ *
+ * <p>Eg.: file java/com/google/foo/Foo.java, package com.google.foo -> com/google/foo/Foo
+ */
+ private static String getArchiveFileNamePrefix(String javaFile, String packageString) {
+ int lastSlashIndex = javaFile.lastIndexOf('/');
+ // On Windows, the separator could be '\\'
+ if (lastSlashIndex == -1) {
+ lastSlashIndex = javaFile.lastIndexOf('\\');
+ }
+ String fileName = lastSlashIndex != -1 ? javaFile.substring(lastSlashIndex + 1) : javaFile;
+ String className = fileName.substring(0, fileName.length() - ".java".length());
+ return packageString.replace('.', '/') + '/' + className;
+ }
+
+ /** Filters a list of jars, keeping anything matching the passed predicate. */
+ private static void filterJars(List<Path> jars, Path output, Predicate<String> shouldKeep)
+ throws IOException {
+ final int bufferSize = 8 * 1024;
+ byte[] buffer = new byte[bufferSize];
+
+ try (ZipOutputStream outputStream =
+ new ZipOutputStream(new FileOutputStream(output.toFile()))) {
+ for (Path jar : jars) {
+ try (ZipFile sourceZipFile = new ZipFile(jar.toFile())) {
+ Enumeration<? extends ZipEntry> entries = sourceZipFile.entries();
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
+ if (!shouldKeep.test(entry.getName())) {
+ continue;
+ }
+
+ ZipEntry newEntry = new ZipEntry(entry.getName());
+ outputStream.putNextEntry(newEntry);
+ try (InputStream inputStream = sourceZipFile.getInputStream(entry)) {
+ int len;
+ while ((len = inputStream.read(buffer)) != -1) {
+ outputStream.write(buffer, 0, len);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @VisibleForTesting
+ static boolean shouldKeepClass(List<String> archiveFileNamePrefixes, String name) {
+ if (!name.endsWith(".class")) {
+ return false;
+ }
+ for (String archiveFileNamePrefix : archiveFileNamePrefixes) {
+ if (name.startsWith(archiveFileNamePrefix)
+ && name.length() > archiveFileNamePrefix.length()) {
+ char c = name.charAt(archiveFileNamePrefix.length());
+ if (c == '.' || c == '$') {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private static boolean shouldKeepJavaFile(List<String> archiveFileNamePrefixes, String name) {
+ if (!name.endsWith(".java")) {
+ return false;
+ }
+ String nameWithoutJava = name.substring(0, name.length() - ".java".length());
+ return archiveFileNamePrefixes.contains(nameWithoutJava);
+ }
+}
diff --git a/aspect/tools/src/com/google/idea/blaze/aspect/OptionParser.java b/aspect/tools/src/com/google/idea/blaze/aspect/OptionParser.java
new file mode 100644
index 0000000..9e9bf4c
--- /dev/null
+++ b/aspect/tools/src/com/google/idea/blaze/aspect/OptionParser.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.aspect;
+
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+import javax.annotation.Nullable;
+
+/**
+ * A very light-weight command-line argument parser.
+ *
+ * <p>Expects args array of the form ["--arg_name1", "value1", "--arg_name2", "value2", ...]
+ */
+public final class OptionParser {
+ private OptionParser() {}
+
+ /**
+ * Searches for '--[name]' in the args list, and parses the following argument using the supplied
+ * parser. Returns null if not such argument found.
+ *
+ * @throws IllegalArgumentException if '--[name]' appears more than once, as this indicates a
+ * programming error on the client side.
+ */
+ @Nullable
+ static <T> T parseSingleOption(String[] args, String name, Function<String, T> parser) {
+ String argName = "--" + name;
+ for (int i = 0; i < args.length; i++) {
+ if (!args[i].equals(argName)) {
+ continue;
+ }
+ if (i == args.length - 1) {
+ throw new IllegalArgumentException("Expected value after " + args[i]);
+ }
+ for (int j = i + 2; j < args.length; j++) {
+ if (args[j].equals(argName)) {
+ throw new IllegalArgumentException("Expected " + args[i] + " to appear at most once");
+ }
+ }
+ return parser.apply(args[i + 1]);
+ }
+ return null;
+ }
+
+ /**
+ * Parse all '--[name]' flags in `args`, return a list of all their parsed values.
+ *
+ * <p>When args[i] = `--[name]`, then args[i+1] is parsed for the value, using `parser`.
+ *
+ * <p>Otherwise args[i] is ignored.
+ *
+ * <p>Returns an empty list if no occurrences of the flag are found.
+ */
+ static <T> List<T> parseMultiOption(String[] args, String name, Function<String, T> parser) {
+ List<T> result = null;
+ String argName = "--" + name;
+ for (int i = 0; i < args.length; i++) {
+ if (!args[i].equals(argName)) {
+ continue;
+ }
+ if (i == args.length - 1) {
+ throw new IllegalArgumentException("Expected value after " + args[i]);
+ }
+ T parsed = parser.apply(args[i + 1]);
+ if (parsed != null) {
+ if (result == null) {
+ result = new ArrayList<>();
+ }
+ result.add(parsed);
+ }
+ i++;
+ }
+ return result == null ? ImmutableList.of() : result;
+ }
+}
diff --git a/aspect/tools/src/com/google/idea/blaze/aspect/PackageParser.java b/aspect/tools/src/com/google/idea/blaze/aspect/PackageParser.java
new file mode 100644
index 0000000..a3a44c1
--- /dev/null
+++ b/aspect/tools/src/com/google/idea/blaze/aspect/PackageParser.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.aspect;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Maps;
+import com.google.common.io.Files;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.repackaged.devtools.intellij.ideinfo.IntellijIdeInfo.ArtifactLocation;
+import com.google.repackaged.devtools.intellij.ideinfo.IntellijIdeInfo.JavaSourcePackage;
+import com.google.repackaged.devtools.intellij.ideinfo.IntellijIdeInfo.PackageManifest;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.FileSystems;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.Executors;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.annotation.Nullable;
+
+/** Parses the package string from each of the source .java files. */
+public class PackageParser {
+
+ /** The options for a {@link PackageParser} action. */
+ @VisibleForTesting
+ static final class PackageParserOptions {
+ List<ArtifactLocation> sources;
+ Path outputManifest;
+ }
+
+ @VisibleForTesting
+ static PackageParserOptions parseArgs(String[] args) {
+ args = parseParamFileIfUsed(args);
+ PackageParserOptions options = new PackageParserOptions();
+ options.sources =
+ OptionParser.parseSingleOption(args, "sources", ArtifactLocationParser::parseList);
+ options.outputManifest =
+ OptionParser.parseSingleOption(
+ args, "output_manifest", string -> FileSystems.getDefault().getPath(string));
+ return options;
+ }
+
+ private static final Logger logger = Logger.getLogger(PackageParser.class.getName());
+
+ private static final Pattern PACKAGE_PATTERN = Pattern.compile("^\\s*package\\s+([\\w\\.]+)");
+
+ public static void main(String[] args) throws Exception {
+ PackageParserOptions options = parseArgs(args);
+ Preconditions.checkNotNull(options.outputManifest);
+
+ try {
+ PackageParser parser = new PackageParser(PackageParserIoProvider.INSTANCE);
+ Map<ArtifactLocation, String> outputMap = parser.parsePackageStrings(options.sources);
+ parser.writeManifest(outputMap, options.outputManifest);
+ } catch (Throwable e) {
+ logger.log(Level.SEVERE, "Error parsing package strings", e);
+ System.exit(1);
+ }
+ System.exit(0);
+ }
+
+ private static Path getExecutionPath(ArtifactLocation location) {
+ return Paths.get(location.getRootExecutionPathFragment(), location.getRelativePath());
+ }
+
+ private static String[] parseParamFileIfUsed(String[] args) {
+ if (args.length != 1 || !args[0].startsWith("@")) {
+ return args;
+ }
+ File paramFile = new File(args[0].substring(1));
+ try {
+ return Files.readLines(paramFile, StandardCharsets.UTF_8).toArray(new String[0]);
+ } catch (IOException e) {
+ throw new RuntimeException("Error parsing param file: " + args[0], e);
+ }
+ }
+
+ private final PackageParserIoProvider ioProvider;
+
+ @VisibleForTesting
+ PackageParser(PackageParserIoProvider ioProvider) {
+ this.ioProvider = ioProvider;
+ }
+
+ @VisibleForTesting
+ void writeManifest(Map<ArtifactLocation, String> sourceToPackageMap, Path outputFile)
+ throws IOException {
+ PackageManifest.Builder builder = PackageManifest.newBuilder();
+ for (Entry<ArtifactLocation, String> entry : sourceToPackageMap.entrySet()) {
+ JavaSourcePackage.Builder srcBuilder =
+ JavaSourcePackage.newBuilder()
+ .setPackageString(entry.getValue())
+ .setArtifactLocation(entry.getKey());
+ builder.addSources(srcBuilder.build());
+ }
+
+ try {
+ ioProvider.writeProto(builder.build(), outputFile);
+ } catch (IOException e) {
+ logger.log(Level.SEVERE, "Error writing package manifest", e);
+ throw e;
+ }
+ }
+
+ @VisibleForTesting
+ Map<ArtifactLocation, String> parsePackageStrings(List<ArtifactLocation> sources)
+ throws Exception {
+
+ ListeningExecutorService executorService =
+ MoreExecutors.listeningDecorator(
+ Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()));
+
+ Map<ArtifactLocation, ListenableFuture<String>> futures = Maps.newHashMap();
+ for (final ArtifactLocation source : sources) {
+ futures.put(source, executorService.submit(() -> getDeclaredPackageOfJavaFile(source)));
+ }
+ Map<ArtifactLocation, String> map = Maps.newHashMap();
+ for (Entry<ArtifactLocation, ListenableFuture<String>> entry : futures.entrySet()) {
+ String value = entry.getValue().get();
+ if (value != null) {
+ map.put(entry.getKey(), value);
+ }
+ }
+ return map;
+ }
+
+ @Nullable
+ private String getDeclaredPackageOfJavaFile(ArtifactLocation source) {
+ try (BufferedReader reader = ioProvider.getReader(getExecutionPath(source))) {
+ return parseDeclaredPackage(reader);
+
+ } catch (IOException e) {
+ logger.log(Level.WARNING, "Error parsing package string from java source: " + source, e);
+ return null;
+ }
+ }
+
+ @Nullable
+ private static String parseDeclaredPackage(BufferedReader reader) throws IOException {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ Matcher packageMatch = PACKAGE_PATTERN.matcher(line);
+ if (packageMatch.find()) {
+ return packageMatch.group(1);
+ }
+ }
+ return null;
+ }
+}
diff --git a/aspect/tools/src/com/google/idea/blaze/aspect/PackageParserIoProvider.java b/aspect/tools/src/com/google/idea/blaze/aspect/PackageParserIoProvider.java
new file mode 100644
index 0000000..3d965c2
--- /dev/null
+++ b/aspect/tools/src/com/google/idea/blaze/aspect/PackageParserIoProvider.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.aspect;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.repackaged.protobuf.MessageLite;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+/** Provides a BufferedReader for the source java files, and a writer for the output proto */
+@VisibleForTesting
+public class PackageParserIoProvider {
+
+ static final PackageParserIoProvider INSTANCE = new PackageParserIoProvider();
+
+ void writeProto(MessageLite message, Path file) throws IOException {
+ try (OutputStream out = Files.newOutputStream(file)) {
+ message.writeTo(out);
+ }
+ }
+
+ BufferedReader getReader(Path file) throws IOException {
+ return Files.newBufferedReader(file, StandardCharsets.UTF_8);
+ }
+}
diff --git a/aspect/tools/tests/unittests/com/google/idea/blaze/aspect/ArtifactLocationParserTest.java b/aspect/tools/tests/unittests/com/google/idea/blaze/aspect/ArtifactLocationParserTest.java
new file mode 100644
index 0000000..9985f48
--- /dev/null
+++ b/aspect/tools/tests/unittests/com/google/idea/blaze/aspect/ArtifactLocationParserTest.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.aspect;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import com.google.common.base.Joiner;
+import com.google.repackaged.devtools.intellij.ideinfo.IntellijIdeInfo.ArtifactLocation;
+import java.nio.file.Paths;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests {@link ArtifactLocationParser}. */
+@RunWith(JUnit4.class)
+public class ArtifactLocationParserTest {
+
+ @Test
+ public void testConverterSourceArtifact() {
+ ArtifactLocation parsed =
+ ArtifactLocationParser.parse(Joiner.on(',').join("", "test.java", "0"));
+ assertThat(parsed)
+ .isEqualTo(
+ ArtifactLocation.newBuilder()
+ .setRelativePath(Paths.get("test.java").toString())
+ .setIsSource(true)
+ .setIsExternal(false)
+ .build());
+ }
+
+ @Test
+ public void testConverterDerivedArtifact() {
+ ArtifactLocation parsed =
+ ArtifactLocationParser.parse(Joiner.on(',').join("bin", "java/com/test.java", "0"));
+ assertThat(parsed)
+ .isEqualTo(
+ ArtifactLocation.newBuilder()
+ .setRootExecutionPathFragment(Paths.get("bin").toString())
+ .setRelativePath(Paths.get("java/com/test.java").toString())
+ .setIsSource(false)
+ .setIsExternal(false)
+ .build());
+ }
+
+ @Test
+ public void testConverterExternal() {
+ ArtifactLocation externalArtifact =
+ ArtifactLocationParser.parse(Joiner.on(',').join("", "test.java", "1"));
+ assertThat(externalArtifact)
+ .isEqualTo(
+ ArtifactLocation.newBuilder()
+ .setRelativePath(Paths.get("test.java").toString())
+ .setIsSource(true)
+ .setIsExternal(true)
+ .build());
+ ArtifactLocation nonExternalArtifact =
+ ArtifactLocationParser.parse(Joiner.on(',').join("", "test.java", "0"));
+ assertThat(nonExternalArtifact)
+ .isEqualTo(
+ ArtifactLocation.newBuilder()
+ .setRelativePath(Paths.get("test.java").toString())
+ .setIsSource(true)
+ .setIsExternal(false)
+ .build());
+ }
+
+ @Test
+ public void testInvalidFormatFails() {
+ assertFails("/root", ArtifactLocationParser.INVALID_FORMAT);
+ assertFails("/root,exec,rel,extra", ArtifactLocationParser.INVALID_FORMAT);
+ }
+
+ @Test
+ public void testParsingArtifactLocationList() {
+ String input =
+ Joiner.on(':')
+ .join(
+ Joiner.on(',').join("", "test.java", "0"),
+ Joiner.on(',').join("bin", "java/com/test.java", "0"),
+ Joiner.on(',').join("", "test.java", "1"));
+
+ assertThat(ArtifactLocationParser.parseList(input))
+ .containsExactly(
+ ArtifactLocation.newBuilder()
+ .setRelativePath(Paths.get("test.java").toString())
+ .setIsSource(true)
+ .build(),
+ ArtifactLocation.newBuilder()
+ .setRootExecutionPathFragment(Paths.get("bin").toString())
+ .setRelativePath(Paths.get("java/com/test.java").toString())
+ .setIsSource(false)
+ .build(),
+ ArtifactLocation.newBuilder()
+ .setRelativePath(Paths.get("test.java").toString())
+ .setIsSource(true)
+ .setIsExternal(true)
+ .build());
+ }
+
+ private void assertFails(String input, String expectedError) {
+ try {
+ ArtifactLocationParser.parse(input);
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertThat(e).hasMessage(expectedError);
+ }
+ }
+}
diff --git a/aspect/tools/tests/unittests/com/google/idea/blaze/aspect/JarFilterTest.java b/aspect/tools/tests/unittests/com/google/idea/blaze/aspect/JarFilterTest.java
new file mode 100644
index 0000000..8153676
--- /dev/null
+++ b/aspect/tools/tests/unittests/com/google/idea/blaze/aspect/JarFilterTest.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.aspect;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.io.Files;
+import com.google.idea.blaze.aspect.JarFilter.JarFilterOptions;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link JarFilter} */
+@RunWith(JUnit4.class)
+public class JarFilterTest {
+
+ @Rule public TemporaryFolder folder = new TemporaryFolder();
+
+ @Test
+ public void testFilterMethod() throws Exception {
+ List<String> prefixes =
+ ImmutableList.of("com/google/foo/Foo", "com/google/bar/Bar", "com/google/baz/Baz");
+ assertThat(JarFilter.shouldKeepClass(prefixes, "com/google/foo/Foo.class")).isTrue();
+ assertThat(JarFilter.shouldKeepClass(prefixes, "com/google/foo/Foo$Inner.class")).isTrue();
+ assertThat(JarFilter.shouldKeepClass(prefixes, "com/google/bar/Bar.class")).isTrue();
+ assertThat(JarFilter.shouldKeepClass(prefixes, "com/google/foo/Foo/NotFoo.class")).isFalse();
+ assertThat(JarFilter.shouldKeepClass(prefixes, "wrong/com/google/foo/Foo.class")).isFalse();
+ }
+
+ @Test
+ public void fullIntegrationTest() throws Exception {
+ File fooJava = folder.newFile("Foo.java");
+ Files.write("package com.google.foo; class Foo { class Inner {} }".getBytes(UTF_8), fooJava);
+
+ File barJava = folder.newFile("Bar.java");
+ Files.write("package com.google.foo.bar; class Bar {}".getBytes(UTF_8), barJava);
+
+ File srcJar = folder.newFile("gen.srcjar");
+ try (ZipOutputStream zo = new ZipOutputStream(new FileOutputStream(srcJar))) {
+ zo.putNextEntry(new ZipEntry("com/google/foo/gen/Gen.java"));
+ zo.write("package gen; class Gen {}".getBytes(UTF_8));
+ zo.closeEntry();
+ zo.putNextEntry(new ZipEntry("com/google/foo/gen/Gen2.java"));
+ zo.write("package gen; class Gen2 {}".getBytes(UTF_8));
+ zo.closeEntry();
+ }
+
+ File src3Jar = folder.newFile("gen3.srcjar");
+ try (ZipOutputStream zo = new ZipOutputStream(new FileOutputStream(src3Jar))) {
+ zo.putNextEntry(new ZipEntry("com/google/foo/gen/Gen3.java"));
+ zo.write("package gen; class Gen3 {}".getBytes(UTF_8));
+ zo.closeEntry();
+ }
+
+ File filterJar = folder.newFile("foo.jar");
+ try (ZipOutputStream zo = new ZipOutputStream(new FileOutputStream(filterJar))) {
+ zo.putNextEntry(new ZipEntry("com/google/foo/Foo.class"));
+ zo.closeEntry();
+ zo.putNextEntry(new ZipEntry("com/google/foo/Foo$Inner.class"));
+ zo.closeEntry();
+ zo.putNextEntry(new ZipEntry("com/google/foo/bar/Bar.class"));
+ zo.closeEntry();
+ zo.putNextEntry(new ZipEntry("gen/Gen.class"));
+ zo.closeEntry();
+ zo.putNextEntry(new ZipEntry("gen/Gen2.class"));
+ zo.closeEntry();
+ zo.putNextEntry(new ZipEntry("gen/Gen3.class"));
+ zo.closeEntry();
+ zo.putNextEntry(new ZipEntry("com/google/foo/Foo2.class"));
+ zo.closeEntry();
+ }
+ File filterSrcJar = folder.newFile("foo-src.jar");
+ try (ZipOutputStream zo = new ZipOutputStream(new FileOutputStream(filterSrcJar))) {
+ zo.putNextEntry(new ZipEntry("com/google/foo/Foo.java"));
+ zo.closeEntry();
+ zo.putNextEntry(new ZipEntry("com/google/foo/bar/Bar.java"));
+ zo.closeEntry();
+ zo.putNextEntry(new ZipEntry("gen/Gen.java"));
+ zo.closeEntry();
+ zo.putNextEntry(new ZipEntry("gen/Gen2.java"));
+ zo.closeEntry();
+ zo.putNextEntry(new ZipEntry("gen/Gen3.java"));
+ zo.closeEntry();
+ zo.putNextEntry(new ZipEntry("com/google/foo/Foo2.java"));
+ zo.closeEntry();
+ zo.putNextEntry(new ZipEntry("com/google/foo/bar/Bar2.java"));
+ zo.closeEntry();
+ }
+
+ File filteredJar = folder.newFile("foo-filtered-gen.jar");
+ File filteredSourceJar = folder.newFile("foo-filtered-gen-src.jar");
+
+ String[] args =
+ new String[] {
+ "--keep_java_file",
+ fooJava.getPath(),
+ "--keep_java_file",
+ barJava.getPath(),
+ "--keep_source_jar",
+ srcJar.getPath(),
+ "--keep_source_jar",
+ src3Jar.getPath(),
+ "--filter_jar",
+ filterJar.getPath(),
+ "--filter_source_jar",
+ filterSrcJar.getPath(),
+ "--filtered_jar",
+ filteredJar.getPath(),
+ "--filtered_source_jar",
+ filteredSourceJar.getPath()
+ };
+ JarFilterOptions options = JarFilter.parseArgs(args);
+ JarFilter.main(options);
+
+ List<String> filteredJarNames = Lists.newArrayList();
+ try (ZipFile zipFile = new ZipFile(filteredJar)) {
+ Enumeration<? extends ZipEntry> entries = zipFile.entries();
+ while (entries.hasMoreElements()) {
+ ZipEntry zipEntry = entries.nextElement();
+ filteredJarNames.add(zipEntry.getName());
+ }
+ }
+
+ List<String> filteredSourceJarNames = Lists.newArrayList();
+ try (ZipFile zipFile = new ZipFile(filteredSourceJar)) {
+ Enumeration<? extends ZipEntry> entries = zipFile.entries();
+ while (entries.hasMoreElements()) {
+ ZipEntry zipEntry = entries.nextElement();
+ filteredSourceJarNames.add(zipEntry.getName());
+ }
+ }
+
+ assertThat(filteredJarNames)
+ .containsExactly(
+ "com/google/foo/Foo.class",
+ "com/google/foo/Foo$Inner.class",
+ "com/google/foo/bar/Bar.class",
+ "gen/Gen.class",
+ "gen/Gen2.class",
+ "gen/Gen3.class");
+
+ assertThat(filteredSourceJarNames)
+ .containsExactly(
+ "com/google/foo/Foo.java",
+ "com/google/foo/bar/Bar.java",
+ "gen/Gen.java",
+ "gen/Gen2.java",
+ "gen/Gen3.java");
+ }
+}
diff --git a/aspect/tools/tests/unittests/com/google/idea/blaze/aspect/OptionParserTest.java b/aspect/tools/tests/unittests/com/google/idea/blaze/aspect/OptionParserTest.java
new file mode 100644
index 0000000..c506e7a
--- /dev/null
+++ b/aspect/tools/tests/unittests/com/google/idea/blaze/aspect/OptionParserTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.aspect;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link OptionParser}. */
+@RunWith(JUnit4.class)
+public class OptionParserTest {
+ @Test
+ public void testParseSingleOption() throws Exception {
+ // Test parsing of a flag that doesn't occur.
+ assertThat(
+ OptionParser.parseSingleOption(
+ new String[] {"--foo", "1", "--bar", "2"}, "unknown", String::toString))
+ .isNull();
+ // Test parsing of an integer flag.
+ assertThat(
+ OptionParser.<Integer>parseSingleOption(
+ new String[] {"--foo", "1", "--bar", "2"}, "bar", Integer::parseInt))
+ .isEqualTo(2);
+ // Test parsing of a flag that's missing its value: if there's a subsequent entry and it can be
+ // parsed, it'll be taken as the flag's value.
+ assertThat(
+ OptionParser.parseSingleOption(
+ new String[] {"--foo", "--bar", "--baz"}, "foo", String::toString))
+ .isEqualTo("--bar");
+ // Test parsing of a flag that's missing its value: if there's no subsequent entry, an exception
+ // is thrown.
+ try {
+ OptionParser.parseSingleOption(new String[] {"--foo", "1", "--bar"}, "bar", String::toString);
+ fail("Expected failure");
+ } catch (IllegalArgumentException e) {
+ assertThat(e).hasMessage("Expected value after --bar");
+ }
+ // Test that a single-value flag should not appear multiple times.
+ try {
+ OptionParser.parseSingleOption(
+ new String[] {"--foo", "1", "--foo", "2"}, "foo", String::toString);
+ fail("Expected failure");
+ } catch (IllegalArgumentException e) {
+ assertThat(e).hasMessage("Expected --foo to appear at most once");
+ }
+ }
+
+ @Test
+ public void testParseMultiOption() throws Exception {
+ // Test parsing of a flag that doesn't occur.
+ assertThat(
+ OptionParser.parseMultiOption(
+ new String[] {"--foo", "1", "--bar", "2"}, "unknown", String::toString))
+ .isEmpty();
+ // Test parsing of an integer multi-flag. Ensure that the flag name is matched wholly, and
+ // doesn't match other flags that it's a prefix of.
+ assertThat(
+ OptionParser.parseMultiOption(
+ new String[] {"--foo", "1", "--foooo", "2", "--foo", "3"},
+ "foo",
+ Integer::parseInt))
+ .containsExactly(1, 3)
+ .inOrder();
+ // Test that the --name=value style of flags is not supported (for sake of simplicity).
+ assertThat(
+ OptionParser.parseMultiOption(
+ new String[] {"--foo", "1", "--foo=2", "--foo", "3"}, "foo", Integer::parseInt))
+ .containsExactly(1, 3)
+ .inOrder();
+ // Test parsing a multi-flag where one occurrence cannot consume a value.
+ try {
+ OptionParser.parseMultiOption(
+ new String[] {"--foo", "1", "--bar", "2", "--foo"}, "foo", String::toString);
+ fail("Expected failure");
+ } catch (IllegalArgumentException e) {
+ assertThat(e).hasMessage("Expected value after --foo");
+ }
+ }
+}
diff --git a/aspect/tools/tests/unittests/com/google/idea/blaze/aspect/PackageParserTest.java b/aspect/tools/tests/unittests/com/google/idea/blaze/aspect/PackageParserTest.java
new file mode 100644
index 0000000..61a6892
--- /dev/null
+++ b/aspect/tools/tests/unittests/com/google/idea/blaze/aspect/PackageParserTest.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.aspect;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.repackaged.devtools.intellij.ideinfo.IntellijIdeInfo.ArtifactLocation;
+import com.google.repackaged.protobuf.MessageLite;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.InvalidPathException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link PackageParser} */
+@RunWith(JUnit4.class)
+public class PackageParserTest {
+
+ private static class MockPackageParserIoProvider extends PackageParserIoProvider {
+ private final Map<Path, InputStream> sources = Maps.newHashMap();
+ private final List<ArtifactLocation> sourceLocations = Lists.newArrayList();
+ private StringWriter writer = new StringWriter();
+
+ public MockPackageParserIoProvider addSource(ArtifactLocation source, String javaSrc) {
+ try {
+ Path path = Paths.get(source.getRootExecutionPathFragment(), source.getRelativePath());
+ sources.put(path, new ByteArrayInputStream(javaSrc.getBytes("UTF-8")));
+ sourceLocations.add(source);
+
+ } catch (UnsupportedEncodingException | InvalidPathException e) {
+ fail(e.getMessage());
+ }
+ return this;
+ }
+
+ public List<ArtifactLocation> getSourceLocations() {
+ return Lists.newArrayList(sourceLocations);
+ }
+
+ @Override
+ public BufferedReader getReader(Path file) throws IOException {
+ InputStream input = sources.get(file);
+ return new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
+ }
+
+ @Override
+ public void writeProto(MessageLite message, Path file) throws IOException {
+ writer.write(message.toString());
+ }
+ }
+
+ private static final ArtifactLocation DUMMY_SOURCE_ARTIFACT =
+ ArtifactLocation.newBuilder()
+ .setRelativePath("java/com/google/Foo.java")
+ .setIsSource(true)
+ .build();
+
+ private static final ArtifactLocation DUMMY_DERIVED_ARTIFACT =
+ ArtifactLocation.newBuilder()
+ .setRootExecutionPathFragment("bin")
+ .setRelativePath("java/com/google/Bla.java")
+ .setIsSource(false)
+ .build();
+
+ private static final ArtifactLocation DUMMY_SCALA_SOURCE_ARTIFACT =
+ ArtifactLocation.newBuilder()
+ .setRelativePath("scala/com/google/Foo.scala")
+ .setIsSource(true)
+ .build();
+
+ private static final ArtifactLocation DUMMY_SCALA_DERIVED_ARTIFACT =
+ ArtifactLocation.newBuilder()
+ .setRootExecutionPathFragment("bin")
+ .setRelativePath("scala/com/google/Bla.scala")
+ .setIsSource(false)
+ .build();
+
+ private MockPackageParserIoProvider mockIoProvider;
+ private PackageParser parser;
+
+ @Before
+ public void setUp() {
+ mockIoProvider = new MockPackageParserIoProvider();
+ parser = new PackageParser(mockIoProvider);
+ }
+
+ private Map<ArtifactLocation, String> parsePackageStrings() throws Exception {
+ List<ArtifactLocation> sources = mockIoProvider.getSourceLocations();
+ return parser.parsePackageStrings(sources);
+ }
+
+ @Test
+ public void testParseCommandLineArguments() throws Exception {
+ String[] args =
+ new String[] {
+ "--output_manifest",
+ "/tmp/out.manifest",
+ "--sources",
+ Joiner.on(':').join(",java/com/google/Foo.java,0", "bin/out,java/com/google/Bla.java,0")
+ };
+ PackageParser.PackageParserOptions options = PackageParser.parseArgs(args);
+ assertThat(options.outputManifest.toString())
+ .isEqualTo(Paths.get("/tmp/out.manifest").toString());
+ assertThat(options.sources).hasSize(2);
+ assertThat(options.sources.get(0))
+ .isEqualTo(
+ ArtifactLocation.newBuilder()
+ .setRelativePath(Paths.get("java/com/google/Foo.java").toString())
+ .setIsSource(true)
+ .build());
+ assertThat(options.sources.get(1))
+ .isEqualTo(
+ ArtifactLocation.newBuilder()
+ .setRootExecutionPathFragment(Paths.get("bin/out").toString())
+ .setRelativePath(Paths.get("java/com/google/Bla.java").toString())
+ .setIsSource(false)
+ .build());
+ }
+
+ @Test
+ public void testReadNoSources() throws Exception {
+ Map<ArtifactLocation, String> map = parsePackageStrings();
+ assertThat(map).isEmpty();
+ }
+
+ @Test
+ public void testSingleRead() throws Exception {
+ mockIoProvider.addSource(DUMMY_SOURCE_ARTIFACT, "package com.google;\n public class Bla {}\"");
+ Map<ArtifactLocation, String> map = parsePackageStrings();
+ assertThat(map).hasSize(1);
+ assertThat(map).containsEntry(DUMMY_SOURCE_ARTIFACT, "com.google");
+ }
+
+ @Test
+ public void testMultiRead() throws Exception {
+ mockIoProvider
+ .addSource(DUMMY_SOURCE_ARTIFACT, "package com.test;\n public class Foo {}\"")
+ .addSource(DUMMY_DERIVED_ARTIFACT, "package com.other;\n public class Bla {}\"");
+ Map<ArtifactLocation, String> map = parsePackageStrings();
+ assertThat(map).hasSize(2);
+ assertThat(map).containsEntry(DUMMY_SOURCE_ARTIFACT, "com.test");
+ assertThat(map).containsEntry(DUMMY_DERIVED_ARTIFACT, "com.other");
+ }
+
+ @Test
+ public void testReadSomeInvalid() throws Exception {
+ mockIoProvider
+ .addSource(DUMMY_SOURCE_ARTIFACT, "package %com.test;\n public class Foo {}\"")
+ .addSource(DUMMY_DERIVED_ARTIFACT, "package com.other;\n public class Bla {}\"");
+ Map<ArtifactLocation, String> map = parsePackageStrings();
+ assertThat(map).hasSize(1);
+ assertThat(map).containsEntry(DUMMY_DERIVED_ARTIFACT, "com.other");
+ }
+
+ @Test
+ public void testReadAllInvalid() throws Exception {
+ mockIoProvider
+ .addSource(DUMMY_SOURCE_ARTIFACT, "#package com.test;\n public class Foo {}\"")
+ .addSource(DUMMY_DERIVED_ARTIFACT, "package %com.other\n public class Bla {}\"");
+ Map<ArtifactLocation, String> map = parsePackageStrings();
+ assertThat(map).isEmpty();
+ }
+
+ @Test
+ public void testReadScala() throws Exception {
+ mockIoProvider
+ .addSource(DUMMY_SCALA_SOURCE_ARTIFACT, "package com.test\n class Foo {}\"")
+ .addSource(DUMMY_SCALA_DERIVED_ARTIFACT, "package com.other {}\n object Bla {}\"");
+ Map<ArtifactLocation, String> map = parsePackageStrings();
+ assertThat(map).containsEntry(DUMMY_SCALA_SOURCE_ARTIFACT, "com.test");
+ assertThat(map).containsEntry(DUMMY_SCALA_DERIVED_ARTIFACT, "com.other");
+ }
+
+ @Test
+ public void testWriteEmptyMap() throws Exception {
+ parser.writeManifest(Maps.newHashMap(), Paths.get("/java/com/google/test.manifest"));
+ assertThat(mockIoProvider.writer.toString()).isEmpty();
+ }
+
+ @Test
+ public void testWriteMap() throws Exception {
+ Map<ArtifactLocation, String> map =
+ ImmutableMap.of(DUMMY_SOURCE_ARTIFACT, "com.google", DUMMY_DERIVED_ARTIFACT, "com.other");
+ parser.writeManifest(map, Paths.get("/java/com/google/test.manifest"));
+
+ String writtenString = mockIoProvider.writer.toString();
+ assertThat(writtenString)
+ .contains(String.format("relative_path: \"%s\"", DUMMY_SOURCE_ARTIFACT.getRelativePath()));
+ assertThat(writtenString).contains("package_string: \"com.google\"");
+
+ assertThat(writtenString)
+ .contains(
+ String.format(
+ "root_execution_path_fragment: \"%s\"",
+ DUMMY_DERIVED_ARTIFACT.getRootExecutionPathFragment()));
+ assertThat(writtenString)
+ .contains(String.format("relative_path: \"%s\"", DUMMY_DERIVED_ARTIFACT.getRelativePath()));
+ assertThat(writtenString).contains("package_string: \"com.other\"");
+ }
+
+ @Test
+ public void testHandlesOldFormat() throws Exception {
+ String[] args =
+ new String[] {
+ "--output_manifest",
+ "/tmp/out.manifest",
+ "--sources",
+ Joiner.on(':')
+ .join(
+ ",java/com/google/Foo.java,/usr/local/google/code",
+ "bin,java/com/google/Bla.java,/usr/local/_tmp/code/bin")
+ };
+ PackageParser.PackageParserOptions options = PackageParser.parseArgs(args);
+ assertThat(options.outputManifest.toString())
+ .isEqualTo(Paths.get("/tmp/out.manifest").toString());
+ assertThat(options.sources).hasSize(2);
+ assertThat(options.sources.get(0))
+ .isEqualTo(
+ ArtifactLocation.newBuilder()
+ .setRelativePath(Paths.get("java/com/google/Foo.java").toString())
+ .setIsSource(true)
+ .build());
+ assertThat(options.sources.get(1))
+ .isEqualTo(
+ ArtifactLocation.newBuilder()
+ .setRootExecutionPathFragment(Paths.get("bin").toString())
+ .setRelativePath(Paths.get("java/com/google/Bla.java").toString())
+ .setIsSource(false)
+ .build());
+ }
+}
diff --git a/aswb/2.3/src/com/android/tools/idea/npw/project/AndroidProjectPaths.java b/aswb/2.3/src/com/android/tools/idea/npw/project/AndroidProjectPaths.java
new file mode 100644
index 0000000..8124a41
--- /dev/null
+++ b/aswb/2.3/src/com/android/tools/idea/npw/project/AndroidProjectPaths.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.idea.npw.project;
+
+import java.io.File;
+import javax.annotation.Nullable;
+
+/** Fake {@link AndroidProjectPaths} for 2.3. */
+public interface AndroidProjectPaths {
+ File getModuleRoot();
+
+ File getSrcDirectory(@Nullable String packageName);
+
+ File getTestDirectory(@Nullable String packageName);
+
+ File getResDirectory();
+
+ File getAidlDirectory(@Nullable String packageName);
+
+ File getManifestDirectory();
+}
diff --git a/aswb/2.3/src/com/android/tools/idea/res/IdeResourceNameValidator.java b/aswb/2.3/src/com/android/tools/idea/res/IdeResourceNameValidator.java
new file mode 100644
index 0000000..117da24
--- /dev/null
+++ b/aswb/2.3/src/com/android/tools/idea/res/IdeResourceNameValidator.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.idea.res;
+
+import com.android.resources.ResourceFolderType;
+
+/** Fake {@link IdeResourceNameValidator} for 2.3, renamed from {@link ResourceNameValidator}. */
+public class IdeResourceNameValidator {
+ public final ResourceNameValidator delegate;
+
+ public IdeResourceNameValidator(ResourceNameValidator delegate) {
+ this.delegate = delegate;
+ }
+
+ public static IdeResourceNameValidator forFilename(
+ ResourceFolderType type, String implicitExtension) {
+ return new IdeResourceNameValidator(ResourceNameValidator.create(false, type));
+ }
+
+ public String getErrorText(String inputString) {
+ return delegate.getErrorText(inputString);
+ }
+}
diff --git a/aswb/2.3/src/com/google/idea/sdkcompat/android/model/AndroidModelAdapter.java b/aswb/2.3/src/com/google/idea/sdkcompat/android/model/AndroidModelAdapter.java
new file mode 100644
index 0000000..b1fa492
--- /dev/null
+++ b/aswb/2.3/src/com/google/idea/sdkcompat/android/model/AndroidModelAdapter.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.sdkcompat.android.model;
+
+import com.android.tools.idea.model.AndroidModel;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import javax.annotation.Nullable;
+
+/** Compatibility adapter for {@link AndroidModel}. */
+public abstract class AndroidModelAdapter implements AndroidModel {
+ @Nullable
+ @Override
+ public Long getLastBuildTimestamp(Project project) {
+ return null;
+ }
+
+ public abstract boolean isClassFileOutOfDate(Module module, String fqcn, VirtualFile classFile);
+}
diff --git a/aswb/2.3/src/com/google/idea/sdkcompat/android/project/AndroidProjectInfoAdapter.java b/aswb/2.3/src/com/google/idea/sdkcompat/android/project/AndroidProjectInfoAdapter.java
new file mode 100644
index 0000000..c91afbe
--- /dev/null
+++ b/aswb/2.3/src/com/google/idea/sdkcompat/android/project/AndroidProjectInfoAdapter.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.sdkcompat.android.project;
+
+import com.android.tools.idea.gradle.util.Projects;
+import com.android.tools.idea.project.AndroidProjectInfo;
+import com.intellij.openapi.project.Project;
+
+/** Compatibility adapter for {@link AndroidProjectInfo}. */
+public class AndroidProjectInfoAdapter {
+ public static boolean requiredAndroidModelMissing(Project project) {
+ return Projects.requiredAndroidModelMissing(project);
+ }
+}
diff --git a/aswb/2.3/src/com/google/idea/sdkcompat/android/project/BuildSystemServiceAdapter.java b/aswb/2.3/src/com/google/idea/sdkcompat/android/project/BuildSystemServiceAdapter.java
new file mode 100644
index 0000000..8af0cec
--- /dev/null
+++ b/aswb/2.3/src/com/google/idea/sdkcompat/android/project/BuildSystemServiceAdapter.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.sdkcompat.android.project;
+
+import com.android.tools.idea.npw.project.AndroidSourceSet;
+import com.android.tools.idea.project.BuildSystemService;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import java.util.List;
+import javax.annotation.Nullable;
+import org.jetbrains.android.facet.AndroidFacet;
+
+/** Compatibility adapter for {@link BuildSystemService}. */
+public abstract class BuildSystemServiceAdapter extends BuildSystemService {
+ public abstract String mergeBuildFiles(
+ String dependencies,
+ String destinationContents,
+ Project project,
+ @Nullable String supportLibVersionFilter);
+
+ public abstract List<AndroidSourceSet> getSourceSets(
+ AndroidFacet facet, @Nullable VirtualFile targetDirectory);
+}
diff --git a/aswb/2.3/src/com/google/idea/sdkcompat/android/res/AppResourceRepositoryAdapter.java b/aswb/2.3/src/com/google/idea/sdkcompat/android/res/AppResourceRepositoryAdapter.java
new file mode 100644
index 0000000..b58d477
--- /dev/null
+++ b/aswb/2.3/src/com/google/idea/sdkcompat/android/res/AppResourceRepositoryAdapter.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.sdkcompat.android.res;
+
+import com.android.tools.idea.res.AppResourceRepository;
+import com.intellij.openapi.module.Module;
+
+/** Compatibility adapter for {@link AppResourceRepository}. */
+public class AppResourceRepositoryAdapter {
+ public static AppResourceRepository getOrCreateInstance(Module module) {
+ return AppResourceRepository.getAppResources(module, true);
+ }
+
+ public static AppResourceRepository findExistingInstance(Module module) {
+ return AppResourceRepository.getAppResources(module, false);
+ }
+}
diff --git a/aswb/2.3/src/com/google/idea/sdkcompat/android/resources/actions/CreateXmlResourceDialogAdapter.java b/aswb/2.3/src/com/google/idea/sdkcompat/android/resources/actions/CreateXmlResourceDialogAdapter.java
new file mode 100644
index 0000000..e7361b0
--- /dev/null
+++ b/aswb/2.3/src/com/google/idea/sdkcompat/android/resources/actions/CreateXmlResourceDialogAdapter.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.sdkcompat.android.resources.actions;
+
+import com.android.resources.ResourceType;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.ValidationInfo;
+import com.intellij.openapi.vfs.VirtualFile;
+import java.util.List;
+import javax.annotation.Nullable;
+import org.jetbrains.android.actions.CreateXmlResourceDialog;
+
+/** Compatibility adapter for {@link CreateXmlResourceDialog}. */
+public class CreateXmlResourceDialogAdapter {
+ public static ValidationInfo checkIfResourceAlreadyExists(
+ Project project,
+ VirtualFile resourceDir,
+ String resourceName,
+ @Nullable String resourceValue,
+ ResourceType resourceType,
+ List<String> dirNames,
+ String fileName) {
+ return CreateXmlResourceDialog.checkIfResourceAlreadyExists(
+ project, resourceDir, resourceName, resourceType, dirNames, fileName);
+ }
+}
diff --git a/aswb/2.3/src/com/google/idea/sdkcompat/android/resources/actions/CreateXmlResourcePanelAdapter.java b/aswb/2.3/src/com/google/idea/sdkcompat/android/resources/actions/CreateXmlResourcePanelAdapter.java
new file mode 100644
index 0000000..620cf18
--- /dev/null
+++ b/aswb/2.3/src/com/google/idea/sdkcompat/android/resources/actions/CreateXmlResourcePanelAdapter.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.sdkcompat.android.resources.actions;
+
+import com.android.tools.idea.res.IdeResourceNameValidator;
+import com.android.tools.idea.res.ResourceNameValidator;
+import org.jetbrains.android.actions.CreateXmlResourcePanel;
+
+/** Compatibility adapter for {@link CreateXmlResourcePanel}. */
+public abstract class CreateXmlResourcePanelAdapter implements CreateXmlResourcePanel {
+ protected abstract IdeResourceNameValidator getResourceNameValidatorCompat();
+
+ @Override
+ public ResourceNameValidator getResourceNameValidator() {
+ return getResourceNameValidatorCompat().delegate;
+ }
+}
diff --git a/aswb/2.3/src/com/google/idea/sdkcompat/android/resources/actions/NewResourceCreationHandlerAdapter.java b/aswb/2.3/src/com/google/idea/sdkcompat/android/resources/actions/NewResourceCreationHandlerAdapter.java
new file mode 100644
index 0000000..c7d8f3d
--- /dev/null
+++ b/aswb/2.3/src/com/google/idea/sdkcompat/android/resources/actions/NewResourceCreationHandlerAdapter.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.sdkcompat.android.resources.actions;
+
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
+import com.android.tools.idea.res.IdeResourceNameValidator;
+import com.android.tools.idea.res.ResourceNameValidator;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.vfs.VirtualFile;
+import java.util.function.Function;
+import javax.annotation.Nullable;
+import org.jetbrains.android.actions.CreateXmlResourcePanel;
+import org.jetbrains.android.actions.NewResourceCreationHandler;
+
+/** Compatibility adapter for {@link NewResourceCreationHandler}. */
+public abstract class NewResourceCreationHandlerAdapter implements NewResourceCreationHandler {
+ protected abstract CreateXmlResourcePanel createNewResourceValuePanelCompat(
+ Module module,
+ ResourceType resourceType,
+ ResourceFolderType folderType,
+ @Nullable String resourceName,
+ @Nullable String resourceValue,
+ boolean chooseName,
+ boolean chooseValue,
+ boolean chooseFilename,
+ @Nullable VirtualFile defaultFile,
+ @Nullable VirtualFile contextFile,
+ Function<Module, IdeResourceNameValidator> nameValidatorFactory);
+
+ @Override
+ public CreateXmlResourcePanel createNewResourceValuePanel(
+ Module module,
+ ResourceType resourceType,
+ ResourceFolderType folderType,
+ @Nullable String resourceName,
+ @Nullable String resourceValue,
+ boolean chooseName,
+ boolean chooseValue,
+ boolean chooseFilename,
+ @Nullable VirtualFile defaultFile,
+ @Nullable VirtualFile contextFile,
+ Function<Module, ResourceNameValidator> nameValidatorFactory) {
+ return createNewResourceValuePanelCompat(
+ module,
+ resourceType,
+ folderType,
+ resourceName,
+ resourceValue,
+ chooseName,
+ chooseValue,
+ chooseFilename,
+ defaultFile,
+ contextFile,
+ m -> new IdeResourceNameValidator(nameValidatorFactory.apply(m)));
+ }
+}
diff --git a/aswb/2.3/src/com/google/idea/sdkcompat/android/sync/BlazeNdkDependencySyncPluginCompat.java b/aswb/2.3/src/com/google/idea/sdkcompat/android/sync/BlazeNdkDependencySyncPluginCompat.java
new file mode 100644
index 0000000..471b696
--- /dev/null
+++ b/aswb/2.3/src/com/google/idea/sdkcompat/android/sync/BlazeNdkDependencySyncPluginCompat.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.sdkcompat.android.sync;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.idea.blaze.android.sync.BlazeNdkDependencySyncPlugin;
+
+/** Compatibility indirection for {@link BlazeNdkDependencySyncPlugin}. */
+public class BlazeNdkDependencySyncPluginCompat {
+ public static final ImmutableMap<String, String> REQUIRED_PLUGINS =
+ ImmutableMap.of(
+ "Android NDK Support",
+ "com.android.tools.ndk",
+ "NDK WorkspaceManager Support",
+ "com.android.tools.ndk.workspace");
+}
diff --git a/aswb/3.0/src/META-INF/ndk-workspace-contents.xml b/aswb/3.0/src/META-INF/ndk-workspace-contents.xml
new file mode 100644
index 0000000..da35648
--- /dev/null
+++ b/aswb/3.0/src/META-INF/ndk-workspace-contents.xml
@@ -0,0 +1,20 @@
+<!--
+ ~ Copyright 2017 The Bazel Authors. All rights reserved.
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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>
+ <extensions defaultExtensionNs="com.android.tools.ndk">
+ <workspaceProvider implementation="com.google.idea.blaze.android.ndk.workspace.BlazeNdkWorkspaceProvider"/>
+ </extensions>
+</idea-plugin>
\ No newline at end of file
diff --git a/aswb/3.0/src/com/google/idea/blaze/android/ndk/workspace/BlazeNdkWorkspaceProvider.java b/aswb/3.0/src/com/google/idea/blaze/android/ndk/workspace/BlazeNdkWorkspaceProvider.java
new file mode 100644
index 0000000..2a3353e
--- /dev/null
+++ b/aswb/3.0/src/com/google/idea/blaze/android/ndk/workspace/BlazeNdkWorkspaceProvider.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.ndk.workspace;
+
+import com.android.tools.ndk.workspace.NdkWorkspaceProvider;
+import com.google.idea.blaze.base.settings.Blaze;
+import com.google.idea.blaze.cpp.BlazeCWorkspace;
+import com.intellij.openapi.project.Project;
+import com.jetbrains.cidr.lang.workspace.OCWorkspace;
+import javax.annotation.Nullable;
+
+/** Extension to provide an NDK workspace. */
+public class BlazeNdkWorkspaceProvider extends NdkWorkspaceProvider {
+ @Nullable
+ @Override
+ public OCWorkspace findNdkWorkspace(Project project) {
+ if (Blaze.isBlazeProject(project)) {
+ return BlazeCWorkspace.getInstance(project);
+ }
+ return null;
+ }
+}
diff --git a/aswb/3.0/src/com/google/idea/sdkcompat/android/model/AndroidModelAdapter.java b/aswb/3.0/src/com/google/idea/sdkcompat/android/model/AndroidModelAdapter.java
new file mode 100644
index 0000000..c992e5a
--- /dev/null
+++ b/aswb/3.0/src/com/google/idea/sdkcompat/android/model/AndroidModelAdapter.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.sdkcompat.android.model;
+
+import com.android.tools.idea.model.AndroidModel;
+
+/** Compatibility adapter for {@link AndroidModel}. */
+public abstract class AndroidModelAdapter implements AndroidModel {}
diff --git a/aswb/3.0/src/com/google/idea/sdkcompat/android/project/AndroidProjectInfoAdapter.java b/aswb/3.0/src/com/google/idea/sdkcompat/android/project/AndroidProjectInfoAdapter.java
new file mode 100644
index 0000000..8ef7963
--- /dev/null
+++ b/aswb/3.0/src/com/google/idea/sdkcompat/android/project/AndroidProjectInfoAdapter.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.sdkcompat.android.project;
+
+import com.android.tools.idea.project.AndroidProjectInfo;
+import com.intellij.openapi.project.Project;
+
+/** Compatibility adapter for {@link AndroidProjectInfo}. */
+public class AndroidProjectInfoAdapter {
+ public static boolean requiredAndroidModelMissing(Project project) {
+ return AndroidProjectInfo.getInstance(project).requiredAndroidModelMissing();
+ }
+}
diff --git a/aswb/3.0/src/com/google/idea/sdkcompat/android/project/BuildSystemServiceAdapter.java b/aswb/3.0/src/com/google/idea/sdkcompat/android/project/BuildSystemServiceAdapter.java
new file mode 100644
index 0000000..e6201dd
--- /dev/null
+++ b/aswb/3.0/src/com/google/idea/sdkcompat/android/project/BuildSystemServiceAdapter.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.sdkcompat.android.project;
+
+import com.android.tools.idea.project.BuildSystemService;
+
+/** Compatibility adapter for {@link BuildSystemService}. */
+public abstract class BuildSystemServiceAdapter extends BuildSystemService {}
diff --git a/aswb/3.0/src/com/google/idea/sdkcompat/android/res/AppResourceRepositoryAdapter.java b/aswb/3.0/src/com/google/idea/sdkcompat/android/res/AppResourceRepositoryAdapter.java
new file mode 100644
index 0000000..890d132
--- /dev/null
+++ b/aswb/3.0/src/com/google/idea/sdkcompat/android/res/AppResourceRepositoryAdapter.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.sdkcompat.android.res;
+
+import com.android.tools.idea.res.AppResourceRepository;
+import com.intellij.openapi.module.Module;
+
+/** Compatibility adapter for {@link AppResourceRepository}. */
+public class AppResourceRepositoryAdapter {
+ public static AppResourceRepository getOrCreateInstance(Module module) {
+ return AppResourceRepository.getOrCreateInstance(module);
+ }
+
+ public static AppResourceRepository findExistingInstance(Module module) {
+ return AppResourceRepository.findExistingInstance(module);
+ }
+}
diff --git a/aswb/3.0/src/com/google/idea/sdkcompat/android/resources/actions/CreateXmlResourceDialogAdapter.java b/aswb/3.0/src/com/google/idea/sdkcompat/android/resources/actions/CreateXmlResourceDialogAdapter.java
new file mode 100644
index 0000000..9a424e1
--- /dev/null
+++ b/aswb/3.0/src/com/google/idea/sdkcompat/android/resources/actions/CreateXmlResourceDialogAdapter.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.sdkcompat.android.resources.actions;
+
+import com.android.resources.ResourceType;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.ValidationInfo;
+import com.intellij.openapi.vfs.VirtualFile;
+import java.util.List;
+import javax.annotation.Nullable;
+import org.jetbrains.android.actions.CreateXmlResourceDialog;
+
+/** Compatibility adapter for {@link CreateXmlResourceDialog}. */
+public class CreateXmlResourceDialogAdapter {
+ public static ValidationInfo checkIfResourceAlreadyExists(
+ Project project,
+ VirtualFile resourceDir,
+ String resourceName,
+ @Nullable String resourceValue,
+ ResourceType resourceType,
+ List<String> dirNames,
+ String fileName) {
+ return CreateXmlResourceDialog.checkIfResourceAlreadyExists(
+ project, resourceDir, resourceName, resourceValue, resourceType, dirNames, fileName);
+ }
+}
diff --git a/aswb/3.0/src/com/google/idea/sdkcompat/android/resources/actions/CreateXmlResourcePanelAdapter.java b/aswb/3.0/src/com/google/idea/sdkcompat/android/resources/actions/CreateXmlResourcePanelAdapter.java
new file mode 100644
index 0000000..15cf7b5
--- /dev/null
+++ b/aswb/3.0/src/com/google/idea/sdkcompat/android/resources/actions/CreateXmlResourcePanelAdapter.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.sdkcompat.android.resources.actions;
+
+import com.android.tools.idea.res.IdeResourceNameValidator;
+import org.jetbrains.android.actions.CreateXmlResourcePanel;
+
+/** Compatibility adapter for {@link CreateXmlResourcePanel}. */
+public abstract class CreateXmlResourcePanelAdapter implements CreateXmlResourcePanel {
+
+ protected abstract IdeResourceNameValidator getResourceNameValidatorCompat();
+
+ @Override
+ public IdeResourceNameValidator getResourceNameValidator() {
+ return getResourceNameValidatorCompat();
+ }
+}
diff --git a/aswb/3.0/src/com/google/idea/sdkcompat/android/resources/actions/NewResourceCreationHandlerAdapter.java b/aswb/3.0/src/com/google/idea/sdkcompat/android/resources/actions/NewResourceCreationHandlerAdapter.java
new file mode 100644
index 0000000..5c3f51f
--- /dev/null
+++ b/aswb/3.0/src/com/google/idea/sdkcompat/android/resources/actions/NewResourceCreationHandlerAdapter.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.sdkcompat.android.resources.actions;
+
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
+import com.android.tools.idea.res.IdeResourceNameValidator;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.vfs.VirtualFile;
+import java.util.function.Function;
+import javax.annotation.Nullable;
+import org.jetbrains.android.actions.CreateXmlResourcePanel;
+import org.jetbrains.android.actions.NewResourceCreationHandler;
+
+/** Compatibility adapter for {@link NewResourceCreationHandler}. */
+public abstract class NewResourceCreationHandlerAdapter implements NewResourceCreationHandler {
+ public abstract CreateXmlResourcePanel createNewResourceValuePanelCompat(
+ Module module,
+ ResourceType resourceType,
+ ResourceFolderType folderType,
+ @Nullable String resourceName,
+ @Nullable String resourceValue,
+ boolean chooseName,
+ boolean chooseValue,
+ boolean chooseFilename,
+ @Nullable VirtualFile defaultFile,
+ @Nullable VirtualFile contextFile,
+ Function<Module, IdeResourceNameValidator> nameValidatorFactory);
+
+ @Override
+ public CreateXmlResourcePanel createNewResourceValuePanel(
+ Module module,
+ ResourceType resourceType,
+ ResourceFolderType folderType,
+ @Nullable String resourceName,
+ @Nullable String resourceValue,
+ boolean chooseName,
+ boolean chooseValue,
+ boolean chooseFilename,
+ @Nullable VirtualFile defaultFile,
+ @Nullable VirtualFile contextFile,
+ Function<Module, IdeResourceNameValidator> nameValidatorFactory) {
+ return createNewResourceValuePanelCompat(
+ module,
+ resourceType,
+ folderType,
+ resourceName,
+ resourceValue,
+ chooseName,
+ chooseValue,
+ chooseFilename,
+ defaultFile,
+ contextFile,
+ nameValidatorFactory);
+ }
+}
diff --git a/aswb/3.0/src/com/google/idea/sdkcompat/android/sync/BlazeNdkDependencySyncPluginCompat.java b/aswb/3.0/src/com/google/idea/sdkcompat/android/sync/BlazeNdkDependencySyncPluginCompat.java
new file mode 100644
index 0000000..3faaaf1
--- /dev/null
+++ b/aswb/3.0/src/com/google/idea/sdkcompat/android/sync/BlazeNdkDependencySyncPluginCompat.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.sdkcompat.android.sync;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.idea.blaze.android.sync.BlazeNdkDependencySyncPlugin;
+
+/** Compatibility indirection for {@link BlazeNdkDependencySyncPlugin}. */
+public class BlazeNdkDependencySyncPluginCompat {
+ public static final ImmutableMap<String, String> REQUIRED_PLUGINS =
+ ImmutableMap.of("Android NDK Support", "com.android.tools.ndk");
+}
diff --git a/aswb/3.0/tests/unittests/com/google/idea/blaze/android/npw/project/BlazeAndroidProjectPathsTest.java b/aswb/3.0/tests/unittests/com/google/idea/blaze/android/npw/project/BlazeAndroidProjectPathsTest.java
new file mode 100644
index 0000000..ef64612
--- /dev/null
+++ b/aswb/3.0/tests/unittests/com/google/idea/blaze/android/npw/project/BlazeAndroidProjectPathsTest.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.npw.project;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.android.builder.model.SourceProvider;
+import com.android.tools.idea.npw.project.AndroidProjectPaths;
+import com.android.tools.idea.npw.project.AndroidSourceSet;
+import com.android.tools.idea.project.BuildSystemService;
+import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.android.project.BlazeBuildSystemService;
+import com.google.idea.blaze.android.sync.model.idea.SourceProviderImpl;
+import com.google.idea.blaze.base.BlazeTestCase;
+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.facet.FacetType;
+import com.intellij.facet.FacetTypeRegistry;
+import com.intellij.facet.impl.FacetTypeRegistryImpl;
+import com.intellij.mock.MockModule;
+import com.intellij.mock.MockVirtualFile;
+import com.intellij.openapi.extensions.ExtensionPointName;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ModuleRootManager;
+import com.intellij.openapi.vfs.VfsUtilCore;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.JavaDirectoryService;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiManager;
+import com.intellij.psi.PsiPackage;
+import com.intellij.psi.impl.file.PsiPackageImpl;
+import java.io.File;
+import java.util.List;
+import org.jetbrains.android.facet.AndroidFacet;
+import org.jetbrains.android.facet.AndroidFacetConfiguration;
+import org.jetbrains.android.facet.AndroidFacetType;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Test cases for {@link BlazeAndroidProjectPaths}. */
+@RunWith(JUnit4.class)
+public class BlazeAndroidProjectPathsTest extends BlazeTestCase {
+ private VirtualFile root = new MockVirtualFile(true, "root");
+ private VirtualFile resource = new MockVirtualFile(true, "root/resource");
+ private VirtualFile target = new MockVirtualFile(true, "root/library/com/google/target");
+
+ @Override
+ protected void initTest(Container applicationServices, Container projectServices) {
+ mockFacetRegistry(applicationServices);
+ mockBlazeImportSettings(projectServices);
+ mockPsiPackage(applicationServices, projectServices);
+
+ registerExtensionPoint(
+ ExtensionPointName.create("com.android.project.buildSystemService"),
+ BuildSystemService.class)
+ .registerExtension(new BlazeBuildSystemService());
+ }
+
+ /**
+ * If we have a resource module and a target directory, then we can get the res dir from the
+ * module, and use the target directory for everything else.
+ */
+ @Test
+ public void getResourceSourceSetsWithTargetDirectory() {
+ AndroidFacet facet = mockResourceFacet();
+ File resourceFile = VfsUtilCore.virtualToIoFile(resource);
+ File targetFile = VfsUtilCore.virtualToIoFile(target);
+ List<AndroidSourceSet> sourceSets = AndroidSourceSet.getSourceSets(facet, target);
+ assertThat(sourceSets).hasSize(1);
+ AndroidSourceSet sourceSet = sourceSets.get(0);
+ AndroidProjectPaths paths = sourceSet.getPaths();
+ assertThat(sourceSet.getName()).isEqualTo("com.google.target");
+ assertThat(paths.getModuleRoot()).isEqualTo(resourceFile);
+ assertThat(paths.getSrcDirectory(null)).isEqualTo(targetFile);
+ assertThat(paths.getTestDirectory(null)).isEqualTo(targetFile);
+ assertThat(paths.getResDirectory()).isEqualTo(new File(resourceFile, "res"));
+ assertThat(paths.getAidlDirectory(null)).isEqualTo(targetFile);
+ assertThat(paths.getManifestDirectory()).isEqualTo(targetFile);
+ }
+
+ /**
+ * If we have a target directory but no resource module, we'll assume the res dir is just
+ * target/res.
+ */
+ @Test
+ public void getWorkspaceSourceSetsWithTargetDirectory() {
+ AndroidFacet facet = mockWorkspaceFacet();
+ File rootFile = VfsUtilCore.virtualToIoFile(root);
+ File targetFile = VfsUtilCore.virtualToIoFile(target);
+ List<AndroidSourceSet> sourceSets = AndroidSourceSet.getSourceSets(facet, target);
+ assertThat(sourceSets).hasSize(1);
+ AndroidSourceSet sourceSet = sourceSets.get(0);
+ AndroidProjectPaths paths = sourceSet.getPaths();
+ assertThat(sourceSet.getName()).isEqualTo("com.google.target");
+ assertThat(paths.getModuleRoot()).isEqualTo(rootFile);
+ assertThat(paths.getSrcDirectory(null)).isEqualTo(targetFile);
+ assertThat(paths.getTestDirectory(null)).isEqualTo(targetFile);
+ assertThat(paths.getResDirectory()).isEqualTo(new File(targetFile, "res"));
+ assertThat(paths.getAidlDirectory(null)).isEqualTo(targetFile);
+ assertThat(paths.getManifestDirectory()).isEqualTo(targetFile);
+ }
+
+ /**
+ * If no target directory is given, but we have a resource module, we can still figure out some
+ * paths.
+ */
+ @Test
+ public void getResourceSourceSetsWithNoTargetDirectory() {
+ AndroidFacet facet = mockResourceFacet();
+ File rootFile = VfsUtilCore.virtualToIoFile(root);
+ File resourceFile = VfsUtilCore.virtualToIoFile(resource);
+ List<AndroidSourceSet> sourceSets = AndroidSourceSet.getSourceSets(facet, null);
+ assertThat(sourceSets).hasSize(1);
+ AndroidSourceSet sourceSet = sourceSets.get(0);
+ AndroidProjectPaths paths = sourceSet.getPaths();
+ assertThat(sourceSet.getName()).isEqualTo("com.google.resource");
+ assertThat(paths.getModuleRoot()).isEqualTo(resourceFile);
+ assertThat(paths.getSrcDirectory(null)).isEqualTo(resourceFile);
+ assertThat(paths.getTestDirectory(null)).isEqualTo(resourceFile);
+ assertThat(paths.getResDirectory()).isEqualTo(new File(resourceFile, "res"));
+ assertThat(paths.getAidlDirectory(null)).isEqualTo(resourceFile);
+ assertThat(paths.getManifestDirectory()).isEqualTo(resourceFile);
+ }
+
+ /**
+ * If no target directory is given, and we have the workspace module, we'll just use the module
+ * root.
+ */
+ @Test
+ public void getWorkspaceSourceSetsWithNoTargetDirectory() {
+ AndroidFacet facet = mockWorkspaceFacet();
+ File rootFile = VfsUtilCore.virtualToIoFile(root);
+ List<AndroidSourceSet> sourceSets = AndroidSourceSet.getSourceSets(facet, null);
+ assertThat(sourceSets).hasSize(1);
+ AndroidSourceSet sourceSet = sourceSets.get(0);
+ AndroidProjectPaths paths = sourceSet.getPaths();
+ assertThat(sourceSet.getName()).isEqualTo(".workspace");
+ assertThat(paths.getModuleRoot()).isEqualTo(rootFile);
+ assertThat(paths.getSrcDirectory(null)).isEqualTo(rootFile);
+ assertThat(paths.getTestDirectory(null)).isEqualTo(rootFile);
+ assertThat(paths.getResDirectory()).isEqualTo(new File(rootFile, "res"));
+ assertThat(paths.getAidlDirectory(null)).isEqualTo(rootFile);
+ assertThat(paths.getManifestDirectory()).isEqualTo(rootFile);
+ }
+
+ private void mockBlazeImportSettings(Container projectServices) {
+ BlazeImportSettingsManager importSettingsManager = new BlazeImportSettingsManager();
+ importSettingsManager.setImportSettings(
+ new BlazeImportSettings("", "", "", "", Blaze.BuildSystem.Blaze));
+ projectServices.register(BlazeImportSettingsManager.class, importSettingsManager);
+ }
+
+ private void mockPsiPackage(Container applicationServices, Container projectServices) {
+ projectServices.register(PsiManager.class, mock(PsiManager.class));
+ applicationServices.register(JavaDirectoryService.class, mock(JavaDirectoryService.class));
+ PsiManager manager = PsiManager.getInstance(project);
+ PsiDirectory targetPsiDirectory = mock(PsiDirectory.class);
+ PsiPackage targetPsiPackage = new PsiPackageImpl(manager, "com.google.target");
+ when(PsiManager.getInstance(project).findDirectory(target)).thenReturn(targetPsiDirectory);
+ when(JavaDirectoryService.getInstance().getPackage(targetPsiDirectory))
+ .thenReturn(targetPsiPackage);
+ }
+
+ private void mockFacetRegistry(Container applicationServices) {
+ applicationServices.register(FacetTypeRegistry.class, new FacetTypeRegistryImpl());
+ registerExtensionPoint(FacetType.EP_NAME, FacetType.class)
+ .registerExtension(new AndroidFacetType());
+ }
+
+ private AndroidFacet mockWorkspaceFacet() {
+ String name = ".workspace";
+ File rootFile = VfsUtilCore.virtualToIoFile(root);
+ SourceProvider sourceProvider =
+ new SourceProviderImpl(name, new File(rootFile, "AndroidManifest.xml"), ImmutableList.of());
+ return new MockAndroidFacet(project, name, root, sourceProvider);
+ }
+
+ private AndroidFacet mockResourceFacet() {
+ String name = "com.google.resource";
+ File resourceFile = VfsUtilCore.virtualToIoFile(resource);
+ SourceProvider sourceProvider =
+ new SourceProviderImpl(
+ name,
+ new File(resourceFile, "AndroidManifest.xml"),
+ ImmutableList.of(new File(resourceFile, "res")));
+ return new MockAndroidFacet(project, name, resource, sourceProvider);
+ }
+
+ private static class MockAndroidFacet extends AndroidFacet {
+ private SourceProvider sourceProvider;
+
+ public MockAndroidFacet(
+ Project project, String name, VirtualFile root, SourceProvider sourceProvider) {
+ super(new MockModule(project, () -> {}), AndroidFacet.NAME, new AndroidFacetConfiguration());
+ MockModule module = (MockModule) getModule();
+ module.setName(name);
+ ModuleRootManager rootManager = mock(ModuleRootManager.class);
+ when(rootManager.getContentRoots()).thenReturn(new VirtualFile[] {root});
+ module.addComponent(ModuleRootManager.class, rootManager);
+ this.sourceProvider = sourceProvider;
+ }
+
+ @Override
+ public SourceProvider getMainSourceProvider() {
+ return sourceProvider;
+ }
+ }
+}
diff --git a/aswb/3.0/tests/unittests/com/google/idea/blaze/android/sync/model/idea/BlazeAndroidModelTest.java b/aswb/3.0/tests/unittests/com/google/idea/blaze/android/sync/model/idea/BlazeAndroidModelTest.java
new file mode 100644
index 0000000..4432472
--- /dev/null
+++ b/aswb/3.0/tests/unittests/com/google/idea/blaze/android/sync/model/idea/BlazeAndroidModelTest.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.idea;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.android.builder.model.SourceProvider;
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.idea.blaze.base.BlazeTestCase;
+import com.google.idea.blaze.base.actions.BlazeBuildService;
+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.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.data.BlazeProjectDataManager;
+import com.google.idea.blaze.base.sync.data.BlazeProjectDataManagerImpl;
+import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver;
+import com.google.idea.sdkcompat.android.model.AndroidModelAdapter;
+import com.intellij.mock.MockFileDocumentManagerImpl;
+import com.intellij.mock.MockModule;
+import com.intellij.mock.MockPsiManager;
+import com.intellij.mock.MockVirtualFile;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.fileTypes.FileTypeManager;
+import com.intellij.openapi.fileTypes.MockFileTypeManager;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.JarFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileManager;
+import com.intellij.openapi.vfs.VirtualFileSystem;
+import com.intellij.psi.JavaPsiFacade;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiManager;
+import com.intellij.psi.impl.JavaPsiFacadeImpl;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.psi.search.ProjectScopeBuilder;
+import com.intellij.psi.search.ProjectScopeBuilderImpl;
+import javax.annotation.Nullable;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Test cases for {@link BlazeAndroidModel}. */
+@RunWith(JUnit4.class)
+public class BlazeAndroidModelTest extends BlazeTestCase {
+ private Module module;
+ private AndroidModelAdapter model;
+ private MockJavaPsiFacade facade;
+
+ @Override
+ protected void initTest(Container applicationServices, Container projectServices) {
+ applicationServices.register(FileTypeManager.class, new MockFileTypeManager());
+ applicationServices.register(
+ FileDocumentManager.class, new MockFileDocumentManagerImpl(null, null));
+ applicationServices.register(VirtualFileManager.class, mock(VirtualFileManager.class));
+ applicationServices.register(BlazeBuildService.class, new BlazeBuildService());
+ projectServices.register(ProjectScopeBuilder.class, new ProjectScopeBuilderImpl(project));
+ projectServices.register(ProjectViewManager.class, new MockProjectViewManager());
+ projectServices.register(
+ BlazeProjectDataManager.class, new BlazeProjectDataManagerImpl(project));
+
+ BlazeImportSettingsManager manager = new BlazeImportSettingsManager();
+ manager.setImportSettings(new BlazeImportSettings("", "", "", "", BuildSystem.Blaze));
+ projectServices.register(BlazeImportSettingsManager.class, manager);
+
+ facade =
+ new MockJavaPsiFacade(
+ project,
+ new MockPsiManager(project),
+ ImmutableList.of("com.google.example.Modified", "com.google.example.NotModified"));
+
+ projectServices.register(JavaPsiFacade.class, facade);
+ module = new MockModule(() -> {});
+ model = new BlazeAndroidModel(project, module, null, mock(SourceProvider.class), null, "", 0);
+ }
+
+ @Test
+ public void testIsClassFileOutOfDate() {
+ VirtualFile modifiedJarFile =
+ new MockJarVirtualFile(
+ "/build/com/google/example/libmodified.jar",
+ facade.getTimestamp("com.google.example.Modified") - 100);
+ VirtualFile notModifiedJarFile =
+ new MockJarVirtualFile(
+ "/build/com/google/example/libnotmodified.jar",
+ facade.getTimestamp("com.google.example.NotModified") + 100);
+ VirtualFile modifiedClassFile =
+ new MockClassVirtualFile(
+ "/build/com/google/example/libmodified.jar!/com/google/example/Modified.class",
+ modifiedJarFile);
+ VirtualFile notModifiedClassFile =
+ new MockClassVirtualFile(
+ "/build/com/google/example/libnotmodified.jar!/com/google/example/NotModified.class",
+ notModifiedJarFile);
+ assertThat(model.isClassFileOutOfDate(module, "com.google.example.Modified", modifiedClassFile))
+ .isTrue();
+ assertThat(
+ model.isClassFileOutOfDate(
+ module, "com.google.example.NotModified", notModifiedClassFile))
+ .isFalse();
+
+ BlazeBuildService.getInstance().buildProject(project);
+ assertThat(model.isClassFileOutOfDate(module, "com.google.example.Modified", modifiedClassFile))
+ .isFalse();
+ assertThat(
+ model.isClassFileOutOfDate(
+ module, "com.google.example.NotModified", notModifiedClassFile))
+ .isFalse();
+ }
+
+ private static class MockJavaPsiFacade extends JavaPsiFacadeImpl {
+ private ImmutableMap<String, PsiClass> classes;
+ private ImmutableMap<String, Long> timestamps;
+
+ MockJavaPsiFacade(
+ Project project, PsiManager psiManager, ImmutableCollection<String> classNames) {
+ super(project, psiManager, null, null);
+ ImmutableMap.Builder<String, PsiClass> classesBuilder = ImmutableMap.builder();
+ ImmutableMap.Builder<String, Long> timestampsBuilder = ImmutableMap.builder();
+ for (String className : classNames) {
+ VirtualFile virtualFile =
+ new MockVirtualFile("/src/" + className.replace('.', '/') + ".java");
+ PsiFile psiFile = mock(PsiFile.class);
+ when(psiFile.getVirtualFile()).thenReturn(virtualFile);
+ PsiClass psiClass = mock(PsiClass.class);
+ when(psiClass.getContainingFile()).thenReturn(psiFile);
+ classesBuilder.put(className, psiClass);
+ timestampsBuilder.put(className, virtualFile.getTimeStamp());
+ }
+ classes = classesBuilder.build();
+ timestamps = timestampsBuilder.build();
+ }
+
+ @Nullable
+ @Override
+ public PsiClass findClass(String qualifiedName, GlobalSearchScope scope) {
+ if (scope.equals(GlobalSearchScope.projectScope(getProject()))) {
+ return classes.get(qualifiedName);
+ }
+ return null;
+ }
+
+ long getTimestamp(String qualifiedName) {
+ return timestamps.get(qualifiedName);
+ }
+ }
+
+ private static class MockClassVirtualFile extends MockVirtualFile {
+ private static JarFileSystem fileSystem = mock(JarFileSystem.class);
+
+ MockClassVirtualFile(String name, VirtualFile jar) {
+ super(name);
+ when(fileSystem.getVirtualFileForJar(this)).thenReturn(jar);
+ }
+
+ @Override
+ public VirtualFileSystem getFileSystem() {
+ return fileSystem;
+ }
+ }
+
+ private static class MockJarVirtualFile extends MockVirtualFile {
+ private long timestamp;
+
+ MockJarVirtualFile(String name, long timestamp) {
+ super(name);
+ this.timestamp = timestamp;
+ }
+
+ @Override
+ public long getTimeStamp() {
+ return timestamp;
+ }
+ }
+
+ private static class MockProjectViewManager extends ProjectViewManager {
+ private ProjectViewSet set = new ProjectViewSet(ImmutableList.of());
+
+ @Nullable
+ @Override
+ public ProjectViewSet getProjectViewSet() {
+ return set;
+ }
+
+ @Nullable
+ @Override
+ public ProjectViewSet reloadProjectView(
+ BlazeContext context, WorkspacePathResolver workspacePathResolver) {
+ return null;
+ }
+ }
+}
diff --git a/aswb/BUILD b/aswb/BUILD
index 7772ebc..93784b5 100644
--- a/aswb/BUILD
+++ b/aswb/BUILD
@@ -8,20 +8,58 @@
"//build_defs:build_defs.bzl",
"intellij_plugin",
"merged_plugin_xml",
+ "optional_plugin_xml",
+ "plugin_deploy_zip",
+ "repackaged_files",
"stamped_plugin_xml",
)
+load(
+ "//build_defs:intellij_plugin_debug_target.bzl",
+ "intellij_plugin_debug_target",
+)
load("//:version.bzl", "VERSION")
+load("//intellij_platform_sdk:build_defs.bzl", "select_for_plugin_api")
+load(
+ "//testing:test_defs.bzl",
+ "intellij_integration_test_suite",
+ "intellij_unit_test_suite",
+)
merged_plugin_xml(
name = "merged_plugin_xml_common",
srcs = [
"src/META-INF/aswb.xml",
"//base:plugin_xml",
- "//cpp:plugin_xml",
"//java:plugin_xml",
],
)
+optional_plugin_xml(
+ name = "optional_cpp_xml",
+ module = "com.intellij.modules.cidr.lang",
+ plugin_xml = "//cpp:plugin_xml",
+)
+
+optional_plugin_xml(
+ name = "optional_ndk_xml",
+ module = "com.android.tools.ndk",
+ plugin_xml = ":merged_ndk_contents_xml",
+)
+
+merged_plugin_xml(
+ name = "merged_ndk_contents_xml",
+ srcs = ["src/META-INF/ndk-contents.xml"] + select_for_plugin_api({
+ "android-studio-2.3.1.0": [],
+ "android-studio-3.0.0.9": ["3.0/src/META-INF/ndk-workspace-contents.xml"],
+ }),
+)
+
+OPTIONAL_PLUGIN_XMLS = [
+ "//java:optional_xml",
+ ":optional_cpp_xml",
+ ":optional_ndk_xml",
+]
+
merged_plugin_xml(
name = "merged_plugin_xml",
srcs = [
@@ -35,7 +73,7 @@
changelog_file = "//:changelog",
include_product_code_in_stamp = True,
plugin_id = "com.google.idea.bazel.aswb",
- plugin_name = "Android Studio with Bazel",
+ plugin_name = "Bazel",
plugin_xml = ":merged_plugin_xml",
stamp_since_build = True,
version = VERSION,
@@ -43,17 +81,19 @@
java_library(
name = "aswb_lib",
- srcs = glob(["src/**/*.java"]),
+ srcs = glob(["src/**/*.java"]) + select_for_plugin_api({
+ "android-studio-2.3.1.0": glob(["2.3/src/**/*.java"]),
+ "android-studio-3.0.0.9": glob(["3.0/src/**/*.java"]),
+ }),
resources = glob(["resources/**/*"]),
- runtime_deps = [
- "//cpp",
- ],
deps = [
"//base",
"//common/experiments",
+ "//cpp",
"//intellij_platform_sdk:plugin_api",
"//java",
"//proto:proto_deps",
+ "//sdkcompat",
"@jsr305_annotations//jar",
],
)
@@ -68,15 +108,12 @@
],
)
-load(
- "//testing:test_defs.bzl",
- "intellij_integration_test_suite",
- "intellij_unit_test_suite",
-)
-
intellij_unit_test_suite(
name = "unit_tests",
- srcs = glob(["tests/unittests/**/*.java"]),
+ srcs = glob(["tests/unittests/**/*.java"]) + select_for_plugin_api({
+ "android-studio-2.3.1.0": [],
+ "android-studio-3.0.0.9": glob(["3.0/tests/unittests/**/*.java"]),
+ }),
test_package_root = "com.google.idea.blaze.android",
deps = [
":aswb_lib",
@@ -92,9 +129,20 @@
],
)
-intellij_integration_test_suite(
+test_suite(
name = "integration_tests",
- srcs = glob(["tests/integrationtests/**/*.java"]),
+ tests = [
+ ":NdkDependenciesTest",
+ ":normal_integration_tests",
+ ],
+)
+
+intellij_integration_test_suite(
+ name = "normal_integration_tests",
+ srcs = glob(
+ ["tests/integrationtests/**/*.java"],
+ exclude = ["tests/integrationtests/com/google/idea/blaze/android/plugin/NdkDependenciesTest.java"],
+ ),
platform_prefix = "AndroidStudio",
required_plugins = "com.google.idea.bazel.aswb",
test_package_root = "com.google.idea.blaze.android",
@@ -113,15 +161,69 @@
"//intellij_platform_sdk:plugin_api_for_tests",
"//java",
"//proto:proto_deps",
+ "//sdkcompat",
"@jsr305_annotations//jar",
"@junit//jar",
],
)
+intellij_integration_test_suite(
+ name = "NdkDependenciesTest",
+ srcs = ["tests/integrationtests/com/google/idea/blaze/android/plugin/NdkDependenciesTest.java"],
+ platform_prefix = "AndroidStudio",
+ required_plugins = "com.google.idea.bazel.aswb",
+ test_package_root = "com.google.idea.blaze.android",
+ runtime_deps = [
+ ":aswb_bazel",
+ "//cpp",
+ "//java",
+ ],
+ deps = [
+ ":aswb_lib",
+ ":integration_test_utils",
+ "//base",
+ "//base:integration_test_utils",
+ "//base:unit_test_utils",
+ "//intellij_platform_sdk:plugin_api_for_tests",
+ "//proto:proto_deps",
+ "@junit//jar",
+ ],
+)
+
intellij_plugin(
name = "aswb_bazel",
+ optional_plugin_xmls = OPTIONAL_PLUGIN_XMLS,
plugin_xml = ":stamped_plugin_xml",
deps = [
":aswb_lib",
],
)
+
+repackaged_files(
+ name = "plugin_jar",
+ srcs = [":aswb_bazel"],
+ prefix = "aswb/lib",
+)
+
+repackaged_files(
+ name = "aspect_directory",
+ srcs = ["//aspect:aspect_files"],
+ prefix = "aswb/aspect",
+)
+
+intellij_plugin_debug_target(
+ name = "aswb_bazel_dev",
+ deps = [
+ ":aspect_directory",
+ ":plugin_jar",
+ ],
+)
+
+plugin_deploy_zip(
+ name = "aswb_bazel_zip",
+ srcs = [
+ ":aspect_directory",
+ ":plugin_jar",
+ ],
+ zip_filename = "aswb_bazel.zip",
+)
diff --git a/aswb/src/META-INF/aswb.xml b/aswb/src/META-INF/aswb.xml
index 4bf7584..1faf854 100644
--- a/aswb/src/META-INF/aswb.xml
+++ b/aswb/src/META-INF/aswb.xml
@@ -51,14 +51,14 @@
<extensionPoints>
<extensionPoint qualifiedName="com.google.idea.blaze.BuildSystemAndroidJdkProvider" interface="com.google.idea.blaze.android.sync.BuildSystemAndroidJdkProvider"/>
+ <extensionPoint qualifiedName="com.google.idea.blaze.AndroidBinaryLaunchMethodsProvider" interface="com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryLaunchMethodsProvider"/>
+ <extensionPoint qualifiedName="com.google.idea.blaze.AndroidTestLaunchMethodsProvider" interface="com.google.idea.blaze.android.run.test.BlazeAndroidTestLaunchMethodsProvider"/>
</extensionPoints>
<extensions defaultExtensionNs="com.google.idea.blaze">
<SyncPlugin implementation="com.google.idea.blaze.android.sync.BlazeAndroidSyncPlugin"/>
<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"/>
- <RunConfigurationFactory implementation="com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationFactory"/>
<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"/>
@@ -66,27 +66,14 @@
<BuildSystemAndroidJdkProvider implementation="com.google.idea.blaze.android.sync.BazelAndroidJdkProvider"/>
<BlazeTestEventsHandler implementation="com.google.idea.blaze.android.run.test.smrunner.BlazeAndroidTestEventsHandler"/>
<ProjectViewDefaultValueProvider implementation="com.google.idea.blaze.android.projectview.AndroidSdkPlatformSection$AndroidSdkPlatformProjectViewDefaultValueProvider"/>
+ <AndroidBinaryLaunchMethodsProvider implementation="com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryLaunchMethodsProviderImpl"/>
+ <AndroidTestLaunchMethodsProvider implementation="com.google.idea.blaze.android.run.test.BlazeAndroidTestLaunchMethodsProviderImpl"/>
</extensions>
<extensions defaultExtensionNs="com.android.ide">
<sdkEventListener implementation="com.google.idea.blaze.android.sdk.AndroidSdkListener"/>
</extensions>
- <!-- BEGIN NDK SUPPORT -->
- <extensions defaultExtensionNs="com.intellij">
- <applicationService serviceInterface="com.google.idea.blaze.android.cppapi.BlazeNativeDebuggerIdProvider"
- serviceImplementation="com.google.idea.blaze.android.cppimpl.debug.BlazeNativeAndroidDebuggerIdProviderImpl"/>
- </extensions>
-
- <extensions defaultExtensionNs="cidr.debugger">
- <languageSupportFactory implementation="com.google.idea.blaze.android.cppimpl.debug.BlazeAndroidNativeDebuggerLanguageSupportFactory"/>
- </extensions>
-
- <extensions defaultExtensionNs="com.android.run">
- <androidDebugger implementation="com.google.idea.blaze.android.cppimpl.debug.BlazeAutoAndroidDebugger"/>
- </extensions>
- <!-- END NDK SUPPORT -->
-
<extensions defaultExtensionNs="com.google.idea.blaze">
<BlazeUserSettingsContributor implementation="com.google.idea.blaze.android.settings.BlazeAndroidUserSettingsContributor$BlazeAndroidUserSettingsProvider"/>
</extensions>
diff --git a/aswb/src/META-INF/ndk-contents.xml b/aswb/src/META-INF/ndk-contents.xml
new file mode 100644
index 0000000..d08d022
--- /dev/null
+++ b/aswb/src/META-INF/ndk-contents.xml
@@ -0,0 +1,36 @@
+<!--
+ ~ Copyright 2017 The Bazel Authors. All rights reserved.
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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.android.tools.ndk</depends>
+
+ <extensions defaultExtensionNs="com.google.idea.blaze">
+ <SyncListener implementation="com.google.idea.blaze.android.cppimpl.BlazeNdkSupportEnabler"/>
+ </extensions>
+
+ <extensions defaultExtensionNs="com.intellij">
+ <applicationService serviceInterface="com.google.idea.blaze.android.cppapi.BlazeNativeDebuggerIdProvider"
+ serviceImplementation="com.google.idea.blaze.android.cppimpl.debug.BlazeNativeAndroidDebuggerIdProviderImpl"/>
+ </extensions>
+
+ <extensions defaultExtensionNs="cidr.debugger">
+ <languageSupportFactory implementation="com.google.idea.blaze.android.cppimpl.debug.BlazeAndroidNativeDebuggerLanguageSupportFactory"/>
+ </extensions>
+
+ <extensions defaultExtensionNs="com.android.run">
+ <androidDebugger implementation="com.google.idea.blaze.android.cppimpl.debug.BlazeAutoAndroidDebugger"/>
+ </extensions>
+
+</idea-plugin>
\ No newline at end of file
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 75e1a12..f806286 100644
--- a/aswb/src/com/google/idea/blaze/android/cppimpl/BlazeNdkSupportEnabler.java
+++ b/aswb/src/com/google/idea/blaze/android/cppimpl/BlazeNdkSupportEnabler.java
@@ -25,10 +25,12 @@
import com.google.idea.blaze.base.settings.BlazeImportSettings;
import com.google.idea.blaze.base.sync.BlazeSyncParams.SyncMode;
import com.google.idea.blaze.base.sync.SyncListener;
+import com.google.idea.blaze.cpp.OCWorkspaceProvider;
+import com.google.idea.sdkcompat.cidr.OCWorkspaceModificationTrackersCompatUtils;
import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.TransactionGuard;
import com.intellij.openapi.project.Project;
import com.jetbrains.cidr.lang.workspace.OCWorkspace;
-import com.jetbrains.cidr.lang.workspace.OCWorkspaceManager;
final class BlazeNdkSupportEnabler extends SyncListener.Adapter {
@@ -56,29 +58,36 @@
* @param enabled if true, turn on C support in the IDE. If false, turn off C support in the IDE.
*/
private static void enableCSupportInIde(Project project, boolean enabled) {
- OCWorkspace workspace = OCWorkspaceManager.getWorkspace(project);
+ OCWorkspace workspace = OCWorkspaceProvider.getWorkspace(project);
+ if (workspace == null) {
+ // NDK workspace manager plugin isn't enabled.
+ return;
+ }
Boolean isCurrentlyEnabled = !LANGUAGE_SUPPORT_DISABLED.get(project, false);
if (isCurrentlyEnabled != enabled) {
NdkHelper.disableCppLanguageSupport(project, !enabled);
- rebuildSymbols(project, workspace);
+ rebuildSymbols(project);
}
}
- private static void rebuildSymbols(Project project, 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();
- }
- });
+ private static void rebuildSymbols(Project project) {
+ TransactionGuard.getInstance()
+ .submitTransactionLater(
+ project,
+ () ->
+ ApplicationManager.getApplication().runReadAction(() -> doRebuildSymbols(project)));
+ }
+
+ private static void doRebuildSymbols(Project project) {
+ if (project.isDisposed()) {
+ return;
+ }
+ // Notifying BuildSettingsChangeTracker in unitTestMode will leads to a dead lock.
+ // See b/23087433 for more information.
+ if (!ApplicationManager.getApplication().isUnitTestMode()) {
+ OCWorkspaceModificationTrackersCompatUtils.getTrackers(project)
+ .getBuildSettingsChangesTracker()
+ .incModificationCount();
+ }
}
}
diff --git a/aswb/src/com/google/idea/blaze/android/npw/project/BlazeAndroidProjectPaths.java b/aswb/src/com/google/idea/blaze/android/npw/project/BlazeAndroidProjectPaths.java
new file mode 100644
index 0000000..6f797eb
--- /dev/null
+++ b/aswb/src/com/google/idea/blaze/android/npw/project/BlazeAndroidProjectPaths.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.npw.project;
+
+import com.android.SdkConstants;
+import com.android.builder.model.SourceProvider;
+import com.android.tools.idea.npw.project.AndroidProjectPaths;
+import com.android.tools.idea.npw.project.AndroidSourceSet;
+import com.google.common.collect.Iterables;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ModuleRootManager;
+import com.intellij.openapi.vfs.VfsUtilCore;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.JavaDirectoryService;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiManager;
+import com.intellij.psi.PsiPackage;
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+import javax.annotation.Nullable;
+import org.jetbrains.android.facet.AndroidFacet;
+
+/**
+ * Project paths for a Blaze Android project.
+ *
+ * <p>We mostly just take whatever directory the user specified and put the new component there.
+ * Unlike Gradle, Blaze has no strict requirements regarding the structure of an Android project,
+ * but there are some common conventions:
+ *
+ * <pre>
+ * google3/
+ * |-java/com/google/foo/bar/... (module root)
+ * | |-BUILD
+ * | |-AndroidManifest.xml (manifest directory)
+ * | |-Baz.java (source directory of com.google.foo.bar.Baz)
+ * | |-Baz.aidl (aidl directory, option 1)
+ * | |-aidl/
+ * | | `-com/google/foo/bar/Baz.aidl (aidl directory, option 2)
+ * | `-res/... (res directory, one of the few things required by the build system)
+ * `-javatest/com/google/foo/bar/...
+ * |-BUILD
+ * `-BazTest.java (test directory of com.google.foo.bar.BazTest)
+ * </pre>
+ *
+ * However, this is also possible (package name unrelated to directory structure):
+ *
+ * <pre>
+ * google3/experimental/users/foo/my/own/project/
+ * |-Baz.java (com.google.foo.bar.Baz)
+ * `-BazTest.java (com.google.foo.bar.BazTest)
+ * </pre>
+ *
+ * So is this (versioned paths that aren't reflected by the package name):
+ *
+ * <pre>
+ * google3/third_party/com/google/foo/bar/
+ * |-v1/Baz.java (com.google.foo.bar.Baz)
+ * `-v2/Baz.java (com.google.foo.bar.Baz)
+ * </pre>
+ */
+public class BlazeAndroidProjectPaths implements AndroidProjectPaths {
+ @Nullable private File moduleRoot;
+ @Nullable private File srcDirectory;
+ @Nullable private File resDirectory;
+
+ @Nullable
+ @Override
+ public File getModuleRoot() {
+ return moduleRoot;
+ }
+
+ @Nullable
+ @Override
+ public File getSrcDirectory(@Nullable String packageName) {
+ return srcDirectory;
+ }
+
+ @Nullable
+ @Override
+ public File getTestDirectory(@Nullable String packageName) {
+ return srcDirectory;
+ }
+
+ @Nullable
+ @Override
+ public File getResDirectory() {
+ return resDirectory;
+ }
+
+ @Nullable
+ @Override
+ public File getAidlDirectory(@Nullable String packageName) {
+ return srcDirectory;
+ }
+
+ @Nullable
+ @Override
+ public File getManifestDirectory() {
+ return srcDirectory;
+ }
+
+ /**
+ * The new component wizard uses {@link AndroidSourceSet#getName()} for the default package name
+ * of the new component. If we can figure it out from the target directory here, then we can pass
+ * it to the new component wizard.
+ */
+ private static String getPackageName(Project project, VirtualFile targetDirectory) {
+ PsiDirectory psiDirectory = PsiManager.getInstance(project).findDirectory(targetDirectory);
+ if (psiDirectory == null) {
+ return null;
+ }
+ PsiPackage psiPackage = JavaDirectoryService.getInstance().getPackage(psiDirectory);
+ if (psiPackage == null) {
+ return null;
+ }
+ return psiPackage.getQualifiedName();
+ }
+
+ public static List<AndroidSourceSet> getSourceSets(
+ AndroidFacet androidFacet, @Nullable VirtualFile targetDirectory) {
+ Module module = androidFacet.getModule();
+ BlazeAndroidProjectPaths paths = new BlazeAndroidProjectPaths();
+ VirtualFile[] roots = ModuleRootManager.getInstance(module).getContentRoots();
+ if (roots.length > 0) {
+ paths.moduleRoot = VfsUtilCore.virtualToIoFile(roots[0]);
+ }
+
+ // We have a res dir if this happens to be a resource module.
+ SourceProvider sourceProvider = androidFacet.getMainSourceProvider();
+ paths.resDirectory = Iterables.getFirst(sourceProvider.getResDirectories(), null);
+
+ // If this happens to be a resource package,
+ // the module name (resource package) would be more descriptive than the facet name (Android).
+ // Otherwise, .workspace is still better than (Android).
+ String name = androidFacet.getModule().getName();
+ if (targetDirectory != null) {
+ String packageName = getPackageName(module.getProject(), targetDirectory);
+ if (packageName != null) {
+ name = packageName;
+ }
+ paths.srcDirectory = VfsUtilCore.virtualToIoFile(targetDirectory);
+ } else {
+ // People usually put the manifest file with their sources.
+ paths.srcDirectory = sourceProvider.getManifestFile().getParentFile();
+ }
+ if (paths.resDirectory == null) {
+ paths.resDirectory = new File(paths.srcDirectory, SdkConstants.FD_RES);
+ }
+ return Collections.singletonList(new AndroidSourceSet(name, paths));
+ }
+}
diff --git a/aswb/src/com/google/idea/blaze/android/project/BlazeBuildSystemService.java b/aswb/src/com/google/idea/blaze/android/project/BlazeBuildSystemService.java
index ac68a65..ce5ff5c 100644
--- a/aswb/src/com/google/idea/blaze/android/project/BlazeBuildSystemService.java
+++ b/aswb/src/com/google/idea/blaze/android/project/BlazeBuildSystemService.java
@@ -15,7 +15,8 @@
*/
package com.google.idea.blaze.android.project;
-import com.android.tools.idea.project.BuildSystemService;
+import com.android.tools.idea.npw.project.AndroidSourceSet;
+import com.google.idea.blaze.android.npw.project.BlazeAndroidProjectPaths;
import com.google.idea.blaze.android.sync.model.AndroidResourceModuleRegistry;
import com.google.idea.blaze.base.actions.BlazeBuildService;
import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
@@ -25,6 +26,7 @@
import com.google.idea.blaze.base.settings.Blaze;
import com.google.idea.blaze.base.sync.BlazeSyncManager;
import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
+import com.google.idea.sdkcompat.android.project.BuildSystemServiceAdapter;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.module.Module;
@@ -32,9 +34,12 @@
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import java.io.File;
+import java.util.List;
+import javax.annotation.Nullable;
+import org.jetbrains.android.facet.AndroidFacet;
/** Blaze implementation of {@link BuildSystemService} for build system specific operations. */
-public class BlazeBuildSystemService extends BuildSystemService {
+public class BlazeBuildSystemService extends BuildSystemServiceAdapter {
@Override
public boolean isApplicable(Project project) {
return Blaze.isBlazeProject(project);
@@ -89,4 +94,20 @@
}
}
}
+
+ @Override
+ public String mergeBuildFiles(
+ String dependencies,
+ String destinationContents,
+ Project project,
+ @Nullable String supportLibVersionFilter) {
+ // TODO: check if necessary to implement.
+ return null;
+ }
+
+ @Override
+ public List<AndroidSourceSet> getSourceSets(
+ AndroidFacet facet, @Nullable VirtualFile targetDirectory) {
+ return BlazeAndroidProjectPaths.getSourceSets(facet, targetDirectory);
+ }
}
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 6c132b0..dc57eab 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,10 +15,11 @@
*/
package com.google.idea.blaze.android.resources.actions;
+import com.android.SdkConstants;
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.android.tools.idea.res.IdeResourceNameValidator;
import com.google.common.annotations.VisibleForTesting;
import com.intellij.CommonBundle;
import com.intellij.ide.actions.TemplateKindCombo;
@@ -221,7 +222,8 @@
if (typeName != null) {
ResourceFolderType type = ResourceFolderType.getFolderType(typeName);
if (type != null) {
- ResourceNameValidator validator = ResourceNameValidator.create(true, type);
+ IdeResourceNameValidator validator =
+ IdeResourceNameValidator.forFilename(type, SdkConstants.DOT_XML);
return validator.getErrorText(fileName);
}
}
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 cf4a6d8..2290928 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
@@ -17,7 +17,9 @@
import com.android.resources.ResourceFolderType;
import com.android.resources.ResourceType;
-import com.android.tools.idea.res.ResourceNameValidator;
+import com.android.tools.idea.res.IdeResourceNameValidator;
+import com.google.idea.sdkcompat.android.resources.actions.CreateXmlResourceDialogAdapter;
+import com.google.idea.sdkcompat.android.resources.actions.CreateXmlResourcePanelAdapter;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
import com.intellij.openapi.module.Module;
@@ -40,24 +42,22 @@
import java.util.List;
import java.util.Set;
import java.util.function.Function;
+import javax.annotation.Nullable;
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;
import org.jetbrains.android.actions.CreateXmlResourceSubdirPanel.Parent;
import org.jetbrains.android.util.AndroidResourceUtil;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
/**
* 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 extends CreateXmlResourcePanelAdapter implements Parent {
private JPanel myPanel;
private JTextField myNameField;
@@ -76,14 +76,14 @@
private JBLabel myDirectoriesLabel;
private CreateXmlResourceSubdirPanel mySubdirPanel;
- private ResourceNameValidator myResourceNameValidator;
+ private final IdeResourceNameValidator myResourceNameValidator;
@Nullable private VirtualFile myContextFile;
@Nullable private VirtualFile myResDirectory;
public BlazeCreateXmlResourcePanel(
- @NotNull Module module,
- @NotNull ResourceType resourceType,
- @NotNull ResourceFolderType folderType,
+ Module module,
+ ResourceType resourceType,
+ ResourceFolderType folderType,
@Nullable String resourceName,
@Nullable String resourceValue,
boolean chooseName,
@@ -91,7 +91,7 @@
boolean chooseFilename,
@Nullable VirtualFile defaultFile,
@Nullable VirtualFile contextFile,
- @NotNull Function<Module, ResourceNameValidator> nameValidatorFactory) {
+ Function<Module, IdeResourceNameValidator> nameValidatorFactory) {
setupUi();
setChangeNameVisible(false);
setChangeValueVisible(false);
@@ -178,7 +178,7 @@
}
@Override
- public void resetFromFile(@NotNull VirtualFile file, @NotNull Project project) {
+ public void resetFromFile(VirtualFile file, Project project) {
final VirtualFile parent = file.getParent();
if (parent == null) {
return;
@@ -247,16 +247,21 @@
return new ValidationInfo("choose directories", myDirectoriesPanel);
}
- return CreateXmlResourceDialog.checkIfResourceAlreadyExists(
- myModule.getProject(), resourceDir, resourceName, myResourceType, directoryNames, fileName);
+ return CreateXmlResourceDialogAdapter.checkIfResourceAlreadyExists(
+ myModule.getProject(),
+ resourceDir,
+ resourceName,
+ null,
+ myResourceType,
+ directoryNames,
+ fileName);
}
@Override
- public ResourceNameValidator getResourceNameValidator() {
+ public IdeResourceNameValidator getResourceNameValidatorCompat() {
return myResourceNameValidator;
}
- @NotNull
@Override
public Module getModule() {
return myModule;
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 6bf7deb..c2b67aa 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
@@ -18,8 +18,9 @@
import com.android.ide.common.resources.configuration.FolderConfiguration;
import com.android.resources.ResourceFolderType;
import com.android.resources.ResourceType;
-import com.android.tools.idea.res.ResourceNameValidator;
+import com.android.tools.idea.res.IdeResourceNameValidator;
import com.google.idea.blaze.base.settings.Blaze;
+import com.google.idea.sdkcompat.android.resources.actions.NewResourceCreationHandlerAdapter;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
@@ -27,41 +28,37 @@
import com.intellij.psi.PsiDirectory;
import java.util.Collection;
import java.util.function.Function;
+import javax.annotation.Nullable;
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;
/** Decides which create resource dialogs to use for Blaze projects. */
-public class BlazeNewResourceCreationHandler implements NewResourceCreationHandler {
+public class BlazeNewResourceCreationHandler extends NewResourceCreationHandlerAdapter {
@Override
- public boolean isApplicable(@NotNull Project project) {
+ public boolean isApplicable(Project project) {
return Blaze.isBlazeProject(project);
}
- @NotNull
@Override
public CreateResourceDirectoryDialogBase createNewResourceDirectoryDialog(
- @NotNull Project project,
+ Project project,
@Nullable Module module,
@Nullable ResourceFolderType resType,
@Nullable PsiDirectory resDirectory,
@Nullable DataContext dataContext,
- @NotNull CreateResourceDirectoryDialogBase.ValidatorFactory validatorFactory) {
+ CreateResourceDirectoryDialogBase.ValidatorFactory validatorFactory) {
return new BlazeCreateResourceDirectoryDialog(
project, module, resType, resDirectory, dataContext, validatorFactory);
}
- @NotNull
@Override
public CreateResourceFileDialogBase createNewResourceFileDialog(
- @NotNull AndroidFacet facet,
- @NotNull Collection<CreateTypedResourceFileAction> actions,
+ AndroidFacet facet,
+ Collection<CreateTypedResourceFileAction> actions,
@Nullable ResourceFolderType folderType,
@Nullable String filename,
@Nullable String rootElement,
@@ -70,7 +67,7 @@
boolean chooseModule,
@Nullable PsiDirectory resDirectory,
@Nullable DataContext dataContext,
- @NotNull CreateResourceFileDialogBase.ValidatorFactory validatorFactory) {
+ CreateResourceFileDialogBase.ValidatorFactory validatorFactory) {
return new BlazeCreateResourceFileDialog(
facet,
actions,
@@ -86,10 +83,10 @@
}
@Override
- public CreateXmlResourcePanel createNewResourceValuePanel(
- @NotNull Module module,
- @NotNull ResourceType resourceType,
- @NotNull ResourceFolderType folderType,
+ public CreateXmlResourcePanel createNewResourceValuePanelCompat(
+ Module module,
+ ResourceType resourceType,
+ ResourceFolderType folderType,
@Nullable String resourceName,
@Nullable String resourceValue,
boolean chooseName,
@@ -97,7 +94,7 @@
boolean chooseFilename,
@Nullable VirtualFile defaultFile,
@Nullable VirtualFile contextFile,
- @NotNull Function<Module, ResourceNameValidator> nameValidatorFactory) {
+ Function<Module, IdeResourceNameValidator> nameValidatorFactory) {
return new BlazeCreateXmlResourcePanel(
module,
resourceType,
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 eaff918..8609d14 100644
--- a/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationCommonState.java
+++ b/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationCommonState.java
@@ -23,7 +23,9 @@
import com.google.idea.blaze.android.cppapi.NdkSupport;
import com.google.idea.blaze.android.run.runner.BlazeAndroidRunConfigurationDebuggerManager;
import com.google.idea.blaze.android.run.runner.BlazeAndroidRunConfigurationDeployTargetManager;
+import com.google.idea.blaze.base.command.BlazeCommandName;
import com.google.idea.blaze.base.command.BlazeFlags;
+import com.google.idea.blaze.base.command.BlazeInvocationContext;
import com.google.idea.blaze.base.projectview.ProjectViewSet;
import com.google.idea.blaze.base.run.state.RunConfigurationFlagsState;
import com.google.idea.blaze.base.run.state.RunConfigurationState;
@@ -44,7 +46,8 @@
public class BlazeAndroidRunConfigurationCommonState implements RunConfigurationState {
private static final String DEPLOY_TARGET_STATES_TAG = "android-deploy-target-states";
private static final String DEBUGGER_STATES_TAG = "android-debugger-states";
- private static final String USER_FLAG_TAG = "blaze-user-flag";
+ private static final String USER_BLAZE_FLAG_TAG = "blaze-user-flag";
+ private static final String USER_EXE_FLAG_TAG = "blaze-user-exe-flag";
private static final String NATIVE_DEBUG_ATTR = "blaze-native-debug";
// We need to split "-c dbg" into two flags because we pass flags
@@ -56,15 +59,18 @@
private final BlazeAndroidRunConfigurationDeployTargetManager deployTargetManager;
private final BlazeAndroidRunConfigurationDebuggerManager debuggerManager;
- private final RunConfigurationFlagsState userFlags;
+ private final RunConfigurationFlagsState blazeFlags;
+ private final RunConfigurationFlagsState exeFlags;
private boolean nativeDebuggingEnabled = false;
public BlazeAndroidRunConfigurationCommonState(String buildSystemName, boolean isAndroidTest) {
this.deployTargetManager = new BlazeAndroidRunConfigurationDeployTargetManager(isAndroidTest);
this.debuggerManager = new BlazeAndroidRunConfigurationDebuggerManager(this);
- this.userFlags =
+ this.blazeFlags =
+ new RunConfigurationFlagsState(USER_BLAZE_FLAG_TAG, buildSystemName + " flags:");
+ this.exeFlags =
new RunConfigurationFlagsState(
- USER_FLAG_TAG, String.format("Custom %s build flags:", buildSystemName));
+ USER_EXE_FLAG_TAG, "Executable flags (mobile-install only):");
}
public BlazeAndroidRunConfigurationDeployTargetManager getDeployTargetManager() {
@@ -76,7 +82,11 @@
}
public RunConfigurationFlagsState getBlazeFlagsState() {
- return userFlags;
+ return blazeFlags;
+ }
+
+ public RunConfigurationFlagsState getExeFlagsState() {
+ return exeFlags;
}
public boolean isNativeDebuggingEnabled() {
@@ -88,9 +98,11 @@
}
public ImmutableList<String> getExpandedBuildFlags(
- Project project, ProjectViewSet projectViewSet) {
+ Project project, ProjectViewSet projectViewSet, BlazeCommandName command) {
return ImmutableList.<String>builder()
- .addAll(BlazeFlags.buildFlags(project, projectViewSet))
+ .addAll(
+ BlazeFlags.blazeFlags(
+ project, projectViewSet, command, BlazeInvocationContext.RunConfiguration))
.addAll(getBlazeFlagsState().getExpandedFlags())
.addAll(getNativeDebuggerFlags())
.build();
@@ -117,7 +129,8 @@
@Override
public void readExternal(Element element) throws InvalidDataException {
- userFlags.readExternal(element);
+ blazeFlags.readExternal(element);
+ exeFlags.readExternal(element);
setNativeDebuggingEnabled(Boolean.parseBoolean(element.getAttributeValue(NATIVE_DEBUG_ATTR)));
Element deployTargetStatesElement = element.getChild(DEPLOY_TARGET_STATES_TAG);
@@ -133,7 +146,8 @@
@Override
public void writeExternal(Element element) throws WriteExternalException {
- userFlags.writeExternal(element);
+ blazeFlags.writeExternal(element);
+ exeFlags.writeExternal(element);
element.setAttribute(NATIVE_DEBUG_ATTR, Boolean.toString(nativeDebuggingEnabled));
element.removeChildren(DEPLOY_TARGET_STATES_TAG);
@@ -155,12 +169,14 @@
private static class BlazeAndroidRunConfigurationCommonStateEditor
implements RunConfigurationStateEditor {
- private final RunConfigurationStateEditor userFlagsEditor;
+ private final RunConfigurationStateEditor blazeFlagsEditor;
+ private final RunConfigurationStateEditor exeFlagsEditor;
private final JCheckBox enableNativeDebuggingCheckBox;
BlazeAndroidRunConfigurationCommonStateEditor(
BlazeAndroidRunConfigurationCommonState state, Project project) {
- userFlagsEditor = state.userFlags.getEditor(project);
+ blazeFlagsEditor = state.blazeFlags.getEditor(project);
+ exeFlagsEditor = state.exeFlags.getEditor(project);
enableNativeDebuggingCheckBox = new JCheckBox("Enable native debugging", false);
}
@@ -168,7 +184,8 @@
public void resetEditorFrom(RunConfigurationState genericState) {
BlazeAndroidRunConfigurationCommonState state =
(BlazeAndroidRunConfigurationCommonState) genericState;
- userFlagsEditor.resetEditorFrom(state.userFlags);
+ blazeFlagsEditor.resetEditorFrom(state.blazeFlags);
+ exeFlagsEditor.resetEditorFrom(state.exeFlags);
enableNativeDebuggingCheckBox.setSelected(state.isNativeDebuggingEnabled());
}
@@ -176,13 +193,15 @@
public void applyEditorTo(RunConfigurationState genericState) {
BlazeAndroidRunConfigurationCommonState state =
(BlazeAndroidRunConfigurationCommonState) genericState;
- userFlagsEditor.applyEditorTo(state.userFlags);
+ blazeFlagsEditor.applyEditorTo(state.blazeFlags);
+ exeFlagsEditor.applyEditorTo(state.exeFlags);
state.setNativeDebuggingEnabled(enableNativeDebuggingCheckBox.isSelected());
}
@Override
public JComponent createComponent() {
- List<Component> result = Lists.newArrayList(userFlagsEditor.createComponent());
+ List<Component> result =
+ Lists.newArrayList(blazeFlagsEditor.createComponent(), exeFlagsEditor.createComponent());
if (NdkSupport.NDK_SUPPORT.getValue()) {
result.add(enableNativeDebuggingCheckBox);
}
@@ -191,7 +210,8 @@
@Override
public void setComponentEnabled(boolean enabled) {
- userFlagsEditor.setComponentEnabled(enabled);
+ blazeFlagsEditor.setComponentEnabled(enabled);
+ exeFlagsEditor.setComponentEnabled(enabled);
enableNativeDebuggingCheckBox.setEnabled(enabled);
}
}
diff --git a/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationFactory.java b/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationFactory.java
deleted file mode 100644
index 30955bf..0000000
--- a/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationFactory.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.android.run;
-
-import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
-import com.google.idea.blaze.base.ideinfo.TargetKey;
-import com.google.idea.blaze.base.model.BlazeProjectData;
-import com.google.idea.blaze.base.model.primitives.Kind;
-import com.google.idea.blaze.base.model.primitives.Label;
-import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
-import com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType;
-import com.google.idea.blaze.base.run.BlazeRunConfigurationFactory;
-import com.intellij.execution.configurations.ConfigurationFactory;
-import com.intellij.execution.configurations.RunConfiguration;
-import com.intellij.openapi.project.Project;
-
-/** Creates run configurations for android_binary and android_test. */
-public class BlazeAndroidRunConfigurationFactory extends BlazeRunConfigurationFactory {
- @Override
- public boolean handlesTarget(Project project, BlazeProjectData blazeProjectData, Label label) {
- TargetIdeInfo target = blazeProjectData.targetMap.get(TargetKey.forPlainTarget(label));
- return target != null && target.kindIsOneOf(Kind.ANDROID_BINARY, Kind.ANDROID_TEST);
- }
-
- @Override
- protected ConfigurationFactory getConfigurationFactory() {
- return BlazeCommandRunConfigurationType.getInstance().getFactory();
- }
-
- @Override
- public void setupConfiguration(RunConfiguration configuration, Label target) {
- final BlazeCommandRunConfiguration blazeConfig = (BlazeCommandRunConfiguration) configuration;
- blazeConfig.setTarget(target);
- blazeConfig.setGeneratedName();
- }
-}
diff --git a/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationValidationUtil.java b/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationValidationUtil.java
index 992f414..85bd239 100644
--- a/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationValidationUtil.java
+++ b/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationValidationUtil.java
@@ -15,7 +15,6 @@
*/
package com.google.idea.blaze.android.run;
-import com.android.tools.idea.gradle.util.Projects;
import com.android.tools.idea.run.ValidationError;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
@@ -25,6 +24,7 @@
import com.google.idea.blaze.base.projectview.ProjectViewSet;
import com.google.idea.blaze.base.run.targetfinder.TargetFinder;
import com.google.idea.blaze.base.settings.Blaze;
+import com.google.idea.sdkcompat.android.project.AndroidProjectInfoAdapter;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.RuntimeConfigurationError;
import com.intellij.execution.configurations.RuntimeConfigurationException;
@@ -73,7 +73,7 @@
return errors;
}
final Project project = module.getProject();
- if (Projects.requiredAndroidModelMissing(project)) {
+ if (AndroidProjectInfoAdapter.requiredAndroidModelMissing(project)) {
errors.add(ValidationError.fatal(SYNC_FAILED_ERR_MSG));
}
return errors;
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 d89f58e..40f7789 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
@@ -17,31 +17,24 @@
import com.android.tools.idea.run.ApkProvisionException;
import com.android.tools.idea.run.ApplicationIdProvider;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
import com.google.idea.blaze.android.run.deployinfo.BlazeAndroidDeployInfo;
+import com.google.idea.blaze.android.run.runner.BlazeApkBuildStep;
import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
+import javax.annotation.Nullable;
import org.jetbrains.android.dom.manifest.Manifest;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
/** Application id provider for android_binary. */
public class BlazeAndroidBinaryApplicationIdProvider implements ApplicationIdProvider {
- private final Project project;
- private final ListenableFuture<BlazeAndroidDeployInfo> deployInfoFuture;
+ private final BlazeApkBuildStep buildStep;
- public BlazeAndroidBinaryApplicationIdProvider(
- Project project, ListenableFuture<BlazeAndroidDeployInfo> deployInfoFuture) {
- this.project = project;
- this.deployInfoFuture = deployInfoFuture;
+ public BlazeAndroidBinaryApplicationIdProvider(BlazeApkBuildStep buildStep) {
+ this.buildStep = buildStep;
}
- @NotNull
@Override
public String getPackageName() throws ApkProvisionException {
- BlazeAndroidDeployInfo deployInfo = Futures.get(deployInfoFuture, ApkProvisionException.class);
+ BlazeAndroidDeployInfo deployInfo = buildStep.getDeployInfo();
Manifest manifest = deployInfo.getMergedManifest();
if (manifest == null) {
throw new ApkProvisionException(
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryLaunchMethodsProvider.java b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryLaunchMethodsProvider.java
new file mode 100644
index 0000000..8aca02d
--- /dev/null
+++ b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryLaunchMethodsProvider.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.intellij.openapi.extensions.ExtensionPointName;
+import com.intellij.openapi.project.Project;
+import java.util.Arrays;
+import java.util.List;
+
+/** Provides a list of supported launch methods for android binaries. */
+public interface BlazeAndroidBinaryLaunchMethodsProvider {
+ ExtensionPointName<BlazeAndroidBinaryLaunchMethodsProvider> EP_NAME =
+ ExtensionPointName.create("com.google.idea.blaze.AndroidBinaryLaunchMethodsProvider");
+
+ static AndroidBinaryLaunchMethodComboEntry[] getAllLaunchMethods(Project project) {
+ return Arrays.stream(EP_NAME.getExtensions())
+ .flatMap(extension -> extension.getLaunchMethods(project).stream())
+ .toArray(AndroidBinaryLaunchMethodComboEntry[]::new);
+ }
+
+ List<AndroidBinaryLaunchMethodComboEntry> getLaunchMethods(Project project);
+
+ /** All possible binary launch methods. */
+ enum AndroidBinaryLaunchMethod {
+ NON_BLAZE,
+ MOBILE_INSTALL,
+ MOBILE_INSTALL_V2,
+ }
+
+ /** Launch methods wrapped for display in a combo box. */
+ class AndroidBinaryLaunchMethodComboEntry {
+ final AndroidBinaryLaunchMethod launchMethod;
+ private final String description;
+
+ public AndroidBinaryLaunchMethodComboEntry(
+ AndroidBinaryLaunchMethod launchMethod, String description) {
+ this.launchMethod = launchMethod;
+ this.description = description;
+ }
+
+ @Override
+ public String toString() {
+ return description;
+ }
+ }
+}
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryLaunchMethodsProviderImpl.java b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryLaunchMethodsProviderImpl.java
new file mode 100644
index 0000000..8d02764
--- /dev/null
+++ b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryLaunchMethodsProviderImpl.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.ImmutableList;
+import com.google.idea.blaze.base.settings.Blaze;
+import com.intellij.openapi.project.Project;
+import java.util.List;
+
+/** Provides a list of supported launch methods from bazel and blaze for android binaries. */
+public class BlazeAndroidBinaryLaunchMethodsProviderImpl
+ implements BlazeAndroidBinaryLaunchMethodsProvider {
+ @Override
+ public List<AndroidBinaryLaunchMethodComboEntry> getLaunchMethods(Project project) {
+ String blaze = Blaze.buildSystemName(project);
+ return ImmutableList.of(
+ new AndroidBinaryLaunchMethodComboEntry(
+ AndroidBinaryLaunchMethod.NON_BLAZE, String.format("Run without using %s", blaze)),
+ new AndroidBinaryLaunchMethodComboEntry(
+ AndroidBinaryLaunchMethod.MOBILE_INSTALL,
+ String.format("Run with %s mobile-install", blaze.toLowerCase())));
+ }
+}
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 cb6b23c..a1abc9b 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
@@ -32,7 +32,6 @@
import com.android.tools.idea.run.tasks.LaunchTasksProvider;
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.deployinfo.BlazeAndroidDeployInfo;
import com.google.idea.blaze.android.run.deployinfo.BlazeApkProvider;
import com.google.idea.blaze.android.run.runner.BlazeAndroidDeviceSelector;
@@ -50,7 +49,6 @@
import java.util.Set;
import javax.annotation.Nullable;
import org.jetbrains.android.facet.AndroidFacet;
-import org.jetbrains.annotations.NotNull;
/** Run context for android_binary. */
class BlazeAndroidBinaryNormalBuildRunContext implements BlazeAndroidRunContext {
@@ -65,24 +63,23 @@
private final BlazeApkProvider apkProvider;
private final ApplicationIdProvider applicationIdProvider;
- public BlazeAndroidBinaryNormalBuildRunContext(
+ BlazeAndroidBinaryNormalBuildRunContext(
Project project,
AndroidFacet facet,
RunConfiguration runConfiguration,
ExecutionEnvironment env,
BlazeAndroidBinaryRunConfigurationState configState,
Label label,
- ImmutableList<String> buildFlags) {
+ ImmutableList<String> blazeFlags) {
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, label, buildFlags);
- this.apkProvider = new BlazeApkProvider(project, buildStep.getDeployInfo());
- this.applicationIdProvider =
- new BlazeAndroidBinaryApplicationIdProvider(project, buildStep.getDeployInfo());
+ this.buildStep = new BlazeApkBuildStepNormalBuild(project, label, blazeFlags);
+ this.apkProvider = new BlazeApkProvider(project, buildStep);
+ this.applicationIdProvider = new BlazeAndroidBinaryApplicationIdProvider(buildStep);
}
@Override
@@ -94,11 +91,10 @@
}
@Override
- public void augmentLaunchOptions(@NotNull LaunchOptions.Builder options) {
+ public void augmentLaunchOptions(LaunchOptions.Builder options) {
options.setDeploy(true).setOpenLogcatAutomatically(true);
}
- @NotNull
@Override
public ConsoleProvider getConsoleProvider() {
return consoleProvider;
@@ -153,8 +149,12 @@
launchOptions.isDebug(),
UserIdHelper.getFlagsFromUserId(userId));
- BlazeAndroidDeployInfo deployInfo =
- Futures.get(buildStep.getDeployInfo(), ExecutionException.class);
+ BlazeAndroidDeployInfo deployInfo;
+ try {
+ deployInfo = buildStep.getDeployInfo();
+ } catch (ApkProvisionException e) {
+ throw new ExecutionException(e);
+ }
return BlazeAndroidBinaryApplicationLaunchTaskProvider.getApplicationLaunchTask(
project,
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 37619aa..c64bb10 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
@@ -18,6 +18,7 @@
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.BlazeAndroidBinaryLaunchMethodsProvider.AndroidBinaryLaunchMethod;
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;
@@ -30,12 +31,11 @@
import com.intellij.execution.runners.DefaultProgramRunner;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.execution.ui.RunContentDescriptor;
-import org.jetbrains.annotations.NotNull;
/** Program runner for {@link BlazeAndroidRunConfiguration} */
public class BlazeAndroidBinaryProgramRunner extends DefaultProgramRunner {
@Override
- public boolean canRun(@NotNull String executorId, @NotNull RunProfile profile) {
+ public boolean canRun(String executorId, RunProfile profile) {
BlazeAndroidRunConfigurationHandler handler =
BlazeAndroidRunConfigurationHandler.getHandlerFrom(profile);
if (handler == null) {
@@ -51,15 +51,17 @@
if (!(handler instanceof BlazeAndroidBinaryRunConfigurationHandler)) {
return false;
}
- return ((BlazeAndroidBinaryRunConfigurationHandler) handler).getState().mobileInstall()
+ AndroidBinaryLaunchMethod launchMethod =
+ ((BlazeAndroidBinaryRunConfigurationHandler) handler).getState().getLaunchMethod();
+ return (AndroidBinaryLaunchMethod.MOBILE_INSTALL.equals(launchMethod)
+ || AndroidBinaryLaunchMethod.MOBILE_INSTALL_V2.equals(launchMethod))
&& (IncrementalInstallDebugExecutor.EXECUTOR_ID.equals(executorId)
|| IncrementalInstallRunExecutor.EXECUTOR_ID.equals(executorId));
}
@Override
protected RunContentDescriptor doExecute(
- @NotNull final RunProfileState state, @NotNull final ExecutionEnvironment env)
- throws ExecutionException {
+ final RunProfileState state, final ExecutionEnvironment env) throws ExecutionException {
RunContentDescriptor descriptor = super.doExecute(state, env);
if (descriptor != null) {
ProcessHandler processHandler = descriptor.getProcessHandler();
@@ -84,7 +86,6 @@
}
@Override
- @NotNull
public String getRunnerId() {
return "AndroidProgramRunner";
}
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
index f57d51f..0a26f27 100644
--- a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationHandler.java
+++ b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationHandler.java
@@ -26,6 +26,7 @@
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.command.BlazeCommandName;
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;
@@ -104,9 +105,14 @@
ProjectViewSet projectViewSet = ProjectViewManager.getInstance(project).getProjectViewSet();
BlazeAndroidRunConfigurationValidationUtil.validateExecution(module, facet, projectViewSet);
- ImmutableList<String> buildFlags =
- configState.getCommonState().getExpandedBuildFlags(project, projectViewSet);
- BlazeAndroidRunContext runContext = createRunContext(project, facet, environment, buildFlags);
+ ImmutableList<String> blazeFlags =
+ configState
+ .getCommonState()
+ .getExpandedBuildFlags(project, projectViewSet, BlazeCommandName.RUN);
+ ImmutableList<String> exeFlags =
+ ImmutableList.copyOf(configState.getCommonState().getExeFlagsState().getExpandedFlags());
+ BlazeAndroidRunContext runContext =
+ createRunContext(project, facet, environment, blazeFlags, exeFlags);
return new BlazeAndroidRunConfigurationRunner(
module,
@@ -120,14 +126,18 @@
Project project,
AndroidFacet facet,
ExecutionEnvironment env,
- ImmutableList<String> buildFlags) {
- if (configState.mobileInstall()) {
- return new BlazeAndroidBinaryMobileInstallRunContext(
- project, facet, configuration, env, configState, getLabel(), buildFlags);
- } else {
- return new BlazeAndroidBinaryNormalBuildRunContext(
- project, facet, configuration, env, configState, getLabel(), buildFlags);
+ ImmutableList<String> blazeFlags,
+ ImmutableList<String> exeFlags) {
+ switch (configState.getLaunchMethod()) {
+ case MOBILE_INSTALL:
+ case MOBILE_INSTALL_V2:
+ return new BlazeAndroidBinaryMobileInstallRunContext(
+ project, facet, configuration, env, configState, getLabel(), blazeFlags, exeFlags);
+ case NON_BLAZE:
+ return new BlazeAndroidBinaryNormalBuildRunContext(
+ project, facet, configuration, env, configState, getLabel(), blazeFlags);
}
+ throw new AssertionError();
}
@Override
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 8bc2076..0dff73d 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
@@ -20,6 +20,7 @@
import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationCommonState;
+import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryLaunchMethodsProvider.AndroidBinaryLaunchMethod;
import com.google.idea.blaze.base.run.state.RunConfigurationState;
import com.google.idea.blaze.base.run.state.RunConfigurationStateEditor;
import com.intellij.openapi.project.Project;
@@ -38,11 +39,15 @@
public static final String DO_NOTHING = "do_nothing";
public static final String LAUNCH_DEEP_LINK = "launch_deep_link";
- private static final String MOBILE_INSTALL_ATTR = "blaze-mobile-install";
+ private static final String LAUNCH_METHOD_ATTR = "launch-method";
+ @Deprecated private static final String MOBILE_INSTALL_ATTR = "blaze-mobile-install";
+ // Remove once v2 becomes default.
private static final String USE_SPLIT_APKS_IF_POSSIBLE = "use-split-apks-if-possible";
+
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 AndroidBinaryLaunchMethod launchMethod = AndroidBinaryLaunchMethod.NON_BLAZE;
private boolean useSplitApksIfPossible = false;
private boolean useWorkProfileIfPresent = false;
private Integer userId;
@@ -65,12 +70,12 @@
return commonState;
}
- boolean mobileInstall() {
- return mobileInstall;
+ public AndroidBinaryLaunchMethod getLaunchMethod() {
+ return launchMethod;
}
- void setMobileInstall(boolean mobileInstall) {
- this.mobileInstall = mobileInstall;
+ void setLaunchMethod(AndroidBinaryLaunchMethod launchMethod) {
+ this.launchMethod = launchMethod;
}
public boolean useSplitApksIfPossible() {
@@ -137,7 +142,16 @@
setActivityClass(Strings.nullToEmpty(element.getAttributeValue(ACTIVITY_CLASS)));
String modeValue = element.getAttributeValue(MODE);
setMode(Strings.isNullOrEmpty(modeValue) ? LAUNCH_DEFAULT_ACTIVITY : modeValue);
- setMobileInstall(Boolean.parseBoolean(element.getAttributeValue(MOBILE_INSTALL_ATTR)));
+ String launchMethodAttribute = element.getAttributeValue(LAUNCH_METHOD_ATTR);
+ if (launchMethodAttribute != null) {
+ launchMethod = AndroidBinaryLaunchMethod.valueOf(launchMethodAttribute);
+ } else {
+ if (Boolean.parseBoolean(element.getAttributeValue(MOBILE_INSTALL_ATTR))) {
+ launchMethod = AndroidBinaryLaunchMethod.MOBILE_INSTALL;
+ } else {
+ launchMethod = AndroidBinaryLaunchMethod.NON_BLAZE;
+ }
+ }
setUseSplitApksIfPossible(
Boolean.parseBoolean(element.getAttributeValue(USE_SPLIT_APKS_IF_POSSIBLE)));
setUseWorkProfileIfPresent(Boolean.parseBoolean(element.getAttributeValue(WORK_PROFILE_ATTR)));
@@ -177,7 +191,7 @@
element.setAttribute(DEEP_LINK, deepLink);
element.setAttribute(ACTIVITY_CLASS, activityClass);
element.setAttribute(MODE, mode);
- element.setAttribute(MOBILE_INSTALL_ATTR, Boolean.toString(mobileInstall));
+ element.setAttribute(LAUNCH_METHOD_ATTR, launchMethod.name());
element.setAttribute(USE_SPLIT_APKS_IF_POSSIBLE, Boolean.toString(useSplitApksIfPossible));
element.setAttribute(WORK_PROFILE_ATTR, Boolean.toString(useWorkProfileIfPresent));
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 8ce6279..65697d1 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
@@ -16,6 +16,8 @@
package com.google.idea.blaze.android.run.binary;
import com.android.tools.idea.run.activity.ActivityLocatorUtils;
+import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryLaunchMethodsProvider.AndroidBinaryLaunchMethod;
+import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryLaunchMethodsProvider.AndroidBinaryLaunchMethodComboEntry;
import com.google.idea.blaze.base.run.state.RunConfigurationState;
import com.google.idea.blaze.base.run.state.RunConfigurationStateEditor;
import com.google.idea.blaze.base.ui.IntegerTextField;
@@ -49,6 +51,7 @@
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
@@ -72,7 +75,7 @@
private JRadioButton launchNothingButton;
private JRadioButton launchDefaultButton;
private JRadioButton launchCustomButton;
- private JCheckBox mobileInstallCheckBox;
+ private JComboBox<AndroidBinaryLaunchMethodComboEntry> launchMethodComboBox;
private JCheckBox splitApksCheckBox;
private JCheckBox useWorkProfileIfPresentCheckBox;
private JLabel userIdLabel;
@@ -131,8 +134,12 @@
launchDefaultButton.addActionListener(listener);
launchNothingButton.addActionListener(listener);
- mobileInstallCheckBox.addActionListener(
- e -> splitApksCheckBox.setVisible(mobileInstallCheckBox.isSelected()));
+ launchMethodComboBox.addActionListener(
+ e ->
+ splitApksCheckBox.setVisible(
+ ((AndroidBinaryLaunchMethodComboEntry) launchMethodComboBox.getSelectedItem())
+ .launchMethod.equals(AndroidBinaryLaunchMethod.MOBILE_INSTALL) // v1 only
+ ));
useWorkProfileIfPresentCheckBox.addActionListener(e -> updateEnabledState());
}
@@ -155,12 +162,18 @@
activityField.getChildComponent().setText(state.getActivityClass());
}
- mobileInstallCheckBox.setSelected(state.mobileInstall());
+ for (int i = 0; i < launchMethodComboBox.getItemCount(); ++i) {
+ if (launchMethodComboBox.getItemAt(i).launchMethod.equals(state.getLaunchMethod())) {
+ launchMethodComboBox.setSelectedIndex(i);
+ break;
+ }
+ }
splitApksCheckBox.setSelected(state.useSplitApksIfPossible());
useWorkProfileIfPresentCheckBox.setSelected(state.useWorkProfileIfPresent());
userIdField.setValue(state.getUserId());
- splitApksCheckBox.setVisible(state.mobileInstall());
+ splitApksCheckBox.setVisible(
+ state.getLaunchMethod().equals(AndroidBinaryLaunchMethod.MOBILE_INSTALL));
updateEnabledState();
}
@@ -180,7 +193,9 @@
} else {
state.setMode(BlazeAndroidBinaryRunConfigurationState.DO_NOTHING);
}
- state.setMobileInstall(mobileInstallCheckBox.isSelected());
+ state.setLaunchMethod(
+ ((AndroidBinaryLaunchMethodComboEntry) launchMethodComboBox.getSelectedItem())
+ .launchMethod);
state.setUseSplitApksIfPossible(splitApksCheckBox.isSelected());
state.setUseWorkProfileIfPresent(useWorkProfileIfPresentCheckBox.isSelected());
}
@@ -199,7 +214,7 @@
launchNothingButton.setEnabled(componentEnabled);
launchDefaultButton.setEnabled(componentEnabled);
launchCustomButton.setEnabled(componentEnabled);
- mobileInstallCheckBox.setEnabled(componentEnabled);
+ launchMethodComboBox.setEnabled(componentEnabled);
splitApksCheckBox.setEnabled(componentEnabled);
useWorkProfileIfPresentCheckBox.setEnabled(componentEnabled);
}
@@ -423,10 +438,10 @@
null,
0,
false));
- mobileInstallCheckBox = new JCheckBox();
- mobileInstallCheckBox.setText(" Use mobile-install");
+ launchMethodComboBox =
+ new JComboBox<>(BlazeAndroidBinaryLaunchMethodsProvider.getAllLaunchMethods(project));
panel.add(
- mobileInstallCheckBox,
+ launchMethodComboBox,
new GridConstraints(
0,
0,
@@ -442,7 +457,7 @@
0,
false));
splitApksCheckBox = new JCheckBox();
- splitApksCheckBox.setText(" Use --split_apks where possible");
+ splitApksCheckBox.setText("Use --split_apks where possible");
panel.add(
splitApksCheckBox,
new GridConstraints(
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 da33631..ff1ab76 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
@@ -16,6 +16,7 @@
package com.google.idea.blaze.android.run.binary.mobileinstall;
import com.android.ddmlib.IDevice;
+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;
@@ -29,10 +30,10 @@
import com.android.tools.idea.run.tasks.LaunchTasksProvider;
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.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.BlazeAndroidBinaryLaunchMethodsProvider.AndroidBinaryLaunchMethod;
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;
@@ -49,7 +50,6 @@
import java.util.Set;
import javax.annotation.Nullable;
import org.jetbrains.android.facet.AndroidFacet;
-import org.jetbrains.annotations.NotNull;
/** Run context for android_binary. */
public class BlazeAndroidBinaryMobileInstallRunContext implements BlazeAndroidRunContext {
@@ -70,7 +70,8 @@
ExecutionEnvironment env,
BlazeAndroidBinaryRunConfigurationState configState,
Label label,
- ImmutableList<String> buildFlags) {
+ ImmutableList<String> blazeFlags,
+ ImmutableList<String> exeFlags) {
this.project = project;
this.facet = facet;
this.runConfiguration = runConfiguration;
@@ -79,9 +80,14 @@
this.consoleProvider = new BlazeAndroidBinaryConsoleProvider(project);
this.buildStep =
new BlazeApkBuildStepMobileInstall(
- project, env, label, buildFlags, configState.useSplitApksIfPossible());
- this.applicationIdProvider =
- new BlazeAndroidBinaryApplicationIdProvider(project, buildStep.getDeployInfo());
+ project,
+ env,
+ label,
+ blazeFlags,
+ exeFlags,
+ configState.useSplitApksIfPossible(),
+ configState.getLaunchMethod().equals(AndroidBinaryLaunchMethod.MOBILE_INSTALL_V2));
+ this.applicationIdProvider = new BlazeAndroidBinaryApplicationIdProvider(buildStep);
}
@Override
@@ -93,11 +99,10 @@
public void augmentEnvironment(ExecutionEnvironment env) {}
@Override
- public void augmentLaunchOptions(@NotNull LaunchOptions.Builder options) {
+ public void augmentLaunchOptions(LaunchOptions.Builder options) {
options.setDeploy(false).setOpenLogcatAutomatically(true);
}
- @NotNull
@Override
public ConsoleProvider getConsoleProvider() {
return consoleProvider;
@@ -144,9 +149,12 @@
project,
launchOptions.isDebug(),
UserIdHelper.getFlagsFromUserId(userId));
-
- BlazeAndroidDeployInfo deployInfo =
- Futures.get(buildStep.getDeployInfo(), ExecutionException.class);
+ BlazeAndroidDeployInfo deployInfo;
+ try {
+ deployInfo = buildStep.getDeployInfo();
+ } catch (ApkProvisionException e) {
+ throw new ExecutionException(e);
+ }
return BlazeAndroidBinaryApplicationLaunchTaskProvider.getApplicationLaunchTask(
project,
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 582afea..6bec217 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
@@ -16,12 +16,12 @@
package com.google.idea.blaze.android.run.binary.mobileinstall;
import com.android.ddmlib.IDevice;
+import com.android.tools.idea.run.ApkProvisionException;
import com.android.tools.idea.run.DeviceFutures;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.util.concurrent.Futures;
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.deployinfo.BlazeAndroidDeployInfo;
import com.google.idea.blaze.android.run.deployinfo.BlazeApkDeployInfoProtoHelper;
@@ -59,21 +59,27 @@
private final Project project;
private final ExecutionEnvironment env;
private final Label label;
- private final ImmutableList<String> buildFlags;
+ private final ImmutableList<String> blazeFlags;
+ private final ImmutableList<String> exeFlags;
private final boolean useSplitApksIfPossible;
- private final SettableFuture<BlazeAndroidDeployInfo> deployInfoFuture = SettableFuture.create();
+ private final boolean mobileInstallV2;
+ private BlazeAndroidDeployInfo deployInfo = null;
public BlazeApkBuildStepMobileInstall(
Project project,
ExecutionEnvironment env,
Label label,
- ImmutableList<String> buildFlags,
- boolean useSplitApksIfPossible) {
+ ImmutableList<String> blazeFlags,
+ ImmutableList<String> exeFlags,
+ boolean useSplitApksIfPossible,
+ boolean mobileInstallV2) {
this.project = project;
this.env = env;
this.label = label;
- this.buildFlags = buildFlags;
+ this.blazeFlags = blazeFlags;
+ this.exeFlags = exeFlags;
this.useSplitApksIfPossible = useSplitApksIfPossible;
+ this.mobileInstallV2 = mobileInstallV2;
}
@Override
@@ -95,31 +101,50 @@
BlazeCommand.builder(
Blaze.getBuildSystemProvider(project).getBinaryPath(),
BlazeCommandName.MOBILE_INSTALL);
- command.addBlazeFlags(BlazeFlags.adbSerialFlags(device.getSerialNumber()));
+
+ if (mobileInstallV2) {
+ // Will become no-op once v2 is default.
+ command.addBlazeFlags("--mode=skylark");
+ }
+
+ if (mobileInstallV2) {
+ command.addExeFlags(BlazeFlags.DEVICE, device.getSerialNumber());
+ } else {
+ command.addBlazeFlags(
+ BlazeFlags.ADB_ARG + "-s ", BlazeFlags.ADB_ARG + device.getSerialNumber());
+ }
if (USE_SDK_ADB.getValue()) {
File adb = AndroidSdkUtils.getAdb(project);
if (adb != null) {
- command.addBlazeFlags(ImmutableList.of("--adb", adb.toString()));
+ if (mobileInstallV2) {
+ command.addExeFlags(BlazeFlags.ADB_PATH, adb.toString());
+ } else {
+ command.addBlazeFlags(BlazeFlags.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);
+ // These flags are obsolete in V2.
+ if (!mobileInstallV2) {
+ // 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);
BlazeApkDeployInfoProtoHelper deployInfoHelper =
- new BlazeApkDeployInfoProtoHelper(project, buildFlags);
+ new BlazeApkDeployInfoProtoHelper(project, blazeFlags);
BuildResultHelper buildResultHelper = deployInfoHelper.getBuildResultHelper();
command
.addTargets(label)
- .addBlazeFlags(buildFlags)
- .addBlazeFlags(buildResultHelper.getBuildFlags());
+ .addBlazeFlags(blazeFlags)
+ .addBlazeFlags(buildResultHelper.getBuildFlags())
+ .addExeFlags(exeFlags);
SaveUtil.saveAllFiles();
int retVal =
@@ -138,12 +163,10 @@
return;
}
- BlazeAndroidDeployInfo deployInfo = deployInfoHelper.readDeployInfo(context);
+ deployInfo = deployInfoHelper.readDeployInfo(context);
if (deployInfo == null) {
IssueOutput.error("Could not read apk deploy info from build").submit(context);
- return;
}
- deployInfoFuture.set(deployInfo);
}
};
@@ -163,8 +186,12 @@
return context.shouldContinue();
}
- public ListenableFuture<BlazeAndroidDeployInfo> getDeployInfo() {
- return deployInfoFuture;
+ @Override
+ public BlazeAndroidDeployInfo getDeployInfo() throws ApkProvisionException {
+ if (deployInfo != null) {
+ return deployInfo;
+ }
+ throw new ApkProvisionException("Failed to read APK deploy info");
}
@Nullable
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 98e2e87..bed1ef9 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
@@ -22,9 +22,11 @@
import com.google.idea.blaze.base.command.buildresult.BuildResultHelper;
import com.google.idea.blaze.base.command.info.BlazeInfo;
import com.google.idea.blaze.base.command.info.BlazeInfoRunner;
+import com.google.idea.blaze.base.model.BlazeProjectData;
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.sync.data.BlazeProjectDataManager;
import com.google.repackaged.devtools.build.lib.rules.android.deployinfo.AndroidDeployInfoOuterClass;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
@@ -49,8 +51,12 @@
this.project = project;
this.buildFlags = buildFlags;
this.workspaceRoot = WorkspaceRoot.fromProject(project);
+
+ BlazeProjectData projectData =
+ BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
this.buildResultHelper =
- BuildResultHelper.forFiles(fileName -> fileName.endsWith(".deployinfo.pb"));
+ BuildResultHelper.forFiles(
+ projectData.blazeVersionData, fileName -> fileName.endsWith(".deployinfo.pb"));
}
public BuildResultHelper getBuildResultHelper() {
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 bdcc5d6..3f1f644 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
@@ -21,30 +21,26 @@
import com.android.tools.idea.run.ApkProvisionException;
import com.android.tools.idea.run.ValidationError;
import com.google.common.collect.ImmutableList;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
import com.google.idea.blaze.android.run.runner.AaptUtil;
+import com.google.idea.blaze.android.run.runner.BlazeApkBuildStep;
import com.intellij.openapi.project.Project;
import java.io.File;
import java.util.Collection;
import java.util.List;
-import org.jetbrains.annotations.NotNull;
/** Apk provider from deploy info proto */
public class BlazeApkProvider implements ApkProvider {
private final Project project;
- private final ListenableFuture<BlazeAndroidDeployInfo> deployInfoFuture;
+ private final BlazeApkBuildStep buildStep;
- public BlazeApkProvider(
- Project project, ListenableFuture<BlazeAndroidDeployInfo> deployInfoFuture) {
+ public BlazeApkProvider(Project project, BlazeApkBuildStep buildStep) {
this.project = project;
- this.deployInfoFuture = deployInfoFuture;
+ this.buildStep = buildStep;
}
- @NotNull
@Override
- public Collection<ApkInfo> getApks(@NotNull IDevice device) throws ApkProvisionException {
- BlazeAndroidDeployInfo deployInfo = Futures.get(deployInfoFuture, ApkProvisionException.class);
+ public Collection<ApkInfo> getApks(IDevice device) throws ApkProvisionException {
+ BlazeAndroidDeployInfo deployInfo = buildStep.getDeployInfo();
ImmutableList.Builder<ApkInfo> apkInfos = ImmutableList.builder();
for (File apk : deployInfo.getApksToDeploy()) {
apkInfos.add(new ApkInfo(apk, manifestPackageForApk(apk)));
@@ -52,8 +48,7 @@
return apkInfos.build();
}
- @NotNull
- private String manifestPackageForApk(@NotNull final File apk) throws ApkProvisionException {
+ private String manifestPackageForApk(final File apk) throws ApkProvisionException {
try {
return AaptUtil.getApkManifestPackage(project, apk);
} catch (AaptUtil.AaptUtilException e) {
@@ -66,7 +61,6 @@
}
}
- @NotNull
@Override
public List<ValidationError> validate() {
return ImmutableList.of();
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 bf56acb..66fe3f0 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
@@ -42,6 +42,7 @@
import com.google.idea.blaze.base.scope.scopes.IdeaLogScope;
import com.google.idea.blaze.base.scope.scopes.IssuesScope;
import com.google.idea.blaze.base.settings.BlazeUserSettings;
+import com.google.idea.blaze.base.settings.BlazeUserSettings.BlazeConsolePopupBehavior;
import com.intellij.execution.DefaultExecutionResult;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.ExecutionResult;
@@ -190,7 +191,10 @@
@Override
public boolean executeBeforeRunTask(ExecutionEnvironment env) {
final Project project = env.getProject();
- boolean suppressConsole = BlazeUserSettings.getInstance().getSuppressConsoleForRunAction();
+ BlazeConsolePopupBehavior consolePopupBehavior =
+ BlazeUserSettings.getInstance().getSuppressConsoleForRunAction()
+ ? BlazeConsolePopupBehavior.NEVER
+ : BlazeConsolePopupBehavior.ALWAYS;
return Scope.root(
context -> {
context
@@ -198,7 +202,7 @@
.push(new ExperimentScope())
.push(
new BlazeConsoleScope.Builder(project)
- .setSuppressConsole(suppressConsole)
+ .setPopupBehavior(consolePopupBehavior)
.build())
.push(new IdeaLogScope());
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 b437f7f..74a0f24 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
@@ -15,14 +15,18 @@
*/
package com.google.idea.blaze.android.run.runner;
+import com.android.tools.idea.run.ApkProvisionException;
+import com.google.idea.blaze.android.run.deployinfo.BlazeAndroidDeployInfo;
import com.google.idea.blaze.base.scope.BlazeContext;
/** Builds the APK. */
public interface BlazeApkBuildStep {
/**
- * Builds an optionally installs the APK.
+ * Builds and optionally installs the APK.
*
* @return True to continue the launch.
*/
boolean build(BlazeContext context, BlazeAndroidDeviceSelector.DeviceSession deviceSession);
+
+ BlazeAndroidDeployInfo getDeployInfo() throws ApkProvisionException;
}
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 58daf4f..11c7f03 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
@@ -15,10 +15,10 @@
*/
package com.google.idea.blaze.android.run.runner;
+import com.android.tools.idea.run.ApkProvisionException;
import com.google.common.collect.ImmutableList;
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.deployinfo.BlazeAndroidDeployInfo;
import com.google.idea.blaze.android.run.deployinfo.BlazeApkDeployInfoProtoHelper;
import com.google.idea.blaze.base.async.executor.BlazeExecutor;
@@ -38,14 +38,13 @@
import com.intellij.execution.ExecutionException;
import com.intellij.openapi.project.Project;
import java.util.concurrent.CancellationException;
-import org.jetbrains.annotations.NotNull;
/** Builds the APK using normal blaze build. */
public class BlazeApkBuildStepNormalBuild implements BlazeApkBuildStep {
private final Project project;
private final Label label;
private final ImmutableList<String> buildFlags;
- private final SettableFuture<BlazeAndroidDeployInfo> deployInfoFuture = SettableFuture.create();
+ private BlazeAndroidDeployInfo deployInfo = null;
public BlazeApkBuildStepNormalBuild(
Project project, Label label, ImmutableList<String> buildFlags) {
@@ -60,7 +59,7 @@
final ScopedTask buildTask =
new ScopedTask(context) {
@Override
- protected void execute(@NotNull BlazeContext context) {
+ protected void execute(BlazeContext context) {
BlazeCommand.Builder command =
BlazeCommand.builder(
Blaze.getBuildSystemProvider(project).getBinaryPath(), BlazeCommandName.BUILD);
@@ -92,12 +91,10 @@
context.setHasError();
return;
}
- BlazeAndroidDeployInfo deployInfo = deployInfoHelper.readDeployInfo(context);
+ deployInfo = deployInfoHelper.readDeployInfo(context);
if (deployInfo == null) {
IssueOutput.error("Could not read apk deploy info from build").submit(context);
- return;
}
- deployInfoFuture.set(deployInfo);
}
};
@@ -117,7 +114,11 @@
return context.shouldContinue();
}
- public ListenableFuture<BlazeAndroidDeployInfo> getDeployInfo() {
- return deployInfoFuture;
+ @Override
+ public BlazeAndroidDeployInfo getDeployInfo() throws ApkProvisionException {
+ if (deployInfo != null) {
+ return deployInfo;
+ }
+ throw new ApkProvisionException("Failed to read APK deploy info");
}
}
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 92311c4..4915256 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
@@ -17,11 +17,11 @@
import com.android.tools.idea.run.ConsoleProvider;
import com.android.tools.idea.testartifacts.instrumented.AndroidTestConsoleProperties;
-import com.google.idea.blaze.base.run.smrunner.BlazeTestEventsHandler;
+import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
+import com.google.idea.blaze.base.run.smrunner.BlazeTestUiSession;
import com.google.idea.blaze.base.run.smrunner.SmRunnerUtils;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.Executor;
-import com.intellij.execution.configurations.RunConfiguration;
import com.intellij.execution.executors.DefaultDebugExecutor;
import com.intellij.execution.filters.TextConsoleBuilderFactory;
import com.intellij.execution.process.ProcessHandler;
@@ -35,39 +35,43 @@
/** Console provider for android_test */
class AndroidTestConsoleProvider implements ConsoleProvider {
private final Project project;
- private final RunConfiguration runConfiguration;
+ private final BlazeCommandRunConfiguration runConfiguration;
private final BlazeAndroidTestRunConfigurationState configState;
- @Nullable private final BlazeTestEventsHandler testEventsHandler;
+ @Nullable private final BlazeTestUiSession testUiSession;
AndroidTestConsoleProvider(
Project project,
- RunConfiguration runConfiguration,
+ BlazeCommandRunConfiguration runConfiguration,
BlazeAndroidTestRunConfigurationState configState,
- @Nullable BlazeTestEventsHandler testEventsHandler) {
+ @Nullable BlazeTestUiSession testUiSession) {
this.project = project;
this.runConfiguration = runConfiguration;
this.configState = configState;
- this.testEventsHandler = testEventsHandler;
+ this.testUiSession = testUiSession;
}
@Override
public ConsoleView createAndAttach(Disposable parent, ProcessHandler handler, Executor executor)
throws ExecutionException {
- if (!configState.isRunThroughBlaze()) {
- return getStockConsoleProvider().createAndAttach(parent, handler, executor);
+ switch (configState.getLaunchMethod()) {
+ case BLAZE_TEST:
+ ConsoleView console = createBlazeTestConsole(executor);
+ console.attachToProcess(handler);
+ return console;
+ case NON_BLAZE:
+ case MOBILE_INSTALL:
+ return getStockConsoleProvider().createAndAttach(parent, handler, executor);
}
- ConsoleView console = createBlazeTestConsole(executor);
- console.attachToProcess(handler);
- return console;
+ throw new AssertionError();
}
private ConsoleView createBlazeTestConsole(Executor executor) {
- if (testEventsHandler == null || isDebugging(executor)) {
+ if (testUiSession == null || isDebugging(executor)) {
// SM runner console not yet supported when debugging, because we're calling this once per
// test case (see ConnectBlazeTestDebuggerTask::setUpForReattachingDebugger)
return TextConsoleBuilderFactory.getInstance().createBuilder(project).getConsole();
}
- return SmRunnerUtils.getConsoleView(project, runConfiguration, executor, testEventsHandler);
+ return SmRunnerUtils.getConsoleView(project, runConfiguration, executor, testUiSession);
}
private static boolean isDebugging(Executor executor) {
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 71f37fd..9ad030c 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
@@ -18,31 +18,24 @@
import com.android.tools.idea.run.ApkProvisionException;
import com.android.tools.idea.run.ApplicationIdProvider;
import com.google.common.collect.Iterables;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
import com.google.idea.blaze.android.run.deployinfo.BlazeAndroidDeployInfo;
+import com.google.idea.blaze.android.run.runner.BlazeApkBuildStep;
import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
+import javax.annotation.Nullable;
import org.jetbrains.android.dom.manifest.Manifest;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
/** Application id provider for android_binary. */
public class BlazeAndroidTestApplicationIdProvider implements ApplicationIdProvider {
- private final Project project;
- private final ListenableFuture<BlazeAndroidDeployInfo> deployInfoFuture;
+ private final BlazeApkBuildStep buildStep;
- public BlazeAndroidTestApplicationIdProvider(
- Project project, ListenableFuture<BlazeAndroidDeployInfo> deployInfoFuture) {
- this.project = project;
- this.deployInfoFuture = deployInfoFuture;
+ BlazeAndroidTestApplicationIdProvider(BlazeApkBuildStep buildStep) {
+ this.buildStep = buildStep;
}
- @NotNull
@Override
public String getPackageName() throws ApkProvisionException {
- BlazeAndroidDeployInfo deployInfo = Futures.get(deployInfoFuture, ApkProvisionException.class);
+ BlazeAndroidDeployInfo deployInfo = buildStep.getDeployInfo();
Manifest manifest = Iterables.getFirst(deployInfo.getAdditionalMergedManifests(), null);
if (manifest == null) {
// The application may not have a separate package,
@@ -61,7 +54,7 @@
@Nullable
@Override
public String getTestPackageName() throws ApkProvisionException {
- BlazeAndroidDeployInfo deployInfo = Futures.get(deployInfoFuture, ApkProvisionException.class);
+ BlazeAndroidDeployInfo deployInfo = buildStep.getDeployInfo();
Manifest manifest = deployInfo.getMergedManifest();
if (manifest == null) {
throw new ApkProvisionException(
diff --git a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestLaunchMethodsProvider.java b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestLaunchMethodsProvider.java
new file mode 100644
index 0000000..5258c85
--- /dev/null
+++ b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestLaunchMethodsProvider.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.intellij.openapi.extensions.ExtensionPointName;
+import com.intellij.openapi.project.Project;
+import java.util.Arrays;
+import java.util.List;
+
+/** Provides a list of supported launch methods for android tests. */
+public interface BlazeAndroidTestLaunchMethodsProvider {
+ ExtensionPointName<BlazeAndroidTestLaunchMethodsProvider> EP_NAME =
+ ExtensionPointName.create("com.google.idea.blaze.AndroidTestLaunchMethodsProvider");
+
+ static AndroidTestLaunchMethodComboEntry[] getAllLaunchMethods(Project project) {
+ return Arrays.stream(EP_NAME.getExtensions())
+ .flatMap(extension -> extension.getLaunchMethods(project).stream())
+ .toArray(AndroidTestLaunchMethodComboEntry[]::new);
+ }
+
+ List<AndroidTestLaunchMethodComboEntry> getLaunchMethods(Project project);
+
+ /** All possible test launch methods. */
+ enum AndroidTestLaunchMethod {
+ NON_BLAZE,
+ BLAZE_TEST,
+ MOBILE_INSTALL,
+ }
+
+ /** Launch methods wrapped for display in a combo box. */
+ class AndroidTestLaunchMethodComboEntry {
+ final AndroidTestLaunchMethod launchMethod;
+ private final String description;
+
+ public AndroidTestLaunchMethodComboEntry(
+ AndroidTestLaunchMethod launchMethod, String description) {
+ this.launchMethod = launchMethod;
+ this.description = description;
+ }
+
+ @Override
+ public String toString() {
+ return description;
+ }
+ }
+}
diff --git a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestLaunchMethodsProviderImpl.java b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestLaunchMethodsProviderImpl.java
new file mode 100644
index 0000000..f60327b
--- /dev/null
+++ b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestLaunchMethodsProviderImpl.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.ImmutableList;
+import com.google.idea.blaze.base.settings.Blaze;
+import com.intellij.openapi.project.Project;
+import java.util.List;
+
+/** Provides a list of supported launch methods from bazel and blaze for android tests. */
+public class BlazeAndroidTestLaunchMethodsProviderImpl
+ implements BlazeAndroidTestLaunchMethodsProvider {
+ @Override
+ public List<AndroidTestLaunchMethodComboEntry> getLaunchMethods(Project project) {
+ String blaze = Blaze.buildSystemName(project);
+ return ImmutableList.of(
+ new AndroidTestLaunchMethodComboEntry(
+ AndroidTestLaunchMethod.NON_BLAZE, String.format("Run without using %s", blaze)),
+ new AndroidTestLaunchMethodComboEntry(
+ AndroidTestLaunchMethod.BLAZE_TEST,
+ String.format("Run with %s test", blaze.toLowerCase())));
+ }
+}
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
index 4b94b3c..adece56 100644
--- a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationHandler.java
+++ b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationHandler.java
@@ -24,6 +24,7 @@
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.command.BlazeCommandName;
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;
@@ -102,9 +103,14 @@
ProjectViewSet projectViewSet = ProjectViewManager.getInstance(project).getProjectViewSet();
BlazeAndroidRunConfigurationValidationUtil.validateExecution(module, facet, projectViewSet);
- ImmutableList<String> buildFlags =
- configState.getCommonState().getExpandedBuildFlags(project, projectViewSet);
- BlazeAndroidRunContext runContext = createRunContext(project, facet, environment, buildFlags);
+ ImmutableList<String> blazeFlags =
+ configState
+ .getCommonState()
+ .getExpandedBuildFlags(project, projectViewSet, BlazeCommandName.TEST);
+ ImmutableList<String> exeFlags =
+ ImmutableList.copyOf(configState.getCommonState().getExeFlagsState().getExpandedFlags());
+ BlazeAndroidRunContext runContext =
+ createRunContext(project, facet, environment, blazeFlags, exeFlags);
return new BlazeAndroidRunConfigurationRunner(
module,
@@ -118,9 +124,10 @@
Project project,
AndroidFacet facet,
ExecutionEnvironment env,
- ImmutableList<String> buildFlags) {
+ ImmutableList<String> blazeFlags,
+ ImmutableList<String> exeFlags) {
return new BlazeAndroidTestRunContext(
- project, facet, configuration, env, configState, getLabel(), buildFlags);
+ project, facet, configuration, env, configState, getLabel(), blazeFlags, exeFlags);
}
@Override
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 074b17b..e67388e 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
@@ -19,6 +19,7 @@
import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationCommonState;
+import com.google.idea.blaze.android.run.test.BlazeAndroidTestLaunchMethodsProvider.AndroidTestLaunchMethod;
import com.google.idea.blaze.base.run.state.RunConfigurationState;
import com.google.idea.blaze.base.run.state.RunConfigurationStateEditor;
import com.intellij.openapi.project.Project;
@@ -34,7 +35,8 @@
/** State specific for the android test configuration. */
final class BlazeAndroidTestRunConfigurationState implements RunConfigurationState {
- private static final String RUN_THROUGH_BLAZE_ATTR = "blaze-run-through-blaze";
+ private static final String LAUNCH_METHOD_ATTR = "launch-method";
+ @Deprecated private static final String RUN_THROUGH_BLAZE_ATTR = "blaze-run-through-blaze";
public static final int TEST_ALL_IN_MODULE = 0;
public static final int TEST_ALL_IN_PACKAGE = 1;
@@ -60,8 +62,7 @@
private String packageName = "";
private String extraOptions = "";
- // Whether to delegate to 'blaze test'.
- private boolean runThroughBlaze;
+ private AndroidTestLaunchMethod launchMethod = AndroidTestLaunchMethod.NON_BLAZE;
private final BlazeAndroidRunConfigurationCommonState commonState;
@@ -74,12 +75,12 @@
}
@Contract(pure = true)
- boolean isRunThroughBlaze() {
- return runThroughBlaze;
+ AndroidTestLaunchMethod getLaunchMethod() {
+ return launchMethod;
}
- void setRunThroughBlaze(boolean runThroughBlaze) {
- this.runThroughBlaze = runThroughBlaze;
+ void setLaunchMethod(AndroidTestLaunchMethod launchMethod) {
+ this.launchMethod = launchMethod;
}
public int getTestingType() {
@@ -152,7 +153,17 @@
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));
+
+ String launchMethodAttribute = element.getAttributeValue(LAUNCH_METHOD_ATTR);
+ if (launchMethodAttribute != null) {
+ launchMethod = AndroidTestLaunchMethod.valueOf(launchMethodAttribute);
+ } else {
+ if (Boolean.parseBoolean(element.getAttributeValue(RUN_THROUGH_BLAZE_ATTR))) {
+ launchMethod = AndroidTestLaunchMethod.BLAZE_TEST;
+ } else {
+ launchMethod = AndroidTestLaunchMethod.NON_BLAZE;
+ }
+ }
for (Map.Entry<String, String> entry : getLegacyValues(element).entrySet()) {
String value = entry.getValue();
@@ -187,7 +198,7 @@
public void writeExternal(Element element) throws WriteExternalException {
commonState.writeExternal(element);
- element.setAttribute(RUN_THROUGH_BLAZE_ATTR, Boolean.toString(runThroughBlaze));
+ element.setAttribute(LAUNCH_METHOD_ATTR, launchMethod.name());
element.setAttribute(TESTING_TYPE, Integer.toString(testingType));
element.setAttribute(INSTRUMENTATION_RUNNER_CLASS, instrumentationRunnerClass);
element.setAttribute(METHOD_NAME, methodName);
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 5e5e6c8..0bf5c63 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
@@ -21,9 +21,9 @@
import static com.android.tools.idea.testartifacts.instrumented.AndroidTestRunConfiguration.TEST_CLASS;
import static com.android.tools.idea.testartifacts.instrumented.AndroidTestRunConfiguration.TEST_METHOD;
+import com.google.idea.blaze.android.run.test.BlazeAndroidTestLaunchMethodsProvider.AndroidTestLaunchMethodComboEntry;
import com.google.idea.blaze.base.run.state.RunConfigurationState;
import com.google.idea.blaze.base.run.state.RunConfigurationStateEditor;
-import com.google.idea.blaze.base.settings.Blaze;
import com.google.idea.blaze.base.ui.UiUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.LabeledComponent;
@@ -38,7 +38,7 @@
import java.util.ResourceBundle;
import javax.swing.AbstractButton;
import javax.swing.ButtonGroup;
-import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
@@ -61,7 +61,7 @@
private JPanel panel;
private LabeledComponent<EditorTextField> runnerComponent;
private JBLabel labelTest;
- private JCheckBox runThroughBlazeTestCheckBox;
+ private JComboBox<AndroidTestLaunchMethodComboEntry> launchMethodComboBox;
private final JRadioButton[] testingType2RadioButton = new JRadioButton[4];
private boolean componentEnabled = true;
@@ -351,13 +351,10 @@
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)));
+ launchMethodComboBox =
+ new JComboBox<>(BlazeAndroidTestLaunchMethodsProvider.getAllLaunchMethods(project));
panel.add(
- runThroughBlazeTestCheckBox,
+ launchMethodComboBox,
new GridConstraints(
0,
0,
@@ -452,7 +449,8 @@
(BlazeAndroidTestRunConfigurationState) genericState;
commonStateEditor.applyEditorTo(state.getCommonState());
- state.setRunThroughBlaze(runThroughBlazeTestCheckBox.isSelected());
+ state.setLaunchMethod(
+ ((AndroidTestLaunchMethodComboEntry) launchMethodComboBox.getSelectedItem()).launchMethod);
state.setTestingType(getTestingType());
state.setClassName(classComponent.getComponent().getText());
@@ -467,8 +465,12 @@
(BlazeAndroidTestRunConfigurationState) genericState;
commonStateEditor.resetEditorFrom(state.getCommonState());
- runThroughBlazeTestCheckBox.setSelected(state.isRunThroughBlaze());
-
+ for (int i = 0; i < launchMethodComboBox.getItemCount(); ++i) {
+ if (launchMethodComboBox.getItemAt(i).launchMethod.equals(state.getLaunchMethod())) {
+ launchMethodComboBox.setSelectedIndex(i);
+ break;
+ }
+ }
updateButtonsAndLabelComponents(state.getTestingType());
packageComponent.getComponent().setText(state.getPackageName());
classComponent.getComponent().setText(state.getClassName());
@@ -498,7 +500,7 @@
methodComponent.setEnabled(componentEnabled);
runnerComponent.setEnabled(componentEnabled);
labelTest.setEnabled(componentEnabled);
- runThroughBlazeTestCheckBox.setEnabled(componentEnabled);
+ launchMethodComboBox.setEnabled(componentEnabled);
for (JComponent button : testingType2RadioButton) {
if (button != null) {
button.setEnabled(componentEnabled);
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 9962acf..b8d5966 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
@@ -32,7 +32,7 @@
import com.android.tools.idea.run.util.ProcessHandlerLaunchStatus;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
-import com.google.common.util.concurrent.Futures;
+import com.google.idea.blaze.android.run.binary.mobileinstall.BlazeApkBuildStepMobileInstall;
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.BlazeAndroidDeviceSelector;
@@ -41,9 +41,12 @@
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.android.run.test.BlazeAndroidTestLaunchMethodsProvider.AndroidTestLaunchMethod;
import com.google.idea.blaze.base.model.primitives.Label;
import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
-import com.google.idea.blaze.base.run.smrunner.BlazeTestEventsHandler;
+import com.google.idea.blaze.base.run.smrunner.BlazeTestUiSession;
+import com.google.idea.blaze.base.run.smrunner.TestUiSessionProvider;
+import com.google.idea.blaze.base.settings.Blaze;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.Executor;
import com.intellij.execution.executors.DefaultDebugExecutor;
@@ -54,7 +57,6 @@
import java.util.Set;
import javax.annotation.Nullable;
import org.jetbrains.android.facet.AndroidFacet;
-import org.jetbrains.annotations.NotNull;
/** Run context for android_test. */
class BlazeAndroidTestRunContext implements BlazeAndroidRunContext {
@@ -65,47 +67,55 @@
private final ExecutionEnvironment env;
private final BlazeAndroidTestRunConfigurationState configState;
private final Label label;
- private final ImmutableList<String> buildFlags;
+ private final ImmutableList<String> blazeFlags;
private final List<Runnable> launchTaskCompleteListeners = Lists.newArrayList();
private final ConsoleProvider consoleProvider;
- private final BlazeApkBuildStepNormalBuild buildStep;
+ private final BlazeApkBuildStep buildStep;
private final ApplicationIdProvider applicationIdProvider;
private final ApkProvider apkProvider;
- public BlazeAndroidTestRunContext(
+ BlazeAndroidTestRunContext(
Project project,
AndroidFacet facet,
BlazeCommandRunConfiguration runConfiguration,
ExecutionEnvironment env,
BlazeAndroidTestRunConfigurationState configState,
Label label,
- ImmutableList<String> buildFlags) {
+ ImmutableList<String> blazeFlags,
+ ImmutableList<String> exeFlags) {
this.project = project;
this.facet = facet;
this.runConfiguration = runConfiguration;
this.env = env;
this.label = label;
this.configState = configState;
- this.buildStep = new BlazeApkBuildStepNormalBuild(project, label, buildFlags);
- this.applicationIdProvider =
- new BlazeAndroidTestApplicationIdProvider(project, buildStep.getDeployInfo());
- this.apkProvider = new BlazeApkProvider(project, buildStep.getDeployInfo());
+ this.buildStep =
+ configState.getLaunchMethod().equals(AndroidTestLaunchMethod.MOBILE_INSTALL)
+ ? new BlazeApkBuildStepMobileInstall(
+ project, env, label, blazeFlags, exeFlags, false, true)
+ : new BlazeApkBuildStepNormalBuild(project, label, blazeFlags);
+ this.applicationIdProvider = new BlazeAndroidTestApplicationIdProvider(buildStep);
+ this.apkProvider = new BlazeApkProvider(project, buildStep);
- BlazeTestEventsHandler testEventsHandler = null;
- if (!isDebugging(env.getExecutor())) {
- testEventsHandler =
- BlazeTestEventsHandler.getHandlerForTarget(project, runConfiguration.getTarget());
- assert (testEventsHandler != null);
- this.buildFlags =
+ BlazeTestUiSession testUiSession =
+ canUseTestUi(env.getExecutor())
+ ? TestUiSessionProvider.createForTarget(project, runConfiguration.getTarget())
+ : null;
+ if (testUiSession != null) {
+ this.blazeFlags =
ImmutableList.<String>builder()
- .addAll(BlazeTestEventsHandler.getBlazeFlags(project))
- .addAll(buildFlags)
+ .addAll(testUiSession.getBlazeFlags())
+ .addAll(blazeFlags)
.build();
} else {
- this.buildFlags = buildFlags;
+ this.blazeFlags = blazeFlags;
}
this.consoleProvider =
- new AndroidTestConsoleProvider(project, runConfiguration, configState, testEventsHandler);
+ new AndroidTestConsoleProvider(project, runConfiguration, configState, testUiSession);
+ }
+
+ private static boolean canUseTestUi(Executor executor) {
+ return !isDebugging(executor);
}
private static boolean isDebugging(Executor executor) {
@@ -122,7 +132,7 @@
@Override
public void augmentLaunchOptions(LaunchOptions.Builder options) {
- options.setDeploy(!configState.isRunThroughBlaze());
+ options.setDeploy(!configState.getLaunchMethod().equals(AndroidTestLaunchMethod.BLAZE_TEST));
}
@Override
@@ -160,25 +170,35 @@
@Override
public ImmutableList<LaunchTask> getDeployTasks(IDevice device, LaunchOptions launchOptions)
throws ExecutionException {
- if (!configState.isRunThroughBlaze()) {
- BlazeAndroidDeployInfo deployInfo =
- Futures.get(buildStep.getDeployInfo(), ExecutionException.class);
- if (!deployInfo.getDataToDeploy().isEmpty()) {
- throw new ExecutionException(
- "This test target has data dependencies (defined in the 'data' attribute).\n"
- + "These can only be installed if the configuration is run through 'blaze test'.\n"
- + "Check the \"Run through 'blaze test'\" checkbox on your "
- + "run configuration and try again.");
- }
+ switch (configState.getLaunchMethod()) {
+ case NON_BLAZE:
+ BlazeAndroidDeployInfo deployInfo;
+ try {
+ deployInfo = buildStep.getDeployInfo();
+ } catch (ApkProvisionException e) {
+ throw new ExecutionException(e);
+ }
+ if (!deployInfo.getDataToDeploy().isEmpty()) {
+ throw new ExecutionException(
+ String.format(
+ "This test target has data dependencies (defined in the 'data' attribute).\n"
+ + "These can only be installed if the configuration is run through blaze.\n"
+ + "Choose \"Run with %1$s test\" on your run configuration and try again.",
+ Blaze.getBuildSystem(project).getLowerCaseName()));
+ }
+ // fall through
+ case BLAZE_TEST:
+ Collection<ApkInfo> apks;
+ try {
+ apks = apkProvider.getApks(device);
+ } catch (ApkProvisionException e) {
+ throw new ExecutionException(e);
+ }
+ return ImmutableList.of(new DeployApkTask(project, launchOptions, apks));
+ case MOBILE_INSTALL:
+ return ImmutableList.of();
}
-
- Collection<ApkInfo> apks;
- try {
- apks = apkProvider.getApks(device);
- } catch (ApkProvisionException e) {
- throw new ExecutionException(e);
- }
- return ImmutableList.of(new DeployApkTask(project, launchOptions, apks));
+ throw new AssertionError();
}
@Nullable
@@ -190,27 +210,35 @@
AndroidDebuggerState androidDebuggerState,
ProcessHandlerLaunchStatus processHandlerLaunchStatus)
throws ExecutionException {
- if (configState.isRunThroughBlaze()) {
- return new BlazeAndroidTestLaunchTask(
- project,
- label,
- buildFlags,
- new BlazeAndroidTestFilter(
- configState.getTestingType(),
- configState.getClassName(),
- configState.getMethodName(),
- configState.getPackageName()),
- this,
- launchOptions.isDebug());
+ switch (configState.getLaunchMethod()) {
+ case BLAZE_TEST:
+ return new BlazeAndroidTestLaunchTask(
+ project,
+ label,
+ blazeFlags,
+ new BlazeAndroidTestFilter(
+ configState.getTestingType(),
+ configState.getClassName(),
+ configState.getMethodName(),
+ configState.getPackageName()),
+ this,
+ launchOptions.isDebug());
+ case NON_BLAZE:
+ case MOBILE_INSTALL:
+ BlazeAndroidDeployInfo deployInfo;
+ try {
+ deployInfo = buildStep.getDeployInfo();
+ } catch (ApkProvisionException e) {
+ throw new ExecutionException(e);
+ }
+ return StockAndroidTestLaunchTask.getStockTestLaunchTask(
+ configState,
+ applicationIdProvider,
+ launchOptions.isDebug(),
+ deployInfo,
+ processHandlerLaunchStatus);
}
- BlazeAndroidDeployInfo deployInfo =
- Futures.get(buildStep.getDeployInfo(), ExecutionException.class);
- return StockAndroidTestLaunchTask.getStockTestLaunchTask(
- configState,
- applicationIdProvider,
- launchOptions.isDebug(),
- deployInfo,
- processHandlerLaunchStatus);
+ throw new AssertionError();
}
@Override
@@ -218,21 +246,25 @@
public DebugConnectorTask getDebuggerTask(
AndroidDebugger androidDebugger,
AndroidDebuggerState androidDebuggerState,
- @NotNull Set<String> packageIds,
+ Set<String> packageIds,
boolean monitorRemoteProcess)
throws ExecutionException {
- if (configState.isRunThroughBlaze()) {
- return new ConnectBlazeTestDebuggerTask(
- env.getProject(), androidDebugger, packageIds, applicationIdProvider, this);
+ switch (configState.getLaunchMethod()) {
+ case BLAZE_TEST:
+ return new ConnectBlazeTestDebuggerTask(
+ env.getProject(), androidDebugger, packageIds, applicationIdProvider, this);
+ case NON_BLAZE:
+ case MOBILE_INSTALL:
+ return androidDebugger.getConnectDebuggerTask(
+ env,
+ null,
+ packageIds,
+ facet,
+ androidDebuggerState,
+ runConfiguration.getType().getId(),
+ monitorRemoteProcess);
}
- return androidDebugger.getConnectDebuggerTask(
- env,
- null,
- packageIds,
- facet,
- androidDebuggerState,
- runConfiguration.getType().getId(),
- monitorRemoteProcess);
+ throw new AssertionError();
}
void onLaunchTaskComplete() {
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 a10773e..01f2b86 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
@@ -25,6 +25,7 @@
import com.android.tools.idea.testartifacts.instrumented.AndroidTestListener;
import com.google.common.collect.ImmutableList;
import com.google.idea.blaze.android.run.deployinfo.BlazeAndroidDeployInfo;
+import com.google.idea.blaze.android.run.test.BlazeAndroidTestLaunchMethodsProvider.AndroidTestLaunchMethod;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Computable;
@@ -37,6 +38,10 @@
final class StockAndroidTestLaunchTask implements LaunchTask {
private static final Logger LOG = Logger.getInstance(StockAndroidTestLaunchTask.class);
+ private static final String TEST_FILE_ARG = "testFile";
+ private static final String TEST_FILE_LOCATION_FORMAT =
+ "/data/local/tmp/deployment/%s/test/all_tests.txt";
+
private final BlazeAndroidTestRunConfigurationState configState;
private final String instrumentationTestRunner;
private final String testApplicationId;
@@ -165,6 +170,10 @@
final RemoteAndroidTestRunner runner =
new RemoteAndroidTestRunner(testApplicationId, instrumentationTestRunner, device);
+ if (configState.getLaunchMethod().equals(AndroidTestLaunchMethod.MOBILE_INSTALL)) {
+ runner.addInstrumentationArg(
+ TEST_FILE_ARG, String.format(TEST_FILE_LOCATION_FORMAT, testApplicationId));
+ }
switch (configState.getTestingType()) {
case BlazeAndroidTestRunConfigurationState.TEST_ALL_IN_MODULE:
break;
@@ -177,6 +186,9 @@
case BlazeAndroidTestRunConfigurationState.TEST_METHOD:
runner.setMethodName(configState.getClassName(), configState.getMethodName());
break;
+ default:
+ LOG.error(String.format("Unrecognized testing type: %d", configState.getTestingType()));
+ return false;
}
runner.setDebug(waitForDebugger);
runner.setRunOptions(configState.getExtraOptions());
diff --git a/aswb/src/com/google/idea/blaze/android/run/test/smrunner/BlazeAndroidTestEventsHandler.java b/aswb/src/com/google/idea/blaze/android/run/test/smrunner/BlazeAndroidTestEventsHandler.java
index 947a070..1a5bc9c 100644
--- a/aswb/src/com/google/idea/blaze/android/run/test/smrunner/BlazeAndroidTestEventsHandler.java
+++ b/aswb/src/com/google/idea/blaze/android/run/test/smrunner/BlazeAndroidTestEventsHandler.java
@@ -30,7 +30,6 @@
import com.intellij.psi.PsiMethod;
import com.intellij.util.io.URLUtil;
import java.util.Collection;
-import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -38,11 +37,11 @@
import javax.annotation.Nullable;
/** Provides java-specific methods needed by the SM-runner test UI. */
-public class BlazeAndroidTestEventsHandler extends BlazeTestEventsHandler {
+public class BlazeAndroidTestEventsHandler implements BlazeTestEventsHandler {
@Override
- protected EnumSet<Kind> handledKinds() {
- return EnumSet.of(Kind.ANDROID_TEST);
+ public boolean handlesKind(@Nullable Kind kind) {
+ return kind == Kind.ANDROID_TEST;
}
@Override
diff --git a/aswb/src/com/google/idea/blaze/android/sync/AndroidPrefetchFileSource.java b/aswb/src/com/google/idea/blaze/android/sync/AndroidPrefetchFileSource.java
index 821f098..930b5e3 100644
--- a/aswb/src/com/google/idea/blaze/android/sync/AndroidPrefetchFileSource.java
+++ b/aswb/src/com/google/idea/blaze/android/sync/AndroidPrefetchFileSource.java
@@ -20,10 +20,10 @@
import com.google.idea.blaze.base.model.BlazeProjectData;
import com.google.idea.blaze.base.prefetch.PrefetchFileSource;
import com.google.idea.blaze.base.projectview.ProjectViewSet;
+import com.google.idea.blaze.base.sync.projectview.ImportRoots;
import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
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. */
@@ -32,8 +32,9 @@
public void addFilesToPrefetch(
Project project,
ProjectViewSet projectViewSet,
+ ImportRoots importRoots,
BlazeProjectData blazeProjectData,
- Collection<File> files) {
+ Set<File> files) {
BlazeAndroidSyncData syncData = blazeProjectData.syncState.get(BlazeAndroidSyncData.class);
if (syncData == null) {
return;
@@ -46,7 +47,7 @@
}
@Override
- public Set<String> prefetchSrcFileExtensions() {
+ public Set<String> prefetchFileExtensions() {
return ImmutableSet.of("xml");
}
}
diff --git a/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidLibrarySource.java b/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidLibrarySource.java
index 73bb993..03f8bf0 100644
--- a/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidLibrarySource.java
+++ b/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidLibrarySource.java
@@ -17,10 +17,12 @@
import com.google.common.collect.ImmutableList;
import com.google.idea.blaze.android.sync.model.BlazeAndroidSyncData;
+import com.google.idea.blaze.base.ideinfo.LibraryArtifact;
import com.google.idea.blaze.base.model.BlazeLibrary;
import com.google.idea.blaze.base.model.BlazeProjectData;
import com.google.idea.blaze.base.sync.libraries.LibrarySource;
-import java.util.Collection;
+import com.google.idea.blaze.java.sync.model.BlazeJarLibrary;
+import java.util.List;
class BlazeAndroidLibrarySource extends LibrarySource.Adapter {
private final BlazeProjectData blazeProjectData;
@@ -30,7 +32,7 @@
}
@Override
- public Collection<BlazeLibrary> getLibraries() {
+ public List<BlazeLibrary> getLibraries() {
BlazeAndroidSyncData syncData = blazeProjectData.syncState.get(BlazeAndroidSyncData.class);
if (syncData == null) {
return ImmutableList.of();
@@ -39,6 +41,10 @@
if (syncData.importResult.resourceLibrary != null) {
libraries.add(syncData.importResult.resourceLibrary);
}
+ if (syncData.importResult.javacJar != null) {
+ libraries.add(
+ new BlazeJarLibrary(new LibraryArtifact(null, syncData.importResult.javacJar, null)));
+ }
return libraries.build();
}
}
diff --git a/aswb/src/com/google/idea/blaze/android/sync/BlazeNdkDependencySyncPlugin.java b/aswb/src/com/google/idea/blaze/android/sync/BlazeNdkDependencySyncPlugin.java
new file mode 100644
index 0000000..0177a1e
--- /dev/null
+++ b/aswb/src/com/google/idea/blaze/android/sync/BlazeNdkDependencySyncPlugin.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.annotations.VisibleForTesting;
+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.plugin.PluginUtils;
+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.sdkcompat.android.sync.BlazeNdkDependencySyncPluginCompat;
+import com.intellij.openapi.project.Project;
+import java.util.stream.Collectors;
+
+/**
+ * Returns an error during sync (with quick-fix) if NDK support is requested, but the required
+ * plugin dependencies aren't enabled.
+ */
+public final class BlazeNdkDependencySyncPlugin extends BlazeSyncPlugin.Adapter {
+
+ private static class PluginNameAndId {
+ final String name;
+ final String id;
+
+ PluginNameAndId(String pluginName, String pluginId) {
+ this.name = pluginName;
+ this.id = pluginId;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+ }
+
+ private static final ImmutableList<PluginNameAndId> REQUIRED_PLUGINS =
+ ImmutableList.copyOf(
+ BlazeNdkDependencySyncPluginCompat.REQUIRED_PLUGINS
+ .entrySet()
+ .stream()
+ .map(e -> new PluginNameAndId(e.getKey(), e.getValue()))
+ .collect(Collectors.toList()));
+
+ /** Returns the IDs of the plugins required for NDK support. */
+ @VisibleForTesting
+ public static ImmutableList<String> getPluginsRequiredForNdkSupport() {
+ return ImmutableList.copyOf(
+ REQUIRED_PLUGINS.stream().map(plugin -> plugin.id).collect(Collectors.toList()));
+ }
+
+ @Override
+ public boolean validate(
+ Project project, BlazeContext context, BlazeProjectData blazeProjectData) {
+ if (!blazeProjectData.workspaceLanguageSettings.isLanguageActive(LanguageClass.C)) {
+ return true;
+ }
+ boolean missingPlugin = false;
+ for (PluginNameAndId plugin : REQUIRED_PLUGINS) {
+ if (!PluginUtils.isPluginEnabled(plugin.id)) {
+ missingPlugin = true;
+ notifyMissingPlugin(context, plugin);
+ }
+ }
+ return !missingPlugin;
+ }
+
+ private static void notifyMissingPlugin(BlazeContext context, PluginNameAndId plugin) {
+ String msg =
+ String.format(
+ "Plugin '%s' required for NDK support isn't enabled.\n"
+ + "Click here to install/enable it, then restart the IDE",
+ plugin.name);
+ IssueOutput.error(msg)
+ .navigatable(PluginUtils.installOrEnablePluginNavigable(plugin.id))
+ .submit(context);
+ }
+}
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 3cb5351..f9228c6 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
@@ -33,6 +33,7 @@
import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
import com.google.idea.blaze.base.ideinfo.TargetKey;
import com.google.idea.blaze.base.ideinfo.TargetMap;
+import com.google.idea.blaze.base.model.primitives.Kind;
import com.google.idea.blaze.base.model.primitives.LanguageClass;
import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
import com.google.idea.blaze.base.projectview.ProjectViewSet;
@@ -45,6 +46,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -88,7 +90,7 @@
targetMap
.targets()
.stream()
- .filter(target -> target.kind.getLanguageClass() == LanguageClass.ANDROID)
+ .filter(target -> target.kind.languageClass == LanguageClass.ANDROID)
.filter(target -> target.androidIdeInfo != null)
.filter(importFilter::isSourceTarget)
.filter(target -> !importFilter.excludeTarget(target))
@@ -114,7 +116,20 @@
buildAndroidResourceModules(workspaceBuilder);
BlazeResourceLibrary resourceLibrary = createResourceLibrary(androidResourceModules);
- return new BlazeAndroidImportResult(androidResourceModules, resourceLibrary);
+ return new BlazeAndroidImportResult(
+ androidResourceModules, resourceLibrary, getJavacJar(targetMap.targets()));
+ }
+
+ private static ArtifactLocation getJavacJar(Collection<TargetIdeInfo> targets) {
+ return targets
+ .stream()
+ .filter(target -> target.kind == Kind.JAVA_TOOLCHAIN)
+ .map(
+ target ->
+ target.javaToolchainIdeInfo != null ? target.javaToolchainIdeInfo.javacJar : null)
+ .filter(Objects::nonNull)
+ .findFirst()
+ .orElse(null);
}
private void addSourceTarget(
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 b4750a2..0ac6cbd 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,6 +16,7 @@
package com.google.idea.blaze.android.sync.model;
import com.google.common.collect.ImmutableCollection;
+import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
import java.io.Serializable;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
@@ -23,15 +24,18 @@
/** The result of a blaze import operation. */
@Immutable
public class BlazeAndroidImportResult implements Serializable {
- private static final long serialVersionUID = 3L;
+ private static final long serialVersionUID = 4L;
public final ImmutableCollection<AndroidResourceModule> androidResourceModules;
@Nullable public final BlazeResourceLibrary resourceLibrary;
+ @Nullable public final ArtifactLocation javacJar;
public BlazeAndroidImportResult(
ImmutableCollection<AndroidResourceModule> androidResourceModules,
- @Nullable BlazeResourceLibrary resourceLibrary) {
+ @Nullable BlazeResourceLibrary resourceLibrary,
+ @Nullable ArtifactLocation javacJar) {
this.androidResourceModules = androidResourceModules;
this.resourceLibrary = resourceLibrary;
+ this.javacJar = javacJar;
}
}
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 b89365f..79cd572 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
@@ -17,28 +17,36 @@
import com.android.builder.model.SourceProvider;
import com.android.sdklib.AndroidVersion;
-import com.android.tools.idea.model.AndroidModel;
import com.android.tools.idea.model.ClassJarProvider;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import com.google.idea.blaze.android.manifest.ManifestParser;
+import com.google.idea.blaze.base.actions.BlazeBuildService;
+import com.google.idea.sdkcompat.android.model.AndroidModelAdapter;
import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
+import com.intellij.openapi.vfs.JarFileSystem;
import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.JavaPsiFacade;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.search.GlobalSearchScope;
import java.io.File;
import java.util.List;
import java.util.Set;
+import javax.annotation.Nullable;
import org.jetbrains.android.dom.manifest.Manifest;
-import org.jetbrains.annotations.Nullable;
/**
* Contains Android-Blaze related state necessary for configuring an IDEA project based on a
* user-selected build variant.
*/
-public class BlazeAndroidModel implements AndroidModel {
+public class BlazeAndroidModel extends AndroidModelAdapter {
private Project project;
private final File rootDirPath;
private final SourceProvider sourceProvider;
@@ -172,9 +180,58 @@
}
@Override
- @Nullable
- public Long getLastBuildTimestamp(Project project) {
- // TODO(jvoung): Coordinate with blaze build actions to be able determine last build time.
- return null;
+ public boolean isClassFileOutOfDate(Module module, String fqcn, VirtualFile classFile) {
+ VirtualFile sourceFile =
+ ApplicationManager.getApplication()
+ .runReadAction(
+ (Computable<VirtualFile>)
+ () -> {
+ PsiClass psiClass =
+ JavaPsiFacade.getInstance(project)
+ .findClass(fqcn, GlobalSearchScope.projectScope(project));
+ if (psiClass == null) {
+ return null;
+ }
+ PsiFile psiFile = psiClass.getContainingFile();
+ if (psiFile == null) {
+ return null;
+ }
+ return psiFile.getVirtualFile();
+ });
+ if (sourceFile == null) {
+ return false;
+ }
+
+ // Edited but not yet saved?
+ if (FileDocumentManager.getInstance().isFileModified(sourceFile)) {
+ return true;
+ }
+
+ long sourceTimeStamp = sourceFile.getTimeStamp();
+ long buildTimeStamp = classFile.getTimeStamp();
+
+ if (classFile.getFileSystem() instanceof JarFileSystem) {
+ JarFileSystem jarFileSystem = (JarFileSystem) classFile.getFileSystem();
+ VirtualFile jarFile = jarFileSystem.getVirtualFileForJar(classFile);
+ if (jarFile != null) {
+ if (jarFile.getFileSystem() instanceof LocalFileSystem) {
+ // The virtual file timestamp could be stale since we don't watch this file.
+ buildTimeStamp = VfsUtilCore.virtualToIoFile(jarFile).lastModified();
+ } else {
+ buildTimeStamp = jarFile.getTimeStamp();
+ }
+ }
+ }
+
+ if (sourceTimeStamp > buildTimeStamp) {
+ // It's possible that the source file's timestamp has been updated, but the content remains
+ // same. In this case, blaze will not try to rebuild the jar, we have to also check whether
+ // the user recently clicked the build button. So they can at least manually get rid of the
+ // error.
+ Long projectBuildTimeStamp = BlazeBuildService.getLastBuildTimeStamp(project);
+ return projectBuildTimeStamp == null || sourceTimeStamp > projectBuildTimeStamp;
+ }
+
+ return false;
}
}
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 9641ced..cbad879 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
@@ -32,6 +32,7 @@
import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
import com.google.idea.blaze.base.targetmaps.TransitiveDependencyMap;
+import com.google.idea.sdkcompat.android.res.AppResourceRepositoryAdapter;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.module.Module;
@@ -44,20 +45,21 @@
import java.io.File;
import java.util.Collection;
import java.util.List;
+import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
-import org.jetbrains.annotations.Nullable;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javax.annotation.Nullable;
/** Collects class jars from the user's build. */
public class BlazeClassJarProvider extends ClassJarProvider {
private final Project project;
- private AtomicBoolean pendingModuleJarsRefresh;
- private AtomicBoolean pendingDependencyJarsRefresh;
+ private final AtomicBoolean pendingJarsRefresh;
public BlazeClassJarProvider(final Project project) {
this.project = project;
- this.pendingModuleJarsRefresh = new AtomicBoolean(false);
- this.pendingDependencyJarsRefresh = new AtomicBoolean(false);
+ this.pendingJarsRefresh = new AtomicBoolean(false);
}
@Override
@@ -69,6 +71,7 @@
return null;
}
+ TargetMap targetMap = blazeProjectData.targetMap;
ArtifactLocationDecoder decoder = blazeProjectData.artifactLocationDecoder;
AndroidResourceModuleRegistry registry = AndroidResourceModuleRegistry.getInstance(project);
TargetIdeInfo target = blazeProjectData.targetMap.get(registry.getTargetKey(module));
@@ -84,9 +87,19 @@
String classNamePath = className.replace('.', File.separatorChar) + SdkConstants.DOT_CLASS;
+ List<LibraryArtifact> jarsToSearch = Lists.newArrayList(target.javaIdeInfo.jars);
+ jarsToSearch.addAll(
+ TransitiveDependencyMap.getInstance(project)
+ .getTransitiveDependencies(target.key)
+ .stream()
+ .map(targetMap::get)
+ .filter(Objects::nonNull)
+ .flatMap(BlazeClassJarProvider::getNonResourceJars)
+ .collect(Collectors.toList()));
+
List<File> missingClassJars = Lists.newArrayList();
- for (LibraryArtifact jar : target.javaIdeInfo.jars) {
- if (jar.classJar == null) {
+ for (LibraryArtifact jar : jarsToSearch) {
+ if (jar.classJar == null || jar.classJar.isSource()) {
continue;
}
File classJarFile = decoder.decode(jar.classJar);
@@ -104,10 +117,21 @@
}
}
- maybeRefreshJars(missingClassJars, pendingModuleJarsRefresh);
+ maybeRefreshJars(missingClassJars, pendingJarsRefresh);
return null;
}
+ private static Stream<LibraryArtifact> getNonResourceJars(TargetIdeInfo target) {
+ if (target.javaIdeInfo == null) {
+ return null;
+ }
+ Stream<LibraryArtifact> jars = target.javaIdeInfo.jars.stream();
+ if (target.androidIdeInfo != null) {
+ jars = jars.filter(jar -> !jar.equals(target.androidIdeInfo.resourceJar));
+ }
+ return jars;
+ }
+
@Nullable
private static VirtualFile findClassInJar(final VirtualFile classJar, String classNamePath) {
VirtualFile jarRoot = getJarRootForLocalFile(classJar);
@@ -137,38 +161,26 @@
return results;
}
- AppResourceRepository repository = AppResourceRepository.getAppResources(module, true);
+ AppResourceRepository repository = AppResourceRepositoryAdapter.getOrCreateInstance(module);
- List<File> missingClassJars = Lists.newArrayList();
for (TargetKey dependencyTargetKey :
TransitiveDependencyMap.getInstance(project).getTransitiveDependencies(target.key)) {
TargetIdeInfo dependencyTarget = targetMap.get(dependencyTargetKey);
if (dependencyTarget == null) {
continue;
}
- JavaIdeInfo javaIdeInfo = dependencyTarget.javaIdeInfo;
- AndroidIdeInfo androidIdeInfo = dependencyTarget.androidIdeInfo;
- // Add all non-resource jars to be searched.
- // Multiple resource jars will have ID conflicts unless generated dynamically.
+ // Add all import jars as external libraries.
+ JavaIdeInfo javaIdeInfo = dependencyTarget.javaIdeInfo;
if (javaIdeInfo != null) {
for (LibraryArtifact jar : javaIdeInfo.jars) {
- if (androidIdeInfo != null && jar.equals(androidIdeInfo.resourceJar)) {
- // No resource jars.
- continue;
- }
- // Some of these could be empty class jars from resource only android_library targets.
- // A potential optimization could be to filter out jars like these,
- // so we don't waste time fetching and searching them.
- // TODO: benchmark to see if optimization is worthwhile.
- if (jar.classJar != null) {
- File classJarFile = decoder.decode(jar.classJar);
+ if (jar.classJar != null && jar.classJar.isSource()) {
VirtualFile classJar =
- VirtualFileSystemProvider.getInstance().getSystem().findFileByIoFile(classJarFile);
+ VirtualFileSystemProvider.getInstance()
+ .getSystem()
+ .findFileByIoFile(decoder.decode(jar.classJar));
if (classJar != null) {
results.add(classJar);
- } else if (classJarFile.exists()) {
- missingClassJars.add(classJarFile);
}
}
}
@@ -185,13 +197,13 @@
// The resource repository remembers the dynamic IDs that it handed out and when the layoutlib
// calls to ask about the name and content of a given resource ID, the repository can just
// answer what it has already stored.
+ AndroidIdeInfo androidIdeInfo = dependencyTarget.androidIdeInfo;
if (androidIdeInfo != null && repository != null) {
ResourceClassRegistry.get(module.getProject())
.addLibrary(repository, androidIdeInfo.resourceJavaPackage);
}
}
- maybeRefreshJars(missingClassJars, pendingDependencyJarsRefresh);
return results;
}
diff --git a/aswb/src/com/google/idea/blaze/android/sync/sdk/AndroidSdkFromProjectView.java b/aswb/src/com/google/idea/blaze/android/sync/sdk/AndroidSdkFromProjectView.java
index c4eb537..17b2fb7 100644
--- a/aswb/src/com/google/idea/blaze/android/sync/sdk/AndroidSdkFromProjectView.java
+++ b/aswb/src/com/google/idea/blaze/android/sync/sdk/AndroidSdkFromProjectView.java
@@ -67,8 +67,8 @@
return null;
}
- String androidSdk = projectViewSet.getScalarValue(AndroidSdkPlatformSection.KEY);
- Integer androidMinSdk = projectViewSet.getScalarValue(AndroidMinSdkSection.KEY);
+ String androidSdk = projectViewSet.getScalarValue(AndroidSdkPlatformSection.KEY).orElse(null);
+ Integer androidMinSdk = projectViewSet.getScalarValue(AndroidMinSdkSection.KEY).orElse(null);
if (androidSdk == null) {
ProjectViewFile projectViewFile = projectViewSet.getTopLevelProjectViewFile();
diff --git a/aswb/tests/integrationtests/com/google/idea/blaze/android/plugin/NdkDependenciesTest.java b/aswb/tests/integrationtests/com/google/idea/blaze/android/plugin/NdkDependenciesTest.java
new file mode 100644
index 0000000..88e15b1
--- /dev/null
+++ b/aswb/tests/integrationtests/com/google/idea/blaze/android/plugin/NdkDependenciesTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.plugin;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.idea.blaze.android.settings.BlazeAndroidUserSettings;
+import com.google.idea.blaze.android.sync.BlazeNdkDependencySyncPlugin;
+import com.google.idea.blaze.base.BlazeIntegrationTestCase;
+import com.google.idea.blaze.base.plugin.PluginUtils;
+import com.google.idea.testing.DisablePluginsTestRule;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Test that ASwB successfully loads when the NDK plugins aren't enabled. Must be run as a
+ * stand-alone test, because it requires its own {@link Application}, and only one such object can
+ * be constructed per test suite.
+ */
+@RunWith(JUnit4.class)
+public class NdkDependenciesTest extends BlazeIntegrationTestCase {
+
+ @ClassRule
+ public static TestRule setDisabledPlugins =
+ new DisablePluginsTestRule(BlazeNdkDependencySyncPlugin.getPluginsRequiredForNdkSupport());
+
+ @Test
+ public void testNdkPluginsInstalled() {
+ assertThat(PluginUtils.isPluginInstalled("com.android.tools.ndk")).isTrue();
+ assertThat(PluginUtils.isPluginInstalled("com.google.idea.bazel.aswb")).isTrue();
+ }
+
+ @Test
+ public void testPluginLoadsWithoutNdkPlugins() {
+ assertThat(PluginUtils.isPluginEnabled("com.android.tools.ndk")).isFalse();
+ assertThat(PluginUtils.isPluginEnabled("com.google.idea.bazel.aswb")).isTrue();
+
+ // Plugins with classloader failures can be spuriously marked as enabled. Also test that
+ // ASwB plugin classes are actually loaded.
+ assertThat(BlazeAndroidUserSettings.getInstance()).isNotNull();
+ }
+}
diff --git a/aswb/tests/integrationtests/com/google/idea/blaze/android/prefetch/AswbPrefetchFileSourceTest.java b/aswb/tests/integrationtests/com/google/idea/blaze/android/prefetch/AswbPrefetchFileSourceTest.java
new file mode 100644
index 0000000..85fe799
--- /dev/null
+++ b/aswb/tests/integrationtests/com/google/idea/blaze/android/prefetch/AswbPrefetchFileSourceTest.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.prefetch;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.idea.blaze.base.BlazeIntegrationTestCase;
+import com.google.idea.blaze.base.prefetch.PrefetchFileSource;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Test for file extensions prefetched in ASwB. */
+@RunWith(JUnit4.class)
+public class AswbPrefetchFileSourceTest extends BlazeIntegrationTestCase {
+
+ @Test
+ public void testPrefetchedExtensions() {
+ assertThat(PrefetchFileSource.getAllPrefetchFileExtensions())
+ .containsExactly(
+ "java", "proto", "c", "cc", "cpp", "cxx", "c++", "C", "h", "hh", "hpp", "hxx", "inc",
+ "xml");
+ }
+}
diff --git a/aswb/tests/integrationtests/com/google/idea/blaze/android/prefetch/CPrefetchFileSourceTest.java b/aswb/tests/integrationtests/com/google/idea/blaze/android/prefetch/CPrefetchFileSourceTest.java
new file mode 100644
index 0000000..354f1e3
--- /dev/null
+++ b/aswb/tests/integrationtests/com/google/idea/blaze/android/prefetch/CPrefetchFileSourceTest.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.prefetch;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.base.Joiner;
+import com.google.idea.blaze.base.BlazeIntegrationTestCase;
+import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
+import com.google.idea.blaze.base.ideinfo.CIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetMapBuilder;
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.model.MockBlazeProjectDataBuilder;
+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.BlazeContext;
+import com.google.idea.blaze.base.settings.BlazeImportSettings;
+import com.google.idea.blaze.base.settings.BlazeImportSettingsManager;
+import com.google.idea.blaze.base.sync.projectview.ImportRoots;
+import com.google.idea.blaze.base.sync.projectview.LanguageSupport;
+import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolverImpl;
+import com.google.idea.blaze.cpp.CPrefetchFileSource;
+import java.io.File;
+import java.util.HashSet;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link CPrefetchFileSource}. */
+@RunWith(JUnit4.class)
+public class CPrefetchFileSourceTest extends BlazeIntegrationTestCase {
+
+ @Test
+ public void testSourceFilesInProjectIgnored() {
+ ProjectViewSet projectViewSet =
+ parseProjectView(
+ "directories:",
+ " java/com/google",
+ "targets:",
+ " //java/com/google:lib",
+ "additional_languages:",
+ " c",
+ "android_sdk_platform: android-25");
+
+ BlazeProjectData projectData =
+ MockBlazeProjectDataBuilder.builder(workspaceRoot)
+ .setTargetMap(
+ TargetMapBuilder.builder()
+ .addTarget(
+ TargetIdeInfo.builder()
+ .setBuildFile(sourceRoot("java/com/google/BUILD"))
+ .setLabel("//java/com/google:lib")
+ .setKind("cc_library")
+ .addSource(sourceRoot("java/com/google/native.cc"))
+ .addSource(sourceRoot("java/com/google/native.h")))
+ .build())
+ .setWorkspaceLanguageSettings(
+ LanguageSupport.createWorkspaceLanguageSettings(projectViewSet))
+ .build();
+
+ Set<File> filesToPrefetch = new HashSet<>();
+ new CPrefetchFileSource()
+ .addFilesToPrefetch(
+ getProject(),
+ projectViewSet,
+ getImportRoots(projectViewSet),
+ projectData,
+ filesToPrefetch);
+
+ assertThat(filesToPrefetch).isEmpty();
+ }
+
+ @Test
+ public void testCppHeaderFilesOutsideProjectIncluded() {
+ ProjectViewSet projectViewSet =
+ parseProjectView(
+ "directories:",
+ " java/com/google",
+ "targets:",
+ " //java/com/google:lib",
+ "additional_languages:",
+ " c",
+ "android_sdk_platform: android-25");
+
+ BlazeProjectData projectData =
+ MockBlazeProjectDataBuilder.builder(workspaceRoot)
+ .setTargetMap(
+ TargetMapBuilder.builder()
+ .addTarget(
+ TargetIdeInfo.builder()
+ .setBuildFile(sourceRoot("third_party/library/BUILD"))
+ .setLabel("//third_party/library:dep")
+ .setKind("cc_library")
+ .setCInfo(
+ CIdeInfo.builder()
+ .addSource(sourceRoot("third_party/library/main.cc"))
+ .addHeader(sourceRoot("third_party/library/dep.h"))
+ .addHeader(sourceRoot("third_party/library/other.h"))
+ .addTextualHeader(sourceRoot("third_party/library/textual.h"))))
+ .build())
+ .setWorkspaceLanguageSettings(
+ LanguageSupport.createWorkspaceLanguageSettings(projectViewSet))
+ .build();
+
+ Set<File> filesToPrefetch = new HashSet<>();
+ new CPrefetchFileSource()
+ .addFilesToPrefetch(
+ getProject(),
+ projectViewSet,
+ getImportRoots(projectViewSet),
+ projectData,
+ filesToPrefetch);
+
+ assertThat(filesToPrefetch)
+ .containsExactly(
+ workspaceFile("third_party/library/dep.h"),
+ workspaceFile("third_party/library/other.h"),
+ workspaceFile("third_party/library/textual.h"));
+ }
+
+ @Test
+ public void testJavaSourceFilesIgnored() {
+ ProjectViewSet projectViewSet =
+ parseProjectView(
+ "directories:",
+ " java/com/google",
+ "targets:",
+ " //java/com/google:lib",
+ "additional_languages:",
+ " c",
+ "android_sdk_platform: android-25");
+
+ BlazeProjectData projectData =
+ MockBlazeProjectDataBuilder.builder(workspaceRoot)
+ .setTargetMap(
+ TargetMapBuilder.builder()
+ .addTarget(
+ TargetIdeInfo.builder()
+ .setBuildFile(sourceRoot("third_party/library/BUILD"))
+ .setLabel("//third_party/library:lib")
+ .setKind("java_library")
+ .addSource(sourceRoot("third_party/library/Library.java")))
+ .build())
+ .setWorkspaceLanguageSettings(
+ LanguageSupport.createWorkspaceLanguageSettings(projectViewSet))
+ .build();
+
+ Set<File> filesToPrefetch = new HashSet<>();
+ new CPrefetchFileSource()
+ .addFilesToPrefetch(
+ getProject(),
+ projectViewSet,
+ getImportRoots(projectViewSet),
+ projectData,
+ filesToPrefetch);
+
+ assertThat(filesToPrefetch).isEmpty();
+ }
+
+ private ProjectViewSet parseProjectView(String... contents) {
+ ProjectViewParser projectViewParser =
+ new ProjectViewParser(new BlazeContext(), new WorkspacePathResolverImpl(workspaceRoot));
+ projectViewParser.parseProjectView(Joiner.on("\n").join(contents));
+ return projectViewParser.getResult();
+ }
+
+ private static ArtifactLocation sourceRoot(String relativePath) {
+ return ArtifactLocation.builder().setRelativePath(relativePath).setIsSource(true).build();
+ }
+
+ private File workspaceFile(String workspacePath) {
+ return workspaceRoot.fileForPath(new WorkspacePath(workspacePath));
+ }
+
+ private ImportRoots getImportRoots(ProjectViewSet projectViewSet) {
+ BlazeImportSettings importSettings =
+ BlazeImportSettingsManager.getInstance(getProject()).getImportSettings();
+ WorkspaceRoot workspaceRoot = WorkspaceRoot.fromImportSettings(importSettings);
+ return ImportRoots.builder(workspaceRoot, importSettings.getBuildSystem())
+ .add(projectViewSet)
+ .build();
+ }
+}
diff --git a/aswb/tests/integrationtests/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationCommonStateTest.java b/aswb/tests/integrationtests/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationCommonStateTest.java
index 0d9cba6..0df7e0c 100644
--- a/aswb/tests/integrationtests/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationCommonStateTest.java
+++ b/aswb/tests/integrationtests/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationCommonStateTest.java
@@ -42,6 +42,7 @@
@Rule
public final AndroidIntegrationTestSetupRule androidSetupRule =
new AndroidIntegrationTestSetupRule();
+
private BlazeAndroidRunConfigurationCommonState state;
@Before
@@ -58,6 +59,7 @@
@Test
public void readAndWriteShouldMatch() throws InvalidDataException, WriteExternalException {
state.getBlazeFlagsState().setRawFlags(ImmutableList.of("--flag1", "--flag2"));
+ state.getExeFlagsState().setRawFlags(ImmutableList.of("--exe1", "--exe2"));
state.setNativeDebuggingEnabled(true);
Element element = new Element("test");
@@ -69,6 +71,9 @@
assertThat(readState.getBlazeFlagsState().getRawFlags())
.containsExactly("--flag1", "--flag2")
.inOrder();
+ assertThat(readState.getExeFlagsState().getRawFlags())
+ .containsExactly("--exe1", "--exe2")
+ .inOrder();
assertThat(readState.isNativeDebuggingEnabled()).isTrue();
}
@@ -82,6 +87,8 @@
assertThat(readState.getBlazeFlagsState().getRawFlags())
.isEqualTo(state.getBlazeFlagsState().getRawFlags());
+ assertThat(readState.getExeFlagsState().getRawFlags())
+ .isEqualTo(state.getExeFlagsState().getRawFlags());
assertThat(readState.isNativeDebuggingEnabled()).isEqualTo(state.isNativeDebuggingEnabled());
}
@@ -90,6 +97,9 @@
state
.getBlazeFlagsState()
.setRawFlags(ImmutableList.of("hi ", "", "I'm", " ", "\t", "Josh\r\n", "\n"));
+ state
+ .getExeFlagsState()
+ .setRawFlags(ImmutableList.of("one ", "", "two", " ", "\t", "three\r\n", "\n"));
Element element = new Element("test");
state.writeExternal(element);
@@ -100,6 +110,9 @@
assertThat(readState.getBlazeFlagsState().getRawFlags())
.containsExactly("hi", "I'm", "Josh")
.inOrder();
+ assertThat(readState.getExeFlagsState().getRawFlags())
+ .containsExactly("one", "two", "three")
+ .inOrder();
}
@Test
@@ -107,6 +120,7 @@
final XMLOutputter xmlOutputter = new XMLOutputter(Format.getCompactFormat());
state.getBlazeFlagsState().setRawFlags(ImmutableList.of("--flag1", "--flag2"));
+ state.getExeFlagsState().setRawFlags(ImmutableList.of("--exe1", "--exe2"));
state.setNativeDebuggingEnabled(true);
Element firstWrite = new Element("test");
diff --git a/aswb/tests/integrationtests/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationStateTest.java b/aswb/tests/integrationtests/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationStateTest.java
index e72c5de..51f8f49 100644
--- a/aswb/tests/integrationtests/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationStateTest.java
+++ b/aswb/tests/integrationtests/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationStateTest.java
@@ -22,6 +22,7 @@
import com.google.idea.blaze.android.AndroidIntegrationTestSetupRule;
import com.google.idea.blaze.android.cppapi.NdkSupport;
import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationCommonState;
+import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryLaunchMethodsProvider.AndroidBinaryLaunchMethod;
import com.google.idea.blaze.base.BlazeIntegrationTestCase;
import com.google.idea.blaze.base.run.state.RunConfigurationStateEditor;
import com.google.idea.common.experiments.ExperimentService;
@@ -45,6 +46,7 @@
@Rule
public final AndroidIntegrationTestSetupRule androidSetupRule =
new AndroidIntegrationTestSetupRule();
+
private BlazeAndroidBinaryRunConfigurationState state;
@Before
@@ -66,7 +68,7 @@
state.setActivityClass("com.example.TestActivity");
state.setMode(BlazeAndroidBinaryRunConfigurationState.LAUNCH_SPECIFIC_ACTIVITY);
- state.setMobileInstall(true);
+ state.setLaunchMethod(AndroidBinaryLaunchMethod.MOBILE_INSTALL);
state.setUseSplitApksIfPossible(false);
state.setUseWorkProfileIfPresent(true);
state.setUserId(2);
@@ -87,7 +89,7 @@
assertThat(readState.getActivityClass()).isEqualTo("com.example.TestActivity");
assertThat(readState.getMode())
.isEqualTo(BlazeAndroidBinaryRunConfigurationState.LAUNCH_SPECIFIC_ACTIVITY);
- assertThat(readState.mobileInstall()).isTrue();
+ assertThat(readState.getLaunchMethod()).isEqualTo(AndroidBinaryLaunchMethod.MOBILE_INSTALL);
assertThat(readState.useSplitApksIfPossible()).isFalse();
assertThat(readState.useWorkProfileIfPresent()).isTrue();
assertThat(readState.getUserId()).isEqualTo(2);
@@ -111,7 +113,7 @@
assertThat(readState.getActivityClass()).isEqualTo(state.getActivityClass());
assertThat(readState.getMode()).isEqualTo(state.getMode());
- assertThat(readState.mobileInstall()).isEqualTo(state.mobileInstall());
+ assertThat(readState.getLaunchMethod()).isEqualTo(state.getLaunchMethod());
assertThat(readState.useSplitApksIfPossible()).isEqualTo(state.useSplitApksIfPossible());
assertThat(readState.useWorkProfileIfPresent()).isEqualTo(state.useWorkProfileIfPresent());
assertThat(readState.getUserId()).isEqualTo(state.getUserId());
@@ -128,7 +130,7 @@
state.setActivityClass("com.example.TestActivity");
state.setMode(BlazeAndroidBinaryRunConfigurationState.LAUNCH_SPECIFIC_ACTIVITY);
- state.setMobileInstall(true);
+ state.setLaunchMethod(AndroidBinaryLaunchMethod.MOBILE_INSTALL);
state.setUseSplitApksIfPossible(false);
state.setUseWorkProfileIfPresent(true);
state.setUserId(2);
@@ -153,12 +155,12 @@
state.setActivityClass("com.example.TestActivity");
state.setMode(BlazeAndroidBinaryRunConfigurationState.LAUNCH_SPECIFIC_ACTIVITY);
- state.setMobileInstall(true);
+ state.setLaunchMethod(AndroidBinaryLaunchMethod.MOBILE_INSTALL);
state.setUseSplitApksIfPossible(false);
state.setUseWorkProfileIfPresent(true);
state.setUserId(2);
// We don't test DeepLink because it is not exposed in the editor.
- //state.setDeepLink("http://deeplink");
+ // state.setDeepLink("http://deeplink");
editor.resetEditorFrom(state);
BlazeAndroidBinaryRunConfigurationState readState =
@@ -173,12 +175,12 @@
assertThat(readState.getActivityClass()).isEqualTo(state.getActivityClass());
assertThat(readState.getMode()).isEqualTo(state.getMode());
- assertThat(readState.mobileInstall()).isEqualTo(state.mobileInstall());
+ assertThat(readState.getLaunchMethod()).isEqualTo(state.getLaunchMethod());
assertThat(readState.useSplitApksIfPossible()).isEqualTo(state.useSplitApksIfPossible());
assertThat(readState.useWorkProfileIfPresent()).isEqualTo(state.useWorkProfileIfPresent());
assertThat(readState.getUserId()).isEqualTo(state.getUserId());
// We don't test DeepLink because it is not exposed in the editor.
- //assertThat(readState.getDeepLink()).isEqualTo(state.getDeepLink());
+ // assertThat(readState.getDeepLink()).isEqualTo(state.getDeepLink());
}
@Test
@@ -199,11 +201,11 @@
assertThat(readState.getActivityClass()).isEqualTo(state.getActivityClass());
assertThat(readState.getMode()).isEqualTo(state.getMode());
- assertThat(readState.mobileInstall()).isEqualTo(state.mobileInstall());
+ assertThat(readState.getLaunchMethod()).isEqualTo(state.getLaunchMethod());
assertThat(readState.useSplitApksIfPossible()).isEqualTo(state.useSplitApksIfPossible());
assertThat(readState.useWorkProfileIfPresent()).isEqualTo(state.useWorkProfileIfPresent());
assertThat(readState.getUserId()).isEqualTo(state.getUserId());
// We don't test DeepLink because it is not exposed in the editor.
- //assertThat(readState.getDeepLink()).isEqualTo(state.getDeepLink());
+ // assertThat(readState.getDeepLink()).isEqualTo(state.getDeepLink());
}
}
diff --git a/aswb/tests/integrationtests/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationStateTest.java b/aswb/tests/integrationtests/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationStateTest.java
index 4944264..800ea7c 100644
--- a/aswb/tests/integrationtests/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationStateTest.java
+++ b/aswb/tests/integrationtests/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationStateTest.java
@@ -21,6 +21,7 @@
import com.google.idea.blaze.android.AndroidIntegrationTestSetupRule;
import com.google.idea.blaze.android.cppapi.NdkSupport;
import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationCommonState;
+import com.google.idea.blaze.android.run.test.BlazeAndroidTestLaunchMethodsProvider.AndroidTestLaunchMethod;
import com.google.idea.blaze.base.BlazeIntegrationTestCase;
import com.google.idea.blaze.base.run.state.RunConfigurationStateEditor;
import com.google.idea.common.experiments.ExperimentService;
@@ -44,6 +45,7 @@
@Rule
public final AndroidIntegrationTestSetupRule androidSetupRule =
new AndroidIntegrationTestSetupRule();
+
private BlazeAndroidTestRunConfigurationState state;
@Before
@@ -61,6 +63,7 @@
public void readAndWriteShouldMatch() throws InvalidDataException, WriteExternalException {
BlazeAndroidRunConfigurationCommonState commonState = state.getCommonState();
commonState.getBlazeFlagsState().setRawFlags(ImmutableList.of("--flag1", "--flag2"));
+ commonState.getExeFlagsState().setRawFlags(ImmutableList.of("--exe1", "--exe2"));
commonState.setNativeDebuggingEnabled(true);
state.setTestingType(BlazeAndroidTestRunConfigurationState.TEST_METHOD);
@@ -68,7 +71,7 @@
state.setMethodName("fooMethod");
state.setClassName("BarClass");
state.setPackageName("com.test.package.name");
- state.setRunThroughBlaze(true);
+ state.setLaunchMethod(AndroidTestLaunchMethod.BLAZE_TEST);
state.setExtraOptions("--option");
Element element = new Element("test");
@@ -81,6 +84,9 @@
assertThat(readCommonState.getBlazeFlagsState().getRawFlags())
.containsExactly("--flag1", "--flag2")
.inOrder();
+ assertThat(readCommonState.getExeFlagsState().getRawFlags())
+ .containsExactly("--exe1", "--exe2")
+ .inOrder();
assertThat(readCommonState.isNativeDebuggingEnabled()).isTrue();
assertThat(readState.getTestingType())
@@ -89,7 +95,7 @@
assertThat(readState.getMethodName()).isEqualTo("fooMethod");
assertThat(readState.getClassName()).isEqualTo("BarClass");
assertThat(readState.getPackageName()).isEqualTo("com.test.package.name");
- assertThat(readState.isRunThroughBlaze()).isTrue();
+ assertThat(readState.getLaunchMethod()).isEqualTo(AndroidTestLaunchMethod.BLAZE_TEST);
assertThat(readState.getExtraOptions()).isEqualTo("--option");
}
@@ -105,6 +111,8 @@
BlazeAndroidRunConfigurationCommonState readCommonState = readState.getCommonState();
assertThat(readCommonState.getBlazeFlagsState().getRawFlags())
.isEqualTo(commonState.getBlazeFlagsState().getRawFlags());
+ assertThat(readCommonState.getExeFlagsState().getRawFlags())
+ .isEqualTo(commonState.getExeFlagsState().getRawFlags());
assertThat(readCommonState.isNativeDebuggingEnabled())
.isEqualTo(commonState.isNativeDebuggingEnabled());
@@ -114,7 +122,7 @@
assertThat(readState.getMethodName()).isEqualTo(state.getMethodName());
assertThat(readState.getClassName()).isEqualTo(state.getClassName());
assertThat(readState.getPackageName()).isEqualTo(state.getPackageName());
- assertThat(readState.isRunThroughBlaze()).isEqualTo(state.isRunThroughBlaze());
+ assertThat(readState.getLaunchMethod()).isEqualTo(state.getLaunchMethod());
assertThat(readState.getExtraOptions()).isEqualTo(state.getExtraOptions());
}
@@ -131,7 +139,7 @@
state.setMethodName("fooMethod");
state.setClassName("BarClass");
state.setPackageName("com.test.package.name");
- state.setRunThroughBlaze(true);
+ state.setLaunchMethod(AndroidTestLaunchMethod.MOBILE_INSTALL);
state.setExtraOptions("--option");
Element firstWrite = new Element("test");
@@ -149,6 +157,7 @@
BlazeAndroidRunConfigurationCommonState commonState = state.getCommonState();
commonState.getBlazeFlagsState().setRawFlags(ImmutableList.of("--flag1", "--flag2"));
+ commonState.getExeFlagsState().setRawFlags(ImmutableList.of("--exe1", "--exe2"));
commonState.setNativeDebuggingEnabled(true);
state.setTestingType(BlazeAndroidTestRunConfigurationState.TEST_METHOD);
@@ -156,9 +165,9 @@
state.setMethodName("fooMethod");
state.setClassName("BarClass");
state.setPackageName("com.test.package.name");
- state.setRunThroughBlaze(true);
+ state.setLaunchMethod(AndroidTestLaunchMethod.BLAZE_TEST);
// We don't test ExtraOptions because it is not exposed in the editor.
- //state.setExtraOptions("--option");
+ // state.setExtraOptions("--option");
editor.resetEditorFrom(state);
BlazeAndroidTestRunConfigurationState readState =
@@ -168,6 +177,8 @@
BlazeAndroidRunConfigurationCommonState readCommonState = readState.getCommonState();
assertThat(readCommonState.getBlazeFlagsState().getRawFlags())
.isEqualTo(commonState.getBlazeFlagsState().getRawFlags());
+ assertThat(readCommonState.getExeFlagsState().getRawFlags())
+ .isEqualTo(commonState.getExeFlagsState().getRawFlags());
assertThat(readCommonState.isNativeDebuggingEnabled())
.isEqualTo(commonState.isNativeDebuggingEnabled());
@@ -177,9 +188,9 @@
assertThat(readState.getMethodName()).isEqualTo(state.getMethodName());
assertThat(readState.getClassName()).isEqualTo(state.getClassName());
assertThat(readState.getPackageName()).isEqualTo(state.getPackageName());
- assertThat(readState.isRunThroughBlaze()).isEqualTo(state.isRunThroughBlaze());
+ assertThat(readState.getLaunchMethod()).isEqualTo(state.getLaunchMethod());
// We don't test ExtraOptions because it is not exposed in the editor.
- //assertThat(readState.getExtraOptions()).isEqualTo(state.getExtraOptions());
+ // assertThat(readState.getExtraOptions()).isEqualTo(state.getExtraOptions());
}
@Test
@@ -195,6 +206,8 @@
BlazeAndroidRunConfigurationCommonState readCommonState = readState.getCommonState();
assertThat(readCommonState.getBlazeFlagsState().getRawFlags())
.isEqualTo(commonState.getBlazeFlagsState().getRawFlags());
+ assertThat(readCommonState.getExeFlagsState().getRawFlags())
+ .isEqualTo(commonState.getExeFlagsState().getRawFlags());
assertThat(readCommonState.isNativeDebuggingEnabled())
.isEqualTo(commonState.isNativeDebuggingEnabled());
@@ -204,8 +217,8 @@
assertThat(readState.getMethodName()).isEqualTo(state.getMethodName());
assertThat(readState.getClassName()).isEqualTo(state.getClassName());
assertThat(readState.getPackageName()).isEqualTo(state.getPackageName());
- assertThat(readState.isRunThroughBlaze()).isEqualTo(state.isRunThroughBlaze());
+ assertThat(readState.getLaunchMethod()).isEqualTo(state.getLaunchMethod());
// We don't test ExtraOptions because it is not exposed in the editor.
- //assertThat(readState.getExtraOptions()).isEqualTo(state.getExtraOptions());
+ // assertThat(readState.getExtraOptions()).isEqualTo(state.getExtraOptions());
}
}
diff --git a/aswb/tests/integrationtests/com/google/idea/blaze/android/sync/AndroidSyncTest.java b/aswb/tests/integrationtests/com/google/idea/blaze/android/sync/AndroidSyncTest.java
index a712648..f1f048a 100644
--- a/aswb/tests/integrationtests/com/google/idea/blaze/android/sync/AndroidSyncTest.java
+++ b/aswb/tests/integrationtests/com/google/idea/blaze/android/sync/AndroidSyncTest.java
@@ -42,7 +42,10 @@
import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
import com.google.idea.blaze.base.sync.projectstructure.ModuleFinder;
import com.google.idea.blaze.cpp.BlazeCWorkspace;
+import com.google.idea.blaze.cpp.CompilerVersionChecker;
+import com.google.idea.blaze.cpp.MockCompilerVersionChecker;
import com.google.idea.blaze.java.sync.BlazeJavaSyncAugmenter;
+import com.google.idea.sdkcompat.cidr.CidrCompilerSwitchesAdapter;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.projectRoots.SdkTypeId;
@@ -74,6 +77,8 @@
public void setup() {
mockSdk("android-25", "Android 25 SDK");
registerProjectService(OCWorkspaceManager.class, new MockOCWorkspaceManager());
+ registerApplicationService(
+ CompilerVersionChecker.class, new MockCompilerVersionChecker("1234"));
}
private void mockSdk(String targetHash, String sdkName) {
@@ -288,7 +293,8 @@
.addTransitiveSystemIncludeDirectories(
ImmutableList.of(
new ExecutionRootPath("third_party/stl/gcc3"),
- new ExecutionRootPath("third_party/java/jdk/include"))))
+ new ExecutionRootPath("third_party/java/jdk/include")))
+ .addSource(sourceRoot("java/com/google/jni/native.cc")))
.addSource(sourceRoot("java/com/google/jni/native.cc"))
.addDependency("//android_ndk_linux/toolchains:aarch64"))
.addTarget(
@@ -307,7 +313,8 @@
.addTransitiveSystemIncludeDirectories(
ImmutableList.of(
new ExecutionRootPath("third_party/stl/gcc3"),
- new ExecutionRootPath("third_party/java/jdk/include"))))
+ new ExecutionRootPath("third_party/java/jdk/include")))
+ .addSource(sourceRoot("java/com/google/jni/native2.cc")))
.addSource(sourceRoot("java/com/google/jni/native2.cc"))
.addDependency("//java/com/google:native_lib")
.addDependency("//android_ndk_linux/toolchains:armv7a"))
@@ -374,7 +381,8 @@
assertThat(resolveConfigurations).hasSize(1);
OCCompilerSettings compilerSettings = resolveConfigurations.get(0).getCompilerSettings();
List<String> compilerSwitches =
- compilerSettings.getCompilerSwitches(OCLanguageKind.CPP, nativeCc).getCommandLineArgs();
+ CidrCompilerSwitchesAdapter.getCommandLineArgs(
+ compilerSettings.getCompilerSwitches(OCLanguageKind.CPP, nativeCc));
assertThat(compilerSwitches)
.contains("--sysroot=android_ndk_linux/platforms/android-21/arch-arm64");
@@ -383,7 +391,8 @@
assertThat(resolveConfigurations).hasSize(1);
compilerSettings = resolveConfigurations.get(0).getCompilerSettings();
compilerSwitches =
- compilerSettings.getCompilerSwitches(OCLanguageKind.CPP, nativeCc).getCommandLineArgs();
+ CidrCompilerSwitchesAdapter.getCommandLineArgs(
+ compilerSettings.getCompilerSwitches(OCLanguageKind.CPP, nativeCc));
assertThat(compilerSwitches)
.contains("--sysroot=android_ndk_linux/platforms/android-18/arch-arm");
}
diff --git a/aswb/tests/integrationtests/com/google/idea/blaze/android/sync/model/idea/BlazeClassJarProviderIntegrationTest.java b/aswb/tests/integrationtests/com/google/idea/blaze/android/sync/model/idea/BlazeClassJarProviderIntegrationTest.java
index e784f6a..f55ad08 100644
--- a/aswb/tests/integrationtests/com/google/idea/blaze/android/sync/model/idea/BlazeClassJarProviderIntegrationTest.java
+++ b/aswb/tests/integrationtests/com/google/idea/blaze/android/sync/model/idea/BlazeClassJarProviderIntegrationTest.java
@@ -39,6 +39,7 @@
import com.google.idea.blaze.base.model.primitives.Label;
import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
+import com.google.idea.sdkcompat.android.res.AppResourceRepositoryAdapter;
import com.intellij.facet.FacetManager;
import com.intellij.facet.ModifiableFacetModel;
import com.intellij.openapi.application.ApplicationManager;
@@ -84,75 +85,165 @@
createClassesInJars();
// Make sure we can find classes in the main resource module.
- assertThat(classJarProvider.findModuleClassFile("com.google.example.main.MainActivity", module))
+ VirtualFile found =
+ classJarProvider.findModuleClassFile("com.google.example.main.MainActivity", module);
+ assertThat(found).isNotNull();
+ assertThat(found)
.isEqualTo(
fileSystem.findFile(
BLAZE_BIN
- + "/com/google/example/main.jar!"
+ + "/com/google/example/libmain.jar!"
+ "/com/google/example/main/MainActivity.class"));
- assertThat(classJarProvider.findModuleClassFile("com.google.example.main.R", module))
+ found = classJarProvider.findModuleClassFile("com.google.example.main.R", module);
+ assertThat(found).isNotNull();
+ assertThat(found)
.isEqualTo(
fileSystem.findFile(
BLAZE_BIN
+ "/com/google/example/main_resources.jar!"
+ "/com/google/example/main/R.class"));
- assertThat(classJarProvider.findModuleClassFile("com.google.example.main.R$string", module))
+
+ found = classJarProvider.findModuleClassFile("com.google.example.main.R$string", module);
+ assertThat(found).isNotNull();
+ assertThat(found)
.isEqualTo(
fileSystem.findFile(
BLAZE_BIN
+ "/com/google/example/main_resources.jar!"
+ "/com/google/example/main/R$string.class"));
+ // And dependencies.
+ found = classJarProvider.findModuleClassFile("com.google.example.java.Java", module);
+ assertThat(found).isNotNull();
+ assertThat(found)
+ .isEqualTo(
+ fileSystem.findFile(
+ BLAZE_BIN
+ + "/com/google/example/libjava.jar!"
+ + "/com/google/example/java/Java.class"));
+ found = classJarProvider.findModuleClassFile("com.google.example.shared.Shared", module);
+ assertThat(found).isNotNull();
+ assertThat(found)
+ .isEqualTo(
+ fileSystem.findFile(
+ BLAZE_BIN
+ + "/com/google/example/libshared.jar!"
+ + "/com/google/example/shared/Shared.class"));
+ found = classJarProvider.findModuleClassFile("com.google.example.shared2.Shared2", module);
+ assertThat(found).isNotNull();
+ assertThat(found)
+ .isEqualTo(
+ fileSystem.findFile(
+ BLAZE_BIN
+ + "/com/google/example/libshared2.jar!"
+ + "/com/google/example/shared2/Shared2.class"));
+ found =
+ classJarProvider.findModuleClassFile("com.google.example.transitive.Transitive", module);
+ assertThat(found).isNotNull();
+ assertThat(found)
+ .isEqualTo(
+ fileSystem.findFile(
+ BLAZE_BIN
+ + "/com/google/example/libtransitive.jar!"
+ + "/com/google/example/transitive/Transitive.class"));
+
+ // But not resource classes in dependencies.
+ assertThat(classJarProvider.findModuleClassFile("com.google.example.resource.R", module))
+ .isNull();
+ assertThat(classJarProvider.findModuleClassFile("com.google.example.resource.R$style", module))
+ .isNull();
+ assertThat(classJarProvider.findModuleClassFile("com.google.example.resource.R$layout", module))
+ .isNull();
+ assertThat(classJarProvider.findModuleClassFile("com.google.example.resource2.R", module))
+ .isNull();
+
// And not classes that are missing.
assertThat(classJarProvider.findModuleClassFile("com.google.example.main.MissingClass", module))
.isNull();
assertThat(classJarProvider.findModuleClassFile("com.google.example.main.R$missing", module))
.isNull();
- // And not classes in other libraries.
- assertThat(classJarProvider.findModuleClassFile("com.google.example.java.CustomView", module))
- .isNull();
- assertThat(classJarProvider.findModuleClassFile("com.google.example.android_res.R", module))
- .isNull();
- assertThat(
- classJarProvider.findModuleClassFile("com.google.example.android_res.R$style", module))
+ // And not imported libraries.
+ assertThat(classJarProvider.findModuleClassFile("com.google.example.import.Import", module))
.isNull();
assertThat(
classJarProvider.findModuleClassFile(
- "com.google.unrelated.android_res.R$layout", module))
+ "com.google.example.transitive.import.TransitiveImport", module))
.isNull();
+
+ // And not unrelated libraries.
+ assertThat(classJarProvider.findModuleClassFile("com.google.unrelated.Java", module)).isNull();
+ assertThat(classJarProvider.findModuleClassFile("com.google.unrelated.Android", module))
+ .isNull();
+ assertThat(classJarProvider.findModuleClassFile("com.google.unrelated.Resource", module))
+ .isNull();
+ assertThat(classJarProvider.findModuleClassFile("com.google.unrelated.R", module)).isNull();
}
@Test
- public void testMissingMainJar() {
+ public void testMissingClassJars() {
createClassesInJars();
ApplicationManager.getApplication()
.runWriteAction(
() -> {
try {
- // Let's pretend that this hasn't been built yet.
- fileSystem.findFile(BLAZE_BIN + "/com/google/example/main.jar").delete(this);
+ // Let's pretend that these haven't been built yet.
+ fileSystem.findFile(BLAZE_BIN + "/com/google/example/libmain.jar").delete(this);
+ fileSystem.findFile(BLAZE_BIN + "/com/google/example/libjava.jar").delete(this);
+ fileSystem.findFile(BLAZE_BIN + "/com/google/example/libshared.jar").delete(this);
+ fileSystem
+ .findFile(BLAZE_BIN + "/com/google/example/libtransitive.jar")
+ .delete(this);
} catch (IOException ignored) {
// ignored
}
});
- // This hasn't been built yet, and shouldn't be found.
+ // These hasn't been built yet, and shouldn't be found.
assertThat(classJarProvider.findModuleClassFile("com.google.example.main.MainActivity", module))
.isNull();
+ assertThat(classJarProvider.findModuleClassFile("com.google.example.java.Java", module))
+ .isNull();
+ assertThat(classJarProvider.findModuleClassFile("com.google.example.shared.Shared", module))
+ .isNull();
+ assertThat(
+ classJarProvider.findModuleClassFile(
+ "com.google.example.transitive.Transitive", module))
+ .isNull();
+
// But these should still be found.
- assertThat(classJarProvider.findModuleClassFile("com.google.example.main.R", module))
+ VirtualFile found = classJarProvider.findModuleClassFile("com.google.example.main.R", module);
+ assertThat(found).isNotNull();
+ assertThat(found)
.isEqualTo(
fileSystem.findFile(
BLAZE_BIN
+ "/com/google/example/main_resources.jar!"
+ "/com/google/example/main/R.class"));
- assertThat(classJarProvider.findModuleClassFile("com.google.example.main.R$string", module))
+ found = classJarProvider.findModuleClassFile("com.google.example.main.R$string", module);
+ assertThat(found).isNotNull();
+ assertThat(found)
.isEqualTo(
fileSystem.findFile(
BLAZE_BIN
+ "/com/google/example/main_resources.jar!"
+ "/com/google/example/main/R$string.class"));
+ found = classJarProvider.findModuleClassFile("com.google.example.android.Android", module);
+ assertThat(found).isNotNull();
+ assertThat(found)
+ .isEqualTo(
+ fileSystem.findFile(
+ BLAZE_BIN
+ + "/com/google/example/libandroid.jar!"
+ + "/com/google/example/android/Android.class"));
+ found = classJarProvider.findModuleClassFile("com.google.example.shared2.Shared2", module);
+ assertThat(found).isNotNull();
+ assertThat(found)
+ .isEqualTo(
+ fileSystem.findFile(
+ BLAZE_BIN
+ + "/com/google/example/libshared2.jar!"
+ + "/com/google/example/shared2/Shared2.class"));
}
@Test
@@ -169,53 +260,40 @@
List<VirtualFile> externalLibraries = classJarProvider.getModuleExternalLibraries(module);
assertThat(externalLibraries)
.containsExactly(
- fileSystem.findFile(BLAZE_BIN + "/com/google/example/android_lib.jar"),
- fileSystem.findFile(BLAZE_BIN + "/com/google/example/android_res.jar"),
- fileSystem.findFile(BLAZE_BIN + "/com/google/example/android_res2.jar"),
- fileSystem.findFile(BLAZE_BIN + "/com/google/example/transitive/android_res.jar"),
- fileSystem.findFile(BLAZE_BIN + "/com/google/example/java.jar"),
- fileSystem.findFile(BLAZE_BIN + "/com/google/example/transitive/java.jar"),
- fileSystem.findFile(BLAZE_BIN + "/com/google/example/shared/java.jar"),
- fileSystem.findFile(BLAZE_BIN + "/com/google/example/shared2/java.jar"),
- fileSystem.findFile("com/google/example/import.jar"),
- fileSystem.findFile("com/google/example/transitive/import.jar"),
- fileSystem.findFile("com/google/example/transitive/import2.jar"));
+ fileSystem.findFile("com/google/example/libimport.jar"),
+ fileSystem.findFile("com/google/example/transitive/libimport.jar"),
+ fileSystem.findFile("com/google/example/transitive/libimport2.jar"));
// Make sure we can generate dynamic classes from all resource packages in dependencies.
ResourceClassRegistry registry = ResourceClassRegistry.get(getProject());
- AppResourceRepository repository = AppResourceRepository.getAppResources(module, false);
+ AppResourceRepository repository = AppResourceRepositoryAdapter.findExistingInstance(module);
assertThat(repository).isNotNull();
- assertThat(registry.findClassDefinition("com.google.example.android_res.R", repository))
+ assertThat(registry.findClassDefinition("com.google.example.resource.R", repository))
.isNotNull();
- assertThat(registry.findClassDefinition("com.google.example.android_res.R$string", repository))
+ assertThat(registry.findClassDefinition("com.google.example.resource.R$string", repository))
.isNotNull();
- assertThat(registry.findClassDefinition("com.google.example.android_res2.R", repository))
+ assertThat(registry.findClassDefinition("com.google.example.resource2.R", repository))
.isNotNull();
- assertThat(registry.findClassDefinition("com.google.example.android_res2.R$layout", repository))
+ assertThat(registry.findClassDefinition("com.google.example.resource2.R$layout", repository))
.isNotNull();
- assertThat(
- registry.findClassDefinition("com.google.example.transitive.android_res.R", repository))
+ assertThat(registry.findClassDefinition("com.google.example.transitive.resource.R", repository))
.isNotNull();
assertThat(
registry.findClassDefinition(
- "com.google.example.transitive.android_res.R$style", repository))
+ "com.google.example.transitive.resource.R$style", repository))
.isNotNull();
// And nothing else.
assertThat(registry.findClassDefinition("com.google.example.main.MainActivity", repository))
.isNull();
- assertThat(registry.findClassDefinition("com.google.example.android_res.Bogus", repository))
+ assertThat(registry.findClassDefinition("com.google.example.resource.Bogus", repository))
.isNull();
assertThat(registry.findClassDefinition("com.google.example.main.R", repository)).isNull();
assertThat(registry.findClassDefinition("com.google.example.main.R$string", repository))
.isNull();
- assertThat(registry.findClassDefinition("com.google.example.java.CustomView", repository))
- .isNull();
- assertThat(registry.findClassDefinition("com.google.unrelated.android_res.R", repository))
- .isNull();
- assertThat(
- registry.findClassDefinition("com.google.unrelated.android_res.R$layout", repository))
- .isNull();
+ assertThat(registry.findClassDefinition("com.google.example.java.Java", repository)).isNull();
+ assertThat(registry.findClassDefinition("com.google.unrelated.R", repository)).isNull();
+ assertThat(registry.findClassDefinition("com.google.unrelated.R$layout", repository)).isNull();
}
@Test
@@ -224,14 +302,9 @@
.runWriteAction(
() -> {
try {
- // Let's pretend that these haven't been built yet.
- fileSystem.findFile(BLAZE_BIN + "/com/google/example/java.jar").delete(this);
- fileSystem
- .findFile(BLAZE_BIN + "/com/google/example/android_res2.jar")
- .delete(this);
- fileSystem
- .findFile(BLAZE_BIN + "/com/google/example/shared2/java.jar")
- .delete(this);
+ // Let's pretend that these were deleted.
+ fileSystem.findFile("com/google/example/libimport.jar").delete(this);
+ fileSystem.findFile("com/google/example/transitive/libimport.jar").delete(this);
} catch (IOException ignored) {
// ignored
}
@@ -239,26 +312,16 @@
List<VirtualFile> externalLibraries = classJarProvider.getModuleExternalLibraries(module);
assertThat(externalLibraries)
.containsExactly(
- fileSystem.findFile(BLAZE_BIN + "/com/google/example/android_lib.jar"),
- fileSystem.findFile(BLAZE_BIN + "/com/google/example/android_res.jar"),
- // This should be missing.
- // fileSystem.findFile(BLAZE_BIN + "/com/google/example/android_res2.jar"),
- fileSystem.findFile(BLAZE_BIN + "/com/google/example/transitive/android_res.jar"),
- // This should be missing.
- // fileSystem.findFile(BLAZE_BIN + "/com/google/example/java.jar"),
- fileSystem.findFile(BLAZE_BIN + "/com/google/example/transitive/java.jar"),
- fileSystem.findFile(BLAZE_BIN + "/com/google/example/shared/java.jar"),
- // This should be missing.
- // fileSystem.findFile(BLAZE_BIN + "/com/google/example/shared2/java.jar"),
- fileSystem.findFile("com/google/example/import.jar"),
- fileSystem.findFile("com/google/example/transitive/import.jar"),
- fileSystem.findFile("com/google/example/transitive/import2.jar"));
+ // These should be missing.
+ // fileSystem.findFile("com/google/example/libimport.jar"),
+ // fileSystem.findFile("com/google/example/transitive/libimport.jar"),
+ fileSystem.findFile("com/google/example/transitive/libimport2.jar"));
}
private void createClassesInJars() {
fileSystem.createFile(
BLAZE_BIN
- + "/com/google/example/main.jar!"
+ + "/com/google/example/libmain.jar!"
+ "/com/google/example/main/MainActivity.class");
fileSystem.createFile(
BLAZE_BIN + "/com/google/example/main_resources.jar!" + "/com/google/example/main/R.class");
@@ -267,37 +330,81 @@
+ "/com/google/example/main_resources.jar!"
+ "/com/google/example/main/R$string.class");
fileSystem.createFile(
- BLAZE_BIN + "/com/google/example/java.jar!" + "/com/google/example/java/CustomView.class");
+ BLAZE_BIN
+ + "/com/google/example/libandroid.jar!"
+ + "/com/google/example/android/Android.class");
fileSystem.createFile(
BLAZE_BIN
- + "/com/google/example/android_res_resources.jar!"
- + "/com/google/example/android_res/R.class");
+ + "/com/google/example/resource_resources.jar!"
+ + "/com/google/example/resource/R.class");
fileSystem.createFile(
BLAZE_BIN
- + "/com/google/example/android_res_resources.jar!"
- + "/com/google/example/android_res/R$style.class");
+ + "/com/google/example/resource_resources.jar!"
+ + "/com/google/example/resource/R$style.class");
fileSystem.createFile(
BLAZE_BIN
- + "/com/google/unrelated/android_res_resources.jar!"
- + "/com/google/unrelated/android_res/R$layout.class");
+ + "/com/google/unrelated/resource_resources.jar!"
+ + "/com/google/unrelated/resource/R$layout.class");
+ fileSystem.createFile(
+ BLAZE_BIN
+ + "/com/google/example/resource2_resources.jar!"
+ + "/com/google/example/resource2/R.class");
+ fileSystem.createFile(
+ BLAZE_BIN
+ + "/com/google/example/transitive/resource_resources.jar!"
+ + "/com/google/example/transitive/R.class");
+ fileSystem.createFile(
+ BLAZE_BIN + "/com/google/example/libjava.jar!" + "/com/google/example/java/Java.class");
+ fileSystem.createFile(
+ BLAZE_BIN
+ + "/com/google/example/libtransitive.jar!"
+ + "/com/google/example/transitive/Transitive.class");
+ fileSystem.createFile(
+ BLAZE_BIN
+ + "/com/google/example/libshared.jar!"
+ + "/com/google/example/shared/Shared.class");
+ fileSystem.createFile(
+ BLAZE_BIN
+ + "/com/google/example/libshared2.jar!"
+ + "/com/google/example/shared2/Shared2.class");
+ fileSystem.createFile(
+ "com/google/example/libimport.jar!" + "/com/google/example/import/Import.class");
+ fileSystem.createFile(
+ "com/google/example/transitive/libimport.jar!"
+ + "/com/google/example/transitive/import/TransitiveImport.class");
+ fileSystem.createFile(
+ BLAZE_BIN
+ + "/com/google/example/unrelated/libjava.jar!"
+ + "/com/google/example/unrelated/Java.class");
+ fileSystem.createFile(
+ BLAZE_BIN
+ + "/com/google/example/unrelated/libandroid.jar!"
+ + "/com/google/example/unrelated/Android.class");
+ fileSystem.createFile(
+ BLAZE_BIN
+ + "/com/google/example/unrelated/libresource.jar!"
+ + "/com/google/example/unrelated/Resource.class");
+ fileSystem.createFile(
+ BLAZE_BIN
+ + "/com/google/example/unrelated/resource_resources.jar!"
+ + "/com/google/example/unrelated/R.class");
}
private TargetMap buildTargetMap() {
Label mainResourceLibrary = Label.create("//com/google/example:main");
- Label androidLibraryDependency = Label.create("//com/google/example:android_lib");
- Label androidResourceDependency = Label.create("//com/google/example:android_res");
- Label androidResourceDependency2 = Label.create("//com/google/example:android_res2");
- Label transitiveResourceDependency =
- Label.create("//com/google/example/transitive:android_res");
+ Label androidDependency = Label.create("//com/google/example:android");
+ Label resourceDependency = Label.create("//com/google/example:resource");
+ Label resourceDependency2 = Label.create("//com/google/example:resource2");
+ Label transitiveResourceDependency = Label.create("//com/google/example/transitive:resource");
Label javaDependency = Label.create("//com/google/example:java");
- Label transitiveJavaDependency = Label.create("//com/google/example/transitive:java");
- Label sharedJavaDependency = Label.create("//com/google/example/shared:java");
- Label sharedJavaDependency2 = Label.create("//com/google/example/shared2:java");
+ Label transitiveJavaDependency = Label.create("//com/google/example:transitive");
+ Label sharedJavaDependency = Label.create("//com/google/example:shared");
+ Label sharedJavaDependency2 = Label.create("//com/google/example:shared2");
Label importDependency = Label.create("//com/google/example:import");
Label transitiveImportDependency = Label.create("//com/google/example/transitive:import");
Label unrelatedJava = Label.create("//com/google/unrelated:java");
- Label unrelatedAndroidLibrary = Label.create("//com/google/unrelated:android_lib");
- Label unrelatedAndroidResource = Label.create("//com/google/unrelated:android_res");
+ Label unrelatedAndroid = Label.create("//com/google/unrelated:android");
+ Label unrelatedResource = Label.create("//com/google/unrelated:resource");
AndroidResourceModuleRegistry registry = new AndroidResourceModuleRegistry();
registry.put(
@@ -306,18 +413,17 @@
// Not using these, but they should be in the registry.
registry.put(
mock(Module.class),
- AndroidResourceModule.builder(TargetKey.forPlainTarget(androidResourceDependency)).build());
+ AndroidResourceModule.builder(TargetKey.forPlainTarget(resourceDependency)).build());
registry.put(
mock(Module.class),
- AndroidResourceModule.builder(TargetKey.forPlainTarget(androidResourceDependency2))
- .build());
+ AndroidResourceModule.builder(TargetKey.forPlainTarget(resourceDependency2)).build());
registry.put(
mock(Module.class),
AndroidResourceModule.builder(TargetKey.forPlainTarget(transitiveResourceDependency))
.build());
registry.put(
mock(Module.class),
- AndroidResourceModule.builder(TargetKey.forPlainTarget(unrelatedAndroidResource)).build());
+ AndroidResourceModule.builder(TargetKey.forPlainTarget(unrelatedResource)).build());
registerProjectService(AndroidResourceModuleRegistry.class, registry);
return TargetMapBuilder.builder()
@@ -327,67 +433,67 @@
.setKind(Kind.ANDROID_LIBRARY)
.setJavaInfo(
javaInfoWithJars(
- "com/google/example/main.jar", "com/google/example/main_resources.jar"))
+ "com/google/example/libmain.jar", "com/google/example/main_resources.jar"))
.setAndroidInfo(
androidInfoWithResourceAndJar(
"com.google.example.main",
"com/google/example/main/res",
"com/google/example/main_resources.jar"))
- .addDependency(androidLibraryDependency)
- .addDependency(androidResourceDependency)
- .addDependency(androidResourceDependency2)
+ .addDependency(androidDependency)
+ .addDependency(resourceDependency)
+ .addDependency(resourceDependency2)
.addDependency(javaDependency)
.addDependency(importDependency))
.addTarget(
TargetIdeInfo.builder()
- .setLabel(androidLibraryDependency)
+ .setLabel(androidDependency)
.setKind(Kind.ANDROID_LIBRARY)
- .setJavaInfo(javaInfoWithJars("com/google/example/android_lib.jar"))
+ .setJavaInfo(javaInfoWithJars("com/google/example/libandroid.jar"))
.addDependency(transitiveResourceDependency))
.addTarget(
TargetIdeInfo.builder()
- .setLabel(androidResourceDependency)
+ .setLabel(resourceDependency)
.setKind(Kind.ANDROID_LIBRARY)
.setJavaInfo(
javaInfoWithJars(
- "com/google/example/android_res.jar",
- "com/google/example/android_res_resources.jar"))
+ "com/google/example/resource.jar",
+ "com/google/example/resource_resources.jar"))
.setAndroidInfo(
androidInfoWithResourceAndJar(
- "com.google.example.android_res",
- "com/google/example/android_res/res",
- "com/google/example/android_res_resources.jar")))
+ "com.google.example.resource",
+ "com/google/example/resource/res",
+ "com/google/example/resource_resources.jar")))
.addTarget(
TargetIdeInfo.builder()
- .setLabel(androidResourceDependency2)
+ .setLabel(resourceDependency2)
.setKind(Kind.ANDROID_LIBRARY)
.setJavaInfo(
javaInfoWithJars(
- "com/google/example/android_res2.jar",
- "com/google/example/android_res2_resources.jar"))
+ "com/google/example/resource2.jar",
+ "com/google/example/resource2_resources.jar"))
.setAndroidInfo(
androidInfoWithResourceAndJar(
- "com.google.example.android_res2",
- "com/google/example/android_res2/res",
- "com/google/example/android_res2_resources.jar")))
+ "com.google.example.resource2",
+ "com/google/example/resource2/res",
+ "com/google/example/resource2_resources.jar")))
.addTarget(
TargetIdeInfo.builder()
.setLabel(transitiveResourceDependency)
.setKind(Kind.ANDROID_LIBRARY)
.setJavaInfo(
javaInfoWithJars(
- "com/google/example/transitive/android_res.jar",
- "com/google/example/transitive/android_res_resources.jar"))
+ "com/google/example/transitive/resource.jar",
+ "com/google/example/transitive/resource_resources.jar"))
.setAndroidInfo(
androidInfoWithResourceAndJar(
- "com.google.example.transitive.android_res",
- "com/google/example/transitive/android_res/res",
- "com/google/example/transitive/android_res_resources.jar")))
+ "com.google.example.transitive.resource",
+ "com/google/example/transitive/resource/res",
+ "com/google/example/transitive/resource_resources.jar")))
.addTarget(
TargetIdeInfo.builder()
.setLabel(javaDependency)
.setKind(Kind.JAVA_LIBRARY)
- .setJavaInfo(javaInfoWithJars("com/google/example/java.jar"))
+ .setJavaInfo(javaInfoWithJars("com/google/example/libjava.jar"))
.addDependency(transitiveJavaDependency)
.addDependency(sharedJavaDependency)
.addDependency(sharedJavaDependency2)
@@ -396,56 +502,56 @@
TargetIdeInfo.builder()
.setLabel(transitiveJavaDependency)
.setKind(Kind.JAVA_LIBRARY)
- .setJavaInfo(javaInfoWithJars("com/google/example/transitive/java.jar"))
+ .setJavaInfo(javaInfoWithJars("com/google/example/libtransitive.jar"))
.addDependency(sharedJavaDependency)
.addDependency(sharedJavaDependency2))
.addTarget(
TargetIdeInfo.builder()
.setLabel(sharedJavaDependency)
.setKind(Kind.JAVA_LIBRARY)
- .setJavaInfo(javaInfoWithJars("com/google/example/shared/java.jar"))
+ .setJavaInfo(javaInfoWithJars("com/google/example/libshared.jar"))
.addDependency(sharedJavaDependency2))
.addTarget(
TargetIdeInfo.builder()
.setLabel(sharedJavaDependency2)
.setKind(Kind.JAVA_LIBRARY)
- .setJavaInfo(javaInfoWithJars("com/google/example/shared2/java.jar")))
+ .setJavaInfo(javaInfoWithJars("com/google/example/libshared2.jar")))
.addTarget(
TargetIdeInfo.builder()
.setLabel(importDependency)
.setKind(Kind.JAVA_IMPORT)
- .setJavaInfo(javaInfoWithCheckedInJars("com/google/example/import.jar")))
+ .setJavaInfo(javaInfoWithCheckedInJars("com/google/example/libimport.jar")))
.addTarget(
TargetIdeInfo.builder()
.setLabel(transitiveImportDependency)
.setKind(Kind.JAVA_IMPORT)
.setJavaInfo(
javaInfoWithCheckedInJars(
- "com/google/example/transitive/import.jar",
- "com/google/example/transitive/import2.jar")))
+ "com/google/example/transitive/libimport.jar",
+ "com/google/example/transitive/libimport2.jar")))
.addTarget(
TargetIdeInfo.builder()
.setLabel(unrelatedJava)
.setKind(Kind.JAVA_LIBRARY)
- .setJavaInfo(javaInfoWithJars("com/google/unrelated/java.jar")))
+ .setJavaInfo(javaInfoWithJars("com/google/unrelated/libjava.jar")))
.addTarget(
TargetIdeInfo.builder()
- .setLabel(unrelatedAndroidLibrary)
+ .setLabel(unrelatedAndroid)
.setKind(Kind.ANDROID_LIBRARY)
- .setJavaInfo(javaInfoWithJars("com/google/unrelated/android_lib.jar")))
+ .setJavaInfo(javaInfoWithJars("com/google/unrelated/libandroid.jar")))
.addTarget(
TargetIdeInfo.builder()
- .setLabel(unrelatedAndroidResource)
+ .setLabel(unrelatedResource)
.setKind(Kind.ANDROID_LIBRARY)
.setJavaInfo(
javaInfoWithJars(
- "com/google/unrelated/android_res.jar",
- "com/google/unrelated/android_res_resources.jar"))
+ "com/google/unrelated/libresource.jar",
+ "com/google/unrelated/resource_resources.jar"))
.setAndroidInfo(
androidInfoWithResourceAndJar(
- "com.google.unrelated.android_res",
- "com/google/unrelated/android_res/res",
- "com/google/unrelated/android_res_resources.jar")))
+ "com.google.unrelated.resource",
+ "com/google/unrelated/resource/res",
+ "com/google/unrelated/resource_resources.jar")))
.build();
}
diff --git a/aswb/tests/unittests/com/google/idea/blaze/android/rendering/BlazeRenderErrorContributorTest.java b/aswb/tests/unittests/com/google/idea/blaze/android/rendering/BlazeRenderErrorContributorTest.java
index 2b49d8c..3163f63 100644
--- a/aswb/tests/unittests/com/google/idea/blaze/android/rendering/BlazeRenderErrorContributorTest.java
+++ b/aswb/tests/unittests/com/google/idea/blaze/android/rendering/BlazeRenderErrorContributorTest.java
@@ -17,6 +17,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import com.android.tools.idea.rendering.RenderErrorContributor;
import com.android.tools.idea.rendering.RenderErrorModelFactory;
@@ -59,6 +60,7 @@
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.JavaPsiFacade;
@@ -70,7 +72,6 @@
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.ProjectScopeBuilder;
import com.intellij.psi.search.ProjectScopeBuilderImpl;
-import com.intellij.psi.util.PsiUtil.NullPsiClass;
import java.io.File;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
@@ -96,6 +97,7 @@
super.initTest(applicationServices, projectServices);
applicationServices.register(FileTypeManager.class, new MockFileTypeManager());
+ projectServices.register(ProjectFileIndex.class, mock(ProjectFileIndex.class));
projectServices.register(BuildReferenceManager.class, new MockBuildReferenceManager(project));
projectServices.register(TransitiveDependencyMap.class, new TransitiveDependencyMap(project));
projectServices.register(ProjectScopeBuilder.class, new ProjectScopeBuilderImpl(project));
@@ -651,15 +653,15 @@
ImmutableMap<String, PsiClass> classes =
ImmutableMap.of(
"com.google.example.independent.LibraryView",
- new MockPsiClass(psiManager, independentLibraryView),
+ mockPsiClass(independentLibraryView),
"com.google.example.independent.LibraryView2",
- new MockPsiClass(psiManager, independentLibraryView2),
+ mockPsiClass(independentLibraryView2),
"com.google.example.independent.Library2View",
- new MockPsiClass(psiManager, independentLibrary2View),
+ mockPsiClass(independentLibrary2View),
"com.google.example.dependent.LibraryView",
- new MockPsiClass(psiManager, dependentLibraryView),
+ mockPsiClass(dependentLibraryView),
"com.google.example.ResourceView",
- new MockPsiClass(psiManager, resourceView));
+ mockPsiClass(resourceView));
ImmutableMap<File, TargetKey> sourceToTarget =
ImmutableMap.of(
@@ -679,6 +681,14 @@
projectServices.register(SourceToTargetMap.class, new MockSourceToTargetMap(sourceToTarget));
}
+ private static PsiClass mockPsiClass(VirtualFile virtualFile) {
+ PsiFile psiFile = mock(PsiFile.class);
+ when(psiFile.getVirtualFile()).thenReturn(virtualFile);
+ PsiClass psiClass = mock(PsiClass.class);
+ when(psiClass.getContainingFile()).thenReturn(psiFile);
+ return psiClass;
+ }
+
private static class MockBlazeProjectDataManager implements BlazeProjectDataManager {
private BlazeProjectData blazeProjectData;
@@ -711,25 +721,6 @@
}
}
- private static class MockPsiClass extends NullPsiClass {
- private PsiFile psiFile;
-
- public MockPsiClass(PsiManager psiManager, VirtualFile virtualFile) {
- psiFile =
- new MockPsiFile(psiManager) {
- @Override
- public VirtualFile getVirtualFile() {
- return virtualFile;
- }
- };
- }
-
- @Override
- public PsiFile getContainingFile() {
- return psiFile;
- }
- }
-
private static class MockJavaPsiFacade extends JavaPsiFacadeImpl {
private ImmutableMap<String, PsiClass> classes;
diff --git a/aswb/tests/unittests/com/google/idea/blaze/android/run/testrecorder/BlazeConfigurationsTest.java b/aswb/tests/unittests/com/google/idea/blaze/android/run/testrecorder/BlazeConfigurationsTest.java
index b48fe02..c9114dd 100644
--- a/aswb/tests/unittests/com/google/idea/blaze/android/run/testrecorder/BlazeConfigurationsTest.java
+++ b/aswb/tests/unittests/com/google/idea/blaze/android/run/testrecorder/BlazeConfigurationsTest.java
@@ -21,7 +21,6 @@
import com.android.tools.idea.run.editor.AndroidJavaDebugger;
import com.android.tools.idea.run.editor.DeployTargetProvider;
import com.android.tools.idea.run.editor.ShowChooserTargetProvider;
-import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.gct.testrecorder.run.TestRecorderRunConfigurationProxy;
import com.google.gct.testrecorder.run.TestRecorderRunConfigurationProxyProvider;
@@ -68,6 +67,7 @@
import com.intellij.openapi.project.Project;
import java.io.File;
import java.util.List;
+import java.util.function.Predicate;
import javax.annotation.Nullable;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -278,6 +278,11 @@
}
@Override
+ public String getLanguageSupportDocumentationUrl(String relativeDocName) {
+ return null;
+ }
+
+ @Override
public boolean isBuildFile(String fileName) {
return false;
}
diff --git a/base/BUILD b/base/BUILD
index 7afe714..e51bb7f 100644
--- a/base/BUILD
+++ b/base/BUILD
@@ -3,16 +3,20 @@
java_library(
name = "base",
srcs = glob(["src/**/*.java"]),
+ javacopts = ["-Xep:FutureReturnValueIgnored:OFF"],
resources = glob(["resources/**/*"]),
visibility = ["//visibility:public"],
deps = [
"//common/actionhelper",
"//common/binaryhelper",
+ "//common/concurrency",
"//common/experiments",
"//common/formatter",
+ "//common/guava",
"//intellij_platform_sdk:plugin_api",
"//proto:proto_deps",
"//sdkcompat",
+ "//third_party/auto_value",
"@jsr305_annotations//jar",
],
)
@@ -90,6 +94,7 @@
":unit_test_utils",
"//common/experiments",
"//common/experiments:unit_test_utils",
+ "//common/guava",
"//intellij_platform_sdk:plugin_api_for_tests",
"//proto:proto_deps",
"@jsr305_annotations//jar",
diff --git a/base/src/META-INF/blaze-base.xml b/base/src/META-INF/blaze-base.xml
index f472193..5755e9e 100644
--- a/base/src/META-INF/blaze-base.xml
+++ b/base/src/META-INF/blaze-base.xml
@@ -175,8 +175,6 @@
serviceImplementation="com.google.idea.blaze.base.io.VirtualFileSystemProviderImpl"/>
<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.targetfinder.TargetFinder"
serviceImplementation="com.google.idea.blaze.base.run.targetfinder.TargetFinderImpl"/>
<applicationService serviceInterface="com.google.idea.blaze.base.command.info.BlazeInfoRunner"
@@ -350,11 +348,14 @@
<extensionPoint qualifiedName="com.google.idea.blaze.AspectStrategyProvider" interface="com.google.idea.blaze.base.sync.aspects.strategy.AspectStrategyProvider"/>
<extensionPoint qualifiedName="com.google.idea.blaze.DistributedExecutorSupport" interface="com.google.idea.blaze.base.run.DistributedExecutorSupport"/>
<extensionPoint qualifiedName="com.google.idea.blaze.FileStringParser" interface="com.google.idea.blaze.base.run.filter.FileResolver"/>
- <extensionPoint qualifiedName="com.google.idea.blaze.BlazeTestXmlFinderStrategy" interface="com.google.idea.blaze.base.run.testlogs.BlazeTestResultFinderStrategy"/>
<extensionPoint qualifiedName="com.google.idea.blaze.BlazeTestEventsHandler" interface="com.google.idea.blaze.base.run.smrunner.BlazeTestEventsHandler"/>
<extensionPoint qualifiedName="com.google.idea.blaze.AttributeSpecificStringLiteralReferenceProvider" interface="com.google.idea.blaze.base.lang.buildfile.references.AttributeSpecificStringLiteralReferenceProvider"/>
<extensionPoint qualifiedName="com.google.idea.blaze.EventLogger" interface="com.google.idea.blaze.base.logging.EventLogger"/>
<extensionPoint qualifiedName="com.google.idea.blaze.ProjectViewDefaultValueProvider" interface="com.google.idea.blaze.base.projectview.section.ProjectViewDefaultValueProvider"/>
+ <extensionPoint qualifiedName="com.google.idea.blaze.TestUiSessionProvider" interface="com.google.idea.blaze.base.run.smrunner.TestUiSessionProvider"/>
+ <extensionPoint qualifiedName="com.google.idea.blaze.BlazeLibrarySorter" interface="com.google.idea.blaze.base.sync.libraries.BlazeLibrarySorter"/>
+ <extensionPoint qualifiedName="com.google.idea.blaze.BuildSystemVersionChecker" interface="com.google.idea.blaze.base.plugin.BuildSystemVersionChecker"/>
+ <extensionPoint qualifiedName="com.google.idea.blaze.BlazeIssueParserProvider" interface="com.google.idea.blaze.base.issueparser.BlazeIssueParserProvider"/>
</extensionPoints>
<extensions defaultExtensionNs="com.google.idea.blaze">
@@ -368,17 +369,19 @@
<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"/>
- <TestTargetHeuristic implementation="com.google.idea.blaze.base.run.TargetNameHeuristic" order="first" id="TargetNameHeuristic"/>
- <TestTargetHeuristic implementation="com.google.idea.blaze.base.run.TestTargetSourcesHeuristic"/>
+ <TestTargetHeuristic implementation="com.google.idea.blaze.base.run.TargetNameHeuristic" id="TargetNameHeuristic"/>
+ <TestTargetHeuristic implementation="com.google.idea.blaze.base.run.TestTargetSourcesHeuristic" order="after TargetNameHeuristic"/>
<TestTargetHeuristic implementation="com.google.idea.blaze.base.run.TestSizeHeuristic" order="last" id="TestSizeHeuristic"/>
<RunConfigurationFactory implementation="com.google.idea.blaze.base.run.BlazeBuildTargetRunConfigurationFactory" order="last"/>
<AspectStrategyProvider implementation="com.google.idea.blaze.base.sync.aspects.strategy.AspectStrategyProviderBazel" order="last"/>
<FileStringParser implementation="com.google.idea.blaze.base.run.filter.StandardFileResolver" order="last"/>
- <BlazeTestXmlFinderStrategy implementation="com.google.idea.blaze.base.run.testlogs.TargetPathTestResultFinderStrategy"/>
- <BlazeTestEventsHandler implementation="com.google.idea.blaze.base.run.smrunner.BlazeCompositeTestEventsHandler" order="last"/>
+ <BlazeTestEventsHandler implementation="com.google.idea.blaze.base.run.smrunner.BlazeGenericTestEventsHandler" order="last"/>
<ProjectViewDefaultValueProvider implementation="com.google.idea.blaze.base.projectview.section.sections.DirectorySection$DirectoriesProjectViewDefaultValueProvider"/>
<ProjectViewDefaultValueProvider implementation="com.google.idea.blaze.base.projectview.section.sections.TargetSection$TargetsProjectViewDefaultValueProvider"/>
<ProjectViewDefaultValueProvider implementation="com.google.idea.blaze.base.projectview.section.sections.AdditionalLanguagesSection$AdditionalLanguagesDefaultValueProvider"/>
+ <PrefetchFileSource implementation="com.google.idea.blaze.base.prefetch.ProtoPrefetchFileSource"/>
+ <TestUiSessionProvider implementation="com.google.idea.blaze.base.run.smrunner.BazelTestUiSessionProvider"/>
+ <BuildSystemVersionChecker implementation="com.google.idea.blaze.base.plugin.BazelVersionChecker"/>
</extensions>
</idea-plugin>
diff --git a/base/src/com/google/idea/blaze/base/actions/BlazeBuildService.java b/base/src/com/google/idea/blaze/base/actions/BlazeBuildService.java
index 36a23e9..50abc6d 100644
--- a/base/src/com/google/idea/blaze/base/actions/BlazeBuildService.java
+++ b/base/src/com/google/idea/blaze/base/actions/BlazeBuildService.java
@@ -44,10 +44,14 @@
import com.google.idea.blaze.base.util.SaveUtil;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Key;
import java.util.List;
/** Utility to build various collections of targets. */
public class BlazeBuildService {
+ private static final Key<Long> PROJECT_LAST_BUILD_TIMESTAMP_KEY =
+ Key.create("blaze.project.last.build.timestamp");
+
public static BlazeBuildService getInstance() {
return ServiceManager.getService(BlazeBuildService.class);
}
@@ -86,6 +90,15 @@
"Make project",
"Make project completed successfully",
"Make project failed"));
+
+ // In case the user touched a file, but didn't change its content. The user will get a false
+ // positive for class file out of date. We need a way for the user to suppress the false
+ // message. Clicking the "build project" link should at least make the message go away.
+ project.putUserData(PROJECT_LAST_BUILD_TIMESTAMP_KEY, System.currentTimeMillis());
+ }
+
+ public static Long getLastBuildTimeStamp(Project project) {
+ return project.getUserData(PROJECT_LAST_BUILD_TIMESTAMP_KEY);
}
@VisibleForTesting
@@ -137,6 +150,7 @@
workspaceRoot,
projectViewSet,
blazeProjectData.blazeVersionData,
+ blazeProjectData.workspaceLanguageSettings,
shardedTargets.shardedTargets);
FileCaches.refresh(project);
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
index 92b0223..b40667c 100644
--- a/base/src/com/google/idea/blaze/base/async/executor/BlazeExecutorImpl.java
+++ b/base/src/com/google/idea/blaze/base/async/executor/BlazeExecutorImpl.java
@@ -18,6 +18,7 @@
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.common.concurrency.ConcurrencyUtil;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
@@ -25,7 +26,9 @@
public class BlazeExecutorImpl extends BlazeExecutor {
private final ListeningExecutorService executorService =
- MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(16));
+ MoreExecutors.listeningDecorator(
+ Executors.newFixedThreadPool(
+ 16, ConcurrencyUtil.namedDaemonThreadPoolFactory(BlazeExecutorImpl.class)));
@Override
public <T> ListenableFuture<T> submit(Callable<T> callable) {
diff --git a/base/src/com/google/idea/blaze/base/bazel/BazelBuildSystemProvider.java b/base/src/com/google/idea/blaze/base/bazel/BazelBuildSystemProvider.java
index 85296be..4e01815 100644
--- a/base/src/com/google/idea/blaze/base/bazel/BazelBuildSystemProvider.java
+++ b/base/src/com/google/idea/blaze/base/bazel/BazelBuildSystemProvider.java
@@ -32,6 +32,8 @@
/** Provides the bazel build system name string. */
public class BazelBuildSystemProvider implements BuildSystemProvider {
+ private static final String BAZEL_DOC_SITE = "https://ij.bazel.build/docs";
+
@Override
public BuildSystem buildSystem() {
return BuildSystem.Bazel;
@@ -64,7 +66,12 @@
@Override
public String getProjectViewDocumentationUrl() {
- return "https://ij.bazel.build/docs/project-views.html";
+ return BAZEL_DOC_SITE + "/project-views.html";
+ }
+
+ @Override
+ public String getLanguageSupportDocumentationUrl(String relativeDocName) {
+ return String.format("%s/%s.html", BAZEL_DOC_SITE, relativeDocName);
}
@Override
diff --git a/base/src/com/google/idea/blaze/base/bazel/BazelVersion.java b/base/src/com/google/idea/blaze/base/bazel/BazelVersion.java
index 19be022..ec82ddb 100644
--- a/base/src/com/google/idea/blaze/base/bazel/BazelVersion.java
+++ b/base/src/com/google/idea/blaze/base/bazel/BazelVersion.java
@@ -29,14 +29,14 @@
public class BazelVersion implements Serializable {
private static final long serialVersionUID = 1L;
- public static final BazelVersion UNKNOWN = new BazelVersion(0, 0, 0);
+ static final BazelVersion DEVELOPMENT = new BazelVersion(999, 999, 999);
private static final Pattern PATTERN = Pattern.compile("([[0-9]\\.]+)");
- public final int major;
- public final int minor;
- public final int bugfix;
+ final int major;
+ final int minor;
+ final int bugfix;
- BazelVersion(int major, int minor, int bugfix) {
+ public BazelVersion(int major, int minor, int bugfix) {
this.bugfix = bugfix;
this.minor = minor;
this.major = major;
@@ -44,21 +44,22 @@
@VisibleForTesting
static BazelVersion parseVersion(@Nullable String string) {
+ // treat all unknown / development versions as the very latest version
if (string == null) {
- return UNKNOWN;
+ return DEVELOPMENT;
}
Matcher matcher = PATTERN.matcher(string);
if (!matcher.find()) {
- return UNKNOWN;
+ return DEVELOPMENT;
}
try {
BazelVersion version = parseVersion(matcher.group(1).split("\\."));
if (version == null) {
- return UNKNOWN;
+ return DEVELOPMENT;
}
return version;
} catch (Exception e) {
- return UNKNOWN;
+ return DEVELOPMENT;
}
}
@@ -76,11 +77,16 @@
return new BazelVersion(major, minor, bugfix);
}
- public static BazelVersion parseVersion(BlazeInfo blazeInfo) {
+ static BazelVersion parseVersion(BlazeInfo blazeInfo) {
return parseVersion(blazeInfo.get(BlazeInfo.RELEASE));
}
@Override
+ public String toString() {
+ return String.format("%s.%s.%s", major, minor, bugfix);
+ }
+
+ @Override
public boolean equals(Object o) {
if (this == o) {
return true;
@@ -97,6 +103,10 @@
return Objects.hashCode(major, minor, bugfix);
}
+ public boolean isAtLeast(BazelVersion version) {
+ return isAtLeast(version.major, version.minor, version.bugfix);
+ }
+
public boolean isAtLeast(int major, int minor, int bugfix) {
return ComparisonChain.start()
.compare(this.major, major)
diff --git a/base/src/com/google/idea/blaze/base/bazel/BuildSystemProvider.java b/base/src/com/google/idea/blaze/base/bazel/BuildSystemProvider.java
index 22392d4..214bc96 100644
--- a/base/src/com/google/idea/blaze/base/bazel/BuildSystemProvider.java
+++ b/base/src/com/google/idea/blaze/base/bazel/BuildSystemProvider.java
@@ -94,6 +94,15 @@
@Nullable
String getProjectViewDocumentationUrl();
+ /**
+ * The URL providing documentation for language support, if one can be found.
+ *
+ * @param relativeDocName the path to the language doc, relative to the plugin documentation
+ * site's base URL, without the webpage's file extension.
+ */
+ @Nullable
+ String getLanguageSupportDocumentationUrl(String relativeDocName);
+
/** Check if the given filename is a valid BUILD file name. */
boolean isBuildFile(String fileName);
diff --git a/base/src/com/google/idea/blaze/base/buildmodifier/BuildFileFormatter.java b/base/src/com/google/idea/blaze/base/buildmodifier/BuildFileFormatter.java
index 5875466..52690ec 100644
--- a/base/src/com/google/idea/blaze/base/buildmodifier/BuildFileFormatter.java
+++ b/base/src/com/google/idea/blaze/base/buildmodifier/BuildFileFormatter.java
@@ -15,17 +15,27 @@
*/
package com.google.idea.blaze.base.buildmodifier;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.idea.blaze.base.async.executor.BlazeExecutor;
+import com.intellij.openapi.diagnostic.Logger;
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 java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import javax.annotation.Nullable;
/** Formats BUILD files using 'buildifier' */
public class BuildFileFormatter {
+ private static final Logger logger = Logger.getInstance(BuildFileFormatter.class);
+ // 10 seconds ought to be enough for formatting a BUILD file...
+ private static final long TIMEOUT_SECS = 10;
+
@Nullable
private static File getBuildifierBinary() {
for (BuildifierBinaryProvider provider : BuildifierBinaryProvider.EP_NAME.getExtensions()) {
@@ -37,7 +47,29 @@
return null;
}
- static String formatText(String text) {
+ /**
+ * Format the BUILD file with a timeout. The tool may be fetched from a network filesystem, and we
+ * don't want to block the UI if there is a network issue.
+ *
+ * @return formatted text, or null if there is an error or timeout
+ */
+ @Nullable
+ static String formatTextWithTimeout(String text) {
+ BlazeExecutor executor = BlazeExecutor.getInstance();
+ ListenableFuture<String> result = executor.submit(() -> formatText(text));
+ try {
+ return result.get(TIMEOUT_SECS, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return null;
+ } catch (ExecutionException | TimeoutException e) {
+ logger.warn(e);
+ return null;
+ }
+ }
+
+ @Nullable
+ private static String formatText(String text) {
try {
File buildifierBinary = getBuildifierBinary();
if (buildifierBinary == null) {
@@ -49,9 +81,9 @@
file.delete();
return formattedFile;
} catch (IOException e) {
- e.printStackTrace();
+ logger.warn(e);
}
- return text;
+ return null;
}
private static void formatFile(File file, String buildifierBinaryPath) throws IOException {
diff --git a/base/src/com/google/idea/blaze/base/buildmodifier/BuildifierDelegatingCodeStyleManager.java b/base/src/com/google/idea/blaze/base/buildmodifier/BuildifierDelegatingCodeStyleManager.java
index ca0827d..d00fcd1 100644
--- a/base/src/com/google/idea/blaze/base/buildmodifier/BuildifierDelegatingCodeStyleManager.java
+++ b/base/src/com/google/idea/blaze/base/buildmodifier/BuildifierDelegatingCodeStyleManager.java
@@ -101,7 +101,7 @@
String text, Collection<TextRange> ranges) {
ImmutableMap.Builder<TextRange, String> output = ImmutableMap.builder();
for (TextRange range : ranges) {
- String result = BuildFileFormatter.formatText(range.substring(text));
+ String result = BuildFileFormatter.formatTextWithTimeout(range.substring(text));
if (result == null) {
return ImmutableMap.of();
}
diff --git a/base/src/com/google/idea/blaze/base/buildmodifier/FileSaveHandler.java b/base/src/com/google/idea/blaze/base/buildmodifier/FileSaveHandler.java
index daeae84..0b2e14b 100644
--- a/base/src/com/google/idea/blaze/base/buildmodifier/FileSaveHandler.java
+++ b/base/src/com/google/idea/blaze/base/buildmodifier/FileSaveHandler.java
@@ -45,7 +45,10 @@
int lines = document.getLineCount();
if (lines > 0) {
String text = document.getText();
- String formattedText = BuildFileFormatter.formatText(text);
+ String formattedText = BuildFileFormatter.formatTextWithTimeout(text);
+ if (formattedText == null) {
+ return;
+ }
updateDocument(document, formattedText);
}
}
diff --git a/base/src/com/google/idea/blaze/base/buildmodifier/FileSystemModifier.java b/base/src/com/google/idea/blaze/base/buildmodifier/FileSystemModifier.java
deleted file mode 100644
index 96719c1..0000000
--- a/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 java.io.File;
-import javax.annotation.Nullable;
-import org.jetbrains.annotations.NotNull;
-
-/** 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
deleted file mode 100644
index 7e873ee..0000000
--- a/base/src/com/google/idea/blaze/base/buildmodifier/FileSystemModifierImpl.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.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 javax.annotation.Nullable;
-import org.jetbrains.annotations.NotNull;
-
-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/BlazeFlags.java b/base/src/com/google/idea/blaze/base/command/BlazeFlags.java
index a44b056..6cb3b09 100644
--- a/base/src/com/google/idea/blaze/base/command/BlazeFlags.java
+++ b/base/src/com/google/idea/blaze/base/command/BlazeFlags.java
@@ -15,12 +15,11 @@
*/
package com.google.idea.blaze.base.command;
-import com.google.common.collect.ImmutableList;
+import com.google.common.annotations.VisibleForTesting;
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.google.idea.blaze.base.projectview.section.sections.SyncFlagsSection;
import com.google.idea.common.experiments.BoolExperiment;
import com.intellij.execution.configurations.ParametersList;
import com.intellij.openapi.project.Project;
@@ -47,39 +46,50 @@
public static final String DISABLE_TEST_SHARDING = "--test_sharding_strategy=disabled";
// Filters the unit tests that are run (used with regexp for Java/Robolectric tests).
public static final String TEST_FILTER = "--test_filter";
- // 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";
+ public static final String LOCAL_TEST_EXECUTION = "--test_strategy=local";
public static final String EXPERIMENTAL_SHOW_ARTIFACTS = "--experimental_show_artifacts";
- public static List<String> buildFlags(Project project, ProjectViewSet projectViewSet) {
- BuildSystem buildSystem = Blaze.getBuildSystem(project);
+ public static final String DELETED_PACKAGES = "--deleted_packages";
+
+ public static List<String> blazeFlags(
+ Project project,
+ ProjectViewSet projectViewSet,
+ BlazeCommandName command,
+ BlazeInvocationContext context) {
List<String> flags = Lists.newArrayList();
for (BuildFlagsProvider buildFlagsProvider : BuildFlagsProvider.EP_NAME.getExtensions()) {
- buildFlagsProvider.addBuildFlags(buildSystem, projectViewSet, flags);
+ buildFlagsProvider.addBuildFlags(project, projectViewSet, command, flags);
}
flags.addAll(expandBuildFlags(projectViewSet.listItems(BuildFlagsSection.KEY)));
+ if (context == BlazeInvocationContext.Sync) {
+ flags.addAll(expandBuildFlags(projectViewSet.listItems(SyncFlagsSection.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);
- }
+ public static final String ADB_PATH = "--adb_path";
+ public static final String DEVICE = "--device";
// Pass-through arg for sending test arguments.
public static final String TEST_ARG = "--test_arg=";
private static final String TOOL_TAG = "--tool_tag=ijwb:";
+ // TODO: remove these when mobile-install V1 is obsolete
+ // 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";
+ // Pass-through arg for sending adb options during mobile-install.
+ public static final String ADB_ARG = "--adb_arg=";
+ public static final String ADB = "--adb";
+
// We add this to every single BlazeCommand instance. It's for tracking usage.
+ @VisibleForTesting
public static String getToolTagFlag() {
String platformPrefix = PlatformUtils.getPlatformPrefix();
diff --git a/base/src/com/google/idea/blaze/base/command/BlazeInvocationContext.java b/base/src/com/google/idea/blaze/base/command/BlazeInvocationContext.java
new file mode 100644
index 0000000..b1517b8
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/command/BlazeInvocationContext.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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;
+
+/** The context in which a blaze command is invoked. */
+public enum BlazeInvocationContext {
+ RunConfiguration,
+ Sync,
+}
diff --git a/base/src/com/google/idea/blaze/base/command/BuildFlagsProvider.java b/base/src/com/google/idea/blaze/base/command/BuildFlagsProvider.java
index 469d663..3d1e133 100644
--- a/base/src/com/google/idea/blaze/base/command/BuildFlagsProvider.java
+++ b/base/src/com/google/idea/blaze/base/command/BuildFlagsProvider.java
@@ -16,15 +16,17 @@
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 com.intellij.openapi.project.Project;
import java.util.List;
-/** Provides additional build flags for bazel/blaze commands. */
+/** Provides additional 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);
+ /** Flags to add to blaze/bazel invocations of the given type. */
+ void addBuildFlags(
+ Project project, ProjectViewSet projectViewSet, BlazeCommandName command, 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
index fcdfc70..afb4157 100644
--- a/base/src/com/google/idea/blaze/base/command/BuildFlagsProviderImpl.java
+++ b/base/src/com/google/idea/blaze/base/command/BuildFlagsProviderImpl.java
@@ -16,7 +16,7 @@
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.project.Project;
import java.util.List;
/** Flags added to blaze/bazel build commands. */
@@ -24,7 +24,10 @@
@Override
public void addBuildFlags(
- BuildSystem buildSystem, ProjectViewSet projectViewSet, List<String> flags) {
+ Project project,
+ ProjectViewSet projectViewSet,
+ BlazeCommandName command,
+ List<String> flags) {
flags.add("--curses=no");
flags.add("--color=no");
flags.add("--noexperimental_ui");
diff --git a/base/src/com/google/idea/blaze/base/command/buildresult/BuildEventProtocolOutputReader.java b/base/src/com/google/idea/blaze/base/command/buildresult/BuildEventProtocolOutputReader.java
new file mode 100644
index 0000000..0320715
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/command/buildresult/BuildEventProtocolOutputReader.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.buildresult;
+
+import static com.google.idea.common.guava.GuavaHelper.toImmutableList;
+import static com.google.idea.common.guava.GuavaHelper.toImmutableSet;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+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.testlogs.BlazeTestResult;
+import com.google.idea.blaze.base.run.testlogs.BlazeTestResult.TestStatus;
+import com.google.idea.blaze.base.run.testlogs.BlazeTestResults;
+import com.google.idea.blaze.base.util.UrlUtil;
+import com.google.repackaged.devtools.build.lib.buildeventstream.BuildEventStreamProtos;
+import com.google.repackaged.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildEventId.NamedSetOfFilesId;
+import com.google.repackaged.devtools.build.lib.buildeventstream.BuildEventStreamProtos.OutputGroup;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Predicate;
+import javax.annotation.Nullable;
+
+/** Utility methods for reading Blaze's build event procotol output, in proto form. */
+public final class BuildEventProtocolOutputReader {
+
+ private BuildEventProtocolOutputReader() {}
+
+ /**
+ * Reads all test results from a BEP-formatted {@link InputStream}.
+ *
+ * @throws IOException if the BEP {@link InputStream} is incorrectly formatted
+ */
+ public static BlazeTestResults parseTestResults(InputStream inputStream) throws IOException {
+ Map<String, Kind> labelToTargetKind = new HashMap<>();
+ ImmutableList.Builder<BlazeTestResult> results = ImmutableList.builder();
+ BuildEventStreamProtos.BuildEvent event;
+ while ((event = BuildEventStreamProtos.BuildEvent.parseDelimitedFrom(inputStream)) != null) {
+ switch (event.getId().getIdCase()) {
+ case TARGET_COMPLETED:
+ String label = event.getId().getTargetCompleted().getLabel();
+ Kind kind = parseTargetKind(event.getCompleted().getTargetKind());
+ if (kind != null) {
+ labelToTargetKind.put(label, kind);
+ }
+ continue;
+ case TARGET_CONFIGURED:
+ label = event.getId().getTargetConfigured().getLabel();
+ kind = parseTargetKind(event.getConfigured().getTargetKind());
+ if (kind != null) {
+ labelToTargetKind.put(label, kind);
+ }
+ continue;
+ case TEST_RESULT:
+ label = event.getId().getTestResult().getLabel();
+ results.add(parseTestResult(label, labelToTargetKind.get(label), event.getTestResult()));
+ continue;
+ default: // continue
+ }
+ }
+ return BlazeTestResults.fromFlatList(results.build());
+ }
+
+ /** Convert BEP 'target_kind' to our internal format */
+ @Nullable
+ private static Kind parseTargetKind(String kind) {
+ return kind.endsWith(" rule")
+ ? Kind.fromString(kind.substring(0, kind.length() - " rule".length()))
+ : null;
+ }
+
+ private static BlazeTestResult parseTestResult(
+ String label, @Nullable Kind kind, BuildEventStreamProtos.TestResult testResult) {
+ ImmutableSet<File> files =
+ testResult
+ .getTestActionOutputList()
+ .stream()
+ .map(file -> parseFile(file, path -> path.endsWith(".xml")))
+ .filter(Objects::nonNull)
+ .collect(toImmutableSet());
+ return BlazeTestResult.create(
+ Label.create(label), kind, convertTestStatus(testResult.getStatus()), files);
+ }
+
+ private static TestStatus convertTestStatus(BuildEventStreamProtos.TestStatus protoStatus) {
+ if (protoStatus == BuildEventStreamProtos.TestStatus.UNRECOGNIZED) {
+ // for forward-compatibility
+ return TestStatus.NO_STATUS;
+ }
+ return TestStatus.valueOf(protoStatus.name());
+ }
+
+ /**
+ * Reads all output files listed in the BEP output that satisfy the specified predicate.
+ *
+ * @throws IOException if the BEP output file is incorrectly formatted
+ */
+ static ImmutableList<File> parseAllOutputFilenames(
+ InputStream inputStream, Predicate<String> fileFilter) throws IOException {
+ ImmutableList.Builder<File> files = ImmutableList.builder();
+ BuildEventStreamProtos.BuildEvent event;
+ while ((event = BuildEventStreamProtos.BuildEvent.parseDelimitedFrom(inputStream)) != null) {
+ files.addAll(parseFilenames(event, fileFilter));
+ }
+ return files.build();
+ }
+
+ /**
+ * Reads all artifacts associated with the given target that satisfy the specified predicate.
+ *
+ * @throws IOException if the BEP output file is incorrectly formatted
+ */
+ static ImmutableList<File> parseArtifactsForTarget(
+ InputStream inputStream, Label label, Predicate<String> fileFilter) throws IOException {
+ Map<String, List<BuildEventStreamProtos.File>> fileSets = new HashMap<>();
+ List<String> fileSetsForLabel = new ArrayList<>();
+ BuildEventStreamProtos.BuildEvent event;
+ while ((event = BuildEventStreamProtos.BuildEvent.parseDelimitedFrom(inputStream)) != null) {
+ if (event.getId().hasNamedSet() && event.hasNamedSetOfFiles()) {
+ fileSets.put(
+ event.getId().getNamedSet().getId(), event.getNamedSetOfFiles().getFilesList());
+ } else if (isTargetCompletedEvent(event, label)) {
+ fileSetsForLabel.addAll(getTargetFileSets(event));
+ }
+ }
+ return fileSetsForLabel
+ .stream()
+ .map(fileSets::get)
+ .flatMap(List::stream)
+ .map(file -> parseFile(file, fileFilter))
+ .collect(toImmutableList());
+ }
+
+ private static boolean isTargetCompletedEvent(
+ BuildEventStreamProtos.BuildEvent event, Label label) {
+ return event.getId().hasTargetCompleted()
+ && event.hasCompleted()
+ && label.toString().equals(event.getId().getTargetCompleted().getLabel());
+ }
+
+ /** Returns all file set IDs associated with the given target completed event. */
+ private static ImmutableList<String> getTargetFileSets(BuildEventStreamProtos.BuildEvent event) {
+ if (!event.hasCompleted()) {
+ return ImmutableList.of();
+ }
+ return event
+ .getCompleted()
+ .getOutputGroupList()
+ .stream()
+ .map(OutputGroup::getFileSetsList)
+ .flatMap(List::stream)
+ .map(NamedSetOfFilesId::getId)
+ .collect(toImmutableList());
+ }
+
+ /**
+ * If this is a NamedSetOfFiles event, reads all associated output files. Otherwise returns an
+ * empty list.
+ */
+ private static ImmutableList<File> parseFilenames(
+ BuildEventStreamProtos.BuildEvent event, Predicate<String> fileFilter) {
+ if (!event.hasNamedSetOfFiles()) {
+ return ImmutableList.of();
+ }
+ return event
+ .getNamedSetOfFiles()
+ .getFilesList()
+ .stream()
+ .map(f -> parseFile(f, fileFilter))
+ .filter(Objects::nonNull)
+ .collect(toImmutableList());
+ }
+
+ @Nullable
+ private static File parseFile(BuildEventStreamProtos.File file, Predicate<String> fileFilter) {
+ String filePath = UrlUtil.urlToFilePath(file.getUri());
+ return filePath != null && fileFilter.test(filePath) ? new File(filePath) : null;
+ }
+}
diff --git a/base/src/com/google/idea/blaze/base/command/buildresult/BuildEventProtocolUtils.java b/base/src/com/google/idea/blaze/base/command/buildresult/BuildEventProtocolUtils.java
new file mode 100644
index 0000000..6cdf791
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/command/buildresult/BuildEventProtocolUtils.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.buildresult;
+
+import com.google.common.collect.ImmutableList;
+import java.io.File;
+import java.util.UUID;
+
+/**
+ * Utility methods for accessing blaze build event data via the build event protocol (BEP for
+ * short).
+ */
+public final class BuildEventProtocolUtils {
+
+ // Instructs BEP to use local file paths (file://...) rather than objfs blobids.
+ private static final String LOCAL_FILE_PATHS =
+ "--noexperimental_build_event_binary_file_path_conversion";
+
+ private BuildEventProtocolUtils() {}
+
+ /**
+ * Creates a temporary output file to write the BEP data to. Callers are responsible for deleting
+ * this file after use.
+ */
+ public static File createTempOutputFile() {
+ File tempDir = new File(System.getProperty("java.io.tmpdir"));
+ String suffix = UUID.randomUUID().toString();
+ String fileName = "intellij-bep-" + suffix;
+ File tempFile = new File(tempDir, fileName);
+ // Callers should delete this file immediately after use. Add a shutdown hook as well, in case
+ // the application exits before then.
+ tempFile.deleteOnExit();
+ return tempFile;
+ }
+
+ /** Returns a build flag instructing blaze to write build events to the given output file. */
+ public static ImmutableList<String> getBuildFlags(File outputFile) {
+ return ImmutableList.of(
+ "--experimental_build_event_binary_file=" + outputFile.getPath(), LOCAL_FILE_PATHS);
+ }
+}
diff --git a/base/src/com/google/idea/blaze/base/command/buildresult/BuildResultHelper.java b/base/src/com/google/idea/blaze/base/command/buildresult/BuildResultHelper.java
index 9adf5e2..cce2e00 100644
--- a/base/src/com/google/idea/blaze/base/command/buildresult/BuildResultHelper.java
+++ b/base/src/com/google/idea/blaze/base/command/buildresult/BuildResultHelper.java
@@ -17,26 +17,25 @@
import com.google.common.collect.ImmutableList;
import com.google.idea.blaze.base.async.process.LineProcessingOutputStream.LineProcessor;
-import com.google.idea.common.experiments.BoolExperiment;
+import com.google.idea.blaze.base.model.BlazeVersionData;
+import com.google.idea.blaze.base.model.primitives.Label;
+import java.io.Closeable;
import java.io.File;
import java.io.OutputStream;
import java.util.List;
import java.util.function.Predicate;
/** Assists in getting build artifacts from a build operation. */
-public interface BuildResultHelper {
- // This experiment does *not* work yet and should remain off
- BoolExperiment USE_BEP = new BoolExperiment("use.bep", false);
+public interface BuildResultHelper extends Closeable {
/**
* Constructs a new build result helper.
*
+ * @param blazeVersion The blaze version used for the build invocation.
* @param files A filter for the output artifacts you are interested in.
*/
- static BuildResultHelper forFiles(Predicate<String> files) {
- return USE_BEP.getValue()
- ? new BuildResultHelperBep(files)
- : new BuildResultHelperStderr(files);
+ static BuildResultHelper forFiles(BlazeVersionData blazeVersion, Predicate<String> files) {
+ return new BuildResultHelperBep(files);
}
/**
@@ -62,4 +61,15 @@
* @return The build artifacts from the build operation.
*/
ImmutableList<File> getBuildArtifacts();
+
+ /**
+ * Returns the build artifacts, attempting to filter out all artifacts not directly produced by
+ * the specified target. Some implementations may return artifacts produced by other targets.
+ *
+ * <p>May only be called once the build is complete, or no artifacts will be returned.
+ */
+ ImmutableList<File> getBuildArtifactsForTarget(Label target);
+
+ @Override
+ void close();
}
diff --git a/base/src/com/google/idea/blaze/base/command/buildresult/BuildResultHelperBep.java b/base/src/com/google/idea/blaze/base/command/buildresult/BuildResultHelperBep.java
index 8a32e85..691e08d 100644
--- a/base/src/com/google/idea/blaze/base/command/buildresult/BuildResultHelperBep.java
+++ b/base/src/com/google/idea/blaze/base/command/buildresult/BuildResultHelperBep.java
@@ -18,9 +18,7 @@
import com.google.common.collect.ImmutableList;
import com.google.idea.blaze.base.async.process.LineProcessingOutputStream;
import com.google.idea.blaze.base.async.process.LineProcessingOutputStream.LineProcessor;
-import com.google.repackaged.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildEvent;
-import com.google.repackaged.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildEventId;
-import com.google.repackaged.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildEventId.IdCase;
+import com.google.idea.blaze.base.model.primitives.Label;
import com.intellij.openapi.diagnostic.Logger;
import java.io.BufferedInputStream;
import java.io.File;
@@ -29,7 +27,7 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
-import java.util.UUID;
+import java.util.Optional;
import java.util.function.Predicate;
/**
@@ -39,6 +37,7 @@
* build events.
*/
class BuildResultHelperBep implements BuildResultHelper {
+
private static final Logger logger = Logger.getInstance(BuildResultHelperBep.class);
private final File outputFile;
private final Predicate<String> fileFilter;
@@ -46,15 +45,12 @@
BuildResultHelperBep(Predicate<String> fileFilter) {
this.fileFilter = fileFilter;
- File tempDir = new File(System.getProperty("java.io.tmpdir"));
- String suffix = UUID.randomUUID().toString();
- String fileName = "intellij-bep-" + suffix;
- this.outputFile = new File(tempDir, fileName);
+ outputFile = BuildEventProtocolUtils.createTempOutputFile();
}
@Override
public List<String> getBuildFlags() {
- return ImmutableList.of("--experimental_build_event_binary_file=" + outputFile.getPath());
+ return BuildEventProtocolUtils.getBuildFlags(outputFile);
}
@Override
@@ -65,34 +61,47 @@
@Override
public ImmutableList<File> getBuildArtifacts() {
if (result == null) {
- result = readResult();
+ result =
+ readResult(
+ input ->
+ BuildEventProtocolOutputReader.parseAllOutputFilenames(input, fileFilter))
+ .orElse(ImmutableList.of());
}
return result;
}
- private ImmutableList<File> readResult() {
- ImmutableList.Builder<File> result = ImmutableList.builder();
+ @Override
+ public ImmutableList<File> getBuildArtifactsForTarget(Label target) {
+ if (result == null) {
+ result =
+ readResult(
+ input ->
+ BuildEventProtocolOutputReader.parseArtifactsForTarget(
+ input, target, fileFilter))
+ .orElse(ImmutableList.of());
+ }
+ return result;
+ }
+
+ private <V> Optional<V> readResult(BepReader<V> readAction) {
try (InputStream inputStream = new BufferedInputStream(new FileInputStream(outputFile))) {
- BuildEvent buildEvent;
- while ((buildEvent = BuildEvent.parseDelimitedFrom(inputStream)) != null) {
- BuildEventId buildEventId = buildEvent.getId();
- // Note: This doesn't actually work. BEP does not issue these for actions
- // that don't execute during the build, so we can't find the files
- // for a no-op build the way we can for --experimental_show_artifacts
- if (buildEventId.getIdCase() == IdCase.ACTION_COMPLETED) {
- String output = buildEventId.getActionCompleted().getPrimaryOutput();
- if (fileFilter.test(output)) {
- result.add(new File(output));
- }
- }
- }
+ return Optional.of(readAction.read(inputStream));
} catch (IOException e) {
logger.error(e);
- return ImmutableList.of();
+ return Optional.empty();
+ } finally {
+ close();
}
+ }
+
+ @Override
+ public void close() {
if (!outputFile.delete()) {
logger.warn("Could not delete BEP output file: " + outputFile);
}
- return result.build();
+ }
+
+ private interface BepReader<V> {
+ V read(InputStream inputStream) throws IOException;
}
}
diff --git a/base/src/com/google/idea/blaze/base/command/buildresult/BuildResultHelperStderr.java b/base/src/com/google/idea/blaze/base/command/buildresult/BuildResultHelperStderr.java
deleted file mode 100644
index ef34dd6..0000000
--- a/base/src/com/google/idea/blaze/base/command/buildresult/BuildResultHelperStderr.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright 2017 The Bazel Authors. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.buildresult;
-
-import com.google.common.collect.ImmutableList;
-import com.google.idea.blaze.base.async.process.LineProcessingOutputStream;
-import com.google.idea.blaze.base.async.process.LineProcessingOutputStream.LineProcessor;
-import com.google.idea.blaze.base.command.BlazeFlags;
-import java.io.File;
-import java.io.OutputStream;
-import java.util.List;
-import java.util.function.Predicate;
-
-class BuildResultHelperStderr implements BuildResultHelper {
- private final ImmutableList.Builder<File> buildArtifacts = ImmutableList.builder();
- private final ExperimentalShowArtifactsLineProcessor experimentalShowArtifactsLineProcessor;
- private ImmutableList<File> result;
-
- BuildResultHelperStderr(Predicate<String> fileFilter) {
- experimentalShowArtifactsLineProcessor =
- new ExperimentalShowArtifactsLineProcessor(buildArtifacts, fileFilter);
- }
-
- @Override
- public List<String> getBuildFlags() {
- return ImmutableList.of(BlazeFlags.EXPERIMENTAL_SHOW_ARTIFACTS);
- }
-
- @Override
- public OutputStream stderr(LineProcessor... lineProcessors) {
- return LineProcessingOutputStream.of(
- ImmutableList.<LineProcessor>builder()
- .add(experimentalShowArtifactsLineProcessor)
- .add(lineProcessors)
- .build());
- }
-
- @Override
- public ImmutableList<File> getBuildArtifacts() {
- if (result == null) {
- result = buildArtifacts.build();
- }
- return result;
- }
-}
diff --git a/base/src/com/google/idea/blaze/base/command/info/BlazeConfigurationHandler.java b/base/src/com/google/idea/blaze/base/command/info/BlazeConfigurationHandler.java
new file mode 100644
index 0000000..ea99577
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/command/info/BlazeConfigurationHandler.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 java.io.File;
+import javax.annotation.Nullable;
+
+/**
+ * Encodes the default configuration for a given Blaze sync, and provides configuration-related
+ * helper methods.
+ */
+public class BlazeConfigurationHandler {
+
+ public final String defaultConfigurationPathComponent;
+ private final String blazeOutPath;
+
+ public BlazeConfigurationHandler(BlazeInfo blazeInfo) {
+ // Would be simpler to use 'output_path' instead, but there's a Bazel-side bug causing that to
+ // point to the wrong place. Instead derive 'output_path' from 'blaze-out'.
+ File blazeOutDir = blazeInfo.getBlazeBinDirectory().getParentFile().getParentFile();
+ blazeOutPath = blazeOutDir + File.separator;
+ defaultConfigurationPathComponent =
+ getConfigurationPathComponent(blazeInfo.getBlazeBinDirectory());
+ assert (defaultConfigurationPathComponent != null);
+ }
+
+ @Nullable
+ public String getConfigurationPathComponent(File artifact) {
+ if (!artifact.getPath().startsWith(blazeOutPath)) {
+ return null;
+ }
+ String relativePath = artifact.getPath().substring(blazeOutPath.length());
+ int endIndex = relativePath.indexOf(File.separatorChar);
+ return endIndex == -1 ? relativePath : relativePath.substring(0, endIndex);
+ }
+}
diff --git a/base/src/com/google/idea/blaze/base/command/info/BlazeInfo.java b/base/src/com/google/idea/blaze/base/command/info/BlazeInfo.java
index bf9d694..479f373 100644
--- a/base/src/com/google/idea/blaze/base/command/info/BlazeInfo.java
+++ b/base/src/com/google/idea/blaze/base/command/info/BlazeInfo.java
@@ -30,6 +30,7 @@
public static final String PACKAGE_PATH_KEY = "package_path";
public static final String BUILD_LANGUAGE = "build-language";
public static final String OUTPUT_BASE_KEY = "output_base";
+ public static final String OUTPUT_PATH_KEY = "output_path";
public static final String MASTER_LOG = "master-log";
public static final String COMMAND_LOG = "command_log";
public static final String RELEASE = "release";
diff --git a/base/src/com/google/idea/blaze/base/console/BlazeConsoleView.java b/base/src/com/google/idea/blaze/base/console/BlazeConsoleView.java
index 58162c5..84a54f4 100644
--- a/base/src/com/google/idea/blaze/base/console/BlazeConsoleView.java
+++ b/base/src/com/google/idea/blaze/base/console/BlazeConsoleView.java
@@ -37,10 +37,8 @@
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.ui.content.Content;
import com.intellij.ui.content.ContentFactory;
-import java.awt.BorderLayout;
import javax.annotation.Nullable;
import javax.swing.JComponent;
-import javax.swing.JPanel;
import org.jetbrains.annotations.NotNull;
class BlazeConsoleView implements Disposable {
@@ -55,14 +53,12 @@
@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) {
@@ -106,7 +102,6 @@
group.add(new StopAction());
JComponent layoutComponent = layoutUi.getComponent();
- myConsolePanel.add(layoutComponent, BorderLayout.CENTER);
//noinspection ConstantConditions
Content content =
@@ -126,11 +121,6 @@
@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);
diff --git a/base/src/com/google/idea/blaze/base/ide/NewBlazePackageAction.java b/base/src/com/google/idea/blaze/base/ide/NewBlazePackageAction.java
index 19c2b10..361f0ff 100644
--- a/base/src/com/google/idea/blaze/base/ide/NewBlazePackageAction.java
+++ b/base/src/com/google/idea/blaze/base/ide/NewBlazePackageAction.java
@@ -20,7 +20,6 @@
import com.google.common.collect.Lists;
import com.google.idea.blaze.base.actions.BlazeProjectAction;
import com.google.idea.blaze.base.buildmodifier.BuildFileModifier;
-import com.google.idea.blaze.base.buildmodifier.FileSystemModifier;
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;
@@ -29,7 +28,6 @@
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.settings.Blaze;
@@ -42,6 +40,8 @@
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.LangDataKeys;
import com.intellij.openapi.actionSystem.Presentation;
+import com.intellij.openapi.application.Result;
+import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
@@ -52,7 +52,9 @@
import com.intellij.psi.PsiManager;
import com.intellij.util.PlatformIcons;
import java.io.File;
+import java.io.IOException;
import java.util.List;
+import java.util.Optional;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.jetbrains.annotations.NotNull;
@@ -70,91 +72,81 @@
protected void actionPerformedInBlazeProject(Project project, AnActionEvent event) {
final IdeView view = event.getData(LangDataKeys.IDE_VIEW);
Scope.root(
- new ScopedOperation() {
- @Override
- public void execute(@NotNull final BlazeContext context) {
- 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
- logger.assertTrue(newRule != null);
- logger.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
- logger.assertTrue(virtualFile != null);
- PsiFile psiFile = PsiManager.getInstance(project).findFile(virtualFile);
- view.selectElement(psiFile);
+ context -> {
+ 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
+ logger.assertTrue(newRule != null);
+ logger.assertTrue(newRuleKind != null);
+
+ context.output(
+ new StatusOutput(
+ String.format("Setting up a new %s package", Blaze.buildSystemName(project))));
+
+ Optional<VirtualFile> virtualFile =
+ createPackageOnDisk(project, context, newRule, newRuleKind);
+ if (!virtualFile.isPresent()) {
+ return;
+ }
+ PsiFile psiFile = PsiManager.getInstance(project).findFile(virtualFile.get());
+ view.selectElement(psiFile);
});
}
- private static Boolean createPackageOnDisk(
- @NotNull Project project,
- @NotNull BlazeContext context,
- @NotNull Label newRule,
- @NotNull Kind ruleKind) {
- LocalHistoryAction action;
+ private Optional<VirtualFile> createPackageOnDisk(
+ Project project, BlazeContext context, Label newRule, Kind ruleKind) {
- String actionName =
+ String commandName =
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 new WriteCommandAction<Optional<VirtualFile>>(project, commandName) {
- return true;
+ @Override
+ protected void run(@NotNull Result<Optional<VirtualFile>> result) throws Throwable {
+ LocalHistory localHistory = LocalHistory.getInstance();
+ LocalHistoryAction action = localHistory.startAction(commandName);
+
+ try {
+ WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProject(project);
+ File dir = workspaceRoot.fileForPath(newRule.blazePackage());
+ try {
+ VirtualFile newDirectory = VfsUtil.createDirectories(dir.getPath());
+ VirtualFile newFile = newDirectory.createChildData(this, BUILD_FILE_NAME);
+ BuildFileModifier buildFileModifier = BuildFileModifier.getInstance();
+ buildFileModifier.addRule(project, context, newRule, ruleKind);
+ result.setResult(Optional.of(newFile));
+ } catch (IOException e) {
+ String errorMessage = "Error creating new package: " + e.getMessage();
+ context.output(PrintOutput.error(errorMessage));
+ logger.warn("Error creating new package", e);
+ result.setResult(Optional.empty());
+ }
+ } finally {
+ action.finish();
+ }
+ }
+ }.execute().getResultObject();
}
@Override
- protected void updateForBlazeProject(Project project, @NotNull AnActionEvent event) {
+ protected void updateForBlazeProject(Project project, AnActionEvent event) {
Presentation presentation = event.getPresentation();
if (isEnabled(event)) {
String text = String.format("New %s Package", Blaze.buildSystemName(project));
diff --git a/base/src/com/google/idea/blaze/base/ide/NewBlazePackageDialog.java b/base/src/com/google/idea/blaze/base/ide/NewBlazePackageDialog.java
index 7317f31..9615339 100644
--- a/base/src/com/google/idea/blaze/base/ide/NewBlazePackageDialog.java
+++ b/base/src/com/google/idea/blaze/base/ide/NewBlazePackageDialog.java
@@ -42,25 +42,24 @@
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JPanel;
-import org.jetbrains.annotations.NotNull;
class NewBlazePackageDialog extends DialogWrapper {
private static final Logger logger = Logger.getInstance(NewBlazePackageDialog.class);
- @NotNull private final Project project;
- @NotNull private final PsiDirectory parentDirectory;
+ private final Project project;
+ 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);
+ private final JPanel component = new JPanel(new GridBagLayout());
+ private final JBLabel packageLabel = new JBLabel("Package name:");
+ private final JBTextField packageNameField = new JBTextField(TEXT_FIELD_LENGTH);
+ private final NewRuleUI newRuleUI = new NewRuleUI(TEXT_FIELD_LENGTH);
- public NewBlazePackageDialog(@NotNull Project project, @NotNull PsiDirectory currentDirectory) {
+ public NewBlazePackageDialog(Project project, PsiDirectory currentDirectory) {
super(project);
this.project = project;
this.parentDirectory = currentDirectory;
@@ -72,16 +71,21 @@
component.add(packageLabel);
component.add(packageNameField, UiUtil.getFillLineConstraints(UI_INDENT_LEVEL));
newRuleUI.fillUI(component, UI_INDENT_LEVEL);
+ newRuleUI.syncRuleNameTo(packageNameField);
UiUtil.fillBottom(component);
init();
}
- @Nullable
@Override
protected JComponent createCenterPanel() {
return component;
}
+ @Override
+ public JComponent getPreferredFocusedComponent() {
+ return packageNameField;
+ }
+
@Nullable
@Override
protected ValidationInfo doValidate() {
@@ -126,7 +130,7 @@
super.doOKAction();
}
- private void showErrorDialog(@NotNull String message) {
+ private void showErrorDialog(String message) {
String title = CommonBundle.getErrorTitle();
Icon icon = Messages.getErrorIcon();
Messages.showMessageDialog(component, message, title, icon);
diff --git a/base/src/com/google/idea/blaze/base/ide/NewBlazeRuleDialog.java b/base/src/com/google/idea/blaze/base/ide/NewBlazeRuleDialog.java
index ae43540..30c3f37 100644
--- a/base/src/com/google/idea/blaze/base/ide/NewBlazeRuleDialog.java
+++ b/base/src/com/google/idea/blaze/base/ide/NewBlazeRuleDialog.java
@@ -24,16 +24,20 @@
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.history.LocalHistory;
+import com.intellij.history.LocalHistoryAction;
+import com.intellij.openapi.application.Result;
+import com.intellij.openapi.command.WriteCommandAction;
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;
+import org.jetbrains.annotations.NotNull;
class NewBlazeRuleDialog extends DialogWrapper {
private static final int UI_INDENT = 0;
@@ -46,7 +50,6 @@
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);
@@ -62,9 +65,6 @@
setOKButtonText("Create");
setCancelButtonText("Cancel");
- component.setPreferredSize(componentSize);
- component.setMinimumSize(componentSize);
-
newRuleUI.fillUI(component, UI_INDENT);
UiUtil.fillBottom(component);
@@ -93,7 +93,22 @@
workspaceRoot.workspacePathFor(new File(buildFile.getParent().getPath()));
Label newRule = Label.create(workspacePath, targetName);
BuildFileModifier buildFileModifier = BuildFileModifier.getInstance();
- boolean success = buildFileModifier.addRule(project, context, newRule, ruleKind);
+
+ String commandName = String.format("Add %s %s rule '%s'", buildSystemName, ruleKind, newRule);
+
+ boolean success =
+ new WriteCommandAction<Boolean>(project, commandName) {
+ @Override
+ protected void run(@NotNull Result<Boolean> result) throws Throwable {
+ LocalHistory localHistory = LocalHistory.getInstance();
+ LocalHistoryAction action = localHistory.startAction(commandName);
+ try {
+ result.setResult(buildFileModifier.addRule(project, context, newRule, ruleKind));
+ } finally {
+ action.finish();
+ }
+ }
+ }.execute().getResultObject();
if (success) {
super.doOKAction();
diff --git a/base/src/com/google/idea/blaze/base/ide/NewRuleUI.java b/base/src/com/google/idea/blaze/base/ide/NewRuleUI.java
index bf0c26a..a2d95bc 100644
--- a/base/src/com/google/idea/blaze/base/ide/NewRuleUI.java
+++ b/base/src/com/google/idea/blaze/base/ide/NewRuleUI.java
@@ -15,54 +15,103 @@
*/
package com.google.idea.blaze.base.ide;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.idea.blaze.base.model.primitives.Kind;
import com.google.idea.blaze.base.model.primitives.TargetName;
import com.google.idea.blaze.base.ui.BlazeValidationError;
import com.google.idea.blaze.base.ui.UiUtil;
import com.intellij.ide.IdeBundle;
+import com.intellij.ide.util.PropertiesComponent;
import com.intellij.openapi.ui.ComboBox;
import com.intellij.openapi.ui.ValidationInfo;
+import com.intellij.ui.DocumentAdapter;
import com.intellij.ui.components.JBLabel;
import com.intellij.ui.components.JBTextField;
import java.util.Collection;
import java.util.List;
import javax.annotation.Nullable;
import javax.swing.JPanel;
-import org.jetbrains.annotations.NotNull;
+import javax.swing.event.DocumentEvent;
final class NewRuleUI {
- private static final String[] POSSIBLE_RULES = {
- "android_library", "java_library", "cc_library", "cc_binary", "proto_library"
- };
+ private static final ImmutableSet<Kind> HANDLED_RULES =
+ ImmutableSet.of(
+ Kind.ANDROID_LIBRARY,
+ Kind.JAVA_LIBRARY,
+ Kind.CC_LIBRARY,
+ Kind.CC_BINARY,
+ Kind.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;
+ private static final String LAST_SELECTED_KIND = "Blaze.Rule.Kind";
+
+ private final ComboBox ruleComboBox = new ComboBox(HANDLED_RULES.toArray(new Kind[0]));
+ private final JBLabel ruleNameLabel = new JBLabel("Rule name:");
+ private final JBTextField ruleNameField;
+
+ private boolean ruleNameEditedByUser = false;
public NewRuleUI(int textFieldLength) {
this.ruleNameField = new JBTextField(textFieldLength);
+ Kind lastValue =
+ Kind.fromString(PropertiesComponent.getInstance().getValue(LAST_SELECTED_KIND));
+ if (HANDLED_RULES.contains(lastValue)) {
+ ruleComboBox.setSelectedItem(lastValue);
+ }
}
- public void fillUI(@NotNull JPanel component, int indentLevel) {
+ public void fillUI(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());
+ Kind kind = (Kind) ruleComboBox.getSelectedItem();
+ PropertiesComponent.getInstance().setValue(LAST_SELECTED_KIND, kind.toString());
+ return kind;
}
- @NotNull
public TargetName getRuleName() {
return TargetName.create(ruleNameField.getText());
}
+ void syncRuleNameTo(JBTextField textField) {
+ ruleNameField
+ .getDocument()
+ .addDocumentListener(
+ new DocumentAdapter() {
+ @Override
+ protected void textChanged(DocumentEvent e) {
+ ruleNameEditedByUser = true;
+ }
+ });
+
+ textField
+ .getDocument()
+ .addDocumentListener(
+ new DocumentAdapter() {
+ @Override
+ protected void textChanged(DocumentEvent e) {
+ if (!ruleNameEditedByUser) {
+ syncRuleName(textField.getText());
+ }
+ }
+ });
+ }
+
+ private void syncRuleName(String text) {
+ ruleNameField.setText(text);
+ // setText triggers an event which flips the field, so we'll set it back to false
+ this.ruleNameEditedByUser = false;
+ }
+
@Nullable
public ValidationInfo validate() {
+ if (ruleComboBox.getSelectedItem() == null) {
+ return new ValidationInfo("Select a rule type", ruleComboBox);
+ }
String ruleName = ruleNameField.getText();
List<BlazeValidationError> errors = Lists.newArrayList();
if (!validateRuleName(ruleName, errors)) {
@@ -73,7 +122,7 @@
}
private static boolean validateRuleName(
- @NotNull String inputString, @Nullable Collection<BlazeValidationError> errors) {
+ String inputString, @Nullable Collection<BlazeValidationError> errors) {
if (inputString.length() == 0) {
BlazeValidationError.collect(
errors, new BlazeValidationError(IdeBundle.message("error.name.should.be.specified")));
diff --git a/base/src/com/google/idea/blaze/base/ideinfo/CIdeInfo.java b/base/src/com/google/idea/blaze/base/ideinfo/CIdeInfo.java
index 215033d..7a993a5 100644
--- a/base/src/com/google/idea/blaze/base/ideinfo/CIdeInfo.java
+++ b/base/src/com/google/idea/blaze/base/ideinfo/CIdeInfo.java
@@ -21,9 +21,11 @@
/** Sister class to {@link JavaIdeInfo} */
public class CIdeInfo implements Serializable {
- private static final long serialVersionUID = 7L;
+ private static final long serialVersionUID = 8L;
public final ImmutableList<ArtifactLocation> sources;
+ public final ImmutableList<ArtifactLocation> headers;
+ public final ImmutableList<ArtifactLocation> textualHeaders;
public final ImmutableList<String> localDefines;
public final ImmutableList<ExecutionRootPath> localIncludeDirectories;
@@ -36,6 +38,8 @@
public CIdeInfo(
ImmutableList<ArtifactLocation> sources,
+ ImmutableList<ArtifactLocation> headers,
+ ImmutableList<ArtifactLocation> textualHeaders,
ImmutableList<String> localDefines,
ImmutableList<ExecutionRootPath> localIncludeDirectories,
ImmutableList<ExecutionRootPath> transitiveIncludeDirectories,
@@ -43,6 +47,8 @@
ImmutableList<String> transitiveDefines,
ImmutableList<ExecutionRootPath> transitiveSystemIncludeDirectories) {
this.sources = sources;
+ this.headers = headers;
+ this.textualHeaders = textualHeaders;
this.localDefines = localDefines;
this.localIncludeDirectories = localIncludeDirectories;
this.transitiveIncludeDirectories = transitiveIncludeDirectories;
@@ -58,6 +64,8 @@
/** Builder for c rule info */
public static class Builder {
private final ImmutableList.Builder<ArtifactLocation> sources = ImmutableList.builder();
+ private final ImmutableList.Builder<ArtifactLocation> headers = ImmutableList.builder();
+ private final ImmutableList.Builder<ArtifactLocation> textualHeaders = ImmutableList.builder();
private final ImmutableList.Builder<String> localDefines = ImmutableList.builder();
private final ImmutableList.Builder<ExecutionRootPath> localIncludeDirectories =
@@ -75,6 +83,31 @@
return this;
}
+ public Builder addSource(ArtifactLocation source) {
+ this.sources.add(source);
+ return this;
+ }
+
+ public Builder addHeaders(Iterable<ArtifactLocation> headers) {
+ this.headers.addAll(headers);
+ return this;
+ }
+
+ public Builder addHeader(ArtifactLocation header) {
+ this.headers.add(header);
+ return this;
+ }
+
+ public Builder addTextualHeaders(Iterable<ArtifactLocation> textualHeaders) {
+ this.textualHeaders.addAll(textualHeaders);
+ return this;
+ }
+
+ public Builder addTextualHeader(ArtifactLocation textualHeader) {
+ this.textualHeaders.add(textualHeader);
+ return this;
+ }
+
public Builder addLocalDefines(Iterable<String> localDefines) {
this.localDefines.addAll(localDefines);
return this;
@@ -111,6 +144,8 @@
public CIdeInfo build() {
return new CIdeInfo(
sources.build(),
+ headers.build(),
+ textualHeaders.build(),
localDefines.build(),
localIncludeDirectories.build(),
transitiveIncludeDirectories.build(),
@@ -127,6 +162,12 @@
+ " sources="
+ sources
+ "\n"
+ + " headers="
+ + headers
+ + "\n"
+ + " textualHeaders="
+ + textualHeaders
+ + "\n"
+ " localDefines="
+ localDefines
+ "\n"
diff --git a/base/src/com/google/idea/blaze/base/ideinfo/GoIdeInfo.java b/base/src/com/google/idea/blaze/base/ideinfo/GoIdeInfo.java
new file mode 100644
index 0000000..61689f3
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/ideinfo/GoIdeInfo.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.base.ideinfo;
+
+import com.google.common.collect.ImmutableList;
+import java.io.Serializable;
+
+/** Ide info specific to go rules. */
+public class GoIdeInfo implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ public final ImmutableList<ArtifactLocation> generatedSources;
+
+ public GoIdeInfo(ImmutableList<ArtifactLocation> generatedSources) {
+ this.generatedSources = generatedSources;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /** Builder for go rule info */
+ public static class Builder {
+ private final ImmutableList.Builder<ArtifactLocation> generatedSources =
+ ImmutableList.builder();
+
+ public Builder addGeneratedSources(Iterable<ArtifactLocation> generatedSources) {
+ this.generatedSources.addAll(generatedSources);
+ return this;
+ }
+
+ public GoIdeInfo build() {
+ return new GoIdeInfo(generatedSources.build());
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "GoIdeInfo{" + "\n" + " generatedSources=" + generatedSources + "\n" + '}';
+ }
+}
diff --git a/base/src/com/google/idea/blaze/base/ideinfo/JavaToolchainIdeInfo.java b/base/src/com/google/idea/blaze/base/ideinfo/JavaToolchainIdeInfo.java
index b9a9dca..c7faade 100644
--- a/base/src/com/google/idea/blaze/base/ideinfo/JavaToolchainIdeInfo.java
+++ b/base/src/com/google/idea/blaze/base/ideinfo/JavaToolchainIdeInfo.java
@@ -16,17 +16,21 @@
package com.google.idea.blaze.base.ideinfo;
import java.io.Serializable;
+import javax.annotation.Nullable;
/** Represents the java_toolchain class */
public class JavaToolchainIdeInfo implements Serializable {
- private static final long serialVersionUID = 1L;
+ private static final long serialVersionUID = 2L;
public final String sourceVersion;
public final String targetVersion;
+ @Nullable public final ArtifactLocation javacJar;
- public JavaToolchainIdeInfo(String sourceVersion, String targetVersion) {
+ public JavaToolchainIdeInfo(
+ String sourceVersion, String targetVersion, @Nullable ArtifactLocation javacJar) {
this.sourceVersion = sourceVersion;
this.targetVersion = targetVersion;
+ this.javacJar = javacJar;
}
@Override
@@ -39,6 +43,9 @@
+ " targetVersion="
+ targetVersion
+ "\n"
+ + " javacJar="
+ + javacJar
+ + "\n"
+ '}';
}
@@ -50,6 +57,7 @@
public static class Builder {
String sourceVersion;
String targetVersion;
+ ArtifactLocation javacJar;
public Builder setSourceVersion(String sourceVersion) {
this.sourceVersion = sourceVersion;
@@ -61,8 +69,13 @@
return this;
}
+ public Builder setJavacJar(ArtifactLocation javacJar) {
+ this.javacJar = javacJar;
+ return this;
+ }
+
public JavaToolchainIdeInfo build() {
- return new JavaToolchainIdeInfo(sourceVersion, targetVersion);
+ return new JavaToolchainIdeInfo(sourceVersion, targetVersion, javacJar);
}
}
}
diff --git a/base/src/com/google/idea/blaze/base/ideinfo/JsIdeInfo.java b/base/src/com/google/idea/blaze/base/ideinfo/JsIdeInfo.java
new file mode 100644
index 0000000..b191d22
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/ideinfo/JsIdeInfo.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.base.ideinfo;
+
+import com.google.common.collect.ImmutableList;
+import java.io.Serializable;
+
+/** Ide info specific to js rules. */
+public class JsIdeInfo implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ public final ImmutableList<ArtifactLocation> sources;
+
+ public JsIdeInfo(ImmutableList<ArtifactLocation> sources) {
+ this.sources = sources;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /** Builder for js rule info */
+ public static class Builder {
+ private final ImmutableList.Builder<ArtifactLocation> sources = ImmutableList.builder();
+
+ public Builder addSources(Iterable<ArtifactLocation> sources) {
+ this.sources.addAll(sources);
+ return this;
+ }
+
+ public JsIdeInfo build() {
+ return new JsIdeInfo(sources.build());
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "JsIdeInfo{" + "\n" + " sources=" + sources + "\n" + '}';
+ }
+}
diff --git a/base/src/com/google/idea/blaze/base/ideinfo/TargetIdeInfo.java b/base/src/com/google/idea/blaze/base/ideinfo/TargetIdeInfo.java
index ecd44c5..5e93271 100644
--- a/base/src/com/google/idea/blaze/base/ideinfo/TargetIdeInfo.java
+++ b/base/src/com/google/idea/blaze/base/ideinfo/TargetIdeInfo.java
@@ -15,6 +15,8 @@
*/
package com.google.idea.blaze.base.ideinfo;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.idea.blaze.base.ideinfo.Dependency.DependencyType;
import com.google.idea.blaze.base.model.primitives.Kind;
@@ -41,6 +43,9 @@
@Nullable public final AndroidIdeInfo androidIdeInfo;
@Nullable public final AndroidSdkIdeInfo androidSdkIdeInfo;
@Nullable public final PyIdeInfo pyIdeInfo;
+ @Nullable public final GoIdeInfo goIdeInfo;
+ @Nullable public final JsIdeInfo jsIdeInfo;
+ @Nullable public final TsIdeInfo tsIdeInfo;
@Nullable public final TestIdeInfo testIdeInfo;
@Nullable public final ProtoLibraryLegacyInfo protoLibraryLegacyInfo;
@Nullable public final JavaToolchainIdeInfo javaToolchainIdeInfo;
@@ -58,6 +63,9 @@
@Nullable AndroidIdeInfo androidIdeInfo,
@Nullable AndroidSdkIdeInfo androidSdkIdeInfo,
@Nullable PyIdeInfo pyIdeInfo,
+ @Nullable GoIdeInfo goIdeInfo,
+ @Nullable JsIdeInfo jsIdeInfo,
+ @Nullable TsIdeInfo tsIdeInfo,
@Nullable TestIdeInfo testIdeInfo,
@Nullable ProtoLibraryLegacyInfo protoLibraryLegacyInfo,
@Nullable JavaToolchainIdeInfo javaToolchainIdeInfo) {
@@ -73,6 +81,9 @@
this.androidIdeInfo = androidIdeInfo;
this.androidSdkIdeInfo = androidSdkIdeInfo;
this.pyIdeInfo = pyIdeInfo;
+ this.goIdeInfo = goIdeInfo;
+ this.jsIdeInfo = jsIdeInfo;
+ this.tsIdeInfo = tsIdeInfo;
this.testIdeInfo = testIdeInfo;
this.protoLibraryLegacyInfo = protoLibraryLegacyInfo;
this.javaToolchainIdeInfo = javaToolchainIdeInfo;
@@ -117,6 +128,9 @@
private JavaIdeInfo javaIdeInfo;
private AndroidIdeInfo androidIdeInfo;
private PyIdeInfo pyIdeInfo;
+ private GoIdeInfo goIdeInfo;
+ private JsIdeInfo jsIdeInfo;
+ private TsIdeInfo tsIdeInfo;
private TestIdeInfo testIdeInfo;
private ProtoLibraryLegacyInfo protoLibraryLegacyInfo;
private JavaToolchainIdeInfo javaToolchainIdeInfo;
@@ -135,8 +149,10 @@
return this;
}
- public Builder setKind(String kind) {
- return setKind(Kind.fromString(kind));
+ @VisibleForTesting
+ public Builder setKind(String kindString) {
+ Kind kind = Preconditions.checkNotNull(Kind.fromString(kindString));
+ return setKind(kind);
}
public Builder setKind(Kind kind) {
@@ -160,6 +176,9 @@
public Builder setCInfo(CIdeInfo cInfo) {
this.cIdeInfo = cInfo;
+ this.sources.addAll(cInfo.sources);
+ this.sources.addAll(cInfo.headers);
+ this.sources.addAll(cInfo.textualHeaders);
return this;
}
@@ -190,6 +209,21 @@
return this;
}
+ public Builder setGoInfo(GoIdeInfo.Builder goInfo) {
+ this.goIdeInfo = goInfo.build();
+ return this;
+ }
+
+ public Builder setJsInfo(JsIdeInfo.Builder jsInfo) {
+ this.jsIdeInfo = jsInfo.build();
+ return this;
+ }
+
+ public Builder setTsInfo(TsIdeInfo.Builder tsInfo) {
+ this.tsIdeInfo = tsInfo.build();
+ return this;
+ }
+
public Builder setTestInfo(TestIdeInfo.Builder testInfo) {
this.testIdeInfo = testInfo.build();
return this;
@@ -245,6 +279,9 @@
androidIdeInfo,
null,
pyIdeInfo,
+ goIdeInfo,
+ jsIdeInfo,
+ tsIdeInfo,
testIdeInfo,
protoLibraryLegacyInfo,
javaToolchainIdeInfo);
diff --git a/base/src/com/google/idea/blaze/base/ideinfo/TsIdeInfo.java b/base/src/com/google/idea/blaze/base/ideinfo/TsIdeInfo.java
new file mode 100644
index 0000000..49e054d
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/ideinfo/TsIdeInfo.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.base.ideinfo;
+
+import com.google.common.collect.ImmutableList;
+import java.io.Serializable;
+
+/** Ide info specific to typescript rules. */
+public class TsIdeInfo implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ public final ImmutableList<ArtifactLocation> sources;
+
+ public TsIdeInfo(ImmutableList<ArtifactLocation> sources) {
+ this.sources = sources;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /** Builder for js rule info */
+ public static class Builder {
+ private final ImmutableList.Builder<ArtifactLocation> sources = ImmutableList.builder();
+
+ public Builder addSources(Iterable<ArtifactLocation> sources) {
+ this.sources.addAll(sources);
+ return this;
+ }
+
+ public TsIdeInfo build() {
+ return new TsIdeInfo(sources.build());
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "TsIdeInfo{" + "\n" + " sources=" + sources + "\n" + '}';
+ }
+}
diff --git a/base/src/com/google/idea/blaze/base/issueparser/BlazeIssueParser.java b/base/src/com/google/idea/blaze/base/issueparser/BlazeIssueParser.java
index 55755ec..f743525 100644
--- a/base/src/com/google/idea/blaze/base/issueparser/BlazeIssueParser.java
+++ b/base/src/com/google/idea/blaze/base/issueparser/BlazeIssueParser.java
@@ -147,7 +147,7 @@
private final WorkspaceRoot workspaceRoot;
CompileParser(WorkspaceRoot workspaceRoot) {
- super("^([^/].*?):([0-9]+):(?:([0-9]+):)? (error|warning): (.*)$");
+ super("^([^/].*?):([0-9]+):(?:([0-9]+):)? (fatal error|error|warning): (.*)$");
this.workspaceRoot = workspaceRoot;
}
@@ -155,9 +155,9 @@
protected IssueOutput createIssue(Matcher matcher) {
final File file = fileFromRelativePath(workspaceRoot, matcher.group(1));
IssueOutput.Category type =
- matcher.group(4).equals("error")
- ? IssueOutput.Category.ERROR
- : IssueOutput.Category.WARNING;
+ matcher.group(4).equals("warning")
+ ? IssueOutput.Category.WARNING
+ : IssueOutput.Category.ERROR;
return IssueOutput.issue(type, matcher.group(5))
.inFile(file)
.onLine(Integer.parseInt(matcher.group(2)))
diff --git a/base/src/com/google/idea/blaze/base/issueparser/BlazeIssueParserProvider.java b/base/src/com/google/idea/blaze/base/issueparser/BlazeIssueParserProvider.java
new file mode 100644
index 0000000..513dc7e
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/issueparser/BlazeIssueParserProvider.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.collect.ImmutableList;
+import com.google.idea.blaze.base.issueparser.BlazeIssueParser.Parser;
+import com.intellij.openapi.extensions.ExtensionPointName;
+import com.intellij.openapi.project.Project;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Extension point for providing {@link
+ * com.google.idea.blaze.base.issueparser.BlazeIssueParser.Parser}s.
+ */
+public interface BlazeIssueParserProvider {
+
+ ExtensionPointName<BlazeIssueParserProvider> EP_NAME =
+ ExtensionPointName.create("com.google.idea.blaze.BlazeIssueParserProvider");
+
+ static List<Parser> getAllIssueParsers(Project project) {
+ return Arrays.stream(EP_NAME.getExtensions())
+ .map(provider -> provider.getIssueParsers(project))
+ .flatMap(Collection::stream)
+ .collect(Collectors.toList());
+ }
+
+ ImmutableList<BlazeIssueParser.Parser> getIssueParsers(Project project);
+}
diff --git a/base/src/com/google/idea/blaze/base/issueparser/IssueOutputLineProcessor.java b/base/src/com/google/idea/blaze/base/issueparser/IssueOutputLineProcessor.java
index a5588dc..0b3dc57 100644
--- a/base/src/com/google/idea/blaze/base/issueparser/IssueOutputLineProcessor.java
+++ b/base/src/com/google/idea/blaze/base/issueparser/IssueOutputLineProcessor.java
@@ -25,7 +25,6 @@
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 javax.annotation.Nullable;
/**
* Forwards output to PrintOutputs, colored by whether or not an issue is found per-line.
@@ -39,26 +38,28 @@
private final BlazeIssueParser blazeIssueParser;
public IssueOutputLineProcessor(
- @Nullable Project project, BlazeContext context, WorkspaceRoot workspaceRoot) {
+ Project project, BlazeContext context, WorkspaceRoot workspaceRoot) {
this.context = context;
- ProjectViewSet projectViewSet =
- project != null ? ProjectViewManager.getInstance(project).getProjectViewSet() : null;
+ ProjectViewSet projectViewSet = ProjectViewManager.getInstance(project).getProjectViewSet();
ImmutableList<BlazeIssueParser.Parser> parsers =
- ImmutableList.of(
- new BlazeIssueParser.CompileParser(workspaceRoot),
- new BlazeIssueParser.TracebackParser(),
- new BlazeIssueParser.BuildParser(),
- new BlazeIssueParser.SkylarkErrorParser(),
- new BlazeIssueParser.LinelessBuildParser(),
- new BlazeIssueParser.ProjectViewLabelParser(projectViewSet),
- new BlazeIssueParser.InvalidTargetProjectViewPackageParser(
- projectViewSet, "no such package '(.*)': BUILD file not found on package path"),
- new BlazeIssueParser.InvalidTargetProjectViewPackageParser(
- projectViewSet, "no targets found beneath '(.*)'"),
- new BlazeIssueParser.InvalidTargetProjectViewPackageParser(
- projectViewSet, "ERROR: invalid target format '(.*)'"),
- new BlazeIssueParser.FileNotFoundBuildParser(workspaceRoot));
+ ImmutableList.<BlazeIssueParser.Parser>builder()
+ .add(
+ new BlazeIssueParser.CompileParser(workspaceRoot),
+ new BlazeIssueParser.TracebackParser(),
+ new BlazeIssueParser.BuildParser(),
+ new BlazeIssueParser.SkylarkErrorParser(),
+ new BlazeIssueParser.LinelessBuildParser(),
+ new BlazeIssueParser.ProjectViewLabelParser(projectViewSet),
+ new BlazeIssueParser.InvalidTargetProjectViewPackageParser(
+ projectViewSet, "no such package '(.*)': BUILD file not found on package path"),
+ new BlazeIssueParser.InvalidTargetProjectViewPackageParser(
+ projectViewSet, "no targets found beneath '(.*)'"),
+ new BlazeIssueParser.InvalidTargetProjectViewPackageParser(
+ projectViewSet, "ERROR: invalid target format '(.*)'"),
+ new BlazeIssueParser.FileNotFoundBuildParser(workspaceRoot))
+ .addAll(BlazeIssueParserProvider.getAllIssueParsers(project))
+ .build();
this.blazeIssueParser = new BlazeIssueParser(parsers);
}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/actions/BuildFileModifierImpl.java b/base/src/com/google/idea/blaze/base/lang/buildfile/actions/BuildFileModifierImpl.java
index dd9178c..a8dc5b5 100644
--- a/base/src/com/google/idea/blaze/base/lang/buildfile/actions/BuildFileModifierImpl.java
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/actions/BuildFileModifierImpl.java
@@ -26,10 +26,8 @@
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;
@@ -41,24 +39,19 @@
@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) {
- logger.error("No BUILD file found at location: " + newRule.blazePackage());
- return false;
- }
- buildFile.add(createRule(project, ruleKind, newRule.targetName().toString()));
- return true;
- });
+ BuildReferenceManager manager = BuildReferenceManager.getInstance(project);
+ File file = manager.resolvePackage(newRule.blazePackage());
+ if (file == null) {
+ return false;
+ }
+ LocalFileSystem.getInstance().refreshIoFiles(ImmutableList.of(file));
+ BuildFile buildFile = manager.resolveBlazePackage(newRule.blazePackage());
+ if (buildFile == null) {
+ logger.error("No BUILD file found at location: " + newRule.blazePackage());
+ return false;
+ }
+ buildFile.add(createRule(project, ruleKind, newRule.targetName().toString()));
+ return true;
}
private PsiElement createRule(Project project, Kind ruleKind, String ruleName) {
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
index 84d16cb..a085253 100644
--- 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
@@ -21,11 +21,13 @@
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.usages.Usage;
import com.intellij.usages.UsageGroup;
+import com.intellij.usages.UsageTarget;
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.annotation.Nullable;
import javax.swing.Icon;
/**
@@ -52,8 +54,9 @@
this.project = project;
}
- @Override
- public UsageGroup groupUsage(Usage usage) {
+ @SuppressWarnings("MissingOverride") // #api171: added in 2017.2
+ @Nullable
+ public UsageGroup getParentGroupFor(Usage usage, UsageTarget[] targets) {
if (!(usage instanceof UsageInFile)) {
return null;
}
@@ -88,5 +91,10 @@
}
};
}
+
+ @Override
+ public UsageGroup groupUsage(Usage usage) {
+ return getParentGroupFor(usage, UsageTarget.EMPTY_ARRAY);
+ }
}
}
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
index 7cfd64b..58bb3a1 100644
--- 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
@@ -32,6 +32,12 @@
String getPresentableText();
+ /** See {@link com.intellij.navigation.ItemPresentation#getLocationString}. */
+ @Nullable
+ default String getLocationString() {
+ return null;
+ }
+
@Nullable
PsiElement getReferencedElement();
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildElementImpl.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildElementImpl.java
index 700f4dc..d6270b9 100644
--- a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildElementImpl.java
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildElementImpl.java
@@ -147,7 +147,7 @@
@Override
public String getLocationString() {
- return null;
+ return element.getLocationString();
}
@Override
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
index 46701da..41f68fc 100644
--- 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
@@ -19,6 +19,7 @@
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.Kind;
import com.google.idea.blaze.base.model.primitives.Label;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.TextRange;
@@ -53,6 +54,12 @@
return node != null ? node.getText() : null;
}
+ @Nullable
+ public Kind getRuleKind() {
+ String functionName = getFunctionName();
+ return functionName != null ? Kind.fromString(functionName) : null;
+ }
+
@Override
@Nullable
public String getName() {
@@ -161,12 +168,15 @@
@Override
public String getPresentableText() {
- String name = getFunctionName();
- if (name == null) {
+ String functionName = getFunctionName();
+ if (functionName == null) {
return super.getPresentableText();
}
String targetName = getNameArgumentValue();
- return targetName != null ? name + "(\"" + targetName + "\")" : name;
+ if (targetName == null) {
+ return functionName;
+ }
+ return String.format("%s : %s", targetName, functionName);
}
@Override
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/LoadStatement.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/LoadStatement.java
index 31dd222..3cd68f8 100644
--- a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/LoadStatement.java
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/LoadStatement.java
@@ -86,7 +86,12 @@
@Override
public String getPresentableText() {
- String path = LabelUtils.getNiceSkylarkFileName(getImportedPath());
- return path != null ? "load: " + path : "load";
+ return "load";
+ }
+
+ @Nullable
+ @Override
+ public String getLocationString() {
+ return LabelUtils.getNiceSkylarkFileName(getImportedPath());
}
}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/references/ExternalWorkspaceReferenceFragment.java b/base/src/com/google/idea/blaze/base/lang/buildfile/references/ExternalWorkspaceReferenceFragment.java
index 521f432..51d8750 100644
--- a/base/src/com/google/idea/blaze/base/lang/buildfile/references/ExternalWorkspaceReferenceFragment.java
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/references/ExternalWorkspaceReferenceFragment.java
@@ -65,7 +65,7 @@
@Nullable
private static BuildFile resolveProjectWorkspaceFile(Project project) {
- WorkspaceRoot projectRoot = WorkspaceRoot.fromProject(project);
+ WorkspaceRoot projectRoot = WorkspaceRoot.fromProjectSafe(project);
if (projectRoot == null) {
return null;
}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/sync/BuildLangSyncPlugin.java b/base/src/com/google/idea/blaze/base/lang/buildfile/sync/BuildLangSyncPlugin.java
index 4a0f12b..526a8cb 100644
--- a/base/src/com/google/idea/blaze/base/lang/buildfile/sync/BuildLangSyncPlugin.java
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/sync/BuildLangSyncPlugin.java
@@ -16,7 +16,9 @@
package com.google.idea.blaze.base.lang.buildfile.sync;
import com.google.common.util.concurrent.ListenableFuture;
+import com.google.idea.blaze.base.command.BlazeCommandName;
import com.google.idea.blaze.base.command.BlazeFlags;
+import com.google.idea.blaze.base.command.BlazeInvocationContext;
import com.google.idea.blaze.base.command.info.BlazeInfo;
import com.google.idea.blaze.base.command.info.BlazeInfoRunner;
import com.google.idea.blaze.base.ideinfo.TargetMap;
@@ -102,15 +104,14 @@
ProjectViewSet projectViewSet,
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 =
BlazeInfoRunner.getInstance()
.runBlazeInfoGetBytes(
context,
Blaze.getBuildSystemProvider(project).getSyncBinaryPath(),
workspace,
- BlazeFlags.buildFlags(project, projectViewSet),
+ BlazeFlags.blazeFlags(
+ project, projectViewSet, BlazeCommandName.INFO, BlazeInvocationContext.Sync),
BlazeInfo.BUILD_LANGUAGE);
return BuildLanguageSpec.fromProto(Build.BuildLanguage.parseFrom(future.get()));
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
index 7ae6217..6800f53 100644
--- 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
@@ -53,4 +53,9 @@
public String getPresentableText() {
return element.getPresentableText();
}
+
+ @Override
+ public String getLocationString() {
+ return element.getLocationString();
+ }
}
diff --git a/base/src/com/google/idea/blaze/base/model/BlazeVersionData.java b/base/src/com/google/idea/blaze/base/model/BlazeVersionData.java
index ded37ad..3356e8b 100644
--- a/base/src/com/google/idea/blaze/base/model/BlazeVersionData.java
+++ b/base/src/com/google/idea/blaze/base/model/BlazeVersionData.java
@@ -15,7 +15,6 @@
*/
package com.google.idea.blaze.base.model;
-import com.google.common.annotations.VisibleForTesting;
import com.google.idea.blaze.base.bazel.BazelVersion;
import com.google.idea.blaze.base.bazel.BuildSystemProvider;
import com.google.idea.blaze.base.command.info.BlazeInfo;
@@ -35,11 +34,6 @@
@Nullable private final Long clientCl;
@Nullable private final BazelVersion bazelVersion;
- @VisibleForTesting
- public BlazeVersionData() {
- this(null, null, null);
- }
-
private BlazeVersionData(
@Nullable Long blazeCl, @Nullable Long clientCl, @Nullable BazelVersion bazelVersion) {
this.blazeCl = blazeCl;
@@ -51,6 +45,10 @@
return blazeCl != null && blazeCl >= cl;
}
+ public boolean blazeClientIsKnown() {
+ return clientCl != null;
+ }
+
public boolean blazeClientIsAtLeastCl(long cl) {
return clientCl != null && clientCl >= cl;
}
@@ -59,24 +57,40 @@
return bazelVersion != null && bazelVersion.isAtLeast(major, minor, bugfix);
}
+ public boolean bazelIsAtLeastVersion(BazelVersion version) {
+ return bazelVersion != null && bazelVersion.isAtLeast(version);
+ }
+
public BuildSystem buildSystem() {
return bazelVersion != null ? BuildSystem.Bazel : BuildSystem.Blaze;
}
+ @Override
+ public String toString() {
+ if (bazelVersion != null) {
+ return bazelVersion.toString();
+ }
+ return String.format("Blaze CL: %s, Client CL: %s", blazeCl, clientCl);
+ }
+
public static BlazeVersionData build(
BuildSystem buildSystem, WorkspaceRoot workspaceRoot, BlazeInfo blazeInfo) {
- Builder builder = new Builder();
+ Builder builder = builder();
for (BuildSystemProvider provider : BuildSystemProvider.EP_NAME.getExtensions()) {
provider.populateBlazeVersionData(buildSystem, workspaceRoot, blazeInfo, builder);
}
return builder.build();
}
+ public static Builder builder() {
+ return new Builder();
+ }
+
/** Builder class for constructing the blaze version data */
public static class Builder {
- public Long blazeCl;
- public Long clientCl;
- public BazelVersion bazelVersion;
+ private Long blazeCl;
+ private Long clientCl;
+ private BazelVersion bazelVersion;
public Builder setBlazeCl(Long blazeCl) {
this.blazeCl = blazeCl;
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
index 5e060b7..e299cbd 100644
--- a/base/src/com/google/idea/blaze/base/model/primitives/ExecutionRootPath.java
+++ b/base/src/com/google/idea/blaze/base/model/primitives/ExecutionRootPath.java
@@ -90,7 +90,9 @@
if (!isAncestor(root.getPath(), path.getPath(), /* strict */ false)) {
return null;
}
- String relativePath = FileUtil.getRelativePath(root, path);
+ String relativePath =
+ FileUtil.getRelativePath(
+ root.getAbsolutePath(), path.getAbsolutePath(), File.separatorChar);
if (relativePath == null) {
return null;
}
diff --git a/base/src/com/google/idea/blaze/base/model/primitives/Kind.java b/base/src/com/google/idea/blaze/base/model/primitives/Kind.java
index b5fbda8..7a041b8 100644
--- a/base/src/com/google/idea/blaze/base/model/primitives/Kind.java
+++ b/base/src/com/google/idea/blaze/base/model/primitives/Kind.java
@@ -20,50 +20,69 @@
import com.google.common.collect.ImmutableMultimap;
import java.util.Arrays;
import java.util.Collection;
+import javax.annotation.Nullable;
/** 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),
- ANDROID_SDK("android_sdk", 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.GENERIC),
- 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),
- TEST_SUITE("test_suite", LanguageClass.GENERIC),
- PY_LIBRARY("py_library", LanguageClass.PYTHON),
- PY_BINARY("py_binary", LanguageClass.PYTHON),
- PY_TEST("py_test", LanguageClass.PYTHON),
- PY_APPENGINE_BINARY("py_appengine_binary", LanguageClass.PYTHON),
- PY_WRAP_CC("py_wrap_cc", LanguageClass.PYTHON),
- GO_TEST("go_test", LanguageClass.GO),
- GO_APPENGINE_TEST("go_appengine_test", LanguageClass.GO),
- GO_BINARY("go_binary", LanguageClass.GO),
- GO_APPENGINE_BINARY("go_appengine_binary", LanguageClass.GO),
- GO_LIBRARY("go_library", LanguageClass.GO),
- GO_APPENGINE_LIBRARY("go_appengine_library", LanguageClass.GO),
- GO_WRAP_CC("go_wrap_cc", LanguageClass.GO),
- INTELLIJ_PLUGIN_DEBUG_TARGET("intellij_plugin_debug_target", LanguageClass.JAVA),
- SCALA_BINARY("scala_binary", LanguageClass.SCALA),
- SCALA_LIBRARY("scala_library", LanguageClass.SCALA),
- SCALA_MACRO_LIBRARY("scala_macro_library", LanguageClass.SCALA),
- SCALA_TEST("scala_test", LanguageClass.SCALA),
+ ANDROID_BINARY("android_binary", LanguageClass.ANDROID, RuleType.BINARY),
+ ANDROID_LIBRARY("android_library", LanguageClass.ANDROID, RuleType.UNKNOWN),
+ ANDROID_TEST("android_test", LanguageClass.ANDROID, RuleType.TEST),
+ ANDROID_ROBOLECTRIC_TEST("android_robolectric_test", LanguageClass.ANDROID, RuleType.TEST),
+ ANDROID_SDK("android_sdk", LanguageClass.ANDROID, RuleType.UNKNOWN),
+ JAVA_LIBRARY("java_library", LanguageClass.JAVA, RuleType.UNKNOWN),
+ JAVA_TEST("java_test", LanguageClass.JAVA, RuleType.TEST),
+ JAVA_BINARY("java_binary", LanguageClass.JAVA, RuleType.BINARY),
+ JAVA_IMPORT("java_import", LanguageClass.JAVA, RuleType.UNKNOWN),
+ JAVA_TOOLCHAIN("java_toolchain", LanguageClass.JAVA, RuleType.UNKNOWN),
+ JAVA_PROTO_LIBRARY("java_proto_library", LanguageClass.JAVA, RuleType.UNKNOWN),
+ JAVA_PLUGIN("java_plugin", LanguageClass.JAVA, RuleType.UNKNOWN),
+ PROTO_LIBRARY("proto_library", LanguageClass.GENERIC, RuleType.UNKNOWN),
+ ANDROID_RESOURCES("android_resources", LanguageClass.ANDROID, RuleType.UNKNOWN),
+ CC_LIBRARY("cc_library", LanguageClass.C, RuleType.UNKNOWN),
+ CC_BINARY("cc_binary", LanguageClass.C, RuleType.BINARY),
+ CC_TEST("cc_test", LanguageClass.C, RuleType.TEST),
+ CC_INC_LIBRARY("cc_inc_library", LanguageClass.C, RuleType.UNKNOWN),
+ CC_TOOLCHAIN("cc_toolchain", LanguageClass.C, RuleType.UNKNOWN),
+ JAVA_WRAP_CC("java_wrap_cc", LanguageClass.JAVA, RuleType.UNKNOWN),
+ GWT_APPLICATION("gwt_application", LanguageClass.JAVA, RuleType.UNKNOWN),
+ GWT_HOST("gwt_host", LanguageClass.JAVA, RuleType.UNKNOWN),
+ GWT_MODULE("gwt_module", LanguageClass.JAVA, RuleType.UNKNOWN),
+ GWT_TEST("gwt_test", LanguageClass.JAVA, RuleType.TEST),
+ TEST_SUITE("test_suite", LanguageClass.GENERIC, RuleType.TEST),
+ PY_LIBRARY("py_library", LanguageClass.PYTHON, RuleType.UNKNOWN),
+ PY_BINARY("py_binary", LanguageClass.PYTHON, RuleType.BINARY),
+ PY_TEST("py_test", LanguageClass.PYTHON, RuleType.TEST),
+ PY_APPENGINE_BINARY("py_appengine_binary", LanguageClass.PYTHON, RuleType.BINARY),
+ PY_WRAP_CC("py_wrap_cc", LanguageClass.PYTHON, RuleType.UNKNOWN),
+ GO_TEST("go_test", LanguageClass.GO, RuleType.TEST),
+ GO_APPENGINE_TEST("go_appengine_test", LanguageClass.GO, RuleType.TEST),
+ GO_BINARY("go_binary", LanguageClass.GO, RuleType.BINARY),
+ GO_APPENGINE_BINARY("go_appengine_binary", LanguageClass.GO, RuleType.BINARY),
+ GO_LIBRARY("go_library", LanguageClass.GO, RuleType.UNKNOWN),
+ GO_APPENGINE_LIBRARY("go_appengine_library", LanguageClass.GO, RuleType.UNKNOWN),
+ GO_PROTO_LIBRARY("go_proto_library", LanguageClass.GO, RuleType.UNKNOWN),
+ GO_WRAP_CC("go_wrap_cc", LanguageClass.GO, RuleType.UNKNOWN),
+ INTELLIJ_PLUGIN_DEBUG_TARGET(
+ "intellij_plugin_debug_target", LanguageClass.JAVA, RuleType.UNKNOWN),
+ SCALA_BINARY("scala_binary", LanguageClass.SCALA, RuleType.BINARY),
+ SCALA_IMPORT("scala_import", LanguageClass.SCALA, RuleType.UNKNOWN),
+ SCALA_LIBRARY("scala_library", LanguageClass.SCALA, RuleType.UNKNOWN),
+ SCALA_MACRO_LIBRARY("scala_macro_library", LanguageClass.SCALA, RuleType.UNKNOWN),
+ SCALA_TEST("scala_test", LanguageClass.SCALA, RuleType.TEST),
+ SCALA_JUNIT_TEST("scala_junit_test", LanguageClass.SCALA, RuleType.TEST),
+ SH_TEST("sh_test", LanguageClass.GENERIC, RuleType.TEST),
+ SH_LIBRARY("sh_library", LanguageClass.GENERIC, RuleType.UNKNOWN),
+ SH_BINARY("sh_binary", LanguageClass.GENERIC, RuleType.BINARY),
+ JS_BINARY("js_binary", LanguageClass.JAVASCRIPT, RuleType.BINARY),
+ JS_MODULE_BINARY("js_module_binary", LanguageClass.JAVASCRIPT, RuleType.BINARY),
+ JS_LIBRARY("js_library", LanguageClass.JAVASCRIPT, RuleType.UNKNOWN),
+ JS_UNIT_TEST("jsunit_test", LanguageClass.JAVASCRIPT, RuleType.TEST),
+ JS_PUPPET_TEST("js_puppet_test", LanguageClass.JAVASCRIPT, RuleType.TEST),
+ PINTO_LIBRARY("pinto_library", LanguageClass.JAVASCRIPT, RuleType.UNKNOWN),
+ PINTO_LIBRARY_MOD("pinto_library_mod", LanguageClass.JAVASCRIPT, RuleType.UNKNOWN),
+ PINTO_MODULE("pinto_module", LanguageClass.JAVASCRIPT, RuleType.UNKNOWN),
+ TS_LIBRARY("ts_library", LanguageClass.TYPESCRIPT, RuleType.UNKNOWN),
+ TS_CONFIG("ts_config", LanguageClass.TYPESCRIPT, RuleType.BINARY),
;
static final ImmutableMap<String, Kind> STRING_TO_KIND = makeStringToKindMap();
@@ -86,6 +105,7 @@
return result.build();
}
+ @Nullable
public static Kind fromString(String kindString) {
return STRING_TO_KIND.get(kindString);
}
@@ -95,11 +115,13 @@
}
private final String kind;
- private final LanguageClass languageClass;
+ public final LanguageClass languageClass;
+ public final RuleType ruleType;
- Kind(String kind, LanguageClass languageClass) {
+ Kind(String kind, LanguageClass languageClass, RuleType ruleType) {
this.kind = kind;
this.languageClass = languageClass;
+ this.ruleType = ruleType;
}
@Override
@@ -107,10 +129,6 @@
return kind;
}
- public LanguageClass getLanguageClass() {
- return languageClass;
- }
-
public boolean isOneOf(Kind... kinds) {
return isOneOf(Arrays.asList(kinds));
}
diff --git a/base/src/com/google/idea/blaze/base/model/primitives/RuleType.java b/base/src/com/google/idea/blaze/base/model/primitives/RuleType.java
new file mode 100644
index 0000000..97e950c
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/model/primitives/RuleType.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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;
+
+/** The general type of a rule (e.g. test, binary, etc.). */
+public enum RuleType {
+ TEST,
+ BINARY,
+ UNKNOWN,
+}
diff --git a/base/src/com/google/idea/blaze/base/plugin/BazelVersionChecker.java b/base/src/com/google/idea/blaze/base/plugin/BazelVersionChecker.java
new file mode 100644
index 0000000..78e146b
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/plugin/BazelVersionChecker.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.bazel.BazelVersion;
+import com.google.idea.blaze.base.model.BlazeVersionData;
+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.BuildSystem;
+
+/** Verifies that the available Bazel version is supported by this plugin. */
+public class BazelVersionChecker implements BuildSystemVersionChecker {
+
+ // prior to version 0.5 there was no BEP support
+ private static final BazelVersion OLDEST_SUPPORTED_VERSION = new BazelVersion(0, 5, 0);
+
+ @Override
+ public boolean versionSupported(BlazeContext context, BlazeVersionData version) {
+ if (version.buildSystem() != BuildSystem.Bazel) {
+ return true;
+ }
+ if (version.bazelIsAtLeastVersion(OLDEST_SUPPORTED_VERSION)) {
+ return true;
+ }
+ IssueOutput.error(
+ String.format(
+ "Bazel version %s is not supported by this version of the Bazel plugin. "
+ + "Please upgrade to Bazel version %s+.\n"
+ + "Upgrade instructions are available at https://bazel.build",
+ version, OLDEST_SUPPORTED_VERSION))
+ .submit(context);
+ return false;
+ }
+}
diff --git a/base/src/com/google/idea/blaze/base/plugin/BuildSystemVersionChecker.java b/base/src/com/google/idea/blaze/base/plugin/BuildSystemVersionChecker.java
new file mode 100644
index 0000000..6667b91
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/plugin/BuildSystemVersionChecker.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.model.BlazeVersionData;
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.intellij.openapi.extensions.ExtensionPointName;
+
+/**
+ * Verifies that the available Blaze version is supported by this plugin.
+ *
+ * <p>Notifies the user if they're using an unsupported version of Blaze.
+ */
+public interface BuildSystemVersionChecker {
+
+ ExtensionPointName<BuildSystemVersionChecker> EP_NAME =
+ ExtensionPointName.create("com.google.idea.blaze.BuildSystemVersionChecker");
+
+ static boolean verifyVersionSupported(BlazeContext context, BlazeVersionData version) {
+ for (BuildSystemVersionChecker checker : EP_NAME.getExtensions()) {
+ if (!checker.versionSupported(context, version)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns false if the blaze version is unsupported.
+ *
+ * <p>Also displays corresponding errors via the {@link BlazeContext}.
+ */
+ boolean versionSupported(BlazeContext context, BlazeVersionData version);
+}
diff --git a/base/src/com/google/idea/blaze/base/prefetch/FetchExecutor.java b/base/src/com/google/idea/blaze/base/prefetch/FetchExecutor.java
index c89cc4f..5011172 100644
--- a/base/src/com/google/idea/blaze/base/prefetch/FetchExecutor.java
+++ b/base/src/com/google/idea/blaze/base/prefetch/FetchExecutor.java
@@ -17,6 +17,7 @@
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
+import com.google.idea.common.concurrency.ConcurrencyUtil;
import com.intellij.util.concurrency.BoundedTaskExecutor;
import java.util.concurrent.Executors;
@@ -25,5 +26,10 @@
private static final int THREAD_COUNT = 32;
public static final ListeningExecutorService EXECUTOR =
MoreExecutors.listeningDecorator(
- new BoundedTaskExecutor(Executors.newFixedThreadPool(THREAD_COUNT), THREAD_COUNT));
+ new BoundedTaskExecutor(
+ // #api171 add this argument, the form without a name is deprecated
+ // FetchExecutor.class.getSimpleName(),
+ Executors.newFixedThreadPool(
+ THREAD_COUNT, ConcurrencyUtil.namedDaemonThreadPoolFactory(FetchExecutor.class)),
+ 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
index 26c7491..1b6a3c2 100644
--- a/base/src/com/google/idea/blaze/base/prefetch/PrefetchFileSource.java
+++ b/base/src/com/google/idea/blaze/base/prefetch/PrefetchFileSource.java
@@ -15,25 +15,41 @@
*/
package com.google.idea.blaze.base.prefetch;
+import com.google.common.collect.ImmutableSet;
import com.google.idea.blaze.base.model.BlazeProjectData;
import com.google.idea.blaze.base.projectview.ProjectViewSet;
+import com.google.idea.blaze.base.sync.projectview.ImportRoots;
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. */
+
+ /** Returns all file extensions provided by available PrefetchFileSource implementations. */
+ static ImmutableSet<String> getAllPrefetchFileExtensions() {
+ ImmutableSet.Builder<String> extensionsToFetchContent = ImmutableSet.builder();
+ for (PrefetchFileSource fileSource : PrefetchFileSource.EP_NAME.getExtensions()) {
+ extensionsToFetchContent.addAll(fileSource.prefetchFileExtensions());
+ }
+ return extensionsToFetchContent.build();
+ }
+
+ /**
+ * Adds any files or directories that we would be interested in prefetching. Project source files
+ * should not be added here, as they're always prefetched.
+ */
void addFilesToPrefetch(
Project project,
ProjectViewSet projectViewSet,
+ ImportRoots importRoots,
BlazeProjectData blazeProjectData,
- Collection<File> files);
+ Set<File> files);
- /** Returns any source file extensions that are a good candidate for the {@link Prefetcher}. */
- Set<String> prefetchSrcFileExtensions();
+ /** Returns any file extensions that are a good candidate for the {@link Prefetcher}. */
+ Set<String> prefetchFileExtensions();
}
diff --git a/base/src/com/google/idea/blaze/base/prefetch/PrefetchService.java b/base/src/com/google/idea/blaze/base/prefetch/PrefetchService.java
index 726f970..c7ab12b 100644
--- a/base/src/com/google/idea/blaze/base/prefetch/PrefetchService.java
+++ b/base/src/com/google/idea/blaze/base/prefetch/PrefetchService.java
@@ -29,8 +29,14 @@
return ServiceManager.getService(PrefetchService.class);
}
- /** Instructs all prefetchers to prefetch these files. */
- ListenableFuture<?> prefetchFiles(Project project, Collection<File> files);
+ /**
+ * Instructs all prefetchers to prefetch these files.
+ *
+ * @param refetchCachedFiles True if all files should be fetched, regardless of whether they were
+ * recently fetched.
+ */
+ ListenableFuture<?> prefetchFiles(
+ Project project, Collection<File> files, boolean refetchCachedFiles);
ListenableFuture<?> prefetchProjectFiles(
Project project, ProjectViewSet projectViewSet, 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
index 05e02a6..27d3c03 100644
--- a/base/src/com/google/idea/blaze/base/prefetch/PrefetchServiceImpl.java
+++ b/base/src/com/google/idea/blaze/base/prefetch/PrefetchServiceImpl.java
@@ -16,9 +16,11 @@
package com.google.idea.blaze.base.prefetch;
import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
+import com.google.idea.blaze.base.io.FileAttributeProvider;
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;
@@ -26,24 +28,86 @@
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.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import java.io.File;
+import java.io.IOException;
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;
/** Implementation for prefetcher. */
public class PrefetchServiceImpl implements PrefetchService {
+ private static final Logger logger = Logger.getInstance(PrefetchServiceImpl.class);
+
+ private static final long REFETCH_PERIOD_MILLIS = 24 * 60 * 60 * 1000;
+ private final Map<Integer, Long> fileToLastFetchTimeMillis = Maps.newConcurrentMap();
+
@Override
- public ListenableFuture<?> prefetchFiles(Project project, Collection<File> files) {
+ public ListenableFuture<?> prefetchFiles(
+ Project project, Collection<File> files, boolean refetchCachedFiles) {
+ if (files.isEmpty() || !enabled(project)) {
+ return Futures.immediateFuture(null);
+ }
+ if (!refetchCachedFiles) {
+ long startTime = System.currentTimeMillis();
+ // ignore recently fetched files
+ files =
+ files
+ .stream()
+ .filter(file -> shouldPrefetch(file, startTime))
+ .collect(Collectors.toList());
+ }
+ FileAttributeProvider provider = FileAttributeProvider.getInstance();
+ List<ListenableFuture<File>> canonicalFiles =
+ files
+ .stream()
+ .map(file -> FetchExecutor.EXECUTOR.submit(() -> toCanonicalFile(provider, file)))
+ .collect(Collectors.toList());
List<ListenableFuture<?>> futures = Lists.newArrayList();
for (Prefetcher prefetcher : Prefetcher.EP_NAME.getExtensions()) {
- futures.add(prefetcher.prefetchFiles(project, files, FetchExecutor.EXECUTOR));
+ futures.add(prefetcher.prefetchFiles(project, canonicalFiles, FetchExecutor.EXECUTOR));
}
return Futures.allAsList(futures);
}
+ private static boolean enabled(Project project) {
+ for (Prefetcher prefetcher : Prefetcher.EP_NAME.getExtensions()) {
+ if (prefetcher.enabled(project)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Nullable
+ private static File toCanonicalFile(FileAttributeProvider provider, File file) {
+ try {
+ File canonicalFile = file.getCanonicalFile();
+ if (provider.exists(canonicalFile)) {
+ return canonicalFile;
+ }
+ } catch (IOException e) {
+ logger.warn(e);
+ }
+ return null;
+ }
+
+ /** Returns false if this file has been recently prefetched. */
+ private boolean shouldPrefetch(File file, long startTime) {
+ // Filter files that have been recently fetched
+ Long lastFetchTime = fileToLastFetchTimeMillis.get(file.hashCode());
+ if (lastFetchTime != null && (startTime - lastFetchTime < REFETCH_PERIOD_MILLIS)) {
+ return false;
+ }
+ fileToLastFetchTimeMillis.put(file.hashCode(), startTime);
+ return true;
+ }
+
@Override
public ListenableFuture<?> prefetchProjectFiles(
Project project, ProjectViewSet projectViewSet, BlazeProjectData blazeProjectData) {
@@ -53,6 +117,10 @@
return Futures.immediateFuture(null);
}
WorkspaceRoot workspaceRoot = WorkspaceRoot.fromImportSettings(importSettings);
+ if (!FileAttributeProvider.getInstance().exists(workspaceRoot.directory())) {
+ // quick sanity check before trying to prefetch each individual file
+ return Futures.immediateFuture(null);
+ }
ImportRoots importRoots =
ImportRoots.builder(workspaceRoot, importSettings.getBuildSystem())
.add(projectViewSet)
@@ -63,8 +131,8 @@
files.add(workspaceRoot.fileForPath(workspacePath));
}
for (PrefetchFileSource fileSource : PrefetchFileSource.EP_NAME.getExtensions()) {
- fileSource.addFilesToPrefetch(project, projectViewSet, blazeProjectData, files);
+ fileSource.addFilesToPrefetch(project, projectViewSet, importRoots, blazeProjectData, files);
}
- return prefetchFiles(project, files);
+ return prefetchFiles(project, files, false);
}
}
diff --git a/base/src/com/google/idea/blaze/base/prefetch/Prefetcher.java b/base/src/com/google/idea/blaze/base/prefetch/Prefetcher.java
index 64503e0..55b30c2 100644
--- a/base/src/com/google/idea/blaze/base/prefetch/Prefetcher.java
+++ b/base/src/com/google/idea/blaze/base/prefetch/Prefetcher.java
@@ -17,6 +17,7 @@
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.idea.blaze.base.settings.Blaze;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.project.Project;
import java.io.File;
@@ -28,10 +29,16 @@
ExtensionPointName.create("com.google.idea.blaze.Prefetcher");
/**
- * Prefetches the given list of files.
+ * Prefetches the given list of canonical 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);
+ Project project,
+ Collection<ListenableFuture<File>> fileFutures,
+ ListeningExecutorService executor);
+
+ default boolean enabled(Project project) {
+ return Blaze.isBlazeProject(project);
+ }
}
diff --git a/base/src/com/google/idea/blaze/base/prefetch/ProtoPrefetchFileSource.java b/base/src/com/google/idea/blaze/base/prefetch/ProtoPrefetchFileSource.java
new file mode 100644
index 0000000..efc4876
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/prefetch/ProtoPrefetchFileSource.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.ImmutableSet;
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.projectview.ProjectViewSet;
+import com.google.idea.blaze.base.sync.projectview.ImportRoots;
+import com.intellij.openapi.project.Project;
+import java.io.File;
+import java.util.Set;
+
+/** Requests that proto source files be prefetched during sync. */
+public class ProtoPrefetchFileSource implements PrefetchFileSource {
+
+ @Override
+ public void addFilesToPrefetch(
+ Project project,
+ ProjectViewSet projectViewSet,
+ ImportRoots importRoots,
+ BlazeProjectData blazeProjectData,
+ Set<File> files) {}
+
+ @Override
+ public Set<String> prefetchFileExtensions() {
+ return ImmutableSet.of("proto");
+ }
+}
diff --git a/base/src/com/google/idea/blaze/base/projectview/ProjectViewSet.java b/base/src/com/google/idea/blaze/base/projectview/ProjectViewSet.java
index e2e1211..29cc456 100644
--- a/base/src/com/google/idea/blaze/base/projectview/ProjectViewSet.java
+++ b/base/src/com/google/idea/blaze/base/projectview/ProjectViewSet.java
@@ -26,6 +26,7 @@
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
+import java.util.Optional;
import javax.annotation.Nullable;
/** A collection of project views and their file names. */
@@ -57,20 +58,12 @@
}
/** 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 */
- @Nullable
- public <T> T getScalarValue(SectionKey<T, ScalarSection<T>> key, @Nullable T defaultValue) {
+ public <T> Optional<T> getScalarValue(SectionKey<T, ScalarSection<T>> key) {
Collection<ScalarSection<T>> sections = getSections(key);
if (sections.isEmpty()) {
- return defaultValue;
- } else {
- return Iterables.getLast(sections).getValue();
+ return Optional.empty();
}
+ return Optional.of(Iterables.getLast(sections).getValue());
}
public <T, SectionType extends Section<T>> Collection<SectionType> getSections(
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
index 9e05e64..68ca00a 100644
--- 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
@@ -100,8 +100,9 @@
private static Set<LanguageClass> availableAdditionalLanguages(ProjectViewSet projectView) {
WorkspaceType workspaceType =
- projectView.getScalarValue(
- WorkspaceTypeSection.KEY, LanguageSupport.getDefaultWorkspaceType());
+ projectView
+ .getScalarValue(WorkspaceTypeSection.KEY)
+ .orElse(LanguageSupport.getDefaultWorkspaceType());
return LanguageSupport.availableAdditionalLanguages(workspaceType);
}
}
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
index 51f0709..1cde7d7 100644
--- 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
@@ -21,6 +21,7 @@
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.settings.Blaze;
import javax.annotation.Nullable;
/** Section for blaze_flags */
@@ -29,7 +30,7 @@
public static final SectionParser PARSER = new BuildFlagsSectionParser();
static class BuildFlagsSectionParser extends ListSectionParser<String> {
- protected BuildFlagsSectionParser() {
+ BuildFlagsSectionParser() {
super(KEY);
}
@@ -51,7 +52,10 @@
@Override
public String quickDocs() {
- return "A set of flags that get passed to all build command invocations as arguments";
+ return String.format(
+ "A set of flags that get passed to all %s build command invocations as arguments. This"
+ + "includes both sync and run configuration actions.",
+ Blaze.guessBuildSystemName());
}
}
}
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
index eda173f..3889951 100644
--- 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
@@ -33,11 +33,13 @@
AdditionalLanguagesSection.PARSER,
TestSourceSection.PARSER,
BuildFlagsSection.PARSER,
+ SyncFlagsSection.PARSER,
ImportTargetOutputSection.PARSER,
ExcludeTargetSection.PARSER,
ExcludedSourceSection.PARSER,
RunConfigurationsSection.PARSER,
- ShardBlazeBuildsSection.PARSER);
+ ShardBlazeBuildsSection.PARSER,
+ TargetShardSizeSection.PARSER);
public static List<SectionParser> getParsers() {
List<SectionParser> parsers = Lists.newArrayList(PARSERS);
diff --git a/base/src/com/google/idea/blaze/base/projectview/section/sections/SyncFlagsSection.java b/base/src/com/google/idea/blaze/base/projectview/section/sections/SyncFlagsSection.java
new file mode 100644
index 0000000..b2c4b11
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/projectview/section/sections/SyncFlagsSection.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 com.google.idea.blaze.base.settings.Blaze;
+import javax.annotation.Nullable;
+
+/** Section for blaze_flags */
+public class SyncFlagsSection {
+ public static final SectionKey<String, ListSection<String>> KEY = SectionKey.of("sync_flags");
+ public static final SectionParser PARSER = new SyncFlagsSectionParser();
+
+ static class SyncFlagsSectionParser extends ListSectionParser<String> {
+ SyncFlagsSectionParser() {
+ 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;
+ }
+
+ @Override
+ public String quickDocs() {
+ return String.format(
+ "A set of flags that get passed to %s build during all sync actions. Unlike"
+ + "'build_flags', these are not used for run configurations, so use 'sync_flags' "
+ + "only when necessary, as they can defeat %<s caching.",
+ Blaze.guessBuildSystemName());
+ }
+ }
+}
diff --git a/base/src/com/google/idea/blaze/base/projectview/section/sections/TargetShardSizeSection.java b/base/src/com/google/idea/blaze/base/projectview/section/sections/TargetShardSizeSection.java
new file mode 100644
index 0000000..7d1b354
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/projectview/section/sections/TargetShardSizeSection.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 javax.annotation.Nullable;
+
+/** Allows the user to tune the maximum number of targets in each blaze build shard. */
+public class TargetShardSizeSection {
+ public static final SectionKey<Integer, ScalarSection<Integer>> KEY =
+ SectionKey.of("target_shard_size");
+ public static final SectionParser PARSER = new TargetShardSizeSectionParser();
+
+ private static class TargetShardSizeSectionParser extends ScalarSectionParser<Integer> {
+ TargetShardSizeSectionParser() {
+ super(KEY, ':');
+ }
+
+ @Nullable
+ @Override
+ protected Integer parseItem(ProjectViewParser parser, ParseContext parseContext, String rest) {
+ try {
+ return Integer.parseInt(rest);
+ } catch (NumberFormatException e) {
+ parseContext.addError(
+ String.format("Invalid shard size '%s': Shard size must be an integer", rest));
+ return null;
+ }
+ }
+
+ @Override
+ protected void printItem(StringBuilder sb, Integer value) {
+ sb.append(value.toString());
+ }
+
+ @Override
+ public ItemType getItemType() {
+ return ItemType.Other;
+ }
+
+ @Override
+ public String quickDocs() {
+ return "Sets the maximum number of targets per shard, when sharding build invocations during "
+ + "sync. Only relevant if 'shard_sync: true' is also set";
+ }
+ }
+}
diff --git a/base/src/com/google/idea/blaze/base/run/BlazeBuildTargetRunConfigurationFactory.java b/base/src/com/google/idea/blaze/base/run/BlazeBuildTargetRunConfigurationFactory.java
index afb6e79..f6fe1fb 100644
--- a/base/src/com/google/idea/blaze/base/run/BlazeBuildTargetRunConfigurationFactory.java
+++ b/base/src/com/google/idea/blaze/base/run/BlazeBuildTargetRunConfigurationFactory.java
@@ -15,9 +15,15 @@
*/
package com.google.idea.blaze.base.run;
+import com.google.common.collect.ImmutableSet;
+import com.google.idea.blaze.base.command.BlazeCommandName;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.model.primitives.Kind;
import com.google.idea.blaze.base.model.primitives.Label;
-import com.google.idea.blaze.base.run.producers.BlazeBuildFileRunConfigurationProducer;
+import com.google.idea.blaze.base.model.primitives.RuleType;
+import com.google.idea.blaze.base.run.state.BlazeCommandRunConfigurationCommonState;
import com.intellij.execution.configurations.ConfigurationFactory;
import com.intellij.execution.configurations.RunConfiguration;
import com.intellij.openapi.project.Project;
@@ -28,9 +34,14 @@
*/
public class BlazeBuildTargetRunConfigurationFactory extends BlazeRunConfigurationFactory {
+ // The rule types we auto-create run configurations for during sync.
+ private static final ImmutableSet<RuleType> HANDLED_RULE_TYPES =
+ ImmutableSet.of(RuleType.TEST, RuleType.BINARY);
+
@Override
public boolean handlesTarget(Project project, BlazeProjectData blazeProjectData, Label label) {
- return BlazeBuildFileRunConfigurationProducer.handlesTarget(project, label);
+ TargetIdeInfo target = blazeProjectData.targetMap.get(TargetKey.forPlainTarget(label));
+ return target != null && HANDLED_RULE_TYPES.contains(target.kind.ruleType);
}
@Override
@@ -40,6 +51,26 @@
@Override
public void setupConfiguration(RunConfiguration configuration, Label target) {
- BlazeBuildFileRunConfigurationProducer.setupConfiguration(configuration, target);
+ BlazeCommandRunConfiguration blazeConfig = (BlazeCommandRunConfiguration) configuration;
+ blazeConfig.setTarget(target);
+
+ BlazeCommandRunConfigurationCommonState state =
+ blazeConfig.getHandlerStateIfType(BlazeCommandRunConfigurationCommonState.class);
+ Kind kind = blazeConfig.getKindForTarget();
+ if (state != null && kind != null) {
+ state.getCommandState().setCommand(commandForRuleType(kind.ruleType));
+ }
+ blazeConfig.setGeneratedName();
+ }
+
+ private static BlazeCommandName commandForRuleType(RuleType ruleType) {
+ switch (ruleType) {
+ case BINARY:
+ return BlazeCommandName.RUN;
+ case TEST:
+ return BlazeCommandName.TEST;
+ default:
+ return BlazeCommandName.BUILD;
+ }
}
}
diff --git a/base/src/com/google/idea/blaze/base/run/BlazeRunConfigurationFactory.java b/base/src/com/google/idea/blaze/base/run/BlazeRunConfigurationFactory.java
index 6a8d573..9e40076 100644
--- a/base/src/com/google/idea/blaze/base/run/BlazeRunConfigurationFactory.java
+++ b/base/src/com/google/idea/blaze/base/run/BlazeRunConfigurationFactory.java
@@ -33,13 +33,8 @@
public abstract boolean handlesTarget(
Project project, BlazeProjectData blazeProjectData, Label label);
- /**
- * 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) {
+ /** Returns whether this factory is compatible with the given run configuration type. */
+ public final boolean handlesConfiguration(RunConfiguration configuration) {
return getConfigurationFactory().getType().equals(configuration.getType());
}
diff --git a/base/src/com/google/idea/blaze/base/run/BlazeRunConfigurationSyncListener.java b/base/src/com/google/idea/blaze/base/run/BlazeRunConfigurationSyncListener.java
index 96be88d..f8a3b01 100644
--- a/base/src/com/google/idea/blaze/base/run/BlazeRunConfigurationSyncListener.java
+++ b/base/src/com/google/idea/blaze/base/run/BlazeRunConfigurationSyncListener.java
@@ -122,7 +122,6 @@
configurationFactory.createForTarget(project, runManager, label);
runManager.addConfiguration(settings, /* isShared */ false);
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/DistributedExecutorSupport.java b/base/src/com/google/idea/blaze/base/run/DistributedExecutorSupport.java
index c6991ca..a5ff002 100644
--- a/base/src/com/google/idea/blaze/base/run/DistributedExecutorSupport.java
+++ b/base/src/com/google/idea/blaze/base/run/DistributedExecutorSupport.java
@@ -23,7 +23,11 @@
import java.util.List;
import javax.annotation.Nullable;
-/** Information about any distributed executor available to the build system. */
+/**
+ * Information about any distributed executor available to the build system.
+ *
+ * <p>TODO(brendandouglas): Temporary migration code. Remove in 2017.09.XX+
+ */
public interface DistributedExecutorSupport {
ExtensionPointName<DistributedExecutorSupport> EP_NAME =
diff --git a/base/src/com/google/idea/blaze/base/run/TestTargetHeuristic.java b/base/src/com/google/idea/blaze/base/run/TestTargetHeuristic.java
index 74a46e4..f063415 100644
--- a/base/src/com/google/idea/blaze/base/run/TestTargetHeuristic.java
+++ b/base/src/com/google/idea/blaze/base/run/TestTargetHeuristic.java
@@ -24,7 +24,10 @@
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import java.io.File;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
import javax.annotation.Nullable;
/** Heuristic to match test targets to source files. */
@@ -64,22 +67,28 @@
File sourceFile,
Collection<TargetIdeInfo> targets,
@Nullable TestSize testSize) {
-
+ if (targets.isEmpty()) {
+ return null;
+ }
+ List<TargetIdeInfo> filteredTargets = new ArrayList<>(targets);
for (TestTargetHeuristic filter : EP_NAME.getExtensions()) {
- TargetIdeInfo match =
- targets
+ List<TargetIdeInfo> matches =
+ filteredTargets
.stream()
.filter(
target ->
filter.matchesSource(project, target, sourcePsiFile, sourceFile, testSize))
- .findFirst()
- .orElse(null);
-
- if (match != null) {
- return match.key.label;
+ .collect(Collectors.toList());
+ if (matches.size() == 1) {
+ return matches.get(0).key.label;
+ }
+ if (!matches.isEmpty()) {
+ // A higher-priority filter found more than one match -- subsequent filters will only
+ // consider these matches.
+ filteredTargets = matches;
}
}
- return targets.isEmpty() ? null : targets.iterator().next().key.label;
+ return filteredTargets.iterator().next().key.label;
}
/** Returns true if the rule and source file match, according to this heuristic. */
diff --git a/base/src/com/google/idea/blaze/base/run/confighandler/BlazeCommandGenericRunConfigurationRunner.java b/base/src/com/google/idea/blaze/base/run/confighandler/BlazeCommandGenericRunConfigurationRunner.java
index 4605c17..5e77bfe 100644
--- a/base/src/com/google/idea/blaze/base/run/confighandler/BlazeCommandGenericRunConfigurationRunner.java
+++ b/base/src/com/google/idea/blaze/base/run/confighandler/BlazeCommandGenericRunConfigurationRunner.java
@@ -20,17 +20,18 @@
import com.google.idea.blaze.base.command.BlazeCommand;
import com.google.idea.blaze.base.command.BlazeCommandName;
import com.google.idea.blaze.base.command.BlazeFlags;
+import com.google.idea.blaze.base.command.BlazeInvocationContext;
import com.google.idea.blaze.base.issueparser.IssueOutputLineProcessor;
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.DistributedExecutorSupport;
import com.google.idea.blaze.base.run.filter.BlazeTargetFilter;
import com.google.idea.blaze.base.run.processhandler.LineProcessingProcessAdapter;
import com.google.idea.blaze.base.run.processhandler.ScopedBlazeProcessHandler;
-import com.google.idea.blaze.base.run.smrunner.BlazeTestEventsHandler;
+import com.google.idea.blaze.base.run.smrunner.BlazeTestUiSession;
import com.google.idea.blaze.base.run.smrunner.SmRunnerUtils;
+import com.google.idea.blaze.base.run.smrunner.TestUiSessionProvider;
import com.google.idea.blaze.base.run.state.BlazeCommandRunConfigurationCommonState;
import com.google.idea.blaze.base.scope.BlazeContext;
import com.google.idea.blaze.base.scope.scopes.IdeaLogScope;
@@ -48,6 +49,7 @@
import com.intellij.execution.configurations.WrappingRunConfiguration;
import com.intellij.execution.filters.Filter;
import com.intellij.execution.filters.TextConsoleBuilderImpl;
+import com.intellij.execution.filters.UrlFilter;
import com.intellij.execution.process.ProcessHandler;
import com.intellij.execution.process.ProcessListener;
import com.intellij.execution.runners.ExecutionEnvironment;
@@ -91,6 +93,7 @@
ImmutableList.<Filter>builder()
.addAll(consoleFilters)
.add(new BlazeTargetFilter(environment.getProject()))
+ .add(new UrlFilter())
.build();
}
@@ -121,18 +124,18 @@
assert projectViewSet != null;
ImmutableList<String> testHandlerFlags = ImmutableList.of();
- BlazeTestEventsHandler testEventsHandler =
+ BlazeTestUiSession testUiSession =
canUseTestUi()
- ? BlazeTestEventsHandler.getHandlerForTarget(project, configuration.getTarget())
+ ? TestUiSessionProvider.createForTarget(project, configuration.getTarget())
: null;
- if (testEventsHandler != null) {
- testHandlerFlags = BlazeTestEventsHandler.getBlazeFlags(project);
+ if (testUiSession != null) {
+ testHandlerFlags = testUiSession.getBlazeFlags();
setConsoleBuilder(
new TextConsoleBuilderImpl(project) {
@Override
protected ConsoleView createConsole() {
return SmRunnerUtils.getConsoleView(
- project, configuration, getEnvironment().getExecutor(), testEventsHandler);
+ project, configuration, getEnvironment().getExecutor(), testUiSession);
}
});
}
@@ -170,23 +173,23 @@
? handlerState.getBlazeBinaryState().getBlazeBinary()
: Blaze.getBuildSystemProvider(project).getBinaryPath();
- BlazeCommand.Builder command =
- BlazeCommand.builder(binaryPath, handlerState.getCommandState().getCommand())
- .addTargets(configuration.getTarget())
- .addBlazeFlags(BlazeFlags.buildFlags(project, projectViewSet))
- .addBlazeFlags(testHandlerFlags)
- .addBlazeFlags(handlerState.getBlazeFlagsState().getExpandedFlags())
- .addExeFlags(handlerState.getExeFlagsState().getExpandedFlags());
+ return BlazeCommand.builder(binaryPath, getCommand())
+ .addTargets(configuration.getTarget())
+ .addBlazeFlags(
+ BlazeFlags.blazeFlags(
+ project, projectViewSet, getCommand(), BlazeInvocationContext.RunConfiguration))
+ .addBlazeFlags(testHandlerFlags)
+ .addBlazeFlags(handlerState.getBlazeFlagsState().getExpandedFlags())
+ .addExeFlags(handlerState.getExeFlagsState().getExpandedFlags())
+ .build();
+ }
- command.addBlazeFlags(
- DistributedExecutorSupport.getBlazeFlags(
- project, handlerState.getRunOnDistributedExecutorState().runOnDistributedExecutor));
- return command.build();
+ private BlazeCommandName getCommand() {
+ return handlerState.getCommandState().getCommand();
}
private boolean canUseTestUi() {
- return BlazeCommandName.TEST.equals(handlerState.getCommandState().getCommand())
- && !handlerState.getRunOnDistributedExecutorState().runOnDistributedExecutor;
+ return BlazeCommandName.TEST.equals(getCommand());
}
}
}
diff --git a/base/src/com/google/idea/blaze/base/run/exporter/RunConfigurationSerializer.java b/base/src/com/google/idea/blaze/base/run/exporter/RunConfigurationSerializer.java
index aed9db4..a52a756 100644
--- a/base/src/com/google/idea/blaze/base/run/exporter/RunConfigurationSerializer.java
+++ b/base/src/com/google/idea/blaze/base/run/exporter/RunConfigurationSerializer.java
@@ -17,6 +17,7 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.idea.blaze.base.run.BlazeRunConfiguration;
+import com.google.idea.sdkcompat.run.RunnerAndConfigurationSettingsCompatUtils;
import com.intellij.execution.RunnerAndConfigurationSettings;
import com.intellij.execution.configurations.RunConfiguration;
import com.intellij.execution.impl.RunManagerImpl;
@@ -89,7 +90,7 @@
throws InvalidDataException {
RunManagerImpl manager = RunManagerImpl.getInstanceImpl(project);
RunnerAndConfigurationSettingsImpl settings = new RunnerAndConfigurationSettingsImpl(manager);
- settings.readExternal(element);
+ RunnerAndConfigurationSettingsCompatUtils.readConfiguration(settings, element);
RunConfiguration config = settings.getConfiguration();
if (config == null) {
return null;
diff --git a/base/src/com/google/idea/blaze/base/run/producers/BlazeBuildFileRunConfigurationProducer.java b/base/src/com/google/idea/blaze/base/run/producers/BlazeBuildFileRunConfigurationProducer.java
index 9a838ed..6ce791c 100644
--- a/base/src/com/google/idea/blaze/base/run/producers/BlazeBuildFileRunConfigurationProducer.java
+++ b/base/src/com/google/idea/blaze/base/run/producers/BlazeBuildFileRunConfigurationProducer.java
@@ -111,14 +111,11 @@
setupConfiguration(
configuration.getProject(), blazeProjectData, generatedConfiguration, 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 (Kind.isTestRule(target.ruleType)) {
- BlazeCommandRunConfigurationCommonState handlerState =
- configuration.getHandlerStateIfType(BlazeCommandRunConfigurationCommonState.class);
- if (handlerState != null && handlerState.getTestFilterFlag() != null) {
- return false;
- }
+ // ignore filtered test configs, produced by other configuration producers.
+ BlazeCommandRunConfigurationCommonState handlerState =
+ configuration.getHandlerStateIfType(BlazeCommandRunConfigurationCommonState.class);
+ if (handlerState != null && handlerState.getTestFilterFlag() != null) {
+ return false;
}
return Objects.equals(configuration.suggestedName(), generatedConfiguration.suggestedName())
diff --git a/base/src/com/google/idea/blaze/base/run/producers/BlazeFilterExistingRunConfigurationProducer.java b/base/src/com/google/idea/blaze/base/run/producers/BlazeFilterExistingRunConfigurationProducer.java
index 92089e4..d1941a1 100644
--- a/base/src/com/google/idea/blaze/base/run/producers/BlazeFilterExistingRunConfigurationProducer.java
+++ b/base/src/com/google/idea/blaze/base/run/producers/BlazeFilterExistingRunConfigurationProducer.java
@@ -31,7 +31,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
-import javax.annotation.Nullable;
+import java.util.Optional;
/**
* Handles the specific case where the user creates a run configuration by selecting test suites /
@@ -53,8 +53,8 @@
BlazeCommandRunConfiguration configuration,
ConfigurationContext context,
Ref<PsiElement> sourceElement) {
- String testFilter = getTestFilter(context);
- if (testFilter == null) {
+ Optional<String> testFilter = getTestFilter(context);
+ if (!testFilter.isPresent()) {
return false;
}
BlazeCommandRunConfigurationCommonState handlerState =
@@ -66,7 +66,7 @@
// replace old test filter flag if present
List<String> flags = new ArrayList<>(handlerState.getBlazeFlagsState().getRawFlags());
flags.removeIf((flag) -> flag.startsWith(BlazeFlags.TEST_FILTER));
- flags.add(testFilter);
+ flags.add(testFilter.get());
if (SmRunnerUtils.countSelectedTestCases(context) == 1
&& !flags.contains(BlazeFlags.DISABLE_TEST_SHARDING)) {
@@ -81,8 +81,8 @@
@Override
protected boolean doIsConfigFromContext(
BlazeCommandRunConfiguration configuration, ConfigurationContext context) {
- String testFilter = getTestFilter(context);
- if (testFilter == null) {
+ Optional<String> testFilter = getTestFilter(context);
+ if (!testFilter.isPresent()) {
return false;
}
BlazeCommandRunConfigurationCommonState handlerState =
@@ -90,28 +90,25 @@
return handlerState != null
&& Objects.equals(handlerState.getCommandState().getCommand(), BlazeCommandName.TEST)
- && Objects.equals(testFilter, handlerState.getTestFilterFlag());
+ && Objects.equals(testFilter.get(), handlerState.getTestFilterFlag());
}
- @Nullable
- private static String getTestFilter(ConfigurationContext context) {
+ private static Optional<String> getTestFilter(ConfigurationContext context) {
RunConfiguration base = context.getOriginalConfiguration(null);
if (!(base instanceof BlazeCommandRunConfiguration)) {
- return null;
+ return Optional.empty();
}
TargetExpression target = ((BlazeCommandRunConfiguration) base).getTarget();
if (target == null) {
- return null;
- }
- BlazeTestEventsHandler testEventsHandler =
- BlazeTestEventsHandler.getHandlerForTarget(context.getProject(), target);
- if (testEventsHandler == null) {
- return null;
+ return Optional.empty();
}
List<Location<?>> selectedElements = SmRunnerUtils.getSelectedSmRunnerTreeElements(context);
if (selectedElements.isEmpty()) {
return null;
}
- return testEventsHandler.getTestFilter(context.getProject(), selectedElements);
+ Optional<BlazeTestEventsHandler> testEventsHandler =
+ BlazeTestEventsHandler.getHandlerForTarget(context.getProject(), target);
+ return testEventsHandler.map(
+ handler -> handler.getTestFilter(context.getProject(), selectedElements));
}
}
diff --git a/base/src/com/google/idea/blaze/base/run/smrunner/BazelTestUiSessionProvider.java b/base/src/com/google/idea/blaze/base/run/smrunner/BazelTestUiSessionProvider.java
new file mode 100644
index 0000000..d9ec034
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/run/smrunner/BazelTestUiSessionProvider.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.smrunner;
+
+import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.base.command.buildresult.BuildEventProtocolUtils;
+import com.google.idea.blaze.base.model.BlazeVersionData;
+import com.google.idea.blaze.base.run.testlogs.BuildEventProtocolTestFinderStrategy;
+import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
+import java.io.File;
+import javax.annotation.Nullable;
+
+/** Provides a {@link BlazeTestUiSession} for Bazel projects. */
+public class BazelTestUiSessionProvider implements TestUiSessionProvider {
+
+ @Nullable
+ @Override
+ public BlazeTestUiSession getTestUiSession(BlazeVersionData blazeVersion) {
+ if (blazeVersion.buildSystem() != BuildSystem.Bazel) {
+ return null;
+ }
+
+ File bepOutputFile = BuildEventProtocolUtils.createTempOutputFile();
+ ImmutableList<String> flags =
+ ImmutableList.<String>builder()
+ .add("--runs_per_test=1", "--flaky_test_attempts=1")
+ .addAll(BuildEventProtocolUtils.getBuildFlags(bepOutputFile))
+ .build();
+
+ return BlazeTestUiSession.create(
+ flags, new BuildEventProtocolTestFinderStrategy(bepOutputFile));
+ }
+}
diff --git a/base/src/com/google/idea/blaze/base/run/smrunner/BlazeCompositeTestEventsHandler.java b/base/src/com/google/idea/blaze/base/run/smrunner/BlazeCompositeTestEventsHandler.java
deleted file mode 100644
index 21bf76a..0000000
--- a/base/src/com/google/idea/blaze/base/run/smrunner/BlazeCompositeTestEventsHandler.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright 2017 The Bazel Authors. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.smrunner;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Maps;
-import com.google.idea.blaze.base.model.primitives.Kind;
-import com.google.idea.blaze.base.run.smrunner.BlazeXmlSchema.TestSuite;
-import com.intellij.execution.Location;
-import com.intellij.execution.testframework.actions.AbstractRerunFailedTestsAction;
-import com.intellij.execution.testframework.sm.runner.SMTestLocator;
-import com.intellij.execution.ui.ConsoleView;
-import com.intellij.openapi.project.Project;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-import javax.annotation.Nullable;
-
-/** Combines multiple language-specific handlers (e.g. to handle test_suite targets). */
-public class BlazeCompositeTestEventsHandler extends BlazeTestEventsHandler {
-
- private static ImmutableMap<Kind, BlazeTestEventsHandler> collectHandlers() {
- Map<Kind, BlazeTestEventsHandler> map = new HashMap<>();
- for (BlazeTestEventsHandler handler : BlazeTestEventsHandler.EP_NAME.getExtensions()) {
- if (handler instanceof BlazeCompositeTestEventsHandler) {
- continue;
- }
- for (Kind kind : handler.handledKinds()) {
- // earlier handlers get priority.
- map.putIfAbsent(kind, handler);
- }
- }
- return Maps.immutableEnumMap(map);
- }
-
- private static ImmutableMap<Kind, BlazeTestEventsHandler> handlers;
-
- private static ImmutableMap<Kind, BlazeTestEventsHandler> getHandlers() {
- if (handlers == null) {
- handlers = collectHandlers();
- }
- return handlers;
- }
-
- @Override
- public boolean handlesTargetKind(@Nullable Kind kind) {
- // composite handler specifically exists to handle test-suites and multi-target blaze
- // invocations, so must handle targets without a kind.
- return kind == null || kind == Kind.TEST_SUITE || handledKinds().contains(kind);
- }
-
- @Override
- protected EnumSet<Kind> handledKinds() {
- ImmutableSet<Kind> handledKinds = getHandlers().keySet();
- return !handledKinds.isEmpty() ? EnumSet.copyOf(handledKinds) : EnumSet.noneOf(Kind.class);
- }
-
- @Override
- public SMTestLocator getTestLocator() {
- return new CompositeSMTestLocator(
- ImmutableList.copyOf(
- getHandlers()
- .values()
- .stream()
- .map(BlazeTestEventsHandler::getTestLocator)
- .collect(Collectors.toList())));
- }
-
- @Nullable
- @Override
- public String getTestFilter(Project project, List<Location<?>> testLocations) {
- // We make no attempt to support re-running a subset of tests for test_suites or target patterns
- return null;
- }
-
- @Nullable
- @Override
- public AbstractRerunFailedTestsAction createRerunFailedTestsAction(ConsoleView consoleView) {
- return null;
- }
-
- @Override
- public boolean ignoreSuite(@Nullable Kind kind, TestSuite suite) {
- BlazeTestEventsHandler handler = kind != null ? getHandlers().get(kind) : null;
- return handler != null ? handler.ignoreSuite(kind, suite) : super.ignoreSuite(kind, suite);
- }
-
- /** Converts the testsuite name in the blaze test XML to a user-friendly format */
- @Override
- public String suiteDisplayName(@Nullable Kind kind, String rawName) {
- BlazeTestEventsHandler handler = kind != null ? getHandlers().get(kind) : null;
- return handler != null
- ? handler.suiteDisplayName(kind, rawName)
- : super.suiteDisplayName(kind, rawName);
- }
-
- /** Converts the testcase name in the blaze test XML to a user-friendly format */
- @Override
- public String testDisplayName(@Nullable Kind kind, String rawName) {
- BlazeTestEventsHandler handler = kind != null ? getHandlers().get(kind) : null;
- return handler != null
- ? handler.testDisplayName(kind, rawName)
- : super.testDisplayName(kind, rawName);
- }
-
- @Override
- public String suiteLocationUrl(@Nullable Kind kind, String name) {
- BlazeTestEventsHandler handler = kind != null ? getHandlers().get(kind) : null;
- return handler != null
- ? handler.suiteLocationUrl(kind, name)
- : super.suiteLocationUrl(kind, name);
- }
-
- @Override
- public String testLocationUrl(
- @Nullable Kind kind, String parentSuite, String name, @Nullable String className) {
- BlazeTestEventsHandler handler = getHandlers().get(kind);
- return handler != null
- ? handler.testLocationUrl(kind, parentSuite, name, className)
- : super.testLocationUrl(kind, parentSuite, name, className);
- }
-}
diff --git a/base/src/com/google/idea/blaze/base/run/smrunner/BlazeGenericTestEventsHandler.java b/base/src/com/google/idea/blaze/base/run/smrunner/BlazeGenericTestEventsHandler.java
new file mode 100644
index 0000000..996b756
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/run/smrunner/BlazeGenericTestEventsHandler.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.smrunner;
+
+import com.google.idea.blaze.base.model.primitives.Kind;
+import com.intellij.execution.Location;
+import com.intellij.execution.testframework.actions.AbstractRerunFailedTestsAction;
+import com.intellij.execution.testframework.sm.runner.SMTestLocator;
+import com.intellij.execution.ui.ConsoleView;
+import com.intellij.openapi.project.Project;
+import java.util.List;
+import javax.annotation.Nullable;
+
+/**
+ * Fallback handler for otherwise unsupported targets. Normally it's undesirable to have a test UI
+ * for such targets, but if they're part of a test_suite or multi-target Blaze invocation, we handle
+ * them in a best-effort way.
+ */
+public class BlazeGenericTestEventsHandler implements BlazeTestEventsHandler {
+
+ @Override
+ public boolean handlesKind(@Nullable Kind kind) {
+ // Generic handler specifically exists to handle test-suites and multi-target blaze
+ // invocations, so must handle any targets without a (known) kind.
+ return kind == null || kind == Kind.TEST_SUITE;
+ }
+
+ @Override
+ @Nullable
+ public SMTestLocator getTestLocator() {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public String getTestFilter(Project project, List<Location<?>> testLocations) {
+ // Test filters are language-specific, and don't work properly for multi-target invocations.
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public AbstractRerunFailedTestsAction createRerunFailedTestsAction(ConsoleView consoleView) {
+ return null;
+ }
+}
diff --git a/base/src/com/google/idea/blaze/base/run/smrunner/BlazeRerunFailedTestsAction.java b/base/src/com/google/idea/blaze/base/run/smrunner/BlazeRerunFailedTestsAction.java
index 578bb9b..4fc47bb 100644
--- a/base/src/com/google/idea/blaze/base/run/smrunner/BlazeRerunFailedTestsAction.java
+++ b/base/src/com/google/idea/blaze/base/run/smrunner/BlazeRerunFailedTestsAction.java
@@ -42,7 +42,7 @@
private final BlazeTestEventsHandler eventsHandler;
- public BlazeRerunFailedTestsAction(
+ BlazeRerunFailedTestsAction(
BlazeTestEventsHandler eventsHandler, ComponentContainer componentContainer) {
super(componentContainer);
this.eventsHandler = eventsHandler;
diff --git a/base/src/com/google/idea/blaze/base/run/smrunner/BlazeTestConsoleProperties.java b/base/src/com/google/idea/blaze/base/run/smrunner/BlazeTestConsoleProperties.java
index f4c95b9..7e55569 100644
--- a/base/src/com/google/idea/blaze/base/run/smrunner/BlazeTestConsoleProperties.java
+++ b/base/src/com/google/idea/blaze/base/run/smrunner/BlazeTestConsoleProperties.java
@@ -15,8 +15,9 @@
*/
package com.google.idea.blaze.base.run.smrunner;
+import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
import com.intellij.execution.Executor;
-import com.intellij.execution.configurations.RunConfiguration;
import com.intellij.execution.testframework.TestConsoleProperties;
import com.intellij.execution.testframework.actions.AbstractRerunFailedTestsAction;
import com.intellij.execution.testframework.sm.SMCustomMessagesParsing;
@@ -24,34 +25,50 @@
import com.intellij.execution.testframework.sm.runner.SMTRunnerConsoleProperties;
import com.intellij.execution.testframework.sm.runner.SMTestLocator;
import com.intellij.execution.ui.ConsoleView;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.stream.Collectors;
import javax.annotation.Nullable;
/** Integrates blaze test results with the SM-runner test UI. */
public class BlazeTestConsoleProperties extends SMTRunnerConsoleProperties
implements SMCustomMessagesParsing {
- private final BlazeTestEventsHandler eventsHandler;
+ private final BlazeCommandRunConfiguration runConfiguration;
+ private final BlazeTestUiSession testUiSession;
public BlazeTestConsoleProperties(
- RunConfiguration runConfiguration, Executor executor, BlazeTestEventsHandler eventsHandler) {
+ BlazeCommandRunConfiguration runConfiguration,
+ Executor executor,
+ BlazeTestUiSession testUiSession) {
super(runConfiguration, SmRunnerUtils.BLAZE_FRAMEWORK, executor);
- this.eventsHandler = eventsHandler;
+ this.runConfiguration = runConfiguration;
+ this.testUiSession = testUiSession;
}
@Override
public OutputToGeneralTestEventsConverter createTestEventsConverter(
String framework, TestConsoleProperties consoleProperties) {
- return new BlazeXmlToTestEventsConverter(framework, consoleProperties, eventsHandler);
+ return new BlazeXmlToTestEventsConverter(
+ framework, consoleProperties, testUiSession.getTestResultFinderStrategy());
}
@Override
public SMTestLocator getTestLocator() {
- return eventsHandler.getTestLocator();
+ return new CompositeSMTestLocator(
+ ImmutableList.copyOf(
+ Arrays.stream(BlazeTestEventsHandler.EP_NAME.getExtensions())
+ .map(BlazeTestEventsHandler::getTestLocator)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList())));
}
@Nullable
@Override
public AbstractRerunFailedTestsAction createRerunFailedTestsAction(ConsoleView consoleView) {
- return eventsHandler.createRerunFailedTestsAction(consoleView);
+ return BlazeTestEventsHandler.getHandlerForTarget(
+ runConfiguration.getProject(), runConfiguration.getTarget())
+ .map(handler -> handler.createRerunFailedTestsAction(consoleView))
+ .orElse(null);
}
}
diff --git a/base/src/com/google/idea/blaze/base/run/smrunner/BlazeTestEventsHandler.java b/base/src/com/google/idea/blaze/base/run/smrunner/BlazeTestEventsHandler.java
index 3ecdeea..47f229c 100644
--- a/base/src/com/google/idea/blaze/base/run/smrunner/BlazeTestEventsHandler.java
+++ b/base/src/com/google/idea/blaze/base/run/smrunner/BlazeTestEventsHandler.java
@@ -16,15 +16,12 @@
package com.google.idea.blaze.base.run.smrunner;
import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableList;
import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
import com.google.idea.blaze.base.model.primitives.Kind;
import com.google.idea.blaze.base.model.primitives.Label;
import com.google.idea.blaze.base.model.primitives.TargetExpression;
import com.google.idea.blaze.base.run.smrunner.BlazeXmlSchema.TestSuite;
import com.google.idea.blaze.base.run.targetfinder.TargetFinder;
-import com.google.idea.blaze.base.settings.Blaze;
-import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
import com.intellij.execution.Location;
import com.intellij.execution.testframework.actions.AbstractRerunFailedTestsAction;
import com.intellij.execution.testframework.sm.runner.SMTestLocator;
@@ -32,46 +29,60 @@
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.project.Project;
import com.intellij.util.io.URLUtil;
-import java.util.EnumSet;
+import java.util.Arrays;
import java.util.List;
+import java.util.Optional;
import javax.annotation.Nullable;
-/** Language-specific handling of SM runner test protocol */
-public abstract class BlazeTestEventsHandler {
+/** Stateless language-specific handling of SM runner test protocol */
+public interface BlazeTestEventsHandler {
- static final ExtensionPointName<BlazeTestEventsHandler> EP_NAME =
+ ExtensionPointName<BlazeTestEventsHandler> EP_NAME =
ExtensionPointName.create("com.google.idea.blaze.BlazeTestEventsHandler");
/**
- * Blaze/Bazel flags required for test UI.<br>
- * Forces local test execution, without retries.
+ * Whether there's a {@link BlazeTestEventsHandler} applicable to the given target.
+ *
+ * <p>Test results will still be displayed for unhandled kinds if they're included in a test_suite
+ * or multi-target Blaze invocation, where we don't know up front the languages involved.
*/
- public static ImmutableList<String> getBlazeFlags(Project project) {
- ImmutableList.Builder<String> flags =
- ImmutableList.<String>builder().add("--runs_per_test=1", "--flaky_test_attempts=1");
- if (Blaze.getBuildSystem(project) == BuildSystem.Blaze) {
- flags.add("--test_strategy=local");
- }
- if (Blaze.getBuildSystem(project) == BuildSystem.Bazel) {
- flags.add("--test_sharding_strategy=disabled");
- }
- return flags.build();
- }
-
- @Nullable
- public static BlazeTestEventsHandler getHandlerForTarget(
- Project project, TargetExpression target) {
+ static boolean targetSupported(Project project, TargetExpression target) {
Kind kind = getKindForTarget(project, target);
- for (BlazeTestEventsHandler handler : EP_NAME.getExtensions()) {
- if (handler.handlesTargetKind(kind)) {
- return handler;
- }
- }
- return null;
+ return Arrays.stream(EP_NAME.getExtensions()).anyMatch(handler -> handler.handlesKind(kind));
+ }
+
+ /**
+ * Returns a {@link BlazeTestEventsHandler} applicable to the given target.
+ *
+ * <p>If no such handler exists, falls back to returning {@link BlazeGenericTestEventsHandler}.
+ * This adds support for test suites / multi-target invocations, which can mix supported and
+ * unsupported target kinds.
+ */
+ static BlazeTestEventsHandler getHandlerForTargetKindOrFallback(@Nullable Kind kind) {
+ return getHandlerForTargetKind(kind).orElse(new BlazeGenericTestEventsHandler());
+ }
+
+ /**
+ * Returns a {@link BlazeTestEventsHandler} applicable to the given target or {@link
+ * Optional#empty()} if no such handler can be found.
+ */
+ static Optional<BlazeTestEventsHandler> getHandlerForTarget(
+ Project project, TargetExpression target) {
+ return getHandlerForTargetKind(getKindForTarget(project, target));
+ }
+
+ /**
+ * Returns a {@link BlazeTestEventsHandler} applicable to the given target kind, or {@link
+ * Optional#empty()} if no such handler can be found.
+ */
+ static Optional<BlazeTestEventsHandler> getHandlerForTargetKind(@Nullable Kind kind) {
+ return Arrays.stream(EP_NAME.getExtensions())
+ .filter(handler -> handler.handlesKind(kind))
+ .findFirst();
}
@Nullable
- private static Kind getKindForTarget(Project project, TargetExpression target) {
+ static Kind getKindForTarget(Project project, TargetExpression target) {
if (!(target instanceof Label)) {
return null;
}
@@ -79,42 +90,46 @@
return targetInfo != null ? targetInfo.kind : null;
}
- public boolean handlesTargetKind(@Nullable Kind kind) {
- return handledKinds().contains(kind);
- }
+ boolean handlesKind(@Nullable Kind kind);
- protected abstract EnumSet<Kind> handledKinds();
-
- public abstract SMTestLocator getTestLocator();
+ /**
+ * A {@link SMTestLocator} to convert location URLs provided by this event handler to project PSI
+ * elements. Returns {@code null} if no such conversion is available.
+ */
+ @Nullable
+ SMTestLocator getTestLocator();
/**
* The --test_filter flag passed to blaze to rerun the given tests.
*
- * @return null if no filter can be constructed for these tests.
+ * @return {@code null} if no filter can be constructed for these tests
*/
@Nullable
- public abstract String getTestFilter(Project project, List<Location<?>> testLocations);
+ String getTestFilter(Project project, List<Location<?>> testLocations);
+ /** Returns {@code null} if this test events handler doesn't support test filtering. */
@Nullable
- public AbstractRerunFailedTestsAction createRerunFailedTestsAction(ConsoleView consoleView) {
+ default AbstractRerunFailedTestsAction createRerunFailedTestsAction(ConsoleView consoleView) {
return new BlazeRerunFailedTestsAction(this, consoleView);
}
- /** Converts the testsuite name in the blaze test XML to a user-friendly format */
- public String suiteDisplayName(@Nullable Kind kind, String rawName) {
+ /** Converts the testsuite name in the blaze test XML to a user-friendly format. */
+ default String suiteDisplayName(@Nullable Kind kind, String rawName) {
return rawName;
}
- /** Converts the testcase name in the blaze test XML to a user-friendly format */
- public String testDisplayName(@Nullable Kind kind, String rawName) {
+ /** Converts the testcase name in the blaze test XML to a user-friendly format. */
+ default String testDisplayName(@Nullable Kind kind, String rawName) {
return rawName;
}
- public String suiteLocationUrl(@Nullable Kind kind, String name) {
+ /** Converts the suite name to a parsable location URL. */
+ default String suiteLocationUrl(@Nullable Kind kind, String name) {
return SmRunnerUtils.GENERIC_SUITE_PROTOCOL + URLUtil.SCHEME_SEPARATOR + name;
}
- public String testLocationUrl(
+ /** Converts the test case and suite names to a parsable location URL. */
+ default String testLocationUrl(
@Nullable Kind kind, String parentSuite, String name, @Nullable String className) {
String base = SmRunnerUtils.GENERIC_TEST_PROTOCOL + URLUtil.SCHEME_SEPARATOR;
if (Strings.isNullOrEmpty(className)) {
@@ -124,7 +139,7 @@
}
/** Whether to skip logging a {@link TestSuite}. */
- public boolean ignoreSuite(@Nullable Kind kind, TestSuite suite) {
+ default boolean ignoreSuite(@Nullable Kind kind, TestSuite suite) {
// by default only include innermost 'testsuite' elements
return !suite.testSuites.isEmpty();
}
diff --git a/base/src/com/google/idea/blaze/base/run/smrunner/BlazeTestUiSession.java b/base/src/com/google/idea/blaze/base/run/smrunner/BlazeTestUiSession.java
new file mode 100644
index 0000000..bf9a6c6
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/run/smrunner/BlazeTestUiSession.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.smrunner;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.base.run.testlogs.BlazeTestResultFinderStrategy;
+
+/**
+ * Created during a single blaze test invocation, to manage test result finding and UI.
+ *
+ * <p>Unlike {@link BlazeTestEventsHandler}, this can be stateful, retaining information shared
+ * between all stages of the test (e.g. an output file path used for both the initial blaze
+ * invocation and required when parsing test results).
+ */
+@AutoValue
+public abstract class BlazeTestUiSession {
+
+ public static BlazeTestUiSession create(
+ ImmutableList<String> blazeFlags, BlazeTestResultFinderStrategy testResultFinderStrategy) {
+ return new AutoValue_BlazeTestUiSession(blazeFlags, testResultFinderStrategy);
+ }
+
+ /**
+ * Blaze flags required for test UI.<br>
+ * Forces local test execution, without retries.
+ */
+ public abstract ImmutableList<String> getBlazeFlags();
+
+ /** Returns a {@link BlazeTestResultFinderStrategy} for this blaze test invocation. */
+ public abstract BlazeTestResultFinderStrategy getTestResultFinderStrategy();
+}
diff --git a/base/src/com/google/idea/blaze/base/run/smrunner/BlazeXmlToTestEventsConverter.java b/base/src/com/google/idea/blaze/base/run/smrunner/BlazeXmlToTestEventsConverter.java
index 0cdee3b..c450d90 100644
--- a/base/src/com/google/idea/blaze/base/run/smrunner/BlazeXmlToTestEventsConverter.java
+++ b/base/src/com/google/idea/blaze/base/run/smrunner/BlazeXmlToTestEventsConverter.java
@@ -15,13 +15,14 @@
*/
package com.google.idea.blaze.base.run.smrunner;
-import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.common.collect.ImmutableMap;
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.smrunner.BlazeXmlSchema.ErrorOrFailureOrSkipped;
import com.google.idea.blaze.base.run.smrunner.BlazeXmlSchema.TestCase;
import com.google.idea.blaze.base.run.smrunner.BlazeXmlSchema.TestSuite;
-import com.google.idea.blaze.base.run.targetfinder.TargetFinder;
+import com.google.idea.blaze.base.run.testlogs.BlazeTestResult;
+import com.google.idea.blaze.base.run.testlogs.BlazeTestResult.TestStatus;
import com.google.idea.blaze.base.run.testlogs.BlazeTestResultFinderStrategy;
import com.google.idea.blaze.base.run.testlogs.BlazeTestResults;
import com.google.idea.sdkcompat.smrunner.SmRunnerCompatUtils;
@@ -35,17 +36,15 @@
import com.intellij.execution.testframework.sm.runner.events.TestStartedEvent;
import com.intellij.execution.testframework.sm.runner.events.TestSuiteFinishedEvent;
import com.intellij.execution.testframework.sm.runner.events.TestSuiteStartedEvent;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.Key;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
-import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
import javax.annotation.Nullable;
-import jetbrains.buildServer.messages.serviceMessages.ServiceMessageVisitor;
import jetbrains.buildServer.messages.serviceMessages.TestSuiteStarted;
/** Converts blaze test runner xml logs to smRunner events. */
@@ -57,32 +56,14 @@
NO_ERROR.message = "No message"; // cannot be null
}
- private final Project project;
- private final BlazeTestEventsHandler eventsHandler;
+ private final BlazeTestResultFinderStrategy testResultFinderStrategy;
public BlazeXmlToTestEventsConverter(
String testFrameworkName,
TestConsoleProperties testConsoleProperties,
- BlazeTestEventsHandler eventsHandler) {
+ BlazeTestResultFinderStrategy testResultFinderStrategy) {
super(testFrameworkName, testConsoleProperties);
- this.project = testConsoleProperties.getProject();
- this.eventsHandler = eventsHandler;
- }
-
- @Override
- protected boolean processServiceMessages(
- String s, Key key, ServiceMessageVisitor serviceMessageVisitor) throws ParseException {
- return super.processServiceMessages(s, key, serviceMessageVisitor);
- }
-
- @Override
- public void process(String text, Key outputType) {
- super.process(text, outputType);
- }
-
- @Override
- public void dispose() {
- super.dispose();
+ this.testResultFinderStrategy = testResultFinderStrategy;
}
@Override
@@ -91,33 +72,29 @@
onStartTesting();
getProcessor().onTestsReporterAttached();
- BlazeTestResults testResults = BlazeTestResultFinderStrategy.locateTestResults(project);
- for (Label target : testResults.failedTargets) {
- reportFailedTarget(target);
+ BlazeTestResults testResults = testResultFinderStrategy.findTestResults();
+ if (testResults == null) {
+ return;
}
- for (Label label : testResults.testXmlFiles.keySet()) {
- processTestSuites(label, testResults.testXmlFiles.get(label));
+ for (Label label : testResults.perTargetResults.keySet()) {
+ processTestSuites(label, testResults.perTargetResults.get(label));
}
}
- private void reportFailedTarget(Label label) {
- GeneralTestEventsProcessor processor = getProcessor();
- TestSuiteStarted suiteStarted = new TestSuiteStarted(label.toString());
- processor.onSuiteStarted(new TestSuiteStartedEvent(suiteStarted, null));
- String targetName = label.targetName().toString();
- processor.onTestStarted(new TestStartedEvent(targetName, null));
- processor.onTestFailure(
- SmRunnerCompatUtils.getTestFailedEvent(
- targetName, "Target failed to build. See console output for details", null, 0));
- processor.onTestFinished(new TestFinishedEvent(targetName, 0L));
- processor.onSuiteFinished(new TestSuiteFinishedEvent(label.toString()));
- }
-
/** Process all test XML files from a single test target. */
- private void processTestSuites(Label label, Collection<File> files) {
- Kind kind = getKind(project, label);
+ private void processTestSuites(Label label, Collection<BlazeTestResult> results) {
+ List<File> outputFiles = new ArrayList<>();
+ results.forEach(result -> outputFiles.addAll(result.getOutputXmlFiles()));
+
+ if (noUsefulOutput(results, outputFiles)) {
+ Optional<TestStatus> status =
+ results.stream().map(BlazeTestResult::getTestStatus).findFirst();
+ status.ifPresent(testStatus -> reportTargetWithoutOutputFiles(label, testStatus));
+ return;
+ }
+
List<TestSuite> targetSuites = new ArrayList<>();
- for (File file : files) {
+ for (File file : outputFiles) {
try (InputStream input = new FileInputStream(file)) {
targetSuites.add(BlazeXmlSchema.parse(input));
} catch (Exception e) {
@@ -128,19 +105,71 @@
if (targetSuites.isEmpty()) {
return;
}
+ Kind kind =
+ results
+ .stream()
+ .map(BlazeTestResult::getTargetKind)
+ .filter(Objects::nonNull)
+ .findFirst()
+ .orElse(null);
+ BlazeTestEventsHandler eventsHandler =
+ BlazeTestEventsHandler.getHandlerForTargetKindOrFallback(kind);
TestSuite suite =
targetSuites.size() == 1 ? targetSuites.get(0) : BlazeXmlSchema.mergeSuites(targetSuites);
- processTestSuite(getProcessor(), kind, suite);
+ processTestSuite(getProcessor(), eventsHandler, kind, suite);
}
- @Nullable
- private static Kind getKind(Project project, Label label) {
- TargetIdeInfo target = TargetFinder.getInstance().targetForLabel(project, label);
- return target != null ? target.kind : null;
+ /** Return false if there's output XML which should be parsed. */
+ private static boolean noUsefulOutput(
+ Collection<BlazeTestResult> results, List<File> outputFiles) {
+ if (outputFiles.isEmpty()) {
+ return true;
+ }
+ TestStatus status =
+ results.stream().map(BlazeTestResult::getTestStatus).findFirst().orElse(null);
+ return status != null && BlazeTestResult.NO_USEFUL_OUTPUT.contains(status);
}
- private void processTestSuite(
- GeneralTestEventsProcessor processor, @Nullable Kind kind, TestSuite suite) {
+ /**
+ * If there are no output files, the test may have failed to build, or timed out. Provide a
+ * suitable message in the test UI.
+ */
+ private void reportTargetWithoutOutputFiles(Label label, TestStatus status) {
+ if (status == TestStatus.PASSED) {
+ // Empty test targets do not produce output XML, yet technically pass. Ignore them.
+ return;
+ }
+ GeneralTestEventsProcessor processor = getProcessor();
+ TestSuiteStarted suiteStarted = new TestSuiteStarted(label.toString());
+ processor.onSuiteStarted(new TestSuiteStartedEvent(suiteStarted, /*locationUrl=*/ null));
+ String targetName = label.targetName().toString();
+ processor.onTestStarted(new TestStartedEvent(targetName, /*locationUrl=*/ null));
+ processor.onTestFailure(
+ SmRunnerCompatUtils.getTestFailedEvent(
+ targetName,
+ STATUS_EXPLANATIONS.get(status) + " See console output for details",
+ /*content=*/ null,
+ /*duration=*/ 0));
+ processor.onTestFinished(new TestFinishedEvent(targetName, /*duration=*/ 0L));
+ processor.onSuiteFinished(new TestSuiteFinishedEvent(label.toString()));
+ }
+
+ /** Status explanations for tests without output XML. */
+ private static final ImmutableMap<TestStatus, String> STATUS_EXPLANATIONS =
+ new ImmutableMap.Builder<TestStatus, String>()
+ .put(TestStatus.TIMEOUT, "Test target timed out.")
+ .put(TestStatus.INCOMPLETE, "Test output was incomplete.")
+ .put(TestStatus.REMOTE_FAILURE, "Remote failure during test execution.")
+ .put(TestStatus.FAILED_TO_BUILD, "Test target failed to build.")
+ .put(TestStatus.BLAZE_HALTED_BEFORE_TESTING, "Test target failed to build.")
+ .put(TestStatus.NO_STATUS, "No output found for test target.")
+ .build();
+
+ private static void processTestSuite(
+ GeneralTestEventsProcessor processor,
+ BlazeTestEventsHandler eventsHandler,
+ @Nullable Kind kind,
+ TestSuite suite) {
if (!hasRunChild(suite)) {
return;
}
@@ -154,13 +183,13 @@
}
for (TestSuite child : suite.testSuites) {
- processTestSuite(processor, kind, child);
+ processTestSuite(processor, eventsHandler, kind, child);
}
for (TestSuite decorator : suite.testDecorators) {
- processTestSuite(processor, kind, decorator);
+ processTestSuite(processor, eventsHandler, kind, decorator);
}
for (TestCase test : suite.testCases) {
- processTestCase(processor, kind, suite, test);
+ processTestCase(processor, eventsHandler, kind, suite, test);
}
if (suite.sysOut != null) {
@@ -224,8 +253,12 @@
return test.failure != null || test.error != null;
}
- private void processTestCase(
- GeneralTestEventsProcessor processor, @Nullable Kind kind, TestSuite parent, TestCase test) {
+ private static void processTestCase(
+ GeneralTestEventsProcessor processor,
+ BlazeTestEventsHandler eventsHandler,
+ @Nullable Kind kind,
+ TestSuite parent,
+ TestCase test) {
if (test.name == null || !wasRun(test) || isCancelled(test)) {
return;
}
diff --git a/base/src/com/google/idea/blaze/base/run/smrunner/SmRunnerUtils.java b/base/src/com/google/idea/blaze/base/run/smrunner/SmRunnerUtils.java
index 54f6595..2c42915 100644
--- a/base/src/com/google/idea/blaze/base/run/smrunner/SmRunnerUtils.java
+++ b/base/src/com/google/idea/blaze/base/run/smrunner/SmRunnerUtils.java
@@ -16,11 +16,11 @@
package com.google.idea.blaze.base.run.smrunner;
import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
import com.intellij.execution.DefaultExecutionResult;
import com.intellij.execution.Executor;
import com.intellij.execution.Location;
import com.intellij.execution.actions.ConfigurationContext;
-import com.intellij.execution.configurations.RunConfiguration;
import com.intellij.execution.testframework.TestConsoleProperties;
import com.intellij.execution.testframework.actions.AbstractRerunFailedTestsAction;
import com.intellij.execution.testframework.sm.SMTestRunnerConnectionUtil;
@@ -52,11 +52,11 @@
public static SMTRunnerConsoleView getConsoleView(
Project project,
- RunConfiguration configuration,
+ BlazeCommandRunConfiguration configuration,
Executor executor,
- BlazeTestEventsHandler eventsHandler) {
+ BlazeTestUiSession testUiSession) {
SMTRunnerConsoleProperties properties =
- new BlazeTestConsoleProperties(configuration, executor, eventsHandler);
+ new BlazeTestConsoleProperties(configuration, executor, testUiSession);
SMTRunnerConsoleView console =
(SMTRunnerConsoleView)
SMTestRunnerConnectionUtil.createConsole(BLAZE_FRAMEWORK, properties);
diff --git a/base/src/com/google/idea/blaze/base/run/smrunner/TestUiSessionProvider.java b/base/src/com/google/idea/blaze/base/run/smrunner/TestUiSessionProvider.java
new file mode 100644
index 0000000..86b7b91
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/run/smrunner/TestUiSessionProvider.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.smrunner;
+
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.model.BlazeVersionData;
+import com.google.idea.blaze.base.model.primitives.TargetExpression;
+import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
+import com.intellij.openapi.extensions.ExtensionPointName;
+import com.intellij.openapi.project.Project;
+import java.util.Arrays;
+import java.util.Objects;
+import javax.annotation.Nullable;
+
+/** Provides a {@link BlazeTestUiSession} for a given project. */
+public interface TestUiSessionProvider {
+ ExtensionPointName<TestUiSessionProvider> EP_NAME =
+ ExtensionPointName.create("com.google.idea.blaze.TestUiSessionProvider");
+
+ /** Returns a {@link BlazeTestUiSession} for the given project and blaze target. */
+ @Nullable
+ static BlazeTestUiSession createForTarget(Project project, TargetExpression target) {
+ if (!BlazeTestEventsHandler.targetSupported(project, target)) {
+ return null;
+ }
+ BlazeProjectData projectData =
+ BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
+ if (projectData == null) {
+ return null;
+ }
+ return Arrays.stream(EP_NAME.getExtensions())
+ .map(provider -> provider.getTestUiSession(projectData.blazeVersionData))
+ .filter(Objects::nonNull)
+ .findFirst()
+ .orElse(null);
+ }
+
+ /**
+ * Returns a {@link BlazeTestUiSession}, or {@code null} if this provider doesn't handle the given
+ * project.
+ */
+ @Nullable
+ BlazeTestUiSession getTestUiSession(BlazeVersionData blazeVersion);
+}
diff --git a/base/src/com/google/idea/blaze/base/run/state/BlazeCommandRunConfigurationCommonState.java b/base/src/com/google/idea/blaze/base/run/state/BlazeCommandRunConfigurationCommonState.java
index 082f4be..267f715 100644
--- a/base/src/com/google/idea/blaze/base/run/state/BlazeCommandRunConfigurationCommonState.java
+++ b/base/src/com/google/idea/blaze/base/run/state/BlazeCommandRunConfigurationCommonState.java
@@ -15,9 +15,7 @@
*/
package com.google.idea.blaze.base.run.state;
-import com.google.idea.blaze.base.command.BlazeCommandName;
import com.google.idea.blaze.base.command.BlazeFlags;
-import com.google.idea.blaze.base.run.state.BlazeRunOnDistributedExecutorState.RunOnExecutorStateEditor;
import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
import com.intellij.execution.configurations.RuntimeConfigurationError;
import com.intellij.execution.configurations.RuntimeConfigurationException;
@@ -37,15 +35,18 @@
private final RunConfigurationFlagsState blazeFlags;
private final RunConfigurationFlagsState exeFlags;
private final BlazeBinaryState blazeBinary;
- private final BlazeRunOnDistributedExecutorState runOnDistributedExecutor;
public BlazeCommandRunConfigurationCommonState(BuildSystem buildSystem) {
command = new BlazeCommandState();
blazeFlags = new RunConfigurationFlagsState(USER_BLAZE_FLAG_TAG, buildSystem + " flags:");
exeFlags = new RunConfigurationFlagsState(USER_EXE_FLAG_TAG, "Executable flags:");
blazeBinary = new BlazeBinaryState();
- runOnDistributedExecutor = new BlazeRunOnDistributedExecutorState(buildSystem);
- addStates(command, blazeFlags, exeFlags, blazeBinary, runOnDistributedExecutor);
+ addStates(command, blazeFlags, exeFlags, blazeBinary);
+
+ // no need to migrate Bazel, which at this time doesn't support distributed execution
+ if (buildSystem == BuildSystem.Blaze) {
+ addStates(new BlazeRunOnDistributedExecutorStateMigrator(buildSystem, blazeFlags));
+ }
}
/** @return The list of blaze flags that the user specified manually. */
@@ -66,10 +67,6 @@
return command;
}
- public BlazeRunOnDistributedExecutorState getRunOnDistributedExecutorState() {
- return runOnDistributedExecutor;
- }
-
/** Searches through all blaze flags for the first one beginning with '--test_filter' */
@Nullable
public String getTestFilterFlag() {
@@ -93,29 +90,6 @@
@Override
public RunConfigurationStateEditor getEditor(Project project) {
- return new RunConfigurationCompositeStateEditor(project, getStates()) {
-
- @Nullable
- private final RunOnExecutorStateEditor runOnExecutorEditor =
- (RunOnExecutorStateEditor)
- editors
- .stream()
- .filter(editor -> editor instanceof RunOnExecutorStateEditor)
- .findFirst()
- .orElse(null);
-
- @Override
- public void applyEditorTo(RunConfigurationState genericState) {
- BlazeCommandRunConfigurationCommonState state =
- (BlazeCommandRunConfigurationCommonState) genericState;
- super.applyEditorTo(genericState);
-
- // this editor needs to update based on state provided by other children.
- if (runOnExecutorEditor != null) {
- boolean isTest = BlazeCommandName.TEST.equals(state.getCommandState().getCommand());
- runOnExecutorEditor.updateVisibility(isTest);
- }
- }
- };
+ return new RunConfigurationCompositeStateEditor(project, getStates());
}
}
diff --git a/base/src/com/google/idea/blaze/base/run/state/BlazeRunOnDistributedExecutorState.java b/base/src/com/google/idea/blaze/base/run/state/BlazeRunOnDistributedExecutorState.java
deleted file mode 100644
index 556b3f3..0000000
--- a/base/src/com/google/idea/blaze/base/run/state/BlazeRunOnDistributedExecutorState.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright 2016 The Bazel Authors. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.state;
-
-import com.google.idea.blaze.base.run.DistributedExecutorSupport;
-import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
-import com.google.idea.blaze.base.ui.UiUtil;
-import com.intellij.icons.AllIcons;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.InvalidDataException;
-import com.intellij.openapi.util.WriteExternalException;
-import com.intellij.ui.components.JBCheckBox;
-import javax.annotation.Nullable;
-import javax.swing.JComponent;
-import javax.swing.JLabel;
-import org.jdom.Element;
-
-/**
- * Provides an option to run blaze/bazel on a distributed executor, if available. If unchecked, we
- * fall back to whatever the default is.
- */
-public class BlazeRunOnDistributedExecutorState implements RunConfigurationState {
-
- private static final String RUN_ON_DISTRIBUTED_EXECUTOR_ATTR =
- "blaze-run-on-distributed-executor";
-
- @Nullable private final DistributedExecutorSupport executorInfo;
-
- public boolean runOnDistributedExecutor;
-
- BlazeRunOnDistributedExecutorState(BuildSystem buildSystem) {
- executorInfo = DistributedExecutorSupport.getAvailableExecutor(buildSystem);
- }
-
- @Override
- public void readExternal(Element element) throws InvalidDataException {
- String string = element.getAttributeValue(RUN_ON_DISTRIBUTED_EXECUTOR_ATTR);
- if (string != null) {
- runOnDistributedExecutor = Boolean.parseBoolean(string);
- }
- }
-
- @Override
- public void writeExternal(Element element) throws WriteExternalException {
- if (executorInfo != null && runOnDistributedExecutor) {
- element.setAttribute(
- RUN_ON_DISTRIBUTED_EXECUTOR_ATTR, Boolean.toString(runOnDistributedExecutor));
- } else {
- element.removeAttribute(RUN_ON_DISTRIBUTED_EXECUTOR_ATTR);
- }
- }
-
- @Override
- public RunOnExecutorStateEditor getEditor(Project project) {
- return new RunOnExecutorStateEditor();
- }
-
- /** Editor for {@link BlazeRunOnDistributedExecutorState} */
- class RunOnExecutorStateEditor implements RunConfigurationStateEditor {
-
- private final String executorName =
- executorInfo != null ? executorInfo.executorName() : "distributed executor";
- private final JBCheckBox checkBox = new JBCheckBox("Run on " + executorName);
- private final JLabel warning =
- new JLabel("Warning: test UI integration is not available when running on " + executorName);
-
- private boolean componentVisible = executorInfo != null;
- private boolean isTest = false;
-
- private RunOnExecutorStateEditor() {
- warning.setIcon(AllIcons.RunConfigurations.ConfigurationWarning);
- checkBox.addItemListener(e -> setVisibility());
- setVisibility();
- }
-
- @Override
- public void resetEditorFrom(RunConfigurationState genericState) {
- BlazeRunOnDistributedExecutorState state = (BlazeRunOnDistributedExecutorState) genericState;
- checkBox.setSelected(state.runOnDistributedExecutor);
- }
-
- @Override
- public void applyEditorTo(RunConfigurationState genericState) {
- BlazeRunOnDistributedExecutorState state = (BlazeRunOnDistributedExecutorState) genericState;
- if (checkBox.isVisible()) {
- state.runOnDistributedExecutor = checkBox.isSelected();
- }
- }
-
- @Override
- public JComponent createComponent() {
- return UiUtil.createBox(checkBox, warning);
- }
-
- @Override
- public void setComponentEnabled(boolean enabled) {
- checkBox.setEnabled(enabled);
- }
-
- void updateVisibility(boolean isTest) {
- this.componentVisible = executorInfo != null;
- this.isTest = isTest;
- setVisibility();
- }
-
- private void setVisibility() {
- warning.setVisible(componentVisible && isTest && checkBox.isSelected());
- checkBox.setVisible(componentVisible);
- }
- }
-}
diff --git a/base/src/com/google/idea/blaze/base/run/state/BlazeRunOnDistributedExecutorStateMigrator.java b/base/src/com/google/idea/blaze/base/run/state/BlazeRunOnDistributedExecutorStateMigrator.java
new file mode 100644
index 0000000..610dce4
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/run/state/BlazeRunOnDistributedExecutorStateMigrator.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.state;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.idea.blaze.base.run.DistributedExecutorSupport;
+import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import java.util.ArrayList;
+import java.util.List;
+import javax.annotation.Nullable;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import org.jdom.Element;
+
+/**
+ * A temporary class to handle migrating from the previous run configuration settings.
+ *
+ * <p>In particular, migrates from setting the local execution flag at runtime to including it in
+ * the user-visible blaze flags UI field.
+ *
+ * <p>TODO(brendandouglas): Temporary migration code. Remove in 2017.09.XX+
+ */
+public class BlazeRunOnDistributedExecutorStateMigrator implements RunConfigurationState {
+
+ private static final String RUN_ON_DISTRIBUTED_EXECUTOR_ATTR =
+ "blaze-run-on-distributed-executor";
+
+ private static final String ALREADY_MIGRATED_ATTR = "blaze-dist-executor-migrated";
+
+ private final RunConfigurationFlagsState blazeFlags;
+ @Nullable private final DistributedExecutorSupport executorInfo;
+
+ @VisibleForTesting
+ public BlazeRunOnDistributedExecutorStateMigrator(
+ BuildSystem buildSystem, RunConfigurationFlagsState blazeFlags) {
+ this.blazeFlags = blazeFlags;
+ executorInfo = DistributedExecutorSupport.getAvailableExecutor(buildSystem);
+ }
+
+ @Override
+ public void readExternal(Element element) throws InvalidDataException {
+ if (Boolean.parseBoolean(element.getAttributeValue(ALREADY_MIGRATED_ATTR))) {
+ return;
+ }
+ String string = element.getAttributeValue(RUN_ON_DISTRIBUTED_EXECUTOR_ATTR);
+ boolean runOnDistributedExecutor = Boolean.parseBoolean(string);
+ if (runOnDistributedExecutor) {
+ setDistributedTestExecution();
+ }
+ }
+
+ private void setDistributedTestExecution() {
+ if (executorInfo == null) {
+ return;
+ }
+ List<String> flags = new ArrayList<>(blazeFlags.getRawFlags());
+ for (String flag : executorInfo.getBlazeFlags(true)) {
+ if (!flags.contains(flag)) {
+ flags.add(flag);
+ }
+ }
+ blazeFlags.setRawFlags(flags);
+ }
+
+ @Override
+ public void writeExternal(Element element) {
+ element.removeAttribute(RUN_ON_DISTRIBUTED_EXECUTOR_ATTR);
+ element.setAttribute(ALREADY_MIGRATED_ATTR, Boolean.toString(true));
+ }
+
+ @Override
+ public RunConfigurationStateEditor getEditor(Project project) {
+ // a dummy implementation of RunConfigurationStateEditor.
+ return new RunConfigurationStateEditor() {
+
+ @Override
+ public void resetEditorFrom(RunConfigurationState state) {}
+
+ @Override
+ public void applyEditorTo(RunConfigurationState state) {}
+
+ @Override
+ public JComponent createComponent() {
+ return new JPanel();
+ }
+
+ @Override
+ public void setComponentEnabled(boolean enabled) {}
+ };
+ }
+}
diff --git a/base/src/com/google/idea/blaze/base/run/state/RunConfigurationFlagsState.java b/base/src/com/google/idea/blaze/base/run/state/RunConfigurationFlagsState.java
index 9cb8a3c..63f6de0 100644
--- a/base/src/com/google/idea/blaze/base/run/state/RunConfigurationFlagsState.java
+++ b/base/src/com/google/idea/blaze/base/run/state/RunConfigurationFlagsState.java
@@ -86,7 +86,7 @@
private static class RunConfigurationFlagsStateEditor implements RunConfigurationStateEditor {
- private final JTextArea flagsField = new JTextArea(5, 1);
+ private final JTextArea flagsField = new JTextArea(10, 1);
private final String fieldLabel;
RunConfigurationFlagsStateEditor(String fieldLabel) {
diff --git a/base/src/com/google/idea/blaze/base/run/targetfinder/TargetFinder.java b/base/src/com/google/idea/blaze/base/run/targetfinder/TargetFinder.java
index 23a03a2..37cf1ee 100644
--- a/base/src/com/google/idea/blaze/base/run/targetfinder/TargetFinder.java
+++ b/base/src/com/google/idea/blaze/base/run/targetfinder/TargetFinder.java
@@ -15,16 +15,13 @@
*/
package com.google.idea.blaze.base.run.targetfinder;
-import com.google.common.base.Predicate;
-import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
-import com.google.idea.blaze.base.model.primitives.Kind;
import com.google.idea.blaze.base.model.primitives.Label;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.project.Project;
-import java.util.Arrays;
import java.util.List;
+import java.util.function.Predicate;
import javax.annotation.Nullable;
/** Searches BlazeProjectData for matching rules. */
@@ -38,22 +35,9 @@
return findTarget(project, target -> target.key.label.equals(label) && target.isPlainTarget());
}
- public ImmutableList<TargetIdeInfo> targetsOfKinds(Project project, final Kind... kinds) {
- return targetsOfKinds(project, Arrays.asList(kinds));
- }
-
- public ImmutableList<TargetIdeInfo> targetsOfKinds(Project project, final List<Kind> kinds) {
- return ImmutableList.copyOf(findTargets(project, target -> target.kindIsOneOf(kinds)));
- }
-
@Nullable
- public TargetIdeInfo firstTargetOfKinds(Project project, Kind... kinds) {
- return Iterables.getFirst(targetsOfKinds(project, kinds), null);
- }
-
- @Nullable
- public TargetIdeInfo firstTargetOfKinds(Project project, List<Kind> kinds) {
- return Iterables.getFirst(targetsOfKinds(project, kinds), null);
+ public TargetIdeInfo findFirstTarget(Project project, Predicate<TargetIdeInfo> predicate) {
+ return Iterables.getFirst(findTargets(project, predicate), null);
}
@Nullable
@@ -63,11 +47,6 @@
return Iterables.getFirst(results, null);
}
- @Nullable
- public TargetIdeInfo findFirstTarget(Project project, Predicate<TargetIdeInfo> predicate) {
- return Iterables.getFirst(findTargets(project, predicate), null);
- }
-
public abstract List<TargetIdeInfo> findTargets(
Project project, Predicate<TargetIdeInfo> predicate);
}
diff --git a/base/src/com/google/idea/blaze/base/run/targetfinder/TargetFinderImpl.java b/base/src/com/google/idea/blaze/base/run/targetfinder/TargetFinderImpl.java
index 46f58c2..02e7e43 100644
--- a/base/src/com/google/idea/blaze/base/run/targetfinder/TargetFinderImpl.java
+++ b/base/src/com/google/idea/blaze/base/run/targetfinder/TargetFinderImpl.java
@@ -15,13 +15,13 @@
*/
package com.google.idea.blaze.base.run.targetfinder;
-import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
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 java.util.function.Predicate;
import org.jetbrains.annotations.NotNull;
/** Implementation of RuleFinder. */
@@ -37,7 +37,7 @@
ImmutableList.Builder<TargetIdeInfo> resultList = ImmutableList.builder();
for (TargetIdeInfo target : projectData.targetMap.targets()) {
- if (predicate.apply(target)) {
+ if (predicate.test(target)) {
resultList.add(target);
}
}
diff --git a/base/src/com/google/idea/blaze/base/run/testlogs/BlazeCommandLogParser.java b/base/src/com/google/idea/blaze/base/run/testlogs/BlazeCommandLogParser.java
index 6920313..06f5291 100644
--- a/base/src/com/google/idea/blaze/base/run/testlogs/BlazeCommandLogParser.java
+++ b/base/src/com/google/idea/blaze/base/run/testlogs/BlazeCommandLogParser.java
@@ -52,7 +52,7 @@
/** Finds the targets which failed to build */
public static ImmutableSet<Label> parseFailedTargets(File commandLog) {
try (Stream<String> stream = Files.lines(Paths.get(commandLog.getPath()))) {
- return parseTestTargets(stream);
+ return parseFailedTargets(stream);
} catch (IOException e) {
logger.warn("Error parsing master log", e);
return ImmutableSet.of();
diff --git a/base/src/com/google/idea/blaze/base/run/testlogs/BlazeTestResult.java b/base/src/com/google/idea/blaze/base/run/testlogs/BlazeTestResult.java
new file mode 100644
index 0000000..1ebe080
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/run/testlogs/BlazeTestResult.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.testlogs;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableSet;
+import com.google.idea.blaze.base.model.primitives.Kind;
+import com.google.idea.blaze.base.model.primitives.Label;
+import java.io.File;
+import javax.annotation.Nullable;
+
+/** The result of a single blaze test action. */
+@AutoValue
+public abstract class BlazeTestResult {
+
+ /** The set of statuses for which no useful output XML is written. */
+ public static final ImmutableSet<TestStatus> NO_USEFUL_OUTPUT =
+ ImmutableSet.of(
+ TestStatus.TIMEOUT,
+ TestStatus.REMOTE_FAILURE,
+ TestStatus.FAILED_TO_BUILD,
+ TestStatus.BLAZE_HALTED_BEFORE_TESTING);
+
+ /** Status for a single blaze test action. */
+ public enum TestStatus {
+ NO_STATUS,
+ PASSED,
+ FLAKY,
+ TIMEOUT,
+ FAILED,
+ INCOMPLETE,
+ REMOTE_FAILURE,
+ FAILED_TO_BUILD,
+ BLAZE_HALTED_BEFORE_TESTING,
+ }
+
+ public static BlazeTestResult create(
+ Label label,
+ @Nullable Kind targetKind,
+ TestStatus testStatus,
+ ImmutableSet<File> outputXmlFiles) {
+ return new AutoValue_BlazeTestResult(label, targetKind, testStatus, outputXmlFiles);
+ }
+
+ public abstract Label getLabel();
+
+ @Nullable
+ public abstract Kind getTargetKind();
+
+ public abstract TestStatus getTestStatus();
+
+ public abstract ImmutableSet<File> getOutputXmlFiles();
+}
diff --git a/base/src/com/google/idea/blaze/base/run/testlogs/BlazeTestResultFinderStrategy.java b/base/src/com/google/idea/blaze/base/run/testlogs/BlazeTestResultFinderStrategy.java
index cff8bd4..7270f54 100644
--- a/base/src/com/google/idea/blaze/base/run/testlogs/BlazeTestResultFinderStrategy.java
+++ b/base/src/com/google/idea/blaze/base/run/testlogs/BlazeTestResultFinderStrategy.java
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -15,40 +15,15 @@
*/
package com.google.idea.blaze.base.run.testlogs;
-import com.google.idea.blaze.base.settings.Blaze;
-import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
-import com.intellij.openapi.extensions.ExtensionPointName;
-import com.intellij.openapi.project.Project;
import javax.annotation.Nullable;
/** A strategy for locating results from 'blaze test' invocation (e.g. output XML files). */
public interface BlazeTestResultFinderStrategy {
- ExtensionPointName<BlazeTestResultFinderStrategy> EP_NAME =
- ExtensionPointName.create("com.google.idea.blaze.BlazeTestXmlFinderStrategy");
-
- /**
- * Attempt to find all output test XML files produced by the most recent blaze invocation, grouped
- * by target label.
- */
- @Nullable
- static BlazeTestResults locateTestResults(Project project) {
- BuildSystem buildSystem = Blaze.getBuildSystem(project);
- for (BlazeTestResultFinderStrategy strategy : EP_NAME.getExtensions()) {
- if (strategy.handlesBuildSystem(buildSystem)) {
- return strategy.findTestResults(project);
- }
- }
- return null;
- }
-
/**
* Attempt to find test results corresponding to the most recent blaze invocation. Called after
* the 'blaze test' process completes.
*/
@Nullable
- BlazeTestResults findTestResults(Project project);
-
- /** Results are taken from the first strategy handling a given build system */
- boolean handlesBuildSystem(BuildSystem buildSystem);
+ BlazeTestResults findTestResults();
}
diff --git a/base/src/com/google/idea/blaze/base/run/testlogs/BlazeTestResults.java b/base/src/com/google/idea/blaze/base/run/testlogs/BlazeTestResults.java
index 7d50bf2..091014e 100644
--- a/base/src/com/google/idea/blaze/base/run/testlogs/BlazeTestResults.java
+++ b/base/src/com/google/idea/blaze/base/run/testlogs/BlazeTestResults.java
@@ -15,22 +15,24 @@
*/
package com.google.idea.blaze.base.run.testlogs;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultimap;
-import com.google.common.collect.ImmutableSet;
import com.google.idea.blaze.base.model.primitives.Label;
-import java.io.File;
/** Results from a 'blaze test' invocation. */
public class BlazeTestResults {
- /** Output test XML files, grouped by target label. */
- public final ImmutableMultimap<Label, File> testXmlFiles;
- /** Targets which failed to build */
- public final ImmutableSet<Label> failedTargets;
+ public static final BlazeTestResults NO_RESULTS = fromFlatList(ImmutableList.of());
- public BlazeTestResults(
- ImmutableMultimap<Label, File> testXmlFiles, ImmutableSet<Label> failedTargets) {
- this.testXmlFiles = testXmlFiles;
- this.failedTargets = failedTargets;
+ public static BlazeTestResults fromFlatList(Iterable<BlazeTestResult> results) {
+ ImmutableMultimap.Builder<Label, BlazeTestResult> map = ImmutableMultimap.builder();
+ results.forEach(result -> map.put(result.getLabel(), result));
+ return new BlazeTestResults(map.build());
+ }
+
+ public final ImmutableMultimap<Label, BlazeTestResult> perTargetResults;
+
+ private BlazeTestResults(ImmutableMultimap<Label, BlazeTestResult> perTargetResults) {
+ this.perTargetResults = perTargetResults;
}
}
diff --git a/base/src/com/google/idea/blaze/base/run/testlogs/BuildEventProtocolTestFinderStrategy.java b/base/src/com/google/idea/blaze/base/run/testlogs/BuildEventProtocolTestFinderStrategy.java
new file mode 100644
index 0000000..e6e37eb
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/run/testlogs/BuildEventProtocolTestFinderStrategy.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.testlogs;
+
+import com.google.idea.blaze.base.command.buildresult.BuildEventProtocolOutputReader;
+import com.google.idea.blaze.base.io.InputStreamProvider;
+import com.intellij.openapi.diagnostic.Logger;
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A strategy for locating results from a single 'blaze test' invocation (e.g. output XML files).
+ *
+ * <p>Parses the output BEP proto written by blaze to locate the test XML files.
+ */
+public final class BuildEventProtocolTestFinderStrategy implements BlazeTestResultFinderStrategy {
+
+ private static final Logger logger =
+ Logger.getInstance(BuildEventProtocolTestFinderStrategy.class);
+
+ private final File outputFile;
+
+ public BuildEventProtocolTestFinderStrategy(File bepOutputFile) {
+ this.outputFile = bepOutputFile;
+ }
+
+ @Override
+ public BlazeTestResults findTestResults() {
+ try (InputStream inputStream =
+ new BufferedInputStream(InputStreamProvider.getInstance().getFile(outputFile))) {
+ return BuildEventProtocolOutputReader.parseTestResults(inputStream);
+ } catch (IOException e) {
+ logger.warn(e);
+ return BlazeTestResults.NO_RESULTS;
+ } finally {
+ if (!outputFile.delete()) {
+ logger.warn("Could not delete BEP output file: " + outputFile);
+ }
+ }
+ }
+}
diff --git a/base/src/com/google/idea/blaze/base/run/testlogs/TargetPathTestResultFinderStrategy.java b/base/src/com/google/idea/blaze/base/run/testlogs/TargetPathTestResultFinderStrategy.java
deleted file mode 100644
index c18cc65..0000000
--- a/base/src/com/google/idea/blaze/base/run/testlogs/TargetPathTestResultFinderStrategy.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright 2017 The Bazel Authors. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.testlogs;
-
-import com.google.common.collect.ImmutableMultimap;
-import com.google.idea.blaze.base.command.info.BlazeInfo;
-import com.google.idea.blaze.base.model.BlazeProjectData;
-import com.google.idea.blaze.base.model.primitives.Label;
-import com.google.idea.blaze.base.settings.Blaze;
-import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
-import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
-import com.intellij.openapi.project.Project;
-import java.io.File;
-import javax.annotation.Nullable;
-
-/**
- * Attempts to parse the list of test targets from the command log, then searches the corresponding
- * path in the bazel-testlogs output tree.
- */
-public class TargetPathTestResultFinderStrategy implements BlazeTestResultFinderStrategy {
-
- @Override
- public boolean handlesBuildSystem(BuildSystem buildSystem) {
- return buildSystem == BuildSystem.Bazel;
- }
-
- @Override
- public BlazeTestResults findTestResults(Project project) {
- File testLogsDir = getTestLogsTree(project);
- if (testLogsDir == null) {
- return null;
- }
- File commandLog = getCommandLog(project);
- if (commandLog == null) {
- return null;
- }
- ImmutableMultimap.Builder<Label, File> output = ImmutableMultimap.builder();
- for (Label label : BlazeCommandLogParser.parseTestTargets(commandLog)) {
- File testXml = findTestXml(testLogsDir, label);
- if (testXml != null) {
- output.put(label, testXml);
- }
- }
- return new BlazeTestResults(
- output.build(), BlazeCommandLogParser.parseFailedTargets(commandLog));
- }
-
- @Nullable
- private static File findTestXml(File testLogsDir, Label label) {
- String labelPath = label.blazePackage() + File.separator + label.targetName();
- return new File(testLogsDir, labelPath + File.separator + "test.xml");
- }
-
- @Nullable
- private static File getTestLogsTree(Project project) {
- BlazeProjectData projectData =
- BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
- if (projectData == null) {
- return null;
- }
- String testLogsLocation =
- projectData.blazeInfo.get(BlazeInfo.blazeTestlogsKey(Blaze.getBuildSystem(project)));
- return testLogsLocation != null ? new File(testLogsLocation) : null;
- }
-
- @Nullable
- private static File getCommandLog(Project project) {
- BlazeProjectData projectData =
- BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
- if (projectData == null) {
- return null;
- }
- String commandLogLocation = projectData.blazeInfo.get(BlazeInfo.COMMAND_LOG);
- return commandLogLocation != null ? new File(commandLogLocation) : null;
- }
-}
diff --git a/base/src/com/google/idea/blaze/base/run/testmap/TestTargetFilterImpl.java b/base/src/com/google/idea/blaze/base/run/testmap/TestTargetFilterImpl.java
index 3a74582..d2027f3 100644
--- a/base/src/com/google/idea/blaze/base/run/testmap/TestTargetFilterImpl.java
+++ b/base/src/com/google/idea/blaze/base/run/testmap/TestTargetFilterImpl.java
@@ -73,6 +73,8 @@
Kind.GWT_TEST,
Kind.CC_TEST,
Kind.PY_TEST,
- Kind.GO_TEST);
+ Kind.GO_TEST,
+ Kind.SCALA_TEST,
+ Kind.SCALA_JUNIT_TEST);
}
}
diff --git a/base/src/com/google/idea/blaze/base/scope/Scope.java b/base/src/com/google/idea/blaze/base/scope/Scope.java
index e288d8a..26104fb 100644
--- a/base/src/com/google/idea/blaze/base/scope/Scope.java
+++ b/base/src/com/google/idea/blaze/base/scope/Scope.java
@@ -38,7 +38,7 @@
} catch (ProcessCanceledException e) {
context.setCancelled();
throw e;
- } catch (RuntimeException e) {
+ } catch (Throwable e) {
context.setHasError();
logger.error(e);
throw e;
@@ -61,7 +61,7 @@
} catch (ProcessCanceledException e) {
context.setCancelled();
throw e;
- } catch (RuntimeException e) {
+ } catch (Throwable e) {
context.setHasError();
logger.error(e);
throw e;
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
index 8690be2..f9fdd98 100644
--- a/base/src/com/google/idea/blaze/base/scope/output/IssueOutput.java
+++ b/base/src/com/google/idea/blaze/base/scope/output/IssueOutput.java
@@ -20,7 +20,6 @@
import com.intellij.pom.Navigatable;
import java.io.File;
import javax.annotation.Nullable;
-import org.jetbrains.annotations.NotNull;
/** An issue in a blaze operation. */
public class IssueOutput implements Output {
@@ -31,8 +30,8 @@
@Nullable private final File file;
private final int line;
private final int column;
- @NotNull private final Category category;
- @NotNull private final String message;
+ private final Category category;
+ private final String message;
@Nullable Navigatable navigatable;
@Nullable IssueData issueData;
@@ -47,61 +46,53 @@
INFORMATION
}
- @NotNull
- public static Builder issue(@NotNull Category category, @NotNull String message) {
+ public static Builder issue(Category category, String message) {
return new Builder(category, message);
}
- @NotNull
- public static Builder error(@NotNull String message) {
+ public static Builder error(String message) {
return new Builder(Category.ERROR, message);
}
- @NotNull
- public static Builder warn(@NotNull String message) {
+ public static Builder warn(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;
+ private final Category category;
+ 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) {
+ public Builder(Category category, 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;
@@ -111,7 +102,7 @@
return new IssueOutput(file, line, column, navigatable, category, message, issueData);
}
- public void submit(@NotNull BlazeContext context) {
+ public void submit(BlazeContext context) {
context.output(build());
if (category == Category.ERROR) {
context.setHasError();
@@ -124,8 +115,8 @@
int line,
int column,
@Nullable Navigatable navigatable,
- @NotNull Category category,
- @NotNull String message,
+ Category category,
+ String message,
@Nullable IssueData issueData) {
this.file = file;
this.line = line;
@@ -154,12 +145,10 @@
return navigatable;
}
- @NotNull
public Category getCategory() {
return category;
}
- @NotNull
public String getMessage() {
return message;
}
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
index 7128856..2b9c295 100644
--- a/base/src/com/google/idea/blaze/base/scope/scopes/BlazeConsoleScope.java
+++ b/base/src/com/google/idea/blaze/base/scope/scopes/BlazeConsoleScope.java
@@ -24,12 +24,12 @@
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.google.idea.blaze.base.settings.BlazeUserSettings.BlazeConsolePopupBehavior;
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 javax.annotation.Nullable;
-import org.jetbrains.annotations.NotNull;
/** Moves print output to the blaze console. */
public class BlazeConsoleScope implements BlazeScope {
@@ -38,20 +38,20 @@
public static class Builder {
private Project project;
private ProgressIndicator progressIndicator;
- private boolean suppressConsole = false;
+ private BlazeConsolePopupBehavior popupBehavior;
private boolean escapeAnsiColorCodes = false;
- public Builder(@NotNull Project project) {
+ public Builder(Project project) {
this(project, null);
}
- public Builder(@NotNull Project project, ProgressIndicator progressIndicator) {
+ public Builder(Project project, ProgressIndicator progressIndicator) {
this.project = project;
this.progressIndicator = progressIndicator;
}
- public Builder setSuppressConsole(boolean suppressConsole) {
- this.suppressConsole = suppressConsole;
+ public Builder setPopupBehavior(BlazeConsolePopupBehavior popupBehavior) {
+ this.popupBehavior = popupBehavior;
return this;
}
@@ -61,24 +61,23 @@
}
public BlazeConsoleScope build() {
- return new BlazeConsoleScope(
- project, progressIndicator, suppressConsole, escapeAnsiColorCodes);
+ return new BlazeConsoleScope(project, progressIndicator, popupBehavior, escapeAnsiColorCodes);
}
}
- @NotNull private final BlazeConsoleService blazeConsoleService;
+ private final BlazeConsoleService blazeConsoleService;
@Nullable private final ProgressIndicator progressIndicator;
- private final boolean showDialogOnChange;
+ private final BlazeConsolePopupBehavior popupBehavior;
private boolean activated;
private final ConsoleStream consoleStream;
private OutputSink<PrintOutput> printSink =
(output) -> {
- @NotNull String text = output.getText();
- @NotNull
+ String text = output.getText();
+
ConsoleViewContentType contentType =
output.getOutputType() == OutputType.ERROR
? ConsoleViewContentType.ERROR_OUTPUT
@@ -89,20 +88,20 @@
private OutputSink<StatusOutput> statusSink =
(output) -> {
- @NotNull String text = output.getStatus();
- @NotNull ConsoleViewContentType contentType = ConsoleViewContentType.NORMAL_OUTPUT;
+ String text = output.getStatus();
+ ConsoleViewContentType contentType = ConsoleViewContentType.NORMAL_OUTPUT;
print(text, contentType);
return OutputSink.Propagation.Continue;
};
private BlazeConsoleScope(
- @NotNull Project project,
+ Project project,
@Nullable ProgressIndicator progressIndicator,
- boolean suppressConsole,
+ BlazeConsolePopupBehavior popupBehavior,
boolean escapeAnsiColorCodes) {
this.blazeConsoleService = BlazeConsoleService.getInstance(project);
this.progressIndicator = progressIndicator;
- this.showDialogOnChange = !suppressConsole;
+ this.popupBehavior = popupBehavior;
ConsoleStream sinkConsoleStream = blazeConsoleService::print;
this.consoleStream =
escapeAnsiColorCodes ? new ColoredConsoleStream(sinkConsoleStream) : sinkConsoleStream;
@@ -112,14 +111,21 @@
consoleStream.print(text, contentType);
consoleStream.print("\n", contentType);
- if (showDialogOnChange && !activated) {
+ if (activated) {
+ return;
+ }
+ boolean activate =
+ popupBehavior == BlazeConsolePopupBehavior.ALWAYS
+ || (popupBehavior == BlazeConsolePopupBehavior.ON_ERROR
+ && contentType == ConsoleViewContentType.ERROR_OUTPUT);
+ if (activate) {
activated = true;
ApplicationManager.getApplication().invokeLater(blazeConsoleService::activateConsoleWindow);
}
}
@Override
- public void onScopeBegin(@NotNull final BlazeContext context) {
+ public void onScopeBegin(final BlazeContext context) {
context.addOutputSink(PrintOutput.class, printSink);
context.addOutputSink(StatusOutput.class, statusSink);
blazeConsoleService.clear();
@@ -133,7 +139,7 @@
}
@Override
- public void onScopeEnd(@NotNull BlazeContext context) {
+ public void onScopeEnd(BlazeContext context) {
blazeConsoleService.setStopHandler(null);
}
}
diff --git a/base/src/com/google/idea/blaze/base/settings/Blaze.java b/base/src/com/google/idea/blaze/base/settings/Blaze.java
index 65d5b93..86954db 100644
--- a/base/src/com/google/idea/blaze/base/settings/Blaze.java
+++ b/base/src/com/google/idea/blaze/base/settings/Blaze.java
@@ -35,7 +35,7 @@
return name();
}
- /** The build system name, capitalized. */
+ /** The build system name, lower case. */
public String getLowerCaseName() {
return name().toLowerCase();
}
diff --git a/base/src/com/google/idea/blaze/base/settings/BlazeUserSettings.java b/base/src/com/google/idea/blaze/base/settings/BlazeUserSettings.java
index 6b7298e..e106aa9 100644
--- a/base/src/com/google/idea/blaze/base/settings/BlazeUserSettings.java
+++ b/base/src/com/google/idea/blaze/base/settings/BlazeUserSettings.java
@@ -36,13 +36,30 @@
)
public class BlazeUserSettings implements PersistentStateComponent<BlazeUserSettings> {
+ /** A setting to control whether the Blaze Console view is activated for a given operation. */
+ public enum BlazeConsolePopupBehavior {
+ ALWAYS("Always"),
+ ON_ERROR("On error"),
+ NEVER("Never");
+
+ private final String uiName;
+
+ BlazeConsolePopupBehavior(String uiName) {
+ this.uiName = uiName;
+ }
+
+ @Override
+ public String toString() {
+ return uiName;
+ }
+ }
+
+ private BlazeConsolePopupBehavior showBlazeConsoleOnSync = BlazeConsolePopupBehavior.ALWAYS;
private 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 boolean formatBuildFilesOnSave = true;
private String blazeBinaryPath = "/usr/bin/blaze";
@@ -85,6 +102,14 @@
return resyncAutomatically;
}
+ public BlazeConsolePopupBehavior getShowBlazeConsoleOnSync() {
+ return showBlazeConsoleOnSync;
+ }
+
+ public void setShowBlazeConsoleOnSync(BlazeConsolePopupBehavior showBlazeConsoleOnSync) {
+ this.showBlazeConsoleOnSync = showBlazeConsoleOnSync;
+ }
+
public boolean getSuppressConsoleForRunAction() {
return suppressConsoleForRunAction;
}
@@ -149,32 +174,4 @@
public void setFormatBuildFilesOnSave(boolean formatBuildFilesOnSave) {
this.formatBuildFilesOnSave = formatBuildFilesOnSave;
}
-
- // 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/ui/BlazeUserSettingsConfigurable.java b/base/src/com/google/idea/blaze/base/settings/ui/BlazeUserSettingsConfigurable.java
index f8a7210..2b0514d 100644
--- a/base/src/com/google/idea/blaze/base/settings/ui/BlazeUserSettingsConfigurable.java
+++ b/base/src/com/google/idea/blaze/base/settings/ui/BlazeUserSettingsConfigurable.java
@@ -22,6 +22,7 @@
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.settings.BlazeUserSettings.BlazeConsolePopupBehavior;
import com.google.idea.blaze.base.ui.FileSelectorWithStoredHistory;
import com.intellij.openapi.options.BaseConfigurable;
import com.intellij.openapi.options.ConfigurationException;
@@ -33,6 +34,7 @@
import java.util.Collection;
import javax.annotation.Nullable;
import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
@@ -49,6 +51,7 @@
private final Collection<BlazeUserSettingsContributor> settingsContributors;
private JPanel myMainPanel;
+ private JComboBox<BlazeConsolePopupBehavior> showBlazeConsoleOnSync;
private JCheckBox suppressConsoleForRunAction;
private JCheckBox resyncAutomatically;
private JCheckBox collapseProjectView;
@@ -81,6 +84,8 @@
@Override
public void apply() throws ConfigurationException {
BlazeUserSettings settings = BlazeUserSettings.getInstance();
+ settings.setShowBlazeConsoleOnSync(
+ (BlazeConsolePopupBehavior) showBlazeConsoleOnSync.getSelectedItem());
settings.setSuppressConsoleForRunAction(suppressConsoleForRunAction.isSelected());
settings.setResyncAutomatically(resyncAutomatically.isSelected());
settings.setCollapseProjectView(collapseProjectView.isSelected());
@@ -96,6 +101,7 @@
@Override
public void reset() {
BlazeUserSettings settings = BlazeUserSettings.getInstance();
+ showBlazeConsoleOnSync.setSelectedItem(settings.getShowBlazeConsoleOnSync());
suppressConsoleForRunAction.setSelected(settings.getSuppressConsoleForRunAction());
resyncAutomatically.setSelected(settings.getResyncAutomatically());
collapseProjectView.setSelected(settings.getCollapseProjectView());
@@ -118,7 +124,8 @@
public boolean isModified() {
BlazeUserSettings settings = BlazeUserSettings.getInstance();
boolean isModified =
- suppressConsoleForRunAction.isSelected() != settings.getSuppressConsoleForRunAction()
+ showBlazeConsoleOnSync.getSelectedItem() != settings.getShowBlazeConsoleOnSync()
+ || suppressConsoleForRunAction.isSelected() != settings.getSuppressConsoleForRunAction()
|| resyncAutomatically.isSelected() != settings.getResyncAutomatically()
|| collapseProjectView.isSelected() != settings.getCollapseProjectView()
|| formatBuildFilesOnSave.isSelected() != settings.getFormatBuildFilesOnSave()
@@ -156,11 +163,46 @@
contributorRowCount += contributor.getRowCount();
}
- final int totalRowSize = 7 + contributorRowCount;
+ final int totalRowSize = 8 + contributorRowCount;
int rowi = 0;
myMainPanel = new JPanel();
myMainPanel.setLayout(new GridLayoutManager(totalRowSize, 2, new Insets(0, 0, 0, 0), -1, -1));
+
+ JLabel label = new JLabel(String.format("Show %s console on sync:", defaultBuildSystem));
+ myMainPanel.add(
+ label,
+ new GridConstraints(
+ rowi,
+ 0,
+ 1,
+ 1,
+ GridConstraints.ANCHOR_WEST,
+ GridConstraints.FILL_NONE,
+ GridConstraints.SIZEPOLICY_FIXED,
+ GridConstraints.SIZEPOLICY_FIXED,
+ null,
+ null,
+ null,
+ 0,
+ false));
+ showBlazeConsoleOnSync = new JComboBox<>(BlazeConsolePopupBehavior.values());
+ myMainPanel.add(
+ showBlazeConsoleOnSync,
+ new GridConstraints(
+ rowi++,
+ 1,
+ 1,
+ 1,
+ GridConstraints.ANCHOR_WEST,
+ GridConstraints.FILL_HORIZONTAL,
+ GridConstraints.SIZEPOLICY_CAN_GROW,
+ GridConstraints.SIZEPOLICY_FIXED,
+ null,
+ null,
+ null,
+ 0,
+ false));
suppressConsoleForRunAction = new JCheckBox();
suppressConsoleForRunAction.setText(
String.format("Suppress %s console for Run/Debug actions", defaultBuildSystem));
diff --git a/base/src/com/google/idea/blaze/base/settings/ui/ProjectViewUi.java b/base/src/com/google/idea/blaze/base/settings/ui/ProjectViewUi.java
index ae6a917..0172f69 100644
--- a/base/src/com/google/idea/blaze/base/settings/ui/ProjectViewUi.java
+++ b/base/src/com/google/idea/blaze/base/settings/ui/ProjectViewUi.java
@@ -93,10 +93,16 @@
return ProjectManager.getInstance().getDefaultProject();
}
- public static Dimension getMinimumSize() {
+ private static Dimension getEditorSize() {
return new Dimension(1000, 550);
}
+ public static Dimension getContainerSize() {
+ // Add pixels so we have room for our extra fields
+ Dimension dimension = getEditorSize();
+ return new Dimension(dimension.width, dimension.height + 200);
+ }
+
private static EditorEx createEditor(String tooltip) {
Project project = getProject();
LightVirtualFile virtualFile =
@@ -125,8 +131,8 @@
settings.setFoldingOutlineShown(false);
settings.setRightMarginShown(false);
settings.setAdditionalPageAtBottom(false);
- editor.getComponent().setMinimumSize(getMinimumSize());
- editor.getComponent().setPreferredSize(getMinimumSize());
+ editor.getComponent().setMinimumSize(getEditorSize());
+ editor.getComponent().setPreferredSize(getEditorSize());
editor.getComponent().setToolTipText(tooltip);
editor.getComponent().setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, null);
editor.getComponent().setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, null);
diff --git a/base/src/com/google/idea/blaze/base/sync/BlazeSyncTask.java b/base/src/com/google/idea/blaze/base/sync/BlazeSyncTask.java
index a019ffd..d5858f9 100644
--- a/base/src/com/google/idea/blaze/base/sync/BlazeSyncTask.java
+++ b/base/src/com/google/idea/blaze/base/sync/BlazeSyncTask.java
@@ -24,7 +24,10 @@
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.command.BlazeCommandName;
import com.google.idea.blaze.base.command.BlazeFlags;
+import com.google.idea.blaze.base.command.BlazeInvocationContext;
+import com.google.idea.blaze.base.command.info.BlazeConfigurationHandler;
import com.google.idea.blaze.base.command.info.BlazeInfo;
import com.google.idea.blaze.base.command.info.BlazeInfoRunner;
import com.google.idea.blaze.base.experiments.ExperimentScope;
@@ -41,6 +44,7 @@
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.plugin.BuildSystemVersionChecker;
import com.google.idea.blaze.base.prefetch.PrefetchService;
import com.google.idea.blaze.base.projectview.ProjectViewManager;
import com.google.idea.blaze.base.projectview.ProjectViewSet;
@@ -82,7 +86,7 @@
import com.google.idea.blaze.base.sync.sharding.BlazeBuildTargetSharder;
import com.google.idea.blaze.base.sync.sharding.BlazeBuildTargetSharder.ShardedTargetsResult;
import com.google.idea.blaze.base.sync.sharding.ShardedTargetList;
-import com.google.idea.blaze.base.sync.sharding.SuggestEnablingShardingNotification;
+import com.google.idea.blaze.base.sync.sharding.SuggestBuildShardingNotification;
import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoderImpl;
import com.google.idea.blaze.base.sync.workspace.WorkingSet;
@@ -146,7 +150,11 @@
if (!syncParams.backgroundSync) {
context
- .push(new BlazeConsoleScope.Builder(project, indicator).build())
+ .push(
+ new BlazeConsoleScope.Builder(project, indicator)
+ .setPopupBehavior(
+ BlazeUserSettings.getInstance().getShowBlazeConsoleOnSync())
+ .build())
.push(new IssuesScope(project))
.push(new IdeaLogScope())
.push(
@@ -199,7 +207,7 @@
updateInMemoryState(project, context, projectViewSet, blazeProjectData);
onSyncComplete(project, context, projectViewSet, blazeProjectData, syncMode, syncResult);
}
- } catch (AssertionError | Exception e) {
+ } catch (Throwable e) {
Throwable rootCause = e;
while (rootCause.getCause() != null) {
rootCause = rootCause.getCause();
@@ -246,7 +254,8 @@
importSettings.getBuildSystem(),
Blaze.getBuildSystemProvider(project).getSyncBinaryPath(),
workspaceRoot,
- BlazeFlags.buildFlags(project, projectViewSet));
+ BlazeFlags.blazeFlags(
+ project, projectViewSet, BlazeCommandName.INFO, BlazeInvocationContext.Sync));
ListenableFuture<WorkingSet> workingSetFuture =
vcsHandler.getWorkingSet(project, context, workspaceRoot, executor);
@@ -265,6 +274,10 @@
BlazeVersionData blazeVersionData =
BlazeVersionData.build(importSettings.getBuildSystem(), workspaceRoot, blazeInfo);
+ if (!BuildSystemVersionChecker.verifyVersionSupported(context, blazeVersionData)) {
+ return SyncResult.FAILURE;
+ }
+
WorkspacePathResolver workspacePathResolver =
workspacePathResolverAndProjectView.workspacePathResolver;
ArtifactLocationDecoder artifactLocationDecoder =
@@ -272,9 +285,6 @@
WorkspaceLanguageSettings workspaceLanguageSettings =
LanguageSupport.createWorkspaceLanguageSettings(projectViewSet);
- if (workspaceLanguageSettings == null) {
- return SyncResult.FAILURE;
- }
for (BlazeSyncPlugin syncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) {
syncPlugin.installSdks(context);
@@ -338,6 +348,7 @@
}
ShardedTargetList shardedTargets = shardedTargetsResult.shardedTargets;
+ BlazeConfigurationHandler configHandler = new BlazeConfigurationHandler(blazeInfo);
boolean mergeWithOldState = !syncParams.addProjectViewTargets;
BlazeIdeInterface.IdeResult ideQueryResult =
getIdeQueryResult(
@@ -345,6 +356,7 @@
context,
projectViewSet,
blazeVersionData,
+ configHandler,
shardedTargets,
workspaceLanguageSettings,
artifactLocationDecoder,
@@ -358,7 +370,7 @@
|| ideQueryResult.buildResult.status == BuildResult.Status.FATAL_ERROR) {
context.setHasError();
if (ideQueryResult.buildResult.outOfMemory()) {
- SuggestEnablingShardingNotification.suggestSharding(project, context);
+ SuggestBuildShardingNotification.syncOutOfMemoryError(project, context);
}
return SyncResult.FAILURE;
}
@@ -371,11 +383,17 @@
BuildResult ideResolveResult =
resolveIdeArtifacts(
- project, context, workspaceRoot, projectViewSet, blazeVersionData, shardedTargets);
+ project,
+ context,
+ workspaceRoot,
+ projectViewSet,
+ blazeVersionData,
+ workspaceLanguageSettings,
+ shardedTargets);
if (ideResolveResult.status == BuildResult.Status.FATAL_ERROR) {
context.setHasError();
if (ideResolveResult.outOfMemory()) {
- SuggestEnablingShardingNotification.suggestSharding(project, context);
+ SuggestBuildShardingNotification.syncOutOfMemoryError(project, context);
}
return SyncResult.FAILURE;
}
@@ -490,21 +508,16 @@
private static void refreshVirtualFileSystem(
BlazeContext context, BlazeProjectData blazeProjectData) {
- Transactions.submitTransactionAndWait(
+ Transactions.submitWriteActionTransactionAndWait(
() ->
- ApplicationManager.getApplication()
- .runWriteAction(
- (Runnable)
- () ->
- Scope.push(
- context,
- (childContext) -> {
- childContext.push(new TimingScope("RefreshVirtualFileSystem"));
- for (BlazeSyncPlugin syncPlugin :
- BlazeSyncPlugin.EP_NAME.getExtensions()) {
- syncPlugin.refreshVirtualFileSystem(blazeProjectData);
- }
- })));
+ Scope.push(
+ context,
+ (childContext) -> {
+ childContext.push(new TimingScope("RefreshVirtualFileSystem"));
+ for (BlazeSyncPlugin syncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) {
+ syncPlugin.refreshVirtualFileSystem(blazeProjectData);
+ }
+ }));
}
static class WorkspacePathResolverAndProjectView {
@@ -639,6 +652,7 @@
BlazeContext parentContext,
ProjectViewSet projectViewSet,
BlazeVersionData blazeVersionData,
+ BlazeConfigurationHandler configHandler,
ShardedTargetList shardedTargets,
WorkspaceLanguageSettings workspaceLanguageSettings,
ArtifactLocationDecoder artifactLocationDecoder,
@@ -660,6 +674,7 @@
workspaceRoot,
projectViewSet,
blazeVersionData,
+ configHandler,
shardedTargets,
workspaceLanguageSettings,
artifactLocationDecoder,
@@ -675,6 +690,7 @@
WorkspaceRoot workspaceRoot,
ProjectViewSet projectViewSet,
BlazeVersionData blazeVersionData,
+ WorkspaceLanguageSettings workspaceLanguageSettings,
ShardedTargetList shardedTargets) {
return Scope.push(
parentContext,
@@ -690,7 +706,13 @@
}
BlazeIdeInterface blazeIdeInterface = BlazeIdeInterface.getInstance();
return blazeIdeInterface.resolveIdeArtifacts(
- project, context, workspaceRoot, projectViewSet, blazeVersionData, shardedTargets);
+ project,
+ context,
+ workspaceRoot,
+ projectViewSet,
+ blazeVersionData,
+ workspaceLanguageSettings,
+ shardedTargets);
});
}
@@ -708,23 +730,22 @@
context.output(new StatusOutput("Committing project structure..."));
try {
- Transactions.submitTransactionAndWait(
+ Transactions.submitWriteActionTransactionAndWait(
() ->
- ApplicationManager.getApplication()
- .runWriteAction(
- (Runnable)
- () ->
- ProjectRootManagerEx.getInstanceEx(this.project)
- .mergeRootsChangesDuring(
- () ->
- updateProjectStructure(
- context,
- importSettings,
- projectViewSet,
- blazeVersionData,
- directoryStructure,
- newBlazeProjectData,
- oldBlazeProjectData))));
+ ProjectRootManagerEx.getInstanceEx(this.project)
+ .mergeRootsChangesDuring(
+ () ->
+ updateProjectStructure(
+ context,
+ importSettings,
+ projectViewSet,
+ blazeVersionData,
+ directoryStructure,
+ newBlazeProjectData,
+ oldBlazeProjectData)));
+ } catch (ProcessCanceledException e) {
+ context.setCancelled();
+ throw e;
} catch (Throwable e) {
IssueOutput.error("Internal error. Error: " + e).submit(context);
logger.error(e);
diff --git a/base/src/com/google/idea/blaze/base/sync/aspects/BlazeIdeInterface.java b/base/src/com/google/idea/blaze/base/sync/aspects/BlazeIdeInterface.java
index 34f952a..b28f13b 100644
--- a/base/src/com/google/idea/blaze/base/sync/aspects/BlazeIdeInterface.java
+++ b/base/src/com/google/idea/blaze/base/sync/aspects/BlazeIdeInterface.java
@@ -15,6 +15,7 @@
*/
package com.google.idea.blaze.base.sync.aspects;
+import com.google.idea.blaze.base.command.info.BlazeConfigurationHandler;
import com.google.idea.blaze.base.ideinfo.TargetMap;
import com.google.idea.blaze.base.model.BlazeVersionData;
import com.google.idea.blaze.base.model.SyncState;
@@ -58,6 +59,7 @@
WorkspaceRoot workspaceRoot,
ProjectViewSet projectViewSet,
BlazeVersionData blazeVersionData,
+ BlazeConfigurationHandler configHandler,
ShardedTargetList shardedTargets,
WorkspaceLanguageSettings workspaceLanguageSettings,
ArtifactLocationDecoder artifactLocationDecoder,
@@ -76,6 +78,7 @@
WorkspaceRoot workspaceRoot,
ProjectViewSet projectViewSet,
BlazeVersionData blazeVersionData,
+ WorkspaceLanguageSettings workspaceLanguageSettings,
ShardedTargetList shardedTargets);
/**
@@ -89,5 +92,6 @@
WorkspaceRoot workspaceRoot,
ProjectViewSet projectViewSet,
BlazeVersionData blazeVersionData,
+ WorkspaceLanguageSettings workspaceLanguageSettings,
ShardedTargetList shardedTargets);
}
diff --git a/base/src/com/google/idea/blaze/base/sync/aspects/BlazeIdeInterfaceAspectsImpl.java b/base/src/com/google/idea/blaze/base/sync/aspects/BlazeIdeInterfaceAspectsImpl.java
index 18a884c..5f03475 100644
--- a/base/src/com/google/idea/blaze/base/sync/aspects/BlazeIdeInterfaceAspectsImpl.java
+++ b/base/src/com/google/idea/blaze/base/sync/aspects/BlazeIdeInterfaceAspectsImpl.java
@@ -16,7 +16,9 @@
package com.google.idea.blaze.base.sync.aspects;
import com.google.common.base.Joiner;
+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.common.collect.Maps;
import com.google.common.collect.Ordering;
@@ -32,7 +34,9 @@
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.BlazeInvocationContext;
import com.google.idea.blaze.base.command.buildresult.BuildResultHelper;
+import com.google.idea.blaze.base.command.info.BlazeConfigurationHandler;
import com.google.idea.blaze.base.filecache.FileDiffer;
import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
import com.google.idea.blaze.base.ideinfo.TargetKey;
@@ -42,13 +46,12 @@
import com.google.idea.blaze.base.model.BlazeVersionData;
import com.google.idea.blaze.base.model.SyncState;
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.LanguageClass;
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.PrefetchFileSource;
import com.google.idea.blaze.base.prefetch.PrefetchService;
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.Result;
import com.google.idea.blaze.base.scope.Scope;
@@ -59,17 +62,19 @@
import com.google.idea.blaze.base.scope.scopes.TimingScope;
import com.google.idea.blaze.base.settings.Blaze;
import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
+import com.google.idea.blaze.base.sync.aspects.BuildResult.Status;
import com.google.idea.blaze.base.sync.aspects.strategy.AspectStrategy;
import com.google.idea.blaze.base.sync.aspects.strategy.AspectStrategyProvider;
+import com.google.idea.blaze.base.sync.projectview.ImportRoots;
+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.sharding.ShardedTargetList;
-import com.google.idea.blaze.base.sync.sharding.WildcardTargetPattern;
import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
import com.google.repackaged.devtools.intellij.ideinfo.IntellijIdeInfo;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.io.FileUtil;
import com.intellij.pom.NavigatableAdapter;
-import com.intellij.util.PathUtil;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@@ -77,6 +82,7 @@
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@@ -86,7 +92,6 @@
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;
import java.util.function.Predicate;
-import java.util.stream.Collectors;
import java.util.zip.GZIPInputStream;
import javax.annotation.Nullable;
@@ -111,6 +116,7 @@
WorkspaceRoot workspaceRoot,
ProjectViewSet projectViewSet,
BlazeVersionData blazeVersionData,
+ BlazeConfigurationHandler configHandler,
ShardedTargetList shardedTargets,
WorkspaceLanguageSettings workspaceLanguageSettings,
ArtifactLocationDecoder artifactLocationDecoder,
@@ -126,14 +132,22 @@
}
// If the aspect strategy has changed, redo everything from scratch
- final AspectStrategy aspectStrategy = getAspectStrategy(project, blazeVersionData);
+ final AspectStrategy aspectStrategy = getAspectStrategy(blazeVersionData);
if (prevState != null
&& !Objects.equals(prevState.aspectStrategyName, aspectStrategy.getName())) {
prevState = null;
}
IdeInfoResult ideInfoResult =
- getIdeInfo(project, context, workspaceRoot, projectViewSet, shardedTargets, aspectStrategy);
+ getIdeInfo(
+ project,
+ context,
+ workspaceRoot,
+ projectViewSet,
+ blazeVersionData,
+ workspaceLanguageSettings.activeLanguages,
+ shardedTargets,
+ aspectStrategy);
if (ideInfoResult.buildResult.status == BuildResult.Status.FATAL_ERROR) {
return new IdeResult(
prevState != null ? prevState.targetMap : null, ideInfoResult.buildResult);
@@ -161,7 +175,7 @@
fileList.size(), updatedFiles.size(), removedFiles.size())));
ListenableFuture<?> prefetchFuture =
- PrefetchService.getInstance().prefetchFiles(project, updatedFiles);
+ PrefetchService.getInstance().prefetchFiles(project, updatedFiles, true);
if (!FutureUtil.waitForFuture(context, prefetchFuture)
.timed("FetchAspectOutput")
.withProgressMessage("Reading IDE info result...")
@@ -170,7 +184,10 @@
return new IdeResult(prevState != null ? prevState.targetMap : null, BuildResult.FATAL_ERROR);
}
- Set<Label> targets = getNonWildcardProjectViewTargets(projectViewSet);
+ ImportRoots importRoots =
+ ImportRoots.builder(workspaceRoot, Blaze.getBuildSystem(project))
+ .add(projectViewSet)
+ .build();
State state =
updateState(
@@ -178,8 +195,9 @@
context,
prevState,
fileState,
+ configHandler,
workspaceLanguageSettings,
- targets,
+ importRoots,
aspectStrategy,
updatedFiles,
removedFiles,
@@ -191,33 +209,6 @@
return new IdeResult(state.targetMap, ideInfoResult.buildResult);
}
- private static Set<Label> getNonWildcardProjectViewTargets(ProjectViewSet projectViewSet) {
- return projectViewSet
- .listItems(TargetSection.KEY)
- .stream()
- .map(BlazeIdeInterfaceAspectsImpl::singleTargetLabel)
- .filter(Objects::nonNull)
- .collect(Collectors.toSet());
- }
-
- @Nullable
- private static Label singleTargetLabel(TargetExpression expression) {
- if (WildcardTargetPattern.fromExpression(expression) != null) {
- return null;
- }
- // convert to a valid Label format
- String pattern = expression.toString();
- if (!pattern.startsWith("//")) {
- pattern = "//" + pattern;
- }
- int colonIndex = pattern.indexOf(':');
- if (colonIndex == -1) {
- // add the implicit rule name
- pattern += ":" + PathUtil.getFileName(pattern);
- }
- return Label.createIfValid(pattern);
- }
-
private static class IdeInfoResult {
final Collection<File> files;
final BuildResult buildResult;
@@ -233,6 +224,8 @@
BlazeContext parentContext,
WorkspaceRoot workspaceRoot,
ProjectViewSet projectViewSet,
+ BlazeVersionData blazeVersionData,
+ ImmutableSet<LanguageClass> activeLanguages,
ShardedTargetList shardedTargets,
AspectStrategy aspectStrategy) {
return Scope.push(
@@ -250,7 +243,14 @@
targets -> {
IdeInfoResult result =
getIdeInfoForTargets(
- project, context, workspaceRoot, projectViewSet, targets, aspectStrategy);
+ project,
+ context,
+ workspaceRoot,
+ projectViewSet,
+ blazeVersionData,
+ activeLanguages,
+ targets,
+ aspectStrategy);
ideInfoFiles.addAll(result.files);
return result.buildResult;
};
@@ -266,22 +266,26 @@
BlazeContext context,
WorkspaceRoot workspaceRoot,
ProjectViewSet projectViewSet,
+ BlazeVersionData blazeVersionData,
+ ImmutableSet<LanguageClass> activeLanguages,
List<TargetExpression> targets,
AspectStrategy aspectStrategy) {
String fileExtension = aspectStrategy.getAspectOutputFileExtension();
String gzFileExtension = fileExtension + ".gz";
Predicate<String> fileFilter =
fileName -> fileName.endsWith(fileExtension) || fileName.endsWith(gzFileExtension);
- BuildResultHelper buildResultHelper = BuildResultHelper.forFiles(fileFilter);
+ BuildResultHelper buildResultHelper = BuildResultHelper.forFiles(blazeVersionData, fileFilter);
BlazeCommand.Builder builder =
BlazeCommand.builder(getBinaryPath(project), BlazeCommandName.BUILD)
.addTargets(targets)
.addBlazeFlags(BlazeFlags.KEEP_GOING)
.addBlazeFlags(buildResultHelper.getBuildFlags())
- .addBlazeFlags(BlazeFlags.buildFlags(project, projectViewSet));
+ .addBlazeFlags(
+ BlazeFlags.blazeFlags(
+ project, projectViewSet, BlazeCommandName.BUILD, BlazeInvocationContext.Sync));
- aspectStrategy.modifyIdeInfoCommand(builder);
+ aspectStrategy.modifyIdeInfoCommand(builder, activeLanguages);
int retVal =
ExternalTask.builder(workspaceRoot)
@@ -294,6 +298,10 @@
.run();
BuildResult buildResult = BuildResult.fromExitCode(retVal);
+ if (buildResult.status == Status.FATAL_ERROR) {
+ buildResultHelper.close();
+ return new IdeInfoResult(ImmutableList.of(), buildResult);
+ }
return new IdeInfoResult(buildResultHelper.getBuildArtifacts(), buildResult);
}
@@ -313,8 +321,9 @@
BlazeContext parentContext,
@Nullable State prevState,
ImmutableMap<File, Long> fileState,
+ BlazeConfigurationHandler configHandler,
WorkspaceLanguageSettings workspaceLanguageSettings,
- Set<Label> nonWildcardProjectTargets,
+ ImportRoots importRoots,
AspectStrategy aspectStrategy,
List<File> newFiles,
List<File> removedFiles,
@@ -346,7 +355,6 @@
state.aspectStrategyName = aspectStrategy.getName();
Map<TargetKey, TargetIdeInfo> targetMap = Maps.newHashMap();
- Map<TargetKey, TargetIdeInfo> updatedTargets = Maps.newHashMap();
if (prevState != null) {
targetMap.putAll(prevState.targetMap.map());
state.fileToTargetMapKey.putAll(prevState.fileToTargetMapKey);
@@ -380,7 +388,7 @@
TargetIdeInfo target =
protoToTarget(
workspaceLanguageSettings,
- nonWildcardProjectTargets,
+ importRoots,
message,
ignoredLanguages);
return new TargetFilePair(file, target);
@@ -388,19 +396,31 @@
}));
}
+ Set<TargetKey> newTargets = new HashSet<>();
+ Set<String> configurations = new LinkedHashSet<>();
+ configurations.add(configHandler.defaultConfigurationPathComponent);
+
// Update state with result from proto files
int duplicateTargetLabels = 0;
try {
- for (TargetFilePair targetFilePairs : Futures.allAsList(futures).get()) {
- if (targetFilePairs.target != null) {
- File file = targetFilePairs.file;
- TargetKey key = targetFilePairs.target.key;
- TargetIdeInfo previousTarget =
- updatedTargets.putIfAbsent(key, targetFilePairs.target);
- if (previousTarget == null) {
+ for (TargetFilePair targetFilePair : Futures.allAsList(futures).get()) {
+ if (targetFilePair.target != null) {
+ File file = targetFilePair.file;
+ String config = configHandler.getConfigurationPathComponent(file);
+ configurations.add(config);
+ TargetKey key = targetFilePair.target.key;
+ if (targetMap.putIfAbsent(key, targetFilePair.target) == null) {
state.fileToTargetMapKey.put(file, key);
} else {
- duplicateTargetLabels++;
+ if (!newTargets.add(key)) {
+ duplicateTargetLabels++;
+ }
+ // prioritize the default configuration over build order
+ if (Objects.equals(
+ config, configHandler.defaultConfigurationPathComponent)) {
+ targetMap.put(key, targetFilePair.target);
+ state.fileToTargetMapKey.put(file, key);
+ }
}
}
}
@@ -410,7 +430,6 @@
} catch (ExecutionException e) {
return Result.error(e);
}
- targetMap.putAll(updatedTargets);
context.output(
PrintOutput.log(
@@ -421,13 +440,16 @@
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%%.",
+ "There were %d duplicate rules, built with the following "
+ + "configurations: %s.\nYour IDE sync is slowed down by ~%d%%.",
duplicateTargetLabels,
+ configurations,
(100 * duplicateTargetLabels / targetMap.size()))));
}
+ ignoredLanguages.retainAll(
+ LanguageSupport.availableAdditionalLanguages(
+ workspaceLanguageSettings.getWorkspaceType()));
warnIgnoredLanguages(project, context, ignoredLanguages);
state.targetMap = new TargetMap(ImmutableMap.copyOf(targetMap));
@@ -444,18 +466,18 @@
@Nullable
private static TargetIdeInfo protoToTarget(
WorkspaceLanguageSettings languageSettings,
- Set<Label> nonWildcardProjectTargets,
+ ImportRoots importRoots,
IntellijIdeInfo.TargetIdeInfo message,
Set<LanguageClass> ignoredLanguages) {
Kind kind = IdeInfoFromProtobuf.getKind(message);
if (kind == null) {
return null;
}
- if (languageSettings.isLanguageActive(kind.getLanguageClass())) {
+ if (languageSettings.isLanguageActive(kind.languageClass)) {
return IdeInfoFromProtobuf.makeTargetIdeInfo(message);
}
- if (nonWildcardProjectTargets.contains(IdeInfoFromProtobuf.getKey(message).label)) {
- ignoredLanguages.add(kind.getLanguageClass());
+ if (importRoots.importAsSource(IdeInfoFromProtobuf.getKey(message).label)) {
+ ignoredLanguages.add(kind.languageClass);
}
return null;
}
@@ -498,9 +520,17 @@
WorkspaceRoot workspaceRoot,
ProjectViewSet projectViewSet,
BlazeVersionData blazeVersionData,
+ WorkspaceLanguageSettings workspaceLanguageSettings,
ShardedTargetList shardedTargets) {
return resolveIdeArtifacts(
- project, context, workspaceRoot, projectViewSet, blazeVersionData, shardedTargets, false);
+ project,
+ context,
+ workspaceRoot,
+ projectViewSet,
+ blazeVersionData,
+ workspaceLanguageSettings,
+ shardedTargets,
+ false);
}
@Override
@@ -510,6 +540,7 @@
WorkspaceRoot workspaceRoot,
ProjectViewSet projectViewSet,
BlazeVersionData blazeVersionData,
+ WorkspaceLanguageSettings workspaceLanguageSettings,
ShardedTargetList shardedTargets) {
boolean ideCompile = hasIdeCompileOutputGroup(blazeVersionData);
return resolveIdeArtifacts(
@@ -518,6 +549,7 @@
workspaceRoot,
projectViewSet,
blazeVersionData,
+ workspaceLanguageSettings,
shardedTargets,
ideCompile);
}
@@ -533,6 +565,7 @@
WorkspaceRoot workspaceRoot,
ProjectViewSet projectViewSet,
BlazeVersionData blazeVersionData,
+ WorkspaceLanguageSettings workspaceLanguageSettings,
ShardedTargetList shardedTargets,
boolean useIdeCompileOutputGroup) {
@@ -543,45 +576,100 @@
count, shardedTargets.shardedTargets.size());
Function<List<TargetExpression>, BuildResult> invocation =
targets ->
- doResolveIdeArtifacts(
- project,
- context,
- workspaceRoot,
- projectViewSet,
- blazeVersionData,
- targets,
- useIdeCompileOutputGroup);
+ useIdeCompileOutputGroup
+ ? doCompileIdeArtifacts(
+ project,
+ context,
+ workspaceRoot,
+ projectViewSet,
+ blazeVersionData,
+ workspaceLanguageSettings,
+ targets)
+ : doResolveIdeArtifacts(
+ project,
+ context,
+ workspaceRoot,
+ projectViewSet,
+ blazeVersionData,
+ workspaceLanguageSettings,
+ targets);
return shardedTargets.runShardedCommand(project, context, progressMessage, invocation);
}
+ /**
+ * Blaze build invocation requesting the 'intellij-resolve' aspect output group.
+ *
+ * <p>Prefetches the output artifacts built by this invocation.
+ */
private static BuildResult doResolveIdeArtifacts(
Project project,
BlazeContext context,
WorkspaceRoot workspaceRoot,
ProjectViewSet projectViewSet,
BlazeVersionData blazeVersionData,
- List<TargetExpression> targets,
- boolean useIdeCompileOutputGroup) {
- AspectStrategy aspectStrategy = getAspectStrategy(project, blazeVersionData);
+ WorkspaceLanguageSettings workspaceLanguageSettings,
+ List<TargetExpression> targets) {
+ BuildResultHelper buildResultHelper =
+ BuildResultHelper.forFiles(blazeVersionData, getGenfilePrefetchFilter());
BlazeCommand.Builder blazeCommandBuilder =
BlazeCommand.builder(getBinaryPath(project), BlazeCommandName.BUILD)
.addTargets(targets)
+ .addBlazeFlags(BlazeFlags.KEEP_GOING)
+ .addBlazeFlags(buildResultHelper.getBuildFlags())
+ .addBlazeFlags(
+ BlazeFlags.blazeFlags(
+ project, projectViewSet, BlazeCommandName.BUILD, BlazeInvocationContext.Sync));
+
+ // Request the 'intellij-resolve' aspect output group.
+ getAspectStrategy(blazeVersionData)
+ .modifyIdeResolveCommand(blazeCommandBuilder, workspaceLanguageSettings.activeLanguages);
+
+ // Run the blaze build command, parsing any output artifacts produced.
+ int retVal =
+ ExternalTask.builder(workspaceRoot)
+ .addBlazeCommand(blazeCommandBuilder.build())
+ .context(context)
+ .stderr(
+ buildResultHelper.stderr(
+ new IssueOutputLineProcessor(project, context, workspaceRoot)))
+ .build()
+ .run(new TimingScope("ExecuteBlazeCommand"));
+
+ BuildResult result = BuildResult.fromExitCode(retVal);
+ if (result.status != BuildResult.Status.FATAL_ERROR) {
+ prefetchGenfiles(project, context, buildResultHelper.getBuildArtifacts());
+ } else {
+ buildResultHelper.close();
+ }
+ return result;
+ }
+
+ /** Blaze build invocation requesting the 'intellij-compile' aspect output group. */
+ private static BuildResult doCompileIdeArtifacts(
+ Project project,
+ BlazeContext context,
+ WorkspaceRoot workspaceRoot,
+ ProjectViewSet projectViewSet,
+ BlazeVersionData blazeVersionData,
+ WorkspaceLanguageSettings workspaceLanguageSettings,
+ List<TargetExpression> targets) {
+ BlazeCommand.Builder blazeCommandBuilder =
+ BlazeCommand.builder(getBinaryPath(project), BlazeCommandName.BUILD)
+ .addTargets(targets)
.addBlazeFlags()
.addBlazeFlags(BlazeFlags.KEEP_GOING)
- .addBlazeFlags(BlazeFlags.buildFlags(project, projectViewSet));
+ .addBlazeFlags(
+ BlazeFlags.blazeFlags(
+ project, projectViewSet, BlazeCommandName.BUILD, BlazeInvocationContext.Sync));
- if (useIdeCompileOutputGroup) {
- aspectStrategy.modifyIdeCompileCommand(blazeCommandBuilder);
- } else {
- aspectStrategy.modifyIdeResolveCommand(blazeCommandBuilder);
- }
+ getAspectStrategy(blazeVersionData)
+ .modifyIdeCompileCommand(blazeCommandBuilder, workspaceLanguageSettings.activeLanguages);
- BlazeCommand blazeCommand = blazeCommandBuilder.build();
-
+ // Run the blaze build command.
int retVal =
ExternalTask.builder(workspaceRoot)
- .addBlazeCommand(blazeCommand)
+ .addBlazeCommand(blazeCommandBuilder.build())
.context(context)
.stderr(
LineProcessingOutputStream.of(
@@ -592,10 +680,26 @@
return BuildResult.fromExitCode(retVal);
}
- private static AspectStrategy getAspectStrategy(
- Project project, BlazeVersionData blazeVersionData) {
+ /** A filename filter for blaze output artifacts to prefetch. */
+ private static Predicate<String> getGenfilePrefetchFilter() {
+ ImmutableSet<String> extensions = PrefetchFileSource.getAllPrefetchFileExtensions();
+ return fileName -> extensions.contains(FileUtil.getExtension(fileName));
+ }
+
+ /** Prefetch a list of blaze output artifacts, blocking until complete. */
+ private static void prefetchGenfiles(
+ Project project, BlazeContext context, ImmutableList<File> artifacts) {
+ ListenableFuture<?> prefetchFuture =
+ PrefetchService.getInstance().prefetchFiles(project, artifacts, false);
+ FutureUtil.waitForFuture(context, prefetchFuture)
+ .timed("PrefetchGenfiles")
+ .withProgressMessage("Prefetching genfiles...")
+ .run();
+ }
+
+ private static AspectStrategy getAspectStrategy(BlazeVersionData blazeVersionData) {
for (AspectStrategyProvider provider : AspectStrategyProvider.EP_NAME.getExtensions()) {
- AspectStrategy aspectStrategy = provider.getAspectStrategy(project, blazeVersionData);
+ AspectStrategy aspectStrategy = provider.getAspectStrategy(blazeVersionData);
if (aspectStrategy != null) {
return aspectStrategy;
}
diff --git a/base/src/com/google/idea/blaze/base/sync/aspects/IdeInfoFromProtobuf.java b/base/src/com/google/idea/blaze/base/sync/aspects/IdeInfoFromProtobuf.java
index a37991f..443dcbc 100644
--- a/base/src/com/google/idea/blaze/base/sync/aspects/IdeInfoFromProtobuf.java
+++ b/base/src/com/google/idea/blaze/base/sync/aspects/IdeInfoFromProtobuf.java
@@ -30,14 +30,17 @@
import com.google.idea.blaze.base.ideinfo.CToolchainIdeInfo;
import com.google.idea.blaze.base.ideinfo.Dependency;
import com.google.idea.blaze.base.ideinfo.Dependency.DependencyType;
+import com.google.idea.blaze.base.ideinfo.GoIdeInfo;
import com.google.idea.blaze.base.ideinfo.JavaIdeInfo;
import com.google.idea.blaze.base.ideinfo.JavaToolchainIdeInfo;
+import com.google.idea.blaze.base.ideinfo.JsIdeInfo;
import com.google.idea.blaze.base.ideinfo.LibraryArtifact;
import com.google.idea.blaze.base.ideinfo.ProtoLibraryLegacyInfo;
import com.google.idea.blaze.base.ideinfo.PyIdeInfo;
import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
import com.google.idea.blaze.base.ideinfo.TargetKey;
import com.google.idea.blaze.base.ideinfo.TestIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TsIdeInfo;
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;
@@ -81,6 +84,8 @@
if (message.hasCIdeInfo()) {
cIdeInfo = makeCIdeInfo(message.getCIdeInfo());
sources.addAll(cIdeInfo.sources);
+ sources.addAll(cIdeInfo.headers);
+ sources.addAll(cIdeInfo.textualHeaders);
}
CToolchainIdeInfo cToolchainIdeInfo = null;
if (message.hasCToolchainIdeInfo()) {
@@ -106,6 +111,21 @@
pyIdeInfo = makePyIdeInfo(message.getPyIdeInfo());
sources.addAll(pyIdeInfo.sources);
}
+ GoIdeInfo goIdeInfo = null;
+ if (message.hasGoIdeInfo()) {
+ goIdeInfo = makeGoIdeInfo(message.getGoIdeInfo());
+ sources.addAll(goIdeInfo.generatedSources);
+ }
+ JsIdeInfo jsIdeInfo = null;
+ if (message.hasJsIdeInfo()) {
+ jsIdeInfo = makeJsIdeInfo(message.getJsIdeInfo());
+ sources.addAll(jsIdeInfo.sources);
+ }
+ TsIdeInfo tsIdeInfo = null;
+ if (message.hasTsIdeInfo()) {
+ tsIdeInfo = makeTsIdeInfo(message.getTsIdeInfo());
+ sources.addAll(tsIdeInfo.sources);
+ }
TestIdeInfo testIdeInfo = null;
if (message.hasTestInfo()) {
testIdeInfo = makeTestIdeInfo(message.getTestInfo());
@@ -133,6 +153,9 @@
androidIdeInfo,
androidSdkIdeInfo,
pyIdeInfo,
+ goIdeInfo,
+ jsIdeInfo,
+ tsIdeInfo,
testIdeInfo,
protoLibraryLegacyInfo,
javaToolchainIdeInfo);
@@ -177,6 +200,9 @@
private static CIdeInfo makeCIdeInfo(IntellijIdeInfo.CIdeInfo cIdeInfo) {
List<ArtifactLocation> sources = makeArtifactLocationList(cIdeInfo.getSourceList());
+ List<ArtifactLocation> headers = makeArtifactLocationList(cIdeInfo.getHeaderList());
+ List<ArtifactLocation> textualHeaders =
+ makeArtifactLocationList(cIdeInfo.getTextualHeaderList());
List<ExecutionRootPath> transitiveIncludeDirectories =
makeExecutionRootPathList(cIdeInfo.getTransitiveIncludeDirectoryList());
List<ExecutionRootPath> transitiveQuoteIncludeDirectories =
@@ -202,6 +228,8 @@
CIdeInfo.Builder builder =
CIdeInfo.builder()
.addSources(sources)
+ .addHeaders(headers)
+ .addTextualHeaders(textualHeaders)
.addLocalDefines(coptDefines)
.addLocalIncludeDirectories(coptIncludeDirectories)
.addTransitiveIncludeDirectories(transitiveIncludeDirectories)
@@ -289,6 +317,20 @@
return PyIdeInfo.builder().addSources(makeArtifactLocationList(info.getSourcesList())).build();
}
+ private static GoIdeInfo makeGoIdeInfo(IntellijIdeInfo.GoIdeInfo info) {
+ return GoIdeInfo.builder()
+ .addGeneratedSources(makeArtifactLocationList(info.getGeneratedSourcesList()))
+ .build();
+ }
+
+ private static JsIdeInfo makeJsIdeInfo(IntellijIdeInfo.JsIdeInfo info) {
+ return JsIdeInfo.builder().addSources(makeArtifactLocationList(info.getSourcesList())).build();
+ }
+
+ private static TsIdeInfo makeTsIdeInfo(IntellijIdeInfo.TsIdeInfo info) {
+ return TsIdeInfo.builder().addSources(makeArtifactLocationList(info.getSourcesList())).build();
+ }
+
private static TestIdeInfo makeTestIdeInfo(IntellijIdeInfo.TestInfo testInfo) {
String size = testInfo.getSize();
TestIdeInfo.TestSize testSize = TestIdeInfo.DEFAULT_RULE_TEST_SIZE;
@@ -343,8 +385,12 @@
private static JavaToolchainIdeInfo makeJavaToolchainIdeInfo(
IntellijIdeInfo.JavaToolchainIdeInfo javaToolchainIdeInfo) {
+ ArtifactLocation javacJar =
+ javaToolchainIdeInfo.hasJavacJar()
+ ? makeArtifactLocation(javaToolchainIdeInfo.getJavacJar())
+ : null;
return new JavaToolchainIdeInfo(
- javaToolchainIdeInfo.getSourceVersion(), javaToolchainIdeInfo.getTargetVersion());
+ javaToolchainIdeInfo.getSourceVersion(), javaToolchainIdeInfo.getTargetVersion(), javacJar);
}
private static Collection<LibraryArtifact> makeLibraryArtifactList(
diff --git a/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategy.java b/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategy.java
index d63630e..cb98797 100644
--- a/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategy.java
+++ b/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategy.java
@@ -15,22 +15,96 @@
*/
package com.google.idea.blaze.base.sync.aspects.strategy;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.base.Joiner;
import com.google.idea.blaze.base.command.BlazeCommand;
+import com.google.idea.blaze.base.command.BlazeCommand.Builder;
+import com.google.idea.blaze.base.model.primitives.LanguageClass;
+import com.google.idea.common.experiments.BoolExperiment;
import com.google.repackaged.devtools.intellij.ideinfo.IntellijIdeInfo;
+import com.google.repackaged.protobuf.TextFormat;
import java.io.IOException;
import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
-/** Indirection for our various ways of calling the aspect. */
-public interface AspectStrategy {
- String getName();
+/** Aspect strategy for Skylark. */
+public abstract class AspectStrategy {
- void modifyIdeInfoCommand(BlazeCommand.Builder blazeCommandBuilder);
+ private static final BoolExperiment usePerLanguageOutputGroups =
+ new BoolExperiment("blaze.use.per.language.output.groups", true);
- void modifyIdeResolveCommand(BlazeCommand.Builder blazeCommandBuilder);
+ public abstract String getName();
- void modifyIdeCompileCommand(BlazeCommand.Builder blazeCommandBuilder);
+ protected abstract List<String> getAspectFlags();
- String getAspectOutputFileExtension();
+ protected abstract boolean hasPerLanguageOutputGroups();
- IntellijIdeInfo.TargetIdeInfo readAspectFile(InputStream inputStream) throws IOException;
+ private boolean usePerLanguageOutputGroups() {
+ return usePerLanguageOutputGroups.getValue() && hasPerLanguageOutputGroups();
+ }
+
+ public final void modifyIdeInfoCommand(
+ BlazeCommand.Builder blazeCommandBuilder, Set<LanguageClass> activeLanguages) {
+ blazeCommandBuilder.addBlazeFlags(getAspectFlags());
+ if (!usePerLanguageOutputGroups()) {
+ blazeCommandBuilder.addBlazeFlags("--output_groups=intellij-info-text");
+ return;
+ }
+ List<String> outputGroups = getOutputGroups(activeLanguages, "intellij-info-");
+ outputGroups.add("intellij-info-generic");
+ String flag = "--output_groups=" + Joiner.on(',').join(outputGroups);
+ blazeCommandBuilder.addBlazeFlags(flag);
+ }
+
+ public final void modifyIdeResolveCommand(
+ BlazeCommand.Builder blazeCommandBuilder, Set<LanguageClass> activeLanguages) {
+ blazeCommandBuilder.addBlazeFlags(getAspectFlags());
+ if (!usePerLanguageOutputGroups()) {
+ blazeCommandBuilder.addBlazeFlags("--output_groups=intellij-resolve");
+ return;
+ }
+ List<String> outputGroups = getOutputGroups(activeLanguages, "intellij-resolve-");
+ String flag = "--output_groups=" + Joiner.on(',').join(outputGroups);
+ blazeCommandBuilder.addBlazeFlags(flag);
+ }
+
+ public final void modifyIdeCompileCommand(
+ Builder blazeCommandBuilder, Set<LanguageClass> activeLanguages) {
+ blazeCommandBuilder.addBlazeFlags(getAspectFlags());
+ if (!usePerLanguageOutputGroups()) {
+ blazeCommandBuilder.addBlazeFlags("--output_groups=intellij-compile");
+ return;
+ }
+ List<String> outputGroups = getOutputGroups(activeLanguages, "intellij-compile-");
+ String flag = "--output_groups=" + Joiner.on(',').join(outputGroups);
+ blazeCommandBuilder.addBlazeFlags(flag);
+ }
+
+ private static List<String> getOutputGroups(Set<LanguageClass> activeLanguages, String prefix) {
+ return activeLanguages
+ .stream()
+ .map(LanguageOutputGroup::forLanguage)
+ .filter(Objects::nonNull)
+ .map(lang -> prefix + lang.suffix)
+ .distinct()
+ .sorted()
+ .collect(Collectors.toList());
+ }
+
+ public final String getAspectOutputFileExtension() {
+ return ".intellij-info.txt";
+ }
+
+ public final IntellijIdeInfo.TargetIdeInfo readAspectFile(InputStream inputStream)
+ throws IOException {
+ IntellijIdeInfo.TargetIdeInfo.Builder builder = IntellijIdeInfo.TargetIdeInfo.newBuilder();
+ TextFormat.Parser parser = TextFormat.Parser.newBuilder().setAllowUnknownFields(true).build();
+ parser.merge(new InputStreamReader(inputStream, UTF_8), builder);
+ return builder.build();
+ }
}
diff --git a/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategyProvider.java b/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategyProvider.java
index 13c7586..268eaca 100644
--- a/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategyProvider.java
+++ b/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategyProvider.java
@@ -17,7 +17,6 @@
import com.google.idea.blaze.base.model.BlazeVersionData;
import com.intellij.openapi.extensions.ExtensionPointName;
-import com.intellij.openapi.project.Project;
import javax.annotation.Nullable;
/** Extension point for providing an aspect strategy */
@@ -26,5 +25,5 @@
ExtensionPointName.create("com.google.idea.blaze.AspectStrategyProvider");
@Nullable
- AspectStrategy getAspectStrategy(Project project, BlazeVersionData blazeVersionData);
+ AspectStrategy getAspectStrategy(BlazeVersionData blazeVersionData);
}
diff --git a/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategyProviderBazel.java b/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategyProviderBazel.java
index 9c0faa4..191ae61 100644
--- a/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategyProviderBazel.java
+++ b/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategyProviderBazel.java
@@ -15,12 +15,66 @@
*/
package com.google.idea.blaze.base.sync.aspects.strategy;
+import com.google.common.collect.ImmutableList;
import com.google.idea.blaze.base.model.BlazeVersionData;
-import com.intellij.openapi.project.Project;
+import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
+import com.intellij.ide.plugins.IdeaPluginDescriptor;
+import com.intellij.ide.plugins.PluginManager;
+import java.io.File;
+import java.util.List;
class AspectStrategyProviderBazel implements AspectStrategyProvider {
@Override
- public AspectStrategy getAspectStrategy(Project project, BlazeVersionData blazeVersionData) {
- return new AspectStrategySkylark();
+ public AspectStrategy getAspectStrategy(BlazeVersionData blazeVersionData) {
+ if (blazeVersionData.buildSystem() != BuildSystem.Bazel) {
+ return null;
+ }
+ return new AspectStrategyBazel(blazeVersionData);
+ }
+
+ private static class AspectStrategyBazel extends AspectStrategy {
+
+ private final BlazeVersionData blazeVersionData;
+
+ private AspectStrategyBazel(BlazeVersionData blazeVersionData) {
+ this.blazeVersionData = blazeVersionData;
+ }
+
+ @Override
+ public String getName() {
+ return "AspectStrategySkylarkBazel";
+ }
+
+ @Override
+ protected boolean hasPerLanguageOutputGroups() {
+ return useBundledAspect();
+ }
+
+ @Override
+ protected List<String> getAspectFlags() {
+ if (useBundledAspect()) {
+ return ImmutableList.of(
+ "--aspects=@intellij_aspect//:intellij_info.bzl%intellij_info_aspect",
+ getAspectRepositoryOverrideFlag());
+ }
+ return ImmutableList.of(
+ "--aspects=@bazel_tools//tools/ide:intellij_info.bzl%intellij_info_aspect");
+ }
+
+ private boolean useBundledAspect() {
+ return blazeVersionData.bazelIsAtLeastVersion(0, 5, 0);
+ }
+
+ private static File findAspectDirectory() {
+ IdeaPluginDescriptor plugin =
+ PluginManager.getPlugin(
+ PluginManager.getPluginByClassName(AspectStrategy.class.getName()));
+ return new File(plugin.getPath(), "aspect");
+ }
+
+ private static String getAspectRepositoryOverrideFlag() {
+ return String.format(
+ "--override_repository=intellij_aspect=%s", findAspectDirectory().getPath());
+ }
}
}
diff --git a/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategySkylark.java b/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategySkylark.java
deleted file mode 100644
index aa3eadc..0000000
--- a/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategySkylark.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.sync.aspects.strategy;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import com.google.idea.blaze.base.command.BlazeCommand;
-import com.google.idea.blaze.base.command.BlazeCommand.Builder;
-import com.google.repackaged.devtools.intellij.ideinfo.IntellijIdeInfo;
-import com.google.repackaged.protobuf.TextFormat;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-
-/** Aspect strategy for Skylark. */
-public class AspectStrategySkylark implements AspectStrategy {
-
- @Override
- public String getName() {
- return "SkylarkAspect";
- }
-
- protected String getAspectFlag() {
- return "--aspects=@bazel_tools//tools/ide:intellij_info.bzl%intellij_info_aspect";
- }
-
- @Override
- public void modifyIdeInfoCommand(BlazeCommand.Builder blazeCommandBuilder) {
- blazeCommandBuilder
- .addBlazeFlags(getAspectFlag())
- .addBlazeFlags("--output_groups=intellij-info-text");
- }
-
- @Override
- public void modifyIdeResolveCommand(BlazeCommand.Builder blazeCommandBuilder) {
- blazeCommandBuilder
- .addBlazeFlags(getAspectFlag())
- .addBlazeFlags("--output_groups=intellij-resolve");
- }
-
- @Override
- public void modifyIdeCompileCommand(Builder blazeCommandBuilder) {
- blazeCommandBuilder
- .addBlazeFlags(getAspectFlag())
- .addBlazeFlags("--output_groups=intellij-compile");
- }
-
- @Override
- public String getAspectOutputFileExtension() {
- return ".intellij-info.txt";
- }
-
- @Override
- public IntellijIdeInfo.TargetIdeInfo readAspectFile(InputStream inputStream) throws IOException {
- IntellijIdeInfo.TargetIdeInfo.Builder builder = IntellijIdeInfo.TargetIdeInfo.newBuilder();
- TextFormat.Parser parser = TextFormat.Parser.newBuilder().setAllowUnknownFields(true).build();
- parser.merge(new InputStreamReader(inputStream, UTF_8), builder);
- return builder.build();
- }
-}
diff --git a/base/src/com/google/idea/blaze/base/sync/aspects/strategy/LanguageOutputGroup.java b/base/src/com/google/idea/blaze/base/sync/aspects/strategy/LanguageOutputGroup.java
new file mode 100644
index 0000000..6f60207
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/aspects/strategy/LanguageOutputGroup.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.base.sync.aspects.strategy;
+
+import com.google.idea.blaze.base.model.primitives.LanguageClass;
+import javax.annotation.Nullable;
+
+/** Enumerates the sets of aspect output groups corresponding to each language */
+public enum LanguageOutputGroup {
+ ANDROID(LanguageClass.ANDROID, "android"),
+ C(LanguageClass.C, "cpp"),
+ JAVA(LanguageClass.JAVA, "java"),
+ PYTHON(LanguageClass.PYTHON, "py"),
+ GO(LanguageClass.GO, "go"),
+ JAVASCRIPT(LanguageClass.JAVASCRIPT, "js"),
+ TYPESCRIPT(LanguageClass.TYPESCRIPT, "ts");
+
+ public final LanguageClass languageClass;
+ public final String suffix;
+
+ LanguageOutputGroup(LanguageClass languageClass, String suffix) {
+ this.languageClass = languageClass;
+ this.suffix = suffix;
+ }
+
+ @Nullable
+ public static LanguageOutputGroup forLanguage(LanguageClass languageClass) {
+ for (LanguageOutputGroup group : values()) {
+ if (group.languageClass == languageClass) {
+ return group;
+ }
+ }
+ return null;
+ }
+}
diff --git a/base/src/com/google/idea/blaze/base/sync/libraries/BlazeLibraryCollector.java b/base/src/com/google/idea/blaze/base/sync/libraries/BlazeLibraryCollector.java
index c8e38db..c5d4c79 100644
--- a/base/src/com/google/idea/blaze/base/sync/libraries/BlazeLibraryCollector.java
+++ b/base/src/com/google/idea/blaze/base/sync/libraries/BlazeLibraryCollector.java
@@ -50,6 +50,8 @@
.filter(Objects::nonNull)
.reduce(Predicate::and)
.orElse(o -> true);
- return result.stream().filter(libraryFilter).collect(Collectors.toList());
+
+ return BlazeLibrarySorter.sortLibraries(
+ result.stream().filter(libraryFilter).collect(Collectors.toList()));
}
}
diff --git a/base/src/com/google/idea/blaze/base/sync/libraries/BlazeLibrarySorter.java b/base/src/com/google/idea/blaze/base/sync/libraries/BlazeLibrarySorter.java
new file mode 100644
index 0000000..e6fca95
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/libraries/BlazeLibrarySorter.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.base.sync.libraries;
+
+import com.google.idea.blaze.base.model.BlazeLibrary;
+import com.intellij.openapi.extensions.ExtensionPointName;
+import java.util.List;
+
+/**
+ * Sorts project libraries found during sync. Sorters are invoked in reverse EP order, so the
+ * highest-priority sorter should appear first in the EP list.
+ */
+public interface BlazeLibrarySorter {
+
+ ExtensionPointName<BlazeLibrarySorter> EP_NAME =
+ ExtensionPointName.create("com.google.idea.blaze.BlazeLibrarySorter");
+
+ static List<BlazeLibrary> sortLibraries(List<BlazeLibrary> libraries) {
+ BlazeLibrarySorter[] sorters = EP_NAME.getExtensions();
+ for (int i = sorters.length - 1; i >= 0; i--) {
+ libraries = sorters[i].sort(libraries);
+ }
+ return libraries;
+ }
+
+ List<BlazeLibrary> sort(List<BlazeLibrary> libraries);
+}
diff --git a/base/src/com/google/idea/blaze/base/sync/libraries/LibrarySource.java b/base/src/com/google/idea/blaze/base/sync/libraries/LibrarySource.java
index 5d15a8d..ef1f275 100644
--- a/base/src/com/google/idea/blaze/base/sync/libraries/LibrarySource.java
+++ b/base/src/com/google/idea/blaze/base/sync/libraries/LibrarySource.java
@@ -18,7 +18,7 @@
import com.google.common.collect.ImmutableList;
import com.google.idea.blaze.base.model.BlazeLibrary;
import com.intellij.openapi.roots.libraries.Library;
-import java.util.Collection;
+import java.util.List;
import java.util.function.Predicate;
import javax.annotation.Nullable;
@@ -26,7 +26,7 @@
public interface LibrarySource {
/** Called during the project structure phase to get libraries. */
- Collection<? extends BlazeLibrary> getLibraries();
+ List<? extends BlazeLibrary> getLibraries();
/**
* Returns a filter on libraries.
@@ -49,7 +49,7 @@
/** Adapter class */
abstract class Adapter implements LibrarySource {
@Override
- public Collection<? extends BlazeLibrary> getLibraries() {
+ public List<? extends BlazeLibrary> getLibraries() {
return ImmutableList.of();
}
diff --git a/base/src/com/google/idea/blaze/base/sync/projectview/LanguageSupport.java b/base/src/com/google/idea/blaze/base/sync/projectview/LanguageSupport.java
index bae9239..aecac37 100644
--- a/base/src/com/google/idea/blaze/base/sync/projectview/LanguageSupport.java
+++ b/base/src/com/google/idea/blaze/base/sync/projectview/LanguageSupport.java
@@ -15,6 +15,8 @@
*/
package com.google.idea.blaze.base.sync.projectview;
+import static com.google.common.base.Preconditions.checkState;
+
import com.google.common.collect.ImmutableSet;
import com.google.idea.blaze.base.model.primitives.LanguageClass;
import com.google.idea.blaze.base.model.primitives.WorkspaceType;
@@ -24,18 +26,13 @@
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.Arrays;
import java.util.EnumSet;
import java.util.Set;
-import javax.annotation.Nullable;
/** Reads the user's language preferences from the project view. */
public class LanguageSupport {
- private static final Logger logger = Logger.getInstance(LanguageSupport.class);
-
- @Nullable
public static WorkspaceType getDefaultWorkspaceType() {
WorkspaceType workspaceType = null;
// prioritize by enum ordinal.
@@ -46,24 +43,19 @@
workspaceType = recommendedType;
}
}
+ // Should never happen, outside of tests without proper set up.
+ checkState(
+ workspaceType != null, "No SyncPlugin present which provides a default workspace type.");
return workspaceType;
}
/**
* Derives {@link WorkspaceLanguageSettings} from the {@link ProjectViewSet}. Does no validation.
*/
- @Nullable
public static WorkspaceLanguageSettings createWorkspaceLanguageSettings(
ProjectViewSet projectViewSet) {
- WorkspaceType workspaceType = projectViewSet.getScalarValue(WorkspaceTypeSection.KEY);
- if (workspaceType == null) {
- workspaceType = getDefaultWorkspaceType();
- }
-
- if (workspaceType == null) {
- logger.error("Could not find workspace type."); // Should never happen
- return null;
- }
+ WorkspaceType workspaceType =
+ projectViewSet.getScalarValue(WorkspaceTypeSection.KEY).orElse(getDefaultWorkspaceType());
ImmutableSet.Builder<LanguageClass> activeLanguages =
ImmutableSet.<LanguageClass>builder()
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
index 5fdd67f..0b52104 100644
--- a/base/src/com/google/idea/blaze/base/sync/projectview/WorkspaceLanguageSettings.java
+++ b/base/src/com/google/idea/blaze/base/sync/projectview/WorkspaceLanguageSettings.java
@@ -30,7 +30,7 @@
private static final long serialVersionUID = 1L;
private final WorkspaceType workspaceType;
- final ImmutableSet<LanguageClass> activeLanguages;
+ public final ImmutableSet<LanguageClass> activeLanguages;
public WorkspaceLanguageSettings(
WorkspaceType workspaceType, ImmutableSet<LanguageClass> activeLanguages) {
@@ -61,7 +61,7 @@
public EnumSet<Kind> getAvailableTargetKinds() {
EnumSet<Kind> kinds = EnumSet.allOf(Kind.class);
- kinds.removeIf(kind -> !activeLanguages.contains(kind.getLanguageClass()));
+ kinds.removeIf(kind -> !activeLanguages.contains(kind.languageClass));
return kinds;
}
diff --git a/base/src/com/google/idea/blaze/base/sync/sharding/BlazeBuildTargetSharder.java b/base/src/com/google/idea/blaze/base/sync/sharding/BlazeBuildTargetSharder.java
index 8f2279c..01cf684 100644
--- a/base/src/com/google/idea/blaze/base/sync/sharding/BlazeBuildTargetSharder.java
+++ b/base/src/com/google/idea/blaze/base/sync/sharding/BlazeBuildTargetSharder.java
@@ -21,11 +21,12 @@
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.ShardBlazeBuildsSection;
+import com.google.idea.blaze.base.projectview.section.sections.TargetShardSizeSection;
import com.google.idea.blaze.base.scope.BlazeContext;
import com.google.idea.blaze.base.sync.aspects.BuildResult;
import com.google.idea.blaze.base.sync.sharding.WildcardTargetExpander.ExpandedTargetsResult;
import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver;
-import com.google.idea.common.experiments.BoolExperiment;
+import com.google.idea.common.experiments.IntExperiment;
import com.intellij.openapi.project.Project;
import java.util.ArrayList;
import java.util.List;
@@ -36,15 +37,13 @@
/** Utility methods for sharding blaze build invocations. */
public class BlazeBuildTargetSharder {
- private static final BoolExperiment allowSharding =
- new BoolExperiment("blaze.build.sharding.allowed", true);
+ /** Default number of individual targets per blaze build shard. Can be overridden by the user. */
+ private static final IntExperiment targetShardSize =
+ new IntExperiment("blaze.target.shard.size", 1000);
// number of packages per blaze query shard
static final int PACKAGE_SHARD_SIZE = 500;
- // number of individual targets per blaze build shard
- private static final int TARGET_SHARD_SIZE = 1000;
-
/** Result of expanding then sharding wildcard target patterns */
public static class ShardedTargetsResult {
public final ShardedTargetList shardedTargets;
@@ -56,20 +55,21 @@
}
}
- /** Returns true if sharding can be enabled for this project, and is not already enabled */
- static boolean canEnableSharding(Project project) {
- if (!allowSharding.getValue()) {
- return false;
- }
+ /** Returns true if sharding is already enabled for this project. */
+ static boolean shardingEnabled(Project project) {
ProjectViewSet projectViewSet = ProjectViewManager.getInstance(project).getProjectViewSet();
- return projectViewSet != null && !shardingEnabled(projectViewSet);
+ return projectViewSet != null && shardingEnabled(projectViewSet);
}
private static boolean shardingEnabled(ProjectViewSet projectViewSet) {
- if (!allowSharding.getValue()) {
- return false;
- }
- return projectViewSet.getScalarValue(ShardBlazeBuildsSection.KEY, false);
+ return projectViewSet.getScalarValue(ShardBlazeBuildsSection.KEY).orElse(false);
+ }
+
+ /** Number of individual targets per blaze build shard */
+ static int getTargetShardSize(ProjectViewSet projectViewSet) {
+ return projectViewSet
+ .getScalarValue(TargetShardSizeSection.KEY)
+ .orElse(targetShardSize.getValue());
}
/** Expand wildcard target patterns and partition the resulting target list. */
@@ -98,7 +98,7 @@
new ShardedTargetList(ImmutableList.of()), expandedTargets.buildResult);
}
return new ShardedTargetsResult(
- shardTargets(expandedTargets.singleTargets, TARGET_SHARD_SIZE),
+ shardTargets(expandedTargets.singleTargets, getTargetShardSize(projectViewSet)),
expandedTargets.buildResult);
}
@@ -150,6 +150,9 @@
for (int index = 0; index < targets.size(); index += shardSize) {
int endIndex = Math.min(targets.size(), index + shardSize);
List<TargetExpression> shard = new ArrayList<>(targets.subList(index, endIndex));
+ if (shard.stream().filter(TargetExpression::isExcluded).count() == shard.size()) {
+ continue;
+ }
List<TargetExpression> remainingExcludes =
targets
.subList(endIndex, targets.size())
diff --git a/base/src/com/google/idea/blaze/base/sync/sharding/PackageLister.java b/base/src/com/google/idea/blaze/base/sync/sharding/PackageLister.java
index d87d205..5531c21 100644
--- a/base/src/com/google/idea/blaze/base/sync/sharding/PackageLister.java
+++ b/base/src/com/google/idea/blaze/base/sync/sharding/PackageLister.java
@@ -129,12 +129,15 @@
if (provider.findBuildFileInDirectory(dir) != null) {
output.add(TargetExpression.allFromPackageNonRecursive(path));
}
- File[] children = FileAttributeProvider.getInstance().listFiles(dir);
+ FileAttributeProvider attributeProvider = FileAttributeProvider.getInstance();
+ File[] children = attributeProvider.listFiles(dir);
if (children == null) {
return;
}
for (File child : children) {
- traversePackageRecursively(provider, pathResolver, child, output);
+ if (attributeProvider.isDirectory(child)) {
+ traversePackageRecursively(provider, pathResolver, child, output);
+ }
}
}
}
diff --git a/base/src/com/google/idea/blaze/base/sync/sharding/SuggestBuildShardingNotification.java b/base/src/com/google/idea/blaze/base/sync/sharding/SuggestBuildShardingNotification.java
new file mode 100644
index 0000000..0fe13e2
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/sharding/SuggestBuildShardingNotification.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.sharding;
+
+import com.google.idea.blaze.base.projectview.ProjectViewEdit;
+import com.google.idea.blaze.base.projectview.ProjectViewEdit.ProjectViewEditor;
+import com.google.idea.blaze.base.projectview.ProjectViewManager;
+import com.google.idea.blaze.base.projectview.section.ScalarSection;
+import com.google.idea.blaze.base.projectview.section.sections.ShardBlazeBuildsSection;
+import com.google.idea.blaze.base.projectview.section.sections.TargetShardSizeSection;
+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.BlazeUserSettings;
+import com.google.idea.blaze.base.sync.BlazeSyncManager;
+import com.google.idea.blaze.base.sync.BlazeSyncParams;
+import com.intellij.notification.Notification;
+import com.intellij.notification.NotificationListener;
+import com.intellij.notification.NotificationType;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.pom.NavigatableAdapter;
+import com.intellij.util.Consumer;
+import com.intellij.xml.util.XmlStringUtil;
+import javax.swing.event.HyperlinkEvent;
+
+/**
+ * If blaze runs out of memory during sync, suggest that the user enables build sharding, or tweaks
+ * the shard sizes if sharding is already enabled.
+ */
+public class SuggestBuildShardingNotification {
+
+ /** Displays any sharding-related notifications (with quick fixes) appropriate to the OOME. */
+ public static void syncOutOfMemoryError(Project project, BlazeContext context) {
+ if (BlazeBuildTargetSharder.shardingEnabled(project)) {
+ suggestReducingShardSize(project, context);
+ } else {
+ suggestSharding(project, context);
+ }
+ }
+
+ private static void suggestReducingShardSize(Project project, BlazeContext context) {
+ String buildSystem = Blaze.buildSystemName(project);
+ String message =
+ String.format(
+ "The %1$s server ran out of memory during sync. You can workaround this by "
+ + "<a href='fix'>reducing the shard size</a> in the project view file, "
+ + "or alternatively allocate more memory to %1$s",
+ buildSystem);
+ IssueOutput.error(
+ StringUtil.stripHtml(message, true) + ". Click here to reduce the shard " + "size.")
+ .navigatable(
+ new NavigatableAdapter() {
+ @Override
+ public void navigate(boolean requestFocus) {
+ reduceShardSizeAndResync(project);
+ }
+ })
+ .submit(context);
+
+ showNotification(project, message, SuggestBuildShardingNotification::reduceShardSizeAndResync);
+ }
+
+ private static void suggestSharding(Project project, BlazeContext context) {
+ String buildSystem = Blaze.buildSystemName(project);
+ String message =
+ String.format(
+ "The %1$s server ran out of memory during sync. This can occur for large projects. You "
+ + "can workaround this by <a href='fix'>sharding the %1$s build during sync</a>, "
+ + "or alternatively allocate more memory to %1$s",
+ buildSystem);
+ IssueOutput.error(StringUtil.stripHtml(message, true) + ". Click here to set up sync sharding.")
+ .navigatable(
+ new NavigatableAdapter() {
+ @Override
+ public void navigate(boolean requestFocus) {
+ enableShardingAndResync(project);
+ }
+ })
+ .submit(context);
+
+ showNotification(project, message, SuggestBuildShardingNotification::enableShardingAndResync);
+ }
+
+ private static void showNotification(
+ Project project, String message, Consumer<Project> projectViewEditor) {
+ Notification notification =
+ new Notification(
+ "Out of memory during sync",
+ Blaze.buildSystemName(project) + " ran out of memory during sync",
+ XmlStringUtil.wrapInHtml(message),
+ NotificationType.ERROR,
+ new NotificationListener.Adapter() {
+ @Override
+ protected void hyperlinkActivated(
+ Notification notification, HyperlinkEvent hyperlinkEvent) {
+ notification.expire();
+ projectViewEditor.consume(project);
+ }
+ });
+ notification.setImportant(true);
+ ApplicationManager.getApplication().invokeLater(() -> notification.notify(project));
+ }
+
+ /** Halve the previous shard size and resync. */
+ private static void reduceShardSizeAndResync(Project project) {
+ int previousShardSize =
+ BlazeBuildTargetSharder.getTargetShardSize(
+ ProjectViewManager.getInstance(project).getProjectViewSet());
+ editProjectViewAndResync(
+ project,
+ builder -> {
+ ScalarSection<Integer> existingSection = builder.getLast(TargetShardSizeSection.KEY);
+ builder.replace(
+ existingSection,
+ ScalarSection.builder(TargetShardSizeSection.KEY).set(previousShardSize / 2));
+ return true;
+ });
+ }
+
+ private static void enableShardingAndResync(Project project) {
+ editProjectViewAndResync(
+ project,
+ builder -> {
+ ScalarSection<Boolean> existingSection = builder.getLast(ShardBlazeBuildsSection.KEY);
+ builder.replace(
+ existingSection, ScalarSection.builder(ShardBlazeBuildsSection.KEY).set(true));
+ return true;
+ });
+ }
+
+ private static void editProjectViewAndResync(Project project, ProjectViewEditor editor) {
+ ProjectViewEdit edit = ProjectViewEdit.editLocalProjectView(project, editor);
+ 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());
+ }
+}
diff --git a/base/src/com/google/idea/blaze/base/sync/sharding/SuggestEnablingShardingNotification.java b/base/src/com/google/idea/blaze/base/sync/sharding/SuggestEnablingShardingNotification.java
deleted file mode 100644
index 71f69c0..0000000
--- a/base/src/com/google/idea/blaze/base/sync/sharding/SuggestEnablingShardingNotification.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright 2017 The Bazel Authors. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.sharding;
-
-import com.google.idea.blaze.base.projectview.ProjectViewEdit;
-import com.google.idea.blaze.base.projectview.section.ScalarSection;
-import com.google.idea.blaze.base.projectview.section.sections.ShardBlazeBuildsSection;
-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.BlazeUserSettings;
-import com.google.idea.blaze.base.sync.BlazeSyncManager;
-import com.google.idea.blaze.base.sync.BlazeSyncParams;
-import com.intellij.notification.Notification;
-import com.intellij.notification.NotificationListener;
-import com.intellij.notification.NotificationType;
-import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.ui.Messages;
-import com.intellij.pom.NavigatableAdapter;
-import com.intellij.xml.util.XmlStringUtil;
-import javax.swing.event.HyperlinkEvent;
-
-/** If blaze runs out of memory during sync, suggest that the user enables build sharding. */
-public class SuggestEnablingShardingNotification {
-
- public static void suggestSharding(Project project, BlazeContext context) {
- if (!BlazeBuildTargetSharder.canEnableSharding(project)) {
- return;
- }
- String buildSystem = Blaze.buildSystemName(project);
- String message =
- String.format(
- "The %1$s server ran out of memory during sync. This can occur for large projects. You "
- + "can workaround this by <a href='fix'>sharding the %1$s build during sync</a>, "
- + "or alternatively allocate more memory to %1$s",
- buildSystem);
- IssueOutput.error(message)
- .navigatable(
- new NavigatableAdapter() {
- @Override
- public void navigate(boolean requestFocus) {
- enableShardingAndResync(project);
- }
- })
- .submit(context);
-
- Notification notification =
- new Notification(
- "Out of memory during sync",
- buildSystem + " ran out of memory during sync",
- XmlStringUtil.wrapInHtml(message),
- NotificationType.ERROR,
- new NotificationListener.Adapter() {
- @Override
- protected void hyperlinkActivated(
- Notification notification, HyperlinkEvent hyperlinkEvent) {
- notification.expire();
- enableShardingAndResync(project);
- }
- });
- notification.setImportant(true);
- ApplicationManager.getApplication().invokeLater(() -> notification.notify(project));
- }
-
- private static void enableShardingAndResync(Project project) {
- ProjectViewEdit edit =
- ProjectViewEdit.editLocalProjectView(
- project,
- builder -> {
- ScalarSection<Boolean> existingSection = builder.getLast(ShardBlazeBuildsSection.KEY);
- builder.replace(
- existingSection, ScalarSection.builder(ShardBlazeBuildsSection.KEY).set(true));
- 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());
- }
-}
diff --git a/base/src/com/google/idea/blaze/base/sync/sharding/WildcardTargetExpander.java b/base/src/com/google/idea/blaze/base/sync/sharding/WildcardTargetExpander.java
index 396c1cf..10ab8aa 100644
--- a/base/src/com/google/idea/blaze/base/sync/sharding/WildcardTargetExpander.java
+++ b/base/src/com/google/idea/blaze/base/sync/sharding/WildcardTargetExpander.java
@@ -21,11 +21,11 @@
import com.google.idea.blaze.base.async.FutureUtil;
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.bazel.BuildSystemProvider;
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.model.primitives.Kind;
import com.google.idea.blaze.base.model.primitives.TargetExpression;
import com.google.idea.blaze.base.model.primitives.WorkspacePath;
@@ -36,6 +36,7 @@
import com.google.idea.blaze.base.scope.output.StatusOutput;
import com.google.idea.blaze.base.settings.Blaze;
import com.google.idea.blaze.base.sync.aspects.BuildResult;
+import com.google.idea.blaze.base.sync.aspects.BuildResult.Status;
import com.google.idea.blaze.base.sync.projectview.LanguageSupport;
import com.google.idea.blaze.base.sync.sharding.QueryResultLineProcessor.RuleTypeAndLabel;
import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver;
@@ -108,7 +109,7 @@
PackageLister.getDirectoriesToPrefetch(pathResolver, includes, excludePredicate);
ListenableFuture<?> prefetchFuture =
- PrefetchService.getInstance().prefetchFiles(project, toPrefetch);
+ PrefetchService.getInstance().prefetchFiles(project, toPrefetch, false);
if (!FutureUtil.waitForFuture(context, prefetchFuture)
.withProgressMessage("Prefetching wildcard target pattern directories...")
.timed("PrefetchingWildcardTargetDirectories")
@@ -144,7 +145,7 @@
ExpandedTargetsResult result =
queryIndividualTargets(project, context, workspaceRoot, handledRuleTypes, shard);
output = output == null ? result : ExpandedTargetsResult.merge(output, result);
- if (output.buildResult == BuildResult.FATAL_ERROR) {
+ if (output.buildResult.status == Status.FATAL_ERROR) {
return output;
}
}
@@ -158,6 +159,11 @@
WorkspaceRoot workspaceRoot,
ImmutableSet<String> handledRuleTypes,
List<TargetExpression> targetPatterns) {
+ String query = queryString(targetPatterns);
+ if (query.isEmpty()) {
+ // will be empty if there are no non-excluded targets
+ return new ExpandedTargetsResult(ImmutableList.of(), BuildResult.SUCCESS);
+ }
BlazeCommand.Builder builder =
BlazeCommand.builder(getBinaryPath(project), BlazeCommandName.QUERY)
.addBlazeFlags(BlazeFlags.KEEP_GOING)
@@ -179,7 +185,9 @@
.addBlazeCommand(builder.build())
.context(context)
.stdout(LineProcessingOutputStream.of(new QueryResultLineProcessor(output, filter)))
- .stderr(LineProcessingOutputStream.of(new PrintOutputLineProcessor(context)))
+ .stderr(
+ LineProcessingOutputStream.of(
+ new IssueOutputLineProcessor(project, context, workspaceRoot)))
.build()
.run();
diff --git a/base/src/com/google/idea/blaze/base/ui/UiUtil.java b/base/src/com/google/idea/blaze/base/ui/UiUtil.java
index 9f28ccb..ca4f65e 100644
--- a/base/src/com/google/idea/blaze/base/ui/UiUtil.java
+++ b/base/src/com/google/idea/blaze/base/ui/UiUtil.java
@@ -38,7 +38,7 @@
}
/** Puts all the given components in order in a box, aligned left. */
- public static Box createBox(@NotNull Iterable<Component> components) {
+ public static Box createBox(@NotNull Iterable<? extends Component> components) {
Box box = Box.createVerticalBox();
box.setAlignmentX(0);
for (Component component : components) {
diff --git a/base/src/com/google/idea/blaze/base/util/SerializationUtil.java b/base/src/com/google/idea/blaze/base/util/SerializationUtil.java
index ea689da..ca03904 100644
--- a/base/src/com/google/idea/blaze/base/util/SerializationUtil.java
+++ b/base/src/com/google/idea/blaze/base/util/SerializationUtil.java
@@ -26,13 +26,16 @@
import java.io.ObjectStreamClass;
import java.io.Serializable;
import javax.annotation.Nullable;
-import org.jetbrains.annotations.NotNull;
/** Utils for serialization. */
public class SerializationUtil {
- public static void saveToDisk(@NotNull File file, @NotNull Serializable serializable)
- throws IOException {
+ /**
+ * Write {@link Serializable} to disk.
+ *
+ * @throws IOException if serialization fails.
+ */
+ public static void saveToDisk(File file, Serializable serializable) throws IOException {
ensureExists(file.getParentFile());
FileOutputStream fos = null;
try {
@@ -48,48 +51,46 @@
}
}
+ /**
+ * Read the serialized objects from disk. Returns null if the file doesn't exist or is empty.
+ *
+ * @throws IOException if deserialization fails.
+ */
@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
- }
+ public static Object loadFromDisk(File file, final Iterable<ClassLoader> classLoaders)
+ throws IOException {
+ if (!file.exists()) {
+ return null;
+ }
+ try (FileInputStream 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);
- }
+ return super.resolveClass(desc);
+ }
+ };
+ try {
+ return ois.readObject();
} finally {
- Closeables.close(fin, false);
+ Closeables.close(ois, false);
}
- } catch (ClassNotFoundException e) {
- throw new IOException(e);
- } catch (ClassCastException e) {
+ } catch (ClassNotFoundException | ClassCastException | IllegalStateException e) {
+ // rethrow as an IOException, handled by callers
throw new IOException(e);
}
}
- private static void ensureExists(@NotNull File dir) throws IOException {
+ 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/base/src/com/google/idea/blaze/base/util/UrlUtil.java b/base/src/com/google/idea/blaze/base/util/UrlUtil.java
index a2cc7b2..0567319 100644
--- a/base/src/com/google/idea/blaze/base/util/UrlUtil.java
+++ b/base/src/com/google/idea/blaze/base/util/UrlUtil.java
@@ -17,10 +17,12 @@
import com.google.idea.blaze.base.io.VirtualFileSystemProvider;
import com.intellij.openapi.util.io.FileUtil;
-import com.intellij.openapi.util.io.FileUtilRt;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.util.io.URLUtil;
import java.io.File;
+import javax.annotation.Nullable;
/** Utility methods for converting between URLs and file paths. */
public class UrlUtil {
@@ -30,7 +32,7 @@
}
public static String fileToIdeaUrl(File path) {
- return pathToUrl(toSystemIndependentName(path.getPath()));
+ return pathToUrl(FileUtil.toSystemIndependentName(path.getPath()));
}
public static String pathToUrl(String filePath) {
@@ -45,7 +47,16 @@
}
}
- private static String toSystemIndependentName(String aFileName) {
- return FileUtilRt.toSystemIndependentName(aFileName);
+ /**
+ * Returns the local file path associated with the given URL, or null if it doesn't refer to a
+ * local file.
+ */
+ @Nullable
+ public static String urlToFilePath(@Nullable String url) {
+ if (url == null || !url.startsWith(LocalFileSystem.PROTOCOL_PREFIX)) {
+ return null;
+ }
+ return FileUtil.toSystemDependentName(
+ StringUtil.trimStart(url, LocalFileSystem.PROTOCOL_PREFIX));
}
}
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
index 4e58d0d..9743cad 100644
--- a/base/src/com/google/idea/blaze/base/vcs/git/GitBlazeVcsHandler.java
+++ b/base/src/com/google/idea/blaze/base/vcs/git/GitBlazeVcsHandler.java
@@ -61,7 +61,7 @@
if (upstreamSha == null) {
return null;
}
- return GitWorkingSetProvider.calculateWorkingSet(workspaceRoot, upstreamSha);
+ return GitWorkingSetProvider.calculateWorkingSet(workspaceRoot, upstreamSha, context);
});
}
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
index 465c6ac..1fd70f4 100644
--- a/base/src/com/google/idea/blaze/base/vcs/git/GitWorkingSetProvider.java
+++ b/base/src/com/google/idea/blaze/base/vcs/git/GitWorkingSetProvider.java
@@ -21,6 +21,8 @@
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.scope.BlazeContext;
+import com.google.idea.blaze.base.scope.scopes.TimingScope;
import com.google.idea.blaze.base.sync.workspace.WorkingSet;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.text.StringUtil;
@@ -40,7 +42,8 @@
* Returns null if an error occurred.
*/
@Nullable
- public static WorkingSet calculateWorkingSet(WorkspaceRoot workspaceRoot, String upstreamSha) {
+ public static WorkingSet calculateWorkingSet(
+ WorkspaceRoot workspaceRoot, String upstreamSha, BlazeContext context) {
String gitRoot = getConsoleOutput(workspaceRoot, "git", "rev-parse", "--show-toplevel");
if (gitRoot == null) {
@@ -53,10 +56,11 @@
int retVal =
ExternalTask.builder(workspaceRoot)
.args("git", "diff", "--name-status", "--no-renames", upstreamSha)
+ .context(context)
.stdout(LineProcessingOutputStream.of(processor))
.stderr(stderr)
.build()
- .run();
+ .run(new TimingScope("GitDiff"));
if (retVal != 0) {
logger.error(stderr);
return null;
diff --git a/base/src/com/google/idea/blaze/base/wizard2/BazelWizardOptionProvider.java b/base/src/com/google/idea/blaze/base/wizard2/BazelWizardOptionProvider.java
index 9e62e78..99a6b1d 100644
--- a/base/src/com/google/idea/blaze/base/wizard2/BazelWizardOptionProvider.java
+++ b/base/src/com/google/idea/blaze/base/wizard2/BazelWizardOptionProvider.java
@@ -16,6 +16,7 @@
package com.google.idea.blaze.base.wizard2;
import com.google.common.collect.ImmutableList;
+import com.intellij.openapi.Disposable;
import java.util.Collection;
/** Provides bazel options for the wizard. */
@@ -23,7 +24,7 @@
@Override
public Collection<BlazeSelectWorkspaceOption> getSelectWorkspaceOptions(
- BlazeNewProjectBuilder builder) {
+ BlazeNewProjectBuilder builder, Disposable parentDisposable) {
return ImmutableList.of(new UseExistingBazelWorkspaceOption(builder));
}
diff --git a/base/src/com/google/idea/blaze/base/wizard2/BlazeWizardOptionProvider.java b/base/src/com/google/idea/blaze/base/wizard2/BlazeWizardOptionProvider.java
index 1739755..904e0e8 100644
--- a/base/src/com/google/idea/blaze/base/wizard2/BlazeWizardOptionProvider.java
+++ b/base/src/com/google/idea/blaze/base/wizard2/BlazeWizardOptionProvider.java
@@ -15,12 +15,14 @@
*/
package com.google.idea.blaze.base.wizard2;
+import com.intellij.openapi.Disposable;
import com.intellij.openapi.components.ServiceManager;
import java.util.Collection;
/** Provides options during the import process. */
public interface BlazeWizardOptionProvider {
- Collection<BlazeSelectWorkspaceOption> getSelectWorkspaceOptions(BlazeNewProjectBuilder builder);
+ Collection<BlazeSelectWorkspaceOption> getSelectWorkspaceOptions(
+ BlazeNewProjectBuilder builder, Disposable parentDisposable);
Collection<BlazeSelectProjectViewOption> getSelectProjectViewOptions(
BlazeNewProjectBuilder builder);
diff --git a/base/src/com/google/idea/blaze/base/wizard2/CopyExternalProjectViewOption.java b/base/src/com/google/idea/blaze/base/wizard2/CopyExternalProjectViewOption.java
index ae00949..25a63e3 100644
--- a/base/src/com/google/idea/blaze/base/wizard2/CopyExternalProjectViewOption.java
+++ b/base/src/com/google/idea/blaze/base/wizard2/CopyExternalProjectViewOption.java
@@ -86,6 +86,9 @@
if (!file.exists()) {
return BlazeValidationResult.failure("Project view file does not exist.");
}
+ if (file.isDirectory()) {
+ return BlazeValidationResult.failure("Specified path is a directory, not a file");
+ }
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
index 577f290..f445e44 100644
--- a/base/src/com/google/idea/blaze/base/wizard2/GenerateFromBuildFileSelectProjectViewOption.java
+++ b/base/src/com/google/idea/blaze/base/wizard2/GenerateFromBuildFileSelectProjectViewOption.java
@@ -110,6 +110,9 @@
if (!file.exists()) {
return BlazeValidationResult.failure("BUILD file does not exist.");
}
+ if (file.isDirectory()) {
+ return BlazeValidationResult.failure("Specified path is a directory, not a file");
+ }
BuildSystemProvider buildSystemProvider =
BuildSystemProvider.getBuildSystemProvider(builder.getBuildSystem());
checkState(buildSystemProvider != null);
diff --git a/base/src/com/google/idea/blaze/base/wizard2/ImportFromWorkspaceProjectViewOption.java b/base/src/com/google/idea/blaze/base/wizard2/ImportFromWorkspaceProjectViewOption.java
index 14a07e0..c727a45 100644
--- a/base/src/com/google/idea/blaze/base/wizard2/ImportFromWorkspaceProjectViewOption.java
+++ b/base/src/com/google/idea/blaze/base/wizard2/ImportFromWorkspaceProjectViewOption.java
@@ -98,6 +98,9 @@
if (!file.exists()) {
return BlazeValidationResult.failure("Project view file does not exist.");
}
+ if (file.isDirectory()) {
+ return BlazeValidationResult.failure("Specified path is a directory, not a file");
+ }
return BlazeValidationResult.success();
}
diff --git a/base/src/com/google/idea/blaze/base/wizard2/ui/BlazeEditProjectViewControl.java b/base/src/com/google/idea/blaze/base/wizard2/ui/BlazeEditProjectViewControl.java
index 5b652d8..e5d0bc7 100644
--- a/base/src/com/google/idea/blaze/base/wizard2/ui/BlazeEditProjectViewControl.java
+++ b/base/src/com/google/idea/blaze/base/wizard2/ui/BlazeEditProjectViewControl.java
@@ -70,18 +70,20 @@
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.components.JBLabel;
import com.intellij.util.SystemProperties;
+import java.awt.BorderLayout;
import java.awt.Component;
-import java.awt.Dimension;
import java.awt.GridBagLayout;
import java.io.File;
import java.io.IOException;
import java.util.Comparator;
import java.util.List;
import javax.annotation.Nullable;
+import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
+import javax.swing.JScrollPane;
import javax.swing.JTextField;
import org.jetbrains.annotations.NotNull;
@@ -122,10 +124,14 @@
public BlazeEditProjectViewControl(BlazeNewProjectBuilder builder, Disposable parentDisposable) {
this.projectViewUi = new ProjectViewUi(parentDisposable);
- JPanel component = new JPanelProvidingProject(ProjectViewUi.getProject(), new GridBagLayout());
- fillUi(component);
+ JPanel content = new JPanel(new GridBagLayout());
+ fillUi(content);
update(builder);
- UiUtil.fillBottom(component);
+ UiUtil.fillBottom(content);
+ JScrollPane scrollPane = new JScrollPane(content);
+ scrollPane.setBorder(BorderFactory.createEmptyBorder());
+ JPanel component = new JPanelProvidingProject(ProjectViewUi.getProject(), new BorderLayout());
+ component.add(scrollPane);
this.component = component;
this.buildSystemName = builder.getBuildSystemName();
}
@@ -137,11 +143,7 @@
private void fillUi(JPanel canvas) {
JLabel projectDataDirLabel = new JBLabel("Project data directory:");
- Dimension minSize = ProjectViewUi.getMinimumSize();
- // Add pixels so we have room for our extra fields
- minSize.setSize(minSize.width, minSize.height + 180);
- canvas.setMinimumSize(minSize);
- canvas.setPreferredSize(minSize);
+ canvas.setPreferredSize(ProjectViewUi.getContainerSize());
projectDataDirField = new TextFieldWithBrowseButton();
projectDataDirField.addBrowseFolderListener(
@@ -491,7 +493,6 @@
return BlazeValidationResult.failure(msg);
}
-
return BlazeValidationResult.success();
}
@@ -528,9 +529,6 @@
}
WorkspaceLanguageSettings workspaceLanguageSettings =
LanguageSupport.createWorkspaceLanguageSettings(projectViewSet);
- if (workspaceLanguageSettings == null) {
- return false;
- }
return ProjectViewVerifier.verifyProjectView(
null, context, workspacePathResolver, projectViewSet, workspaceLanguageSettings);
}
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
index 69a36e4..132f2dd 100644
--- a/base/src/com/google/idea/blaze/base/wizard2/ui/BlazeSelectOptionControl.java
+++ b/base/src/com/google/idea/blaze/base/wizard2/ui/BlazeSelectOptionControl.java
@@ -25,13 +25,15 @@
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.awt.BorderLayout;
import java.util.Collection;
+import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
+import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.border.EmptyBorder;
@@ -61,10 +63,9 @@
this.userSettings = builder.getUserSettings();
- JPanel canvas = new JPanel(new VerticalLayout(4));
+ JPanel canvas = new JPanel(new BorderLayout(0, 4));
- Dimension minSize = ProjectViewUi.getMinimumSize();
- canvas.setPreferredSize(minSize);
+ canvas.setPreferredSize(ProjectViewUi.getContainerSize());
titleLabel = new JLabel(getTitle());
canvas.add(titleLabel);
@@ -72,7 +73,9 @@
JPanel content = new JPanel(new VerticalLayout(12));
content.setBorder(new EmptyBorder(20, 100, 0, 0));
- canvas.add(content);
+ JScrollPane scrollPane = new JScrollPane(content);
+ scrollPane.setBorder(BorderFactory.createEmptyBorder());
+ canvas.add(scrollPane);
ButtonGroup buttonGroup = new ButtonGroup();
Collection<OptionUiEntry<T>> optionUiEntryList = Lists.newArrayList();
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
index e24b446..62b2a7c 100644
--- a/base/src/com/google/idea/blaze/base/wizard2/ui/BlazeSelectWorkspaceControl.java
+++ b/base/src/com/google/idea/blaze/base/wizard2/ui/BlazeSelectWorkspaceControl.java
@@ -19,6 +19,7 @@
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 com.intellij.openapi.Disposable;
import java.util.Collection;
import javax.swing.JComponent;
@@ -26,9 +27,10 @@
public class BlazeSelectWorkspaceControl {
BlazeSelectOptionControl<BlazeSelectWorkspaceOption> selectOptionControl;
- public BlazeSelectWorkspaceControl(BlazeNewProjectBuilder builder) {
+ public BlazeSelectWorkspaceControl(BlazeNewProjectBuilder builder, Disposable parentDisposable) {
Collection<BlazeSelectWorkspaceOption> options =
- BlazeWizardOptionProvider.getInstance().getSelectWorkspaceOptions(builder);
+ BlazeWizardOptionProvider.getInstance()
+ .getSelectWorkspaceOptions(builder, parentDisposable);
this.selectOptionControl =
new BlazeSelectOptionControl<BlazeSelectWorkspaceOption>(builder, options) {
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/actions/BuildFileModifierTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/actions/BuildFileModifierTest.java
index 691dc74..2790cea 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/actions/BuildFileModifierTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/actions/BuildFileModifierTest.java
@@ -22,6 +22,8 @@
import com.google.idea.blaze.base.model.primitives.Label;
import com.google.idea.blaze.base.model.primitives.WorkspacePath;
import com.google.idea.blaze.base.scope.BlazeContext;
+import com.intellij.openapi.command.WriteCommandAction;
+import com.intellij.openapi.util.Computable;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -34,8 +36,16 @@
public void testAddNewTarget() {
BuildFile buildFile =
createBuildFile(new WorkspacePath("BUILD"), "java_library(name = 'existing')", "");
- BuildFileModifier.getInstance()
- .addRule(getProject(), new BlazeContext(), Label.create("//:new_target"), Kind.JAVA_TEST);
+ WriteCommandAction.runWriteCommandAction(
+ getProject(),
+ (Computable<Boolean>)
+ () ->
+ BuildFileModifier.getInstance()
+ .addRule(
+ getProject(),
+ new BlazeContext(),
+ Label.create("//:new_target"),
+ Kind.JAVA_TEST));
assertFileContents(
buildFile,
"java_library(name = 'existing')",
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/run/producer/BlazeBuildFileRunConfigurationProducerTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/run/producer/BlazeBuildFileRunConfigurationProducerTest.java
index dcf3e6b..3e67be8 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/run/producer/BlazeBuildFileRunConfigurationProducerTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/run/producer/BlazeBuildFileRunConfigurationProducerTest.java
@@ -17,14 +17,18 @@
import static com.google.common.truth.Truth.assertThat;
+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.lang.buildfile.psi.FuncallExpression;
import com.google.idea.blaze.base.lang.buildfile.psi.StringLiteral;
import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
+import com.google.idea.blaze.base.model.primitives.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.run.BlazeCommandRunConfiguration;
import com.google.idea.blaze.base.run.producers.BlazeBuildFileRunConfigurationProducer;
+import com.google.idea.blaze.base.run.state.BlazeCommandRunConfigurationCommonState;
import com.intellij.execution.actions.ConfigurationContext;
import com.intellij.execution.actions.ConfigurationFromContext;
import com.intellij.psi.PsiFile;
@@ -87,4 +91,68 @@
.isEqualTo(TargetExpression.fromString("//java/com/google/test:unit_tests"));
assertThat(getCommandType(config)).isEqualTo(BlazeCommandName.TEST);
}
+
+ @Test
+ public void testConfigFromContextRecognizesItsOwnConfig() {
+ PsiFile buildFile =
+ workspace.createPsiFile(
+ new WorkspacePath("java/com/google/test/BUILD"), "java_test(name='unit_tests'");
+
+ StringLiteral nameString =
+ PsiUtils.findFirstChildOfClassRecursive(buildFile, StringLiteral.class);
+ ConfigurationContext context = createContextFromPsi(nameString);
+ BlazeCommandRunConfiguration config =
+ (BlazeCommandRunConfiguration) context.getConfiguration().getConfiguration();
+
+ assertThat(
+ new BlazeBuildFileRunConfigurationProducer()
+ .isConfigurationFromContext(config, context))
+ .isTrue();
+ }
+
+ @Test
+ public void testConfigWithDifferentLabelIgnored() {
+ PsiFile buildFile =
+ workspace.createPsiFile(
+ new WorkspacePath("java/com/google/test/BUILD"), "java_test(name='unit_tests'");
+
+ StringLiteral nameString =
+ PsiUtils.findFirstChildOfClassRecursive(buildFile, StringLiteral.class);
+ ConfigurationContext context = createContextFromPsi(nameString);
+ BlazeCommandRunConfiguration config =
+ (BlazeCommandRunConfiguration) context.getConfiguration().getConfiguration();
+
+ // modify the label, and check that is enough for the producer to class it as different.
+ config.setTarget(Label.create("//java/com/google/test:integration_tests"));
+
+ assertThat(
+ new BlazeBuildFileRunConfigurationProducer()
+ .isConfigurationFromContext(config, context))
+ .isFalse();
+ }
+
+ @Test
+ public void testConfigWithTestFilterIgnored() {
+ PsiFile buildFile =
+ workspace.createPsiFile(
+ new WorkspacePath("java/com/google/test/BUILD"), "java_test(name='unit_tests'");
+
+ StringLiteral nameString =
+ PsiUtils.findFirstChildOfClassRecursive(buildFile, StringLiteral.class);
+ ConfigurationContext context = createContextFromPsi(nameString);
+ BlazeCommandRunConfiguration config =
+ (BlazeCommandRunConfiguration) context.getConfiguration().getConfiguration();
+
+ BlazeCommandRunConfigurationCommonState handlerState =
+ config.getHandlerStateIfType(BlazeCommandRunConfigurationCommonState.class);
+ handlerState
+ .getBlazeFlagsState()
+ .setRawFlags(
+ ImmutableList.of(BlazeFlags.TEST_FILTER + "=com.google.test.SingleTestClass#"));
+
+ assertThat(
+ new BlazeBuildFileRunConfigurationProducer()
+ .isConfigurationFromContext(config, context))
+ .isFalse();
+ }
}
diff --git a/base/tests/unittests/com/google/idea/blaze/base/actions/BlazeBuildServiceTest.java b/base/tests/unittests/com/google/idea/blaze/base/actions/BlazeBuildServiceTest.java
index 23e40c8..18fed93 100644
--- a/base/tests/unittests/com/google/idea/blaze/base/actions/BlazeBuildServiceTest.java
+++ b/base/tests/unittests/com/google/idea/blaze/base/actions/BlazeBuildServiceTest.java
@@ -107,6 +107,22 @@
verify(service).buildTargetExpressions(eq(project), eq(targets), eq(viewSet), any());
}
+ @Test
+ public void testLastBuildProjectTimestampIsNull() {
+ assertThat(BlazeBuildService.getLastBuildTimeStamp(project)).isNull();
+ }
+
+ @Test
+ public void testGetLastBuildProjectTimestamp() {
+ long beforeTime = System.currentTimeMillis() - 1;
+ service.buildProject(project);
+ long afterTime = System.currentTimeMillis() + 1;
+ Long timestamp = BlazeBuildService.getLastBuildTimeStamp(project);
+ assertThat(timestamp).isNotNull();
+ assertThat(timestamp).isGreaterThan(beforeTime);
+ assertThat(timestamp).isLessThan(afterTime);
+ }
+
private static class MockProjectViewManager extends ProjectViewManager {
private final ProjectViewSet viewSet;
diff --git a/base/tests/unittests/com/google/idea/blaze/base/bazel/BazelVersionTest.java b/base/tests/unittests/com/google/idea/blaze/base/bazel/BazelVersionTest.java
index 5860e40..673f810 100644
--- a/base/tests/unittests/com/google/idea/blaze/base/bazel/BazelVersionTest.java
+++ b/base/tests/unittests/com/google/idea/blaze/base/bazel/BazelVersionTest.java
@@ -53,9 +53,10 @@
}
@Test
- public void testParseVersionFormatManualOld() {
+ public void testParseDevelopmentVersion() {
BazelVersion version = BazelVersion.parseVersion("development version");
- assertThat(version).isEqualTo(BazelVersion.UNKNOWN);
+ assertThat(version).isEqualTo(BazelVersion.DEVELOPMENT);
+ assertThat(version.isAtLeast(9, 9, 9)).isTrue();
}
@Test
diff --git a/base/tests/unittests/com/google/idea/blaze/base/command/buildresult/BuildEventProtocolOutputReaderTest.java b/base/tests/unittests/com/google/idea/blaze/base/command/buildresult/BuildEventProtocolOutputReaderTest.java
new file mode 100644
index 0000000..7026b1d
--- /dev/null
+++ b/base/tests/unittests/com/google/idea/blaze/base/command/buildresult/BuildEventProtocolOutputReaderTest.java
@@ -0,0 +1,412 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.buildresult;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.idea.common.guava.GuavaHelper.toImmutableList;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+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.testlogs.BlazeTestResult;
+import com.google.idea.blaze.base.run.testlogs.BlazeTestResult.TestStatus;
+import com.google.idea.blaze.base.run.testlogs.BlazeTestResults;
+import com.google.repackaged.devtools.build.lib.buildeventstream.BuildEventStreamProtos;
+import com.google.repackaged.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildEvent;
+import com.google.repackaged.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildEventId;
+import com.google.repackaged.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildEventId.TargetCompletedId;
+import com.google.repackaged.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildEventId.TargetConfiguredId;
+import com.google.repackaged.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildEventId.TestResultId;
+import com.google.repackaged.devtools.build.lib.buildeventstream.BuildEventStreamProtos.NamedSetOfFiles;
+import com.google.repackaged.devtools.build.lib.buildeventstream.BuildEventStreamProtos.TargetComplete;
+import com.google.repackaged.devtools.build.lib.buildeventstream.BuildEventStreamProtos.TargetConfigured;
+import com.google.repackaged.devtools.build.lib.buildeventstream.BuildEventStreamProtos.TestResult;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link BuildEventProtocolOutputReader}. */
+@RunWith(JUnit4.class)
+public class BuildEventProtocolOutputReaderTest {
+
+ @Rule public TemporaryFolder tmpFolder = new TemporaryFolder();
+
+ @Test
+ public void parseAllOutputFilenames_singleFileEvent_returnsAllFilenames() throws IOException {
+ ImmutableList<String> filePaths =
+ ImmutableList.of(
+ "/usr/local/lib/File.py", "/usr/bin/python2.7", "/usr/local/home/script.sh");
+ BuildEvent.Builder event = BuildEvent.newBuilder().setNamedSetOfFiles(setOfFiles(filePaths));
+
+ ImmutableList<File> parsedFilenames =
+ BuildEventProtocolOutputReader.parseAllOutputFilenames(asInputStream(event), path -> true);
+
+ assertThat(parsedFilenames)
+ .containsExactly(filePaths.stream().map(File::new).toArray())
+ .inOrder();
+ }
+
+ @Test
+ public void parseAllOutputFilenamesWithFilter_singleFileEvent_returnsFilteredFilenames()
+ throws IOException {
+ Predicate<String> filter = path -> path.endsWith(".py");
+ ImmutableList<String> filePaths =
+ ImmutableList.of(
+ "/usr/local/lib/File.py", "/usr/bin/python2.7", "/usr/local/home/script.sh");
+ BuildEvent.Builder event = BuildEvent.newBuilder().setNamedSetOfFiles(setOfFiles(filePaths));
+
+ ImmutableList<File> parsedFilenames =
+ BuildEventProtocolOutputReader.parseAllOutputFilenames(asInputStream(event), filter);
+
+ assertThat(parsedFilenames).containsExactly(new File("/usr/local/lib/File.py"));
+ }
+
+ @Test
+ public void parseAllOutputFilenames_nonFileEvent_returnsEmptyList() throws IOException {
+ BuildEvent.Builder targetFinishedEvent =
+ BuildEvent.newBuilder()
+ .setCompleted(BuildEventStreamProtos.TargetComplete.getDefaultInstance());
+
+ ImmutableList<File> parsedFilenames =
+ BuildEventProtocolOutputReader.parseAllOutputFilenames(
+ asInputStream(targetFinishedEvent), path -> true);
+
+ assertThat(parsedFilenames).isEmpty();
+ }
+
+ @Test
+ public void parseAllOutputFilenames_streamWithOneFileEvent_returnsAllFilenames()
+ throws IOException {
+ ImmutableList<String> filePaths =
+ ImmutableList.of(
+ "/usr/local/lib/Provider.java",
+ "/usr/local/home/Executor.java",
+ "/google/code/script.sh");
+ List<BuildEvent.Builder> events =
+ ImmutableList.of(
+ BuildEvent.newBuilder()
+ .setStarted(BuildEventStreamProtos.BuildStarted.getDefaultInstance()),
+ BuildEvent.newBuilder()
+ .setProgress(BuildEventStreamProtos.Progress.getDefaultInstance()),
+ BuildEvent.newBuilder().setNamedSetOfFiles(setOfFiles(filePaths)));
+
+ ImmutableList<File> parsedFilenames =
+ BuildEventProtocolOutputReader.parseAllOutputFilenames(asInputStream(events), path -> true);
+
+ assertThat(parsedFilenames)
+ .containsExactly(filePaths.stream().map(File::new).toArray())
+ .inOrder();
+ }
+
+ @Test
+ public void parseAllOutputFilenames_streamWithMultipleFileEvents_returnsAllFilenames()
+ throws IOException {
+ ImmutableList<String> fileSet1 =
+ ImmutableList.of(
+ "/usr/local/lib/Provider.java",
+ "/usr/local/home/Executor.java",
+ "/google/code/script.sh");
+
+ ImmutableList<String> fileSet2 =
+ ImmutableList.of(
+ "/usr/local/code/ParserTest.java",
+ "/usr/local/code/action_output.bzl",
+ "/usr/genfiles/BUILD.bazel");
+
+ List<BuildEvent.Builder> events =
+ ImmutableList.of(
+ BuildEvent.newBuilder()
+ .setStarted(BuildEventStreamProtos.BuildStarted.getDefaultInstance()),
+ BuildEvent.newBuilder()
+ .setProgress(BuildEventStreamProtos.Progress.getDefaultInstance()),
+ BuildEvent.newBuilder().setNamedSetOfFiles(setOfFiles(fileSet1)),
+ BuildEvent.newBuilder()
+ .setProgress(BuildEventStreamProtos.Progress.getDefaultInstance()),
+ BuildEvent.newBuilder().setNamedSetOfFiles(setOfFiles(fileSet2)),
+ BuildEvent.newBuilder()
+ .setCompleted(BuildEventStreamProtos.TargetComplete.getDefaultInstance()));
+
+ ImmutableList<File> allFiles =
+ ImmutableList.<String>builder()
+ .addAll(fileSet1)
+ .addAll(fileSet2)
+ .build()
+ .stream()
+ .map(File::new)
+ .collect(toImmutableList());
+
+ ImmutableList<File> parsedFilenames =
+ BuildEventProtocolOutputReader.parseAllOutputFilenames(asInputStream(events), path -> true);
+
+ assertThat(parsedFilenames).containsExactlyElementsIn(allFiles).inOrder();
+ }
+
+ @Test
+ public void testStatusEnum_handlesAllProtoEnumValues() {
+ Set<String> protoValues =
+ EnumSet.allOf(BuildEventStreamProtos.TestStatus.class)
+ .stream()
+ .map(Enum::name)
+ .collect(Collectors.toSet());
+ protoValues.remove(BuildEventStreamProtos.TestStatus.UNRECOGNIZED.name());
+ Set<String> handledValues =
+ EnumSet.allOf(TestStatus.class).stream().map(Enum::name).collect(Collectors.toSet());
+
+ assertThat(protoValues).containsExactlyElementsIn(handledValues);
+ }
+
+ @Test
+ public void parseTestResults_singleEvent_returnsTestResults() throws IOException {
+ Label label = Label.create("//java/com/google:unit_tests");
+ BuildEventStreamProtos.TestStatus status = BuildEventStreamProtos.TestStatus.FAILED;
+ ImmutableList<String> filePaths = ImmutableList.of("/usr/local/tmp/_cache/test_result.xml");
+ BuildEvent.Builder event = testResultEvent(label.toString(), status, filePaths);
+
+ BlazeTestResults results =
+ BuildEventProtocolOutputReader.parseTestResults(asInputStream(event));
+
+ assertThat(results.perTargetResults.keySet()).containsExactly(label);
+ assertThat(results.perTargetResults.get(label)).hasSize(1);
+ BlazeTestResult result = results.perTargetResults.get(label).iterator().next();
+ assertThat(result.getTestStatus()).isEqualTo(TestStatus.FAILED);
+ assertThat(result.getOutputXmlFiles())
+ .containsExactly(new File("/usr/local/tmp/_cache/test_result.xml"));
+ }
+
+ @Test
+ public void parseTestResults_singleTestEventWithTargetConfigured_resultsIncludeTargetKind()
+ throws IOException {
+ Label label = Label.create("//java/com/google:unit_tests");
+ BuildEventStreamProtos.TestStatus status = BuildEventStreamProtos.TestStatus.FAILED;
+ ImmutableList<String> filePaths = ImmutableList.of("/usr/local/tmp/_cache/test_result.xml");
+ InputStream events =
+ asInputStream(
+ targetConfiguredEvent(label.toString(), "java_test rule"),
+ testResultEvent(label.toString(), status, filePaths));
+
+ BlazeTestResults results = BuildEventProtocolOutputReader.parseTestResults(events);
+
+ assertThat(results.perTargetResults.keySet()).containsExactly(label);
+ assertThat(results.perTargetResults.get(label)).hasSize(1);
+ BlazeTestResult result = results.perTargetResults.get(label).iterator().next();
+ assertThat(result.getTargetKind()).isEqualTo(Kind.JAVA_TEST);
+ assertThat(result.getTestStatus()).isEqualTo(TestStatus.FAILED);
+ assertThat(result.getOutputXmlFiles())
+ .containsExactly(new File("/usr/local/tmp/_cache/test_result.xml"));
+ }
+
+ @Test
+ public void parseTestResults_singleTestEventWithTargetCompleted_resultsIncludeTargetKind()
+ throws IOException {
+ Label label = Label.create("//java/com/google:unit_tests");
+ BuildEventStreamProtos.TestStatus status = BuildEventStreamProtos.TestStatus.FAILED;
+ ImmutableList<String> filePaths = ImmutableList.of("/usr/local/tmp/_cache/test_result.xml");
+ InputStream events =
+ asInputStream(
+ targetCompletedEvent(label.toString(), "java_test rule"),
+ testResultEvent(label.toString(), status, filePaths));
+
+ BlazeTestResults results = BuildEventProtocolOutputReader.parseTestResults(events);
+
+ assertThat(results.perTargetResults.keySet()).containsExactly(label);
+ assertThat(results.perTargetResults.get(label)).hasSize(1);
+ BlazeTestResult result = results.perTargetResults.get(label).iterator().next();
+ assertThat(result.getTargetKind()).isEqualTo(Kind.JAVA_TEST);
+ assertThat(result.getTestStatus()).isEqualTo(TestStatus.FAILED);
+ assertThat(result.getOutputXmlFiles())
+ .containsExactly(new File("/usr/local/tmp/_cache/test_result.xml"));
+ }
+
+ @Test
+ public void parseTestResults_multipleTargetKindSources_resultsIncludeCorrectTargetKind()
+ throws IOException {
+ Label label = Label.create("//java/com/google:unit_tests");
+ BuildEventStreamProtos.TestStatus status = BuildEventStreamProtos.TestStatus.FAILED;
+ ImmutableList<String> filePaths = ImmutableList.of("/usr/local/tmp/_cache/test_result.xml");
+ InputStream events =
+ asInputStream(
+ targetConfiguredEvent(label.toString(), "java_test rule"),
+ targetCompletedEvent(label.toString(), "java_test rule"),
+ testResultEvent(label.toString(), status, filePaths));
+
+ BlazeTestResults results = BuildEventProtocolOutputReader.parseTestResults(events);
+
+ assertThat(results.perTargetResults.keySet()).containsExactly(label);
+ assertThat(results.perTargetResults.get(label)).hasSize(1);
+ BlazeTestResult result = results.perTargetResults.get(label).iterator().next();
+ assertThat(result.getTargetKind()).isEqualTo(Kind.JAVA_TEST);
+ assertThat(result.getTestStatus()).isEqualTo(TestStatus.FAILED);
+ assertThat(result.getOutputXmlFiles())
+ .containsExactly(new File("/usr/local/tmp/_cache/test_result.xml"));
+ }
+
+ @Test
+ public void parseTestResults_singleEvent_ignoresNonXmlOutputFiles() throws IOException {
+ Label label = Label.create("//java/com/google:unit_tests");
+ BuildEventStreamProtos.TestStatus status = BuildEventStreamProtos.TestStatus.FAILED;
+ ImmutableList<String> filePaths =
+ ImmutableList.of(
+ "/usr/local/tmp/_cache/test_result.xml",
+ "/usr/local/tmp/_cache/test_result.log",
+ "/usr/local/tmp/other_output_file");
+ BuildEvent.Builder event = testResultEvent(label.toString(), status, filePaths);
+
+ BlazeTestResults results =
+ BuildEventProtocolOutputReader.parseTestResults(asInputStream(event));
+
+ BlazeTestResult result = results.perTargetResults.get(label).iterator().next();
+ assertThat(result.getOutputXmlFiles())
+ .containsExactly(new File("/usr/local/tmp/_cache/test_result.xml"));
+ }
+
+ @Test
+ public void parseTestResults_singleTargetWithMultipleEvents_returnsTestResults()
+ throws IOException {
+ Label label = Label.create("//java/com/google:unit_tests");
+ BuildEventStreamProtos.TestStatus status = BuildEventStreamProtos.TestStatus.PASSED;
+ BuildEvent.Builder shard1 =
+ testResultEvent(
+ label.toString(), status, ImmutableList.of("/usr/local/tmp/_cache/shard1_of_2.xml"));
+ BuildEvent.Builder shard2 =
+ testResultEvent(
+ label.toString(), status, ImmutableList.of("/usr/local/tmp/_cache/shard2_of_2.xml"));
+
+ BlazeTestResults results =
+ BuildEventProtocolOutputReader.parseTestResults(asInputStream(shard1, shard2));
+
+ assertThat(results.perTargetResults).hasSize(2);
+ Collection<BlazeTestResult> targetResults = results.perTargetResults.get(label);
+ assertThat(targetResults)
+ .containsExactly(
+ BlazeTestResult.create(
+ label,
+ null,
+ TestStatus.PASSED,
+ ImmutableSet.of(new File("/usr/local/tmp/_cache/shard1_of_2.xml"))),
+ BlazeTestResult.create(
+ label,
+ null,
+ TestStatus.PASSED,
+ ImmutableSet.of(new File("/usr/local/tmp/_cache/shard2_of_2.xml"))));
+ }
+
+ @Test
+ public void parseTestResults_multipleEvents_returnsAllResults() throws IOException {
+ BuildEvent.Builder test1 =
+ testResultEvent(
+ "//java/com/google:Test1",
+ BuildEventStreamProtos.TestStatus.PASSED,
+ ImmutableList.of("/usr/local/tmp/_cache/test_result.xml"));
+ BuildEvent.Builder test2 =
+ testResultEvent(
+ "//java/com/google:Test2",
+ BuildEventStreamProtos.TestStatus.INCOMPLETE,
+ ImmutableList.of("/usr/local/tmp/_cache/second_result.xml"));
+
+ BlazeTestResults results =
+ BuildEventProtocolOutputReader.parseTestResults(asInputStream(test1, test2));
+
+ assertThat(results.perTargetResults).hasSize(2);
+ assertThat(results.perTargetResults.get(Label.create("//java/com/google:Test1"))).hasSize(1);
+ assertThat(results.perTargetResults.get(Label.create("//java/com/google:Test2"))).hasSize(1);
+ BlazeTestResult result1 =
+ results.perTargetResults.get(Label.create("//java/com/google:Test1")).iterator().next();
+ assertThat(result1.getTestStatus()).isEqualTo(TestStatus.PASSED);
+ assertThat(result1.getOutputXmlFiles())
+ .containsExactly(new File("/usr/local/tmp/_cache/test_result.xml"));
+ BlazeTestResult result2 =
+ results.perTargetResults.get(Label.create("//java/com/google:Test2")).iterator().next();
+ assertThat(result2.getTestStatus()).isEqualTo(TestStatus.INCOMPLETE);
+ assertThat(result2.getOutputXmlFiles())
+ .containsExactly(new File("/usr/local/tmp/_cache/second_result.xml"));
+ }
+
+ private static InputStream asInputStream(BuildEvent.Builder... events) throws IOException {
+ return asInputStream(Arrays.asList(events));
+ }
+
+ private static InputStream asInputStream(Iterable<BuildEvent.Builder> events) throws IOException {
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ for (BuildEvent.Builder event : events) {
+ event.build().writeDelimitedTo(output);
+ }
+ return new ByteArrayInputStream(output.toByteArray());
+ }
+
+ private static BuildEvent.Builder targetConfiguredEvent(String label, String targetKind) {
+ return BuildEvent.newBuilder()
+ .setId(
+ BuildEventId.newBuilder()
+ .setTargetConfigured(TargetConfiguredId.newBuilder().setLabel(label)))
+ .setConfigured(TargetConfigured.newBuilder().setTargetKind(targetKind));
+ }
+
+ private static BuildEvent.Builder targetCompletedEvent(String label, String targetKind) {
+ return BuildEvent.newBuilder()
+ .setId(
+ BuildEventId.newBuilder()
+ .setTargetCompleted(TargetCompletedId.newBuilder().setLabel(label)))
+ .setCompleted(TargetComplete.newBuilder().setTargetKind(targetKind));
+ }
+
+ private static BuildEvent.Builder testResultEvent(
+ String label, BuildEventStreamProtos.TestStatus status, List<String> filePaths) {
+ return BuildEvent.newBuilder()
+ .setId(BuildEventId.newBuilder().setTestResult(TestResultId.newBuilder().setLabel(label)))
+ .setTestResult(
+ TestResult.newBuilder()
+ .setStatus(status)
+ .addAllTestActionOutput(
+ filePaths
+ .stream()
+ .map(BuildEventProtocolOutputReaderTest::toEventFile)
+ .collect(toImmutableList())));
+ }
+
+ private static NamedSetOfFiles setOfFiles(List<String> filePaths) {
+ return NamedSetOfFiles.newBuilder()
+ .addAllFiles(
+ filePaths
+ .stream()
+ .map(BuildEventProtocolOutputReaderTest::toEventFile)
+ .collect(toImmutableList()))
+ .build();
+ }
+
+ private static BuildEventStreamProtos.File toEventFile(String filePath) {
+ return BuildEventStreamProtos.File.newBuilder().setUri(fileUrl(filePath)).build();
+ }
+
+ private static String fileUrl(String filePath) {
+ return LocalFileSystem.PROTOCOL_PREFIX + filePath;
+ }
+}
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
index 6ed7193..f3a7dae 100644
--- a/base/tests/unittests/com/google/idea/blaze/base/issueparser/BlazeIssueParserTest.java
+++ b/base/tests/unittests/com/google/idea/blaze/base/issueparser/BlazeIssueParserTest.java
@@ -135,6 +135,20 @@
}
@Test
+ public void testParseCompileFatalErrorWithColumn() {
+ // Clang also has a 'fatal error' category.
+ BlazeIssueParser blazeIssueParser = new BlazeIssueParser(parsers);
+ IssueOutput issue =
+ blazeIssueParser.parseIssue(
+ "net/something/foo_bar.cc:29:10: fatal error: 'util/ptr_util2.h' file not found");
+ assertNotNull(issue);
+ assertThat(issue.getLine()).isEqualTo(29);
+ assertThat(issue.getColumn()).isEqualTo(10);
+ assertThat(issue.getMessage()).isEqualTo("'util/ptr_util2.h' file not found");
+ assertThat(issue.getCategory()).isEqualTo(IssueOutput.Category.ERROR);
+ }
+
+ @Test
public void testParseBuildError() {
BlazeIssueParser blazeIssueParser = new BlazeIssueParser(parsers);
IssueOutput issue =
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
index 9f1ad20..bb3063d 100644
--- a/base/tests/unittests/com/google/idea/blaze/base/projectview/ProjectViewSetTest.java
+++ b/base/tests/unittests/com/google/idea/blaze/base/projectview/ProjectViewSetTest.java
@@ -41,7 +41,9 @@
import com.google.idea.blaze.base.projectview.section.sections.RunConfigurationsSection;
import com.google.idea.blaze.base.projectview.section.sections.Sections;
import com.google.idea.blaze.base.projectview.section.sections.ShardBlazeBuildsSection;
+import com.google.idea.blaze.base.projectview.section.sections.SyncFlagsSection;
import com.google.idea.blaze.base.projectview.section.sections.TargetSection;
+import com.google.idea.blaze.base.projectview.section.sections.TargetShardSizeSection;
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;
@@ -82,6 +84,7 @@
.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(SyncFlagsSection.KEY).add("--config=arm"))
.add(
ListSection.builder(ImportTargetOutputSection.KEY)
.add(Label.create("//test:test")))
@@ -96,6 +99,7 @@
ListSection.builder(RunConfigurationsSection.KEY)
.add(new WorkspacePath("test")))
.add(ScalarSection.builder(ShardBlazeBuildsSection.KEY).set(false))
+ .add(ScalarSection.builder(TargetShardSizeSection.KEY).set(500))
.build())
.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
index f0047bd..3ed4fd1 100644
--- a/base/tests/unittests/com/google/idea/blaze/base/run/BlazeCommandRunConfigurationTest.java
+++ b/base/tests/unittests/com/google/idea/blaze/base/run/BlazeCommandRunConfigurationTest.java
@@ -17,7 +17,6 @@
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.TargetIdeInfo;
@@ -33,6 +32,7 @@
import com.intellij.openapi.extensions.impl.ExtensionPointImpl;
import com.intellij.openapi.project.Project;
import java.util.List;
+import java.util.function.Predicate;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
diff --git a/base/tests/unittests/com/google/idea/blaze/base/run/testlogs/BuildEventProtocolTestFinderStrategyTest.java b/base/tests/unittests/com/google/idea/blaze/base/run/testlogs/BuildEventProtocolTestFinderStrategyTest.java
new file mode 100644
index 0000000..a90660c
--- /dev/null
+++ b/base/tests/unittests/com/google/idea/blaze/base/run/testlogs/BuildEventProtocolTestFinderStrategyTest.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.testlogs;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.idea.common.guava.GuavaHelper.toImmutableList;
+
+import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.base.BlazeTestCase;
+import com.google.idea.blaze.base.command.buildresult.BuildEventProtocolOutputReader;
+import com.google.idea.blaze.base.io.InputStreamProvider;
+import com.google.idea.blaze.base.io.MockInputStreamProvider;
+import com.google.repackaged.devtools.build.lib.buildeventstream.BuildEventStreamProtos;
+import com.google.repackaged.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildEventId.TestResultId;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mockito;
+
+/** Unit tests for {@link BuildEventProtocolTestFinderStrategy}. */
+@RunWith(JUnit4.class)
+public class BuildEventProtocolTestFinderStrategyTest extends BlazeTestCase {
+
+ private MockInputStreamProvider inputStreamProvider;
+ private final Set<File> deletedFiles = new HashSet<>();
+
+ @After
+ public void clearState() {
+ deletedFiles.clear();
+ }
+
+ @Override
+ protected void initTest(Container applicationServices, Container projectServices) {
+ inputStreamProvider = new MockInputStreamProvider();
+ applicationServices.register(InputStreamProvider.class, inputStreamProvider);
+ }
+
+ @Test
+ public void findTestResults_fileDeletedAfterReading() throws IOException {
+ File file = createMockFile("/tmp/bep_output.txt", new byte[0]);
+
+ new BuildEventProtocolTestFinderStrategy(file).findTestResults();
+
+ assertThat(deletedFiles).contains(file);
+ }
+
+ @Test
+ public void findTestResults_shouldMatchBuildEventProtocolOutputReader() throws IOException {
+ BuildEventStreamProtos.BuildEvent.Builder test1 =
+ testResultEvent(
+ "//java/com/google:Test1",
+ BuildEventStreamProtos.TestStatus.PASSED,
+ ImmutableList.of("/usr/local/tmp/_cache/test_result.xml"));
+ BuildEventStreamProtos.BuildEvent.Builder test2 =
+ testResultEvent(
+ "//java/com/google:Test2",
+ BuildEventStreamProtos.TestStatus.INCOMPLETE,
+ ImmutableList.of("/usr/local/tmp/_cache/second_result.xml"));
+ File bepOutputFile =
+ createMockFile("/tmp/bep_output.txt", asByteArray(ImmutableList.of(test1, test2)));
+ BuildEventProtocolTestFinderStrategy strategy =
+ new BuildEventProtocolTestFinderStrategy(bepOutputFile);
+
+ BlazeTestResults results =
+ BuildEventProtocolOutputReader.parseTestResults(inputStreamProvider.getFile(bepOutputFile));
+ BlazeTestResults finderStrategyResults = strategy.findTestResults();
+
+ assertThat(finderStrategyResults.perTargetResults).isEqualTo(results.perTargetResults);
+ }
+
+ private File createMockFile(String path, byte[] contents) {
+ File org = new File(path);
+ File spy = Mockito.spy(org);
+ inputStreamProvider.addFile(path, contents);
+ Mockito.when(spy.delete())
+ .then(
+ invocationOnMock -> {
+ deletedFiles.add(spy);
+ return true;
+ });
+ return spy;
+ }
+
+ private static byte[] asByteArray(Iterable<BuildEventStreamProtos.BuildEvent.Builder> events)
+ throws IOException {
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ for (BuildEventStreamProtos.BuildEvent.Builder event : events) {
+ event.build().writeDelimitedTo(output);
+ }
+ return output.toByteArray();
+ }
+
+ private static BuildEventStreamProtos.BuildEvent.Builder testResultEvent(
+ String label, BuildEventStreamProtos.TestStatus status, List<String> filePaths) {
+ return BuildEventStreamProtos.BuildEvent.newBuilder()
+ .setId(
+ BuildEventStreamProtos.BuildEventId.newBuilder()
+ .setTestResult(TestResultId.newBuilder().setLabel(label)))
+ .setTestResult(
+ BuildEventStreamProtos.TestResult.newBuilder()
+ .setStatus(status)
+ .addAllTestActionOutput(
+ filePaths
+ .stream()
+ .map(BuildEventProtocolTestFinderStrategyTest::toEventFile)
+ .collect(toImmutableList())));
+ }
+
+ private static BuildEventStreamProtos.File toEventFile(String filePath) {
+ return BuildEventStreamProtos.File.newBuilder().setUri(fileUrl(filePath)).build();
+ }
+
+ private static String fileUrl(String filePath) {
+ return LocalFileSystem.PROTOCOL_PREFIX + filePath;
+ }
+}
diff --git a/base/tests/unittests/com/google/idea/blaze/base/sync/LanguageSupportTest.java b/base/tests/unittests/com/google/idea/blaze/base/sync/LanguageSupportTest.java
index fdc880e..5adae26 100644
--- a/base/tests/unittests/com/google/idea/blaze/base/sync/LanguageSupportTest.java
+++ b/base/tests/unittests/com/google/idea/blaze/base/sync/LanguageSupportTest.java
@@ -71,6 +71,11 @@
public ImmutableList<WorkspaceType> getSupportedWorkspaceTypes() {
return ImmutableList.of(WorkspaceType.C);
}
+
+ @Override
+ public WorkspaceType getDefaultWorkspaceType() {
+ return WorkspaceType.C;
+ }
});
ProjectViewSet projectViewSet =
@@ -91,6 +96,13 @@
@Test
public void testFailWithUnsupportedWorkspaceType() {
+ syncPlugins.registerExtension(
+ new BlazeSyncPlugin.Adapter() {
+ @Override
+ public WorkspaceType getDefaultWorkspaceType() {
+ return WorkspaceType.JAVA;
+ }
+ });
ProjectViewSet projectViewSet =
ProjectViewSet.builder()
.add(
@@ -117,6 +129,11 @@
public ImmutableList<WorkspaceType> getSupportedWorkspaceTypes() {
return ImmutableList.of(WorkspaceType.C);
}
+
+ @Override
+ public WorkspaceType getDefaultWorkspaceType() {
+ return WorkspaceType.C;
+ }
});
ProjectViewSet projectViewSet =
@@ -149,6 +166,11 @@
public ImmutableList<WorkspaceType> getSupportedWorkspaceTypes() {
return ImmutableList.of(WorkspaceType.ANDROID);
}
+
+ @Override
+ public WorkspaceType getDefaultWorkspaceType() {
+ return WorkspaceType.ANDROID;
+ }
});
ProjectViewSet projectViewSet =
diff --git a/base/tests/unittests/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategyTest.java b/base/tests/unittests/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategyTest.java
new file mode 100644
index 0000000..147a547
--- /dev/null
+++ b/base/tests/unittests/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategyTest.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.base.sync.aspects.strategy;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.idea.blaze.base.BlazeTestCase;
+import com.google.idea.blaze.base.command.BlazeCommand;
+import com.google.idea.blaze.base.command.BlazeCommandName;
+import com.google.idea.blaze.base.model.primitives.LanguageClass;
+import com.google.idea.common.experiments.ExperimentService;
+import com.google.idea.common.experiments.MockExperimentService;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link AspectStrategy}. */
+@RunWith(JUnit4.class)
+public class AspectStrategyTest extends BlazeTestCase {
+
+ @Override
+ protected void initTest(Container applicationServices, Container projectServices) {
+ applicationServices.register(ExperimentService.class, new MockExperimentService());
+ }
+
+ @Test
+ public void testLegacyOutputGroupsUnchanged() {
+ AspectStrategy strategy = MockAspectStrategy.noPerLanguageOutputGroups();
+ Set<LanguageClass> activeLanguages = ImmutableSet.of(LanguageClass.JAVA, LanguageClass.ANDROID);
+
+ BlazeCommand.Builder builder = emptyBuilder();
+ strategy.modifyIdeInfoCommand(builder, activeLanguages);
+ assertThat(getBlazeFlags(builder)).containsExactly("--output_groups=intellij-info-text");
+
+ builder = emptyBuilder();
+ strategy.modifyIdeResolveCommand(builder, activeLanguages);
+ assertThat(getBlazeFlags(builder)).containsExactly("--output_groups=intellij-resolve");
+
+ builder = emptyBuilder();
+ strategy.modifyIdeCompileCommand(builder, activeLanguages);
+ assertThat(getBlazeFlags(builder)).containsExactly("--output_groups=intellij-compile");
+ }
+
+ @Test
+ public void testGenericOutputGroupAlwaysPresent() {
+ AspectStrategy strategy = MockAspectStrategy.withPerLanguageOutputGroups();
+ Set<LanguageClass> activeLanguages = ImmutableSet.of();
+
+ BlazeCommand.Builder builder = emptyBuilder();
+ strategy.modifyIdeInfoCommand(builder, activeLanguages);
+ assertThat(getOutputGroups(builder)).containsExactly("intellij-info-generic");
+ }
+
+ @Test
+ public void testNoGenericOutputGroupInResolveOrCompile() {
+ AspectStrategy strategy = MockAspectStrategy.withPerLanguageOutputGroups();
+ Set<LanguageClass> activeLanguages = ImmutableSet.of(LanguageClass.JAVA);
+
+ BlazeCommand.Builder builder = emptyBuilder();
+ strategy.modifyIdeResolveCommand(builder, activeLanguages);
+ assertThat(getOutputGroups(builder)).containsExactly("intellij-resolve-java");
+
+ builder = emptyBuilder();
+ strategy.modifyIdeCompileCommand(builder, activeLanguages);
+ assertThat(getOutputGroups(builder)).containsExactly("intellij-compile-java");
+ }
+
+ @Test
+ public void testAllPerLanguageOutputGroupsRecognized() {
+ AspectStrategy strategy = MockAspectStrategy.withPerLanguageOutputGroups();
+ Set<LanguageClass> activeLanguages =
+ Arrays.stream(LanguageOutputGroup.values())
+ .map(lang -> lang.languageClass)
+ .collect(Collectors.toSet());
+
+ BlazeCommand.Builder builder = emptyBuilder();
+ strategy.modifyIdeInfoCommand(builder, activeLanguages);
+ assertThat(getOutputGroups(builder))
+ .containsExactly(
+ "intellij-info-generic",
+ "intellij-info-java",
+ "intellij-info-cpp",
+ "intellij-info-android",
+ "intellij-info-py",
+ "intellij-info-go",
+ "intellij-info-js",
+ "intellij-info-ts");
+
+ builder = emptyBuilder();
+ strategy.modifyIdeResolveCommand(builder, activeLanguages);
+ assertThat(getOutputGroups(builder))
+ .containsExactly(
+ "intellij-resolve-java",
+ "intellij-resolve-cpp",
+ "intellij-resolve-android",
+ "intellij-resolve-py",
+ "intellij-resolve-go",
+ "intellij-resolve-js",
+ "intellij-resolve-ts");
+
+ builder = emptyBuilder();
+ strategy.modifyIdeCompileCommand(builder, activeLanguages);
+ assertThat(getOutputGroups(builder))
+ .containsExactly(
+ "intellij-compile-java",
+ "intellij-compile-cpp",
+ "intellij-compile-android",
+ "intellij-compile-py",
+ "intellij-compile-go",
+ "intellij-compile-js",
+ "intellij-compile-ts");
+ }
+
+ private static BlazeCommand.Builder emptyBuilder() {
+ return BlazeCommand.builder("/usr/bin/blaze", BlazeCommandName.BUILD);
+ }
+
+ private static ImmutableList<String> getBlazeFlags(BlazeCommand.Builder builder) {
+ ImmutableList<String> args = builder.build().toList();
+ return args.subList(3, args.indexOf("--"));
+ }
+
+ private static ImmutableList<String> getOutputGroups(BlazeCommand.Builder builder) {
+ List<String> blazeFlags = getBlazeFlags(builder);
+ assertThat(blazeFlags).hasSize(1);
+ String groups = blazeFlags.get(0).substring("--output_groups=".length());
+ return ImmutableList.copyOf(groups.split(","));
+ }
+
+ private static class MockAspectStrategy extends AspectStrategy {
+
+ static MockAspectStrategy withPerLanguageOutputGroups() {
+ return new MockAspectStrategy(true);
+ }
+
+ static MockAspectStrategy noPerLanguageOutputGroups() {
+ return new MockAspectStrategy(false);
+ }
+
+ final boolean hasPerLanguageOutputGroups;
+
+ private MockAspectStrategy(boolean hasPerLanguageOutputGroups) {
+ this.hasPerLanguageOutputGroups = hasPerLanguageOutputGroups;
+ }
+
+ @Override
+ public String getName() {
+ return "MockAspectStrategy";
+ }
+
+ @Override
+ protected List<String> getAspectFlags() {
+ return ImmutableList.of();
+ }
+
+ @Override
+ protected boolean hasPerLanguageOutputGroups() {
+ return hasPerLanguageOutputGroups;
+ }
+ }
+}
diff --git a/base/tests/unittests/com/google/idea/blaze/base/sync/projectview/SourceTestConfigTest.java b/base/tests/unittests/com/google/idea/blaze/base/sync/projectview/SourceTestConfigTest.java
index 5f2051f..35c24f6 100644
--- a/base/tests/unittests/com/google/idea/blaze/base/sync/projectview/SourceTestConfigTest.java
+++ b/base/tests/unittests/com/google/idea/blaze/base/sync/projectview/SourceTestConfigTest.java
@@ -17,6 +17,8 @@
import static com.google.common.truth.Truth.assertThat;
+import com.google.idea.blaze.base.BlazeTestCase;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -24,6 +26,8 @@
/** Unit tests for {@link SourceTestConfig} */
@RunWith(JUnit4.class)
public class SourceTestConfigTest {
+ @Rule
+ public final BlazeTestCase.IgnoreOnWindowsRule rule = new BlazeTestCase.IgnoreOnWindowsRule();
@Test
public void testGlobModification() {
diff --git a/base/tests/unittests/com/google/idea/blaze/base/sync/sharding/PartitionTargetsTest.java b/base/tests/unittests/com/google/idea/blaze/base/sync/sharding/PartitionTargetsTest.java
index 7da1a7a..dfa2957 100644
--- a/base/tests/unittests/com/google/idea/blaze/base/sync/sharding/PartitionTargetsTest.java
+++ b/base/tests/unittests/com/google/idea/blaze/base/sync/sharding/PartitionTargetsTest.java
@@ -83,7 +83,7 @@
.inOrder();
shards = BlazeBuildTargetSharder.shardTargets(targets, 1);
- assertThat(shards.shardedTargets).hasSize(6);
+ assertThat(shards.shardedTargets).hasSize(3);
assertThat(shards.shardedTargets.get(0))
.containsExactly(
TargetExpression.fromString("//java/com/google:one"),
@@ -92,4 +92,21 @@
TargetExpression.fromString("-//java/com/google:six"))
.inOrder();
}
+
+ @Test
+ public void testShardWithOnlyExcludedTargetsIsDropped() {
+ List<TargetExpression> targets =
+ ImmutableList.of(
+ TargetExpression.fromString("//java/com/google:one"),
+ TargetExpression.fromString("//java/com/google:two"),
+ TargetExpression.fromString("//java/com/google:three"),
+ TargetExpression.fromString("-//java/com/google:four"),
+ TargetExpression.fromString("-//java/com/google:five"),
+ TargetExpression.fromString("-//java/com/google:six"));
+
+ ShardedTargetList shards = BlazeBuildTargetSharder.shardTargets(targets, 3);
+
+ assertThat(shards.shardedTargets).hasSize(1);
+ assertThat(shards.shardedTargets.get(0)).hasSize(6);
+ }
}
diff --git a/base/tests/utils/integration/com/google/idea/blaze/base/BlazeIntegrationTestCase.java b/base/tests/utils/integration/com/google/idea/blaze/base/BlazeIntegrationTestCase.java
index 9080df7..af3969a 100644
--- a/base/tests/utils/integration/com/google/idea/blaze/base/BlazeIntegrationTestCase.java
+++ b/base/tests/utils/integration/com/google/idea/blaze/base/BlazeIntegrationTestCase.java
@@ -27,6 +27,7 @@
import com.google.idea.testing.EdtRule;
import com.google.idea.testing.IntellijTestSetupRule;
import com.google.idea.testing.ServiceHelper;
+import com.google.idea.testing.VerifyRequiredPluginsEnabled;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.extensions.ExtensionPointName;
@@ -132,6 +133,11 @@
});
registerApplicationService(
VirtualFileSystemProvider.class, new TestFileSystem.TempVirtualFileSystemProvider());
+
+ String requiredPlugins = System.getProperty("idea.required.plugins.id");
+ if (requiredPlugins != null) {
+ VerifyRequiredPluginsEnabled.runCheck(requiredPlugins.split(","));
+ }
}
@After
diff --git a/base/tests/utils/integration/com/google/idea/blaze/base/sync/BlazeSyncIntegrationTestCase.java b/base/tests/utils/integration/com/google/idea/blaze/base/sync/BlazeSyncIntegrationTestCase.java
index a227d50..9a435b4 100644
--- a/base/tests/utils/integration/com/google/idea/blaze/base/sync/BlazeSyncIntegrationTestCase.java
+++ b/base/tests/utils/integration/com/google/idea/blaze/base/sync/BlazeSyncIntegrationTestCase.java
@@ -26,6 +26,7 @@
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.BlazeConfigurationHandler;
import com.google.idea.blaze.base.command.info.BlazeInfo;
import com.google.idea.blaze.base.command.info.BlazeInfoRunner;
import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
@@ -77,10 +78,11 @@
// blaze-info data
private static final String OUTPUT_BASE = "/output_base";
private static final String EXECUTION_ROOT = "/execroot/root";
+ private static final String OUTPUT_PATH = EXECUTION_ROOT + "/blaze-out";
private static final String BLAZE_BIN =
- EXECUTION_ROOT + "/blaze-out/gcc-4.X.Y-crosstool-v17-hybrid-grtev3-k8-fastbuild/bin";
+ OUTPUT_PATH + "/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";
+ OUTPUT_PATH + "/gcc-4.X.Y-crosstool-v17-hybrid-grtev3-k8-fastbuild/genfiles";
private MockProjectViewManager projectViewManager;
private MockBlazeVcsHandler vcsHandler;
@@ -147,17 +149,14 @@
fileSystem.createDirectory(projectDataDirectory.getPath() + "/.blaze/modules");
blazeInfoData.setResults(
- ImmutableMap.of(
- BlazeInfo.blazeBinKey(Blaze.getBuildSystem(getProject())),
- BLAZE_BIN,
- BlazeInfo.blazeGenfilesKey(Blaze.getBuildSystem(getProject())),
- BLAZE_GENFILES,
- BlazeInfo.EXECUTION_ROOT_KEY,
- EXECUTION_ROOT,
- BlazeInfo.OUTPUT_BASE_KEY,
- OUTPUT_BASE,
- BlazeInfo.PACKAGE_PATH_KEY,
- workspaceRoot.toString()));
+ ImmutableMap.<String, String>builder()
+ .put(BlazeInfo.blazeBinKey(Blaze.getBuildSystem(getProject())), BLAZE_BIN)
+ .put(BlazeInfo.blazeGenfilesKey(Blaze.getBuildSystem(getProject())), BLAZE_GENFILES)
+ .put(BlazeInfo.EXECUTION_ROOT_KEY, EXECUTION_ROOT)
+ .put(BlazeInfo.OUTPUT_BASE_KEY, OUTPUT_BASE)
+ .put(BlazeInfo.OUTPUT_PATH_KEY, OUTPUT_PATH)
+ .put(BlazeInfo.PACKAGE_PATH_KEY, workspaceRoot.toString())
+ .build());
}
/** The workspace content entries created during sync */
@@ -337,6 +336,7 @@
WorkspaceRoot workspaceRoot,
ProjectViewSet projectViewSet,
BlazeVersionData blazeVersionData,
+ BlazeConfigurationHandler configHandler,
ShardedTargetList shardedTargets,
WorkspaceLanguageSettings workspaceLanguageSettings,
ArtifactLocationDecoder artifactLocationDecoder,
@@ -353,6 +353,7 @@
WorkspaceRoot workspaceRoot,
ProjectViewSet projectViewSet,
BlazeVersionData blazeVersionData,
+ WorkspaceLanguageSettings workspaceLanguageSettings,
ShardedTargetList shardedTargets) {
return BuildResult.SUCCESS;
}
@@ -364,6 +365,7 @@
WorkspaceRoot workspaceRoot,
ProjectViewSet projectViewSet,
BlazeVersionData blazeVersionData,
+ WorkspaceLanguageSettings workspaceLanguageSettings,
ShardedTargetList shardedTargets) {
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
index 020ee9f..52246ab 100644
--- a/base/tests/utils/unit/com/google/idea/blaze/base/BlazeTestCase.java
+++ b/base/tests/utils/unit/com/google/idea/blaze/base/BlazeTestCase.java
@@ -28,7 +28,6 @@
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.SystemInfo;
-import org.jetbrains.annotations.NotNull;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -40,14 +39,11 @@
/**
* Test base class.
*
- * <p>
- *
* <p>Provides a mock application and a mock project.
*/
public class BlazeTestCase {
/** Test rule that ensures tests do not run on Windows (see http://b.android.com/222904) */
public static class IgnoreOnWindowsRule implements TestRule {
- @NotNull
@Override
public Statement apply(Statement base, Description description) {
if (SystemInfo.isWindows) {
@@ -80,7 +76,7 @@
public static class Container {
private final MutablePicoContainer container;
- Container(@NotNull MutablePicoContainer container) {
+ Container(MutablePicoContainer container) {
this.container = container;
}
@@ -115,11 +111,10 @@
return project;
}
- protected void initTest(
- @NotNull Container applicationServices, @NotNull Container projectServices) {}
+ protected void initTest(Container applicationServices, Container projectServices) {}
protected <T> ExtensionPointImpl<T> registerExtensionPoint(
- @NotNull ExtensionPointName<T> name, @NotNull Class<T> type) {
+ ExtensionPointName<T> name, Class<T> type) {
PluginDescriptor pluginDescriptor =
new DefaultPluginDescriptor(PluginId.getId(type.getName()), type.getClassLoader());
extensionsArea.registerExtensionPoint(name.getName(), type.getName(), pluginDescriptor);
diff --git a/base/tests/utils/unit/com/google/idea/blaze/base/io/MockInputStreamProvider.java b/base/tests/utils/unit/com/google/idea/blaze/base/io/MockInputStreamProvider.java
new file mode 100644
index 0000000..8f6a723
--- /dev/null
+++ b/base/tests/utils/unit/com/google/idea/blaze/base/io/MockInputStreamProvider.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 static org.junit.Assert.fail;
+
+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.Map;
+
+/**
+ * Mocks {@link InputStreamProvider} for tests, providing an input stream corresponding to given
+ * file contents without IO operations.
+ */
+public class MockInputStreamProvider implements InputStreamProvider {
+
+ private final Map<String, byte[]> files = new HashMap<>();
+
+ /** Add a file to provide an {@link InputStream} for, with specified contents. */
+ public MockInputStreamProvider addFile(String filePath, String src) {
+ try {
+ addFile(filePath, src.getBytes("UTF-8"));
+ } catch (UnsupportedEncodingException e) {
+ fail(e.getMessage());
+ }
+ return this;
+ }
+
+ /** Add a file to provide an {@link InputStream} for, with specified contents. */
+ public MockInputStreamProvider addFile(String filePath, byte[] contents) {
+ files.put(filePath, contents);
+ return this;
+ }
+
+ @Override
+ public InputStream getFile(File path) throws FileNotFoundException {
+ final byte[] contents = files.get(path.getPath());
+ if (contents == null) {
+ throw new FileNotFoundException(path + " has not been mapped into MockInputStreamProvider.");
+ }
+ return new ByteArrayInputStream(contents);
+ }
+}
diff --git a/base/tests/utils/unit/com/google/idea/blaze/base/model/MockBlazeProjectDataBuilder.java b/base/tests/utils/unit/com/google/idea/blaze/base/model/MockBlazeProjectDataBuilder.java
index 8e104a7..9f9dedc 100644
--- a/base/tests/utils/unit/com/google/idea/blaze/base/model/MockBlazeProjectDataBuilder.java
+++ b/base/tests/utils/unit/com/google/idea/blaze/base/model/MockBlazeProjectDataBuilder.java
@@ -125,7 +125,7 @@
outputBase + "/execroot/gen");
}
BlazeVersionData blazeVersionData =
- this.blazeVersionData != null ? this.blazeVersionData : new BlazeVersionData();
+ this.blazeVersionData != null ? this.blazeVersionData : BlazeVersionData.builder().build();
WorkspacePathResolver workspacePathResolver =
this.workspacePathResolver != null
? this.workspacePathResolver
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
index bedd5d9..309ba63 100644
--- 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
@@ -27,7 +27,8 @@
public class MockPrefetchService implements PrefetchService {
@Override
- public ListenableFuture<?> prefetchFiles(Project project, Collection<File> files) {
+ public ListenableFuture<?> prefetchFiles(
+ Project project, Collection<File> files, boolean refetchCachedFiles) {
return Futures.immediateFuture(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
index 3362bae..eab80bb 100644
--- 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
@@ -20,14 +20,13 @@
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) {
+ public Propagation onOutput(IssueOutput output) {
issues.add(output);
return Propagation.Continue;
}
@@ -36,7 +35,7 @@
assertThat(issues).isEmpty();
}
- public void assertIssues(@NotNull String... requiredMessages) {
+ public void assertIssues(String... requiredMessages) {
List<String> messages = Lists.newArrayList();
for (IssueOutput issue : issues) {
messages.add(issue.getMessage());
@@ -44,7 +43,7 @@
assertThat(messages).containsExactly((Object[]) requiredMessages);
}
- public void assertIssueContaining(@NotNull String s) {
+ public void assertIssueContaining(String s) {
assertThat(issues.stream().anyMatch((issue) -> issue.getMessage().contains(s)))
.named("Issues must contain: " + s)
.isTrue();
diff --git a/build_defs/BUILD b/build_defs/BUILD
index 19ad33f..fd3fe33 100644
--- a/build_defs/BUILD
+++ b/build_defs/BUILD
@@ -35,3 +35,8 @@
name = "package_meta_inf_files",
srcs = ["package_meta_inf_files.py"],
)
+
+py_binary(
+ name = "zip_plugin_files",
+ srcs = ["zip_plugin_files.py"],
+)
diff --git a/build_defs/append_optional_xml_elements.py b/build_defs/append_optional_xml_elements.py
index c987fe1..c6761af 100755
--- a/build_defs/append_optional_xml_elements.py
+++ b/build_defs/append_optional_xml_elements.py
@@ -37,9 +37,9 @@
if args.output:
with file(args.output, "w") as f:
- f.write(dom.toxml())
+ f.write(dom.toxml(encoding="utf-8"))
else:
- print dom.toxml()
+ print dom.toxml(encoding="utf-8")
if __name__ == "__main__":
diff --git a/build_defs/build_defs.bzl b/build_defs/build_defs.bzl
index 5325028..a4e2c6d 100644
--- a/build_defs/build_defs.bzl
+++ b/build_defs/build_defs.bzl
@@ -1,7 +1,6 @@
"""Custom build macros for IntelliJ plugin handling.
"""
-load(":intellij_plugin_debug_target.bzl", "intellij_plugin_debug_target")
load(":intellij_plugin.bzl", "intellij_plugin", "optional_plugin_xml")
def merged_plugin_xml(name, srcs, **kwargs):
@@ -179,61 +178,123 @@
tools = [api_version_txt_tool],
**kwargs)
-def repackaged_jar(name, deps, rules, **kwargs):
- """Repackages classes in a jar, to avoid collisions in the classpath.
-
- Args:
- name: the name of this target
- deps: The dependencies repackage
- rules: the rules to apply in the repackaging
- 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.
- """
- java_binary_name = name + "_orig"
- out = name + ".jar"
- native.java_binary(
- name = java_binary_name,
- create_executable = 0,
- stamp = 0,
- runtime_deps = deps)
- _repackaged_jar(name, java_binary_name, out, rules, **kwargs)
-
-def _repackaged_jar(name, src_rule, out, rules, **kwargs):
- """Repackages classes in a jar, to avoid collisions in the classpath."""
- 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 + "_repackaged"
- 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 beta_gensignature(name, srcs, stable, stable_version, beta_version):
if stable_version == beta_version:
native.alias(name = name, actual = stable)
else:
native.gensignature(name = name, srcs = srcs)
+
+repackaged_files_data = provider()
+
+def _repackaged_files_impl(ctx):
+ prefix = ctx.attr.prefix
+ if prefix.startswith("/"):
+ fail("'prefix' must be a relative path")
+ input_files = depset()
+ for target in ctx.attr.srcs:
+ input_files = input_files | target.files
+
+ return [
+ # TODO(brendandouglas): Only valid for Bazel 0.5 onwards. Uncomment when
+ # 0.5 used more widely.
+ # DefaultInfo(files = input_files),
+ repackaged_files_data(
+ files = input_files,
+ prefix = prefix,
+ )
+ ]
+
+_repackaged_files = rule(
+ implementation = _repackaged_files_impl,
+ attrs = {
+ "srcs": attr.label_list(mandatory = True, allow_files = True),
+ "prefix": attr.string(mandatory = True),
+ },
+)
+
+def repackaged_files(name, srcs, prefix, **kwargs):
+ """Assembles files together so that they can be packaged as an IntelliJ plugin.
+
+ A cut-down version of the internal 'pkgfilegroup' rule.
+
+ Args:
+ name: The name of this target
+ srcs: A list of targets which are dependencies of this rule. All output files of each of these
+ targets will be repackaged.
+ prefix: Where the package should install these files, relative to the 'plugins' directory. The
+ input file path is stripped prior to applying this prefix.
+ **kwargs: Any further arguments to be passed to the target
+ """
+ _repackaged_files(name = name, srcs = srcs, prefix = prefix, **kwargs)
+
+def _plugin_deploy_zip_impl(ctx):
+ zip_name = ctx.attr.zip_filename
+ zip_file = ctx.new_file(zip_name)
+
+ input_files = depset()
+ exec_path_to_zip_path = {}
+ for target in ctx.attr.srcs:
+ data = target[repackaged_files_data]
+ input_files = input_files | data.files
+ for f in data.files:
+ exec_path_to_zip_path[f.path] = data.prefix + "/" + f.basename
+
+ args = []
+ args.extend(["--output", zip_file.path])
+ for exec_path, zip_path in exec_path_to_zip_path.items():
+ args.extend([exec_path, zip_path])
+ ctx.action(
+ executable = ctx.executable._zip_plugin_files,
+ arguments = args,
+ inputs = list(input_files),
+ outputs = [zip_file],
+ mnemonic = "ZipPluginFiles",
+ progress_message = "Creating final plugin zip archive",
+ )
+ files = depset([zip_file])
+ return struct(
+ files = files,
+ )
+
+_plugin_deploy_zip = rule(
+ implementation = _plugin_deploy_zip_impl,
+ attrs = {
+ "srcs": attr.label_list(mandatory = True, providers = []),
+ "zip_filename": attr.string(mandatory = True),
+ "_zip_plugin_files": attr.label(
+ default = Label("//build_defs:zip_plugin_files"),
+ executable = True,
+ cfg = "host",
+ ),
+ }
+)
+
+def plugin_deploy_zip(name, srcs, zip_filename):
+ """Packages up plugin files into a zip archive.
+
+ Args:
+ name: The name of this target
+ srcs: A list of targets of type 'repackaged_files', specifying the input files and relative
+ paths to include in the output zip archive.
+ zip_filename: The output zip filename.
+ """
+ _plugin_deploy_zip(name = name, zip_filename = zip_filename, srcs = srcs)
+
+def unescape_filenames(name, srcs):
+ """Macro to generate files with spaces in their names instead of underscores.
+
+ For each file in the srcs, a file will be generated with the same name but with all underscores
+ replaced with spaces.
+
+ Args:
+ name: The name of the generator rule
+ srcs: A list of source files to process
+ """
+ outs = [s.replace("_", " ") for s in srcs]
+ cmd = "&&".join(["cp \"{}\" $(@D)/\"{}\"".format(s, d) for (s,d) in zip(srcs, outs)])
+ native.genrule(
+ name = name,
+ srcs = srcs,
+ outs = outs,
+ cmd = cmd,
+ )
diff --git a/build_defs/intellij_plugin.bzl b/build_defs/intellij_plugin.bzl
index 207435d..3a827e4 100644
--- a/build_defs/intellij_plugin.bzl
+++ b/build_defs/intellij_plugin.bzl
@@ -131,7 +131,7 @@
module_to_merged_xmls = _merge_optional_plugin_xmls(ctx)
final_plugin_xml_file = _add_optional_dependencies_to_plugin_xml(ctx, module_to_merged_xmls.keys())
jar_file = _package_meta_inf_files(ctx, final_plugin_xml_file, module_to_merged_xmls)
- files = set([jar_file])
+ files = depset([jar_file])
return struct(
files = files,
)
diff --git a/build_defs/intellij_plugin_debug_target.bzl b/build_defs/intellij_plugin_debug_target.bzl
index 37ec6f6..bbf21f4 100644
--- a/build_defs/intellij_plugin_debug_target.bzl
+++ b/build_defs/intellij_plugin_debug_target.bzl
@@ -5,12 +5,11 @@
Any files are stripped of their prefix and installed into
<sandbox>/plugins. If you need structure, first put the files
-into a pkgfilegroup. The files will be installed relative to the
-'plugins' directory if present in the pkgfilegroup prefix.
+into //build_defs:build_defs%repackage_files.
intellij_plugin_debug_targets can be nested.
-pkgfilegroup(
+repackaged_files(
name = "foo_files",
srcs = [
":my_plugin_jar",
@@ -28,35 +27,17 @@
"""
+load("//build_defs:build_defs.bzl", "repackaged_files_data")
+
SUFFIX = ".intellij-plugin-debug-target-deploy-info"
def _trim_start(path, prefix):
return path[len(prefix):] if path.startswith(prefix) else path
-def _pkgfilegroup_deploy_file(ctx, f):
- strip_prefix = ctx.rule.attr.strip_prefix
- prefix = ctx.rule.attr.prefix
- if strip_prefix == ".":
- stripped_relative_path = f.basename
- elif strip_prefix.startswith("/"):
- stripped_relative_path = _trim_start(f.short_path, strip_prefix[1:])
- else:
- stripped_relative_path = _trim_start(f.short_path, PACKAGE_NAME)
- stripped_relative_path = _trim_start(stripped_relative_path, strip_prefix)
- stripped_relative_path = _trim_start(stripped_relative_path, "/")
-
- # If there's a 'plugins' directory, make destination relative to that
- plugini = prefix.find("plugins/")
- plugins_prefix = prefix[plugini + len("plugins/"):] if plugini >= 0 else prefix
-
- # If the install location is still absolute, fail
- if plugins_prefix.startswith("/"):
- fail("Cannot compute plugins-relative install directory for pkgfilegroup")
-
- dest = plugins_prefix + "/" + stripped_relative_path if plugins_prefix else stripped_relative_path
+def _repackaged_deploy_file(f, prefix):
return struct(
src = f,
- deploy_location = dest,
+ deploy_location = prefix + "/" + f.basename,
)
def _flat_deploy_file(f):
@@ -68,19 +49,24 @@
def _intellij_plugin_debug_target_aspect_impl(target, ctx):
aspect_intellij_plugin_deploy_info = None
+ files = target.files
if ctx.rule.kind == "intellij_plugin_debug_target":
aspect_intellij_plugin_deploy_info = target.intellij_plugin_deploy_info
- elif ctx.rule.kind == "pkgfilegroup":
+ elif ctx.rule.kind == "_repackaged_files":
+ data = target[repackaged_files_data]
+ prefix = data.prefix
aspect_intellij_plugin_deploy_info = struct(
- deploy_files = [_pkgfilegroup_deploy_file(ctx, f) for f in target.files],
+ deploy_files = [_repackaged_deploy_file(f, prefix) for f in data.files],
)
+ # TODO(brendandouglas): Remove when migrating to Bazel 0.5, when DefaultInfo
+ # provider can be populated by '_repackaged_files' directly
+ files = files | data.files
else:
aspect_intellij_plugin_deploy_info = struct(
deploy_files = [_flat_deploy_file(f) for f in target.files],
)
-
return struct(
- files = target.files,
+ input_files = files,
aspect_intellij_plugin_deploy_info = aspect_intellij_plugin_deploy_info,
)
@@ -95,10 +81,10 @@
)
def _intellij_plugin_debug_target_impl(ctx):
- files = set()
+ files = depset()
deploy_files = []
for target in ctx.attr.deps:
- files = files | target.files
+ files = files | target.input_files
deploy_files.extend(target.aspect_intellij_plugin_deploy_info.deploy_files)
deploy_info = struct(
deploy_files = [_build_deploy_info_file(f) for f in deploy_files]
@@ -108,8 +94,8 @@
# We've already consumed any dependent intellij_plugin_debug_targets into our own,
# do not build or report these
- files = set([f for f in files if not f.path.endswith(SUFFIX)])
- files = files | set([output])
+ files = depset([f for f in files if not f.path.endswith(SUFFIX)])
+ files = files | depset([output])
return struct(
files = files,
diff --git a/build_defs/stamp_plugin_xml.py b/build_defs/stamp_plugin_xml.py
index 72baa66..a59c426 100755
--- a/build_defs/stamp_plugin_xml.py
+++ b/build_defs/stamp_plugin_xml.py
@@ -1,6 +1,7 @@
"""Stamps a plugin xml with build information."""
import argparse
+import io
import re
from xml.dom.minidom import parse
@@ -62,7 +63,7 @@
def _read_changelog(changelog_file):
"""Reads the changelog and transforms it into trivial HTML."""
- with open(changelog_file) as f:
+ with io.open(changelog_file, encoding="utf-8") as f:
return "\n".join("<p>" + line + "</p>" for line in f.readlines())
@@ -189,7 +190,7 @@
for new_element in new_elements:
idea_plugin.appendChild(new_element)
- print dom.toxml()
+ print dom.toxml(encoding="utf-8")
if __name__ == "__main__":
diff --git a/build_defs/zip_plugin_files.py b/build_defs/zip_plugin_files.py
new file mode 100644
index 0000000..c86dcf3
--- /dev/null
+++ b/build_defs/zip_plugin_files.py
@@ -0,0 +1,35 @@
+"""Packages plugin files into a zip archive."""
+
+import argparse
+from itertools import izip
+import time
+import zipfile
+
+parser = argparse.ArgumentParser()
+
+parser.add_argument("--output", help="The output filename.", required=True)
+parser.add_argument(
+ "files_to_zip", nargs="+", help="Sequence of exec_path, zip_path... pairs")
+
+
+def pairwise(t):
+ it = iter(t)
+ return izip(it, it)
+
+
+def main():
+ args = parser.parse_args()
+
+ # zipfile cannot be coaxed into putting a custom timestamp into the zip files,
+ # and we cannot modify the timestamp of the file itself (because it's a CAS
+ # entry on Forge). Therefore, we replace time.localtime().
+ time.localtime = lambda _: [2000, 1, 1, 0, 0, 0, 0, 0, 0]
+
+ outfile = zipfile.ZipFile(args.output, "w", zipfile.ZIP_DEFLATED)
+ for exec_path, zip_path in pairwise(args.files_to_zip):
+ outfile.write(exec_path, zip_path)
+ outfile.close()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/clwb/BUILD b/clwb/BUILD
index e420e93..bfa5d6e 100644
--- a/clwb/BUILD
+++ b/clwb/BUILD
@@ -8,9 +8,14 @@
"//build_defs:build_defs.bzl",
"intellij_plugin",
"merged_plugin_xml",
+ "plugin_deploy_zip",
+ "repackaged_files",
"stamped_plugin_xml",
)
-load("//intellij_platform_sdk:build_defs.bzl", "select_for_plugin_api")
+load(
+ "//build_defs:intellij_plugin_debug_target.bzl",
+ "intellij_plugin_debug_target",
+)
load("//:version.bzl", "VERSION")
merged_plugin_xml(
@@ -36,7 +41,7 @@
changelog_file = "//:changelog",
include_product_code_in_stamp = True,
plugin_id = "com.google.idea.bazel.clwb",
- plugin_name = "CLion with Bazel",
+ plugin_name = "Bazel",
plugin_xml = ":merged_plugin_xml",
stamp_since_build = True,
version = VERSION,
@@ -46,11 +51,10 @@
name = "clwb_lib",
srcs = glob(["src/**/*.java"]),
runtime_deps = [
+ "//golang",
+ "//python",
"//terminal",
- ] + select_for_plugin_api({
- "clion-2016.3.2": [],
- "default": ["//python"],
- }),
+ ],
deps = [
"//base",
"//common/experiments",
@@ -62,6 +66,7 @@
)
OPTIONAL_PLUGIN_XMLS = [
+ "//golang:optional_xml",
"//python:optional_xml",
"//terminal:optional_xml",
]
@@ -74,3 +79,32 @@
":clwb_lib",
],
)
+
+repackaged_files(
+ name = "plugin_jar",
+ srcs = [":clwb_bazel"],
+ prefix = "clwb/lib",
+)
+
+repackaged_files(
+ name = "aspect_directory",
+ srcs = ["//aspect:aspect_files"],
+ prefix = "clwb/aspect",
+)
+
+intellij_plugin_debug_target(
+ name = "clwb_bazel_dev",
+ deps = [
+ ":aspect_directory",
+ ":plugin_jar",
+ ],
+)
+
+plugin_deploy_zip(
+ name = "clwb_bazel_zip",
+ srcs = [
+ ":aspect_directory",
+ ":plugin_jar",
+ ],
+ zip_filename = "clwb_bazel.zip",
+)
diff --git a/clwb/src/META-INF/clwb.xml b/clwb/src/META-INF/clwb.xml
index 1b79d3c..186ca5d 100644
--- a/clwb/src/META-INF/clwb.xml
+++ b/clwb/src/META-INF/clwb.xml
@@ -49,7 +49,6 @@
<extensions defaultExtensionNs="com.google.idea.blaze">
<SyncPlugin implementation="com.google.idea.blaze.clwb.sync.BlazeCLionSyncPlugin"/>
<BlazeCommandRunConfigurationHandlerProvider implementation="com.google.idea.blaze.clwb.run.BlazeCidrRunConfigurationHandlerProvider" order="first"/>
- <RunConfigurationFactory implementation="com.google.idea.blaze.clwb.run.BlazeCidrDebuggableConfigurationFactory"/>
<BlazeTestEventsHandler implementation="com.google.idea.blaze.clwb.run.test.BlazeCidrTestEventsHandler"/>
</extensions>
diff --git a/clwb/src/com/google/idea/blaze/clwb/run/BlazeCidrDebuggableConfigurationFactory.java b/clwb/src/com/google/idea/blaze/clwb/run/BlazeCidrDebuggableConfigurationFactory.java
deleted file mode 100644
index 55b0592..0000000
--- a/clwb/src/com/google/idea/blaze/clwb/run/BlazeCidrDebuggableConfigurationFactory.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.clwb.run;
-
-import com.google.idea.blaze.base.command.BlazeCommandName;
-import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
-import com.google.idea.blaze.base.ideinfo.TargetKey;
-import com.google.idea.blaze.base.model.BlazeProjectData;
-import com.google.idea.blaze.base.model.primitives.Kind;
-import com.google.idea.blaze.base.model.primitives.Label;
-import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
-import com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType;
-import com.google.idea.blaze.base.run.BlazeRunConfigurationFactory;
-import com.google.idea.blaze.base.run.state.BlazeCommandRunConfigurationCommonState;
-import com.intellij.execution.configurations.ConfigurationFactory;
-import com.intellij.execution.configurations.RunConfiguration;
-import com.intellij.openapi.project.Project;
-
-/**
- * Creates run configurations for debuggable cc targets. Non-debuggable targets are handled by the
- * default factory.
- */
-public class BlazeCidrDebuggableConfigurationFactory extends BlazeRunConfigurationFactory {
- @Override
- public boolean handlesTarget(Project project, BlazeProjectData blazeProjectData, Label label) {
- TargetIdeInfo target = blazeProjectData.targetMap.get(TargetKey.forPlainTarget(label));
- return target != null && RunConfigurationUtils.canUseClionHandler(target.kind);
- }
-
- @Override
- protected ConfigurationFactory getConfigurationFactory() {
- return BlazeCommandRunConfigurationType.getInstance().getFactory();
- }
-
- @Override
- public void setupConfiguration(RunConfiguration configuration, Label target) {
- final BlazeCommandRunConfiguration blazeConfig = (BlazeCommandRunConfiguration) configuration;
- blazeConfig.setTarget(target);
-
- BlazeCommandRunConfigurationCommonState state =
- blazeConfig.getHandlerStateIfType(BlazeCommandRunConfigurationCommonState.class);
- Kind kind = blazeConfig.getKindForTarget();
- if (state != null) {
- if (kind != null && Kind.isTestRule(kind.toString())) {
- state.getCommandState().setCommand(BlazeCommandName.TEST);
- } else {
- state.getCommandState().setCommand(BlazeCommandName.RUN);
- }
- }
- blazeConfig.setGeneratedName();
- }
-}
diff --git a/clwb/src/com/google/idea/blaze/clwb/run/BlazeCidrLauncher.java b/clwb/src/com/google/idea/blaze/clwb/run/BlazeCidrLauncher.java
index 473400b..ba964cf 100644
--- a/clwb/src/com/google/idea/blaze/clwb/run/BlazeCidrLauncher.java
+++ b/clwb/src/com/google/idea/blaze/clwb/run/BlazeCidrLauncher.java
@@ -20,17 +20,20 @@
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.BlazeInvocationContext;
import com.google.idea.blaze.base.issueparser.IssueOutputLineProcessor;
+import com.google.idea.blaze.base.model.primitives.Kind;
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.BlazeCommandRunConfiguration;
-import com.google.idea.blaze.base.run.DistributedExecutorSupport;
+import com.google.idea.blaze.base.run.filter.BlazeTargetFilter;
import com.google.idea.blaze.base.run.processhandler.LineProcessingProcessAdapter;
import com.google.idea.blaze.base.run.processhandler.ScopedBlazeProcessHandler;
-import com.google.idea.blaze.base.run.smrunner.BlazeTestEventsHandler;
+import com.google.idea.blaze.base.run.smrunner.BlazeTestUiSession;
import com.google.idea.blaze.base.run.smrunner.SmRunnerUtils;
+import com.google.idea.blaze.base.run.smrunner.TestUiSessionProvider;
import com.google.idea.blaze.base.run.state.BlazeCommandRunConfigurationCommonState;
import com.google.idea.blaze.base.scope.BlazeContext;
import com.google.idea.blaze.base.scope.scopes.IssuesScope;
@@ -41,6 +44,8 @@
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.CommandLineState;
import com.intellij.execution.configurations.GeneralCommandLine;
+import com.intellij.execution.filters.Filter;
+import com.intellij.execution.filters.UrlFilter;
import com.intellij.execution.process.ProcessHandler;
import com.intellij.execution.process.ProcessListener;
import com.intellij.execution.runners.ExecutionEnvironment;
@@ -54,6 +59,7 @@
import com.jetbrains.cidr.execution.debugger.CidrDebugProcess;
import com.jetbrains.cidr.execution.debugger.CidrLocalDebugProcess;
import com.jetbrains.cidr.execution.testing.CidrLauncher;
+import com.jetbrains.cidr.execution.testing.google.CidrGoogleTestConsoleProperties;
import java.io.File;
import javax.annotation.Nullable;
@@ -92,12 +98,12 @@
LOG.assertTrue(projectViewSet != null);
ImmutableList<String> testHandlerFlags = ImmutableList.of();
- BlazeTestEventsHandler testEventsHandler =
+ BlazeTestUiSession testUiSession =
useTestUi()
- ? BlazeTestEventsHandler.getHandlerForTarget(project, configuration.getTarget())
+ ? TestUiSessionProvider.createForTarget(project, configuration.getTarget())
: null;
- if (testEventsHandler != null) {
- testHandlerFlags = BlazeTestEventsHandler.getBlazeFlags(project);
+ if (testUiSession != null) {
+ testHandlerFlags = testUiSession.getBlazeFlags();
}
BlazeCommand.Builder command =
@@ -105,16 +111,18 @@
Blaze.getBuildSystemProvider(project).getBinaryPath(),
handlerState.getCommandState().getCommand())
.addTargets(configuration.getTarget())
- .addBlazeFlags(BlazeFlags.buildFlags(project, ProjectViewSet.builder().build()))
+ .addBlazeFlags(
+ BlazeFlags.blazeFlags(
+ project,
+ ProjectViewSet.builder().build(),
+ handlerState.getCommandState().getCommand(),
+ BlazeInvocationContext.RunConfiguration))
.addBlazeFlags(testHandlerFlags)
.addBlazeFlags(handlerState.getBlazeFlagsState().getExpandedFlags())
.addExeFlags(handlerState.getExeFlagsState().getExpandedFlags());
- command.addBlazeFlags(
- DistributedExecutorSupport.getBlazeFlags(
- project, handlerState.getRunOnDistributedExecutorState().runOnDistributedExecutor));
-
- state.setConsoleBuilder(createConsoleBuilder(testEventsHandler));
+ state.setConsoleBuilder(createConsoleBuilder(testUiSession));
+ state.addConsoleFilters(getConsoleFilters().toArray(new Filter[0]));
WorkspaceRoot workspaceRoot = WorkspaceRoot.fromImportSettings(importSettings);
return new ScopedBlazeProcessHandler(
@@ -155,6 +163,16 @@
File workingDir = workspaceRoot.directory();
commandLine.setWorkDirectory(workingDir);
commandLine.addParameters(handlerState.getExeFlagsState().getExpandedFlags());
+ // Disable colored output, to workaround parsing bug (CPP-10054)
+ // Note: cc_test runner currently only supports GUnit tests.
+ if (Kind.CC_TEST.equals(configuration.getKindForTarget())) {
+ commandLine.addParameter("--gunit_color=no");
+ }
+
+ String testFilter = convertToGUnitTestFilter(handlerState.getTestFilterFlag());
+ if (testFilter != null) {
+ commandLine.addParameter(testFilter);
+ }
TrivialInstaller installer = new TrivialInstaller(commandLine);
ImmutableList<String> startupCommands = getGdbStartupCommands(workingDir);
@@ -162,18 +180,33 @@
new CLionRunParameters(
new BlazeGDBDriverConfiguration(project, startupCommands, workspaceRoot), installer);
+ state.setConsoleBuilder(createConsoleBuilder(null));
+ state.addConsoleFilters(getConsoleFilters().toArray(new Filter[0]));
return new CidrLocalDebugProcess(parameters, session, state.getConsoleBuilder());
}
+ /** Convert Blaze test filter to gunit test filter */
+ @Nullable
+ private static String convertToGUnitTestFilter(@Nullable String blazeTestFilter) {
+ if (blazeTestFilter == null || !blazeTestFilter.startsWith(BlazeFlags.TEST_FILTER)) {
+ return null;
+ }
+ return "--gunit_filter" + blazeTestFilter.substring(BlazeFlags.TEST_FILTER.length());
+ }
+
@Override
protected Project getProject() {
return project;
}
- private CidrConsoleBuilder createConsoleBuilder(
- @Nullable BlazeTestEventsHandler testEventsHandler) {
- if (testEventsHandler != null) {
- return new GoogleTestConsoleBuilder(configuration.getProject(), testEventsHandler);
+ private ImmutableList<Filter> getConsoleFilters() {
+ return ImmutableList.of(new BlazeTargetFilter(project), new UrlFilter());
+ }
+
+ private CidrConsoleBuilder createConsoleBuilder(@Nullable BlazeTestUiSession testUiSession) {
+ if (BlazeCommandName.TEST.equals(handlerState.getCommandState().getCommand())) {
+ // hook up the test tree UI
+ return new GoogleTestConsoleBuilder(configuration.getProject(), testUiSession);
}
return new CidrConsoleBuilderAdapter(configuration.getProject());
}
@@ -190,26 +223,35 @@
}
private boolean useTestUi() {
- return BlazeCommandName.TEST.equals(handlerState.getCommandState().getCommand())
- && !handlerState.getRunOnDistributedExecutorState().runOnDistributedExecutor;
+ return BlazeCommandName.TEST.equals(handlerState.getCommandState().getCommand());
}
private final class GoogleTestConsoleBuilder extends CidrConsoleBuilderAdapter {
- private final BlazeTestEventsHandler testEventsHandler;
+ @Nullable private final BlazeTestUiSession testUiSession;
- private GoogleTestConsoleBuilder(Project project, BlazeTestEventsHandler testEventsHandler) {
+ private GoogleTestConsoleBuilder(Project project, @Nullable BlazeTestUiSession testUiSession) {
super(project);
- this.testEventsHandler = testEventsHandler;
+ this.testUiSession = testUiSession;
addFilter(new BlazeCidrTestOutputFilter(project));
}
@Override
protected ConsoleView createConsole() {
- return SmRunnerUtils.getConsoleView(
- configuration.getProject(),
- configuration,
- executionEnvironment.getExecutor(),
- testEventsHandler);
+ if (testUiSession != null) {
+ return SmRunnerUtils.getConsoleView(
+ configuration.getProject(),
+ configuration,
+ executionEnvironment.getExecutor(),
+ testUiSession);
+ }
+ // When debugging, we run gdb manually on the debug binary, so the blaze test runners aren't
+ // involved.
+ CidrGoogleTestConsoleProperties consoleProperties =
+ new CidrGoogleTestConsoleProperties(
+ configuration,
+ executionEnvironment.getExecutor(),
+ executionEnvironment.getExecutionTarget());
+ return createConsole(configuration.getType(), consoleProperties);
}
}
}
diff --git a/clwb/src/com/google/idea/blaze/clwb/run/BlazeCidrRunConfigurationRunner.java b/clwb/src/com/google/idea/blaze/clwb/run/BlazeCidrRunConfigurationRunner.java
index 42a78c7..a64e9d0 100644
--- a/clwb/src/com/google/idea/blaze/clwb/run/BlazeCidrRunConfigurationRunner.java
+++ b/clwb/src/com/google/idea/blaze/clwb/run/BlazeCidrRunConfigurationRunner.java
@@ -16,15 +16,17 @@
package com.google.idea.blaze.clwb.run;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
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.command.BlazeFlags;
+import com.google.idea.blaze.base.command.BlazeInvocationContext;
import com.google.idea.blaze.base.command.buildresult.BuildResultHelper;
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.projectview.ProjectViewManager;
import com.google.idea.blaze.base.projectview.ProjectViewSet;
@@ -37,6 +39,7 @@
import com.google.idea.blaze.base.scope.scopes.BlazeConsoleScope;
import com.google.idea.blaze.base.scope.scopes.IssuesScope;
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;
@@ -47,8 +50,12 @@
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.util.PathUtil;
import com.jetbrains.cidr.execution.CidrCommandLineState;
import java.io.File;
+import java.util.List;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
/** CLion-specific handler for {@link BlazeCommandRunConfiguration}s. */
public class BlazeCidrRunConfigurationRunner implements BlazeCommandRunConfigurationRunner {
@@ -74,6 +81,7 @@
@Override
public boolean executeBeforeRunTask(ExecutionEnvironment environment) {
+ executableToDebug = null;
if (!isDebugging(environment)) {
return true;
}
@@ -107,7 +115,10 @@
final ProjectViewSet projectViewSet =
ProjectViewManager.getInstance(project).getProjectViewSet();
- BuildResultHelper buildResultHelper = BuildResultHelper.forFiles(file -> true);
+ BlazeProjectData projectData =
+ BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
+ BuildResultHelper buildResultHelper =
+ BuildResultHelper.forFiles(projectData.blazeVersionData, file -> true);
final ListenableFuture<Void> buildOperation =
BlazeExecutor.submitTask(
@@ -126,14 +137,19 @@
Blaze.getBuildSystemProvider(project).getBinaryPath(),
BlazeCommandName.BUILD)
.addTargets(configuration.getTarget())
- .addBlazeFlags(BlazeFlags.buildFlags(project, projectViewSet))
+ .addBlazeFlags(
+ BlazeFlags.blazeFlags(
+ project,
+ projectViewSet,
+ BlazeCommandName.BUILD,
+ BlazeInvocationContext.RunConfiguration))
.addBlazeFlags(handlerState.getBlazeFlagsState().getExpandedFlags())
.addBlazeFlags(buildResultHelper.getBuildFlags());
// If we are trying to debug, make sure we are building in debug mode.
// This can cause a rebuild, so it is a heavyweight setting.
if (FORCE_DEBUG_BUILD_FOR_DEBUGGING_TEST.getValue()) {
- command.addBlazeFlags("-c", "dbg");
+ command.addBlazeFlags("-c", "dbg", "--copt=-g", "--strip=never");
}
ExternalTask.builder(workspaceRoot)
@@ -153,18 +169,42 @@
} catch (InterruptedException | java.util.concurrent.ExecutionException e) {
throw new ExecutionException(e);
}
- ImmutableList<File> outputArtifacts = buildResultHelper.getBuildArtifacts();
- if (outputArtifacts.isEmpty()) {
+ List<File> candidateFiles =
+ buildResultHelper
+ .getBuildArtifactsForTarget((Label) configuration.getTarget())
+ .stream()
+ .filter(File::canExecute)
+ .collect(Collectors.toList());
+ if (candidateFiles.isEmpty()) {
throw new ExecutionException(
String.format("No output artifacts found when building %s", configuration.getTarget()));
}
- if (outputArtifacts.size() > 1) {
+ File file = findExecutable((Label) configuration.getTarget(), candidateFiles);
+ if (file == null) {
throw new ExecutionException(
String.format(
"More than 1 executable was produced when building %s; don't know which one to debug",
configuration.getTarget()));
}
- LocalFileSystem.getInstance().refreshIoFiles(outputArtifacts);
- return Iterables.getOnlyElement(outputArtifacts);
+ LocalFileSystem.getInstance().refreshIoFiles(ImmutableList.of(file));
+ return file;
+ }
+
+ /**
+ * Basic heuristic for choosing between multiple output files. Currently just looks for a filename
+ * matching the target name.
+ */
+ @Nullable
+ private static File findExecutable(Label target, List<File> outputs) {
+ if (outputs.size() == 1) {
+ return outputs.get(0);
+ }
+ String name = PathUtil.getFileName(target.targetName().toString());
+ for (File file : outputs) {
+ if (file.getName().equals(name)) {
+ return file;
+ }
+ }
+ return null;
}
}
diff --git a/clwb/src/com/google/idea/blaze/clwb/run/test/BlazeCidrTestEventsHandler.java b/clwb/src/com/google/idea/blaze/clwb/run/test/BlazeCidrTestEventsHandler.java
index f117615..839bd7c 100644
--- a/clwb/src/com/google/idea/blaze/clwb/run/test/BlazeCidrTestEventsHandler.java
+++ b/clwb/src/com/google/idea/blaze/clwb/run/test/BlazeCidrTestEventsHandler.java
@@ -23,16 +23,15 @@
import com.intellij.execution.testframework.sm.runner.SMTestLocator;
import com.intellij.openapi.project.Project;
import java.util.ArrayList;
-import java.util.EnumSet;
import java.util.List;
import javax.annotation.Nullable;
/** Provides C/C++ specific methods needed by the SM-runner test UI. */
-public class BlazeCidrTestEventsHandler extends BlazeTestEventsHandler {
+public class BlazeCidrTestEventsHandler implements BlazeTestEventsHandler {
@Override
- protected EnumSet<Kind> handledKinds() {
- return EnumSet.of(Kind.CC_TEST);
+ public boolean handlesKind(@Nullable Kind kind) {
+ return kind == Kind.CC_TEST;
}
@Override
diff --git a/clwb/src/com/google/idea/blaze/clwb/wizard2/BlazeSelectWorkspaceImportWizardStep.java b/clwb/src/com/google/idea/blaze/clwb/wizard2/BlazeSelectWorkspaceImportWizardStep.java
index 3d719bb..1b95745 100644
--- a/clwb/src/com/google/idea/blaze/clwb/wizard2/BlazeSelectWorkspaceImportWizardStep.java
+++ b/clwb/src/com/google/idea/blaze/clwb/wizard2/BlazeSelectWorkspaceImportWizardStep.java
@@ -48,7 +48,8 @@
}
private void init() {
- control = new BlazeSelectWorkspaceControl(getProjectBuilder());
+ control =
+ new BlazeSelectWorkspaceControl(getProjectBuilder(), getWizardContext().getDisposable());
this.component.add(control.getUiComponent());
settingsInitialised = true;
}
diff --git a/clwb/src/com/google/idea/blaze/plugin/CMakeWorkspaceOverride.java b/clwb/src/com/google/idea/blaze/plugin/CMakeWorkspaceOverride.java
index 7a2c2ca..ed1fc04 100644
--- a/clwb/src/com/google/idea/blaze/plugin/CMakeWorkspaceOverride.java
+++ b/clwb/src/com/google/idea/blaze/plugin/CMakeWorkspaceOverride.java
@@ -35,9 +35,11 @@
public class CMakeWorkspaceOverride extends CMakeWorkspace {
private final boolean isBlazeProject;
+ private final Project projectOverride;
public CMakeWorkspaceOverride(Project project) {
super(project);
+ projectOverride = project;
isBlazeProject = Blaze.isBlazeProject(project);
}
@@ -47,7 +49,7 @@
super.projectOpened();
return;
}
- removeClasspathStorageFromModules(myProject);
+ removeClasspathStorageFromModules(projectOverride);
}
/**
diff --git a/common/concurrency/BUILD b/common/concurrency/BUILD
new file mode 100644
index 0000000..a1d7730
--- /dev/null
+++ b/common/concurrency/BUILD
@@ -0,0 +1,11 @@
+licenses(["notice"]) # Apache 2.0
+
+java_library(
+ name = "concurrency",
+ srcs = glob(["src/**/*.java"]),
+ visibility = ["//visibility:public"],
+ deps = [
+ "//intellij_platform_sdk:plugin_api",
+ "@jsr305_annotations//jar",
+ ],
+)
diff --git a/common/concurrency/src/com/google/idea/common/concurrency/ConcurrencyUtil.java b/common/concurrency/src/com/google/idea/common/concurrency/ConcurrencyUtil.java
new file mode 100644
index 0000000..16d4a46
--- /dev/null
+++ b/common/concurrency/src/com/google/idea/common/concurrency/ConcurrencyUtil.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.concurrency;
+
+import com.google.common.util.concurrent.ListeningScheduledExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import com.intellij.util.concurrency.AppExecutorUtil;
+import java.util.concurrent.ThreadFactory;
+
+/** Utils for concurrency. */
+public final class ConcurrencyUtil {
+
+ private static final ListeningScheduledExecutorService executor =
+ MoreExecutors.listeningDecorator(AppExecutorUtil.getAppScheduledExecutorService());
+
+ private ConcurrencyUtil() {}
+
+ public static ListeningScheduledExecutorService getAppExecutorService() {
+ return executor;
+ }
+
+ public static ThreadFactory namedDaemonThreadPoolFactory(Class<?> klass) {
+ return new ThreadFactoryBuilder()
+ .setNameFormat(klass.getSimpleName() + "-%d")
+ .setDaemon(true)
+ .build();
+ }
+}
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
index c144c70..144f178 100644
--- 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
@@ -51,6 +51,10 @@
return defaultValue;
}
+ public void setExperimentInt(IntExperiment experiment, int value) {
+ experiments.put(experiment.getKey(), value);
+ }
+
@Override
public int getExperimentInt(String key, int defaultValue) {
if (experiments.containsKey(key)) {
diff --git a/common/guava/BUILD b/common/guava/BUILD
new file mode 100644
index 0000000..aa0735d
--- /dev/null
+++ b/common/guava/BUILD
@@ -0,0 +1,8 @@
+licenses(["notice"]) # Apache 2.0
+
+java_library(
+ name = "guava",
+ srcs = glob(["**/*.java"]),
+ visibility = ["//visibility:public"],
+ deps = ["//intellij_platform_sdk:plugin_api"],
+)
diff --git a/common/guava/src/com/google/idea/common/guava/GuavaHelper.java b/common/guava/src/com/google/idea/common/guava/GuavaHelper.java
new file mode 100644
index 0000000..0d4aa6c
--- /dev/null
+++ b/common/guava/src/com/google/idea/common/guava/GuavaHelper.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.guava;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Collector;
+import java.util.stream.Stream;
+
+/** Adds a few methods that aren't available until future versions of Guava. */
+public final class GuavaHelper {
+
+ private static final Collector<Object, ?, ImmutableSet<Object>> TO_IMMUTABLE_SET =
+ Collector.of(
+ ImmutableSet::<Object>builder,
+ ImmutableSet.Builder::add,
+ GuavaHelper::combineSet,
+ ImmutableSet.Builder::build);
+
+ private static final Collector<Object, ?, ImmutableList<Object>> TO_IMMUTABLE_LIST =
+ Collector.of(
+ ImmutableList::<Object>builder,
+ ImmutableList.Builder::add,
+ GuavaHelper::combineList,
+ ImmutableList.Builder::build);
+
+ /** Replaces {@code ImmutableSet#toImmutableSet}, which isn't available until Guava 21. */
+ @SuppressWarnings("unchecked") // so we can use a singleton
+ public static <E> Collector<E, ?, ImmutableSet<E>> toImmutableSet() {
+ return (Collector) TO_IMMUTABLE_SET;
+ }
+
+ /** Replaces {@code ImmutableList#toImmutableList}, which isn't available until Guava 21. */
+ @SuppressWarnings("unchecked") // so we can use a singleton
+ public static <E> Collector<E, ?, ImmutableList<E>> toImmutableList() {
+ return (Collector) TO_IMMUTABLE_LIST;
+ }
+
+ /** Replaces {@code ImmutableMap#toImmutableMap}, which isn't available until Guava 21. */
+ public static <T, K, V> Collector<T, ?, ImmutableMap<K, V>> toImmutableMap(
+ Function<? super T, ? extends K> keyFunction,
+ Function<? super T, ? extends V> valueFunction) {
+ return Collector.of(
+ ImmutableMap.Builder<K, V>::new,
+ (builder, input) -> builder.put(keyFunction.apply(input), valueFunction.apply(input)),
+ GuavaHelper::combineMap,
+ ImmutableMap.Builder::build);
+ }
+
+ private static <T> ImmutableSet.Builder<T> combineSet(
+ ImmutableSet.Builder<T> one, ImmutableSet.Builder<T> two) {
+ return one.addAll(two.build());
+ }
+
+ private static <T> ImmutableList.Builder<T> combineList(
+ ImmutableList.Builder<T> one, ImmutableList.Builder<T> two) {
+ return one.addAll(two.build());
+ }
+
+ private static <K, V> ImmutableMap.Builder<K, V> combineMap(
+ ImmutableMap.Builder<K, V> one, ImmutableMap.Builder<K, V> two) {
+ return one.putAll(two.build());
+ }
+
+ public static <T> Stream<T> stream(Optional<T> optional) {
+ return optional.isPresent() ? Stream.of(optional.get()) : Stream.of();
+ }
+}
diff --git a/cpp/BUILD b/cpp/BUILD
index bbf1c7e..e1f64c1 100644
--- a/cpp/BUILD
+++ b/cpp/BUILD
@@ -1,5 +1,17 @@
licenses(["notice"]) # Apache 2.0
+load(
+ "//build_defs:build_defs.bzl",
+ "intellij_plugin",
+ "merged_plugin_xml",
+ "stamped_plugin_xml",
+)
+load(
+ "//testing:test_defs.bzl",
+ "intellij_integration_test_suite",
+ "intellij_unit_test_suite",
+)
+
java_library(
name = "cpp",
srcs = glob(
@@ -11,6 +23,7 @@
"//common/experiments",
"//intellij_platform_sdk:plugin_api",
"//sdkcompat",
+ "//third_party/auto_value",
"@jsr305_annotations//jar",
],
)
@@ -21,17 +34,58 @@
visibility = ["//visibility:public"],
)
-load(
- "//testing:test_defs.bzl",
- "intellij_unit_test_suite",
-)
-
intellij_unit_test_suite(
name = "unit_tests",
srcs = glob(["tests/unittests/**/*.java"]),
test_package_root = "com.google.idea.blaze.cpp",
deps = [
":cpp",
+ "//base",
+ "//base:unit_test_utils",
+ "//intellij_platform_sdk:plugin_api_for_tests",
+ "//sdkcompat",
+ "@jsr305_annotations//jar",
+ "@junit//jar",
+ ],
+)
+
+merged_plugin_xml(
+ name = "merged_plugin_xml",
+ srcs = [
+ "//base:plugin_xml",
+ ] + [
+ ":plugin_xml",
+ ],
+)
+
+stamped_plugin_xml(
+ name = "cpp_plugin_xml",
+ plugin_id = "com.google.idea.bazel.cpp",
+ plugin_name = "com.google.idea.bazel.cpp",
+ plugin_xml = ":merged_plugin_xml",
+)
+
+intellij_plugin(
+ name = "cpp_integration_test_plugin",
+ testonly = 1,
+ plugin_xml = ":cpp_plugin_xml",
+ deps = [
+ ":cpp",
+ ],
+)
+
+intellij_integration_test_suite(
+ name = "integration_tests",
+ srcs = glob(["tests/integrationtests/**/*.java"]),
+ required_plugins = "com.google.idea.bazel.cpp",
+ test_package_root = "com.google.idea.blaze.cpp",
+ runtime_deps = [
+ ":cpp_integration_test_plugin",
+ ],
+ deps = [
+ ":cpp",
+ "//base",
+ "//base:integration_test_utils",
"//base:unit_test_utils",
"//intellij_platform_sdk:plugin_api_for_tests",
"//sdkcompat",
diff --git a/cpp/src/META-INF/blaze-cpp.xml b/cpp/src/META-INF/blaze-cpp.xml
index 158f6d8..34f7f67 100644
--- a/cpp/src/META-INF/blaze-cpp.xml
+++ b/cpp/src/META-INF/blaze-cpp.xml
@@ -38,5 +38,11 @@
</extensions>
<extensions defaultExtensionNs="com.intellij">
<projectService serviceImplementation="com.google.idea.blaze.cpp.BlazeCWorkspace"/>
+ <projectViewNodeDecorator implementation="com.google.idea.blaze.cpp.syncstatus.BlazeCppSyncStatusFileNodeDecorator"/>
+ <editorTabColorProvider implementation="com.google.idea.blaze.cpp.syncstatus.BlazeCppSyncStatusEditorTabColorProvider"/>
+ <editorTabTitleProvider implementation="com.google.idea.blaze.cpp.syncstatus.BlazeCppSyncStatusEditorTabTitleProvider"/>
+
+ <applicationService serviceInterface="com.google.idea.blaze.cpp.CompilerVersionChecker"
+ serviceImplementation="com.google.idea.blaze.cpp.CompilerVersionCheckerImpl"/>
</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
index aa1736e..d26722d 100644
--- a/cpp/src/com/google/idea/blaze/cpp/BlazeCSyncPlugin.java
+++ b/cpp/src/com/google/idea/blaze/cpp/BlazeCSyncPlugin.java
@@ -32,7 +32,6 @@
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.jetbrains.cidr.lang.workspace.OCWorkspace;
-import com.jetbrains.cidr.lang.workspace.OCWorkspaceManager;
import java.util.Set;
final class BlazeCSyncPlugin extends BlazeSyncPlugin.Adapter {
@@ -65,10 +64,10 @@
childContext -> {
childContext.push(new TimingScope("Setup C Workspace"));
- OCWorkspace workspace = OCWorkspaceManager.getWorkspace(project);
+ OCWorkspace workspace = OCWorkspaceProvider.getWorkspace(project);
if (workspace instanceof BlazeCWorkspace) {
BlazeCWorkspace blazeCWorkspace = (BlazeCWorkspace) workspace;
- blazeCWorkspace.update(childContext, blazeProjectData);
+ blazeCWorkspace.update(childContext, workspaceRoot, projectViewSet, blazeProjectData);
}
});
}
diff --git a/cpp/src/com/google/idea/blaze/cpp/BlazeCWorkspace.java b/cpp/src/com/google/idea/blaze/cpp/BlazeCWorkspace.java
index c0f5b94..835aa1e 100644
--- a/cpp/src/com/google/idea/blaze/cpp/BlazeCWorkspace.java
+++ b/cpp/src/com/google/idea/blaze/cpp/BlazeCWorkspace.java
@@ -18,10 +18,11 @@
import com.google.common.collect.ImmutableList;
import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
+import com.google.idea.blaze.base.projectview.ProjectViewSet;
import com.google.idea.blaze.base.scope.BlazeContext;
import com.google.idea.sdkcompat.cidr.OCWorkspaceAdapter;
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;
@@ -32,26 +33,28 @@
/** Main entry point for C/CPP configuration data. */
public final class BlazeCWorkspace extends OCWorkspaceAdapter {
- private static final Logger logger = Logger.getInstance(BlazeCWorkspace.class);
-
private final BlazeConfigurationResolver configurationResolver;
+ private BlazeConfigurationResolverResult resolverResult;
private BlazeCWorkspace(Project project) {
super(project);
this.configurationResolver = new BlazeConfigurationResolver(project);
+ this.resolverResult = BlazeConfigurationResolverResult.empty(project);
}
public static BlazeCWorkspace getInstance(Project project) {
return ServiceManager.getService(project, BlazeCWorkspace.class);
}
- public void update(BlazeContext context, BlazeProjectData blazeProjectData) {
- // Non-incremental update to our c configurations.
- long start = System.currentTimeMillis();
- configurationResolver.update(context, blazeProjectData);
- long end = System.currentTimeMillis();
-
- logger.info(String.format("Blaze OCWorkspace update took: %d ms", (end - start)));
+ public void update(
+ BlazeContext context,
+ WorkspaceRoot workspaceRoot,
+ ProjectViewSet projectViewSet,
+ BlazeProjectData blazeProjectData) {
+ BlazeConfigurationResolverResult oldResult = resolverResult;
+ resolverResult =
+ configurationResolver.update(
+ context, workspaceRoot, projectViewSet, blazeProjectData, oldResult);
}
@Override
@@ -82,8 +85,8 @@
}
@Override
- public List<? extends OCResolveConfiguration> getConfigurations() {
- return configurationResolver.getAllConfigurations();
+ public List<BlazeResolveConfiguration> getConfigurations() {
+ return resolverResult.getAllConfigurations();
}
@Override
@@ -92,7 +95,12 @@
if (sourceFile == null || !sourceFile.isValid()) {
return ImmutableList.of();
}
- OCResolveConfiguration config = configurationResolver.getConfigurationForFile(sourceFile);
+ OCResolveConfiguration config = resolverResult.getConfigurationForFile(sourceFile);
return config == null ? ImmutableList.of() : ImmutableList.of(config);
}
+
+ @Nullable
+ BlazeConfigurationResolverDiff getConfigurationDiff() {
+ return resolverResult.getConfigurationDiff();
+ }
}
diff --git a/cpp/src/com/google/idea/blaze/cpp/BlazeCompilerMacros.java b/cpp/src/com/google/idea/blaze/cpp/BlazeCompilerMacros.java
index 654a8e2..b68aa1b 100644
--- a/cpp/src/com/google/idea/blaze/cpp/BlazeCompilerMacros.java
+++ b/cpp/src/com/google/idea/blaze/cpp/BlazeCompilerMacros.java
@@ -15,29 +15,33 @@
*/
package com.google.idea.blaze.cpp;
+import com.google.common.base.Objects;
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.google.idea.sdkcompat.cidr.OCCompilerMacrosAdapter;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
+import com.jetbrains.cidr.lang.OCLanguageKind;
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;
+final class BlazeCompilerMacros extends OCCompilerMacrosAdapter {
private final Project project;
- public BlazeCompilerMacros(
+ private final CompilerInfoCache compilerInfoCache;
+ private final OCCompilerSettings compilerSettings;
+
+ private final ImmutableCollection<String> globalDefines;
+ private final ImmutableMap<String, String> globalFeatures;
+
+ BlazeCompilerMacros(
Project project,
CompilerInfoCache compilerInfoCache,
OCCompilerSettings compilerSettings,
@@ -51,14 +55,10 @@
}
@Override
- protected void fillFileMacros(OCInclusionContext context, PsiFile sourceFile) {
- // Get the default compiler info for this file.
- VirtualFile vf = OCInclusionContextUtil.getVirtualFile(sourceFile);
+ public String getAllDefines(OCLanguageKind kind, VirtualFile vf) {
CidrCompilerResult<CompilerInfoCache.Entry> compilerInfoProvider =
- compilerInfoCache.getCompilerInfoCache(
- project, compilerSettings, context.getLanguageKind(), vf);
+ compilerInfoCache.getCompilerInfoCache(project, compilerSettings, kind, 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] [VALUE]\n#define [VAR_NAME2] [VALUE]\n...",
@@ -76,14 +76,45 @@
allDefines += "\n" + compilerInfo.defines;
}
+ return allDefines;
+ }
+
+ @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();
+
Map<String, String> allFeatures = Maps.newHashMap();
allFeatures.putAll(globalFeatures);
if (compilerInfo != null) {
- allFeatures.putAll(compilerInfo.features);
+ addAllFeatures(allFeatures, compilerInfo.features);
}
- fillSubstitutions(context, allDefines);
+ fillSubstitutions(context, getAllDefines(context.getLanguageKind(), vf));
enableClangFeatures(context, allFeatures);
enableClangExtensions(context, allFeatures);
}
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof BlazeCompilerMacros)) {
+ return false;
+ }
+ BlazeCompilerMacros other = (BlazeCompilerMacros) obj;
+ return this.globalDefines.equals(other.globalDefines)
+ && this.globalFeatures.equals(other.globalFeatures);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(globalDefines, globalFeatures);
+ }
}
diff --git a/cpp/src/com/google/idea/blaze/cpp/BlazeCompilerSettings.java b/cpp/src/com/google/idea/blaze/cpp/BlazeCompilerSettings.java
index ccc178d..679d19e 100644
--- a/cpp/src/com/google/idea/blaze/cpp/BlazeCompilerSettings.java
+++ b/cpp/src/com/google/idea/blaze/cpp/BlazeCompilerSettings.java
@@ -18,19 +18,22 @@
import com.google.common.collect.ImmutableList;
import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
import com.google.idea.sdkcompat.cidr.CidrSwitchBuilderAdapter;
+import com.google.idea.sdkcompat.cidr.OCCompilerSettingsAdapter;
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.CidrToolEnvironment;
import com.jetbrains.cidr.lang.toolchains.DefaultCidrToolEnvironment;
+import com.jetbrains.cidr.lang.workspace.compiler.CidrCompilerResult;
import com.jetbrains.cidr.lang.workspace.compiler.OCCompilerKind;
-import com.jetbrains.cidr.lang.workspace.compiler.OCCompilerSettings;
+import com.jetbrains.cidr.toolchains.CompilerInfoCache;
+import com.jetbrains.cidr.toolchains.CompilerInfoCache.Entry;
import java.io.File;
import java.util.List;
import javax.annotation.Nullable;
-final class BlazeCompilerSettings extends OCCompilerSettings {
+final class BlazeCompilerSettings extends OCCompilerSettingsAdapter {
private final CidrToolEnvironment toolEnvironment = new DefaultCidrToolEnvironment();
private final Project project;
@@ -38,18 +41,24 @@
@Nullable private final File cppCompiler;
private final CidrCompilerSwitches cCompilerSwitches;
private final CidrCompilerSwitches cppCompilerSwitches;
+ private final String compilerVersion;
+ private final CompilerInfoCache compilerInfoCache;
BlazeCompilerSettings(
Project project,
@Nullable File cCompiler,
@Nullable File cppCompiler,
ImmutableList<String> cFlags,
- ImmutableList<String> cppFlags) {
+ ImmutableList<String> cppFlags,
+ String compilerVersion,
+ CompilerInfoCache compilerInfoCache) {
this.project = project;
this.cCompiler = cCompiler;
this.cppCompiler = cppCompiler;
this.cCompilerSwitches = getCompilerSwitches(cFlags);
this.cppCompilerSwitches = getCompilerSwitches(cppFlags);
+ this.compilerVersion = compilerVersion;
+ this.compilerInfoCache = compilerInfoCache;
}
@Override
@@ -93,7 +102,21 @@
return new CidrSwitchBuilderAdapter().build();
}
+ String getCompilerVersion() {
+ return compilerVersion;
+ }
+
private static CidrCompilerSwitches getCompilerSwitches(List<String> allCompilerFlags) {
return new CidrSwitchBuilderAdapter().addAllRaw(allCompilerFlags).build();
}
+
+ public CidrCompilerResult<Entry> getCompilerInfo(
+ OCLanguageKind ocLanguageKind, @Nullable VirtualFile virtualFile) {
+ return compilerInfoCache.getCompilerInfoCache(project, this, ocLanguageKind, virtualFile);
+ }
+
+ @Override
+ public String getCompilerKey(OCLanguageKind ocLanguageKind, @Nullable VirtualFile virtualFile) {
+ return getCompiler(ocLanguageKind).toString();
+ }
}
diff --git a/cpp/src/com/google/idea/blaze/cpp/BlazeConfigurationResolver.java b/cpp/src/com/google/idea/blaze/cpp/BlazeConfigurationResolver.java
index d01798e..5708f9b 100644
--- a/cpp/src/com/google/idea/blaze/cpp/BlazeConfigurationResolver.java
+++ b/cpp/src/com/google/idea/blaze/cpp/BlazeConfigurationResolver.java
@@ -15,104 +15,134 @@
*/
package com.google.idea.blaze.cpp;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import com.google.common.collect.ImmutableCollection;
+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.Lists;
import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
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.command.info.BlazeInfo;
+import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
+import com.google.idea.blaze.base.ideinfo.CIdeInfo;
import com.google.idea.blaze.base.ideinfo.CToolchainIdeInfo;
import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
import com.google.idea.blaze.base.ideinfo.TargetKey;
import com.google.idea.blaze.base.ideinfo.TargetMap;
import com.google.idea.blaze.base.io.FileAttributeProvider;
+import com.google.idea.blaze.base.io.VirtualFileSystemProvider;
import com.google.idea.blaze.base.model.BlazeProjectData;
import com.google.idea.blaze.base.model.primitives.ExecutionRootPath;
import com.google.idea.blaze.base.model.primitives.Kind;
import com.google.idea.blaze.base.model.primitives.LanguageClass;
import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
+import com.google.idea.blaze.base.projectview.ProjectViewSet;
import com.google.idea.blaze.base.scope.BlazeContext;
import com.google.idea.blaze.base.scope.Scope;
import com.google.idea.blaze.base.scope.ScopedFunction;
+import com.google.idea.blaze.base.scope.ScopedOperation;
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.google.idea.blaze.base.settings.Blaze;
+import com.google.idea.blaze.base.sync.projectview.ProjectViewTargetImportFilter;
+import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
import com.google.idea.blaze.base.sync.workspace.ExecutionRootPathResolver;
import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver;
-import com.google.idea.blaze.base.targetmaps.SourceToTargetMap;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.openapi.vfs.LocalFileSystem;
-import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
-import com.jetbrains.cidr.lang.workspace.OCResolveConfiguration;
import com.jetbrains.cidr.toolchains.CompilerInfoCache;
import java.io.File;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.AbstractMap.SimpleImmutableEntry;
+import java.util.ArrayDeque;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.Queue;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
final class BlazeConfigurationResolver {
- private static final class MapEntry {
- public final TargetKey targetKey;
- public final BlazeResolveConfiguration configuration;
+ private static final Logger logger = Logger.getInstance(BlazeConfigurationResolver.class);
+ // Don't recursively check too many directories, in case the root is just too big.
+ // Sometimes genfiles/java is considered a header search root.
+ private static final int GEN_HEADER_ROOT_SEARCH_LIMIT = 50;
- public MapEntry(TargetKey targetKey, BlazeResolveConfiguration configuration) {
- this.targetKey = targetKey;
- this.configuration = configuration;
- }
- }
-
- private static final Logger LOG = Logger.getInstance(BlazeConfigurationResolver.class);
private final Project project;
- private ImmutableMap<TargetKey, BlazeResolveConfiguration> resolveConfigurations =
- ImmutableMap.of();
-
- public BlazeConfigurationResolver(Project project) {
+ BlazeConfigurationResolver(Project project) {
this.project = project;
}
- public void update(BlazeContext context, BlazeProjectData blazeProjectData) {
+ public BlazeConfigurationResolverResult update(
+ BlazeContext context,
+ WorkspaceRoot workspaceRoot,
+ ProjectViewSet projectViewSet,
+ BlazeProjectData blazeProjectData,
+ BlazeConfigurationResolverResult oldResult) {
+ ExecutionRootPathResolver executionRootPathResolver =
+ new ExecutionRootPathResolver(
+ Blaze.getBuildSystem(project),
+ WorkspaceRoot.fromProject(project),
+ blazeProjectData.blazeInfo.getExecutionRoot(),
+ blazeProjectData.workspacePathResolver);
ImmutableMap<TargetKey, CToolchainIdeInfo> toolchainLookupMap =
- BlazeResolveConfiguration.buildToolchainLookupMap(context, blazeProjectData.targetMap);
+ BlazeConfigurationToolchainResolver.buildToolchainLookupMap(
+ context, blazeProjectData.targetMap);
ImmutableMap<File, VirtualFile> headerRoots =
- collectHeaderRoots(context, blazeProjectData, toolchainLookupMap);
- ImmutableMap<CToolchainIdeInfo, BlazeCompilerSettings> compilerSettings =
- buildCompilerSettingsMap(
- context, project, toolchainLookupMap, blazeProjectData.workspacePathResolver);
+ collectHeaderRoots(
+ context, blazeProjectData, toolchainLookupMap, executionRootPathResolver);
CompilerInfoCache compilerInfoCache = new CompilerInfoCache();
- resolveConfigurations =
- buildBlazeConfigurationMap(
+ ImmutableMap<CToolchainIdeInfo, BlazeCompilerSettings> compilerSettings =
+ BlazeConfigurationToolchainResolver.buildCompilerSettingsMap(
context,
- blazeProjectData,
+ project,
+ workspaceRoot,
toolchainLookupMap,
- headerRoots,
- compilerSettings,
- compilerInfoCache);
+ blazeProjectData.workspacePathResolver,
+ compilerInfoCache,
+ oldResult.compilerSettings);
+ BlazeConfigurationResolverResult.Builder builder =
+ BlazeConfigurationResolverResult.builder(project);
+ buildBlazeConfigurationData(
+ context,
+ workspaceRoot,
+ projectViewSet,
+ blazeProjectData,
+ toolchainLookupMap,
+ headerRoots,
+ compilerSettings,
+ compilerInfoCache,
+ executionRootPathResolver,
+ oldResult,
+ builder);
+ builder.setCompilerSettings(compilerSettings);
+ builder.setResolveDiff(
+ computeConfigurationDiff(
+ blazeProjectData, builder.configurationMap, oldResult.configurationMap));
+ return builder.build();
}
- private ImmutableMap<File, VirtualFile> collectHeaderRoots(
+ private static ImmutableMap<File, VirtualFile> collectHeaderRoots(
BlazeContext parentContext,
BlazeProjectData blazeProjectData,
- ImmutableMap<TargetKey, CToolchainIdeInfo> toolchainLookupMap) {
+ ImmutableMap<TargetKey, CToolchainIdeInfo> toolchainLookupMap,
+ ExecutionRootPathResolver executionRootPathResolver) {
// Type specification needed to avoid incorrect type inference during command line build.
return Scope.push(
parentContext,
@@ -121,20 +151,20 @@
context.push(new TimingScope("Resolve header include roots"));
Set<ExecutionRootPath> paths =
collectExecutionRootPaths(blazeProjectData.targetMap, toolchainLookupMap);
- return doCollectHeaderRoots(context, blazeProjectData, paths);
+ return doCollectHeaderRoots(
+ context, blazeProjectData, paths, executionRootPathResolver);
});
}
- private ImmutableMap<File, VirtualFile> doCollectHeaderRoots(
- BlazeContext context, BlazeProjectData projectData, Set<ExecutionRootPath> rootPaths) {
- ExecutionRootPathResolver pathResolver =
- new ExecutionRootPathResolver(
- Blaze.getBuildSystem(project),
- WorkspaceRoot.fromProject(project),
- projectData.blazeInfo.getExecutionRoot(),
- projectData.workspacePathResolver);
+ private static ImmutableMap<File, VirtualFile> doCollectHeaderRoots(
+ BlazeContext context,
+ BlazeProjectData projectData,
+ Set<ExecutionRootPath> rootPaths,
+ ExecutionRootPathResolver pathResolver) {
ConcurrentMap<File, VirtualFile> rootsMap = Maps.newConcurrentMap();
List<ListenableFuture<Void>> futures = Lists.newArrayListWithCapacity(rootPaths.size());
+ AtomicInteger genRootsWithHeaders = new AtomicInteger();
+ AtomicInteger genRootsWithoutHeaders = new AtomicInteger();
for (ExecutionRootPath path : rootPaths) {
futures.add(
submit(
@@ -144,11 +174,20 @@
for (File file : possibleDirectories) {
VirtualFile vf = getVirtualFile(file);
if (vf != null) {
- rootsMap.put(file, vf);
+ // Check gen directories to see if they actually contain headers and not just
+ // other random generated files (like .s, .cc, or module maps).
+ if (!isOutputArtifact(projectData.blazeInfo, path)) {
+ rootsMap.put(file, vf);
+ } else if (genRootMayContainHeaders(vf)) {
+ genRootsWithHeaders.incrementAndGet();
+ rootsMap.put(file, vf);
+ } else {
+ genRootsWithoutHeaders.incrementAndGet();
+ }
} else if (!isOutputArtifact(projectData.blazeInfo, path)
&& FileAttributeProvider.getInstance().exists(file)) {
// If it's not a blaze output file, we expect it to always resolve.
- LOG.info(String.format("Unresolved header root %s", file.getAbsolutePath()));
+ logger.info(String.format("Unresolved header root %s", file.getAbsolutePath()));
}
}
return null;
@@ -156,17 +195,46 @@
}
try {
Futures.allAsList(futures).get();
- return ImmutableMap.copyOf(rootsMap);
+ ImmutableMap<File, VirtualFile> result = ImmutableMap.copyOf(rootsMap);
+ logger.info(
+ String.format(
+ "CollectHeaderRoots: %s roots, (%s, %s) genroots with/without headers",
+ result.size(), genRootsWithHeaders.get(), genRootsWithoutHeaders.get()));
+ return result;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
context.setCancelled();
} catch (ExecutionException e) {
IssueOutput.error("Error resolving header include roots: " + e).submit(context);
- LOG.error("Error resolving header include roots", e);
+ logger.error("Error resolving header include roots", e);
}
return ImmutableMap.of();
}
+ private static boolean genRootMayContainHeaders(VirtualFile directory) {
+ int totalDirectoriesChecked = 0;
+ Queue<VirtualFile> worklist = new ArrayDeque<>();
+ worklist.add(directory);
+ while (!worklist.isEmpty()) {
+ totalDirectoriesChecked++;
+ if (totalDirectoriesChecked > GEN_HEADER_ROOT_SEARCH_LIMIT) {
+ return true;
+ }
+ VirtualFile dir = worklist.poll();
+ for (VirtualFile child : dir.getChildren()) {
+ if (child.isDirectory()) {
+ worklist.add(child);
+ continue;
+ }
+ String fileExtension = child.getExtension();
+ if (CFileExtensions.HEADER_EXTENSIONS.contains(fileExtension)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
private static boolean isOutputArtifact(BlazeInfo blazeInfo, ExecutionRootPath path) {
return ExecutionRootPath.isAncestor(blazeInfo.getBlazeGenfilesExecutionRootPath(), path, false)
|| ExecutionRootPath.isAncestor(blazeInfo.getBlazeBinExecutionRootPath(), path, false);
@@ -193,7 +261,7 @@
@Nullable
private static VirtualFile getVirtualFile(File file) {
- LocalFileSystem fileSystem = LocalFileSystem.getInstance();
+ LocalFileSystem fileSystem = VirtualFileSystemProvider.getInstance().getSystem();
VirtualFile vf = fileSystem.findFileByPathIfCached(file.getPath());
if (vf == null) {
vf = fileSystem.findFileByIoFile(file);
@@ -201,77 +269,155 @@
return vf;
}
- private ImmutableMap<TargetKey, BlazeResolveConfiguration> buildBlazeConfigurationMap(
+ private static boolean containsCompiledSources(TargetIdeInfo target) {
+ Predicate<ArtifactLocation> isCompiled =
+ location -> {
+ String locationExtension = FileUtilRt.getExtension(location.getRelativePath());
+ return CFileExtensions.SOURCE_EXTENSIONS.contains(locationExtension);
+ };
+ return target.cIdeInfo != null
+ && target.cIdeInfo.sources.stream().filter(ArtifactLocation::isSource).anyMatch(isCompiled);
+ }
+
+ private void buildBlazeConfigurationData(
BlazeContext parentContext,
+ WorkspaceRoot workspaceRoot,
+ ProjectViewSet projectViewSet,
BlazeProjectData blazeProjectData,
ImmutableMap<TargetKey, CToolchainIdeInfo> toolchainLookupMap,
ImmutableMap<File, VirtualFile> headerRoots,
ImmutableMap<CToolchainIdeInfo, BlazeCompilerSettings> compilerSettings,
- CompilerInfoCache compilerInfoCache) {
+ CompilerInfoCache compilerInfoCache,
+ ExecutionRootPathResolver executionRootPathResolver,
+ BlazeConfigurationResolverResult oldConfigurationData,
+ BlazeConfigurationResolverResult.Builder builder) {
// Type specification needed to avoid incorrect type inference during command line build.
- return Scope.push(
+ Scope.push(
parentContext,
- (ScopedFunction<ImmutableMap<TargetKey, BlazeResolveConfiguration>>)
+ (ScopedOperation)
context -> {
context.push(new TimingScope("Build C configuration map"));
- List<ListenableFuture<MapEntry>> mapEntryFutures = Lists.newArrayList();
+ ProjectViewTargetImportFilter filter =
+ new ProjectViewTargetImportFilter(project, workspaceRoot, projectViewSet);
- for (TargetIdeInfo target : blazeProjectData.targetMap.targets()) {
- if (target.kind.getLanguageClass() == LanguageClass.C
- && target.kind != Kind.CC_TOOLCHAIN) {
- ListenableFuture<MapEntry> future =
- submit(
- () ->
- createResolveConfiguration(
- target,
- toolchainLookupMap,
- headerRoots,
- compilerSettings,
- blazeProjectData,
- compilerInfoCache));
- mapEntryFutures.add(future);
- }
- }
-
- ImmutableMap.Builder<TargetKey, BlazeResolveConfiguration> newResolveConfigurations =
- ImmutableMap.builder();
- List<MapEntry> mapEntries;
+ ConcurrentMap<TargetKey, BlazeResolveConfigurationData> targetToData =
+ Maps.newConcurrentMap();
+ List<ListenableFuture<?>> targetToDataFutures =
+ blazeProjectData
+ .targetMap
+ .targets()
+ .stream()
+ .filter(target -> target.kind.languageClass == LanguageClass.C)
+ .filter(target -> target.kind != Kind.CC_TOOLCHAIN)
+ .filter(filter::isSourceTarget)
+ .filter(BlazeConfigurationResolver::containsCompiledSources)
+ .map(
+ target ->
+ submit(
+ () -> {
+ BlazeResolveConfigurationData data =
+ createResolveConfiguration(
+ target,
+ toolchainLookupMap,
+ headerRoots,
+ compilerSettings,
+ compilerInfoCache,
+ executionRootPathResolver);
+ if (data != null) {
+ targetToData.put(target.key, data);
+ }
+ return null;
+ }))
+ .collect(Collectors.toList());
try {
- mapEntries = Futures.allAsList(mapEntryFutures).get();
+ Futures.allAsList(targetToDataFutures).get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
context.setCancelled();
- return ImmutableMap.of();
+ return;
} catch (ExecutionException e) {
IssueOutput.error("Could not build C resolve configurations: " + e).submit(context);
- LOG.error("Could not build C resolve configurations", e);
- return ImmutableMap.of();
+ logger.error("Could not build C resolve configurations", e);
+ return;
}
-
- for (MapEntry mapEntry : mapEntries) {
- // Skip over labels that don't have C configuration data.
- if (mapEntry != null) {
- newResolveConfigurations.put(mapEntry.targetKey, mapEntry.configuration);
- }
- }
- return newResolveConfigurations.build();
+ findEquivalenceClasses(
+ context,
+ project,
+ blazeProjectData.workspacePathResolver,
+ targetToData,
+ oldConfigurationData,
+ builder);
});
}
+ private static void findEquivalenceClasses(
+ BlazeContext context,
+ Project project,
+ WorkspacePathResolver workspacePathResolver,
+ Map<TargetKey, BlazeResolveConfigurationData> targetToData,
+ BlazeConfigurationResolverResult oldConfigurationData,
+ BlazeConfigurationResolverResult.Builder builder) {
+ Map<BlazeResolveConfigurationData, BlazeResolveConfiguration> dataToConfiguration =
+ new HashMap<>();
+ Multimap<BlazeResolveConfigurationData, TargetKey> dataEquivalenceClasses =
+ ArrayListMultimap.create();
+ int reused = 0;
+ for (Map.Entry<TargetKey, BlazeResolveConfigurationData> entry : targetToData.entrySet()) {
+ TargetKey target = entry.getKey();
+ BlazeResolveConfigurationData data = entry.getValue();
+ if (!dataToConfiguration.containsKey(data)) {
+ BlazeResolveConfiguration configuration;
+ if (oldConfigurationData.uniqueResolveConfigurations.containsKey(data)) {
+ configuration = oldConfigurationData.uniqueResolveConfigurations.get(data);
+ reused++;
+ } else {
+ configuration =
+ BlazeResolveConfiguration.createForTargets(
+ project, workspacePathResolver, data, ImmutableList.of(target));
+ }
+ dataToConfiguration.put(data, configuration);
+ }
+ dataEquivalenceClasses.put(data, target);
+ }
+ ImmutableMap.Builder<TargetKey, BlazeResolveConfiguration> targetToConfiguration =
+ ImmutableMap.builder();
+ for (Map.Entry<BlazeResolveConfigurationData, Collection<TargetKey>> entry :
+ dataEquivalenceClasses.asMap().entrySet()) {
+ BlazeResolveConfigurationData data = entry.getKey();
+ Collection<TargetKey> targets = entry.getValue();
+ BlazeResolveConfiguration configuration = dataToConfiguration.get(data);
+ configuration.representMultipleTargets(targets);
+ for (TargetKey targetKey : targets) {
+ targetToConfiguration.put(targetKey, configuration);
+ }
+ }
+ context.output(
+ PrintOutput.log(
+ String.format(
+ "%s unique C configurations (%s reused), %s C targets",
+ dataEquivalenceClasses.keySet().size(), reused, dataEquivalenceClasses.size())));
+ builder.setConfigurationMap(targetToConfiguration.build());
+ builder.setUniqueConfigurations(ImmutableMap.copyOf(dataToConfiguration));
+ }
+
private static <T> ListenableFuture<T> submit(Callable<T> callable) {
return BlazeExecutor.getInstance().submit(callable);
}
@Nullable
- private MapEntry createResolveConfiguration(
+ private BlazeResolveConfigurationData createResolveConfiguration(
TargetIdeInfo target,
ImmutableMap<TargetKey, CToolchainIdeInfo> toolchainLookupMap,
ImmutableMap<File, VirtualFile> headerRoots,
ImmutableMap<CToolchainIdeInfo, BlazeCompilerSettings> compilerSettingsMap,
- BlazeProjectData blazeProjectData,
- CompilerInfoCache compilerInfoCache) {
+ CompilerInfoCache compilerInfoCache,
+ ExecutionRootPathResolver executionRootPathResolver) {
TargetKey targetKey = target.key;
+ CIdeInfo cIdeInfo = target.cIdeInfo;
+ if (cIdeInfo == null) {
+ return null;
+ }
CToolchainIdeInfo toolchainIdeInfo = toolchainLookupMap.get(targetKey);
if (toolchainIdeInfo == null) {
return null;
@@ -280,177 +426,76 @@
if (compilerSettings == null) {
return null;
}
- BlazeResolveConfiguration config =
- BlazeResolveConfiguration.createConfigurationForTarget(
- project,
- new ExecutionRootPathResolver(
- Blaze.getBuildSystem(project),
- WorkspaceRoot.fromProject(project),
- blazeProjectData.blazeInfo.getExecutionRoot(),
- blazeProjectData.workspacePathResolver),
- blazeProjectData.workspacePathResolver,
- headerRoots,
- blazeProjectData.targetMap.get(targetKey),
- toolchainIdeInfo,
- compilerSettings,
- compilerInfoCache);
- if (config == null) {
- return null;
- }
- return new MapEntry(targetKey, config);
+ return BlazeResolveConfigurationData.create(
+ project,
+ executionRootPathResolver,
+ headerRoots,
+ cIdeInfo,
+ toolchainIdeInfo,
+ compilerSettings,
+ compilerInfoCache);
}
- private static ImmutableMap<CToolchainIdeInfo, BlazeCompilerSettings> buildCompilerSettingsMap(
- BlazeContext context,
- Project project,
- ImmutableMap<TargetKey, CToolchainIdeInfo> toolchainLookupMap,
- WorkspacePathResolver workspacePathResolver) {
- Set<CToolchainIdeInfo> toolchains =
- toolchainLookupMap.values().stream().distinct().collect(Collectors.toSet());
- List<ListenableFuture<Map.Entry<CToolchainIdeInfo, BlazeCompilerSettings>>>
- compilerSettingsFutures = new ArrayList<>();
- for (CToolchainIdeInfo toolchain : toolchains) {
- compilerSettingsFutures.add(
- submit(
- () -> {
- BlazeCompilerSettings settings =
- createBlazeCompilerSettings(project, toolchain, workspacePathResolver);
- if (settings == null) {
- return null;
- }
- return new SimpleImmutableEntry<>(toolchain, settings);
- }));
+ @Nullable
+ private static BlazeConfigurationResolverDiff computeConfigurationDiff(
+ BlazeProjectData blazeProjectData,
+ ImmutableMap<TargetKey, BlazeResolveConfiguration> newConfigs,
+ ImmutableMap<TargetKey, BlazeResolveConfiguration> oldConfigs) {
+ if (oldConfigs.isEmpty()) {
+ return null;
}
- ImmutableMap.Builder<CToolchainIdeInfo, BlazeCompilerSettings> compilerSettingsMap =
- ImmutableMap.builder();
+ List<ListenableFuture<List<VirtualFile>>> fileResolveFutures = new ArrayList<>();
+ for (Map.Entry<TargetKey, BlazeResolveConfiguration> entry : newConfigs.entrySet()) {
+ TargetKey targetKey = entry.getKey();
+ BlazeResolveConfiguration newConfiguration = entry.getValue();
+ BlazeResolveConfiguration oldConfiguration = oldConfigs.get(targetKey);
+ if (newConfiguration != oldConfiguration) {
+ fileResolveFutures.add(
+ submit(
+ () ->
+ changedFilesForTarget(
+ blazeProjectData.targetMap,
+ blazeProjectData.artifactLocationDecoder,
+ targetKey)));
+ }
+ }
+ ImmutableSet.Builder<VirtualFile> changedFiles = ImmutableSet.builder();
try {
- List<Map.Entry<CToolchainIdeInfo, BlazeCompilerSettings>> createdSettings =
- Futures.allAsList(compilerSettingsFutures).get();
- for (Map.Entry<CToolchainIdeInfo, BlazeCompilerSettings> createdSetting : createdSettings) {
- if (createdSetting != null) {
- compilerSettingsMap.put(createdSetting);
- }
+ for (List<VirtualFile> changedFilesForTarget : Futures.allAsList(fileResolveFutures).get()) {
+ changedFiles.addAll(changedFilesForTarget);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
- context.setCancelled();
+ return null;
} catch (ExecutionException e) {
- IssueOutput.error("Could not build C compiler settings map: " + e).submit(context);
- LOG.error("Could not build C compiler settings map", e);
- }
- return compilerSettingsMap.build();
- }
-
- @Nullable
- private static BlazeCompilerSettings createBlazeCompilerSettings(
- Project project,
- CToolchainIdeInfo toolchainIdeInfo,
- WorkspacePathResolver workspacePathResolver) {
- File compilerWrapper = getCompilerWrapper(toolchainIdeInfo, workspacePathResolver);
- if (compilerWrapper == null) {
+ logger.error("Error getting changed files", e);
return null;
}
- 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);
- return new BlazeCompilerSettings(
- project, compilerWrapper, compilerWrapper, cFlagsBuilder.build(), cppFlagsBuilder.build());
+ return new BlazeConfigurationResolverDiff(
+ changedFiles.build(), hasRemovedTargets(newConfigs, oldConfigs));
}
- @Nullable
- private static File getCompilerWrapper(
- CToolchainIdeInfo toolchainIdeInfo, WorkspacePathResolver workspacePathResolver) {
- File cppExecutable = toolchainIdeInfo.cppExecutable.getAbsoluteOrRelativeFile();
- if (cppExecutable != null && !cppExecutable.isAbsolute()) {
- cppExecutable = workspacePathResolver.resolveToFile(cppExecutable.getPath());
- }
- if (cppExecutable == null) {
- LOG.warn(
- String.format(
- "Unable to find compiler executable: %s for toolchain %s",
- toolchainIdeInfo.cppExecutable.toString(), toolchainIdeInfo));
- return null;
- }
- return createCompilerExecutableWrapper(cppExecutable);
- }
-
- /**
- * 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;
+ private static List<VirtualFile> changedFilesForTarget(
+ TargetMap targetMap, ArtifactLocationDecoder locationDecoder, TargetKey targetKey) {
+ List<VirtualFile> changedFilesForTarget = new ArrayList<>();
+ for (ArtifactLocation sourceLocation : targetMap.get(targetKey).sources) {
+ File sourceFile = locationDecoder.decode(sourceLocation);
+ VirtualFile virtualFile = getVirtualFile(sourceFile);
+ if (virtualFile != null) {
+ changedFilesForTarget.add(virtualFile);
}
- 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");
+ }
+ return changedFilesForTarget;
+ }
- try (PrintWriter pw = new PrintWriter(blazeCompilerWrapper, UTF_8.name())) {
- compilerWrapperScriptLines.forEach(pw::println);
+ private static boolean hasRemovedTargets(
+ Map<TargetKey, BlazeResolveConfiguration> newConfigs,
+ Map<TargetKey, BlazeResolveConfiguration> oldConfigs) {
+ for (TargetKey oldKey : oldConfigs.keySet()) {
+ if (!newConfigs.containsKey(oldKey)) {
+ return true;
}
- return blazeCompilerWrapper;
- } catch (IOException e) {
- return null;
}
- }
-
- @Nullable
- public OCResolveConfiguration getConfigurationForFile(VirtualFile sourceFile) {
- SourceToTargetMap sourceToTargetMap = SourceToTargetMap.getInstance(project);
- ImmutableCollection<TargetKey> targetsForSourceFile =
- sourceToTargetMap.getRulesForSourceFile(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.
- TargetKey targetKey = targetsForSourceFile.stream().min(TargetKey::compareTo).orElse(null);
- assert (targetKey != null);
-
- return resolveConfigurations.get(targetKey);
- }
-
- ImmutableList<? extends OCResolveConfiguration> getAllConfigurations() {
- return resolveConfigurations.values().asList();
+ return false;
}
}
diff --git a/cpp/src/com/google/idea/blaze/cpp/BlazeConfigurationResolverDiff.java b/cpp/src/com/google/idea/blaze/cpp/BlazeConfigurationResolverDiff.java
new file mode 100644
index 0000000..bb1e0df
--- /dev/null
+++ b/cpp/src/com/google/idea/blaze/cpp/BlazeConfigurationResolverDiff.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.intellij.openapi.vfs.VirtualFile;
+import javax.annotation.concurrent.Immutable;
+
+/** Summary of differences between two {@link BlazeConfigurationResolver}s. */
+@Immutable
+final class BlazeConfigurationResolverDiff {
+
+ private final ImmutableSet<VirtualFile> changedFiles;
+ private final boolean hasRemovedTargets;
+
+ BlazeConfigurationResolverDiff(
+ ImmutableSet<VirtualFile> changedFiles, boolean hasRemovedTargets) {
+ this.changedFiles = changedFiles;
+ this.hasRemovedTargets = hasRemovedTargets;
+ }
+
+ ImmutableSet<VirtualFile> getChangedFiles() {
+ return changedFiles;
+ }
+
+ boolean hasChanges() {
+ return hasRemovedTargets || !changedFiles.isEmpty();
+ }
+}
diff --git a/cpp/src/com/google/idea/blaze/cpp/BlazeConfigurationResolverResult.java b/cpp/src/com/google/idea/blaze/cpp/BlazeConfigurationResolverResult.java
new file mode 100644
index 0000000..6d95662
--- /dev/null
+++ b/cpp/src/com/google/idea/blaze/cpp/BlazeConfigurationResolverResult.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.Preconditions;
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.idea.blaze.base.ideinfo.CToolchainIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
+import com.google.idea.blaze.base.targetmaps.SourceToTargetMap;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VfsUtilCore;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.jetbrains.cidr.lang.workspace.OCResolveConfiguration;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * Resolve configuration maps, etc. obtained from running the {@link BlazeConfigurationResolver}.
+ */
+@Immutable
+final class BlazeConfigurationResolverResult {
+
+ private final Project project;
+
+ // Multiple target keys may map to the same resolve configuration.
+ final ImmutableMap<TargetKey, BlazeResolveConfiguration> configurationMap;
+ final ImmutableMap<BlazeResolveConfigurationData, BlazeResolveConfiguration>
+ uniqueResolveConfigurations;
+ final ImmutableMap<CToolchainIdeInfo, BlazeCompilerSettings> compilerSettings;
+ @Nullable final BlazeConfigurationResolverDiff resolverDiff;
+
+ BlazeConfigurationResolverResult(
+ Project project,
+ ImmutableMap<TargetKey, BlazeResolveConfiguration> configurationMap,
+ ImmutableMap<BlazeResolveConfigurationData, BlazeResolveConfiguration>
+ uniqueResolveConfigurations,
+ ImmutableMap<CToolchainIdeInfo, BlazeCompilerSettings> compilerSettings,
+ @Nullable BlazeConfigurationResolverDiff resolverDiff) {
+ this.project = project;
+ this.configurationMap = configurationMap;
+ this.uniqueResolveConfigurations = uniqueResolveConfigurations;
+ this.compilerSettings = compilerSettings;
+ this.resolverDiff = resolverDiff;
+ }
+
+ static Builder builder(Project project) {
+ return new Builder(project);
+ }
+
+ static BlazeConfigurationResolverResult empty(Project project) {
+ return builder(project).build();
+ }
+
+ @Nullable
+ OCResolveConfiguration getConfigurationForFile(VirtualFile sourceFile) {
+ SourceToTargetMap sourceToTargetMap = SourceToTargetMap.getInstance(project);
+ ImmutableCollection<TargetKey> targetsForSourceFile =
+ sourceToTargetMap.getRulesForSourceFile(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.
+ TargetKey targetKey = targetsForSourceFile.stream().min(TargetKey::compareTo).orElse(null);
+ Preconditions.checkNotNull(targetKey);
+
+ return configurationMap.get(targetKey);
+ }
+
+ ImmutableList<BlazeResolveConfiguration> getAllConfigurations() {
+ return uniqueResolveConfigurations.values().asList();
+ }
+
+ /**
+ * The difference between the latest resolver result and the previous one, if known. Returns null
+ * if unknown (or there is no previous result).
+ */
+ @Nullable
+ BlazeConfigurationResolverDiff getConfigurationDiff() {
+ return resolverDiff;
+ }
+
+ static class Builder {
+ final Project project;
+ ImmutableMap<TargetKey, BlazeResolveConfiguration> configurationMap = ImmutableMap.of();
+ ImmutableMap<BlazeResolveConfigurationData, BlazeResolveConfiguration> uniqueConfigurations =
+ ImmutableMap.of();
+ ImmutableMap<CToolchainIdeInfo, BlazeCompilerSettings> compilerSettings = ImmutableMap.of();
+ @Nullable BlazeConfigurationResolverDiff resolverDiff;
+
+ public Builder(Project project) {
+ this.project = project;
+ }
+
+ BlazeConfigurationResolverResult build() {
+ return new BlazeConfigurationResolverResult(
+ project, configurationMap, uniqueConfigurations, compilerSettings, resolverDiff);
+ }
+
+ void setConfigurationMap(ImmutableMap<TargetKey, BlazeResolveConfiguration> configurationMap) {
+ this.configurationMap = configurationMap;
+ }
+
+ void setUniqueConfigurations(
+ ImmutableMap<BlazeResolveConfigurationData, BlazeResolveConfiguration>
+ uniqueConfigurations) {
+ this.uniqueConfigurations = uniqueConfigurations;
+ }
+
+ void setCompilerSettings(
+ ImmutableMap<CToolchainIdeInfo, BlazeCompilerSettings> compilerSettings) {
+ this.compilerSettings = compilerSettings;
+ }
+
+ void setResolveDiff(@Nullable BlazeConfigurationResolverDiff resolverDiff) {
+ this.resolverDiff = resolverDiff;
+ }
+ }
+}
diff --git a/cpp/src/com/google/idea/blaze/cpp/BlazeConfigurationToolchainResolver.java b/cpp/src/com/google/idea/blaze/cpp/BlazeConfigurationToolchainResolver.java
new file mode 100644
index 0000000..9ba2448
--- /dev/null
+++ b/cpp/src/com/google/idea/blaze/cpp/BlazeConfigurationToolchainResolver.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.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.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
+import com.google.idea.blaze.base.ideinfo.TargetMap;
+import com.google.idea.blaze.base.model.primitives.Kind;
+import com.google.idea.blaze.base.model.primitives.LanguageClass;
+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.workspace.WorkspacePathResolver;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.io.FileUtil;
+import com.jetbrains.cidr.toolchains.CompilerInfoCache;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.AbstractMap.SimpleImmutableEntry;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
+
+/**
+ * Converts {@link CToolchainIdeInfo} to interfaces used by {@link
+ * com.jetbrains.cidr.lang.workspace.OCResolveConfiguration}
+ */
+final class BlazeConfigurationToolchainResolver {
+ private static final Logger logger =
+ Logger.getInstance(BlazeConfigurationToolchainResolver.class);
+
+ private BlazeConfigurationToolchainResolver() {}
+
+ /** Returns the toolchain used by each target */
+ static ImmutableMap<TargetKey, CToolchainIdeInfo> buildToolchainLookupMap(
+ BlazeContext context, TargetMap targetMap) {
+ return Scope.push(
+ context,
+ childContext -> {
+ childContext.push(new TimingScope("Build toolchain lookup map"));
+
+ Map<TargetKey, CToolchainIdeInfo> toolchains = Maps.newLinkedHashMap();
+ for (TargetIdeInfo target : targetMap.targets()) {
+ CToolchainIdeInfo cToolchainIdeInfo = target.cToolchainIdeInfo;
+ if (cToolchainIdeInfo != null) {
+ toolchains.put(target.key, cToolchainIdeInfo);
+ }
+ }
+
+ ImmutableMap.Builder<TargetKey, CToolchainIdeInfo> lookupTable = ImmutableMap.builder();
+ for (TargetIdeInfo target : targetMap.targets()) {
+ if (target.kind.languageClass != LanguageClass.C || target.kind == Kind.CC_TOOLCHAIN) {
+ continue;
+ }
+ List<TargetKey> toolchainDeps =
+ target
+ .dependencies
+ .stream()
+ .map(dep -> dep.targetKey)
+ .filter(toolchains::containsKey)
+ .collect(Collectors.toList());
+ if (toolchainDeps.size() != 1) {
+ issueToolchainWarning(context, target, toolchainDeps);
+ }
+ if (!toolchainDeps.isEmpty()) {
+ TargetKey toolchainKey = toolchainDeps.get(0);
+ CToolchainIdeInfo toolchainInfo = toolchains.get(toolchainKey);
+ lookupTable.put(target.key, toolchainInfo);
+ } else {
+ CToolchainIdeInfo arbitraryToolchain = Iterables.getFirst(toolchains.values(), null);
+ if (arbitraryToolchain != null) {
+ lookupTable.put(target.key, arbitraryToolchain);
+ }
+ }
+ }
+ return lookupTable.build();
+ });
+ }
+
+ private static void issueToolchainWarning(
+ BlazeContext context, TargetIdeInfo target, List<TargetKey> toolchainDeps) {
+ String warningMessage =
+ String.format(
+ "cc target %s does not depend on exactly 1 cc toolchain. " + " Found %d toolchains.",
+ target.key, toolchainDeps.size());
+ if (usesAppleCcToolchain(target)) {
+ logger.warn(warningMessage + " (apple_cc_toolchain)");
+ } else {
+ IssueOutput.warn(warningMessage).submit(context);
+ }
+ }
+
+ private static boolean usesAppleCcToolchain(TargetIdeInfo target) {
+ return target
+ .dependencies
+ .stream()
+ .anyMatch(dep -> dep.targetKey.label.toString().startsWith("//tools/osx/crosstool"));
+ }
+
+ /** Returns the compiler settings for each toolchain. */
+ static ImmutableMap<CToolchainIdeInfo, BlazeCompilerSettings> buildCompilerSettingsMap(
+ BlazeContext context,
+ Project project,
+ WorkspaceRoot workspaceRoot,
+ ImmutableMap<TargetKey, CToolchainIdeInfo> toolchainLookupMap,
+ WorkspacePathResolver workspacePathResolver,
+ CompilerInfoCache compilerInfoCache,
+ ImmutableMap<CToolchainIdeInfo, BlazeCompilerSettings> oldCompilerSettings) {
+ return Scope.push(
+ context,
+ childContext -> {
+ childContext.push(new TimingScope("Build compiler settings map"));
+ return doBuildCompilerSettingsMap(
+ context,
+ project,
+ workspaceRoot,
+ toolchainLookupMap,
+ workspacePathResolver,
+ compilerInfoCache,
+ oldCompilerSettings);
+ });
+ }
+
+ private static ImmutableMap<CToolchainIdeInfo, BlazeCompilerSettings> doBuildCompilerSettingsMap(
+ BlazeContext context,
+ Project project,
+ WorkspaceRoot workspaceRoot,
+ ImmutableMap<TargetKey, CToolchainIdeInfo> toolchainLookupMap,
+ WorkspacePathResolver workspacePathResolver,
+ CompilerInfoCache compilerInfoCache,
+ ImmutableMap<CToolchainIdeInfo, BlazeCompilerSettings> oldCompilerSettings) {
+ Set<CToolchainIdeInfo> toolchains =
+ toolchainLookupMap.values().stream().distinct().collect(Collectors.toSet());
+ List<ListenableFuture<Map.Entry<CToolchainIdeInfo, BlazeCompilerSettings>>>
+ compilerSettingsFutures = new ArrayList<>();
+ for (CToolchainIdeInfo toolchain : toolchains) {
+ compilerSettingsFutures.add(
+ submit(
+ () -> {
+ File cppExecutable = resolveCompilerExecutable(toolchain, workspacePathResolver);
+ if (cppExecutable == null) {
+ logger.warn(
+ String.format(
+ "Unable to find compiler executable: %s for toolchain %s",
+ toolchain.cppExecutable.toString(), toolchain));
+ return null;
+ }
+ String compilerVersion =
+ CompilerVersionChecker.getInstance()
+ .checkCompilerVersion(workspaceRoot, cppExecutable);
+ if (compilerVersion == null) {
+ logger.warn(
+ String.format(
+ "Unable to determine version of compiler: %s for toolchain %s",
+ cppExecutable, toolchain));
+ return null;
+ }
+ BlazeCompilerSettings oldSettings = oldCompilerSettings.get(toolchain);
+ if (oldSettings != null
+ && oldSettings.getCompilerVersion().equals(compilerVersion)) {
+ return new SimpleImmutableEntry<>(toolchain, oldSettings);
+ }
+ BlazeCompilerSettings settings =
+ createBlazeCompilerSettings(
+ project, toolchain, cppExecutable, compilerVersion, compilerInfoCache);
+ if (settings == null) {
+ return null;
+ }
+ return new SimpleImmutableEntry<>(toolchain, settings);
+ }));
+ }
+ ImmutableMap.Builder<CToolchainIdeInfo, BlazeCompilerSettings> compilerSettingsMap =
+ ImmutableMap.builder();
+ try {
+ List<Map.Entry<CToolchainIdeInfo, BlazeCompilerSettings>> createdSettings =
+ Futures.allAsList(compilerSettingsFutures).get();
+ for (Map.Entry<CToolchainIdeInfo, BlazeCompilerSettings> createdSetting : createdSettings) {
+ if (createdSetting != null) {
+ compilerSettingsMap.put(createdSetting);
+ }
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ context.setCancelled();
+ } catch (ExecutionException e) {
+ IssueOutput.error("Could not build C compiler settings map: " + e).submit(context);
+ logger.error("Could not build C compiler settings map", e);
+ }
+ return compilerSettingsMap.build();
+ }
+
+ @Nullable
+ private static BlazeCompilerSettings createBlazeCompilerSettings(
+ Project project,
+ CToolchainIdeInfo toolchainIdeInfo,
+ File cppExecutable,
+ String compilerVersion,
+ CompilerInfoCache compilerInfoCache) {
+ File compilerWrapper = createCompilerExecutableWrapper(cppExecutable);
+ if (compilerWrapper == null) {
+ return null;
+ }
+ 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);
+ return new BlazeCompilerSettings(
+ project,
+ compilerWrapper,
+ compilerWrapper,
+ cFlagsBuilder.build(),
+ cppFlagsBuilder.build(),
+ compilerVersion,
+ compilerInfoCache);
+ }
+
+ @Nullable
+ private static File resolveCompilerExecutable(
+ CToolchainIdeInfo toolchainIdeInfo, WorkspacePathResolver workspacePathResolver) {
+ File cppExecutable = toolchainIdeInfo.cppExecutable.getAbsoluteOrRelativeFile();
+ if (cppExecutable != null && !cppExecutable.isAbsolute()) {
+ cppExecutable = workspacePathResolver.resolveToFile(cppExecutable.getPath());
+ }
+ return cppExecutable;
+ }
+
+ /**
+ * 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)) {
+ logger.warn("Unable to make compiler wrapper script executable: " + blazeCompilerWrapper);
+ 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, UTF_8.name())) {
+ compilerWrapperScriptLines.forEach(pw::println);
+ }
+ return blazeCompilerWrapper;
+ } catch (IOException e) {
+ logger.warn(
+ "Unable to write compiler wrapper script executable: " + blazeCompilerExecutableFile, e);
+ return null;
+ }
+ }
+
+ private static <T> ListenableFuture<T> submit(Callable<T> callable) {
+ return BlazeExecutor.getInstance().submit(callable);
+ }
+}
diff --git a/cpp/src/com/google/idea/blaze/cpp/BlazeCppAutoImportHelper.java b/cpp/src/com/google/idea/blaze/cpp/BlazeCppAutoImportHelper.java
index 4a63ee9..562615c 100644
--- a/cpp/src/com/google/idea/blaze/cpp/BlazeCppAutoImportHelper.java
+++ b/cpp/src/com/google/idea/blaze/cpp/BlazeCppAutoImportHelper.java
@@ -18,64 +18,78 @@
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.HeadersSearchRoot;
import com.jetbrains.cidr.lang.workspace.headerRoots.IncludedHeadersRoot;
+import java.util.List;
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.
+ * CLion's auto-import suggestions result in include paths relative to the current file (CPP-7593,
+ * CPP-6369). 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 CLion 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;
+ return rootAndConfiguration.getConfiguration() instanceof BlazeResolveConfiguration;
}
/**
- * Search in project header roots only. All other cases are covered by CLion's default
+ * Search in the configuration's 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,
+ 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;
- }
+ // Check system headers of library roots first. Project roots may include the workspace root,
+ // and the system headers might be under the workspace root as well.
+ ImportSpecification specification =
+ findMatchingRoot(
+ fileToImport,
+ rootAndConfiguration.getLibraryHeadersRoots().getRoots(),
+ /* asUserHeader= */ false);
+ if (specification != null && !processor.process(specification)) {
+ return false;
}
+ specification =
+ findMatchingRoot(
+ fileToImport,
+ rootAndConfiguration.getProjectHeadersRoots().getRoots(),
+ /* asUserHeader= */ true);
+ return specification == null || processor.process(specification);
+ }
- for (PsiFileSystemItem root : rootAndConfiguration.getProjectHeadersRoots().getRoots()) {
+ @Nullable
+ private static ImportSpecification findMatchingRoot(
+ VirtualFile fileToImport, List<HeadersSearchRoot> roots, boolean asUserHeader) {
+ for (HeadersSearchRoot root : roots) {
if (!(root instanceof IncludedHeadersRoot)) {
continue;
}
+ IncludedHeadersRoot includedHeadersRoot = (IncludedHeadersRoot) root;
+ if (asUserHeader != includedHeadersRoot.isUserHeaders()) {
+ 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 new ImportSpecification(
+ relativePath,
+ asUserHeader
+ ? ImportSpecification.Kind.USER_HEADER_SEARCH_PATH
+ : ImportSpecification.Kind.SYSTEM_HEADER_SEARCH_PATH);
}
- return true;
+ return null;
}
}
diff --git a/cpp/src/com/google/idea/blaze/cpp/BlazeCppSymbolRebuildSyncListener.java b/cpp/src/com/google/idea/blaze/cpp/BlazeCppSymbolRebuildSyncListener.java
index fb9e190..f49311a 100644
--- a/cpp/src/com/google/idea/blaze/cpp/BlazeCppSymbolRebuildSyncListener.java
+++ b/cpp/src/com/google/idea/blaze/cpp/BlazeCppSymbolRebuildSyncListener.java
@@ -21,16 +21,20 @@
import com.google.idea.blaze.base.settings.BlazeImportSettings;
import com.google.idea.blaze.base.sync.BlazeSyncParams.SyncMode;
import com.google.idea.blaze.base.sync.SyncListener;
+import com.google.idea.common.experiments.BoolExperiment;
import com.google.idea.sdkcompat.cidr.OCWorkspaceModificationTrackersCompatUtils;
import com.google.idea.sdkcompat.transactions.Transactions;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
+import com.jetbrains.cidr.lang.symbols.symtable.OCSymbolTablesBuildingActivity;
import com.jetbrains.cidr.lang.workspace.OCWorkspace;
-import com.jetbrains.cidr.lang.workspace.OCWorkspaceManager;
/** Runs after sync, triggering a rebuild of the symbol tables. */
public class BlazeCppSymbolRebuildSyncListener extends SyncListener.Adapter {
+ private static final BoolExperiment prefillCidrCaches =
+ new BoolExperiment("prefill.cidr.caches.on.startup", true);
+
@Override
public void onSyncComplete(
Project project,
@@ -40,15 +44,25 @@
BlazeProjectData blazeProjectData,
SyncMode syncMode,
SyncResult syncResult) {
-
- OCWorkspace workspace = OCWorkspaceManager.getWorkspace(project);
+ OCWorkspace workspace = OCWorkspaceProvider.getWorkspace(project);
if (!(workspace instanceof BlazeCWorkspace)) {
return;
}
- rebuildSymbolTables(project);
+ if (syncMode == SyncMode.INCREMENTAL || syncMode == SyncMode.PARTIAL) {
+ BlazeConfigurationResolverDiff resolverDiff =
+ ((BlazeCWorkspace) workspace).getConfigurationDiff();
+ if (resolverDiff != null) {
+ incrementallyUpdateSymbolTables(project, resolverDiff);
+ return;
+ }
+ }
+ loadOrRebuildSymbolTables(project);
+ if (syncMode == SyncMode.STARTUP && prefillCidrCaches.getValue()) {
+ CidrCacheFiller.prefillCaches(project);
+ }
}
- private static void rebuildSymbolTables(Project project) {
+ private static void loadOrRebuildSymbolTables(Project project) {
Transactions.submitTransactionAndWait(
() ->
ApplicationManager.getApplication()
@@ -57,4 +71,24 @@
OCWorkspaceModificationTrackersCompatUtils.incrementModificationCounts(
project)));
}
+
+ private static void incrementallyUpdateSymbolTables(
+ Project project, BlazeConfigurationResolverDiff resolverDiff) {
+ Transactions.submitTransactionAndWait(
+ () -> {
+ ApplicationManager.getApplication()
+ .runWriteAction(
+ () -> {
+ if (resolverDiff.hasChanges()) {
+ OCWorkspaceModificationTrackersCompatUtils.partialIncModificationCounts(
+ project);
+ }
+ OCSymbolTablesBuildingActivity.getInstance(project)
+ .getModificationTracker()
+ .incModificationCount();
+ });
+ OCSymbolTablesBuildingActivity.getInstance(project)
+ .buildSymbolsForFiles(resolverDiff.getChangedFiles());
+ });
+ }
}
diff --git a/cpp/src/com/google/idea/blaze/cpp/BlazeLanguageKindCalculatorHelper.java b/cpp/src/com/google/idea/blaze/cpp/BlazeLanguageKindCalculatorHelper.java
index 254c94a..9ad6514 100644
--- a/cpp/src/com/google/idea/blaze/cpp/BlazeLanguageKindCalculatorHelper.java
+++ b/cpp/src/com/google/idea/blaze/cpp/BlazeLanguageKindCalculatorHelper.java
@@ -35,10 +35,13 @@
public OCLanguageKind getLanguageByExtension(Project project, String name) {
if (Blaze.isBlazeProject(project)) {
String extension = FileUtilRt.getExtension(name);
- if (extension.equalsIgnoreCase("c")) {
+ if (CFileExtensions.C_FILE_EXTENSIONS.contains(extension)) {
return OCLanguageKind.C;
}
- if (extension.equalsIgnoreCase("cc")) {
+ if (CFileExtensions.CXX_FILE_EXTENSIONS.contains(extension)) {
+ return OCLanguageKind.CPP;
+ }
+ if (CFileExtensions.CXX_ONLY_HEADER_EXTENSIONS.contains(extension)) {
return OCLanguageKind.CPP;
}
}
diff --git a/cpp/src/com/google/idea/blaze/cpp/BlazeResolveConfiguration.java b/cpp/src/com/google/idea/blaze/cpp/BlazeResolveConfiguration.java
index 6581b12..78c4890 100644
--- a/cpp/src/com/google/idea/blaze/cpp/BlazeResolveConfiguration.java
+++ b/cpp/src/com/google/idea/blaze/cpp/BlazeResolveConfiguration.java
@@ -15,31 +15,15 @@
*/
package com.google.idea.blaze.cpp;
-import com.google.common.collect.ImmutableCollection;
+import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Maps;
-import com.google.idea.blaze.base.ideinfo.CIdeInfo;
-import com.google.idea.blaze.base.ideinfo.CToolchainIdeInfo;
-import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
import com.google.idea.blaze.base.ideinfo.TargetKey;
-import com.google.idea.blaze.base.ideinfo.TargetMap;
-import com.google.idea.blaze.base.model.primitives.ExecutionRootPath;
-import com.google.idea.blaze.base.model.primitives.Kind;
-import com.google.idea.blaze.base.model.primitives.LanguageClass;
-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.workspace.ExecutionRootPathResolver;
import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver;
+import com.google.idea.sdkcompat.cidr.OCCompilerMacrosAdapter;
import com.google.idea.sdkcompat.cidr.OCResolveConfigurationAdapter;
-import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
-import com.intellij.openapi.util.UserDataHolderBase;
import com.intellij.openapi.vfs.VirtualFile;
import com.jetbrains.cidr.lang.OCFileTypeHelpers;
import com.jetbrains.cidr.lang.OCLanguageKind;
@@ -49,206 +33,65 @@
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.Collection;
import java.util.List;
-import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
-import java.util.stream.Collectors;
import javax.annotation.Nullable;
-final class BlazeResolveConfiguration extends UserDataHolderBase
- implements OCResolveConfigurationAdapter {
+/** Blaze implementation of {@link OCResolveConfiguration}. */
+final class BlazeResolveConfiguration extends OCResolveConfigurationAdapter {
- private static final Logger logger = Logger.getInstance(BlazeResolveConfiguration.class);
- private final ExecutionRootPathResolver executionRootPathResolver;
+ private final Project project;
+ private final ConcurrentMap<Pair<OCLanguageKind, VirtualFile>, HeaderRoots>
+ libraryIncludeRootsCache = new ConcurrentHashMap<>();
private final WorkspacePathResolver workspacePathResolver;
- /* project, label are protected instead of private just so v145 can access */
- protected final Project project;
- protected final TargetKey targetKey;
+ private final BlazeResolveConfigurationData configurationData;
- private final ImmutableList<HeadersSearchRoot> cLibraryIncludeRoots;
- private final ImmutableList<HeadersSearchRoot> cppLibraryIncludeRoots;
- private final HeaderRoots projectIncludeRoots;
- private final ConcurrentMap<Pair<OCLanguageKind, VirtualFile>, HeaderRoots> libraryIncludeRoots =
- new ConcurrentHashMap<>();
+ private String displayNameIdentifier;
- private final CompilerInfoCache compilerInfoCache;
- private final BlazeCompilerMacros compilerMacros;
- private final BlazeCompilerSettings compilerSettings;
- private final CToolchainIdeInfo toolchainIdeInfo;
-
- @Nullable
- public static BlazeResolveConfiguration createConfigurationForTarget(
+ private BlazeResolveConfiguration(
Project project,
- ExecutionRootPathResolver executionRootPathResolver,
WorkspacePathResolver workspacePathResolver,
- ImmutableMap<File, VirtualFile> headerRoots,
- TargetIdeInfo target,
- CToolchainIdeInfo toolchainIdeInfo,
- BlazeCompilerSettings compilerSettings,
- CompilerInfoCache compilerInfoCache) {
- CIdeInfo cIdeInfo = target.cIdeInfo;
- if (cIdeInfo == null) {
- return null;
- }
-
- ImmutableSet.Builder<ExecutionRootPath> systemIncludesBuilder = ImmutableSet.builder();
- systemIncludesBuilder.addAll(cIdeInfo.transitiveSystemIncludeDirectories);
- systemIncludesBuilder.addAll(toolchainIdeInfo.builtInIncludeDirectories);
- systemIncludesBuilder.addAll(toolchainIdeInfo.unfilteredToolchainSystemIncludes);
-
- ImmutableSet.Builder<ExecutionRootPath> userIncludesBuilder = ImmutableSet.builder();
- userIncludesBuilder.addAll(cIdeInfo.transitiveIncludeDirectories);
- userIncludesBuilder.addAll(cIdeInfo.localIncludeDirectories);
-
- ImmutableSet.Builder<ExecutionRootPath> userQuoteIncludesBuilder = ImmutableSet.builder();
- userQuoteIncludesBuilder.addAll(cIdeInfo.transitiveQuoteIncludeDirectories);
-
- ImmutableList.Builder<String> defines = ImmutableList.builder();
- defines.addAll(cIdeInfo.transitiveDefines);
- defines.addAll(cIdeInfo.localDefines);
-
- ImmutableMap<String, String> features = ImmutableMap.of();
-
- return new BlazeResolveConfiguration(
- project,
- executionRootPathResolver,
- workspacePathResolver,
- headerRoots,
- target.key,
- systemIncludesBuilder.build(),
- systemIncludesBuilder.build(),
- userQuoteIncludesBuilder.build(),
- userIncludesBuilder.build(),
- userIncludesBuilder.build(),
- defines.build(),
- features,
- compilerSettings,
- compilerInfoCache,
- toolchainIdeInfo);
- }
-
- static ImmutableMap<TargetKey, CToolchainIdeInfo> buildToolchainLookupMap(
- BlazeContext context, TargetMap targetMap) {
- return Scope.push(
- context,
- childContext -> {
- childContext.push(new TimingScope("Build toolchain lookup map"));
-
- Map<TargetKey, CToolchainIdeInfo> toolchains = Maps.newLinkedHashMap();
- for (TargetIdeInfo target : targetMap.targets()) {
- CToolchainIdeInfo cToolchainIdeInfo = target.cToolchainIdeInfo;
- if (cToolchainIdeInfo != null) {
- toolchains.put(target.key, cToolchainIdeInfo);
- }
- }
-
- ImmutableMap.Builder<TargetKey, CToolchainIdeInfo> lookupTable = ImmutableMap.builder();
- for (TargetIdeInfo target : targetMap.targets()) {
- if (target.kind.getLanguageClass() != LanguageClass.C
- || target.kind == Kind.CC_TOOLCHAIN) {
- continue;
- }
- List<TargetKey> toolchainDeps =
- target
- .dependencies
- .stream()
- .map(dep -> dep.targetKey)
- .filter(toolchains::containsKey)
- .collect(Collectors.toList());
- if (toolchainDeps.size() != 1) {
- issueToolchainWarning(context, target, toolchainDeps);
- }
- if (!toolchainDeps.isEmpty()) {
- TargetKey toolchainKey = toolchainDeps.get(0);
- CToolchainIdeInfo toolchainInfo = toolchains.get(toolchainKey);
- lookupTable.put(target.key, toolchainInfo);
- } else {
- CToolchainIdeInfo arbitraryToolchain = Iterables.getFirst(toolchains.values(), null);
- if (arbitraryToolchain != null) {
- lookupTable.put(target.key, arbitraryToolchain);
- }
- }
- }
- return lookupTable.build();
- });
- }
-
- private static void issueToolchainWarning(
- BlazeContext context, TargetIdeInfo target, List<TargetKey> toolchainDeps) {
- String warningMessage =
- String.format(
- "cc target %s does not depend on exactly 1 cc toolchain. " + " Found %d toolchains.",
- target.key, toolchainDeps.size());
- if (usesAppleCcToolchain(target)) {
- logger.warn(warningMessage + " (apple_cc_toolchain)");
- } else {
- IssueOutput.warn(warningMessage).submit(context);
- }
- }
-
- private static boolean usesAppleCcToolchain(TargetIdeInfo target) {
- return target
- .dependencies
- .stream()
- .anyMatch(dep -> dep.targetKey.label.toString().startsWith("//tools/osx/crosstool"));
- }
-
- public BlazeResolveConfiguration(
- Project project,
- ExecutionRootPathResolver executionRootPathResolver,
- WorkspacePathResolver workspacePathResolver,
- ImmutableMap<File, VirtualFile> headerRoots,
- TargetKey targetKey,
- ImmutableCollection<ExecutionRootPath> cSystemIncludeDirs,
- ImmutableCollection<ExecutionRootPath> cppSystemIncludeDirs,
- ImmutableCollection<ExecutionRootPath> quoteIncludeDirs,
- ImmutableCollection<ExecutionRootPath> cIncludeDirs,
- ImmutableCollection<ExecutionRootPath> cppIncludeDirs,
- ImmutableCollection<String> defines,
- ImmutableMap<String, String> features,
- BlazeCompilerSettings compilerSettings,
- CompilerInfoCache compilerInfoCache,
- CToolchainIdeInfo toolchainIdeInfo) {
- this.executionRootPathResolver = executionRootPathResolver;
- this.workspacePathResolver = workspacePathResolver;
+ BlazeResolveConfigurationData configurationData) {
this.project = project;
- this.targetKey = targetKey;
- this.toolchainIdeInfo = toolchainIdeInfo;
+ this.workspacePathResolver = workspacePathResolver;
+ this.configurationData = configurationData;
+ }
- ImmutableList.Builder<HeadersSearchRoot> cIncludeRootsBuilder = ImmutableList.builder();
- collectHeaderRoots(headerRoots, cIncludeRootsBuilder, cIncludeDirs, true /* isUserHeader */);
- collectHeaderRoots(
- headerRoots, cIncludeRootsBuilder, cSystemIncludeDirs, false /* isUserHeader */);
- this.cLibraryIncludeRoots = cIncludeRootsBuilder.build();
+ static BlazeResolveConfiguration createForTargets(
+ Project project,
+ WorkspacePathResolver workspacePathResolver,
+ BlazeResolveConfigurationData configurationData,
+ Collection<TargetKey> targets) {
+ BlazeResolveConfiguration result =
+ new BlazeResolveConfiguration(project, workspacePathResolver, configurationData);
+ result.representMultipleTargets(targets);
+ return result;
+ }
- ImmutableList.Builder<HeadersSearchRoot> cppIncludeRootsBuilder = ImmutableList.builder();
- collectHeaderRoots(
- headerRoots, cppIncludeRootsBuilder, cppIncludeDirs, true /* isUserHeader */);
- collectHeaderRoots(
- headerRoots, cppIncludeRootsBuilder, cppSystemIncludeDirs, false /* isUserHeader */);
- this.cppLibraryIncludeRoots = cppIncludeRootsBuilder.build();
-
- ImmutableList.Builder<HeadersSearchRoot> quoteIncludeRootsBuilder = ImmutableList.builder();
- collectHeaderRoots(
- headerRoots, quoteIncludeRootsBuilder, quoteIncludeDirs, true /* isUserHeader */);
- this.projectIncludeRoots = new HeaderRoots(quoteIncludeRootsBuilder.build());
-
- this.compilerSettings = compilerSettings;
- this.compilerInfoCache = compilerInfoCache;
- this.compilerMacros =
- new BlazeCompilerMacros(project, compilerInfoCache, compilerSettings, defines, features);
+ /**
+ * Indicate that this single configuration represents N other targets. NOTE: this changes the
+ * identifier used by {@link #compareTo}, so any data structures using compareTo must be
+ * invalidated when this changes.
+ */
+ void representMultipleTargets(Collection<TargetKey> targets) {
+ TargetKey minTargetKey = targets.stream().min(TargetKey::compareTo).orElse(null);
+ Preconditions.checkNotNull(minTargetKey);
+ String minTarget = minTargetKey.toString();
+ if (targets.size() == 1) {
+ displayNameIdentifier = minTarget;
+ } else {
+ displayNameIdentifier =
+ String.format("%s and %d other target(s)", minTarget, targets.size() - 1);
+ }
}
@Override
@@ -262,7 +105,36 @@
@Override
public String getDisplayName(boolean shorten) {
- return targetKey.toString();
+ return displayNameIdentifier;
+ }
+
+ @Override
+ public int compareTo(OCResolveConfiguration other) {
+ // This is a bit of a weak comparison -- it just uses the display name (ignoring case)
+ // and doesn't compare the actual fields of the configuration.
+ // It should only be used for simple things like sorting for the UI.
+ return OCWorkspaceUtil.compareConfigurations(this, other);
+ }
+
+ @Override
+ public int hashCode() {
+ // There should only be one configuration per target, and the display name is derived
+ // from a target
+ return Objects.hashCode(displayNameIdentifier);
+ }
+
+ @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;
}
@Nullable
@@ -305,7 +177,7 @@
@Override
public HeaderRoots getProjectHeadersRoots() {
- return projectIncludeRoots;
+ return configurationData.projectIncludeRoots;
}
@Override
@@ -316,20 +188,20 @@
languageKind = getLanguageKind(sourceFile);
}
Pair<OCLanguageKind, VirtualFile> cacheKey = Pair.create(languageKind, sourceFile);
- return libraryIncludeRoots.computeIfAbsent(
+ return libraryIncludeRootsCache.computeIfAbsent(
cacheKey,
key -> {
OCLanguageKind lang = key.first;
VirtualFile source = key.second;
ImmutableSet.Builder<HeadersSearchRoot> roots = ImmutableSet.builder();
if (lang == OCLanguageKind.C) {
- roots.addAll(cLibraryIncludeRoots);
+ roots.addAll(configurationData.cLibraryIncludeRoots);
} else {
- roots.addAll(cppLibraryIncludeRoots);
+ roots.addAll(configurationData.cppLibraryIncludeRoots);
}
CidrCompilerResult<CompilerInfoCache.Entry> compilerInfoCacheHolder =
- compilerInfoCache.getCompilerInfoCache(project, compilerSettings, lang, source);
+ configurationData.compilerSettings.getCompilerInfo(lang, source);
CompilerInfoCache.Entry compilerInfo = compilerInfoCacheHolder.getResult();
if (compilerInfo != null) {
roots.addAll(compilerInfo.headerSearchPaths);
@@ -338,91 +210,55 @@
});
}
- private void collectHeaderRoots(
- ImmutableMap<File, VirtualFile> virtualFileCache,
- ImmutableList.Builder<HeadersSearchRoot> roots,
- ImmutableCollection<ExecutionRootPath> paths,
- boolean isUserHeader) {
- for (ExecutionRootPath executionRootPath : paths) {
- ImmutableList<File> possibleDirectories =
- executionRootPathResolver.resolveToIncludeDirectories(executionRootPath);
- for (File f : possibleDirectories) {
- VirtualFile vf = virtualFileCache.get(f);
- if (vf != null) {
- roots.add(new IncludedHeadersRoot(project, vf, false /* recursive */, isUserHeader));
- }
- }
- }
- }
-
@Override
- public OCCompilerMacros getCompilerMacros() {
- return compilerMacros;
+ public OCCompilerMacrosAdapter getCompilerMacros() {
+ return configurationData.compilerMacros;
}
@Override
public OCCompilerSettings getCompilerSettings() {
- return compilerSettings;
+ return configurationData.compilerSettings;
}
@Override
public Object getIndexingCluster() {
- return toolchainIdeInfo;
+ return configurationData.toolchainIdeInfo;
}
- @Override
- public int compareTo(OCResolveConfiguration other) {
- return OCWorkspaceUtil.compareConfigurations(this, other);
- }
-
- @Override
- public int hashCode() {
- // There should only be one configuration per target.
- return Objects.hash(targetKey);
- }
-
- @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;
- }
-
- /* This function is part of the v162/v163 plugin APIs. */
+ /* #api163 */
@Nullable
@Override
public VirtualFile getPrecompiledHeader() {
return null;
}
- /* This function is part of the v162/v163 plugin APIs. */
+ /* #api163 */
@Override
public OCLanguageKind getPrecompiledLanguageKind() {
return getMaximumLanguageKind();
}
- /* This function is part of the v171 plugin API. */
+ /* #api171 */
@Override
public Set<VirtualFile> getPrecompiledHeaders() {
return ImmutableSet.of();
}
- /* This function is part of the v171 plugin API. */
+ /* #api171 */
@Override
public List<VirtualFile> getPrecompiledHeaders(OCLanguageKind kind, VirtualFile sourceFile) {
return ImmutableList.of();
}
- /* This function is part of the v171 plugin API. */
+ /* #api171 */
@Override
public Collection<VirtualFile> getSources() {
return ImmutableList.of();
}
+
+ /* #api172 */
+ @Override
+ public String getPreprocessorDefines(OCLanguageKind kind, VirtualFile virtualFile) {
+ return configurationData.compilerMacros.getAllDefines(kind, virtualFile);
+ }
}
diff --git a/cpp/src/com/google/idea/blaze/cpp/BlazeResolveConfigurationData.java b/cpp/src/com/google/idea/blaze/cpp/BlazeResolveConfigurationData.java
new file mode 100644
index 0000000..df63c2d
--- /dev/null
+++ b/cpp/src/com/google/idea/blaze/cpp/BlazeResolveConfigurationData.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.ImmutableSet;
+import com.google.idea.blaze.base.ideinfo.CIdeInfo;
+import com.google.idea.blaze.base.ideinfo.CToolchainIdeInfo;
+import com.google.idea.blaze.base.model.primitives.ExecutionRootPath;
+import com.google.idea.blaze.base.sync.workspace.ExecutionRootPathResolver;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+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.Objects;
+
+/** Data used by a {@link BlazeResolveConfiguration}. */
+final class BlazeResolveConfigurationData {
+
+ final BlazeCompilerSettings compilerSettings;
+
+ final ImmutableList<HeadersSearchRoot> cLibraryIncludeRoots;
+ final ImmutableList<HeadersSearchRoot> cppLibraryIncludeRoots;
+ final HeaderRoots projectIncludeRoots;
+ final BlazeCompilerMacros compilerMacros;
+ final CToolchainIdeInfo toolchainIdeInfo;
+
+ static BlazeResolveConfigurationData create(
+ Project project,
+ ExecutionRootPathResolver executionRootPathResolver,
+ ImmutableMap<File, VirtualFile> headerRoots,
+ CIdeInfo cIdeInfo,
+ CToolchainIdeInfo toolchainIdeInfo,
+ BlazeCompilerSettings compilerSettings,
+ CompilerInfoCache compilerInfoCache) {
+ ImmutableSet.Builder<ExecutionRootPath> systemIncludesBuilder = ImmutableSet.builder();
+ systemIncludesBuilder.addAll(cIdeInfo.transitiveSystemIncludeDirectories);
+ systemIncludesBuilder.addAll(toolchainIdeInfo.builtInIncludeDirectories);
+ systemIncludesBuilder.addAll(toolchainIdeInfo.unfilteredToolchainSystemIncludes);
+
+ ImmutableSet.Builder<ExecutionRootPath> userIncludesBuilder = ImmutableSet.builder();
+ userIncludesBuilder.addAll(cIdeInfo.transitiveIncludeDirectories);
+ userIncludesBuilder.addAll(cIdeInfo.localIncludeDirectories);
+
+ ImmutableSet.Builder<ExecutionRootPath> userQuoteIncludesBuilder = ImmutableSet.builder();
+ userQuoteIncludesBuilder.addAll(cIdeInfo.transitiveQuoteIncludeDirectories);
+
+ ImmutableList.Builder<String> defines = ImmutableList.builder();
+ defines.addAll(cIdeInfo.transitiveDefines);
+ defines.addAll(cIdeInfo.localDefines);
+
+ ImmutableMap<String, String> features = ImmutableMap.of();
+
+ return new BlazeResolveConfigurationData(
+ project,
+ executionRootPathResolver,
+ headerRoots,
+ systemIncludesBuilder.build(),
+ systemIncludesBuilder.build(),
+ userQuoteIncludesBuilder.build(),
+ userIncludesBuilder.build(),
+ userIncludesBuilder.build(),
+ defines.build(),
+ features,
+ compilerSettings,
+ compilerInfoCache,
+ toolchainIdeInfo);
+ }
+
+ private BlazeResolveConfigurationData(
+ Project project,
+ ExecutionRootPathResolver executionRootPathResolver,
+ ImmutableMap<File, VirtualFile> headerRoots,
+ ImmutableCollection<ExecutionRootPath> cSystemIncludeDirs,
+ ImmutableCollection<ExecutionRootPath> cppSystemIncludeDirs,
+ ImmutableCollection<ExecutionRootPath> quoteIncludeDirs,
+ ImmutableCollection<ExecutionRootPath> cIncludeDirs,
+ ImmutableCollection<ExecutionRootPath> cppIncludeDirs,
+ ImmutableCollection<String> defines,
+ ImmutableMap<String, String> features,
+ BlazeCompilerSettings compilerSettings,
+ CompilerInfoCache compilerInfoCache,
+ CToolchainIdeInfo toolchainIdeInfo) {
+ this.toolchainIdeInfo = toolchainIdeInfo;
+
+ HeaderRootsCollector headerRootsCollector =
+ new HeaderRootsCollector(project, executionRootPathResolver, headerRoots);
+ ImmutableList.Builder<HeadersSearchRoot> cIncludeRootsBuilder = ImmutableList.builder();
+ headerRootsCollector.collectHeaderRoots(
+ cIncludeRootsBuilder, cIncludeDirs, true /* isUserHeader */);
+ headerRootsCollector.collectHeaderRoots(
+ cIncludeRootsBuilder, cSystemIncludeDirs, false /* isUserHeader */);
+ this.cLibraryIncludeRoots = cIncludeRootsBuilder.build();
+
+ ImmutableList.Builder<HeadersSearchRoot> cppIncludeRootsBuilder = ImmutableList.builder();
+ headerRootsCollector.collectHeaderRoots(
+ cppIncludeRootsBuilder, cppIncludeDirs, true /* isUserHeader */);
+ headerRootsCollector.collectHeaderRoots(
+ cppIncludeRootsBuilder, cppSystemIncludeDirs, false /* isUserHeader */);
+ this.cppLibraryIncludeRoots = cppIncludeRootsBuilder.build();
+
+ ImmutableList.Builder<HeadersSearchRoot> quoteIncludeRootsBuilder = ImmutableList.builder();
+ headerRootsCollector.collectHeaderRoots(
+ quoteIncludeRootsBuilder, quoteIncludeDirs, true /* isUserHeader */);
+ this.projectIncludeRoots = new HeaderRoots(quoteIncludeRootsBuilder.build());
+
+ this.compilerSettings = compilerSettings;
+ this.compilerMacros =
+ new BlazeCompilerMacros(project, compilerInfoCache, compilerSettings, defines, features);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (!(other instanceof BlazeResolveConfigurationData)) {
+ return false;
+ }
+ BlazeResolveConfigurationData otherData = (BlazeResolveConfigurationData) other;
+ return this.cLibraryIncludeRoots.equals(otherData.cLibraryIncludeRoots)
+ && this.cppLibraryIncludeRoots.equals(otherData.cppLibraryIncludeRoots)
+ && this.projectIncludeRoots.equals(otherData.projectIncludeRoots)
+ && this.compilerMacros.equals(otherData.compilerMacros)
+ && this.toolchainIdeInfo.equals(otherData.toolchainIdeInfo)
+ && this.compilerSettings
+ .getCompilerVersion()
+ .equals(otherData.compilerSettings.getCompilerVersion());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ cLibraryIncludeRoots,
+ cppLibraryIncludeRoots,
+ projectIncludeRoots,
+ compilerMacros,
+ toolchainIdeInfo,
+ compilerSettings.getCompilerVersion());
+ }
+
+ private static class HeaderRootsCollector {
+ private final Project project;
+ private final ExecutionRootPathResolver executionRootPathResolver;
+ private final ImmutableMap<File, VirtualFile> virtualFileCache;
+
+ HeaderRootsCollector(
+ Project project,
+ ExecutionRootPathResolver executionRootPathResolver,
+ ImmutableMap<File, VirtualFile> virtualFileCache) {
+ this.project = project;
+ this.executionRootPathResolver = executionRootPathResolver;
+ this.virtualFileCache = virtualFileCache;
+ }
+
+ void collectHeaderRoots(
+ ImmutableList.Builder<HeadersSearchRoot> roots,
+ ImmutableCollection<ExecutionRootPath> paths,
+ boolean isUserHeader) {
+ for (ExecutionRootPath executionRootPath : paths) {
+ ImmutableList<File> possibleDirectories =
+ executionRootPathResolver.resolveToIncludeDirectories(executionRootPath);
+ for (File f : possibleDirectories) {
+ VirtualFile vf = virtualFileCache.get(f);
+ if (vf != null) {
+ roots.add(new IncludedHeadersRoot(project, vf, false /* recursive */, isUserHeader));
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/cpp/src/com/google/idea/blaze/cpp/CFileExtensions.java b/cpp/src/com/google/idea/blaze/cpp/CFileExtensions.java
new file mode 100644
index 0000000..7b9287c
--- /dev/null
+++ b/cpp/src/com/google/idea/blaze/cpp/CFileExtensions.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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;
+
+/** C/C++ file extensions categorized. */
+final class CFileExtensions {
+
+ // See https://bazel.build/versions/master/docs/be/c-cpp.html#cc_binary.srcs
+ static final ImmutableSet<String> C_FILE_EXTENSIONS = ImmutableSet.of("c");
+ static final ImmutableSet<String> CXX_FILE_EXTENSIONS =
+ ImmutableSet.of("cc", "cpp", "cxx", "c++", "C");
+
+ static final ImmutableSet<String> CXX_ONLY_HEADER_EXTENSIONS =
+ ImmutableSet.of("hh", "hpp", "hxx");
+ private static final ImmutableSet<String> SHARED_HEADER_EXTENSIONS = ImmutableSet.of("h", "inc");
+
+ static final ImmutableSet<String> SOURCE_EXTENSIONS =
+ ImmutableSet.<String>builder().addAll(C_FILE_EXTENSIONS).addAll(CXX_FILE_EXTENSIONS).build();
+ static final ImmutableSet<String> HEADER_EXTENSIONS =
+ ImmutableSet.<String>builder()
+ .addAll(SHARED_HEADER_EXTENSIONS)
+ .addAll(CXX_ONLY_HEADER_EXTENSIONS)
+ .build();
+
+ private CFileExtensions() {}
+}
diff --git a/cpp/src/com/google/idea/blaze/cpp/CPrefetchFileSource.java b/cpp/src/com/google/idea/blaze/cpp/CPrefetchFileSource.java
index 4e63e71..911c714 100644
--- a/cpp/src/com/google/idea/blaze/cpp/CPrefetchFileSource.java
+++ b/cpp/src/com/google/idea/blaze/cpp/CPrefetchFileSource.java
@@ -16,25 +16,66 @@
package com.google.idea.blaze.cpp;
import com.google.common.collect.ImmutableSet;
+import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.model.primitives.LanguageClass;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
import com.google.idea.blaze.base.prefetch.PrefetchFileSource;
import com.google.idea.blaze.base.projectview.ProjectViewSet;
+import com.google.idea.blaze.base.sync.projectview.ImportRoots;
+import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
+import com.google.idea.common.experiments.BoolExperiment;
import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.io.FileUtil;
import java.io.File;
-import java.util.Collection;
import java.util.Set;
+import java.util.function.Predicate;
/** Causes C files to become prefetched. */
public class CPrefetchFileSource implements PrefetchFileSource {
+
+ private static final BoolExperiment prefetchAllCppSources =
+ new BoolExperiment("prefetch.all.cpp.sources", true);
+
@Override
public void addFilesToPrefetch(
Project project,
ProjectViewSet projectViewSet,
+ ImportRoots importRoots,
BlazeProjectData blazeProjectData,
- Collection<File> files) {}
+ Set<File> files) {
+ if (!blazeProjectData.workspaceLanguageSettings.isLanguageActive(LanguageClass.C)
+ || !prefetchAllCppSources.getValue()) {
+ return;
+ }
+ // Prefetch all non-project CPP header files encountered during sync
+ Predicate<ArtifactLocation> shouldPrefetch =
+ location -> {
+ if (!location.isSource || location.isExternal) {
+ return false;
+ }
+ WorkspacePath path = WorkspacePath.createIfValid(location.relativePath);
+ if (path == null || importRoots.containsWorkspacePath(path)) {
+ return false;
+ }
+ String extension = FileUtil.getExtension(path.relativePath());
+ return CFileExtensions.HEADER_EXTENSIONS.contains(extension);
+ };
+ ArtifactLocationDecoder decoder = blazeProjectData.artifactLocationDecoder;
+ for (TargetIdeInfo target : blazeProjectData.targetMap.targets()) {
+ if (target.cIdeInfo == null) {
+ continue;
+ }
+ target.sources.stream().filter(shouldPrefetch).map(decoder::decode).forEach(files::add);
+ }
+ }
@Override
- public Set<String> prefetchSrcFileExtensions() {
- return ImmutableSet.of("c", "cc", "cpp", "h", "hh", "hpp");
+ public Set<String> prefetchFileExtensions() {
+ return ImmutableSet.<String>builder()
+ .addAll(CFileExtensions.SOURCE_EXTENSIONS)
+ .addAll(CFileExtensions.HEADER_EXTENSIONS)
+ .build();
}
}
diff --git a/cpp/src/com/google/idea/blaze/cpp/CidrCacheFiller.java b/cpp/src/com/google/idea/blaze/cpp/CidrCacheFiller.java
new file mode 100644
index 0000000..982b389
--- /dev/null
+++ b/cpp/src/com/google/idea/blaze/cpp/CidrCacheFiller.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.Stopwatch;
+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.executor.BlazeExecutor;
+import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.io.VirtualFileSystemProvider;
+import com.google.idea.blaze.base.model.BlazeProjectData;
+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.sync.data.BlazeProjectDataManager;
+import com.google.idea.blaze.base.sync.projectview.ProjectViewTargetImportFilter;
+import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ModalityState;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.progress.ProcessCanceledException;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.progress.util.ProgressIndicatorUtils;
+import com.intellij.openapi.project.DumbModeTask;
+import com.intellij.openapi.project.DumbService;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.EmptyRunnable;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.jetbrains.cidr.lang.preprocessor.OCImportGraph;
+import com.jetbrains.cidr.lang.preprocessor.OCInclusionContextUtil;
+import com.jetbrains.cidr.lang.workspace.OCResolveConfiguration;
+import com.jetbrains.cidr.lang.workspace.OCWorkspace;
+import com.jetbrains.cidr.lang.workspace.OCWorkspaceManager;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Fills some cidr caches after loading/rebuilding symbol tables.
+ *
+ * <p>Namely, some of the code for determining the {@link OCResolveConfiguration} of files depend on
+ * the {@link OCImportGraph}. If the cidr code becomes less dependent on {@link OCImportGraph} we
+ * can remove this step. See upstream bug for potential freezes due to depenence on OCImportGraph
+ * (https://youtrack.jetbrains.com/issue/CPP-10557).
+ */
+class CidrCacheFiller extends DumbModeTask {
+ private static final Logger logger = Logger.getInstance(CidrCacheFiller.class);
+
+ private final Project project;
+ private final ListeningExecutorService executor;
+
+ private CidrCacheFiller(Project project, ListeningExecutorService executor) {
+ this.project = project;
+ this.executor = executor;
+ }
+
+ static void prefillCaches(Project project) {
+ DumbService.getInstance(project)
+ .queueTask(new CidrCacheFiller(project, BlazeExecutor.getInstance().getExecutor()));
+ }
+
+ @Override
+ public void performInDumbMode(ProgressIndicator indicator) {
+ BlazeProjectData blazeProjectData =
+ BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
+ ProjectViewSet projectViewSet = ProjectViewManager.getInstance(project).getProjectViewSet();
+ if (blazeProjectData == null || projectViewSet == null) {
+ return;
+ }
+ OCWorkspace ocWorkspace = OCWorkspaceManager.getWorkspace(project);
+ if (!(ocWorkspace instanceof BlazeCWorkspace)) {
+ return;
+ }
+ Stopwatch timer = Stopwatch.createStarted();
+ indicator.setText("Pre-filling C++ caches");
+ indicator.setIndeterminate(false);
+ indicator.setFraction(0);
+ ProjectViewTargetImportFilter filter =
+ new ProjectViewTargetImportFilter(
+ project, WorkspaceRoot.fromProject(project), projectViewSet);
+ ArtifactLocationDecoder decoder = blazeProjectData.artifactLocationDecoder;
+ Set<File> cSourceFiles = new HashSet<>();
+ for (TargetIdeInfo target : blazeProjectData.targetMap.targets()) {
+ if (target.cIdeInfo == null || !filter.isSourceTarget(target)) {
+ continue;
+ }
+ for (ArtifactLocation sourceLocation : target.sources) {
+ cSourceFiles.add(decoder.decode(sourceLocation));
+ }
+ }
+ int numSources = cSourceFiles.size();
+ AtomicInteger numDone = new AtomicInteger();
+ indicator.setText(String.format("Pre-filling C++ caches (%s files)", numSources));
+ VirtualFileSystemProvider vfsProvider = VirtualFileSystemProvider.getInstance();
+ List<ListenableFuture<?>> futures = new ArrayList<>();
+ for (File sourceFile : cSourceFiles) {
+ futures.add(
+ executor.submit(
+ () -> {
+ VirtualFile sourceRoot = vfsProvider.getSystem().findFileByIoFile(sourceFile);
+ if (sourceRoot == null
+ || OCInclusionContextUtil.isNeedToFindRoot(sourceRoot, project)) {
+ updateIndicator(numDone, indicator, numSources);
+ return;
+ }
+ List<? extends OCResolveConfiguration> configurations =
+ ocWorkspace.getConfigurationsForFile(sourceRoot);
+ if (configurations.isEmpty()) {
+ updateIndicator(numDone, indicator, numSources);
+ return;
+ }
+ OCResolveConfiguration someConfiguration = configurations.get(0);
+ runInReadActionWithWriteActionPriorityWithRetries(
+ () ->
+ OCImportGraph.getAllRootHeaders(someConfiguration, sourceRoot, indicator));
+ updateIndicator(numDone, indicator, numSources);
+ }));
+ }
+ try {
+ Futures.allAsList(futures).get();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return;
+ } catch (ProcessCanceledException e) {
+ return;
+ } catch (ExecutionException e) {
+ logger.warn(e);
+ }
+ logger.info(
+ String.format(
+ "Pre-fill C++ caches took: %s ms for %s files",
+ timer.elapsed(TimeUnit.MILLISECONDS), numSources));
+ }
+
+ private static void updateIndicator(
+ AtomicInteger numDone, ProgressIndicator indicator, int total) {
+ int currentDone = numDone.incrementAndGet();
+ indicator.setFraction((double) currentDone / total);
+ }
+
+ // Use a friendlier read action which will allow write actions to jump in while still
+ // running multiple read actions in parallel.
+ // This is essentially DebuggerUtilImpl#runInReadActionWithWriteActionPriorityWithRetries
+ // but avoid depending on an Impl class for this simple loop.
+ private static void runInReadActionWithWriteActionPriorityWithRetries(Runnable runnable) {
+ while (true) {
+ if (runInReadActionWithWriteActionPriority(runnable)) {
+ return;
+ }
+ }
+ }
+
+ // In #api_171: we can just use ProgressManager#runInReadActionWithWriteActionPriority
+ // instead of these two following methods.
+ private static boolean runInReadActionWithWriteActionPriority(Runnable runnable) {
+ boolean success = ProgressIndicatorUtils.runInReadActionWithWriteActionPriority(runnable);
+ if (!success) {
+ yieldToPendingWriteActions();
+ }
+ return success;
+ }
+
+ private static void yieldToPendingWriteActions() {
+ ApplicationManager.getApplication().invokeAndWait(EmptyRunnable.INSTANCE, ModalityState.any());
+ }
+}
diff --git a/cpp/src/com/google/idea/blaze/cpp/CompilerVersionChecker.java b/cpp/src/com/google/idea/blaze/cpp/CompilerVersionChecker.java
new file mode 100644
index 0000000..5599ff3
--- /dev/null
+++ b/cpp/src/com/google/idea/blaze/cpp/CompilerVersionChecker.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.model.primitives.WorkspaceRoot;
+import com.intellij.openapi.components.ServiceManager;
+import java.io.File;
+import javax.annotation.Nullable;
+
+/** Runs a compiler to check its version. */
+public interface CompilerVersionChecker {
+
+ static CompilerVersionChecker getInstance() {
+ return ServiceManager.getService(CompilerVersionChecker.class);
+ }
+
+ /** Returns the compiler's version string, or null on failure */
+ @Nullable
+ String checkCompilerVersion(WorkspaceRoot workspaceRoot, File cppExecutable);
+}
diff --git a/cpp/src/com/google/idea/blaze/cpp/CompilerVersionCheckerImpl.java b/cpp/src/com/google/idea/blaze/cpp/CompilerVersionCheckerImpl.java
new file mode 100644
index 0000000..3d7e94c
--- /dev/null
+++ b/cpp/src/com/google/idea/blaze/cpp/CompilerVersionCheckerImpl.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.async.process.ExternalTask;
+import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
+import com.intellij.openapi.diagnostic.Logger;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+
+/** Runs a compiler to check its version. */
+public class CompilerVersionCheckerImpl implements CompilerVersionChecker {
+
+ private static final Logger logger = Logger.getInstance(CompilerVersionCheckerImpl.class);
+
+ @Override
+ public String checkCompilerVersion(WorkspaceRoot workspaceRoot, File cppExecutable) {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ ByteArrayOutputStream errStream = new ByteArrayOutputStream();
+ int result =
+ ExternalTask.builder(workspaceRoot)
+ .args(cppExecutable.toString())
+ // NOTE: this won't work with MSVC if we ever support that (check CToolchainIdeInfo?)
+ .args("--version")
+ .stdout(outputStream)
+ .stderr(errStream)
+ .build()
+ .run();
+ if (result != 0) {
+ logger.warn(String.format("Error getting compiler version: \"%s\"", errStream));
+ return null;
+ }
+ return outputStream.toString();
+ }
+}
diff --git a/cpp/src/com/google/idea/blaze/cpp/MockCompilerVersionChecker.java b/cpp/src/com/google/idea/blaze/cpp/MockCompilerVersionChecker.java
new file mode 100644
index 0000000..e00fcb2
--- /dev/null
+++ b/cpp/src/com/google/idea/blaze/cpp/MockCompilerVersionChecker.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.model.primitives.WorkspaceRoot;
+import java.io.File;
+import javax.annotation.Nullable;
+
+/** {@link CompilerVersionChecker} for tests. */
+public class MockCompilerVersionChecker implements CompilerVersionChecker {
+
+ private String compilerVersion;
+
+ public MockCompilerVersionChecker(String compilerVersion) {
+ this.compilerVersion = compilerVersion;
+ }
+
+ @Nullable
+ @Override
+ public String checkCompilerVersion(WorkspaceRoot workspaceRoot, File cppExecutable) {
+ return compilerVersion;
+ }
+
+ public void setCompilerVersion(String compilerVersion) {
+ this.compilerVersion = compilerVersion;
+ }
+}
diff --git a/cpp/src/com/google/idea/blaze/cpp/OCWorkspaceProvider.java b/cpp/src/com/google/idea/blaze/cpp/OCWorkspaceProvider.java
new file mode 100644
index 0000000..f2e90e3
--- /dev/null
+++ b/cpp/src/com/google/idea/blaze/cpp/OCWorkspaceProvider.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.components.ServiceManager;
+import com.intellij.openapi.project.Project;
+import com.jetbrains.cidr.lang.workspace.OCWorkspace;
+import com.jetbrains.cidr.lang.workspace.OCWorkspaceManager;
+import javax.annotation.Nullable;
+
+/**
+ * An Android-Studio-safe version of {@link OCWorkspaceManager}, which handles the case where no
+ * such manager is available (e.g. because the 'NDK WorkspaceManager Support' plugin isn't enabled).
+ */
+public final class OCWorkspaceProvider {
+
+ private OCWorkspaceProvider() {}
+
+ @Nullable
+ public static OCWorkspace getWorkspace(Project project) {
+ OCWorkspaceManager manager = ServiceManager.getService(project, OCWorkspaceManager.class);
+ return manager != null ? manager.getWorkspace() : null;
+ }
+}
diff --git a/cpp/src/com/google/idea/blaze/cpp/includes/IncludePath.java b/cpp/src/com/google/idea/blaze/cpp/includes/IncludePath.java
new file mode 100644
index 0000000..c223349
--- /dev/null
+++ b/cpp/src/com/google/idea/blaze/cpp/includes/IncludePath.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.includes;
+
+import com.google.auto.value.AutoValue;
+import com.intellij.openapi.util.text.StringUtil;
+import com.jetbrains.cidr.lang.psi.OCIncludeDirective.Delimiters;
+
+/** Information extracted from a #include directive. */
+@AutoValue
+abstract class IncludePath {
+ abstract String headerPath();
+
+ abstract Delimiters headerDelim();
+
+ static IncludePath create(String headerPath, Delimiters headerDelim) {
+ return new AutoValue_IncludePath(headerPath, headerDelim);
+ }
+
+ static IncludePath create(String rawHeaderPath) {
+ if (rawHeaderPath.startsWith(Delimiters.QUOTES.getBeforeText())
+ && rawHeaderPath.endsWith(Delimiters.QUOTES.getAfterText())) {
+ return new AutoValue_IncludePath(
+ StringUtil.trimEnd(
+ StringUtil.trimStart(rawHeaderPath, Delimiters.QUOTES.getBeforeText()),
+ Delimiters.QUOTES.getAfterText()),
+ Delimiters.QUOTES);
+ }
+ if (rawHeaderPath.startsWith(Delimiters.ANGLE_BRACKETS.getBeforeText())
+ && rawHeaderPath.endsWith(Delimiters.ANGLE_BRACKETS.getAfterText())) {
+ return new AutoValue_IncludePath(
+ StringUtil.trimEnd(
+ StringUtil.trimStart(rawHeaderPath, Delimiters.ANGLE_BRACKETS.getBeforeText()),
+ Delimiters.ANGLE_BRACKETS.getAfterText()),
+ Delimiters.ANGLE_BRACKETS);
+ }
+ return new AutoValue_IncludePath(rawHeaderPath, Delimiters.NONE);
+ }
+}
diff --git a/cpp/src/com/google/idea/blaze/cpp/includes/IwyuPragmas.java b/cpp/src/com/google/idea/blaze/cpp/includes/IwyuPragmas.java
new file mode 100644
index 0000000..497efdc
--- /dev/null
+++ b/cpp/src/com/google/idea/blaze/cpp/includes/IwyuPragmas.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.includes;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.psi.PsiComment;
+import com.jetbrains.cidr.lang.editor.OCCommenter;
+import com.jetbrains.cidr.lang.psi.OCFile;
+import com.jetbrains.cidr.lang.psi.OCIncludeDirective;
+import com.jetbrains.cidr.lang.psi.visitors.OCRecursiveVisitor;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Parses IWYU pragmas specified in a given file.
+ *
+ * <p>See:
+ * https://github.com/include-what-you-use/include-what-you-use/blob/master/docs/IWYUPragmas.md
+ */
+final class IwyuPragmas {
+ private static final String IWYU_PREFIX = "IWYU pragma:";
+
+ public final OCFile file;
+ public final Optional<PrivatePragma> privatePragma;
+ public final ImmutableSet<KeepPragma> keeps;
+ public final ImmutableSet<ExportPragma> exports;
+ public final Optional<IncludePath> associatedHeader;
+
+ private IwyuPragmas(
+ OCFile file,
+ Optional<PrivatePragma> privatePragma,
+ ImmutableSet<KeepPragma> keeps,
+ ImmutableSet<ExportPragma> exports,
+ Optional<IncludePath> associatedHeader) {
+ this.file = file;
+ this.privatePragma = privatePragma;
+ this.keeps = keeps;
+ this.exports = exports;
+ this.associatedHeader = associatedHeader;
+ }
+
+ public static IwyuPragmas parse(OCFile file) {
+ BuildingVisitor builder = new BuildingVisitor(file);
+ file.accept(builder);
+ return builder.build();
+ }
+
+ /** Parse pragmas that are trailing comments */
+ interface TrailingPragmaParser {
+ /**
+ * Checks if the pragmaContent is parsed by this parser, and updates the builder state if it is
+ * actually parsed.
+ *
+ * @param builder builder to update on success
+ * @param directive include directive with a trailing pragma comment
+ * @param pragmaContent content from the pragma comment to parse
+ * @return true if handled by this parser
+ */
+ boolean tryParse(BuildingVisitor builder, OCIncludeDirective directive, String pragmaContent);
+ }
+
+ /** Parse pragmas are standalone comments */
+ interface StandalonePragmaParser {
+ /**
+ * Checks if the pragmaContent is parsed by this parser, and updates the builder state if it is
+ * actually parsed.
+ *
+ * @param builder builder to update on success
+ * @param pragmaContent content from the pragma comment to parse
+ * @return true if handled by this parser
+ */
+ boolean tryParse(BuildingVisitor builder, String pragmaContent);
+ }
+
+ /** Represents a "keep" pragma */
+ @AutoValue
+ public abstract static class KeepPragma {
+ /* TODO: keep pragmas can also be attached to a forward include. We don't yet handle that */
+ private static final TrailingPragmaParser PARSER = new Parser();
+
+ abstract IncludePath includePath();
+
+ static KeepPragma create(IncludePath includePath) {
+ return new AutoValue_IwyuPragmas_KeepPragma(includePath);
+ }
+
+ private static class Parser implements TrailingPragmaParser {
+ private static final Pattern KEEP_PATTERN = Pattern.compile("^\\s*keep\\s*$");
+
+ @Override
+ public boolean tryParse(
+ BuildingVisitor builder, OCIncludeDirective directive, String pragmaContent) {
+ Matcher matcher = KEEP_PATTERN.matcher(pragmaContent);
+ if (!matcher.find()) {
+ return false;
+ }
+ builder.keeps.add(
+ KeepPragma.create(
+ IncludePath.create(directive.getReferenceText(), directive.getDelimiters())));
+ return true;
+ }
+ }
+ }
+
+ /** Represents an "export" pragma */
+ @AutoValue
+ public abstract static class ExportPragma {
+ private static final TrailingPragmaParser TRAIL_PARSER = new TrailParser();
+ private static final StandalonePragmaParser RANGE_PARSER = new RangeParser();
+
+ abstract IncludePath includePath();
+
+ static ExportPragma create(IncludePath includePath) {
+ return new AutoValue_IwyuPragmas_ExportPragma(includePath);
+ }
+
+ private static class TrailParser implements TrailingPragmaParser {
+
+ private static final Pattern EXPORT_PATTERN = Pattern.compile("^\\s*export\\s*$");
+
+ @Override
+ public boolean tryParse(
+ BuildingVisitor builder, OCIncludeDirective directive, String pragmaContent) {
+ Matcher matcher = EXPORT_PATTERN.matcher(pragmaContent);
+ if (!matcher.find()) {
+ return false;
+ }
+ builder.exports.add(
+ ExportPragma.create(
+ IncludePath.create(directive.getReferenceText(), directive.getDelimiters())));
+ return true;
+ }
+ }
+
+ private static class RangeParser implements StandalonePragmaParser {
+
+ private static final Pattern BEGIN_PATTERN = Pattern.compile("^\\s*begin_exports\\s*$");
+ private static final Pattern END_PATTERN = Pattern.compile("^\\s*end_exports\\s*$");
+
+ @Override
+ public boolean tryParse(BuildingVisitor builder, String pragmaContent) {
+ Matcher matcher = BEGIN_PATTERN.matcher(pragmaContent);
+ if (matcher.matches()) {
+ builder.includesInRange.clear();
+ builder.collectRange = true;
+ return true;
+ }
+ matcher = END_PATTERN.matcher(pragmaContent);
+ if (matcher.matches()) {
+ builder.collectRange = false;
+ for (OCIncludeDirective directive : builder.includesInRange) {
+ builder.exports.add(
+ ExportPragma.create(
+ IncludePath.create(directive.getReferenceText(), directive.getDelimiters())));
+ }
+ builder.includesInRange.clear();
+ return true;
+ }
+ return false;
+ }
+ }
+ }
+
+ /** Represents the "private" pragma */
+ public static class PrivatePragma {
+ private static final StandalonePragmaParser PARSER = new Parser();
+
+ public final Optional<IncludePath> includeOther;
+
+ PrivatePragma(IncludePath includeOther) {
+ this.includeOther = Optional.of(includeOther);
+ }
+
+ PrivatePragma() {
+ this.includeOther = Optional.empty();
+ }
+
+ private static class Parser implements StandalonePragmaParser {
+ private static final Pattern PRIVATE_PATTERN =
+ Pattern.compile("^\\s*private\\s*(,\\s*include\\s*(?<includename>.*)\\s*)?$");
+
+ @Override
+ public boolean tryParse(BuildingVisitor builder, String pragmaContent) {
+ Matcher matcher = PRIVATE_PATTERN.matcher(pragmaContent);
+ if (!matcher.find()) {
+ return false;
+ }
+ String alternateInclude = matcher.group("includename");
+ if (alternateInclude != null) {
+ builder.privatePragma =
+ Optional.of(new PrivatePragma(IncludePath.create(alternateInclude)));
+ } else {
+ builder.privatePragma = Optional.of(new PrivatePragma());
+ }
+ return true;
+ }
+ }
+ }
+
+ /** Represents the "associated" pragma */
+ public static class AssociatedPragma {
+ private static final TrailingPragmaParser PARSER = new Parser();
+
+ private static class Parser implements TrailingPragmaParser {
+ private static final Pattern ASSOCIATED_PATTERN = Pattern.compile("^\\s*associated\\s*$");
+
+ @Override
+ public boolean tryParse(
+ BuildingVisitor builder, OCIncludeDirective directive, String pragmaContent) {
+ Matcher matcher = ASSOCIATED_PATTERN.matcher(pragmaContent);
+ if (!matcher.find()) {
+ return false;
+ }
+ builder.associatedHeader =
+ Optional.of(
+ IncludePath.create(directive.getReferenceText(), directive.getDelimiters()));
+ return true;
+ }
+ }
+ }
+
+ private static final ImmutableList<TrailingPragmaParser> TRAILING_PARSERS =
+ ImmutableList.of(KeepPragma.PARSER, ExportPragma.TRAIL_PARSER, AssociatedPragma.PARSER);
+ private static final ImmutableList<StandalonePragmaParser> STANDALONE_PARSERS =
+ ImmutableList.of(ExportPragma.RANGE_PARSER, PrivatePragma.PARSER);
+
+ private static class BuildingVisitor extends OCRecursiveVisitor {
+
+ final OCFile file;
+ final OCCommenter commenter;
+
+ Optional<PrivatePragma> privatePragma = Optional.empty();
+ ImmutableSet.Builder<KeepPragma> keeps = ImmutableSet.builder();
+ ImmutableSet.Builder<ExportPragma> exports = ImmutableSet.builder();
+ Optional<IncludePath> associatedHeader = Optional.empty();
+
+ List<OCIncludeDirective> includesInRange = new ArrayList<>();
+ boolean collectRange;
+
+ BuildingVisitor(OCFile file) {
+ this.file = file;
+ this.commenter = new OCCommenter();
+ }
+
+ IwyuPragmas build() {
+ return new IwyuPragmas(file, privatePragma, keeps.build(), exports.build(), associatedHeader);
+ }
+
+ @Override
+ public void visitImportDirective(OCIncludeDirective directive) {
+ if (collectRange) {
+ includesInRange.add(directive);
+ }
+ visitTrailingComments(directive);
+ super.visitImportDirective(directive);
+ }
+
+ @Override
+ public void visitComment(PsiComment comment) {
+ String text = trimCommentContent(comment.getText());
+ if (text.startsWith(IWYU_PREFIX)) {
+ String pragmaContent = StringUtil.trimStart(text, IWYU_PREFIX);
+ for (StandalonePragmaParser parser : STANDALONE_PARSERS) {
+ if (parser.tryParse(this, pragmaContent)) {
+ break;
+ }
+ }
+ }
+ super.visitComment(comment);
+ }
+
+ // In older CIDR implementations, trailing comments are not separate PsiComment nodes. They
+ // are simply part of the "directive content" PsiElement (which also has #include path).
+ // Thus, we have to handle it at the OCIncludeDirective level instead of waiting for
+ // visitComment() to run. In newer CIDR implementations, trailing comments are a separate
+ // PsiComment node, but they are still a child of the OCIncludeDirective, so pragma should be
+ // found in the directive's getText().
+ private void visitTrailingComments(OCIncludeDirective directive) {
+ String fullText = directive.getText();
+ String pathText = directive.getReferenceText();
+ if (pathText.isEmpty()) {
+ return;
+ }
+ String afterPath = fullText.substring(fullText.indexOf(pathText) + pathText.length());
+ OCIncludeDirective.Delimiters delimiters = directive.getDelimiters();
+ int delimIndex = afterPath.indexOf(delimiters.getAfterText());
+ if (delimIndex == -1) {
+ return;
+ }
+ afterPath = afterPath.substring(delimIndex + delimiters.getAfterText().length()).trim();
+ String trimmed = trimCommentContent(afterPath);
+ if (trimmed.startsWith(IWYU_PREFIX)) {
+ String pragmaContent = StringUtil.trimStart(trimmed, IWYU_PREFIX);
+ for (TrailingPragmaParser parser : TRAILING_PARSERS) {
+ if (parser.tryParse(this, directive, pragmaContent)) {
+ break;
+ }
+ }
+ }
+ }
+
+ private String trimCommentContent(String text) {
+ if (text.startsWith(commenter.getLineCommentPrefix())) {
+ return StringUtil.trimStart(text, commenter.getLineCommentPrefix()).trim();
+ } else if (text.startsWith(commenter.getBlockCommentPrefix())) {
+ return StringUtil.trimEnd(
+ StringUtil.trimStart(text, commenter.getBlockCommentPrefix()),
+ commenter.getBlockCommentSuffix())
+ .trim();
+ }
+ return text.trim();
+ }
+ }
+}
diff --git a/cpp/src/com/google/idea/blaze/cpp/syncstatus/BlazeCppSyncStatusEditorTabColorProvider.java b/cpp/src/com/google/idea/blaze/cpp/syncstatus/BlazeCppSyncStatusEditorTabColorProvider.java
new file mode 100644
index 0000000..53fbaf9
--- /dev/null
+++ b/cpp/src/com/google/idea/blaze/cpp/syncstatus/BlazeCppSyncStatusEditorTabColorProvider.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.syncstatus;
+
+import com.intellij.openapi.fileEditor.impl.EditorTabColorProvider;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiManager;
+import com.intellij.ui.JBColor;
+import com.jetbrains.cidr.lang.psi.OCFile;
+import java.awt.Color;
+import javax.annotation.Nullable;
+
+/** Changes the color for unsynced files. */
+public class BlazeCppSyncStatusEditorTabColorProvider 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(Project project, VirtualFile file) {
+ PsiFile psiFile = PsiManager.getInstance(project).findFile(file);
+ if (psiFile instanceof OCFile && SyncStatusHelper.isUnsynced(project, file)) {
+ return UNSYNCED_COLOR;
+ }
+ return null;
+ }
+}
diff --git a/cpp/src/com/google/idea/blaze/cpp/syncstatus/BlazeCppSyncStatusEditorTabTitleProvider.java b/cpp/src/com/google/idea/blaze/cpp/syncstatus/BlazeCppSyncStatusEditorTabTitleProvider.java
new file mode 100644
index 0000000..c3f7d2a
--- /dev/null
+++ b/cpp/src/com/google/idea/blaze/cpp/syncstatus/BlazeCppSyncStatusEditorTabTitleProvider.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.syncstatus;
+
+import com.intellij.openapi.fileEditor.impl.EditorTabTitleProvider;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiManager;
+import com.jetbrains.cidr.lang.psi.OCFile;
+import javax.annotation.Nullable;
+
+/** Changes the tab title for unsynced files. */
+public class BlazeCppSyncStatusEditorTabTitleProvider implements EditorTabTitleProvider {
+ @Nullable
+ @Override
+ public String getEditorTabTitle(Project project, VirtualFile file) {
+ PsiFile psiFile = PsiManager.getInstance(project).findFile(file);
+ if (psiFile instanceof OCFile && SyncStatusHelper.isUnsynced(project, file)) {
+ return file.getPresentableName() + " (unsynced)";
+ }
+ return null;
+ }
+}
diff --git a/cpp/src/com/google/idea/blaze/cpp/syncstatus/BlazeCppSyncStatusFileNodeDecorator.java b/cpp/src/com/google/idea/blaze/cpp/syncstatus/BlazeCppSyncStatusFileNodeDecorator.java
new file mode 100644
index 0000000..db3172c
--- /dev/null
+++ b/cpp/src/com/google/idea/blaze/cpp/syncstatus/BlazeCppSyncStatusFileNodeDecorator.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.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.PsiFileNode;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packageDependencies.ui.PackageDependenciesNode;
+import com.intellij.psi.PsiFile;
+import com.intellij.ui.ColoredTreeCellRenderer;
+import com.intellij.ui.SimpleTextAttributes;
+import com.jetbrains.cidr.lang.psi.OCFile;
+
+/** Grays out any unreachable (from project view targets) C++ files. */
+public class BlazeCppSyncStatusFileNodeDecorator implements ProjectViewNodeDecorator {
+
+ @Override
+ public void decorate(ProjectViewNode node, PresentationData data) {
+ if (!(node instanceof PsiFileNode)) {
+ return;
+ }
+ PsiFile psiFile = ((PsiFileNode) node).getValue();
+ if (!(psiFile instanceof OCFile)) {
+ return;
+ }
+ VirtualFile virtualFile = psiFile.getVirtualFile();
+ if (virtualFile == null) {
+ return;
+ }
+ Project project = node.getProject();
+ if (SyncStatusHelper.isUnsynced(project, virtualFile)) {
+ data.clearText();
+ data.addText(psiFile.getName(), SimpleTextAttributes.GRAY_ATTRIBUTES);
+ data.addText(" (unsynced)", SimpleTextAttributes.GRAY_ATTRIBUTES);
+ }
+ }
+
+ @Override
+ public void decorate(PackageDependenciesNode node, ColoredTreeCellRenderer cellRenderer) {}
+}
diff --git a/cpp/src/com/google/idea/blaze/cpp/syncstatus/SyncStatusHelper.java b/cpp/src/com/google/idea/blaze/cpp/syncstatus/SyncStatusHelper.java
new file mode 100644
index 0000000..1aa61e6
--- /dev/null
+++ b/cpp/src/com/google/idea/blaze/cpp/syncstatus/SyncStatusHelper.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.syncstatus;
+
+import com.google.idea.blaze.base.targetmaps.SourceToTargetMap;
+import com.google.idea.blaze.cpp.BlazeCWorkspace;
+import com.google.idea.blaze.cpp.OCWorkspaceProvider;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ProjectFileIndex;
+import com.intellij.openapi.vfs.VfsUtilCore;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.jetbrains.cidr.lang.workspace.OCWorkspace;
+
+/** Checks if we have sync data for the given C++ file. */
+final class SyncStatusHelper {
+ private SyncStatusHelper() {}
+
+ static boolean isUnsynced(Project project, VirtualFile virtualFile) {
+ if (!virtualFile.isInLocalFileSystem()) {
+ return false;
+ }
+ if (ProjectFileIndex.SERVICE.getInstance(project).getModuleForFile(virtualFile) == null) {
+ return false;
+ }
+ OCWorkspace workspace = OCWorkspaceProvider.getWorkspace(project);
+ if (!(workspace instanceof BlazeCWorkspace)) {
+ // Skip if the project isn't a Blaze project or doesn't have C support enabled anyway.
+ return false;
+ }
+ if (workspace.getConfigurations().isEmpty()) {
+ // The workspace configurations may not have been loaded yet.
+ return false;
+ }
+ SourceToTargetMap sourceToTargetMap = SourceToTargetMap.getInstance(project);
+ return sourceToTargetMap
+ .getRulesForSourceFile(VfsUtilCore.virtualToIoFile(virtualFile))
+ .isEmpty();
+ }
+}
diff --git a/cpp/tests/integrationtests/com/google/idea/blaze/cpp/BlazeCppAutoImportHelperTest.java b/cpp/tests/integrationtests/com/google/idea/blaze/cpp/BlazeCppAutoImportHelperTest.java
new file mode 100644
index 0000000..7750a32
--- /dev/null
+++ b/cpp/tests/integrationtests/com/google/idea/blaze/cpp/BlazeCppAutoImportHelperTest.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.ideinfo.ArtifactLocation;
+import com.google.idea.blaze.base.ideinfo.CIdeInfo;
+import com.google.idea.blaze.base.ideinfo.CToolchainIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetMap;
+import com.google.idea.blaze.base.ideinfo.TargetMapBuilder;
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.model.MockBlazeProjectDataBuilder;
+import com.google.idea.blaze.base.model.MockBlazeProjectDataManager;
+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.TargetExpression;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
+import com.google.idea.blaze.base.projectview.ProjectView;
+import com.google.idea.blaze.base.projectview.ProjectViewSet;
+import com.google.idea.blaze.base.projectview.section.ListSection;
+import com.google.idea.blaze.base.projectview.section.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.scope.BlazeContext;
+import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
+import com.jetbrains.cidr.lang.psi.OCFile;
+import com.jetbrains.cidr.lang.psi.OCReferenceElement;
+import com.jetbrains.cidr.lang.quickfixes.OCImportSymbolFix;
+import com.jetbrains.cidr.lang.symbols.symtable.FileSymbolTablesCache;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests that {@link BlazeCppAutoImportHelper} is able to get the correct form of #include for the
+ * {@link OCImportSymbolFix} quickfix, given typical workspace layouts / location of system headers.
+ */
+@RunWith(JUnit4.class)
+public class BlazeCppAutoImportHelperTest extends BlazeCppIntegrationTestCase {
+
+ @Before
+ public void setup() {
+ createHeaderRoots();
+ registerApplicationService(
+ CompilerVersionChecker.class, new MockCompilerVersionChecker("1234"));
+ }
+
+ @Test
+ public void stlPathsUnderWorkspaceRoot_importStlHeader() {
+ ProjectView projectView = projectView(directories("foo/bar"), targets("//foo/bar:bar"));
+ TargetMap targetMap =
+ TargetMapBuilder.builder()
+ .addTarget(createCcToolchain())
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:bar", Kind.CC_LIBRARY, sources("foo/bar/bar.cc"), sources()))
+ .addTarget(
+ createCcTarget(
+ "//third_party/stl:stl",
+ Kind.CC_LIBRARY,
+ sources(),
+ sources("third_party/stl/vector.h")))
+ .build();
+ // Normally this is <vector> without .h, but we need to trick the file type detector into
+ // realizing that this is an OCFile.
+ OCFile header =
+ createFile(
+ "third_party/stl/vector.h",
+ "namespace std {",
+ "template<typename T> class vector {};",
+ "}");
+ OCFile file = createFile("foo/bar/bar.cc", "std::vector<int> my_vector;");
+
+ resolve(projectView, targetMap, file, header);
+
+ testFixture.openFileInEditor(file.getVirtualFile());
+ OCReferenceElement referenceElement =
+ testFixture.findElementByText("std::vector<int>", OCReferenceElement.class);
+ OCImportSymbolFix fix = new OCImportSymbolFix(referenceElement);
+ assertThat(fix.isAvailable(getProject(), testFixture.getEditor(), file)).isTrue();
+ assertThat(fix.getAutoImportItems()).hasSize(1);
+ assertThat(fix.getAutoImportItems().get(0).getTitleAndLocation().getFirst())
+ .isEqualTo("class 'std::vector'");
+ assertThat(fix.getAutoImportItems().get(0).getTitleAndLocation().getSecond())
+ .isEqualTo("<vector.h>");
+ }
+
+ @Test
+ public void sameDirectory_importUserHeader() {
+ ProjectView projectView = projectView(directories("foo/bar"), targets("//foo/bar:bar"));
+ TargetMap targetMap =
+ TargetMapBuilder.builder()
+ .addTarget(createCcToolchain())
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:bar",
+ Kind.CC_LIBRARY,
+ sources("foo/bar/bar.cc"),
+ sources("foo/bar/test.h")))
+ .build();
+ OCFile header = createFile("foo/bar/test.h", "class SomeClass {};");
+ OCFile file = createFile("foo/bar/bar.cc", "SomeClass* my_class = new SomeClass();");
+
+ resolve(projectView, targetMap, file, header);
+
+ testFixture.openFileInEditor(file.getVirtualFile());
+ OCReferenceElement referenceElement =
+ testFixture.findElementByText("SomeClass*", OCReferenceElement.class);
+ OCImportSymbolFix fix = new OCImportSymbolFix(referenceElement);
+ assertThat(fix.isAvailable(getProject(), testFixture.getEditor(), file)).isTrue();
+ assertThat(fix.getAutoImportItems()).hasSize(1);
+ assertThat(fix.getAutoImportItems().get(0).getTitleAndLocation().getFirst())
+ .isEqualTo("class 'SomeClass'");
+ assertThat(fix.getAutoImportItems().get(0).getTitleAndLocation().getSecond())
+ .isEqualTo("\"foo/bar/test.h\"");
+ }
+
+ @Test
+ public void differentDirectory_importUserHeader() {
+ ProjectView projectView =
+ projectView(directories("foo/bar", "baz"), targets("//foo/bar", "//baz"));
+ TargetMap targetMap =
+ TargetMapBuilder.builder()
+ .addTarget(createCcToolchain())
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:bar", Kind.CC_LIBRARY, sources("foo/bar/bar.cc"), sources()))
+ .addTarget(
+ createCcTarget("//baz:baz", Kind.CC_LIBRARY, sources(""), sources("baz/test.h")))
+ .build();
+ OCFile header = createFile("baz/test.h", "class SomeClass {};");
+ OCFile file = createFile("foo/bar/bar.cc", "SomeClass* my_class = new SomeClass();");
+
+ resolve(projectView, targetMap, file, header);
+
+ testFixture.openFileInEditor(file.getVirtualFile());
+ OCReferenceElement referenceElement =
+ testFixture.findElementByText("SomeClass*", OCReferenceElement.class);
+ OCImportSymbolFix fix = new OCImportSymbolFix(referenceElement);
+ assertThat(fix.isAvailable(getProject(), testFixture.getEditor(), file)).isTrue();
+ assertThat(fix.getAutoImportItems()).hasSize(1);
+ assertThat(fix.getAutoImportItems().get(0).getTitleAndLocation().getFirst())
+ .isEqualTo("class 'SomeClass'");
+ assertThat(fix.getAutoImportItems().get(0).getTitleAndLocation().getSecond())
+ .isEqualTo("\"baz/test.h\"");
+ }
+
+ private static List<ArtifactLocation> sources(String... paths) {
+ return Arrays.stream(paths)
+ .map(path -> ArtifactLocation.builder().setRelativePath(path).setIsSource(true).build())
+ .collect(Collectors.toList());
+ }
+
+ private void createHeaderRoots() {
+ workspace.createDirectory(new WorkspacePath("output/genfiles"));
+ workspace.createDirectory(new WorkspacePath("include/third_party/libxml/_/libxml"));
+ workspace.createDirectory(new WorkspacePath("third_party/stl"));
+ workspace.createDirectory(new WorkspacePath("third_party/lib_that_expects_angle_include"));
+ workspace.createDirectory(new WorkspacePath("third_party/toolchain/include/c++/4.9"));
+ }
+
+ private TargetIdeInfo.Builder createCcTarget(
+ String label, Kind kind, List<ArtifactLocation> sources, List<ArtifactLocation> headers) {
+ TargetIdeInfo.Builder targetInfo =
+ TargetIdeInfo.builder().setLabel(label).setKind(kind).addDependency("//:toolchain");
+ sources.forEach(targetInfo::addSource);
+ return targetInfo.setCInfo(
+ CIdeInfo.builder()
+ .addSources(sources)
+ .addHeaders(headers)
+ .addTransitiveIncludeDirectories(
+ ImmutableList.of(new ExecutionRootPath("include/third_party/libxml/_/libxml")))
+ .addTransitiveQuoteIncludeDirectories(
+ ImmutableList.of(
+ new ExecutionRootPath("."), new ExecutionRootPath("output/genfiles")))
+ .addTransitiveSystemIncludeDirectories(
+ ImmutableList.of(
+ new ExecutionRootPath("third_party/stl"),
+ new ExecutionRootPath("third_party/lib_that_expects_angle_include"))));
+ }
+
+ private static TargetIdeInfo.Builder createCcToolchain() {
+ return TargetIdeInfo.builder()
+ .setLabel("//:toolchain")
+ .setKind(Kind.CC_TOOLCHAIN)
+ .setCToolchainInfo(
+ CToolchainIdeInfo.builder()
+ .setCppExecutable(new ExecutionRootPath("cc"))
+ .addBuiltInIncludeDirectories(
+ ImmutableList.of(
+ new ExecutionRootPath("third_party/toolchain/include/c++/4.9"))));
+ }
+
+ private static ListSection<DirectoryEntry> directories(String... directories) {
+ return ListSection.builder(DirectorySection.KEY)
+ .addAll(
+ Arrays.stream(directories)
+ .map(directory -> DirectoryEntry.include(WorkspacePath.createIfValid(directory)))
+ .collect(Collectors.toList()))
+ .build();
+ }
+
+ private static ListSection<TargetExpression> targets(String... targets) {
+ return ListSection.builder(TargetSection.KEY)
+ .addAll(
+ Arrays.stream(targets).map(TargetExpression::fromString).collect(Collectors.toList()))
+ .build();
+ }
+
+ private static ProjectView projectView(
+ ListSection<DirectoryEntry> directories, ListSection<TargetExpression> targets) {
+ return ProjectView.builder().add(directories).add(targets).build();
+ }
+
+ private MockBlazeProjectDataBuilder projectDataBuilder() {
+ return MockBlazeProjectDataBuilder.builder(workspaceRoot)
+ .setOutputBase(fileSystem.getRootDir() + "/output");
+ }
+
+ private void resolve(ProjectView projectView, TargetMap targetMap, OCFile... files) {
+ BlazeProjectData blazeProjectData = projectDataBuilder().setTargetMap(targetMap).build();
+ registerProjectService(
+ BlazeProjectDataManager.class, new MockBlazeProjectDataManager(blazeProjectData));
+ BlazeCWorkspace.getInstance(getProject())
+ .update(
+ new BlazeContext(),
+ workspaceRoot,
+ ProjectViewSet.builder().add(projectView).build(),
+ blazeProjectData);
+ for (OCFile file : files) {
+ resetFileSymbols(file);
+ }
+ FileSymbolTablesCache.getInstance(getProject()).ensurePendingFilesProcessed();
+ }
+
+ private void resetFileSymbols(OCFile file) {
+ FileSymbolTablesCache.getInstance(getProject()).handleFileChange(file, true);
+ }
+}
diff --git a/cpp/tests/integrationtests/com/google/idea/blaze/cpp/BlazeCppIntegrationTestCase.java b/cpp/tests/integrationtests/com/google/idea/blaze/cpp/BlazeCppIntegrationTestCase.java
new file mode 100644
index 0000000..84b35c1
--- /dev/null
+++ b/cpp/tests/integrationtests/com/google/idea/blaze/cpp/BlazeCppIntegrationTestCase.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static com.jetbrains.cidr.lang.OCLanguage.LANGUAGE_SUPPORT_DISABLED;
+
+import com.google.idea.blaze.base.BlazeIntegrationTestCase;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiFile;
+import com.jetbrains.cidr.lang.psi.OCFile;
+import com.jetbrains.cidr.lang.workspace.OCWorkspace;
+import com.jetbrains.cidr.lang.workspace.OCWorkspaceManager;
+import org.junit.Before;
+
+/** Base C++ test class for integration tests. */
+public class BlazeCppIntegrationTestCase extends BlazeIntegrationTestCase {
+
+ @Before
+ public void enableCppLanguageSupport() throws Throwable {
+ registerProjectService(OCWorkspaceManager.class, new TestOCWorkspaceManager());
+ enableCSupportInIde(getProject());
+ }
+
+ protected OCFile createFile(String relativePath, String... contentLines) {
+ PsiFile file = workspace.createPsiFile(new WorkspacePath(relativePath), contentLines);
+ assertThat(file).isInstanceOf(OCFile.class);
+ return (OCFile) file;
+ }
+
+ private static void enableCSupportInIde(Project project) {
+ OCWorkspace workspace = OCWorkspaceProvider.getWorkspace(project);
+ assertThat(workspace).isNotNull();
+ if (LANGUAGE_SUPPORT_DISABLED.get(project, false)) {
+ LANGUAGE_SUPPORT_DISABLED.set(project, false);
+ }
+ }
+
+ private class TestOCWorkspaceManager extends OCWorkspaceManager {
+ @Override
+ public OCWorkspace getWorkspace() {
+ return BlazeCWorkspace.getInstance(getProject());
+ }
+ }
+}
diff --git a/cpp/tests/integrationtests/com/google/idea/blaze/cpp/includes/IwyuPragmasTest.java b/cpp/tests/integrationtests/com/google/idea/blaze/cpp/includes/IwyuPragmasTest.java
new file mode 100644
index 0000000..b592636
--- /dev/null
+++ b/cpp/tests/integrationtests/com/google/idea/blaze/cpp/includes/IwyuPragmasTest.java
@@ -0,0 +1,452 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.includes;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.idea.blaze.cpp.BlazeCppIntegrationTestCase;
+import com.google.idea.blaze.cpp.includes.IwyuPragmas.ExportPragma;
+import com.google.idea.blaze.cpp.includes.IwyuPragmas.KeepPragma;
+import com.google.idea.blaze.cpp.includes.IwyuPragmas.PrivatePragma;
+import com.jetbrains.cidr.lang.psi.OCFile;
+import com.jetbrains.cidr.lang.psi.OCIncludeDirective.Delimiters;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for parsing {@link IwyuPragmas}. */
+@RunWith(JUnit4.class)
+public class IwyuPragmasTest extends BlazeCppIntegrationTestCase {
+
+ @Test
+ public void noPragmas() {
+ OCFile file =
+ createFile(
+ "bar.cc",
+ "#include \"bar.h\"",
+ "",
+ "#include <memory>",
+ "#include <vector>",
+ "",
+ "#include \"f/foo1.h\"",
+ "#include \"f/foo2.h\"",
+ "#include \"f/foo3.h\" // blah",
+ "",
+ "void bar() {}");
+ IwyuPragmas pragmas = IwyuPragmas.parse(file);
+ assertThat(pragmas.privatePragma.isPresent()).isFalse();
+ assertThat(pragmas.keeps).isEmpty();
+ assertThat(pragmas.exports).isEmpty();
+ assertThat(pragmas.associatedHeader.isPresent()).isFalse();
+ }
+
+ @Test
+ public void incompletePath_parse() {
+ OCFile file = createFile("bar.cc", "#include <memory>", "#include <vecto");
+ IwyuPragmas pragmas = IwyuPragmas.parse(file);
+ assertThat(pragmas.keeps).isEmpty();
+ }
+
+ @Test
+ public void noPathYet_parse() {
+ OCFile file = createFile("bar.cc", "#include <memory>", "#include ");
+ IwyuPragmas pragmas = IwyuPragmas.parse(file);
+ assertThat(pragmas.keeps).isEmpty();
+ }
+
+ @Test
+ public void lineComment_parseKeep() {
+ OCFile file =
+ createFile(
+ "bar.cc",
+ "#include \"bar.h\"",
+ "",
+ "#include <memory>",
+ "#include <vector> // IWYU pragma: keep",
+ "",
+ "#include \"f/foo1.h\" // IWYU pragma: keep",
+ "#include \"f/foo2.h\"",
+ "#include \"f/foo3.h\" // IWYU pragma: keep ",
+ "",
+ "void bar() {}");
+ IwyuPragmas pragmas = IwyuPragmas.parse(file);
+ assertThat(pragmas.keeps)
+ .containsExactly(
+ KeepPragma.create(IncludePath.create("vector", Delimiters.ANGLE_BRACKETS)),
+ KeepPragma.create(IncludePath.create("f/foo1.h", Delimiters.QUOTES)),
+ KeepPragma.create(IncludePath.create("f/foo3.h", Delimiters.QUOTES)));
+ }
+
+ @Test
+ public void blockComment_parseKeep() {
+ OCFile file =
+ createFile(
+ "bar.cc",
+ "#include \"bar.h\"",
+ "",
+ "#include <memory>",
+ "#include <vector> /* IWYU pragma: keep */",
+ "",
+ "#include \"f/foo1.h\" /* IWYU pragma: keep */",
+ "#include \"f/foo2.h\"",
+ "#include \"f/foo3.h\" /* IWYU pragma: keep */",
+ "",
+ "void bar() {}");
+ IwyuPragmas pragmas = IwyuPragmas.parse(file);
+ assertThat(pragmas.keeps)
+ .containsExactly(
+ KeepPragma.create(IncludePath.create("vector", Delimiters.ANGLE_BRACKETS)),
+ KeepPragma.create(IncludePath.create("f/foo1.h", Delimiters.QUOTES)),
+ KeepPragma.create(IncludePath.create("f/foo3.h", Delimiters.QUOTES)));
+ }
+
+ @Test
+ public void lineCommentIncludeGuardPrefixes_parseKeep() {
+ OCFile file =
+ createFile(
+ "bar.h",
+ "#ifndef BAR_H_",
+ "#define BAR_H_",
+ "#include <memory>",
+ "#include <vector> // IWYU pragma: keep",
+ "",
+ "#include \"f/foo1.h\" // IWYU pragma: keep",
+ "#include \"f/foo2.h\"",
+ "#include \"f/foo3.h\" // IWYU pragma: keep ",
+ "",
+ "void bar();",
+ "#endif");
+ IwyuPragmas pragmas = IwyuPragmas.parse(file);
+ assertThat(pragmas.keeps)
+ .containsExactly(
+ KeepPragma.create(IncludePath.create("vector", Delimiters.ANGLE_BRACKETS)),
+ KeepPragma.create(IncludePath.create("f/foo1.h", Delimiters.QUOTES)),
+ KeepPragma.create(IncludePath.create("f/foo3.h", Delimiters.QUOTES)));
+ }
+
+ @Test
+ public void incompletePragma_parseKeep() {
+ OCFile file =
+ createFile(
+ "bar.cc",
+ "#include \"f/foo1.h\" // IWYU pragma: kee",
+ "#include \"f/foo2.h\" // IWYU pragma: keep");
+ IwyuPragmas pragmas = IwyuPragmas.parse(file);
+ assertThat(pragmas.keeps)
+ .containsExactly(KeepPragma.create(IncludePath.create("f/foo2.h", Delimiters.QUOTES)));
+ }
+
+ @Test
+ public void hasSuffixSoNoMatch_parseKeep() {
+ OCFile file =
+ createFile(
+ "bar.cc",
+ "#include \"f/foo1.h\" // IWYU pragma: keepaway",
+ "#include \"f/foo2.h\" // IWYU pragma: keep");
+ IwyuPragmas pragmas = IwyuPragmas.parse(file);
+ assertThat(pragmas.keeps)
+ .containsExactly(KeepPragma.create(IncludePath.create("f/foo2.h", Delimiters.QUOTES)));
+ }
+
+ @Test
+ public void insideNamespace_parseKeep() {
+ OCFile file =
+ createFile(
+ "bar.cc",
+ "#include \"f/foo1.h\" // IWYU pragma: keep",
+ "#include \"f/foo2.h\"",
+ "namespace change_namespace {",
+ "#include \"f/foo3.h\" // IWYU pragma: keep",
+ "}",
+ "class C {",
+ "#include \"f/foo4.h\" // IWYU pragma: keep",
+ "};");
+ IwyuPragmas pragmas = IwyuPragmas.parse(file);
+ assertThat(pragmas.keeps)
+ .containsExactly(
+ KeepPragma.create(IncludePath.create("f/foo1.h", Delimiters.QUOTES)),
+ KeepPragma.create(IncludePath.create("f/foo3.h", Delimiters.QUOTES)),
+ KeepPragma.create(IncludePath.create("f/foo4.h", Delimiters.QUOTES)));
+ }
+
+ @Test
+ public void duplicate_parseKeep() {
+ // Probably need to disambiguate the PSI nodes somewhere (last one shouldn't be kept?)
+ OCFile file =
+ createFile(
+ "bar.cc",
+ "#include \"f/foo1.h\" // IWYU pragma: keep",
+ "#include \"f/foo1.h\" // IWYU pragma: keep",
+ "#include \"f/foo1.h\"");
+ IwyuPragmas pragmas = IwyuPragmas.parse(file);
+ assertThat(pragmas.keeps)
+ .containsExactly(KeepPragma.create(IncludePath.create("f/foo1.h", Delimiters.QUOTES)));
+ }
+
+ @Test
+ public void parseExport() {
+ OCFile file =
+ createFile(
+ "public/foo.h",
+ "/** Stuff */",
+ "#include <private/memory> // IWYU pragma: export",
+ "#include <vector>",
+ "",
+ "#include \"private/foo1.h\" // IWYU pragma: export",
+ "#include \"private/foo2.h\"",
+ "#include \"private/foo3.h\" // IWYU pragma: export",
+ "",
+ "void doFoo(Foo* f);");
+ IwyuPragmas pragmas = IwyuPragmas.parse(file);
+ assertThat(pragmas.exports)
+ .containsExactly(
+ ExportPragma.create(IncludePath.create("private/memory", Delimiters.ANGLE_BRACKETS)),
+ ExportPragma.create(IncludePath.create("private/foo1.h", Delimiters.QUOTES)),
+ ExportPragma.create(IncludePath.create("private/foo3.h", Delimiters.QUOTES)));
+ }
+
+ @Test
+ public void singeRange_parseExportBeginEnd() {
+ OCFile file =
+ createFile(
+ "public/foo.h",
+ "/** Stuff */",
+ "// IWYU pragma: begin_exports",
+ "#include <private/memory>",
+ "#include \"private/foo1.h\"",
+ "#include \"private/foo2.h\"",
+ "// IWYU pragma: end_exports",
+ "#include \"private/foo3.h\"");
+ IwyuPragmas pragmas = IwyuPragmas.parse(file);
+ assertThat(pragmas.exports)
+ .containsExactly(
+ ExportPragma.create(IncludePath.create("private/memory", Delimiters.ANGLE_BRACKETS)),
+ ExportPragma.create(IncludePath.create("private/foo1.h", Delimiters.QUOTES)),
+ ExportPragma.create(IncludePath.create("private/foo2.h", Delimiters.QUOTES)));
+ }
+
+ @Test
+ public void multipleRanges_parseExportBeginEnd() {
+ OCFile file =
+ createFile(
+ "public/foo.h",
+ "/** Stuff */",
+ "// IWYU pragma: begin_exports",
+ "#include <private/memory>",
+ "#include \"private/foo1.h\"",
+ "#include \"private/foo2.h\"",
+ "// IWYU pragma: end_exports",
+ "#include \"private/foo3.h\"",
+ "#include \"private/foo4.h\"",
+ " // IWYU pragma: begin_exports",
+ "#include \"private/foo5.h\"",
+ " // IWYU pragma: end_exports",
+ "#include \"private/foo6.h\"",
+ "",
+ "void doFoo(Foo& f);");
+ IwyuPragmas pragmas = IwyuPragmas.parse(file);
+ assertThat(pragmas.exports)
+ .containsExactly(
+ ExportPragma.create(IncludePath.create("private/memory", Delimiters.ANGLE_BRACKETS)),
+ ExportPragma.create(IncludePath.create("private/foo1.h", Delimiters.QUOTES)),
+ ExportPragma.create(IncludePath.create("private/foo2.h", Delimiters.QUOTES)),
+ ExportPragma.create(IncludePath.create("private/foo5.h", Delimiters.QUOTES)));
+ }
+
+ @Test
+ public void exportRangePlusInlineKeep() {
+ // Probably need to disambiguate the PSI nodes somewhere (last one shouldn't be kept?)
+ OCFile file =
+ createFile(
+ "public/foo.h",
+ "// IWYU pragma: begin_exports",
+ "#include <memory>",
+ "#include \"private/foo1.h\" // IWYU pragma: keep",
+ "#include \"private/foo2.h\" // IWYU pragma: keep",
+ "#include \"private/foo3.h\"",
+ "// IWYU pragma: end_exports",
+ "#include \"private/foo4.h\"",
+ "",
+ "void doFoo(Foo& f);");
+ IwyuPragmas pragmas = IwyuPragmas.parse(file);
+ assertThat(pragmas.keeps)
+ .containsExactly(
+ KeepPragma.create(IncludePath.create("private/foo1.h", Delimiters.QUOTES)),
+ KeepPragma.create(IncludePath.create("private/foo2.h", Delimiters.QUOTES)));
+ assertThat(pragmas.exports)
+ .containsExactly(
+ ExportPragma.create(IncludePath.create("memory", Delimiters.ANGLE_BRACKETS)),
+ ExportPragma.create(IncludePath.create("private/foo1.h", Delimiters.QUOTES)),
+ ExportPragma.create(IncludePath.create("private/foo2.h", Delimiters.QUOTES)),
+ ExportPragma.create(IncludePath.create("private/foo3.h", Delimiters.QUOTES)));
+ }
+
+ @Test
+ public void parsePrivate() {
+ OCFile file =
+ createFile(
+ "private.h", "// Stuff", "// IWYU pragma: private", "#include \"private_details.h\"");
+ IwyuPragmas pragmas = IwyuPragmas.parse(file);
+ assertThat(pragmas.privatePragma.isPresent()).isTrue();
+ PrivatePragma privatePragma = pragmas.privatePragma.get();
+ assertThat(privatePragma.includeOther.isPresent()).isFalse();
+ }
+
+ @Test
+ public void incomplete_parsePrivate() {
+ OCFile file =
+ createFile(
+ "private.h", "// Stuff", "// IWYU pragma: privat", "#include \"private_details.h\"");
+ IwyuPragmas pragmas = IwyuPragmas.parse(file);
+ assertThat(pragmas.privatePragma.isPresent()).isFalse();
+ }
+
+ @Test
+ public void parsePrivateIncludeOther() {
+ OCFile file =
+ createFile(
+ "private.h",
+ "// IWYU pragma: private, include \"f/public.h\"",
+ "#include \"private_details.h\"");
+ IwyuPragmas pragmas = IwyuPragmas.parse(file);
+ assertThat(pragmas.privatePragma.isPresent()).isTrue();
+ PrivatePragma privatePragma = pragmas.privatePragma.get();
+ assertThat(privatePragma.includeOther.isPresent()).isTrue();
+ assertThat(privatePragma.includeOther.get())
+ .isEqualTo(IncludePath.create("f/public.h", Delimiters.QUOTES));
+ }
+
+ @Test
+ public void withWhiteSpace_parsePrivateIncludeOther() {
+ OCFile file =
+ createFile(
+ "private.h",
+ "// IWYU pragma:private,include\"f/public.h\" ",
+ "#include \"private_details.h\"");
+ IwyuPragmas pragmas = IwyuPragmas.parse(file);
+ assertThat(pragmas.privatePragma.isPresent()).isTrue();
+ PrivatePragma privatePragma = pragmas.privatePragma.get();
+ assertThat(privatePragma.includeOther.isPresent()).isTrue();
+ assertThat(privatePragma.includeOther.get())
+ .isEqualTo(IncludePath.create("f/public.h", Delimiters.QUOTES));
+ }
+
+ @Test
+ public void parsePrivateIncludeOtherAngle() {
+ OCFile file =
+ createFile(
+ "private.h",
+ "// IWYU pragma: private, include <f/public.h>",
+ "#include \"private_details.h\"");
+ IwyuPragmas pragmas = IwyuPragmas.parse(file);
+ assertThat(pragmas.privatePragma.isPresent()).isTrue();
+ PrivatePragma privatePragma = pragmas.privatePragma.get();
+ assertThat(privatePragma.includeOther.isPresent()).isTrue();
+ assertThat(privatePragma.includeOther.get())
+ .isEqualTo(IncludePath.create("f/public.h", Delimiters.ANGLE_BRACKETS));
+ }
+
+ @Test
+ public void parsePrivateIncludeOtherNoQuotes() {
+ OCFile file =
+ createFile(
+ "private.h",
+ "// IWYU pragma: private, include f/public.h",
+ "#include \"private_details.h\"");
+ IwyuPragmas pragmas = IwyuPragmas.parse(file);
+ assertThat(pragmas.privatePragma.isPresent()).isTrue();
+ PrivatePragma privatePragma = pragmas.privatePragma.get();
+ assertThat(privatePragma.includeOther.isPresent()).isTrue();
+ assertThat(privatePragma.includeOther.get())
+ .isEqualTo(IncludePath.create("f/public.h", Delimiters.NONE));
+ }
+
+ @Test
+ public void hasMultiple_parsePrivate() {
+ // We shouldn't have multiple private pragmas, but we just take the last one.
+ // An alternative might be to take the most descriptive one (when there is a unique
+ // most descriptive one).
+ OCFile file =
+ createFile(
+ "private.h",
+ "// Stuff",
+ "// IWYU pragma: private",
+ "// IWYU pragma: private, include \"public.h\"",
+ "#include \"private_details.h\"");
+ IwyuPragmas pragmas = IwyuPragmas.parse(file);
+ assertThat(pragmas.privatePragma.isPresent()).isTrue();
+ PrivatePragma privatePragma = pragmas.privatePragma.get();
+ assertThat(privatePragma.includeOther.isPresent()).isTrue();
+ assertThat(privatePragma.includeOther.get())
+ .isEqualTo(IncludePath.create("public.h", Delimiters.QUOTES));
+ }
+
+ @Test
+ public void hasMultipleOtherOrder_parsePrivate() {
+ // Like hasMultiple_parsePrivate, but with order swapped
+ OCFile file =
+ createFile(
+ "private.h",
+ "// Stuff",
+ "// IWYU pragma: private, include \"public.h\"",
+ "// IWYU pragma: private",
+ "#include \"private_details.h\"");
+ IwyuPragmas pragmas = IwyuPragmas.parse(file);
+ assertThat(pragmas.privatePragma.isPresent()).isTrue();
+ PrivatePragma privatePragma = pragmas.privatePragma.get();
+ assertThat(privatePragma.includeOther.isPresent()).isFalse();
+ }
+
+ @Test
+ public void sortOfCommentViaIfdef_parsePrivate() {
+ OCFile file =
+ createFile("bar.cc", "#include <memory>", "#if 0", "IWYU pragma: private", "#endif");
+ IwyuPragmas pragmas = IwyuPragmas.parse(file);
+ // We don't really support this, but #ifdef'ed out things may be parsed as PsiComment,
+ // so at least make sure we don't assert. Answer could go either way.
+ assertThat(pragmas.privatePragma.isPresent()).isTrue();
+ PrivatePragma privatePragma = pragmas.privatePragma.get();
+ assertThat(privatePragma.includeOther.isPresent()).isFalse();
+ }
+
+ @Test
+ public void parseAssociated() {
+ OCFile file =
+ createFile(
+ "implementation.cc",
+ "#include \"some/interface.h\" // IWYU pragma: associated",
+ "",
+ "#include \"other_stuff.h\"");
+ IwyuPragmas pragmas = IwyuPragmas.parse(file);
+ assertThat(pragmas.associatedHeader.isPresent()).isTrue();
+ assertThat(pragmas.associatedHeader.get())
+ .isEqualTo(IncludePath.create("some/interface.h", Delimiters.QUOTES));
+ }
+
+ @Test
+ public void parseAssociatedAngle() {
+ OCFile file =
+ createFile(
+ "implementation.cc",
+ "#include <some/interface.h> // IWYU pragma: associated",
+ "",
+ "#include \"other_stuff.h\"");
+ IwyuPragmas pragmas = IwyuPragmas.parse(file);
+ assertThat(pragmas.associatedHeader.isPresent()).isTrue();
+ assertThat(pragmas.associatedHeader.get())
+ .isEqualTo(IncludePath.create("some/interface.h", Delimiters.ANGLE_BRACKETS));
+ }
+}
diff --git a/cpp/tests/unittests/com/google/idea/blaze/cpp/BlazeCompilerSettingsTest.java b/cpp/tests/unittests/com/google/idea/blaze/cpp/BlazeCompilerSettingsTest.java
index a1e748c..e1fbe27 100644
--- a/cpp/tests/unittests/com/google/idea/blaze/cpp/BlazeCompilerSettingsTest.java
+++ b/cpp/tests/unittests/com/google/idea/blaze/cpp/BlazeCompilerSettingsTest.java
@@ -22,6 +22,7 @@
import com.google.idea.sdkcompat.cidr.CidrCompilerSwitchesAdapter;
import com.jetbrains.cidr.lang.OCLanguageKind;
import com.jetbrains.cidr.lang.toolchains.CidrCompilerSwitches;
+import com.jetbrains.cidr.toolchains.CompilerInfoCache;
import java.io.File;
import java.util.List;
import org.junit.Test;
@@ -36,8 +37,16 @@
public void testCompilerSwitchesSimple() {
File cppExe = new File("bin/cpp");
ImmutableList<String> cFlags = ImmutableList.of("-fast", "-slow");
+ CompilerInfoCache compilerInfoCache = new CompilerInfoCache();
BlazeCompilerSettings settings =
- new BlazeCompilerSettings(getProject(), cppExe, cppExe, cFlags, cFlags);
+ new BlazeCompilerSettings(
+ getProject(),
+ cppExe,
+ cppExe,
+ cFlags,
+ cFlags,
+ "cc version (trunk r123456)",
+ compilerInfoCache);
CidrCompilerSwitches compilerSwitches = settings.getCompilerSwitches(OCLanguageKind.C, null);
List<String> commandLineArgs = CidrCompilerSwitchesAdapter.getFileArgs(compilerSwitches);
diff --git a/cpp/tests/unittests/com/google/idea/blaze/cpp/BlazeConfigurationResolverTest.java b/cpp/tests/unittests/com/google/idea/blaze/cpp/BlazeConfigurationResolverTest.java
new file mode 100644
index 0000000..612f87d
--- /dev/null
+++ b/cpp/tests/unittests/com/google/idea/blaze/cpp/BlazeConfigurationResolverTest.java
@@ -0,0 +1,698 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.ImmutableList;
+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.bazel.BazelBuildSystemProvider;
+import com.google.idea.blaze.base.bazel.BuildSystemProvider;
+import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
+import com.google.idea.blaze.base.ideinfo.CIdeInfo;
+import com.google.idea.blaze.base.ideinfo.CToolchainIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetMap;
+import com.google.idea.blaze.base.ideinfo.TargetMapBuilder;
+import com.google.idea.blaze.base.io.VirtualFileSystemProvider;
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.model.MockBlazeProjectDataBuilder;
+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.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.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.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.BlazeImportSettings;
+import com.google.idea.blaze.base.settings.BlazeImportSettingsManager;
+import com.intellij.openapi.progress.ProgressManager;
+import com.intellij.openapi.progress.impl.ProgressManagerImpl;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileManager;
+import com.jetbrains.cidr.lang.OCLanguageKind;
+import com.jetbrains.cidr.lang.workspace.OCResolveConfiguration;
+import com.jetbrains.cidr.lang.workspace.OCResolveRootAndConfiguration;
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link BlazeConfigurationResolver}. */
+@RunWith(JUnit4.class)
+public class BlazeConfigurationResolverTest extends BlazeTestCase {
+ private final BlazeContext context = new BlazeContext();
+ private final ErrorCollector errorCollector = new ErrorCollector();
+ private final WorkspaceRoot workspaceRoot = new WorkspaceRoot(new File("/root"));
+
+ private BlazeConfigurationResolver resolver;
+ private BlazeConfigurationResolverResult resolverResult;
+ private MockCompilerVersionChecker compilerVersionChecker;
+ private LocalFileSystem mockFileSystem;
+
+ @Override
+ protected void initTest(Container applicationServices, Container projectServices) {
+ super.initTest(applicationServices, projectServices);
+ applicationServices.register(BlazeExecutor.class, new MockBlazeExecutor());
+ compilerVersionChecker = new MockCompilerVersionChecker("1234");
+ applicationServices.register(CompilerVersionChecker.class, compilerVersionChecker);
+ applicationServices.register(ProgressManager.class, new ProgressManagerImpl());
+ applicationServices.register(VirtualFileManager.class, mock(VirtualFileManager.class));
+ mockFileSystem = mock(LocalFileSystem.class);
+ applicationServices.register(
+ VirtualFileSystemProvider.class, mock(VirtualFileSystemProvider.class));
+ when(VirtualFileSystemProvider.getInstance().getSystem()).thenReturn(mockFileSystem);
+
+ projectServices.register(BlazeImportSettingsManager.class, new BlazeImportSettingsManager());
+ BuildSystemProvider buildSystemProvider = new BazelBuildSystemProvider();
+ registerExtensionPoint(BuildSystemProvider.EP_NAME, BuildSystemProvider.class)
+ .registerExtension(buildSystemProvider);
+ BlazeImportSettingsManager.getInstance(getProject())
+ .setImportSettings(
+ new BlazeImportSettings("", "", "", "", buildSystemProvider.buildSystem()));
+
+ context.addOutputSink(IssueOutput.class, errorCollector);
+
+ resolver = new BlazeConfigurationResolver(project);
+ resolverResult = BlazeConfigurationResolverResult.empty(project);
+ }
+
+ @Test
+ public void testEmptyProject() {
+ ProjectView projectView = projectView(directories(), targets());
+ TargetMap targetMap = TargetMapBuilder.builder().build();
+ assertThatResolving(projectView, targetMap).producesNoConfigurations();
+ }
+
+ @Test
+ public void testTargetWithoutSources() {
+ ProjectView projectView = projectView(directories("foo/bar"), targets("//foo/bar:library"));
+ TargetMap targetMap =
+ TargetMapBuilder.builder()
+ .addTarget(createCcToolchain())
+ .addTarget(createCcTarget("//foo/bar:library", Kind.CC_LIBRARY, ImmutableList.of()))
+ .build();
+ assertThatResolving(projectView, targetMap).producesNoConfigurations();
+ }
+
+ @Test
+ public void testTargetWithGeneratedSources() {
+ ProjectView projectView = projectView(directories("foo/bar"), targets("//foo/bar:library"));
+ TargetMap targetMap =
+ TargetMapBuilder.builder()
+ .addTarget(createCcToolchain())
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:library",
+ Kind.CC_LIBRARY,
+ ImmutableList.of(gen("foo/bar/library.cc"))))
+ .build();
+ assertThatResolving(projectView, targetMap).producesNoConfigurations();
+ }
+
+ @Test
+ public void testTargetWithMixedSources() {
+ ProjectView projectView = projectView(directories("foo/bar"), targets("//foo/bar:binary"));
+ TargetMap targetMap =
+ TargetMapBuilder.builder()
+ .addTarget(createCcToolchain())
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:binary",
+ Kind.CC_BINARY,
+ ImmutableList.of(src("foo/bar/binary.cc"), gen("foo/bar/generated.cc"))))
+ .build();
+ assertThatResolving(projectView, targetMap).producesConfigurationsFor("//foo/bar:binary");
+ }
+
+ @Test
+ public void testSingleSourceTarget() {
+ ProjectView projectView = projectView(directories("foo/bar"), targets("//foo/bar:binary"));
+ TargetMap targetMap =
+ TargetMapBuilder.builder()
+ .addTarget(createCcToolchain())
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:binary", Kind.CC_BINARY, ImmutableList.of(src("foo/bar/binary.cc"))))
+ .build();
+ assertThatResolving(projectView, targetMap).producesConfigurationsFor("//foo/bar:binary");
+ }
+
+ @Test
+ public void testSingleSourceTargetWithLibraryDependencies() {
+ ProjectView projectView = projectView(directories("foo/bar"), targets("//foo/bar:binary"));
+ TargetMap targetMap =
+ TargetMapBuilder.builder()
+ .addTarget(createCcToolchain())
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:binary",
+ Kind.CC_BINARY,
+ ImmutableList.of(src("foo/bar/binary.cc")))
+ .addDependency("//bar/baz:library")
+ .addDependency("//third_party:library"))
+ .addTarget(
+ createCcTarget(
+ "//bar/baz:library",
+ Kind.CC_LIBRARY,
+ ImmutableList.of(src("bar/baz/library.cc"))))
+ .addTarget(
+ createCcTarget(
+ "//third_party:library",
+ Kind.CC_LIBRARY,
+ ImmutableList.of(src("third_party/library.cc"))))
+ .build();
+ assertThatResolving(projectView, targetMap).producesConfigurationsFor("//foo/bar:binary");
+ }
+
+ @Test
+ public void testSingleSourceTargetWithSourceDependencies() {
+ ProjectView projectView = projectView(directories("foo/bar"), targets("//foo/bar:binary"));
+ TargetMap targetMap =
+ TargetMapBuilder.builder()
+ .addTarget(createCcToolchain())
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:binary",
+ Kind.CC_BINARY,
+ ImmutableList.of(src("foo/bar/binary.cc")))
+ .addDependency("//foo/bar:library")
+ .addDependency("//third_party:library"))
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:library",
+ Kind.CC_LIBRARY,
+ ImmutableList.of(src("foo/bar/library.cc")),
+ ImmutableList.of("SOME_DEFINE=1")))
+ .addTarget(
+ createCcTarget(
+ "//third_party:library",
+ Kind.CC_LIBRARY,
+ ImmutableList.of(src("third_party/library.cc"))))
+ .build();
+ assertThatResolving(projectView, targetMap)
+ .producesConfigurationsFor("//foo/bar:binary", "//foo/bar:library");
+ }
+
+ @Test
+ public void testComplexProject() {
+ ProjectView projectView =
+ projectView(
+ directories("foo/bar", "foo/baz"),
+ targets("//foo:test", "//foo/bar:binary", "//foo/baz:test"));
+ TargetMap targetMap =
+ TargetMapBuilder.builder()
+ .addTarget(createCcToolchain())
+ .addTarget(
+ createCcTarget("//foo:test", Kind.CC_TEST, ImmutableList.of(src("foo/test.cc")))
+ .addDependency("//foo:library")
+ .addDependency("//foo/bar:library")
+ .addDependency("//third_party:library"))
+ .addTarget(
+ createCcTarget(
+ "//foo:library", Kind.CC_TEST, ImmutableList.of(src("foo/library.cc"))))
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:binary",
+ Kind.CC_BINARY,
+ ImmutableList.of(src("foo/bar/binary.cc")),
+ ImmutableList.of("SOME_DEFINE=1"))
+ .addDependency("//foo/bar:library")
+ .addDependency("//foo/bar:empty")
+ .addDependency("//foo/bar:generated")
+ .addDependency("//foo/bar:mixed")
+ .addDependency("//third_party:library"))
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:library",
+ Kind.CC_LIBRARY,
+ ImmutableList.of(src("foo/bar/library.cc")),
+ ImmutableList.of("SOME_DEFINE=2")))
+ .addTarget(createCcTarget("//foo/bar:empty", Kind.CC_LIBRARY, ImmutableList.of()))
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:generated",
+ Kind.CC_LIBRARY,
+ ImmutableList.of(gen("foo/bar/generated.cc"))))
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:mixed",
+ Kind.CC_LIBRARY,
+ ImmutableList.of(src("foo/bar/mixed_src.cc"), gen("foo/bar/mixed_gen.cc")),
+ ImmutableList.of("SOME_DEFINE=3")))
+ .addTarget(
+ createCcTarget(
+ "//foo/baz:test",
+ Kind.CC_TEST,
+ ImmutableList.of(src("foo/baz/test.cc")),
+ ImmutableList.of("SOME_DEFINE=4"))
+ .addDependency("//foo/baz:binary")
+ .addDependency("//foo/baz:library")
+ .addDependency("//foo/qux:library"))
+ .addTarget(
+ createCcTarget(
+ "//foo/baz:binary",
+ Kind.CC_BINARY,
+ ImmutableList.of(src("foo/baz/binary.cc")),
+ ImmutableList.of("SOME_DEFINE=5")))
+ .addTarget(
+ createCcTarget(
+ "//foo/baz:library",
+ Kind.CC_LIBRARY,
+ ImmutableList.of(src("foo/baz/library.cc")),
+ ImmutableList.of("SOME_DEFINE=6")))
+ .addTarget(
+ createCcTarget(
+ "//foo/qux:library",
+ Kind.CC_LIBRARY,
+ ImmutableList.of(src("foo/qux/library.cc"))))
+ .addTarget(
+ createCcTarget(
+ "//third_party:library",
+ Kind.CC_LIBRARY,
+ ImmutableList.of(src("third_party/library.cc"))))
+ .build();
+ assertThatResolving(projectView, targetMap)
+ .producesConfigurationsFor(
+ "//foo/bar:binary",
+ "//foo/bar:library",
+ "//foo/bar:mixed",
+ "//foo/baz:test",
+ "//foo/baz:binary",
+ "//foo/baz:library");
+ }
+
+ @Test
+ public void firstResolve_testNotIncremental() {
+ ProjectView projectView = projectView(directories("foo/bar"), targets("//foo/bar:binary"));
+ TargetMap targetMap =
+ TargetMapBuilder.builder()
+ .addTarget(createCcToolchain())
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:binary", Kind.CC_BINARY, ImmutableList.of(src("foo/bar/binary.cc"))))
+ .build();
+ ImmutableList<BlazeResolveConfiguration> noReusedConfigurations = ImmutableList.of();
+ assertThatResolving(projectView, targetMap)
+ .reusedConfigurations(noReusedConfigurations, "//foo/bar:binary");
+ }
+
+ @Test
+ public void identicalTargets_testIncrementalUpdateFullReuse() {
+ ProjectView projectView = projectView(directories("foo/bar"), targets("//foo/bar:binary"));
+ TargetMap targetMap =
+ TargetMapBuilder.builder()
+ .addTarget(createCcToolchain())
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:binary", Kind.CC_BINARY, ImmutableList.of(src("foo/bar/binary.cc"))))
+ .build();
+
+ assertThatResolving(projectView, targetMap).producesConfigurationsFor("//foo/bar:binary");
+ Collection<BlazeResolveConfiguration> initialConfigurations =
+ resolverResult.getAllConfigurations();
+
+ assertThatResolving(projectView, targetMap).reusedConfigurations(initialConfigurations);
+ }
+
+ @Test
+ public void newTarget_testIncrementalUpdatePartlyReused() {
+ ProjectView projectView = projectView(directories("foo/bar"), targets("//foo/bar:*"));
+ TargetMapBuilder targetMapBuilder =
+ TargetMapBuilder.builder()
+ .addTarget(createCcToolchain())
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:binary",
+ Kind.CC_BINARY,
+ ImmutableList.of(src("foo/bar/binary.cc"))));
+ assertThatResolving(projectView, targetMapBuilder.build())
+ .producesConfigurationsFor("//foo/bar:binary");
+ Collection<BlazeResolveConfiguration> initialConfigurations =
+ resolverResult.getAllConfigurations();
+
+ targetMapBuilder.addTarget(
+ createCcTarget(
+ "//foo/bar:library",
+ Kind.CC_LIBRARY,
+ ImmutableList.of(src("foo/bar/library.cc")),
+ ImmutableList.of("OTHER=1")));
+
+ assertThatResolving(projectView, targetMapBuilder.build())
+ .reusedConfigurations(initialConfigurations, "//foo/bar:library");
+ }
+
+ @Test
+ public void afterQueryingConfiguration_newTarget_testIncrementalUpdatePartlyReused() {
+ ProjectView projectView = projectView(directories("foo/bar"), targets("//foo/bar:*"));
+ TargetMapBuilder targetMapBuilder =
+ TargetMapBuilder.builder()
+ .addTarget(createCcToolchain())
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:binary",
+ Kind.CC_BINARY,
+ ImmutableList.of(src("foo/bar/binary.cc"))));
+ assertThatResolving(projectView, targetMapBuilder.build())
+ .producesConfigurationsFor("//foo/bar:binary");
+ Collection<BlazeResolveConfiguration> initialConfigurations =
+ resolverResult.getAllConfigurations();
+
+ // Make sure that if we *query* the configuration in some way, it doesn't affect its
+ // compatibility / reusability. There may be caches attached to the configuration and those
+ // should not be compared when checking equivalence.
+ OCResolveConfiguration firstConfiguration = initialConfigurations.iterator().next();
+ firstConfiguration.getLibraryHeadersRoots(
+ new OCResolveRootAndConfiguration(firstConfiguration, OCLanguageKind.CPP));
+
+ targetMapBuilder.addTarget(
+ createCcTarget(
+ "//foo/bar:library",
+ Kind.CC_LIBRARY,
+ ImmutableList.of(src("foo/bar/library.cc")),
+ ImmutableList.of("OTHER=1")));
+
+ assertThatResolving(projectView, targetMapBuilder.build())
+ .reusedConfigurations(initialConfigurations, "//foo/bar:library");
+ }
+
+ @Test
+ public void completelyDifferentTargetsSameProjectView_testIncrementalUpdateNoReuse() {
+ ProjectView projectView = projectView(directories("foo/bar"), targets("//foo/bar:*"));
+ TargetMap targetMap =
+ TargetMapBuilder.builder()
+ .addTarget(createCcToolchain())
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:binary", Kind.CC_BINARY, ImmutableList.of(src("foo/bar/binary.cc"))))
+ .build();
+ ImmutableList<BlazeResolveConfiguration> noReusedConfigurations = ImmutableList.of();
+ assertThatResolving(projectView, targetMap)
+ .reusedConfigurations(noReusedConfigurations, "//foo/bar:binary");
+
+ TargetMap targetMap2 =
+ TargetMapBuilder.builder()
+ .addTarget(createCcToolchain())
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:library",
+ Kind.CC_LIBRARY,
+ ImmutableList.of(src("foo/bar/library.cc")),
+ ImmutableList.of("OTHER=1")))
+ .build();
+ assertThatResolving(projectView, targetMap2)
+ .reusedConfigurations(noReusedConfigurations, "//foo/bar:library");
+ }
+
+ @Test
+ public void completelyDifferentTargetsDifferentProjectView_testIncrementalUpdateNoReuse() {
+ ProjectView projectView = projectView(directories("foo/bar"), targets("//foo/bar:binary"));
+ TargetMap targetMap =
+ TargetMapBuilder.builder()
+ .addTarget(createCcToolchain())
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:binary", Kind.CC_BINARY, ImmutableList.of(src("foo/bar/binary.cc"))))
+ .build();
+ ImmutableList<BlazeResolveConfiguration> noReusedConfigurations = ImmutableList.of();
+ assertThatResolving(projectView, targetMap)
+ .reusedConfigurations(noReusedConfigurations, "//foo/bar:binary");
+
+ ProjectView projectView2 = projectView(directories("foo/zoo"), targets("//foo/zoo:library"));
+ TargetMap targetMap2 =
+ TargetMapBuilder.builder()
+ .addTarget(createCcToolchain())
+ .addTarget(
+ createCcTarget(
+ "//foo/zoo:library",
+ Kind.CC_LIBRARY,
+ ImmutableList.of(src("foo/zoo/library.cc")),
+ ImmutableList.of("OTHER=1")))
+ .build();
+ assertThatResolving(projectView2, targetMap2)
+ .reusedConfigurations(noReusedConfigurations, "//foo/zoo:library");
+ }
+
+ @Test
+ public void changeCompilerVersion_testIncrementalUpdateNoReuse() {
+ ProjectView projectView = projectView(directories("foo/bar"), targets("//foo/bar:binary"));
+ TargetMap targetMap =
+ TargetMapBuilder.builder()
+ .addTarget(createCcToolchain())
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:binary", Kind.CC_BINARY, ImmutableList.of(src("foo/bar/binary.cc"))))
+ .build();
+
+ ImmutableList<BlazeResolveConfiguration> noReusedConfigurations = ImmutableList.of();
+ assertThatResolving(projectView, targetMap)
+ .reusedConfigurations(noReusedConfigurations, "//foo/bar:binary");
+
+ compilerVersionChecker.setCompilerVersion("cc modified version");
+ assertThatResolving(projectView, targetMap)
+ .reusedConfigurations(noReusedConfigurations, "//foo/bar:binary");
+ }
+
+ @Test
+ public void noChange_testIncrementalUpdateGetChangedFiles() {
+ ProjectView projectView = projectView(directories("foo/bar"), targets("//foo/bar:binary"));
+ TargetMap targetMap =
+ TargetMapBuilder.builder()
+ .addTarget(createCcToolchain())
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:binary", Kind.CC_BINARY, ImmutableList.of(src("foo/bar/binary.cc"))))
+ .build();
+ assertThatResolving(projectView, targetMap).producesConfigurationsFor("//foo/bar:binary");
+
+ assertThatResolving(projectView, targetMap).hasChangedRemovedFiles(ImmutableList.of(), false);
+ }
+
+ @Test
+ public void addFiles_testIncrementalUpdateGetChangedFiles() {
+ ProjectView projectView = projectView(directories("foo/bar"), targets("//foo/bar:*"));
+ TargetMapBuilder targetMapBuilder =
+ TargetMapBuilder.builder()
+ .addTarget(createCcToolchain())
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:binary",
+ Kind.CC_BINARY,
+ ImmutableList.of(src("foo/bar/binary.cc"))));
+ TargetMap targetMap = targetMapBuilder.build();
+ createVirtualFile("/root/foo/bar/binary.cc");
+ assertThatResolving(projectView, targetMap).producesConfigurationsFor("//foo/bar:binary");
+
+ TargetMap targetMap2 =
+ targetMapBuilder
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:library",
+ Kind.CC_LIBRARY,
+ ImmutableList.of(src("foo/bar/library.cc"))))
+ .build();
+ VirtualFile libraryCc = createVirtualFile("/root/foo/bar/library.cc");
+ assertThatResolving(projectView, targetMap2)
+ .hasChangedRemovedFiles(ImmutableList.of(libraryCc.getPath()), true);
+ }
+
+ @Test
+ public void removeFiles_testIncrementalUpdateGetChangedFiles() {
+ ProjectView projectView = projectView(directories("foo/bar"), targets("//foo/bar:*"));
+ TargetMap targetMap =
+ TargetMapBuilder.builder()
+ .addTarget(createCcToolchain())
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:binary", Kind.CC_BINARY, ImmutableList.of(src("foo/bar/binary.cc"))))
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:library",
+ Kind.CC_LIBRARY,
+ ImmutableList.of(src("foo/bar/library.cc")),
+ ImmutableList.of("SOME_DEFINE=1")))
+ .build();
+ createVirtualFile("/root/foo/bar/binary.cc");
+ createVirtualFile("/root/foo/bar/library.cc");
+ assertThatResolving(projectView, targetMap)
+ .producesConfigurationsFor("//foo/bar:binary", "//foo/bar:library");
+
+ TargetMap targetMap2 =
+ TargetMapBuilder.builder()
+ .addTarget(createCcToolchain())
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:binary", Kind.CC_BINARY, ImmutableList.of(src("foo/bar/binary.cc"))))
+ .build();
+ assertThatResolving(projectView, targetMap2).hasChangedRemovedFiles(ImmutableList.of(), true);
+ }
+
+ private static ArtifactLocation src(String path) {
+ return ArtifactLocation.builder().setRelativePath(path).setIsSource(true).build();
+ }
+
+ private static ArtifactLocation gen(String path) {
+ return ArtifactLocation.builder().setRelativePath(path).setIsSource(false).build();
+ }
+
+ private static TargetIdeInfo.Builder createCcTarget(
+ String label, Kind kind, ImmutableList<ArtifactLocation> sources) {
+ return createCcTarget(label, kind, sources, ImmutableList.of());
+ }
+
+ private static TargetIdeInfo.Builder createCcTarget(
+ String label,
+ Kind kind,
+ ImmutableList<ArtifactLocation> sources,
+ ImmutableList<String> defines) {
+ TargetIdeInfo.Builder targetInfo =
+ TargetIdeInfo.builder().setLabel(label).setKind(kind).addDependency("//:toolchain");
+ sources.forEach(targetInfo::addSource);
+ return targetInfo.setCInfo(CIdeInfo.builder().addSources(sources).addLocalDefines(defines));
+ }
+
+ private static TargetIdeInfo.Builder createCcToolchain() {
+ return TargetIdeInfo.builder()
+ .setLabel("//:toolchain")
+ .setKind(Kind.CC_TOOLCHAIN)
+ .setCToolchainInfo(
+ CToolchainIdeInfo.builder().setCppExecutable(new ExecutionRootPath("cc")));
+ }
+
+ private static ListSection<DirectoryEntry> directories(String... directories) {
+ return ListSection.builder(DirectorySection.KEY)
+ .addAll(
+ Arrays.stream(directories)
+ .map(directory -> DirectoryEntry.include(WorkspacePath.createIfValid(directory)))
+ .collect(Collectors.toList()))
+ .build();
+ }
+
+ private static ListSection<TargetExpression> targets(String... targets) {
+ return ListSection.builder(TargetSection.KEY)
+ .addAll(
+ Arrays.stream(targets).map(TargetExpression::fromString).collect(Collectors.toList()))
+ .build();
+ }
+
+ private static ProjectView projectView(
+ ListSection<DirectoryEntry> directories, ListSection<TargetExpression> targets) {
+ return ProjectView.builder().add(directories).add(targets).build();
+ }
+
+ private VirtualFile createVirtualFile(String path) {
+ VirtualFile mockFile = mock(VirtualFile.class);
+ when(mockFile.getPath()).thenReturn(path);
+ when(mockFileSystem.findFileByIoFile(new File(path))).thenReturn(mockFile);
+ return mockFile;
+ }
+
+ private Subject assertThatResolving(ProjectView projectView, TargetMap targetMap) {
+ BlazeProjectData blazeProjectData =
+ MockBlazeProjectDataBuilder.builder(workspaceRoot).setTargetMap(targetMap).build();
+ resolverResult =
+ resolver.update(
+ context,
+ workspaceRoot,
+ ProjectViewSet.builder().add(projectView).build(),
+ blazeProjectData,
+ resolverResult);
+ errorCollector.assertNoIssues();
+ return new Subject() {
+ @Override
+ public void producesConfigurationsFor(String... expected) {
+ List<String> targets =
+ resolverResult
+ .getAllConfigurations()
+ .stream()
+ .map(configuration -> configuration.getDisplayName(false))
+ .collect(Collectors.toList());
+ assertThat(targets).containsExactly((Object[]) expected);
+ }
+
+ @Override
+ public void producesNoConfigurations() {
+ assertThat(resolverResult.getAllConfigurations()).isEmpty();
+ }
+
+ @Override
+ public void reusedConfigurations(
+ Collection<BlazeResolveConfiguration> expectedReused, String... expectedNotReused) {
+ Collection<BlazeResolveConfiguration> currentConfigurations =
+ resolverResult.getAllConfigurations();
+ assertContainsAllInIdentity(expectedReused, currentConfigurations);
+ List<String> notReusedTargets =
+ currentConfigurations
+ .stream()
+ .filter(
+ configuration ->
+ expectedReused
+ .stream()
+ .noneMatch(reusedConfig -> configuration == reusedConfig))
+ .map(configuration -> configuration.getDisplayName(false))
+ .collect(Collectors.toList());
+ assertThat(notReusedTargets).containsExactly((Object[]) expectedNotReused);
+ }
+
+ @Override
+ public void hasChangedRemovedFiles(
+ ImmutableList<String> expectedChangedFiles, boolean expectedHasChanges) {
+ BlazeConfigurationResolverDiff diff = resolverResult.getConfigurationDiff();
+ assertThat(diff).isNotNull();
+ List<String> changedFileNames =
+ diff.getChangedFiles().stream().map(VirtualFile::getPath).collect(Collectors.toList());
+ assertThat(changedFileNames).containsExactlyElementsIn(expectedChangedFiles);
+ assertThat(diff.hasChanges()).isEqualTo(expectedHasChanges);
+ }
+
+ // In newer truth libraries, we could use:
+ // assertThat(actual).comparingElementsUsing(IdentityCorrespondence).containsAllIn(expected)
+ // but that isn't available in truth 0.30 from older plugin APIs.
+ private <T> void assertContainsAllInIdentity(Collection<T> expected, Collection<T> actual) {
+ for (T expectedItem : expected) {
+ assertThat(actual.stream().anyMatch(actualItem -> actualItem == expectedItem)).isTrue();
+ }
+ }
+ };
+ }
+
+ private interface Subject {
+ void producesConfigurationsFor(String... expected);
+
+ void producesNoConfigurations();
+
+ void reusedConfigurations(Collection<BlazeResolveConfiguration> reused, String... notReused);
+
+ void hasChangedRemovedFiles(
+ ImmutableList<String> expectedChangedFiles, boolean hasRemovedFiles);
+ }
+}
diff --git a/cpp/tests/unittests/com/google/idea/blaze/cpp/BlazeResolveConfigurationEquivalenceTest.java b/cpp/tests/unittests/com/google/idea/blaze/cpp/BlazeResolveConfigurationEquivalenceTest.java
new file mode 100644
index 0000000..a08bfc9
--- /dev/null
+++ b/cpp/tests/unittests/com/google/idea/blaze/cpp/BlazeResolveConfigurationEquivalenceTest.java
@@ -0,0 +1,661 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static com.google.common.truth.Truth.assertWithMessage;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.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.bazel.BazelBuildSystemProvider;
+import com.google.idea.blaze.base.bazel.BuildSystemProvider;
+import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
+import com.google.idea.blaze.base.ideinfo.CIdeInfo;
+import com.google.idea.blaze.base.ideinfo.CToolchainIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetMap;
+import com.google.idea.blaze.base.ideinfo.TargetMapBuilder;
+import com.google.idea.blaze.base.io.VirtualFileSystemProvider;
+import com.google.idea.blaze.base.model.MockBlazeProjectDataBuilder;
+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.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.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.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.BlazeImportSettings;
+import com.google.idea.blaze.base.settings.BlazeImportSettingsManager;
+import com.intellij.mock.MockPsiManager;
+import com.intellij.openapi.progress.ProgressManager;
+import com.intellij.openapi.progress.impl.ProgressManagerImpl;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileManager;
+import com.intellij.openapi.vfs.newvfs.impl.StubVirtualFile;
+import com.intellij.psi.PsiManager;
+import com.jetbrains.cidr.lang.OCLanguageKind;
+import com.jetbrains.cidr.lang.workspace.OCResolveRootAndConfiguration;
+import com.jetbrains.cidr.lang.workspace.headerRoots.HeadersSearchRoot;
+import com.jetbrains.cidr.lang.workspace.headerRoots.IncludedHeadersRoot;
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests that we group equivalent {@link BlazeResolveConfiguration}s. */
+@RunWith(JUnit4.class)
+public class BlazeResolveConfigurationEquivalenceTest extends BlazeTestCase {
+ private final BlazeContext context = new BlazeContext();
+ private final ErrorCollector errorCollector = new ErrorCollector();
+ private final WorkspaceRoot workspaceRoot = new WorkspaceRoot(new File("/root"));
+
+ private BlazeConfigurationResolver resolver;
+ private BlazeConfigurationResolverResult resolverResult;
+ private LocalFileSystem mockFileSystem;
+
+ @Override
+ protected void initTest(Container applicationServices, Container projectServices) {
+ super.initTest(applicationServices, projectServices);
+ applicationServices.register(BlazeExecutor.class, new MockBlazeExecutor());
+ applicationServices.register(
+ CompilerVersionChecker.class, new MockCompilerVersionChecker("1234"));
+
+ applicationServices.register(ProgressManager.class, new ProgressManagerImpl());
+ applicationServices.register(VirtualFileManager.class, mock(VirtualFileManager.class));
+ mockFileSystem = mock(LocalFileSystem.class);
+ applicationServices.register(
+ VirtualFileSystemProvider.class, mock(VirtualFileSystemProvider.class));
+ when(VirtualFileSystemProvider.getInstance().getSystem()).thenReturn(mockFileSystem);
+
+ projectServices.register(PsiManager.class, new MockPsiManager(project));
+ projectServices.register(BlazeImportSettingsManager.class, new BlazeImportSettingsManager());
+
+ BuildSystemProvider buildSystemProvider = new BazelBuildSystemProvider();
+ registerExtensionPoint(BuildSystemProvider.EP_NAME, BuildSystemProvider.class)
+ .registerExtension(buildSystemProvider);
+ BlazeImportSettingsManager.getInstance(getProject())
+ .setImportSettings(
+ new BlazeImportSettings("", "", "", "", buildSystemProvider.buildSystem()));
+
+ context.addOutputSink(IssueOutput.class, errorCollector);
+
+ resolver = new BlazeConfigurationResolver(project);
+ resolverResult = BlazeConfigurationResolverResult.empty(project);
+ }
+
+ @Test
+ public void testEmptyConfigurations() {
+ ProjectView projectView =
+ projectView(
+ directories("foo/bar"), targets("//foo/bar:one", "//foo/bar:two", "//foo/bar:three"));
+ TargetMap targetMap =
+ TargetMapBuilder.builder()
+ .addTarget(createCcToolchain())
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:one",
+ Kind.CC_BINARY,
+ sources("foo/bar/one.cc"),
+ defines(),
+ includes()))
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:two",
+ Kind.CC_BINARY,
+ sources("foo/bar/two.cc"),
+ defines(),
+ includes()))
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:three",
+ Kind.CC_BINARY,
+ sources("foo/bar/three.cc"),
+ defines(),
+ includes()))
+ .build();
+ List<BlazeResolveConfiguration> configurations = resolve(projectView, targetMap);
+ assertThat(configurations).hasSize(1);
+ assertThat(get(configurations, "//foo/bar:one and 2 other target(s)")).isNotNull();
+ for (BlazeResolveConfiguration configuration : configurations) {
+ assertThat(configuration.getProjectHeadersRoots().getRoots()).isEmpty();
+ assertThat(getHeaders(configuration, OCLanguageKind.CPP)).isEmpty();
+ assertThat(configuration.getCompilerMacros()).isEqualTo(macros());
+ }
+ }
+
+ @Test
+ public void testDefines() {
+ ProjectView projectView =
+ projectView(
+ directories("foo/bar"), targets("//foo/bar:one", "//foo/bar:two", "//foo/bar:three"));
+ TargetMap targetMap =
+ TargetMapBuilder.builder()
+ .addTarget(createCcToolchain())
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:one",
+ Kind.CC_BINARY,
+ sources("foo/bar/one.cc"),
+ defines("SAME=1"),
+ includes()))
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:two",
+ Kind.CC_BINARY,
+ sources("foo/bar/two.cc"),
+ defines("SAME=1"),
+ includes()))
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:three",
+ Kind.CC_BINARY,
+ sources("foo/bar/three.cc"),
+ defines("DIFFERENT=1"),
+ includes()))
+ .build();
+ List<BlazeResolveConfiguration> configurations = resolve(projectView, targetMap);
+ assertThat(configurations).hasSize(2);
+ assertThat(get(configurations, "//foo/bar:one and 1 other target(s)").getCompilerMacros())
+ .isEqualTo(macros("SAME=1"));
+ assertThat(get(configurations, "//foo/bar:three").getCompilerMacros())
+ .isEqualTo(macros("DIFFERENT=1"));
+ for (BlazeResolveConfiguration configuration : configurations) {
+ assertThat(configuration.getProjectHeadersRoots().getRoots()).isEmpty();
+ assertThat(getHeaders(configuration, OCLanguageKind.CPP)).isEmpty();
+ }
+ }
+
+ @Test
+ public void testIncludes() {
+ ProjectView projectView =
+ projectView(
+ directories("foo/bar"), targets("//foo/bar:one", "//foo/bar:two", "//foo/bar:three"));
+ TargetMap targetMap =
+ TargetMapBuilder.builder()
+ .addTarget(createCcToolchain())
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:one",
+ Kind.CC_BINARY,
+ sources("foo/bar/one.cc"),
+ defines(),
+ includes("foo/same")))
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:two",
+ Kind.CC_BINARY,
+ sources("foo/bar/two.cc"),
+ defines(),
+ includes("foo/same")))
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:three",
+ Kind.CC_BINARY,
+ sources("foo/bar/three.cc"),
+ defines(),
+ includes("foo/different")))
+ .build();
+ VirtualFile includeSame = createVirtualFile("/root/foo/same");
+ VirtualFile includeDifferent = createVirtualFile("/root/foo/different");
+ List<BlazeResolveConfiguration> configurations = resolve(projectView, targetMap);
+ assertThat(configurations).hasSize(2);
+ assertThat(
+ getHeaders(
+ get(configurations, "//foo/bar:one and 1 other target(s)"), OCLanguageKind.CPP))
+ .containsExactly(header(includeSame));
+ assertThat(getHeaders(get(configurations, "//foo/bar:three"), OCLanguageKind.CPP))
+ .containsExactly(header(includeDifferent));
+ for (BlazeResolveConfiguration configuration : configurations) {
+ assertThat(configuration.getProjectHeadersRoots().getRoots()).isEmpty();
+ assertThat(configuration.getCompilerMacros()).isEqualTo(macros());
+ }
+ }
+
+ // Test a series of permutations of labels a, b, c, d.
+ // Initial state is {a=1, b=1, c=1, d=0}, and we flip some of the 1 to 0.
+ private TargetMap incrementalUpdateTestCaseInitialTargetMap() {
+ return TargetMapBuilder.builder()
+ .addTarget(createCcToolchain())
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:a",
+ Kind.CC_BINARY,
+ sources("foo/bar/a.cc"),
+ defines("SAME=1"),
+ includes()))
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:b",
+ Kind.CC_BINARY,
+ sources("foo/bar/b.cc"),
+ defines("SAME=1"),
+ includes()))
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:c",
+ Kind.CC_BINARY,
+ sources("foo/bar/c.cc"),
+ defines("SAME=1"),
+ includes()))
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:d",
+ Kind.CC_BINARY,
+ sources("foo/bar/d.cc"),
+ defines("DIFFERENT=1"),
+ includes()))
+ .build();
+ }
+
+ // TODO(jvoung): This could be a separate Parameterized test.
+ private static final Map<List<String>, ReusedConfigurationExpectations>
+ permutationsAndExpectations =
+ ImmutableMap.<List<String>, ReusedConfigurationExpectations>builder()
+ .put(
+ ImmutableList.of("a"),
+ // Since we already had a config at 1 and one at 0, flipping any 1 to 0 will
+ // always
+ // result in reuse. The old configurations will get renamed.
+ new ReusedConfigurationExpectations(
+ ImmutableList.of(
+ "//foo/bar:a and 1 other target(s)", "//foo/bar:b and 1 other target(s)"),
+ ImmutableList.of()))
+ .put(
+ ImmutableList.of("b"),
+ new ReusedConfigurationExpectations(
+ ImmutableList.of(
+ "//foo/bar:a and 1 other target(s)", "//foo/bar:b and 1 other target(s)"),
+ ImmutableList.of()))
+ .put(
+ ImmutableList.of("c"),
+ new ReusedConfigurationExpectations(
+ ImmutableList.of(
+ "//foo/bar:a and 1 other target(s)", "//foo/bar:c and 1 other target(s)"),
+ ImmutableList.of()))
+ .put(
+ ImmutableList.of("a", "b"),
+ new ReusedConfigurationExpectations(
+ ImmutableList.of("//foo/bar:a and 2 other target(s)", "//foo/bar:c"),
+ ImmutableList.of()))
+ .put(
+ ImmutableList.of("b", "c"),
+ new ReusedConfigurationExpectations(
+ ImmutableList.of("//foo/bar:a", "//foo/bar:b and 2 other target(s)"),
+ ImmutableList.of()))
+ .put(
+ ImmutableList.of("a", "c"),
+ new ReusedConfigurationExpectations(
+ ImmutableList.of("//foo/bar:a and 2 other target(s)", "//foo/bar:b"),
+ ImmutableList.of()))
+ .put(
+ ImmutableList.of("a", "b", "c"),
+ new ReusedConfigurationExpectations(
+ ImmutableList.of("//foo/bar:a and 3 other target(s)"), ImmutableList.of()))
+ .build();
+
+ @Test
+ public void changeDefines_testIncrementalUpdate_0() {
+ Map.Entry<List<String>, ReusedConfigurationExpectations> testCase =
+ Iterables.get(permutationsAndExpectations.entrySet(), 0);
+ do_changeDefines_testIncrementalUpdate(testCase.getKey(), testCase.getValue());
+ }
+
+ @Test
+ public void changeDefines_testIncrementalUpdate_1() {
+ Map.Entry<List<String>, ReusedConfigurationExpectations> testCase =
+ Iterables.get(permutationsAndExpectations.entrySet(), 1);
+ do_changeDefines_testIncrementalUpdate(testCase.getKey(), testCase.getValue());
+ }
+
+ @Test
+ public void changeDefines_testIncrementalUpdate_2() {
+ Map.Entry<List<String>, ReusedConfigurationExpectations> testCase =
+ Iterables.get(permutationsAndExpectations.entrySet(), 2);
+ do_changeDefines_testIncrementalUpdate(testCase.getKey(), testCase.getValue());
+ }
+
+ @Test
+ public void changeDefines_testIncrementalUpdate_3() {
+ Map.Entry<List<String>, ReusedConfigurationExpectations> testCase =
+ Iterables.get(permutationsAndExpectations.entrySet(), 3);
+ do_changeDefines_testIncrementalUpdate(testCase.getKey(), testCase.getValue());
+ }
+
+ @Test
+ public void changeDefines_testIncrementalUpdate_4() {
+ Map.Entry<List<String>, ReusedConfigurationExpectations> testCase =
+ Iterables.get(permutationsAndExpectations.entrySet(), 4);
+ do_changeDefines_testIncrementalUpdate(testCase.getKey(), testCase.getValue());
+ }
+
+ @Test
+ public void changeDefines_testIncrementalUpdate_5() {
+ Map.Entry<List<String>, ReusedConfigurationExpectations> testCase =
+ Iterables.get(permutationsAndExpectations.entrySet(), 5);
+ do_changeDefines_testIncrementalUpdate(testCase.getKey(), testCase.getValue());
+ }
+
+ @Test
+ public void changeDefines_testIncrementalUpdate_6() {
+ Map.Entry<List<String>, ReusedConfigurationExpectations> testCase =
+ Iterables.get(permutationsAndExpectations.entrySet(), 6);
+ do_changeDefines_testIncrementalUpdate(testCase.getKey(), testCase.getValue());
+ assertThat(permutationsAndExpectations.size()).isEqualTo(7);
+ }
+
+ private void do_changeDefines_testIncrementalUpdate(
+ List<String> labelsToFlip, ReusedConfigurationExpectations expectation) {
+ ProjectView projectView = projectView(directories("foo/bar"), targets("//foo/bar:...:all"));
+ List<BlazeResolveConfiguration> configurations =
+ resolve(projectView, incrementalUpdateTestCaseInitialTargetMap());
+ assertThat(configurations).hasSize(2);
+ assertThat(get(configurations, "//foo/bar:a and 2 other target(s)")).isNotNull();
+ assertThat(get(configurations, "//foo/bar:d")).isNotNull();
+
+ TargetMapBuilder targetMapBuilder = TargetMapBuilder.builder().addTarget(createCcToolchain());
+ for (String target : ImmutableList.of("a", "b", "c")) {
+ if (labelsToFlip.contains(target)) {
+ targetMapBuilder.addTarget(
+ createCcTarget(
+ String.format("//foo/bar:%s", target),
+ Kind.CC_BINARY,
+ sources(String.format("foo/bar/%s.cc", target)),
+ defines("DIFFERENT=1"),
+ includes()));
+ } else {
+ targetMapBuilder.addTarget(
+ createCcTarget(
+ String.format("//foo/bar:%s", target),
+ Kind.CC_BINARY,
+ sources(String.format("foo/bar/%s.cc", target)),
+ defines("SAME=1"),
+ includes()));
+ }
+ }
+ targetMapBuilder.addTarget(
+ createCcTarget(
+ "//foo/bar:d",
+ Kind.CC_BINARY,
+ sources("foo/bar/d.cc"),
+ defines("DIFFERENT=1"),
+ includes()));
+ List<BlazeResolveConfiguration> newConfigurations =
+ resolve(projectView, targetMapBuilder.build());
+ assertReusedConfigs(configurations, newConfigurations, expectation);
+ }
+
+ @Test
+ public void changeDefinesWithSameStructure_testIncrementalUpdate() {
+ ProjectView projectView = projectView(directories("foo/bar"), targets("//foo/bar:...:all"));
+ TargetMap targetMap = incrementalUpdateTestCaseInitialTargetMap();
+ List<BlazeResolveConfiguration> configurations = resolve(projectView, targetMap);
+ assertThat(configurations).hasSize(2);
+ assertThat(get(configurations, "//foo/bar:a and 2 other target(s)")).isNotNull();
+ assertThat(get(configurations, "//foo/bar:d")).isNotNull();
+
+ targetMap =
+ TargetMapBuilder.builder()
+ .addTarget(createCcToolchain())
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:a",
+ Kind.CC_BINARY,
+ sources("foo/bar/a.cc"),
+ defines("CHANGED=1"),
+ includes()))
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:b",
+ Kind.CC_BINARY,
+ sources("foo/bar/b.cc"),
+ defines("CHANGED=1"),
+ includes()))
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:c",
+ Kind.CC_BINARY,
+ sources("foo/bar/c.cc"),
+ defines("CHANGED=1"),
+ includes()))
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:d",
+ Kind.CC_BINARY,
+ sources("foo/bar/d.cc"),
+ defines("DIFFERENT=1"),
+ includes()))
+ .build();
+ List<BlazeResolveConfiguration> newConfigurations = resolve(projectView, targetMap);
+ assertThat(newConfigurations).hasSize(2);
+ assertReusedConfigs(
+ configurations,
+ newConfigurations,
+ new ReusedConfigurationExpectations(
+ ImmutableList.of("//foo/bar:d"),
+ ImmutableList.of("//foo/bar:a and 2 other target(s)")));
+ }
+
+ @Test
+ public void changeDefinesMakeAllSame_testIncrementalUpdate() {
+ ProjectView projectView = projectView(directories("foo/bar"), targets("//foo/bar:...:all"));
+ TargetMap targetMap = incrementalUpdateTestCaseInitialTargetMap();
+ List<BlazeResolveConfiguration> configurations = resolve(projectView, targetMap);
+ assertThat(configurations).hasSize(2);
+ assertThat(get(configurations, "//foo/bar:a and 2 other target(s)")).isNotNull();
+ assertThat(get(configurations, "//foo/bar:d")).isNotNull();
+
+ targetMap =
+ TargetMapBuilder.builder()
+ .addTarget(createCcToolchain())
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:a",
+ Kind.CC_BINARY,
+ sources("foo/bar/a.cc"),
+ defines("SAME=1"),
+ includes()))
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:b",
+ Kind.CC_BINARY,
+ sources("foo/bar/b.cc"),
+ defines("SAME=1"),
+ includes()))
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:c",
+ Kind.CC_BINARY,
+ sources("foo/bar/c.cc"),
+ defines("SAME=1"),
+ includes()))
+ .addTarget(
+ createCcTarget(
+ "//foo/bar:d",
+ Kind.CC_BINARY,
+ sources("foo/bar/d.cc"),
+ defines("SAME=1"),
+ includes()))
+ .build();
+ List<BlazeResolveConfiguration> newConfigurations = resolve(projectView, targetMap);
+ assertThat(newConfigurations).hasSize(1);
+ // What used to be "//foo/bar:a and 2 other target(s)" will be renamed to
+ // "//foo/bar:a and 3 other target(s)" and reused.
+ assertReusedConfigs(
+ configurations,
+ newConfigurations,
+ new ReusedConfigurationExpectations(
+ ImmutableList.of("//foo/bar:a and 3 other target(s)"), ImmutableList.of()));
+ }
+
+ private static List<ArtifactLocation> sources(String... paths) {
+ return Arrays.stream(paths)
+ .map(path -> ArtifactLocation.builder().setRelativePath(path).setIsSource(true).build())
+ .collect(Collectors.toList());
+ }
+
+ private static List<String> defines(String... defines) {
+ return Arrays.asList(defines);
+ }
+
+ private static List<ExecutionRootPath> includes(String... paths) {
+ return Arrays.stream(paths).map(ExecutionRootPath::new).collect(Collectors.toList());
+ }
+
+ private static TargetIdeInfo.Builder createCcTarget(
+ String label,
+ Kind kind,
+ List<ArtifactLocation> sources,
+ List<String> defines,
+ List<ExecutionRootPath> includes) {
+ TargetIdeInfo.Builder targetInfo =
+ TargetIdeInfo.builder().setLabel(label).setKind(kind).addDependency("//:toolchain");
+ sources.forEach(targetInfo::addSource);
+ return targetInfo.setCInfo(
+ CIdeInfo.builder()
+ .addSources(sources)
+ .addLocalDefines(defines)
+ .addLocalIncludeDirectories(includes));
+ }
+
+ private static TargetIdeInfo.Builder createCcToolchain() {
+ return TargetIdeInfo.builder()
+ .setLabel("//:toolchain")
+ .setKind(Kind.CC_TOOLCHAIN)
+ .setCToolchainInfo(
+ CToolchainIdeInfo.builder().setCppExecutable(new ExecutionRootPath("cc")));
+ }
+
+ private static ListSection<DirectoryEntry> directories(String... directories) {
+ return ListSection.builder(DirectorySection.KEY)
+ .addAll(
+ Arrays.stream(directories)
+ .map(directory -> DirectoryEntry.include(WorkspacePath.createIfValid(directory)))
+ .collect(Collectors.toList()))
+ .build();
+ }
+
+ private static ListSection<TargetExpression> targets(String... targets) {
+ return ListSection.builder(TargetSection.KEY)
+ .addAll(
+ Arrays.stream(targets).map(TargetExpression::fromString).collect(Collectors.toList()))
+ .build();
+ }
+
+ private static ProjectView projectView(
+ ListSection<DirectoryEntry> directories, ListSection<TargetExpression> targets) {
+ return ProjectView.builder().add(directories).add(targets).build();
+ }
+
+ private List<BlazeResolveConfiguration> resolve(ProjectView projectView, TargetMap targetMap) {
+ resolverResult =
+ resolver.update(
+ context,
+ workspaceRoot,
+ ProjectViewSet.builder().add(projectView).build(),
+ MockBlazeProjectDataBuilder.builder(workspaceRoot).setTargetMap(targetMap).build(),
+ resolverResult);
+ errorCollector.assertNoIssues();
+ return resolverResult.getAllConfigurations();
+ }
+
+ private static BlazeResolveConfiguration get(
+ List<BlazeResolveConfiguration> configurations, String name) {
+ List<BlazeResolveConfiguration> filteredConfigurations =
+ configurations
+ .stream()
+ .filter(c -> c.getDisplayName(false).equals(name))
+ .collect(Collectors.toList());
+ assertWithMessage(
+ String.format(
+ "%s contains %s",
+ configurations
+ .stream()
+ .map(c -> c.getDisplayName(false))
+ .collect(Collectors.toList()),
+ name))
+ .that(filteredConfigurations)
+ .hasSize(1);
+ return filteredConfigurations.get(0);
+ }
+
+ private BlazeCompilerMacros macros(String... defines) {
+ return new BlazeCompilerMacros(
+ project, null, null, ImmutableList.copyOf(defines), ImmutableMap.of());
+ }
+
+ private HeadersSearchRoot header(VirtualFile include) {
+ return new IncludedHeadersRoot(project, include, false, true);
+ }
+
+ private static List<HeadersSearchRoot> getHeaders(
+ BlazeResolveConfiguration configuration, OCLanguageKind languageKind) {
+ return configuration
+ .getLibraryHeadersRoots(new OCResolveRootAndConfiguration(configuration, languageKind))
+ .getRoots();
+ }
+
+ private VirtualFile createVirtualFile(String path) {
+ VirtualFile stub = new StubVirtualFile();
+ when(mockFileSystem.findFileByIoFile(new File(path))).thenReturn(stub);
+ return stub;
+ }
+
+ private static void assertReusedConfigs(
+ List<BlazeResolveConfiguration> oldConfigurations,
+ List<BlazeResolveConfiguration> newConfigurations,
+ ReusedConfigurationExpectations expected) {
+ for (String label : expected.reusedLabels) {
+ assertWithMessage(String.format("Checking that %s is reused", label))
+ .that(get(newConfigurations, label))
+ .isSameAs(get(oldConfigurations, label));
+ }
+ for (String label : expected.notReusedLabels) {
+ assertWithMessage(String.format("Checking that %s is NOT reused", label))
+ .that(get(newConfigurations, label))
+ .isNotSameAs(get(oldConfigurations, label));
+ }
+ }
+
+ private static class ReusedConfigurationExpectations {
+ final ImmutableCollection<String> reusedLabels;
+ final ImmutableCollection<String> notReusedLabels;
+
+ ReusedConfigurationExpectations(
+ ImmutableCollection<String> reusedLabels, ImmutableCollection<String> notReusedLabels) {
+ this.reusedLabels = reusedLabels;
+ this.notReusedLabels = notReusedLabels;
+ }
+ }
+}
diff --git a/golang/BUILD b/golang/BUILD
index 16a486c..99181ab 100644
--- a/golang/BUILD
+++ b/golang/BUILD
@@ -1,26 +1,69 @@
licenses(["notice"]) # Apache 2.0
+load(
+ "//testing:test_defs.bzl",
+ "intellij_integration_test_suite",
+ "intellij_unit_test_suite",
+)
+load(
+ "//build_defs:build_defs.bzl",
+ "intellij_plugin",
+ "merged_plugin_xml",
+ "optional_plugin_xml",
+ "stamped_plugin_xml",
+)
+
java_library(
name = "golang",
srcs = glob(["src/**/*.java"]),
visibility = ["//visibility:public"],
deps = [
"//base",
+ "//common/experiments",
"//intellij_platform_sdk:plugin_api",
"//sdkcompat",
+ "//third_party/go",
"@jsr305_annotations//jar",
],
)
filegroup(
name = "plugin_xml",
- srcs = ["src/META-INF/golang.xml"],
+ srcs = ["src/META-INF/blaze-go.xml"],
visibility = ["//visibility:public"],
)
-load(
- "//testing:test_defs.bzl",
- "intellij_unit_test_suite",
+optional_plugin_xml(
+ name = "optional_xml",
+ module = "org.jetbrains.plugins.go",
+ plugin_xml = "src/META-INF/go-contents.xml",
+ visibility = ["//visibility:public"],
+)
+
+merged_plugin_xml(
+ name = "merged_plugin_xml",
+ srcs = [
+ "//base:plugin_xml",
+ ] + [
+ ":plugin_xml",
+ ],
+)
+
+stamped_plugin_xml(
+ name = "golang_plugin_xml",
+ plugin_id = "com.google.idea.blaze.golang",
+ plugin_name = "com.google.idea.blaze.golang",
+ plugin_xml = "merged_plugin_xml",
+)
+
+intellij_plugin(
+ name = "golang_integration_test_plugin",
+ testonly = 1,
+ optional_plugin_xmls = [":optional_xml"],
+ plugin_xml = ":golang_plugin_xml",
+ deps = [
+ ":golang",
+ ],
)
intellij_unit_test_suite(
@@ -36,3 +79,26 @@
"@junit//jar",
],
)
+
+intellij_integration_test_suite(
+ name = "integration_tests",
+ srcs = glob(["tests/integrationtests/**/*.java"]),
+ platform_prefix = "",
+ required_plugins = "com.google.idea.blaze.golang",
+ test_package_root = "com.google.idea.blaze.golang",
+ runtime_deps = [
+ ":golang_integration_test_plugin",
+ ],
+ deps = [
+ ":golang",
+ "//base",
+ "//base:integration_test_utils",
+ "//base:unit_test_utils",
+ "//common/experiments",
+ "//common/experiments:unit_test_utils",
+ "//intellij_platform_sdk:plugin_api_for_tests",
+ "//third_party/go:go_for_tests",
+ "@jsr305_annotations//jar",
+ "@junit//jar",
+ ],
+)
diff --git a/golang/src/META-INF/blaze-go.xml b/golang/src/META-INF/blaze-go.xml
new file mode 100644
index 0000000..5219884
--- /dev/null
+++ b/golang/src/META-INF/blaze-go.xml
@@ -0,0 +1,20 @@
+<!--
+ ~ Copyright 2017 The Bazel Authors. All rights reserved.
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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>
+ <extensions defaultExtensionNs="com.google.idea.blaze">
+ <SyncPlugin implementation="com.google.idea.blaze.golang.sync.AlwaysPresentGoSyncPlugin"/>
+ </extensions>
+</idea-plugin>
\ No newline at end of file
diff --git a/golang/src/META-INF/go-contents.xml b/golang/src/META-INF/go-contents.xml
new file mode 100644
index 0000000..079f1ed
--- /dev/null
+++ b/golang/src/META-INF/go-contents.xml
@@ -0,0 +1,28 @@
+<!--
+ ~ Copyright 2017 The Bazel Authors. All rights reserved.
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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>
+
+ <extensions defaultExtensionNs="com.google.idea.blaze">
+ <SyncPlugin implementation="com.google.idea.blaze.golang.sync.BlazeGoSyncPlugin"/>
+ <SyncListener implementation="com.google.idea.blaze.golang.sync.BlazeGoSdkUpdater"/>
+ </extensions>
+
+ <extensions defaultExtensionNs="com.goide">
+ <importResolver implementation="com.google.idea.blaze.golang.resolve.BlazeGoImportResolver"
+ order="first"/>
+ </extensions>
+
+</idea-plugin>
\ No newline at end of file
diff --git a/golang/src/META-INF/golang.xml b/golang/src/META-INF/golang.xml
deleted file mode 100644
index 32948ee..0000000
--- a/golang/src/META-INF/golang.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<idea-plugin>
-
- <extensions defaultExtensionNs="com.google.idea.blaze">
- <SyncPlugin implementation="com.google.idea.blaze.golang.sync.BlazeGoSyncPlugin"/>
- </extensions>
-
-</idea-plugin>
diff --git a/golang/src/com/google/idea/blaze/golang/BlazeGoSupport.java b/golang/src/com/google/idea/blaze/golang/BlazeGoSupport.java
new file mode 100644
index 0000000..fb6afe1
--- /dev/null
+++ b/golang/src/com/google/idea/blaze/golang/BlazeGoSupport.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.golang;
+
+import com.google.idea.common.experiments.BoolExperiment;
+
+/**
+ * Controls whether Blaze go-lang support is activated. If this is disabled, we make no attempt to
+ * resolve Blaze-specific import formats, etc.
+ *
+ * <p>If this is enabled, we override some default Go plugin behaviors.
+ */
+public class BlazeGoSupport {
+
+ public static final BoolExperiment blazeGoSupportEnabled =
+ new BoolExperiment("blaze.go.support.enabled", false);
+}
diff --git a/golang/src/com/google/idea/blaze/golang/resolve/BlazeGoImportResolver.java b/golang/src/com/google/idea/blaze/golang/resolve/BlazeGoImportResolver.java
new file mode 100644
index 0000000..1767d6b
--- /dev/null
+++ b/golang/src/com/google/idea/blaze/golang/resolve/BlazeGoImportResolver.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.golang.resolve;
+
+import com.goide.psi.GoImportSpec;
+import com.goide.psi.impl.imports.GoImportResolver;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
+import com.google.idea.blaze.base.io.VirtualFileSystemProvider;
+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.BuildReferenceManager;
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.model.primitives.Kind;
+import com.google.idea.blaze.base.model.primitives.Label;
+import com.google.idea.blaze.base.model.primitives.TargetName;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
+import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
+import com.google.idea.blaze.base.settings.Blaze;
+import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
+import com.google.idea.blaze.golang.BlazeGoSupport;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiManager;
+import java.io.File;
+import javax.annotation.Nullable;
+
+/**
+ * Resolves go imports in a blaze workspace, of the form:
+ *
+ * <p>"[workspace_name]/path/to/blaze/package/[go_library target]"
+ *
+ * <p>Only the first non-null import candidate is considered, so all blaze-specific import handling
+ * is done in this {@link GoImportResolver}, to more easily manage priority.
+ */
+public class BlazeGoImportResolver implements GoImportResolver {
+
+ @Nullable
+ @Override
+ public PsiDirectory resolve(GoImportSpec goImportSpec) {
+ Project project = goImportSpec.getProject();
+ if (!Blaze.isBlazeProject(project) || !BlazeGoSupport.blazeGoSupportEnabled.getValue()) {
+ return null;
+ }
+ // TODO: Handle go packages whose sources are in multiple directories (requires upstream change)
+ String pathString = goImportSpec.getPath();
+ String packageName = getPackageName(pathString);
+
+ GoTarget target = findGoTarget(project, pathString, packageName);
+ if (target == null) {
+ return null;
+ }
+ switch (target.kind) {
+ case GO_LIBRARY:
+ case GO_APPENGINE_LIBRARY:
+ return resolveFile(PsiManager.getInstance(project), target.buildFile.getParentFile());
+ case PROTO_LIBRARY:
+ case GO_WRAP_CC:
+ return resolveGenfilesPath(project, target.label.blazePackage().relativePath());
+ default:
+ return null;
+ }
+ }
+
+ private static String getPackageName(String pathString) {
+ int ix = pathString.lastIndexOf('/');
+ return ix == -1 ? pathString : pathString.substring(ix + 1);
+ }
+
+ @Nullable
+ private static GoTarget findGoTarget(Project project, String importPath, String packageName) {
+ BlazeProjectData projectData =
+ BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
+ if (projectData == null) {
+ return null;
+ }
+ TargetName targetName = TargetName.createIfValid(packageName);
+ if (targetName == null) {
+ return null;
+ }
+ WorkspacePath workspacePath = blazePackageWorkspacePath(project, importPath);
+ if (workspacePath == null) {
+ return null;
+ }
+ Label label = Label.create(workspacePath, targetName);
+ GoTarget goTarget = GoTarget.fromTargetIdeInfo(projectData, label);
+ if (goTarget != null) {
+ return goTarget;
+ }
+ // if the target wasn't indexed, try parsing the BUILD file manually
+ return GoTarget.manuallyParseBuildFile(project, label);
+ }
+
+ @Nullable
+ private static PsiDirectory resolveGenfilesPath(Project project, String relativePath) {
+ BlazeProjectData projectData =
+ BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
+ if (projectData == null) {
+ return null;
+ }
+ File genfiles = projectData.blazeInfo.getGenfilesDirectory();
+ return resolveFile(PsiManager.getInstance(project), new File(genfiles, relativePath));
+ }
+
+ @Nullable
+ private static PsiDirectory resolveFile(PsiManager manager, File file) {
+ VirtualFile vf =
+ VirtualFileSystemProvider.getInstance().getSystem().findFileByPath(file.getPath());
+ return vf != null && vf.isDirectory() ? manager.findDirectory(vf) : null;
+ }
+
+ @Nullable
+ private static WorkspacePath blazePackageWorkspacePath(Project project, String importPath) {
+ String workspaceName = WorkspaceRoot.fromProject(project).directory().getName();
+ if (!importPath.startsWith(workspaceName + "/")) {
+ return null;
+ }
+ // strip first and last path components (workspace name, go package name)
+ importPath = importPath.substring(workspaceName.length() + 1);
+ int lastSeparator = importPath.lastIndexOf('/');
+ if (lastSeparator <= 0) {
+ return null;
+ }
+ return WorkspacePath.createIfValid(importPath.substring(0, lastSeparator));
+ }
+
+ private static class GoTarget {
+ final File buildFile;
+ final Kind kind;
+ final Label label;
+
+ GoTarget(File buildFile, Kind kind, Label label) {
+ this.buildFile = buildFile;
+ this.kind = kind;
+ this.label = label;
+ }
+
+ @Nullable
+ static GoTarget fromTargetIdeInfo(BlazeProjectData projectData, Label label) {
+ TargetIdeInfo target = projectData.targetMap.get(TargetKey.forPlainTarget(label));
+ if (target == null) {
+ return null;
+ }
+ File buildFile = projectData.artifactLocationDecoder.decode(target.buildFile);
+ return new GoTarget(buildFile, target.kind, target.key.label);
+ }
+
+ @Nullable
+ static GoTarget manuallyParseBuildFile(Project project, Label label) {
+ PsiElement psiElement = BuildReferenceManager.getInstance(project).resolveLabel(label);
+ if (!(psiElement instanceof FuncallExpression)) {
+ return null;
+ }
+ FuncallExpression funcall = (FuncallExpression) psiElement;
+ Kind kind = funcall.getRuleKind();
+ BuildFile parentFile = funcall.getContainingFile();
+ return kind == null || parentFile == null
+ ? null
+ : new GoTarget(parentFile.getFile(), kind, label);
+ }
+ }
+}
diff --git a/golang/src/com/google/idea/blaze/golang/sdk/GoSdkUtil.java b/golang/src/com/google/idea/blaze/golang/sdk/GoSdkUtil.java
deleted file mode 100644
index ba42571..0000000
--- a/golang/src/com/google/idea/blaze/golang/sdk/GoSdkUtil.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.golang.sdk;
-
-import com.intellij.execution.configurations.PathEnvironmentVariableUtil;
-import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.openapi.vfs.LocalFileSystem;
-import com.intellij.openapi.vfs.VirtualFile;
-import java.io.File;
-import java.io.IOException;
-import javax.annotation.Nullable;
-
-/**
- * Go-lang SDK utility methods.
- *
- * <p>TODO: Remove this, and reference go-lang plugin source code directly.
- */
-public class GoSdkUtil {
-
- @Nullable
- public static VirtualFile suggestSdkDirectory() {
- String fromEnv = suggestSdkDirectoryPathFromEnv();
- if (fromEnv != null) {
- return LocalFileSystem.getInstance().findFileByPath(fromEnv);
- }
- return LocalFileSystem.getInstance().findFileByPath("/usr/local/go");
- }
-
- @Nullable
- private static String suggestSdkDirectoryPathFromEnv() {
- File fileFromPath = PathEnvironmentVariableUtil.findInPath("go");
- if (fileFromPath != null) {
- File canonicalFile;
- try {
- canonicalFile = fileFromPath.getCanonicalFile();
- String path = canonicalFile.getPath();
- if (path.endsWith("bin/go")) {
- return StringUtil.trimEnd(path, "bin/go");
- }
- } catch (IOException e) {
- // if it can't be found, just silently return null
- }
- }
- return null;
- }
-}
diff --git a/golang/src/com/google/idea/blaze/golang/sync/AlwaysPresentGoSyncPlugin.java b/golang/src/com/google/idea/blaze/golang/sync/AlwaysPresentGoSyncPlugin.java
new file mode 100644
index 0000000..a97a5f6
--- /dev/null
+++ b/golang/src/com/google/idea/blaze/golang/sync/AlwaysPresentGoSyncPlugin.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.golang.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.plugin.PluginUtils;
+import com.google.idea.blaze.base.projectview.ProjectView;
+import com.google.idea.blaze.base.projectview.ProjectViewEdit;
+import com.google.idea.blaze.base.projectview.ProjectViewSet;
+import com.google.idea.blaze.base.projectview.ProjectViewSet.ProjectViewFile;
+import com.google.idea.blaze.base.projectview.section.ListSection;
+import com.google.idea.blaze.base.projectview.section.ScalarSection;
+import com.google.idea.blaze.base.projectview.section.sections.AdditionalLanguagesSection;
+import com.google.idea.blaze.base.projectview.section.sections.WorkspaceTypeSection;
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.scope.output.IssueOutput;
+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.BlazeSyncPlugin;
+import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
+import com.intellij.ide.plugins.PluginManager;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.pom.NavigatableAdapter;
+import com.intellij.util.PlatformUtils;
+import java.util.Set;
+import javax.annotation.Nullable;
+
+/**
+ * Unlike most of the go-specific code, will be run even if the JetBrains Go plugin isn't enabled.
+ */
+public class AlwaysPresentGoSyncPlugin extends BlazeSyncPlugin.Adapter {
+
+ private static final String GO_PLUGIN_ID = "org.jetbrains.plugins.go";
+ private static final String OLD_GO_PLUGIN_ID = "ro.redeul.google.go";
+
+ @Override
+ public Set<LanguageClass> getSupportedLanguagesInWorkspace(WorkspaceType workspaceType) {
+ return ImmutableSet.of(LanguageClass.GO);
+ }
+
+ @Override
+ public boolean validate(
+ Project project, BlazeContext context, BlazeProjectData blazeProjectData) {
+ if (!blazeProjectData.workspaceLanguageSettings.isLanguageActive(LanguageClass.GO)
+ || PluginUtils.isPluginEnabled(GO_PLUGIN_ID)) {
+ return true;
+ }
+ if (PlatformUtils.isIdeaCommunity() && !ApplicationManager.getApplication().isUnitTestMode()) {
+ IssueOutput.error(
+ String.format(
+ "Go is no longer supported by the %s plugin with IntelliJ Community Edition.\n"
+ + "Please install Ultimate Edition and upgrade to the JetBrains Go plugin",
+ Blaze.defaultBuildSystemName()))
+ .submit(context);
+ }
+ if (PluginUtils.isPluginEnabled(OLD_GO_PLUGIN_ID)) {
+ String error =
+ String.format(
+ "The currently installed Go plugin is no longer supported by the %s plugin.\n"
+ + "Click here to install the new JetBrains Go plugin and restart.",
+ Blaze.defaultBuildSystemName());
+ IssueOutput.error(error)
+ .navigatable(
+ new NavigatableAdapter() {
+ @Override
+ public void navigate(boolean requestFocus) {
+ PluginManager.disablePlugin(OLD_GO_PLUGIN_ID);
+ PluginUtils.installOrEnablePlugin(GO_PLUGIN_ID);
+ }
+ })
+ .submit(context);
+ return true;
+ }
+ IssueOutput.error(
+ "Go support requires the Go plugin. Click here to install/enable the JetBrains Go "
+ + "plugin, then restart the IDE")
+ .navigatable(PluginUtils.installOrEnablePluginNavigable(GO_PLUGIN_ID))
+ .submit(context);
+ return true;
+ }
+
+ @Override
+ public boolean validateProjectView(
+ @Nullable Project project,
+ BlazeContext context,
+ ProjectViewSet projectViewSet,
+ WorkspaceLanguageSettings workspaceLanguageSettings) {
+ if (!workspaceLanguageSettings.isWorkspaceType(WorkspaceType.GO)) {
+ return true;
+ }
+ ProjectViewFile topLevelProjectViewFile = projectViewSet.getTopLevelProjectViewFile();
+ String msg =
+ "Go workspace_type is no longer supported. Please add 'go' to "
+ + "additional_languages instead";
+ boolean fixable =
+ project != null
+ && topLevelProjectViewFile != null
+ && topLevelProjectViewFile.projectView.getScalarValue(WorkspaceTypeSection.KEY)
+ == WorkspaceType.GO;
+ msg += fixable ? ". Click here to fix your .blazeproject and resync." : ", then resync.";
+ IssueOutput.error(msg)
+ .navigatable(
+ !fixable
+ ? null
+ : new NavigatableAdapter() {
+ @Override
+ public void navigate(boolean requestFocus) {
+ fixLanguageSupport(project);
+ }
+ })
+ .submit(context);
+ return false;
+ }
+
+ private static void fixLanguageSupport(Project project) {
+ ProjectViewEdit edit =
+ ProjectViewEdit.editLocalProjectView(
+ project,
+ builder -> {
+ removeGoWorkspaceType(builder);
+ addToAdditionalLanguages(builder);
+ 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());
+ }
+
+ private static void removeGoWorkspaceType(ProjectView.Builder builder) {
+ ScalarSection<WorkspaceType> section = builder.getLast(WorkspaceTypeSection.KEY);
+ if (section != null && section.getValue() == WorkspaceType.GO) {
+ builder.remove(section);
+ }
+ }
+
+ private static void addToAdditionalLanguages(ProjectView.Builder builder) {
+ ListSection<LanguageClass> existingSection = builder.getLast(AdditionalLanguagesSection.KEY);
+ builder.replace(
+ existingSection,
+ ListSection.update(AdditionalLanguagesSection.KEY, existingSection).add(LanguageClass.GO));
+ }
+}
diff --git a/golang/src/com/google/idea/blaze/golang/sync/BlazeGoLibrarySource.java b/golang/src/com/google/idea/blaze/golang/sync/BlazeGoLibrarySource.java
index 693c5f7..bbc8f9d 100644
--- a/golang/src/com/google/idea/blaze/golang/sync/BlazeGoLibrarySource.java
+++ b/golang/src/com/google/idea/blaze/golang/sync/BlazeGoLibrarySource.java
@@ -33,6 +33,14 @@
static boolean isGoLibrary(Library library) {
String name = library.getName();
- return name != null && name.startsWith(BlazeGoSyncPlugin.GO_LIBRARY_PREFIX);
+ if (name == null) {
+ return false;
+ }
+ for (String prefix : BlazeGoSyncPlugin.GO_LIBRARY_PREFIXES) {
+ if (name.startsWith(prefix)) {
+ return true;
+ }
+ }
+ return false;
}
}
diff --git a/golang/src/com/google/idea/blaze/golang/sync/BlazeGoSdkUpdater.java b/golang/src/com/google/idea/blaze/golang/sync/BlazeGoSdkUpdater.java
new file mode 100644
index 0000000..7b8d621
--- /dev/null
+++ b/golang/src/com/google/idea/blaze/golang/sync/BlazeGoSdkUpdater.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.golang.sync;
+
+import com.goide.project.GoModuleSettings;
+import com.goide.sdk.GoSdk;
+import com.goide.sdk.GoSdkService;
+import com.goide.sdk.GoSdkUtil;
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.model.primitives.LanguageClass;
+import com.google.idea.blaze.base.projectview.ProjectViewSet;
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.settings.BlazeImportSettings;
+import com.google.idea.blaze.base.sync.BlazeSyncParams.SyncMode;
+import com.google.idea.blaze.base.sync.SyncListener;
+import com.google.idea.blaze.base.sync.data.BlazeDataStorage;
+import com.google.idea.sdkcompat.transactions.Transactions;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ReadAction;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import javax.annotation.Nullable;
+
+/**
+ * Runs after sync. Sets up a Go SDK library if Go-lang is active, and there's no existing library
+ * set up.
+ */
+public class BlazeGoSdkUpdater extends SyncListener.Adapter {
+
+ @Override
+ public void onSyncComplete(
+ Project project,
+ BlazeContext context,
+ BlazeImportSettings importSettings,
+ ProjectViewSet projectViewSet,
+ BlazeProjectData blazeProjectData,
+ SyncMode syncMode,
+ SyncResult syncResult) {
+ if (!blazeProjectData.workspaceLanguageSettings.isLanguageActive(LanguageClass.GO)) {
+ return;
+ }
+ Module workspaceModule = getWorkspaceModule(project);
+ if (workspaceModule == null || GoSdkService.getInstance(project).isGoModule(workspaceModule)) {
+ return;
+ }
+ String sdkPath = getOrSuggestSdkPath(workspaceModule);
+ if (sdkPath != null) {
+ setSdkPath(project, workspaceModule, sdkPath);
+ }
+ }
+
+ private static void setSdkPath(Project project, Module workspaceModule, String path) {
+ Transactions.submitTransactionAndWait(
+ () ->
+ ApplicationManager.getApplication()
+ .runWriteAction(
+ () -> {
+ GoSdkService.getInstance(project).setSdkHomePath(path);
+ GoModuleSettings.getInstance(workspaceModule).setGoSupportEnabled(true);
+ }));
+ }
+
+ @Nullable
+ private static String getOrSuggestSdkPath(Module module) {
+ GoSdk sdk = GoSdkService.getInstance(module.getProject()).getSdk(module);
+ if (sdk != GoSdk.NULL) {
+ return sdk.getHomePath();
+ }
+ VirtualFile defaultSdk = GoSdkUtil.suggestSdkDirectory();
+ return defaultSdk != null ? defaultSdk.getPath() : null;
+ }
+
+ @Nullable
+ private static Module getWorkspaceModule(Project project) {
+ return ReadAction.compute(
+ () ->
+ ModuleManager.getInstance(project)
+ .findModuleByName(BlazeDataStorage.WORKSPACE_MODULE_NAME));
+ }
+}
diff --git a/golang/src/com/google/idea/blaze/golang/sync/BlazeGoSyncPlugin.java b/golang/src/com/google/idea/blaze/golang/sync/BlazeGoSyncPlugin.java
index d65c8e6..0905ac1 100644
--- a/golang/src/com/google/idea/blaze/golang/sync/BlazeGoSyncPlugin.java
+++ b/golang/src/com/google/idea/blaze/golang/sync/BlazeGoSyncPlugin.java
@@ -15,40 +15,25 @@
*/
package com.google.idea.blaze.golang.sync;
-import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.io.VirtualFileSystemProvider;
import com.google.idea.blaze.base.model.BlazeProjectData;
-import com.google.idea.blaze.base.model.BlazeVersionData;
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.plugin.PluginUtils;
import com.google.idea.blaze.base.projectview.ProjectViewSet;
import com.google.idea.blaze.base.scope.BlazeContext;
-import com.google.idea.blaze.base.scope.output.IssueOutput;
import com.google.idea.blaze.base.sync.BlazeSyncPlugin;
-import com.google.idea.blaze.base.sync.GenericSourceFolderProvider;
-import com.google.idea.blaze.base.sync.SourceFolderProvider;
-import com.google.idea.blaze.base.sync.data.BlazeDataStorage;
import com.google.idea.blaze.base.sync.libraries.LibrarySource;
-import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
-import com.google.idea.blaze.golang.sdk.GoSdkUtil;
-import com.google.idea.sdkcompat.transactions.Transactions;
-import com.intellij.openapi.application.ApplicationManager;
+import com.google.idea.common.experiments.BoolExperiment;
+import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
-import com.intellij.openapi.module.ModuleType;
-import com.intellij.openapi.module.ModuleTypeManager;
import com.intellij.openapi.project.Project;
-import com.intellij.openapi.projectRoots.ProjectJdkTable;
-import com.intellij.openapi.projectRoots.Sdk;
-import com.intellij.openapi.projectRoots.SdkType;
-import com.intellij.openapi.projectRoots.SdkTypeId;
-import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil;
import com.intellij.openapi.roots.ModifiableRootModel;
-import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.roots.libraries.Library;
import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar;
+import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import java.util.List;
import java.util.Set;
@@ -57,46 +42,16 @@
/** Supports golang. */
public class BlazeGoSyncPlugin extends BlazeSyncPlugin.Adapter {
- static final String GO_LIBRARY_PREFIX = "GOPATH";
- private static final String GO_MODULE_TYPE_ID = "GO_MODULE";
- private static final String GO_PLUGIN_ID = "ro.redeul.google.go";
- private static final String GO_SDK_TYPE_ID = "Go SDK";
+ private static final Logger logger = Logger.getInstance(BlazeGoSyncPlugin.class);
- @Nullable
- @Override
- public ModuleType<?> getWorkspaceModuleType(WorkspaceType workspaceType) {
- if (workspaceType == WorkspaceType.GO) {
- return ModuleTypeManager.getInstance().findByID(GO_MODULE_TYPE_ID);
- }
- return null;
- }
+ private static final BoolExperiment refreshExecRoot =
+ new BoolExperiment("refresh.exec.root.golang", true);
- @Override
- public ImmutableList<WorkspaceType> getSupportedWorkspaceTypes() {
- return ImmutableList.of(WorkspaceType.GO);
- }
+ static final ImmutableSet<String> GO_LIBRARY_PREFIXES = ImmutableSet.of("GOPATH", "Go SDK");
@Override
public Set<LanguageClass> getSupportedLanguagesInWorkspace(WorkspaceType workspaceType) {
- if (workspaceType == WorkspaceType.GO) {
- return ImmutableSet.of(LanguageClass.GO);
- }
- return ImmutableSet.of();
- }
-
- @Nullable
- @Override
- public WorkspaceType getDefaultWorkspaceType() {
- return WorkspaceType.GO;
- }
-
- @Nullable
- @Override
- public SourceFolderProvider getSourceFolderProvider(BlazeProjectData projectData) {
- if (!projectData.workspaceLanguageSettings.isWorkspaceType(WorkspaceType.GO)) {
- return null;
- }
- return GenericSourceFolderProvider.INSTANCE;
+ return ImmutableSet.of(LanguageClass.GO);
}
@Override
@@ -129,25 +84,14 @@
}
}
- String moduleLibraryName =
- String.format("%s <%s>", GO_LIBRARY_PREFIX, BlazeDataStorage.WORKSPACE_MODULE_NAME);
- Library goModuleLibrary =
- registrar.getLibraryTable(project).getLibraryByName(moduleLibraryName);
- if (goModuleLibrary != null) {
- libraries.add(goModuleLibrary);
+ for (Library lib : registrar.getLibraryTable(project).getLibraries()) {
+ if (BlazeGoLibrarySource.isGoLibrary(lib)) {
+ libraries.add(lib);
+ }
}
return libraries;
}
- /**
- * By default the Go plugin will create duplicate copies of project libraries, one for each
- * module. We only care about library associated with the workspace module.
- */
- static boolean isGoLibraryForModule(Library library, String moduleName) {
- String name = library.getName();
- return name != null && name.equals("GOPATH <" + moduleName + ">");
- }
-
@Nullable
@Override
public LibrarySource getLibrarySource(
@@ -159,62 +103,28 @@
}
@Override
- public boolean validateProjectView(
- @Nullable Project project,
- BlazeContext context,
- ProjectViewSet projectViewSet,
- WorkspaceLanguageSettings workspaceLanguageSettings) {
- if (!workspaceLanguageSettings.isLanguageActive(LanguageClass.GO)) {
- return true;
- }
- if (!PluginUtils.isPluginEnabled(GO_PLUGIN_ID)) {
- IssueOutput.error("Go plugin needed for Go language support.")
- .navigatable(PluginUtils.installOrEnablePluginNavigable(GO_PLUGIN_ID))
- .submit(context);
- return false;
- }
- return true;
- }
-
- @Override
- public void updateProjectSdk(
- Project project,
- BlazeContext context,
- ProjectViewSet projectViewSet,
- BlazeVersionData blazeVersionData,
- BlazeProjectData blazeProjectData) {
- if (!blazeProjectData.workspaceLanguageSettings.isWorkspaceType(WorkspaceType.GO)) {
+ public void refreshVirtualFileSystem(BlazeProjectData blazeProjectData) {
+ if (!blazeProjectData.workspaceLanguageSettings.isLanguageActive(LanguageClass.GO)) {
return;
}
- Sdk currentSdk = ProjectRootManager.getInstance(project).getProjectSdk();
- if (currentSdk != null && currentSdk.getSdkType().getName().equals(GO_SDK_TYPE_ID)) {
+ if (!refreshExecRoot.getValue()) {
return;
}
- Sdk sdk = getOrCreateGoSdk();
- if (sdk != null) {
- setProjectSdk(project, sdk);
- }
+ long start = System.currentTimeMillis();
+ refreshExecRoot(blazeProjectData);
+ long end = System.currentTimeMillis();
+ logger.info(String.format("Refreshing execution root took: %d ms", (end - start)));
}
- @Nullable
- private static Sdk getOrCreateGoSdk() {
- ProjectJdkTable sdkTable = ProjectJdkTable.getInstance();
- SdkTypeId type = sdkTable.getSdkTypeByName(GO_SDK_TYPE_ID);
- List<Sdk> sdk = sdkTable.getSdksOfType(type);
- if (!sdk.isEmpty()) {
- return sdk.get(0);
+ private static void refreshExecRoot(BlazeProjectData blazeProjectData) {
+ // recursive refresh of the blaze execution root. This is required because our blaze aspect
+ // can't yet tell us exactly which genfiles are required to resolve the project.
+ VirtualFile execRoot =
+ VirtualFileSystemProvider.getInstance()
+ .getSystem()
+ .refreshAndFindFileByIoFile(blazeProjectData.blazeInfo.getExecutionRoot());
+ if (execRoot != null) {
+ VfsUtil.markDirtyAndRefresh(false, true, true, execRoot);
}
- VirtualFile defaultSdk = GoSdkUtil.suggestSdkDirectory();
- if (defaultSdk != null) {
- return SdkConfigurationUtil.createAndAddSDK(defaultSdk.getPath(), (SdkType) type);
- }
- return null;
- }
-
- private static void setProjectSdk(Project project, Sdk sdk) {
- Transactions.submitTransactionAndWait(
- () ->
- ApplicationManager.getApplication()
- .runWriteAction(() -> ProjectRootManager.getInstance(project).setProjectSdk(sdk)));
}
}
diff --git a/golang/tests/integrationtests/com/google/idea/blaze/golang/resolve/BlazeGoImportResolverTest.java b/golang/tests/integrationtests/com/google/idea/blaze/golang/resolve/BlazeGoImportResolverTest.java
new file mode 100644
index 0000000..1dce235
--- /dev/null
+++ b/golang/tests/integrationtests/com/google/idea/blaze/golang/resolve/BlazeGoImportResolverTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.golang.resolve;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.goide.psi.GoFile;
+import com.goide.psi.GoImportSpec;
+import com.goide.psi.GoTypeReferenceExpression;
+import com.goide.psi.GoTypeSpec;
+import com.google.idea.blaze.base.BlazeIntegrationTestCase;
+import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetMapBuilder;
+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.MockBlazeProjectDataBuilder;
+import com.google.idea.blaze.base.model.MockBlazeProjectDataManager;
+import com.google.idea.blaze.base.model.primitives.Kind;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
+import com.google.idea.blaze.base.plugin.PluginUtils;
+import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
+import com.google.idea.blaze.golang.BlazeGoSupport;
+import com.google.idea.common.experiments.ExperimentService;
+import com.google.idea.common.experiments.MockExperimentService;
+import java.util.Arrays;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Integration tests for {@link BlazeGoImportResolver}. */
+@RunWith(JUnit4.class)
+public class BlazeGoImportResolverTest extends BlazeIntegrationTestCase {
+
+ @Before
+ public void init() {
+ MockExperimentService experimentService = new MockExperimentService();
+ experimentService.setExperiment(BlazeGoSupport.blazeGoSupportEnabled, true);
+ registerApplicationComponent(ExperimentService.class, experimentService);
+ }
+
+ @Test
+ public void testGoPluginEnabled() {
+ assertThat(PluginUtils.isPluginEnabled("org.jetbrains.plugins.go")).isTrue();
+ }
+
+ @Test
+ public void testGoLibraryPackageResolves() {
+ setProjectTargets(
+ TargetIdeInfo.builder()
+ .setKind(Kind.GO_LIBRARY)
+ .setLabel("//package/path/foo:bar")
+ .setBuildFile(sourceRoot("package/path/foo/BUILD"))
+ .build());
+ GoFile barFile =
+ (GoFile)
+ workspace.createPsiFile(
+ new WorkspacePath("package/path/foo/baz.go"),
+ "package bar",
+ "type Struct struct {}");
+ GoFile referencingFile =
+ (GoFile)
+ workspace.createPsiFile(
+ new WorkspacePath("different/package/path/gofile.go"),
+ "package bar",
+ "import \"workspace/package/path/foo/bar\"",
+ "",
+ "type Context interface {",
+ " Method() (x bar.Struct)",
+ "}");
+
+ GoImportSpec importSpec =
+ PsiUtils.findFirstChildOfClassRecursive(referencingFile, GoImportSpec.class);
+ assertThat(importSpec).isNotNull();
+ assertThat(importSpec.resolve()).isEqualTo(barFile.getParent());
+
+ GoTypeReferenceExpression typeReference =
+ PsiUtils.findLastChildOfClassRecursive(referencingFile, GoTypeReferenceExpression.class);
+ GoTypeSpec typeSpec = PsiUtils.findFirstChildOfClassRecursive(barFile, GoTypeSpec.class);
+ assertThat(typeReference).isNotNull();
+ assertThat(typeReference.resolve()).isEqualTo(typeSpec);
+ }
+
+ private void setProjectTargets(TargetIdeInfo... targets) {
+ TargetMapBuilder targetMap = TargetMapBuilder.builder();
+ Arrays.stream(targets).forEach(targetMap::addTarget);
+ BlazeProjectData projectData =
+ MockBlazeProjectDataBuilder.builder(workspaceRoot).setTargetMap(targetMap.build()).build();
+ registerProjectService(
+ BlazeProjectDataManager.class, new MockBlazeProjectDataManager(projectData));
+ }
+
+ private static ArtifactLocation sourceRoot(String relativePath) {
+ return ArtifactLocation.builder().setRelativePath(relativePath).setIsSource(true).build();
+ }
+}
diff --git a/golang/tests/unittests/com/google/idea/blaze/golang/sync/BlazeGoSyncPluginTest.java b/golang/tests/unittests/com/google/idea/blaze/golang/sync/BlazeGoSyncPluginTest.java
index fe0cf37..f6ddb27 100644
--- a/golang/tests/unittests/com/google/idea/blaze/golang/sync/BlazeGoSyncPluginTest.java
+++ b/golang/tests/unittests/com/google/idea/blaze/golang/sync/BlazeGoSyncPluginTest.java
@@ -57,31 +57,8 @@
syncPluginEp = registerExtensionPoint(BlazeSyncPlugin.EP_NAME, BlazeSyncPlugin.class);
syncPluginEp.registerExtension(new BlazeGoSyncPlugin());
- context = new BlazeContext();
- context.addOutputSink(IssueOutput.class, errorCollector);
- }
-
- @Test
- public void testGoWorkspaceTypeSupported() {
- ProjectViewSet projectViewSet =
- ProjectViewSet.builder()
- .add(
- ProjectView.builder()
- .add(ScalarSection.builder(WorkspaceTypeSection.KEY).set(WorkspaceType.GO))
- .build())
- .build();
- WorkspaceLanguageSettings workspaceLanguageSettings =
- LanguageSupport.createWorkspaceLanguageSettings(projectViewSet);
- errorCollector.assertNoIssues();
- assertThat(workspaceLanguageSettings)
- .isEqualTo(
- new WorkspaceLanguageSettings(
- WorkspaceType.GO, ImmutableSet.of(LanguageClass.GENERIC, LanguageClass.GO)));
- }
-
- @Test
- public void testGoNotAValidAdditionalLanguage() {
- // add a java sync plugin so we have another workspace type available
+ syncPluginEp.registerExtension(new AlwaysPresentGoSyncPlugin());
+ // At least one sync plugin providing a default workspace type must be present
syncPluginEp.registerExtension(
new BlazeSyncPlugin.Adapter() {
@Override
@@ -100,7 +77,27 @@
return WorkspaceType.JAVA;
}
});
+ context = new BlazeContext();
+ context.addOutputSink(IssueOutput.class, errorCollector);
+ }
+ @Test
+ public void testGoWorkspaceTypeError() {
+ ProjectViewSet projectViewSet =
+ ProjectViewSet.builder()
+ .add(
+ ProjectView.builder()
+ .add(ScalarSection.builder(WorkspaceTypeSection.KEY).set(WorkspaceType.GO))
+ .build())
+ .build();
+ WorkspaceLanguageSettings workspaceLanguageSettings =
+ LanguageSupport.createWorkspaceLanguageSettings(projectViewSet);
+ LanguageSupport.validateLanguageSettings(context, workspaceLanguageSettings);
+ errorCollector.assertIssueContaining("Workspace type 'go' is not supported by this plugin");
+ }
+
+ @Test
+ public void testGoAdditionalLanguageSupported() {
ProjectViewSet projectViewSet =
ProjectViewSet.builder()
.add(
@@ -112,7 +109,7 @@
WorkspaceLanguageSettings workspaceLanguageSettings =
LanguageSupport.createWorkspaceLanguageSettings(projectViewSet);
LanguageSupport.validateLanguageSettings(context, workspaceLanguageSettings);
- errorCollector.assertIssueContaining(
- "Language 'go' is not supported for this plugin with workspace type: 'java'");
+ errorCollector.assertNoIssues();
+ assertThat(workspaceLanguageSettings.isLanguageActive(LanguageClass.GO)).isTrue();
}
}
diff --git a/ijwb/BUILD b/ijwb/BUILD
index ea9be4f..51cbc5d 100644
--- a/ijwb/BUILD
+++ b/ijwb/BUILD
@@ -8,9 +8,20 @@
"//build_defs:build_defs.bzl",
"intellij_plugin",
"merged_plugin_xml",
+ "plugin_deploy_zip",
+ "repackaged_files",
"stamped_plugin_xml",
)
+load(
+ "//build_defs:intellij_plugin_debug_target.bzl",
+ "intellij_plugin_debug_target",
+)
load("//:version.bzl", "VERSION")
+load(
+ "//testing:test_defs.bzl",
+ "intellij_integration_test_suite",
+ "intellij_unit_test_suite",
+)
merged_plugin_xml(
name = "merged_plugin_xml_common",
@@ -19,7 +30,6 @@
"//base:plugin_xml",
"//golang:plugin_xml",
"//java:plugin_xml",
- "//plugin_dev:plugin_xml",
"//python:plugin_xml",
"//scala:plugin_xml",
],
@@ -38,7 +48,7 @@
changelog_file = "//:changelog",
include_product_code_in_stamp = True,
plugin_id = "com.google.idea.bazel.ijwb",
- plugin_name = "IntelliJ with Bazel",
+ plugin_name = "Bazel",
plugin_xml = ":merged_plugin_xml",
stamp_since_build = True,
version = VERSION,
@@ -53,21 +63,25 @@
runtime_deps = [
"//golang",
"//python",
+ "//scala",
"//terminal",
],
deps = [
"//base",
+ "//common/experiments",
"//intellij_platform_sdk:plugin_api",
"//java",
- "//scala",
"//sdkcompat",
"@jsr305_annotations//jar",
],
)
OPTIONAL_PLUGIN_XMLS = [
- "//scala:optional_xml",
+ "//golang:optional_xml",
+ "//java:optional_xml",
+ "//plugin_dev:optional_xml",
"//python:optional_xml",
+ "//scala:optional_xml",
"//terminal:optional_xml",
]
@@ -80,10 +94,33 @@
],
)
-load(
- "//testing:test_defs.bzl",
- "intellij_integration_test_suite",
- "intellij_unit_test_suite",
+repackaged_files(
+ name = "plugin_jar",
+ srcs = [":ijwb_bazel"],
+ prefix = "ijwb/lib",
+)
+
+repackaged_files(
+ name = "aspect_directory",
+ srcs = ["//aspect:aspect_files"],
+ prefix = "ijwb/aspect",
+)
+
+intellij_plugin_debug_target(
+ name = "ijwb_bazel_dev",
+ deps = [
+ ":aspect_directory",
+ ":plugin_jar",
+ ],
+)
+
+plugin_deploy_zip(
+ name = "ijwb_bazel_zip",
+ srcs = [
+ ":aspect_directory",
+ ":plugin_jar",
+ ],
+ zip_filename = "ijwb_bazel.zip",
)
intellij_unit_test_suite(
@@ -105,7 +142,7 @@
intellij_integration_test_suite(
name = "integration_tests",
srcs = glob(["tests/integrationtests/**/*.java"]),
- required_plugins = "com.google.idea.blaze.ijwb",
+ required_plugins = "com.google.idea.bazel.ijwb",
test_package_root = "com.google.idea.blaze.ijwb",
runtime_deps = [
":ijwb_bazel",
diff --git a/ijwb/src/META-INF/ijwb.xml b/ijwb/src/META-INF/ijwb.xml
index 805e46e..8e22848 100644
--- a/ijwb/src/META-INF/ijwb.xml
+++ b/ijwb/src/META-INF/ijwb.xml
@@ -27,6 +27,8 @@
<SyncPlugin implementation="com.google.idea.blaze.ijwb.typescript.BlazeTypescriptSyncPlugin"/>
<SyncPlugin implementation="com.google.idea.blaze.ijwb.dart.BlazeDartSyncPlugin"/>
<JavaSyncAugmenter implementation="com.google.idea.blaze.ijwb.android.BlazeAndroidLiteJavaSyncAugmenter"/>
+ <PrefetchFileSource implementation="com.google.idea.blaze.ijwb.javascript.JavascriptPrefetchFileSource"/>
+ <PrefetchFileSource implementation="com.google.idea.blaze.ijwb.typescript.TypescriptPrefetchFileSource"/>
</extensions>
</idea-plugin>
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 5f3e545..606c768 100644
--- a/ijwb/src/com/google/idea/blaze/ijwb/android/BlazeAndroidLiteSyncPlugin.java
+++ b/ijwb/src/com/google/idea/blaze/ijwb/android/BlazeAndroidLiteSyncPlugin.java
@@ -29,7 +29,6 @@
import com.google.idea.blaze.base.sync.BlazeSyncPlugin;
import com.google.idea.blaze.base.sync.libraries.LibrarySource;
import com.google.idea.blaze.java.sync.model.BlazeJarLibrary;
-import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -62,7 +61,7 @@
}
return new LibrarySource.Adapter() {
@Override
- public Collection<? extends BlazeLibrary> getLibraries() {
+ public List<? extends BlazeLibrary> getLibraries() {
return ImmutableList.of(sdkLibrary);
}
};
diff --git a/ijwb/src/com/google/idea/blaze/ijwb/javascript/JavascriptPrefetchFileSource.java b/ijwb/src/com/google/idea/blaze/ijwb/javascript/JavascriptPrefetchFileSource.java
new file mode 100644
index 0000000..81d95e4
--- /dev/null
+++ b/ijwb/src/com/google/idea/blaze/ijwb/javascript/JavascriptPrefetchFileSource.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.ijwb.javascript;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.model.primitives.LanguageClass;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
+import com.google.idea.blaze.base.prefetch.PrefetchFileSource;
+import com.google.idea.blaze.base.projectview.ProjectViewSet;
+import com.google.idea.blaze.base.sync.projectview.ImportRoots;
+import com.google.idea.common.experiments.BoolExperiment;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.io.FileUtil;
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+/** Declare that js files should be prefetched. */
+public class JavascriptPrefetchFileSource implements PrefetchFileSource {
+
+ private static final BoolExperiment prefetchAllJsSources =
+ new BoolExperiment("prefetch.all.js.sources", true);
+
+ @Override
+ public void addFilesToPrefetch(
+ Project project,
+ ProjectViewSet projectViewSet,
+ ImportRoots importRoots,
+ BlazeProjectData blazeProjectData,
+ Set<File> files) {
+ if (!blazeProjectData.workspaceLanguageSettings.isLanguageActive(LanguageClass.JAVASCRIPT)
+ || !blazeProjectData.workspaceLanguageSettings.isLanguageActive(LanguageClass.TYPESCRIPT)) {
+ return;
+ }
+ if (!prefetchAllJsSources.getValue()) {
+ return;
+ }
+ // Prefetch all non-project js source files found during sync
+ Predicate<ArtifactLocation> shouldPrefetch =
+ location -> {
+ if (!location.isSource || location.isExternal) {
+ return false;
+ }
+ WorkspacePath path = WorkspacePath.createIfValid(location.relativePath);
+ if (path == null || importRoots.containsWorkspacePath(path)) {
+ return false;
+ }
+ String extension = FileUtil.getExtension(path.relativePath());
+ return prefetchFileExtensions().contains(extension);
+ };
+ List<File> sourceFiles =
+ blazeProjectData
+ .targetMap
+ .targets()
+ .stream()
+ .map(JavascriptPrefetchFileSource::getJsSources)
+ .flatMap(Collection::stream)
+ .filter(shouldPrefetch)
+ .map(blazeProjectData.artifactLocationDecoder::decode)
+ .collect(Collectors.toList());
+ files.addAll(sourceFiles);
+ }
+
+ @Override
+ public Set<String> prefetchFileExtensions() {
+ return ImmutableSet.of("js", "html", "css", "gss");
+ }
+
+ private static Collection<ArtifactLocation> getJsSources(TargetIdeInfo target) {
+ if (target.jsIdeInfo != null) {
+ return target.jsIdeInfo.sources;
+ }
+ if (target.kind.languageClass == LanguageClass.JAVASCRIPT) {
+ return target.sources;
+ }
+ return ImmutableList.of();
+ }
+}
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 8d8ab1a..016f0cb 100644
--- a/ijwb/src/com/google/idea/blaze/ijwb/typescript/BlazeTypescriptSyncPlugin.java
+++ b/ijwb/src/com/google/idea/blaze/ijwb/typescript/BlazeTypescriptSyncPlugin.java
@@ -19,9 +19,11 @@
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.bazel.BuildSystemProvider;
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.BlazeInvocationContext;
import com.google.idea.blaze.base.command.info.BlazeInfo;
import com.google.idea.blaze.base.ideinfo.TargetMap;
import com.google.idea.blaze.base.issueparser.IssueOutputLineProcessor;
@@ -54,6 +56,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
+import java.util.Optional;
import java.util.Set;
import javax.annotation.Nullable;
@@ -89,7 +92,7 @@
Set<Label> tsConfigTargets = getTsConfigTargets(projectViewSet);
if (tsConfigTargets.isEmpty()) {
- invalidProjectViewError(context);
+ invalidProjectViewError(context, Blaze.getBuildSystemProvider(project));
return;
}
@@ -104,7 +107,12 @@
Blaze.getBuildSystemProvider(project).getSyncBinaryPath(),
BlazeCommandName.RUN)
.addTargets(new ArrayList<>(tsConfigTargets))
- .addBlazeFlags(BlazeFlags.buildFlags(project, projectViewSet))
+ .addBlazeFlags(
+ BlazeFlags.blazeFlags(
+ project,
+ projectViewSet,
+ BlazeCommandName.RUN,
+ BlazeInvocationContext.Sync))
.build();
int retVal =
@@ -162,26 +170,30 @@
// Must have either both typescript and ts_config_rules or neither
if (typescriptActive ^ !getTsConfigTargets(projectViewSet).isEmpty()) {
- invalidProjectViewError(context);
+ invalidProjectViewError(context, Blaze.getBuildSystemProvider(project));
return false;
}
return true;
}
- private void invalidProjectViewError(BlazeContext context) {
- IssueOutput.error(
- "For Typescript support you must add both `additional_languages`: "
- + "typescript and the `ts_config_rules` attribute.")
- .submit(context);
+ private void invalidProjectViewError(
+ BlazeContext context, BuildSystemProvider buildSystemProvider) {
+ String errorNote =
+ "For Typescript support you must add both `additional_languages: typescript`"
+ + " and the `ts_config_rules` attribute.";
+ String documentationUrl =
+ buildSystemProvider.getLanguageSupportDocumentationUrl("dynamic-languages-typescript");
+ if (documentationUrl != null) {
+ errorNote += String.format("<p>See <a href=\"%1$s\">%1$s</a>.", documentationUrl);
+ }
+ IssueOutput.error(errorNote).submit(context);
}
private static Set<Label> getTsConfigTargets(ProjectViewSet projectViewSet) {
- Label oldSectionType = projectViewSet.getScalarValue(TsConfigRuleSection.KEY);
+ Optional<Label> oldSectionType = projectViewSet.getScalarValue(TsConfigRuleSection.KEY);
Set<Label> labels = new LinkedHashSet<>(projectViewSet.listItems(TsConfigRulesSection.KEY));
- if (oldSectionType != null) {
- labels.add(oldSectionType);
- }
+ oldSectionType.ifPresent(labels::add);
return labels;
}
diff --git a/ijwb/src/com/google/idea/blaze/ijwb/typescript/TypescriptPrefetchFileSource.java b/ijwb/src/com/google/idea/blaze/ijwb/typescript/TypescriptPrefetchFileSource.java
new file mode 100644
index 0000000..9a57919
--- /dev/null
+++ b/ijwb/src/com/google/idea/blaze/ijwb/typescript/TypescriptPrefetchFileSource.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.ijwb.typescript;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.model.primitives.LanguageClass;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
+import com.google.idea.blaze.base.prefetch.PrefetchFileSource;
+import com.google.idea.blaze.base.projectview.ProjectViewSet;
+import com.google.idea.blaze.base.sync.projectview.ImportRoots;
+import com.google.idea.common.experiments.BoolExperiment;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.io.FileUtil;
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+/** Declare that ts files should be prefetched. */
+public class TypescriptPrefetchFileSource implements PrefetchFileSource {
+
+ private static final BoolExperiment prefetchAllTsSources =
+ new BoolExperiment("prefetch.all.ts.sources", true);
+
+ @Override
+ public void addFilesToPrefetch(
+ Project project,
+ ProjectViewSet projectViewSet,
+ ImportRoots importRoots,
+ BlazeProjectData blazeProjectData,
+ Set<File> files) {
+ if (!blazeProjectData.workspaceLanguageSettings.isLanguageActive(LanguageClass.TYPESCRIPT)
+ || !prefetchAllTsSources.getValue()) {
+ return;
+ }
+ // Prefetch all non-project ts source files found during sync
+ Predicate<ArtifactLocation> shouldPrefetch =
+ location -> {
+ if (!location.isSource || location.isExternal) {
+ return false;
+ }
+ WorkspacePath path = WorkspacePath.createIfValid(location.relativePath);
+ if (path == null || importRoots.containsWorkspacePath(path)) {
+ return false;
+ }
+ String extension = FileUtil.getExtension(path.relativePath());
+ return prefetchFileExtensions().contains(extension);
+ };
+ List<File> sourceFiles =
+ blazeProjectData
+ .targetMap
+ .targets()
+ .stream()
+ .map(TypescriptPrefetchFileSource::getJsSources)
+ .flatMap(Collection::stream)
+ .filter(shouldPrefetch)
+ .map(blazeProjectData.artifactLocationDecoder::decode)
+ .collect(Collectors.toList());
+ files.addAll(sourceFiles);
+ }
+
+ @Override
+ public Set<String> prefetchFileExtensions() {
+ return ImmutableSet.of("ts", "tsx");
+ }
+
+ private static Collection<ArtifactLocation> getJsSources(TargetIdeInfo target) {
+ if (target.tsIdeInfo != null) {
+ return target.tsIdeInfo.sources;
+ }
+ if (target.kind.languageClass == LanguageClass.TYPESCRIPT) {
+ return target.sources;
+ }
+ return ImmutableList.of();
+ }
+}
diff --git a/ijwb/tests/integrationtests/com/google/idea/blaze/ijwb/prefetch/IntelliJBazelPrefetchFileSourceTest.java b/ijwb/tests/integrationtests/com/google/idea/blaze/ijwb/prefetch/IntelliJBazelPrefetchFileSourceTest.java
new file mode 100644
index 0000000..6fee570
--- /dev/null
+++ b/ijwb/tests/integrationtests/com/google/idea/blaze/ijwb/prefetch/IntelliJBazelPrefetchFileSourceTest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.prefetch;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.idea.blaze.base.BlazeIntegrationTestCase;
+import com.google.idea.blaze.base.prefetch.PrefetchFileSource;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Test for file extensions prefetched in the IntelliJ Bazel plugin. */
+@RunWith(JUnit4.class)
+public class IntelliJBazelPrefetchFileSourceTest extends BlazeIntegrationTestCase {
+
+ @Test
+ public void testPrefetchedExtensions() {
+ assertThat(PrefetchFileSource.getAllPrefetchFileExtensions())
+ .containsExactly("java", "proto", "js", "html", "css", "gss", "ts", "tsx");
+ }
+}
diff --git a/ijwb/tests/unittests/com/google/idea/blaze/ijwb/android/BlazeAndroidLiteSyncPluginTest.java b/ijwb/tests/unittests/com/google/idea/blaze/ijwb/android/BlazeAndroidLiteSyncPluginTest.java
index aa90aa5..7062bd1 100644
--- a/ijwb/tests/unittests/com/google/idea/blaze/ijwb/android/BlazeAndroidLiteSyncPluginTest.java
+++ b/ijwb/tests/unittests/com/google/idea/blaze/ijwb/android/BlazeAndroidLiteSyncPluginTest.java
@@ -68,6 +68,11 @@
public Set<LanguageClass> getSupportedLanguagesInWorkspace(WorkspaceType workspaceType) {
return ImmutableSet.of(LanguageClass.JAVA);
}
+
+ @Override
+ public WorkspaceType getDefaultWorkspaceType() {
+ return WorkspaceType.JAVA;
+ }
});
context = new BlazeContext();
diff --git a/ijwb/tests/unittests/com/google/idea/blaze/ijwb/dart/BlazeDartSyncPluginTest.java b/ijwb/tests/unittests/com/google/idea/blaze/ijwb/dart/BlazeDartSyncPluginTest.java
index d8a27e3..807f5a6 100644
--- a/ijwb/tests/unittests/com/google/idea/blaze/ijwb/dart/BlazeDartSyncPluginTest.java
+++ b/ijwb/tests/unittests/com/google/idea/blaze/ijwb/dart/BlazeDartSyncPluginTest.java
@@ -68,6 +68,11 @@
public Set<LanguageClass> getSupportedLanguagesInWorkspace(WorkspaceType workspaceType) {
return ImmutableSet.of(LanguageClass.JAVA);
}
+
+ @Override
+ public WorkspaceType getDefaultWorkspaceType() {
+ return WorkspaceType.JAVA;
+ }
});
context = new BlazeContext();
diff --git a/ijwb/tests/unittests/com/google/idea/blaze/ijwb/javascript/BlazeJavascriptSyncPluginTest.java b/ijwb/tests/unittests/com/google/idea/blaze/ijwb/javascript/BlazeJavascriptSyncPluginTest.java
index da42baf..de61e08 100644
--- a/ijwb/tests/unittests/com/google/idea/blaze/ijwb/javascript/BlazeJavascriptSyncPluginTest.java
+++ b/ijwb/tests/unittests/com/google/idea/blaze/ijwb/javascript/BlazeJavascriptSyncPluginTest.java
@@ -54,6 +54,13 @@
ExtensionPointImpl<BlazeSyncPlugin> ep =
registerExtensionPoint(BlazeSyncPlugin.EP_NAME, BlazeSyncPlugin.class);
ep.registerExtension(new BlazeJavascriptSyncPlugin());
+ ep.registerExtension(
+ new BlazeSyncPlugin.Adapter() {
+ @Override
+ public WorkspaceType getDefaultWorkspaceType() {
+ return WorkspaceType.JAVA;
+ }
+ });
context = new BlazeContext();
context.addOutputSink(IssueOutput.class, errorCollector);
diff --git a/ijwb/tests/unittests/com/google/idea/blaze/ijwb/typescript/BlazeTypescriptSyncPluginTest.java b/ijwb/tests/unittests/com/google/idea/blaze/ijwb/typescript/BlazeTypescriptSyncPluginTest.java
index 1975ae8..1db8e99 100644
--- a/ijwb/tests/unittests/com/google/idea/blaze/ijwb/typescript/BlazeTypescriptSyncPluginTest.java
+++ b/ijwb/tests/unittests/com/google/idea/blaze/ijwb/typescript/BlazeTypescriptSyncPluginTest.java
@@ -70,6 +70,11 @@
public Set<LanguageClass> getSupportedLanguagesInWorkspace(WorkspaceType workspaceType) {
return ImmutableSet.of(LanguageClass.JAVA);
}
+
+ @Override
+ public WorkspaceType getDefaultWorkspaceType() {
+ return WorkspaceType.JAVA;
+ }
});
context = new BlazeContext();
diff --git a/intellij_platform_sdk/BUILD b/intellij_platform_sdk/BUILD
index d20389e..fa127ea 100644
--- a/intellij_platform_sdk/BUILD
+++ b/intellij_platform_sdk/BUILD
@@ -17,25 +17,51 @@
)
config_setting(
+ name = "intellij-ue-latest",
+ values = {
+ "define": "ij_product=intellij-ue-latest",
+ },
+)
+
+config_setting(
name = "intellij-beta",
values = {
"define": "ij_product=intellij-beta",
},
)
-# IntelliJ CE 2017.1.1
config_setting(
- name = "intellij-2017.1.1",
+ name = "intellij-ue-beta",
values = {
- "define": "ij_product=intellij-2017.1.1",
+ "define": "ij_product=intellij-ue-beta",
},
)
-# IntelliJ CE 2016.3.1
config_setting(
- name = "intellij-2016.3.1",
+ name = "intellij-2017.2.2",
values = {
- "define": "ij_product=intellij-2016.3.1",
+ "define": "ij_product=intellij-2017.2.2",
+ },
+)
+
+config_setting(
+ name = "intellij-ue-2017.2.2",
+ values = {
+ "define": "ij_product=intellij-ue-2017.2.2",
+ },
+)
+
+config_setting(
+ name = "intellij-2017.1.5",
+ values = {
+ "define": "ij_product=intellij-2017.1.5",
+ },
+)
+
+config_setting(
+ name = "intellij-ue-2017.1.5",
+ values = {
+ "define": "ij_product=intellij-ue-2017.1.5",
},
)
@@ -53,15 +79,13 @@
},
)
-# Android Studio 2.3.0.8
config_setting(
- name = "android-studio-2.3.0.8",
+ name = "android-studio-3.0.0.9",
values = {
- "define": "ij_product=android-studio-2.3.0.8",
+ "define": "ij_product=android-studio-3.0.0.9",
},
)
-# Android Studio 2.3.1.0
config_setting(
name = "android-studio-2.3.1.0",
values = {
@@ -83,7 +107,6 @@
},
)
-# CLion 2017.1.1
config_setting(
name = "clion-2017.1.1",
values = {
@@ -91,19 +114,10 @@
},
)
-# CLion 2016.3.2
config_setting(
- name = "clion-2016.3.2",
+ name = "clion-2017.2.1",
values = {
- "define": "ij_product=clion-2016.3.2",
- },
-)
-
-# CLion 2016.2.2
-config_setting(
- name = "clion-162.1967.7",
- values = {
- "define": "ij_product=clion-162.1967.7",
+ "define": "ij_product=clion-2017.2.1",
},
)
@@ -156,7 +170,35 @@
java_library(
name = "plugin_api_for_grammar_kit",
visibility = ["//third_party/java/jetbrains/grammar_kit:__pkg__"],
- exports = ["//intellij_platform_sdk/intellij_ce_2016_3_1:sdk"],
+ exports = ["//intellij_platform_sdk/intellij_ce_2017_2_2:sdk"],
+)
+
+# The version of guava bundled with the IntelliJ plugin API.
+java_library(
+ name = "guava",
+ exports = select_from_plugin_api_directory(
+ android_studio = [":guava"],
+ clion = [":guava"],
+ intellij = [":guava"],
+ ),
+)
+
+# The version of truth bundled with the IntelliJ plugin API.
+java_library(
+ name = "truth",
+ testonly = 1,
+ exports = ["@truth//jar"],
+)
+
+# IntelliJ Coverage plugin
+java_library(
+ name = "coverage",
+ neverlink = 1,
+ exports = select_from_plugin_api_directory(
+ android_studio = [],
+ clion = [],
+ intellij = [":coverage"],
+ ),
)
# Used to support IntelliJ plugin development in our plugin
@@ -231,9 +273,6 @@
java_library(
name = "missing_test_classes",
srcs = select_for_plugin_api({
- "android-studio-2.3.0.8": [
- "missing/tests/com/jetbrains/cidr/modulemap/resolve/MockModuleMapManagerImpl.java",
- ],
"android-studio-2.3.1.0": [
"missing/tests/com/jetbrains/cidr/modulemap/resolve/MockModuleMapManagerImpl.java",
],
@@ -259,5 +298,6 @@
clion = ["clion_application_info_name.txt"],
default = ["intellij_application_info_name.txt"],
intellij = ["intellij_application_info_name.txt"],
+ intellij_ue = ["intellij_ue_application_info_name.txt"],
),
)
diff --git a/intellij_platform_sdk/BUILD.android_studio b/intellij_platform_sdk/BUILD.android_studio
index 1a8a88c..8ba77c9 100644
--- a/intellij_platform_sdk/BUILD.android_studio
+++ b/intellij_platform_sdk/BUILD.android_studio
@@ -6,9 +6,8 @@
java_import(
name = "sdk",
- jars = glob([
- "android-studio/lib/*.jar",
- ]),
+ jars = glob(["android-studio/lib/*.jar"]),
+ deps = ["@error_prone_annotations//jar"],
tags = ["intellij-provided-by-sdk"],
)
@@ -33,6 +32,11 @@
jars = glob(["android-studio/plugins/junit/lib/*.jar"]),
)
+java_import(
+ name = "guava",
+ jars = glob(["android-studio/lib/guava-*.jar"]),
+)
+
# The plugins required by ASwB. We need to include them
# when running integration tests.
java_import(
@@ -45,6 +49,7 @@
"android-studio/plugins/junit/lib/*.jar",
"android-studio/plugins/ndk-workspace/lib/*.jar",
"android-studio/plugins/properties/lib/*.jar",
+ "android-studio/plugins/smali/lib/*.jar",
],
exclude = [
# Conflict with lib/guava-*.jar
diff --git a/intellij_platform_sdk/BUILD.clion b/intellij_platform_sdk/BUILD.clion
index d43b8a1..fe2487f 100644
--- a/intellij_platform_sdk/BUILD.clion
+++ b/intellij_platform_sdk/BUILD.clion
@@ -7,6 +7,7 @@
java_import(
name = "sdk",
jars = glob(["clion-*/lib/*.jar"]),
+ deps = ["@error_prone_annotations//jar"],
tags = ["intellij-provided-by-sdk"],
)
@@ -16,6 +17,11 @@
)
java_import(
+ name = "guava",
+ jars = glob(["clion-*/lib/guava-*.jar"]),
+)
+
+java_import(
name = "terminal",
jars = glob(["clion-*/plugins/terminal/lib/terminal.jar"]),
)
@@ -36,4 +42,4 @@
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
index 4133e09..8755e7f 100644
--- a/intellij_platform_sdk/BUILD.idea
+++ b/intellij_platform_sdk/BUILD.idea
@@ -7,6 +7,7 @@
java_import(
name = "sdk",
jars = glob(["lib/*.jar"]),
+ deps = ["@error_prone_annotations//jar"],
tags = ["intellij-provided-by-sdk"],
)
@@ -16,6 +17,11 @@
)
java_import(
+ name = "guava",
+ jars = glob(["lib/guava-*.jar"]),
+)
+
+java_import(
name = "hg4idea",
jars = ["plugins/hg4idea/lib/hg4idea.jar"],
)
diff --git a/intellij_platform_sdk/build_defs.bzl b/intellij_platform_sdk/build_defs.bzl
index 5c1bbda..1d1c314 100644
--- a/intellij_platform_sdk/build_defs.bzl
+++ b/intellij_platform_sdk/build_defs.bzl
@@ -1,39 +1,55 @@
"""Convenience methods for plugin_api."""
+# BUILD_VARS for each IDE corresponding to indirect ij_products, eg. "intellij-latest"
+
+
+
+
+
+
+
+
+
# The current indirect ij_product mapping (eg. "intellij-latest")
INDIRECT_IJ_PRODUCTS = {
- "intellij-latest": "intellij-2017.1.1",
- "intellij-beta": "intellij-2017.1.1",
+ "intellij-latest": "intellij-2017.1.5",
+ "intellij-beta": "intellij-2017.2.2",
+ "intellij-ue-latest": "intellij-ue-2017.1.5",
+ "intellij-ue-beta": "intellij-ue-2017.2.2",
"android-studio-latest": "android-studio-2.3.1.0",
- "android-studio-beta": "android-studio-2.3.1.0",
+ "android-studio-beta": "android-studio-3.0.0.9",
"clion-latest": "clion-2017.1.1",
- "clion-beta": "clion-2017.1.1",
+ "clion-beta": "clion-2017.2.1",
}
DIRECT_IJ_PRODUCTS = {
- "intellij-2017.1.1": struct(
+ "intellij-2017.2.2": struct(
ide="intellij",
- directory="intellij_ce_2017_1_1",
+ directory="intellij_ce_2017_2_2",
),
- "intellij-2016.3.1": struct(
+ "intellij-ue-2017.2.2": struct(
+ ide="intellij-ue",
+ directory="intellij_ue_2017_2_2",
+ ),
+ "intellij-2017.1.5": struct(
ide="intellij",
- directory="intellij_ce_2016_3_1",
+ directory="intellij_ce_2017_1_5",
),
- "android-studio-2.3.0.8": struct(
+ "intellij-ue-2017.1.5": struct(
+ ide="intellij-ue",
+ directory="intellij_ue_2017_1_5",
+ ),
+ "android-studio-3.0.0.9": struct(
ide="android-studio",
- directory="android_studio_2_3_0_8",
+ directory="android_studio_3_0_0_9",
),
"android-studio-2.3.1.0": struct(
ide="android-studio",
directory="android_studio_2_3_1_0",
),
- "clion-162.1967.7": struct(
+ "clion-2017.2.1": struct(
ide="clion",
- directory="CL_162_1967_7",
- ),
- "clion-2016.3.2": struct(
- ide="clion",
- directory="clion_2016_3_2",
+ directory="clion_2017_2_1",
),
"clion-2017.1.1": struct(
ide="clion",
@@ -94,11 +110,12 @@
return select(select_params)
-def select_for_ide(intellij=None, android_studio=None, clion=None, default=[]):
+def select_for_ide(intellij=None, intellij_ue=None, android_studio=None, clion=None, default=[]):
"""Selects for the supported IDEs.
Args:
intellij: Files to use for IntelliJ. If None, will use default.
+ intellij_ue: Files to use for IntelliJ UE. If None, will use value chosen for 'intellij'.
android_studio: Files to use for Android Studio. If None will use default.
clion: Files to use for CLion. If None will use default.
default: Files to use for any IDEs not passed.
@@ -115,11 +132,13 @@
)
"""
intellij = intellij if intellij != None else default
+ intellij_ue = intellij_ue if intellij_ue != None else intellij
android_studio = android_studio if android_studio != None else default
clion = clion if clion != None else default
ide_to_value = {
"intellij" : intellij,
+ "intellij-ue" : intellij_ue,
"android-studio": android_studio,
"clion": clion,
}
@@ -135,11 +154,12 @@
def _plugin_api_directory(value):
return "@" + value.directory + "//"
-def select_from_plugin_api_directory(intellij, android_studio, clion):
+def select_from_plugin_api_directory(intellij, android_studio, clion, intellij_ue=None):
"""Internal convenience method to generate select statement from the IDE's plugin_api directories."""
ide_to_value = {
"intellij" : intellij,
+ "intellij-ue" : intellij_ue if intellij_ue else intellij,
"android-studio": android_studio,
"clion": clion,
}
diff --git a/intellij_platform_sdk/intellij_ue_application_info_name.txt b/intellij_platform_sdk/intellij_ue_application_info_name.txt
new file mode 100644
index 0000000..c60c001
--- /dev/null
+++ b/intellij_platform_sdk/intellij_ue_application_info_name.txt
@@ -0,0 +1 @@
+idea/ApplicationInfo.xml
diff --git a/java/BUILD b/java/BUILD
index fb36558..1bd67b8 100644
--- a/java/BUILD
+++ b/java/BUILD
@@ -2,9 +2,10 @@
load(
"//build_defs:build_defs.bzl",
- "merged_plugin_xml",
- "stamped_plugin_xml",
"intellij_plugin",
+ "merged_plugin_xml",
+ "optional_plugin_xml",
+ "stamped_plugin_xml",
)
load(
"//testing:test_defs.bzl",
@@ -15,13 +16,12 @@
java_library(
name = "java",
srcs = glob(["src/**/*.java"]),
+ javacopts = ["-Xep:FutureReturnValueIgnored:OFF"],
visibility = ["//visibility:public"],
- runtime_deps = [
- "//common/actionhelper",
- "//common/experiments",
- ],
+ runtime_deps = ["//common/actionhelper"],
deps = [
"//base",
+ "//common/experiments",
"//intellij_platform_sdk:junit",
"//intellij_platform_sdk:plugin_api",
"//proto:proto_deps",
@@ -52,9 +52,17 @@
plugin_xml = "merged_plugin_xml",
)
+optional_plugin_xml(
+ name = "optional_xml",
+ module = "JUnit",
+ plugin_xml = "src/META-INF/java-contents.xml",
+ visibility = ["//visibility:public"],
+)
+
intellij_plugin(
name = "java_integration_test_plugin",
testonly = 1,
+ optional_plugin_xmls = [":optional_xml"],
plugin_xml = ":java_plugin_xml",
deps = [
":java",
diff --git a/java/src/META-INF/blaze-java.xml b/java/src/META-INF/blaze-java.xml
index e22c895..08f7fd4 100644
--- a/java/src/META-INF/blaze-java.xml
+++ b/java/src/META-INF/blaze-java.xml
@@ -1,11 +1,11 @@
<!--
- ~ Copyright 2016 The Bazel Authors. All rights reserved.
+ ~ Copyright 2017 The Bazel Authors. All rights reserved.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
- ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,119 +14,9 @@
~ limitations under the License.
-->
<idea-plugin>
- <depends>JUnit</depends>
- <depends>com.intellij.modules.java</depends>
-
- <actions>
- <action class="com.google.idea.blaze.java.libraries.ExcludeLibraryAction"
- id="Blaze.ExcludeLibraryAction"
- text="Exclude Library and Resync">
- </action>
- <action class="com.google.idea.blaze.java.libraries.AttachSourceJarAction"
- id="Blaze.AttachSourceJarAction"
- text="Attach Source Jar">
- </action>
- <action class="com.google.idea.blaze.java.libraries.AddLibraryTargetDirectoryToProjectViewAction"
- id="Blaze.AddLibraryTargetDirectoryToProjectView"
- text="Add Library Target Directory to Project View">
- </action>
- <action class="com.google.idea.blaze.java.libraries.DetachAllSourceJarsAction"
- id="Blaze.DetachAllSourceJars"
- text="Detach All Blaze Source Jars">
- </action>
-
- <group id="Blaze.Java.ProjectViewPopupMenu">
- <add-to-group group-id="Blaze.PerFileContextMenu"/>
- <reference id="Blaze.ExcludeLibraryAction"/>
- <reference id="Blaze.AttachSourceJarAction"/>
- <reference id="Blaze.AddLibraryTargetDirectoryToProjectView"/>
- </group>
-
- <group id="Blaze.JavaMenuGroup.Outer">
- <add-to-group group-id="Blaze.MainMenuActionGroup" relative-to-action="Blaze.MenuFooter" anchor="after"/>
- <group id="Blaze.JavaMenuGroup" text="Java">
- <reference id="Blaze.DetachAllSourceJars"/>
- </group>
- </group>
-
- <!-- 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"/>
- <RunConfigurationFactory implementation="com.google.idea.blaze.java.run.BlazeJavaRunConfigurationFactory"/>
- <RunConfigurationFactory implementation="com.google.idea.blaze.java.run.BlazeJavaTestRunConfigurationFactory"/>
- <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"/>
- <BlazeTestEventsHandler implementation="com.google.idea.blaze.java.run.BlazeJavaTestEventsHandler"/>
- <AttributeSpecificStringLiteralReferenceProvider implementation="com.google.idea.blaze.java.lang.build.references.JavaClassQualifiedNameReference"/>
- <JavaLikeLanguage implementation="com.google.idea.blaze.java.sync.source.JavaLikeLanguage$Java"/>
- <TestTargetHeuristic implementation="com.google.idea.blaze.java.run.JUnitTestHeuristic" order="before TestSizeHeuristic"/>
- </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"/>
- <runConfigurationProducer
- implementation="com.google.idea.blaze.java.run.producers.BlazeJavaAbstractTestCaseConfigurationProducer"
- order="first"/>
- <runConfigurationProducer
- implementation="com.google.idea.blaze.java.run.producers.MultipleJavaClassesTestConfigurationProducer"
- 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"/>
- <!--duplicated here in case the Kotlin plugin is present, as it also tries to replace javaProcessor-->
- <refactoring.safeDeleteProcessor id="build_file_safe_delete_copy" order="before kotlinProcessor"
- 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.AddLibraryTargetDirectoryToProjectViewAttachSourcesProvider"/>
- <attachSourcesProvider implementation="com.google.idea.blaze.java.libraries.BlazeAttachSourceProvider"/>
- <applicationService serviceImplementation="com.google.idea.blaze.java.settings.BlazeJavaUserSettings"/>
- <psi.referenceContributor language="BUILD" implementation="com.google.idea.blaze.java.lang.build.references.JavaClassReferenceContributor"/>
- </extensions>
-
- <project-components>
+ <application-components>
<component>
- <implementation-class>com.google.idea.blaze.java.run.producers.NonBlazeProducerSuppressor</implementation-class>
+ <implementation-class>com.google.idea.blaze.java.plugin.JUnitPluginDependencyWarning</implementation-class>
</component>
- </project-components>
-
- <extensionPoints>
- <extensionPoint qualifiedName="com.google.idea.blaze.JavaSyncAugmenter"
- interface="com.google.idea.blaze.java.sync.BlazeJavaSyncAugmenter"/>
- <extensionPoint qualifiedName="com.google.idea.blaze.JavaLikeLanguage"
- interface="com.google.idea.blaze.java.sync.source.JavaLikeLanguage"/>
- <extensionPoint qualifiedName="com.google.idea.blaze.JUnitParameterizedClassHeuristic"
- interface="com.google.idea.blaze.java.run.producers.JUnitParameterizedClassHeuristic"/>
- </extensionPoints>
-</idea-plugin>
+ </application-components>
+</idea-plugin>
\ No newline at end of file
diff --git a/java/src/META-INF/java-contents.xml b/java/src/META-INF/java-contents.xml
new file mode 100644
index 0000000..a48e8f1
--- /dev/null
+++ b/java/src/META-INF/java-contents.xml
@@ -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.
+ -->
+<idea-plugin>
+ <depends>JUnit</depends>
+ <depends>com.intellij.modules.java</depends>
+
+ <actions>
+ <action class="com.google.idea.blaze.java.libraries.ExcludeLibraryAction"
+ id="Blaze.ExcludeLibraryAction"
+ text="Exclude Library and Resync">
+ </action>
+ <action class="com.google.idea.blaze.java.libraries.AttachSourceJarAction"
+ id="Blaze.AttachSourceJarAction"
+ text="Attach Source Jar">
+ </action>
+ <action class="com.google.idea.blaze.java.libraries.AddLibraryTargetDirectoryToProjectViewAction"
+ id="Blaze.AddLibraryTargetDirectoryToProjectView"
+ text="Add Library Target Directory to Project View">
+ </action>
+ <action class="com.google.idea.blaze.java.libraries.DetachAllSourceJarsAction"
+ id="Blaze.DetachAllSourceJars"
+ text="Detach All Blaze Source Jars">
+ </action>
+
+ <group id="Blaze.Java.ProjectViewPopupMenu">
+ <add-to-group group-id="Blaze.PerFileContextMenu"/>
+ <reference id="Blaze.ExcludeLibraryAction"/>
+ <reference id="Blaze.AttachSourceJarAction"/>
+ <reference id="Blaze.AddLibraryTargetDirectoryToProjectView"/>
+ </group>
+
+ <group id="Blaze.JavaMenuGroup.Outer">
+ <add-to-group group-id="Blaze.MainMenuActionGroup" relative-to-action="Blaze.MenuFooter" anchor="after"/>
+ <group id="Blaze.JavaMenuGroup" text="Java">
+ <reference id="Blaze.DetachAllSourceJars"/>
+ </group>
+ </group>
+
+ <!-- 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"/>
+ <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"/>
+ <BlazeTestEventsHandler implementation="com.google.idea.blaze.java.run.BlazeJavaTestEventsHandler"/>
+ <AttributeSpecificStringLiteralReferenceProvider implementation="com.google.idea.blaze.java.lang.build.references.JavaClassQualifiedNameReference"/>
+ <JavaLikeLanguage implementation="com.google.idea.blaze.java.sync.source.JavaLikeLanguage$Java"/>
+ <TestTargetHeuristic implementation="com.google.idea.blaze.java.run.JUnitTestHeuristic" order="before TestSizeHeuristic"/>
+ <TestTargetHeuristic implementation="com.google.idea.blaze.java.run.QualifiedClassNameHeuristic" order="before TargetNameHeuristic"/>
+ <SyncListener implementation="com.google.idea.blaze.java.libraries.BlazeSourceJarNavigationPolicy$SyncTrackerUpdater"/>
+ </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"/>
+ <runConfigurationProducer
+ implementation="com.google.idea.blaze.java.run.producers.BlazeJavaAbstractTestCaseConfigurationProducer"
+ order="first"/>
+ <runConfigurationProducer
+ implementation="com.google.idea.blaze.java.run.producers.MultipleJavaClassesTestConfigurationProducer"
+ 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"/>
+ <!--duplicated here in case the Kotlin plugin is present, as it also tries to replace javaProcessor-->
+ <refactoring.safeDeleteProcessor id="build_file_safe_delete_copy" order="before kotlinProcessor"
+ 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.AddLibraryTargetDirectoryToProjectViewAttachSourcesProvider"/>
+ <attachSourcesProvider implementation="com.google.idea.blaze.java.libraries.BlazeAttachSourceProvider"/>
+ <applicationService serviceImplementation="com.google.idea.blaze.java.settings.BlazeJavaUserSettings"/>
+ <psi.referenceContributor language="BUILD" implementation="com.google.idea.blaze.java.lang.build.references.JavaClassReferenceContributor"/>
+ <useScopeEnlarger implementation="com.google.idea.blaze.java.psi.AutoFactoryUseScopeEnlarger"/>
+ <implicitUsageProvider implementation="com.google.idea.blaze.java.psi.AutoFactoryImplicitUsageProvider"/>
+ <psi.clsCustomNavigationPolicy implementation="com.google.idea.blaze.java.libraries.BlazeSourceJarNavigationPolicy"/>
+ </extensions>
+
+ <project-components>
+ <component>
+ <implementation-class>com.google.idea.blaze.java.run.producers.NonBlazeProducerSuppressor</implementation-class>
+ </component>
+ </project-components>
+
+ <extensionPoints>
+ <extensionPoint qualifiedName="com.google.idea.blaze.JavaSyncAugmenter"
+ interface="com.google.idea.blaze.java.sync.BlazeJavaSyncAugmenter"/>
+ <extensionPoint qualifiedName="com.google.idea.blaze.JavaLikeLanguage"
+ interface="com.google.idea.blaze.java.sync.source.JavaLikeLanguage"/>
+ <extensionPoint qualifiedName="com.google.idea.blaze.JUnitParameterizedClassHeuristic"
+ interface="com.google.idea.blaze.java.run.producers.JUnitParameterizedClassHeuristic"/>
+ </extensionPoints>
+</idea-plugin>
diff --git a/java/src/com/google/idea/blaze/java/libraries/AddLibraryTargetDirectoryToProjectViewAction.java b/java/src/com/google/idea/blaze/java/libraries/AddLibraryTargetDirectoryToProjectViewAction.java
index c5e45c6..a35c789 100644
--- a/java/src/com/google/idea/blaze/java/libraries/AddLibraryTargetDirectoryToProjectViewAction.java
+++ b/java/src/com/google/idea/blaze/java/libraries/AddLibraryTargetDirectoryToProjectViewAction.java
@@ -97,7 +97,7 @@
}
// To start with, we whitelist only library rules
// It makes no sense to add directories for java_imports and the like
- if (!target.kind.isOneOf(Kind.JAVA_LIBRARY, Kind.ANDROID_LIBRARY)) {
+ if (!target.kind.isOneOf(Kind.JAVA_LIBRARY, Kind.ANDROID_LIBRARY, Kind.PROTO_LIBRARY)) {
return null;
}
if (target.buildFile == null) {
diff --git a/java/src/com/google/idea/blaze/java/libraries/BlazeSourceJarNavigationPolicy.java b/java/src/com/google/idea/blaze/java/libraries/BlazeSourceJarNavigationPolicy.java
new file mode 100644
index 0000000..8af9539
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/libraries/BlazeSourceJarNavigationPolicy.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.scope.BlazeContext;
+import com.google.idea.blaze.base.sync.BlazeSyncParams.SyncMode;
+import com.google.idea.blaze.base.sync.SyncListener;
+import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
+import com.google.idea.blaze.java.sync.model.BlazeJarLibrary;
+import com.google.idea.common.experiments.BoolExperiment;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.ProjectManager;
+import com.intellij.openapi.project.ProjectManagerAdapter;
+import com.intellij.openapi.roots.LibraryOrderEntry;
+import com.intellij.openapi.roots.OrderEntry;
+import com.intellij.openapi.roots.OrderRootType;
+import com.intellij.openapi.roots.libraries.Library;
+import com.intellij.openapi.roots.libraries.LibraryUtil;
+import com.intellij.openapi.util.SimpleModificationTracker;
+import com.intellij.openapi.vfs.JarFileSystem;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiClassOwner;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiJavaFile;
+import com.intellij.psi.impl.compiled.ClsClassImpl;
+import com.intellij.psi.impl.compiled.ClsCustomNavigationPolicyEx;
+import com.intellij.psi.impl.compiled.ClsFileImpl;
+import com.intellij.psi.util.CachedValueProvider.Result;
+import com.intellij.psi.util.CachedValuesManager;
+import java.io.File;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import javax.annotation.Nullable;
+
+/**
+ * A navigation policy that allows navigating to source (or reading javadocs) even when that source
+ * isn't officially attached to the library in the project.
+ *
+ * <p>Attaching sources has been shown to slow indexing time because IntelliJ indexes all the source
+ * files attached to project jars. This isn't a huge problem for non-Blaze projects, since the jars
+ * change infrequently. With blaze, however, the jars are reshuffled after every blaze build and so
+ * the indexing time increases dramatically if you attach too many of them.
+ *
+ * <p>This class attempts to work around that problem by providing a way to navigate to the source
+ * without having that source actually indexed.
+ */
+final class BlazeSourceJarNavigationPolicy extends ClsCustomNavigationPolicyEx {
+
+ private static final BoolExperiment enabled =
+ new BoolExperiment("blaze.source.jar.navigation.policy", true);
+ static final BoolExperiment cacheEnabled =
+ new BoolExperiment("blaze.source.jar.navigation.policy.cache", false);
+
+ private final ConcurrentMap<Project, SimpleModificationTracker> projectSyncTrackers =
+ new ConcurrentHashMap<>();
+
+ public BlazeSourceJarNavigationPolicy() {
+ ApplicationManager.getApplication()
+ .getMessageBus()
+ .connect()
+ .subscribe(ProjectManager.TOPIC, new RemoveSyncTrackerOnProjectClosing());
+ }
+
+ @Nullable
+ @Override
+ public PsiFile getFileNavigationElement(ClsFileImpl file) {
+ if (!enabled.getValue()) {
+ return null;
+ }
+
+ return CachedValuesManager.getCachedValue(
+ file,
+ () -> {
+ Result<PsiFile> result = getPsiFile(file);
+ if (result == null) {
+ result = notFound(file);
+ }
+ return result;
+ });
+ }
+
+ @Nullable
+ private Result<PsiFile> getPsiFile(ClsFileImpl file) {
+ Project project = file.getProject();
+ BlazeProjectData blazeProjectData =
+ BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
+ if (blazeProjectData == null) {
+ return null;
+ }
+
+ VirtualFile root = getSourceJarRoot(project, blazeProjectData, file);
+ if (root == null) {
+ return null;
+ }
+
+ return getSourceFileResult(file, root);
+ }
+
+ @Nullable
+ private VirtualFile getSourceJarRoot(
+ Project project, BlazeProjectData blazeProjectData, PsiJavaFile clsFile) {
+
+ Library library = findLibrary(project, clsFile);
+ if (library == null || library.getFiles(OrderRootType.SOURCES).length != 0) {
+ // If the library already has sources attached, no need to hunt for them.
+ return null;
+ }
+
+ BlazeJarLibrary blazeLibrary =
+ LibraryActionHelper.findLibraryFromIntellijLibrary(project, blazeProjectData, library);
+ if (blazeLibrary == null) {
+ return null;
+ }
+
+ File sourceJar =
+ JarCache.getInstance(project)
+ .getCachedSourceJar(blazeProjectData.artifactLocationDecoder, blazeLibrary);
+
+ if (sourceJar == null && blazeLibrary.libraryArtifact.sourceJar != null) {
+ sourceJar =
+ blazeProjectData.artifactLocationDecoder.decode(blazeLibrary.libraryArtifact.sourceJar);
+ }
+
+ if (sourceJar == null) {
+ return null;
+ }
+
+ VirtualFile vfsFile = VfsUtil.findFileByIoFile(sourceJar, true);
+ if (vfsFile == null) {
+ return null;
+ }
+ return JarFileSystem.getInstance().getJarRootForLocalFile(vfsFile);
+ }
+
+ @Nullable
+ private Library findLibrary(Project project, PsiJavaFile clsFile) {
+ OrderEntry libraryEntry = LibraryUtil.findLibraryEntry(clsFile.getVirtualFile(), project);
+ if (!(libraryEntry instanceof LibraryOrderEntry)) {
+ return null;
+ }
+ return ((LibraryOrderEntry) libraryEntry).getLibrary();
+ }
+
+ @Nullable
+ private Result<PsiFile> getSourceFileResult(ClsFileImpl clsFile, VirtualFile root) {
+ // This code is adapted from JavaPsiImplementationHelperImpl#getClsFileNavigationElement
+ PsiClass[] classes = clsFile.getClasses();
+ if (classes.length == 0) {
+ return null;
+ }
+
+ String sourceFileName = ((ClsClassImpl) classes[0]).getSourceFileName();
+ String packageName = clsFile.getPackageName();
+ String relativePath =
+ packageName.isEmpty()
+ ? sourceFileName
+ : packageName.replace('.', '/') + '/' + sourceFileName;
+
+ VirtualFile source = root.findFileByRelativePath(relativePath);
+ if (source != null && source.isValid()) {
+ // Since we have an actual source jar tracked down, use that source jar as the modification
+ // tracker. This means the result will continue to be cached unless that source jar changes.
+ // If we didn't find a source jar, we use a modification tracker that invalidates on every
+ // Blaze sync, which is less efficient.
+ PsiFile psiSource = clsFile.getManager().findFile(source);
+ if (psiSource instanceof PsiClassOwner) {
+ return Result.create(psiSource, source);
+ }
+ return Result.create(null, source);
+ }
+
+ return null;
+ }
+
+ private Result<PsiFile> notFound(ClsFileImpl file) {
+ // A "not-found" result is null, but depends on the project sync tracker, so it will expire
+ // after the next blaze sync. This means we'll run this check again after every sync for files
+ // that don't have source jars, but it's not a huge deal because checking for the source jar
+ // only takes a few microseconds.
+ projectSyncTrackers.putIfAbsent(file.getProject(), new SimpleModificationTracker());
+ return Result.create(null, projectSyncTrackers.get(file.getProject()));
+ }
+
+ // In #api_163 and beyond, this can simply implement ProjectManagerListener
+ private class RemoveSyncTrackerOnProjectClosing extends ProjectManagerAdapter {
+
+ @Override
+ public void projectClosing(Project project) {
+ projectSyncTrackers.remove(project);
+ }
+ }
+
+ class SyncTrackerUpdater extends SyncListener.Adapter {
+
+ @Override
+ public void afterSync(
+ Project project, BlazeContext context, SyncMode syncMode, SyncResult syncResult) {
+ SimpleModificationTracker modificationTracker = projectSyncTrackers.get(project);
+ if (modificationTracker != null) {
+ modificationTracker.incModificationCount();
+ }
+ }
+ }
+}
diff --git a/java/src/com/google/idea/blaze/java/libraries/JarCache.java b/java/src/com/google/idea/blaze/java/libraries/JarCache.java
index 66eafaa..a6fe08c 100644
--- a/java/src/com/google/idea/blaze/java/libraries/JarCache.java
+++ b/java/src/com/google/idea/blaze/java/libraries/JarCache.java
@@ -112,9 +112,11 @@
artifactLocationDecoder.decode(library.libraryArtifact.jarForIntellijLibrary());
sourceFileToCacheKey.put(jarFile, cacheKeyForJar(jarFile));
- boolean attachSourceJar =
- attachAllSourceJars || sourceJarManager.hasSourceJarAttached(library.key);
- if (attachSourceJar && library.libraryArtifact.sourceJar != null) {
+ boolean copySourceJar =
+ attachAllSourceJars
+ || BlazeSourceJarNavigationPolicy.cacheEnabled.getValue()
+ || sourceJarManager.hasSourceJarAttached(library.key);
+ if (copySourceJar && library.libraryArtifact.sourceJar != null) {
File srcJarFile = artifactLocationDecoder.decode(library.libraryArtifact.sourceJar);
sourceFileToCacheKey.put(srcJarFile, cacheKeyForSourceJar(srcJarFile));
}
diff --git a/java/src/com/google/idea/blaze/java/plugin/JUnitPluginDependencyWarning.java b/java/src/com/google/idea/blaze/java/plugin/JUnitPluginDependencyWarning.java
new file mode 100644
index 0000000..57e3b1a
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/plugin/JUnitPluginDependencyWarning.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.plugin;
+
+import com.google.idea.blaze.base.plugin.PluginUtils;
+import com.google.idea.blaze.base.settings.Blaze;
+import com.google.idea.sdkcompat.transactions.Transactions;
+import com.intellij.ide.AppLifecycleListener;
+import com.intellij.notification.Notification;
+import com.intellij.notification.NotificationListener;
+import com.intellij.notification.NotificationType;
+import com.intellij.notification.Notifications;
+import com.intellij.openapi.application.Application;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.components.ApplicationComponent;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.ProjectManager;
+import com.intellij.openapi.project.ProjectManagerAdapter;
+import com.intellij.openapi.ui.MessageType;
+import com.intellij.openapi.ui.popup.Balloon;
+import com.intellij.openapi.ui.popup.JBPopupFactory;
+import com.intellij.openapi.util.Ref;
+import com.intellij.openapi.wm.WindowManager;
+import com.intellij.ui.HyperlinkAdapter;
+import com.intellij.ui.awt.RelativePoint;
+import com.intellij.util.messages.MessageBusConnection;
+import java.awt.Point;
+import java.awt.Rectangle;
+import javax.annotation.Nullable;
+import javax.swing.JComponent;
+import javax.swing.event.HyperlinkEvent;
+
+/**
+ * Runs on startup, displaying an error if the JUnit plugin (required for the blaze plugin to
+ * properly function) is not enabled.
+ */
+public class JUnitPluginDependencyWarning extends ApplicationComponent.Adapter {
+
+ private static final String JUNIT_PLUGIN_ID = "JUnit";
+
+ @Override
+ public void initComponent() {
+ if (!PluginUtils.isPluginEnabled(JUNIT_PLUGIN_ID)) {
+ notifyJUnitNotEnabled();
+ }
+ }
+
+ /**
+ * Pop up a notification asking user to enable the JUnit plugin, and also add an error item to the
+ * event log.
+ */
+ private static void notifyJUnitNotEnabled() {
+ String buildSystem = Blaze.defaultBuildSystemName();
+
+ String message =
+ String.format(
+ "<html>The JUnit plugin is disabled, but it's required for the %s plugin to function."
+ + "<br>Please <a href=\"fix\">enable the JUnit plugin</a> and restart the IDE",
+ buildSystem);
+
+ NotificationListener listener =
+ new NotificationListener.Adapter() {
+ @Override
+ protected void hyperlinkActivated(Notification notification, HyperlinkEvent e) {
+ if ("fix".equals(e.getDescription())) {
+ PluginUtils.installOrEnablePlugin(JUNIT_PLUGIN_ID);
+ }
+ }
+ };
+
+ Notification notification =
+ new Notification(
+ buildSystem + " Plugin Error",
+ buildSystem + " plugin dependencies are missing",
+ message,
+ NotificationType.ERROR,
+ listener);
+ notification.setImportant(true);
+
+ Application app = ApplicationManager.getApplication();
+ MessageBusConnection connection = app.getMessageBus().connect(app);
+ connection.subscribe(
+ AppLifecycleListener.TOPIC,
+ new AppLifecycleListener.Adapter() {
+ @Override
+ public void appStarting(@Nullable Project projectFromCommandLine) {
+ // Adds an error item to the 'Event Log' tab.
+ // Easy to ignore, but remains in event log until manually cleared.
+ Transactions.submitTransactionAndWait(() -> Notifications.Bus.notify(notification));
+ }
+
+ @Override
+ public void appFrameCreated(String[] commandLineArgs, Ref<Boolean> willOpenProject) {
+ // Popup dialog in welcome screen.
+ app.invokeLater(() -> showPopupNotification(message));
+ }
+ });
+ if (!ApplicationManager.getApplication().isHeadlessEnvironment()) {
+ connection.subscribe(
+ ProjectManager.TOPIC,
+ new ProjectManagerAdapter() {
+ @Override
+ public void projectOpened(Project project) {
+ // Popup dialog on project open, for users bypassing the welcome screen.
+ if (Blaze.isBlazeProject(project)) {
+ app.invokeLater(() -> showPopupNotification(message));
+ }
+ }
+ });
+ }
+ }
+
+ private static void showPopupNotification(String message) {
+ JComponent component = WindowManager.getInstance().findVisibleFrame().getRootPane();
+ if (component == null) {
+ return;
+ }
+ Rectangle rect = component.getVisibleRect();
+ JBPopupFactory.getInstance()
+ .createHtmlTextBalloonBuilder(
+ message,
+ MessageType.WARNING,
+ new HyperlinkAdapter() {
+ @Override
+ protected void hyperlinkActivated(HyperlinkEvent e) {
+ PluginUtils.installOrEnablePlugin(JUNIT_PLUGIN_ID);
+ }
+ })
+ .setFadeoutTime(-1)
+ .setHideOnLinkClick(true)
+ .setHideOnFrameResize(false)
+ .setHideOnClickOutside(false)
+ .setHideOnKeyOutside(false)
+ .setDisposable(ApplicationManager.getApplication())
+ .createBalloon()
+ .show(
+ new RelativePoint(component, new Point(rect.x + 30, rect.y + rect.height - 10)),
+ Balloon.Position.above);
+ }
+}
diff --git a/java/src/com/google/idea/blaze/java/projectview/JavaLanguageLevelSection.java b/java/src/com/google/idea/blaze/java/projectview/JavaLanguageLevelSection.java
index 09cd3b4..6035da6 100644
--- a/java/src/com/google/idea/blaze/java/projectview/JavaLanguageLevelSection.java
+++ b/java/src/com/google/idea/blaze/java/projectview/JavaLanguageLevelSection.java
@@ -33,11 +33,10 @@
public static LanguageLevel getLanguageLevel(
ProjectViewSet projectViewSet, LanguageLevel defaultValue) {
- Integer level = projectViewSet.getScalarValue(KEY, null);
- if (level == null) {
- return defaultValue;
- }
- return getLanguageLevel(level, defaultValue);
+ return projectViewSet
+ .getScalarValue(KEY)
+ .map(i -> getLanguageLevel(i, defaultValue))
+ .orElse(defaultValue);
}
@Nullable
diff --git a/java/src/com/google/idea/blaze/java/psi/AutoFactoryImplicitUsageProvider.java b/java/src/com/google/idea/blaze/java/psi/AutoFactoryImplicitUsageProvider.java
new file mode 100644
index 0000000..84570a3
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/psi/AutoFactoryImplicitUsageProvider.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.intellij.codeInsight.daemon.ImplicitUsageProvider;
+import com.intellij.psi.PsiElement;
+
+/** Suppresses 'unused' warnings for @AutoFactory annotated classes / constructors. */
+public class AutoFactoryImplicitUsageProvider implements ImplicitUsageProvider {
+
+ @Override
+ public boolean isImplicitUsage(PsiElement element) {
+ return AutoFactoryUseScopeEnlarger.isAutoFactoryClass(element);
+ }
+
+ @Override
+ public boolean isImplicitRead(PsiElement element) {
+ return false;
+ }
+
+ @Override
+ public boolean isImplicitWrite(PsiElement element) {
+ return false;
+ }
+}
diff --git a/java/src/com/google/idea/blaze/java/psi/AutoFactoryUseScopeEnlarger.java b/java/src/com/google/idea/blaze/java/psi/AutoFactoryUseScopeEnlarger.java
new file mode 100644
index 0000000..02bdba5
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/psi/AutoFactoryUseScopeEnlarger.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.intellij.psi.PsiClass;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiMethod;
+import com.intellij.psi.PsiModifier;
+import com.intellij.psi.PsiModifierList;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.psi.search.SearchScope;
+import com.intellij.psi.search.UseScopeEnlarger;
+import javax.annotation.Nullable;
+
+/**
+ * Find usages of @AutoFactory-annotated classes in project libraries.
+ *
+ * <p>Without implementing {@link UseScopeEnlarger}, IntelliJ will not search libraries for usages
+ * of symbols defined in the project.
+ */
+public class AutoFactoryUseScopeEnlarger extends UseScopeEnlarger {
+
+ private static final String AUTO_FACTORY_ANNOTATION = "com.google.auto.factory.AutoFactory";
+
+ @Nullable
+ @Override
+ public SearchScope getAdditionalUseScope(PsiElement element) {
+ if (isAutoFactoryClass(element)) {
+ return GlobalSearchScope.allScope(element.getProject());
+ }
+ return null;
+ }
+
+ @Nullable
+ private static PsiClass getPsiClass(PsiElement element) {
+ if (element instanceof PsiClass) {
+ return (PsiClass) element;
+ }
+ if (element instanceof PsiMethod && ((PsiMethod) element).isConstructor()) {
+ return ((PsiMethod) element).getContainingClass();
+ }
+ return null;
+ }
+
+ static boolean isAutoFactoryClass(PsiElement element) {
+ PsiClass psiClass = getPsiClass(element);
+ if (psiClass == null || !psiClass.hasModifierProperty(PsiModifier.PUBLIC)) {
+ return false;
+ }
+ PsiModifierList modifiers = psiClass.getModifierList();
+ return modifiers != null && modifiers.findAnnotation(AUTO_FACTORY_ANNOTATION) != null;
+ }
+}
diff --git a/java/src/com/google/idea/blaze/java/run/BlazeJavaRunConfigurationFactory.java b/java/src/com/google/idea/blaze/java/run/BlazeJavaRunConfigurationFactory.java
deleted file mode 100644
index 3daee7a..0000000
--- a/java/src/com/google/idea/blaze/java/run/BlazeJavaRunConfigurationFactory.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.java.run;
-
-import com.google.idea.blaze.base.command.BlazeCommandName;
-import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
-import com.google.idea.blaze.base.ideinfo.TargetKey;
-import com.google.idea.blaze.base.model.BlazeProjectData;
-import com.google.idea.blaze.base.model.primitives.Kind;
-import com.google.idea.blaze.base.model.primitives.Label;
-import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
-import com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType;
-import com.google.idea.blaze.base.run.BlazeRunConfigurationFactory;
-import com.google.idea.blaze.base.run.state.BlazeCommandRunConfigurationCommonState;
-import com.intellij.execution.configurations.ConfigurationFactory;
-import com.intellij.execution.configurations.RunConfiguration;
-import com.intellij.openapi.project.Project;
-
-/** Creates run configurations for java_binary. */
-public class BlazeJavaRunConfigurationFactory extends BlazeRunConfigurationFactory {
- @Override
- public boolean handlesTarget(Project project, BlazeProjectData blazeProjectData, Label label) {
- TargetIdeInfo target = blazeProjectData.targetMap.get(TargetKey.forPlainTarget(label));
- return target != null && target.kind == Kind.JAVA_BINARY;
- }
-
- @Override
- protected ConfigurationFactory getConfigurationFactory() {
- return BlazeCommandRunConfigurationType.getInstance().getFactory();
- }
-
- @Override
- public void setupConfiguration(RunConfiguration configuration, Label target) {
- final BlazeCommandRunConfiguration blazeConfig = (BlazeCommandRunConfiguration) configuration;
- blazeConfig.setTarget(target);
-
- BlazeCommandRunConfigurationCommonState state =
- blazeConfig.getHandlerStateIfType(BlazeCommandRunConfigurationCommonState.class);
- if (state != null) {
- state.getCommandState().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
index ca6a022..fbfe454 100644
--- a/java/src/com/google/idea/blaze/java/run/BlazeJavaRunConfigurationHandlerProvider.java
+++ b/java/src/com/google/idea/blaze/java/run/BlazeJavaRunConfigurationHandlerProvider.java
@@ -26,7 +26,13 @@
implements BlazeCommandRunConfigurationHandlerProvider {
private static final ImmutableSet<Kind> RELEVANT_RULE_KINDS =
- ImmutableSet.of(Kind.ANDROID_ROBOLECTRIC_TEST, Kind.JAVA_TEST, Kind.JAVA_BINARY);
+ ImmutableSet.of(
+ Kind.ANDROID_ROBOLECTRIC_TEST,
+ Kind.JAVA_TEST,
+ Kind.JAVA_BINARY,
+ Kind.SCALA_BINARY,
+ Kind.SCALA_TEST,
+ Kind.SCALA_JUNIT_TEST);
static boolean supportsKind(Kind kind) {
return RELEVANT_RULE_KINDS.contains(kind);
@@ -46,5 +52,4 @@
public String getId() {
return "BlazeJavaRunConfigurationHandlerProvider";
}
-
}
diff --git a/java/src/com/google/idea/blaze/java/run/BlazeJavaRunProfileState.java b/java/src/com/google/idea/blaze/java/run/BlazeJavaRunProfileState.java
index e7c4773..67a1cd5 100644
--- a/java/src/com/google/idea/blaze/java/run/BlazeJavaRunProfileState.java
+++ b/java/src/com/google/idea/blaze/java/run/BlazeJavaRunProfileState.java
@@ -21,18 +21,19 @@
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.BlazeInvocationContext;
import com.google.idea.blaze.base.issueparser.IssueOutputLineProcessor;
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.DistributedExecutorSupport;
import com.google.idea.blaze.base.run.filter.BlazeTargetFilter;
import com.google.idea.blaze.base.run.processhandler.LineProcessingProcessAdapter;
import com.google.idea.blaze.base.run.processhandler.ScopedBlazeProcessHandler;
-import com.google.idea.blaze.base.run.smrunner.BlazeTestEventsHandler;
+import com.google.idea.blaze.base.run.smrunner.BlazeTestUiSession;
import com.google.idea.blaze.base.run.smrunner.SmRunnerUtils;
+import com.google.idea.blaze.base.run.smrunner.TestUiSessionProvider;
import com.google.idea.blaze.base.run.state.BlazeCommandRunConfigurationCommonState;
import com.google.idea.blaze.base.scope.BlazeContext;
import com.google.idea.blaze.base.scope.scopes.IdeaLogScope;
@@ -97,23 +98,20 @@
assert projectViewSet != null;
BlazeCommand blazeCommand;
- if (useTestUi()) {
- BlazeTestEventsHandler eventsHandler =
- BlazeTestEventsHandler.getHandlerForTarget(project, configuration.getTarget());
- assert (eventsHandler != null);
+ BlazeTestUiSession testUiSession =
+ useTestUi()
+ ? TestUiSessionProvider.createForTarget(project, configuration.getTarget())
+ : null;
+ if (testUiSession != null) {
blazeCommand =
getBlazeCommand(
- project,
- configuration,
- projectViewSet,
- BlazeTestEventsHandler.getBlazeFlags(project),
- debug);
+ project, configuration, projectViewSet, testUiSession.getBlazeFlags(), debug);
setConsoleBuilder(
new TextConsoleBuilderImpl(project) {
@Override
protected ConsoleView createConsole() {
return SmRunnerUtils.getConsoleView(
- project, configuration, getEnvironment().getExecutor(), eventsHandler);
+ project, configuration, getEnvironment().getExecutor(), testUiSession);
}
});
} else {
@@ -153,9 +151,7 @@
private boolean useTestUi() {
BlazeCommandRunConfigurationCommonState state =
configuration.getHandlerStateIfType(BlazeCommandRunConfigurationCommonState.class);
- return state != null
- && BlazeCommandName.TEST.equals(state.getCommandState().getCommand())
- && !state.getRunOnDistributedExecutorState().runOnDistributedExecutor;
+ return state != null && BlazeCommandName.TEST.equals(state.getCommandState().getCommand());
}
@Override
@@ -192,22 +188,20 @@
BlazeCommand.Builder command =
BlazeCommand.builder(binaryPath, blazeCommand)
.addTargets(configuration.getTarget())
- .addBlazeFlags(BlazeFlags.buildFlags(project, projectViewSet))
+ .addBlazeFlags(
+ BlazeFlags.blazeFlags(
+ project, projectViewSet, blazeCommand, BlazeInvocationContext.RunConfiguration))
.addBlazeFlags(extraBlazeFlags)
.addBlazeFlags(handlerState.getBlazeFlagsState().getExpandedFlags());
if (debug) {
Kind kind = configuration.getKindForTarget();
- boolean isJavaBinary = kind == Kind.JAVA_BINARY;
- if (isJavaBinary) {
+ boolean isBinary = kind != null && kind.isOneOf(Kind.JAVA_BINARY, Kind.SCALA_BINARY);
+ if (isBinary) {
command.addExeFlags(BlazeFlags.JAVA_BINARY_DEBUG);
} else {
command.addBlazeFlags(BlazeFlags.JAVA_TEST_DEBUG);
}
- } else {
- boolean runDistributed =
- handlerState.getRunOnDistributedExecutorState().runOnDistributedExecutor;
- command.addBlazeFlags(DistributedExecutorSupport.getBlazeFlags(project, runDistributed));
}
command.addExeFlags(handlerState.getExeFlagsState().getExpandedFlags());
diff --git a/java/src/com/google/idea/blaze/java/run/BlazeJavaTestEventsHandler.java b/java/src/com/google/idea/blaze/java/run/BlazeJavaTestEventsHandler.java
index 8091b51..73b1ef8 100644
--- a/java/src/com/google/idea/blaze/java/run/BlazeJavaTestEventsHandler.java
+++ b/java/src/com/google/idea/blaze/java/run/BlazeJavaTestEventsHandler.java
@@ -15,6 +15,7 @@
*/
package com.google.idea.blaze.java.run;
+import com.google.common.collect.ImmutableSet;
import com.google.idea.blaze.base.command.BlazeFlags;
import com.google.idea.blaze.base.model.primitives.Kind;
import com.google.idea.blaze.base.run.smrunner.BlazeTestEventsHandler;
@@ -30,7 +31,6 @@
import com.intellij.psi.PsiMethod;
import com.intellij.util.io.URLUtil;
import java.util.Collection;
-import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -38,11 +38,19 @@
import javax.annotation.Nullable;
/** Provides java-specific methods needed by the SM-runner test UI. */
-public class BlazeJavaTestEventsHandler extends BlazeTestEventsHandler {
+public class BlazeJavaTestEventsHandler implements BlazeTestEventsHandler {
+
+ private static final ImmutableSet<Kind> HANDLED_KINDS =
+ ImmutableSet.of(
+ Kind.JAVA_TEST,
+ Kind.ANDROID_ROBOLECTRIC_TEST,
+ Kind.GWT_TEST,
+ Kind.SCALA_TEST,
+ Kind.SCALA_JUNIT_TEST);
@Override
- protected EnumSet<Kind> handledKinds() {
- return EnumSet.of(Kind.JAVA_TEST, Kind.ANDROID_ROBOLECTRIC_TEST, Kind.GWT_TEST);
+ public boolean handlesKind(@Nullable Kind kind) {
+ return HANDLED_KINDS.contains(kind);
}
/** Overridden to support parameterized tests, which use nested test_suite XML elements. */
diff --git a/java/src/com/google/idea/blaze/java/run/BlazeJavaTestRunConfigurationFactory.java b/java/src/com/google/idea/blaze/java/run/BlazeJavaTestRunConfigurationFactory.java
deleted file mode 100644
index 9b9aad8..0000000
--- a/java/src/com/google/idea/blaze/java/run/BlazeJavaTestRunConfigurationFactory.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.java.run;
-
-import com.google.idea.blaze.base.command.BlazeCommandName;
-import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
-import com.google.idea.blaze.base.ideinfo.TargetKey;
-import com.google.idea.blaze.base.model.BlazeProjectData;
-import com.google.idea.blaze.base.model.primitives.Kind;
-import com.google.idea.blaze.base.model.primitives.Label;
-import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
-import com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType;
-import com.google.idea.blaze.base.run.BlazeRunConfigurationFactory;
-import com.google.idea.blaze.base.run.state.BlazeCommandRunConfigurationCommonState;
-import com.intellij.execution.configurations.ConfigurationFactory;
-import com.intellij.execution.configurations.RunConfiguration;
-import com.intellij.openapi.project.Project;
-
-/** Creates run configurations for java_test and android_robolectric_test. */
-public class BlazeJavaTestRunConfigurationFactory extends BlazeRunConfigurationFactory {
- @Override
- public boolean handlesTarget(Project project, BlazeProjectData blazeProjectData, Label label) {
- TargetIdeInfo target = blazeProjectData.targetMap.get(TargetKey.forPlainTarget(label));
- return target != null && target.kindIsOneOf(Kind.JAVA_TEST, Kind.ANDROID_ROBOLECTRIC_TEST);
- }
-
- @Override
- protected ConfigurationFactory getConfigurationFactory() {
- return BlazeCommandRunConfigurationType.getInstance().getFactory();
- }
-
- @Override
- public void setupConfiguration(RunConfiguration configuration, Label target) {
- final BlazeCommandRunConfiguration blazeConfig = (BlazeCommandRunConfiguration) configuration;
- blazeConfig.setTarget(target);
-
- BlazeCommandRunConfigurationCommonState state =
- blazeConfig.getHandlerStateIfType(BlazeCommandRunConfigurationCommonState.class);
- if (state != null) {
- state.getCommandState().setCommand(BlazeCommandName.TEST);
- }
- blazeConfig.setGeneratedName();
- }
-}
diff --git a/java/src/com/google/idea/blaze/java/run/QualifiedClassNameHeuristic.java b/java/src/com/google/idea/blaze/java/run/QualifiedClassNameHeuristic.java
new file mode 100644
index 0000000..164e704
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/run/QualifiedClassNameHeuristic.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TestIdeInfo.TestSize;
+import com.google.idea.blaze.base.run.TestTargetHeuristic;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiClassOwner;
+import com.intellij.psi.PsiFile;
+import java.io.File;
+import javax.annotation.Nullable;
+
+/** Matches test targets to source files based on fully qualified class names. */
+public class QualifiedClassNameHeuristic implements TestTargetHeuristic {
+
+ @Override
+ public boolean matchesSource(
+ Project project,
+ TargetIdeInfo target,
+ @Nullable PsiFile sourcePsiFile,
+ File sourceFile,
+ @Nullable TestSize testSize) {
+ if (!(sourcePsiFile instanceof PsiClassOwner)) {
+ return false;
+ }
+ String targetName = target.key.label.targetName().toString();
+ if (!targetName.contains(".")) {
+ return false;
+ }
+ for (PsiClass psiClass : ((PsiClassOwner) sourcePsiFile).getClasses()) {
+ String fqcn = psiClass.getQualifiedName();
+ if (fqcn != null && fqcn.endsWith(targetName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/java/src/com/google/idea/blaze/java/run/producers/BlazeJavaTestClassConfigurationProducer.java b/java/src/com/google/idea/blaze/java/run/producers/BlazeJavaTestClassConfigurationProducer.java
index 1609e30..12f0075 100644
--- a/java/src/com/google/idea/blaze/java/run/producers/BlazeJavaTestClassConfigurationProducer.java
+++ b/java/src/com/google/idea/blaze/java/run/producers/BlazeJavaTestClassConfigurationProducer.java
@@ -20,6 +20,7 @@
import com.google.idea.blaze.base.command.BlazeFlags;
import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
import com.google.idea.blaze.base.ideinfo.TestIdeInfo;
+import com.google.idea.blaze.base.model.primitives.Label;
import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
import com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType;
import com.google.idea.blaze.base.run.BlazeConfigurationNameBuilder;
@@ -34,7 +35,6 @@
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.PsiModifier;
import java.util.ArrayList;
import java.util.Collection;
@@ -44,7 +44,6 @@
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
-import org.jetbrains.annotations.NotNull;
/** Producer for run configurations related to Java test classes in Blaze. */
public class BlazeJavaTestClassConfigurationProducer
@@ -54,49 +53,70 @@
super(BlazeCommandRunConfigurationType.getInstance());
}
- @Override
- protected boolean doSetupConfigFromContext(
- @NotNull BlazeCommandRunConfiguration configuration,
- @NotNull ConfigurationContext context,
- @NotNull Ref<PsiElement> sourceElement) {
+ private static class TestLocation {
+ final PsiClass testClass;
+ final Label blazeTarget;
- final Location contextLocation = context.getLocation();
- assert contextLocation != null;
- final Location location = JavaExecutionUtil.stepIntoSingleClass(contextLocation);
+ private TestLocation(PsiClass testClass, Label blazeTarget) {
+ this.testClass = testClass;
+ this.blazeTarget = blazeTarget;
+ }
+ }
+
+ /**
+ * Returns the {@link TestLocation} corresponding to the single selected JUnit test class, or
+ * {@code null} if something else is selected.
+ */
+ @Nullable
+ private static TestLocation getSingleJUnitTestClass(ConfigurationContext context) {
+ Location<?> location = context.getLocation();
if (location == null) {
- return false;
+ return null;
+ }
+ location = JavaExecutionUtil.stepIntoSingleClass(location);
+ if (location == null) {
+ return null;
}
+ // check for contexts handled by a different producer
if (!SmRunnerUtils.getSelectedSmRunnerTreeElements(context).isEmpty()) {
- // handled by a different producer
- return false;
+ return null;
}
if (JUnitConfigurationUtil.isMultipleElementsSelected(context)) {
- return false;
+ return null;
+ }
+ if (TestMethodSelectionUtil.getSelectedMethods(context) != null) {
+ return null;
}
PsiClass testClass = JUnitUtil.getTestClass(location);
- if (testClass == null) {
- return false;
+ if (testClass == null || testClass.hasModifierProperty(PsiModifier.ABSTRACT)) {
+ return null;
}
- if (testClass.hasModifierProperty(PsiModifier.ABSTRACT)) {
- return false;
- }
- sourceElement.set(testClass);
TestIdeInfo.TestSize testSize = TestSizeAnnotationMap.getTestSize(testClass);
TargetIdeInfo target = RunUtil.targetForTestClass(testClass, testSize);
- if (target == null) {
+ return target != null ? new TestLocation(testClass, target.key.label) : null;
+ }
+
+ @Override
+ protected boolean doSetupConfigFromContext(
+ BlazeCommandRunConfiguration configuration,
+ ConfigurationContext context,
+ Ref<PsiElement> sourceElement) {
+ TestLocation location = getSingleJUnitTestClass(context);
+ if (location == null) {
return false;
}
+ sourceElement.set(location.testClass);
+ configuration.setTarget(location.blazeTarget);
- configuration.setTarget(target.key.label);
BlazeCommandRunConfigurationCommonState handlerState =
configuration.getHandlerStateIfType(BlazeCommandRunConfigurationCommonState.class);
if (handlerState == null) {
return false;
}
- String testFilter = getTestFilter(testClass);
+ String testFilter = getTestFilter(location.testClass);
if (testFilter == null) {
return false;
}
@@ -108,47 +128,30 @@
flags.add(BlazeFlags.TEST_FILTER + "=" + testFilter);
handlerState.getBlazeFlagsState().setRawFlags(flags);
- BlazeConfigurationNameBuilder nameBuilder = new BlazeConfigurationNameBuilder(configuration);
- nameBuilder.setTargetString(testClass.getName());
- configuration.setName(nameBuilder.build());
+ String name =
+ new BlazeConfigurationNameBuilder(configuration)
+ .setTargetString(location.testClass.getName())
+ .build();
+ configuration.setName(name);
configuration.setNameChangedByUser(true); // don't revert to generated name
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);
+ BlazeCommandRunConfiguration configuration, ConfigurationContext context) {
+ TestLocation location = getSingleJUnitTestClass(context);
if (location == null) {
return false;
}
-
- if (!SmRunnerUtils.getSelectedSmRunnerTreeElements(context).isEmpty()) {
- // handled by a different producer
- 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);
+ return checkIfAttributesAreTheSame(configuration, location);
}
private boolean checkIfAttributesAreTheSame(
- @NotNull BlazeCommandRunConfiguration configuration, @NotNull PsiClass testClass) {
+ BlazeCommandRunConfiguration configuration, TestLocation location) {
+ if (!location.blazeTarget.equals(configuration.getTarget())) {
+ return false;
+ }
BlazeCommandRunConfigurationCommonState handlerState =
configuration.getHandlerStateIfType(BlazeCommandRunConfigurationCommonState.class);
if (handlerState == null) {
@@ -157,11 +160,9 @@
if (!Objects.equals(handlerState.getCommandState().getCommand(), BlazeCommandName.TEST)) {
return false;
}
- String filter = getTestFilter(testClass);
- if (filter == null) {
- return false;
- }
- return Objects.equals(BlazeFlags.TEST_FILTER + "=" + filter, handlerState.getTestFilterFlag());
+ String filter = getTestFilter(location.testClass);
+ return filter != null
+ && Objects.equals(BlazeFlags.TEST_FILTER + "=" + filter, handlerState.getTestFilterFlag());
}
@Nullable
diff --git a/java/src/com/google/idea/blaze/java/run/producers/BlazeJavaTestMethodConfigurationProducer.java b/java/src/com/google/idea/blaze/java/run/producers/BlazeJavaTestMethodConfigurationProducer.java
index b1cabb3..e97ca5f 100644
--- a/java/src/com/google/idea/blaze/java/run/producers/BlazeJavaTestMethodConfigurationProducer.java
+++ b/java/src/com/google/idea/blaze/java/run/producers/BlazeJavaTestMethodConfigurationProducer.java
@@ -19,6 +19,7 @@
import com.google.idea.blaze.base.command.BlazeFlags;
import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
import com.google.idea.blaze.base.ideinfo.TestIdeInfo;
+import com.google.idea.blaze.base.model.primitives.Label;
import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
import com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType;
import com.google.idea.blaze.base.run.BlazeConfigurationNameBuilder;
@@ -41,21 +42,24 @@
public class BlazeJavaTestMethodConfigurationProducer
extends BlazeRunConfigurationProducer<BlazeCommandRunConfiguration> {
- private static class SelectedMethodInfo {
+ private static class TestMethodContext {
private final PsiMethod firstMethod;
private final PsiClass containingClass;
private final List<String> methodNames;
private final String testFilterFlag;
+ private final Label blazeTarget;
- public SelectedMethodInfo(
+ TestMethodContext(
PsiMethod firstMethod,
PsiClass containingClass,
List<String> methodNames,
- String testFilterFlag) {
+ String testFilterFlag,
+ Label blazeTarget) {
this.firstMethod = firstMethod;
this.containingClass = containingClass;
this.methodNames = methodNames;
this.testFilterFlag = testFilterFlag;
+ this.blazeTarget = blazeTarget;
}
}
@@ -69,23 +73,17 @@
ConfigurationContext context,
Ref<PsiElement> sourceElement) {
- SelectedMethodInfo methodInfo = getSelectedMethodInfo(context);
- if (methodInfo == null) {
+ TestMethodContext methodContext = getSelectedMethodContext(context);
+ if (methodContext == 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);
+ sourceElement.set(methodContext.firstMethod);
- TestIdeInfo.TestSize testSize = TestSizeAnnotationMap.getTestSize(methodInfo.firstMethod);
- TargetIdeInfo target = RunUtil.targetForTestClass(methodInfo.containingClass, testSize);
- if (target == null) {
- return false;
- }
-
- configuration.setTarget(target.key.label);
+ configuration.setTarget(methodContext.blazeTarget);
BlazeCommandRunConfigurationCommonState handlerState =
configuration.getHandlerStateIfType(BlazeCommandRunConfigurationCommonState.class);
if (handlerState == null) {
@@ -96,7 +94,7 @@
// remove old test filter flag if present
List<String> flags = new ArrayList<>(handlerState.getBlazeFlagsState().getRawFlags());
flags.removeIf((flag) -> flag.startsWith(BlazeFlags.TEST_FILTER));
- flags.add(methodInfo.testFilterFlag);
+ flags.add(methodContext.testFilterFlag);
if (!flags.contains(BlazeFlags.DISABLE_TEST_SHARDING)) {
flags.add(BlazeFlags.DISABLE_TEST_SHARDING);
}
@@ -106,7 +104,7 @@
nameBuilder.setTargetString(
String.format(
"%s.%s",
- methodInfo.containingClass.getName(), String.join(",", methodInfo.methodNames)));
+ methodContext.containingClass.getName(), String.join(",", methodContext.methodNames)));
configuration.setName(nameBuilder.build());
configuration.setNameChangedByUser(true); // don't revert to generated name
return true;
@@ -124,17 +122,14 @@
return false;
}
- SelectedMethodInfo methodInfo = getSelectedMethodInfo(context);
- if (methodInfo == null) {
- return false;
- }
-
- List<String> flags = handlerState.getBlazeFlagsState().getRawFlags();
- return flags.contains(methodInfo.testFilterFlag);
+ TestMethodContext methodContext = getSelectedMethodContext(context);
+ return methodContext != null
+ && handlerState.getBlazeFlagsState().getRawFlags().contains(methodContext.testFilterFlag)
+ && methodContext.blazeTarget.equals(configuration.getTarget());
}
@Nullable
- private static SelectedMethodInfo getSelectedMethodInfo(ConfigurationContext context) {
+ private static TestMethodContext getSelectedMethodContext(ConfigurationContext context) {
if (!SmRunnerUtils.getSelectedSmRunnerTreeElements(context).isEmpty()) {
// handled by a different producer
return null;
@@ -155,6 +150,13 @@
return null;
}
}
+
+ TestIdeInfo.TestSize testSize = TestSizeAnnotationMap.getTestSize(firstMethod);
+ TargetIdeInfo target = RunUtil.targetForTestClass(containingClass, testSize);
+ if (target == null) {
+ return null;
+ }
+
String testFilter =
BlazeJUnitTestFilterFlags.testFilterForClassAndMethods(containingClass, selectedMethods);
if (testFilter == null) {
@@ -164,6 +166,7 @@
List<String> methodNames =
selectedMethods.stream().map(PsiMethod::getName).sorted().collect(Collectors.toList());
final String testFilterFlag = BlazeFlags.TEST_FILTER + "=" + testFilter;
- return new SelectedMethodInfo(firstMethod, containingClass, methodNames, testFilterFlag);
+ return new TestMethodContext(
+ firstMethod, containingClass, methodNames, testFilterFlag, target.key.label);
}
}
diff --git a/java/src/com/google/idea/blaze/java/run/producers/NonBlazeProducerSuppressor.java b/java/src/com/google/idea/blaze/java/run/producers/NonBlazeProducerSuppressor.java
index 4260daa..73ec889 100644
--- a/java/src/com/google/idea/blaze/java/run/producers/NonBlazeProducerSuppressor.java
+++ b/java/src/com/google/idea/blaze/java/run/producers/NonBlazeProducerSuppressor.java
@@ -17,6 +17,7 @@
import com.google.common.collect.ImmutableList;
import com.google.idea.blaze.base.settings.Blaze;
+import com.google.idea.sdkcompat.java.JavaConfigurationProducerList;
import com.intellij.execution.RunConfigurationProducerService;
import com.intellij.execution.actions.RunConfigurationProducer;
import com.intellij.ide.plugins.IdeaPluginDescriptor;
@@ -32,15 +33,8 @@
/** Suppresses certain non-Blaze configuration producers in Blaze projects. */
public class NonBlazeProducerSuppressor extends AbstractProjectComponent {
- private static final Collection<Class<? extends RunConfigurationProducer<?>>>
- PRODUCERS_TO_SUPPRESS =
- ImmutableList.of(
- com.intellij.execution.junit.AllInDirectoryConfigurationProducer.class,
- com.intellij.execution.junit.AllInPackageConfigurationProducer.class,
- com.intellij.execution.junit.TestClassConfigurationProducer.class,
- com.intellij.execution.junit.TestMethodConfigurationProducer.class,
- com.intellij.execution.junit.PatternConfigurationProducer.class,
- com.intellij.execution.application.ApplicationConfigurationProducer.class);
+ private static final String KOTLIN_PLUGIN_ID = "org.jetbrains.kotlin";
+ private static final String ANDROID_PLUGIN_ID = "org.jetbrains.android";
private static final ImmutableList<String> KOTLIN_PRODUCERS =
ImmutableList.of(
@@ -48,15 +42,26 @@
"org.jetbrains.kotlin.idea.run.KotlinPatternConfigurationProducer",
"org.jetbrains.kotlin.idea.run.KotlinRunConfigurationProducer");
- private static Collection<Class<? extends RunConfigurationProducer<?>>> getKotlinProducers() {
- // rather than compiling against the Kotlin plugin, and including a switch in the our
+ private static final ImmutableList<String> ANDROID_PRODUCERS =
+ ImmutableList.of(
+ "com.android.tools.idea.run.AndroidConfigurationProducer",
+ "com.android.tools.idea.testartifacts.instrumented.AndroidTestConfigurationProducer",
+ "com.android.tools.idea.testartifacts.junit.TestClassAndroidConfigurationProducer",
+ "com.android.tools.idea.testartifacts.junit.TestDirectoryAndroidConfigurationProducer",
+ "com.android.tools.idea.testartifacts.junit.TestMethodAndroidConfigurationProducer",
+ "com.android.tools.idea.testartifacts.junit.TestPackageAndroidConfigurationProducer",
+ "com.android.tools.idea.testartifacts.junit.TestPatternConfigurationProducer");
+
+ private static Collection<Class<? extends RunConfigurationProducer<?>>> getProducers(
+ String pluginId, Collection<String> qualifiedClassNames) {
+ // rather than compiling against additional plugins, and including a switch in the our
// plugin.xml, just get the classes manually via the plugin class loader.
- IdeaPluginDescriptor plugin = PluginManager.getPlugin(PluginId.getId("org.jetbrains.kotlin"));
+ IdeaPluginDescriptor plugin = PluginManager.getPlugin(PluginId.getId(pluginId));
if (plugin == null || !plugin.isEnabled()) {
return ImmutableList.of();
}
ClassLoader loader = plugin.getPluginClassLoader();
- return KOTLIN_PRODUCERS
+ return qualifiedClassNames
.stream()
.map((qualifiedName) -> loadClass(loader, qualifiedName))
.filter(Objects::nonNull)
@@ -92,8 +97,9 @@
RunConfigurationProducerService producerService =
RunConfigurationProducerService.getInstance(project);
ImmutableList.<Class<? extends RunConfigurationProducer<?>>>builder()
- .addAll(PRODUCERS_TO_SUPPRESS)
- .addAll(getKotlinProducers())
+ .addAll(JavaConfigurationProducerList.PRODUCERS_TO_SUPPRESS)
+ .addAll(getProducers(KOTLIN_PLUGIN_ID, KOTLIN_PRODUCERS))
+ .addAll(getProducers(ANDROID_PLUGIN_ID, ANDROID_PRODUCERS))
.build()
.forEach(producerService::addIgnoredProducer);
}
diff --git a/java/src/com/google/idea/blaze/java/run/producers/SubclassTestChooser.java b/java/src/com/google/idea/blaze/java/run/producers/SubclassTestChooser.java
index 1bc5250..6ea06e0 100644
--- a/java/src/com/google/idea/blaze/java/run/producers/SubclassTestChooser.java
+++ b/java/src/com/google/idea/blaze/java/run/producers/SubclassTestChooser.java
@@ -47,7 +47,7 @@
}
PsiClassListCellRenderer renderer = new PsiClassListCellRenderer();
classes.sort(renderer.getComparator());
- // JBList has no generics in AS 2.2. TODO: Add generics here when we migrate to AS 2.3.
+ // #api171 add generics to JBList.
JBList list = new JBList(classes);
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
list.setCellRenderer(renderer);
@@ -57,6 +57,7 @@
.setMovable(false)
.setResizable(false)
.setRequestFocus(true)
+ .setCancelOnWindowDeactivation(false)
.setItemChoosenCallback(
() -> callbackOnClassSelection.accept((PsiClass) list.getSelectedValue()))
.createPopup()
diff --git a/java/src/com/google/idea/blaze/java/settings/BlazeJavaUserSettings.java b/java/src/com/google/idea/blaze/java/settings/BlazeJavaUserSettings.java
index d9b9d57..a30b8b3 100644
--- a/java/src/com/google/idea/blaze/java/settings/BlazeJavaUserSettings.java
+++ b/java/src/com/google/idea/blaze/java/settings/BlazeJavaUserSettings.java
@@ -17,13 +17,11 @@
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"))
@@ -31,12 +29,9 @@
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;
+ return ServiceManager.getService(BlazeJavaUserSettings.class);
}
private static boolean getDefaultJarCacheValue() {
@@ -44,7 +39,6 @@
}
@Override
- @NotNull
public BlazeJavaUserSettings getState() {
return this;
}
@@ -52,20 +46,6 @@
@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() {
@@ -91,14 +71,4 @@
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/sync/BlazeJavaLibrarySource.java b/java/src/com/google/idea/blaze/java/sync/BlazeJavaLibrarySource.java
index 0fd68e1..fea7d94 100644
--- a/java/src/com/google/idea/blaze/java/sync/BlazeJavaLibrarySource.java
+++ b/java/src/com/google/idea/blaze/java/sync/BlazeJavaLibrarySource.java
@@ -20,7 +20,7 @@
import com.google.idea.blaze.base.model.BlazeProjectData;
import com.google.idea.blaze.base.sync.libraries.LibrarySource;
import com.google.idea.blaze.java.sync.model.BlazeJavaSyncData;
-import java.util.Collection;
+import java.util.List;
import java.util.function.Predicate;
import javax.annotation.Nullable;
@@ -33,12 +33,12 @@
}
@Override
- public Collection<? extends BlazeLibrary> getLibraries() {
+ public List<? extends BlazeLibrary> getLibraries() {
BlazeJavaSyncData syncData = blazeProjectData.syncState.get(BlazeJavaSyncData.class);
if (syncData == null) {
return ImmutableList.of();
}
- return syncData.importResult.libraries.values();
+ return syncData.importResult.libraries.values().asList();
}
@Nullable
diff --git a/java/src/com/google/idea/blaze/java/sync/BlazeJavaSyncPlugin.java b/java/src/com/google/idea/blaze/java/sync/BlazeJavaSyncPlugin.java
index 1d31d91..8748288 100644
--- a/java/src/com/google/idea/blaze/java/sync/BlazeJavaSyncPlugin.java
+++ b/java/src/com/google/idea/blaze/java/sync/BlazeJavaSyncPlugin.java
@@ -58,7 +58,6 @@
import com.google.idea.blaze.java.sync.projectstructure.Jdks;
import com.google.idea.blaze.java.sync.workingset.JavaWorkingSet;
import com.google.idea.sdkcompat.transactions.Transactions;
-import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.module.ModuleType;
import com.intellij.openapi.module.StdModuleTypes;
import com.intellij.openapi.project.Project;
@@ -214,18 +213,13 @@
private static void setProjectSdkAndLanguageLevel(
final Project project, final Sdk sdk, final LanguageLevel javaLanguageLevel) {
- Transactions.submitTransactionAndWait(
- () ->
- ApplicationManager.getApplication()
- .runWriteAction(
- () -> {
- ProjectRootManagerEx rootManager =
- ProjectRootManagerEx.getInstanceEx(project);
- rootManager.setProjectSdk(sdk);
- LanguageLevelProjectExtension ext =
- LanguageLevelProjectExtension.getInstance(project);
- ext.setLanguageLevel(javaLanguageLevel);
- }));
+ Transactions.submitWriteActionTransactionAndWait(
+ () -> {
+ ProjectRootManagerEx rootManager = ProjectRootManagerEx.getInstanceEx(project);
+ rootManager.setProjectSdk(sdk);
+ LanguageLevelProjectExtension ext = LanguageLevelProjectExtension.getInstance(project);
+ ext.setLanguageLevel(javaLanguageLevel);
+ });
}
@Override
diff --git a/java/src/com/google/idea/blaze/java/sync/JavaPrefetchFileSource.java b/java/src/com/google/idea/blaze/java/sync/JavaPrefetchFileSource.java
index c39eb64..a950fd4 100644
--- a/java/src/com/google/idea/blaze/java/sync/JavaPrefetchFileSource.java
+++ b/java/src/com/google/idea/blaze/java/sync/JavaPrefetchFileSource.java
@@ -21,6 +21,7 @@
import com.google.idea.blaze.base.prefetch.PrefetchFileSource;
import com.google.idea.blaze.base.projectview.ProjectViewSet;
import com.google.idea.blaze.base.sync.libraries.BlazeLibraryCollector;
+import com.google.idea.blaze.base.sync.projectview.ImportRoots;
import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
import com.google.idea.blaze.java.libraries.JarCache;
import com.google.idea.blaze.java.libraries.SourceJarManager;
@@ -38,8 +39,9 @@
public void addFilesToPrefetch(
Project project,
ProjectViewSet projectViewSet,
+ ImportRoots importRoots,
BlazeProjectData blazeProjectData,
- Collection<File> files) {
+ Set<File> files) {
BlazeJavaSyncData syncData = blazeProjectData.syncState.get(BlazeJavaSyncData.class);
if (syncData == null) {
return;
@@ -70,7 +72,7 @@
}
@Override
- public Set<String> prefetchSrcFileExtensions() {
+ public Set<String> prefetchFileExtensions() {
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
index c089e53..63d0c08 100644
--- a/java/src/com/google/idea/blaze/java/sync/importer/BlazeJavaWorkspaceImporter.java
+++ b/java/src/com/google/idea/blaze/java/sync/importer/BlazeJavaWorkspaceImporter.java
@@ -34,6 +34,7 @@
import com.google.idea.blaze.base.ideinfo.TargetKey;
import com.google.idea.blaze.base.ideinfo.TargetMap;
import com.google.idea.blaze.base.model.LibraryKey;
+import com.google.idea.blaze.base.model.primitives.Kind;
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;
@@ -222,9 +223,9 @@
WorkspaceBuilder workspaceBuilder,
TargetMap targetMap,
Map<LibraryKey, BlazeJarLibrary> result) {
- List<TargetKey> version1Roots = Lists.newArrayList();
- List<TargetKey> immutableRoots = Lists.newArrayList();
- List<TargetKey> mutableRoots = Lists.newArrayList();
+ List<TargetKey> version1Targets = Lists.newArrayList();
+ List<TargetKey> immutableTargets = Lists.newArrayList();
+ List<TargetKey> mutableTargets = Lists.newArrayList();
for (TargetKey targetKey : workspaceBuilder.directDeps) {
TargetIdeInfo target = targetMap.get(targetKey);
if (target == null) {
@@ -236,17 +237,17 @@
}
switch (protoLibraryLegacyInfo.apiFlavor) {
case VERSION_1:
- version1Roots.add(targetKey);
+ version1Targets.add(targetKey);
break;
case IMMUTABLE:
- immutableRoots.add(targetKey);
+ immutableTargets.add(targetKey);
break;
case MUTABLE:
- mutableRoots.add(targetKey);
+ mutableTargets.add(targetKey);
break;
case BOTH:
- mutableRoots.add(targetKey);
- immutableRoots.add(targetKey);
+ mutableTargets.add(targetKey);
+ immutableTargets.add(targetKey);
break;
default:
// Can't happen
@@ -255,25 +256,20 @@
}
addProtoLegacyLibrariesFromDirectDepsForFlavor(
- targetMap, ProtoLibraryLegacyInfo.ApiFlavor.VERSION_1, version1Roots, result);
+ targetMap, ProtoLibraryLegacyInfo.ApiFlavor.VERSION_1, version1Targets, result);
addProtoLegacyLibrariesFromDirectDepsForFlavor(
- targetMap, ProtoLibraryLegacyInfo.ApiFlavor.IMMUTABLE, immutableRoots, result);
+ targetMap, ProtoLibraryLegacyInfo.ApiFlavor.IMMUTABLE, immutableTargets, result);
addProtoLegacyLibrariesFromDirectDepsForFlavor(
- targetMap, ProtoLibraryLegacyInfo.ApiFlavor.MUTABLE, mutableRoots, result);
+ targetMap, ProtoLibraryLegacyInfo.ApiFlavor.MUTABLE, mutableTargets, result);
}
private void addProtoLegacyLibrariesFromDirectDepsForFlavor(
TargetMap targetMap,
ProtoLibraryLegacyInfo.ApiFlavor apiFlavor,
- List<TargetKey> roots,
+ List<TargetKey> targetKeys,
Map<LibraryKey, BlazeJarLibrary> result) {
- Set<TargetKey> seen = Sets.newHashSet();
- while (!roots.isEmpty()) {
- TargetKey targetKey = roots.remove(roots.size() - 1);
- if (!seen.add(targetKey)) {
- continue;
- }
- TargetIdeInfo target = targetMap.get(targetKey);
+ for (TargetKey key : targetKeys) {
+ TargetIdeInfo target = targetMap.get(key);
if (target == null) {
continue;
}
@@ -304,12 +300,6 @@
result.put(library.key, library);
}
}
-
- for (Dependency dep : target.dependencies) {
- if (dep.dependencyType == DependencyType.COMPILE_TIME) {
- roots.add(dep.targetKey);
- }
- }
}
}
@@ -346,7 +336,15 @@
// Add self, so we pick up our own gen jars if in working set
workspaceBuilder.directDeps.add(targetKey);
for (Dependency dep : target.dependencies) {
- if (dep.dependencyType == DependencyType.COMPILE_TIME) {
+ if (dep.dependencyType != DependencyType.COMPILE_TIME) {
+ continue;
+ }
+ // forward deps from java_proto_library
+ TargetIdeInfo depTarget = targetMap.get(dep.targetKey);
+ if (depTarget != null && depTarget.kind == Kind.JAVA_PROTO_LIBRARY) {
+ workspaceBuilder.directDeps.addAll(
+ depTarget.dependencies.stream().map(d -> d.targetKey).collect(Collectors.toList()));
+ } else {
workspaceBuilder.directDeps.add(dep.targetKey);
}
}
diff --git a/java/src/com/google/idea/blaze/java/sync/importer/JavaSourceFilter.java b/java/src/com/google/idea/blaze/java/sync/importer/JavaSourceFilter.java
index 474b729..5199ec9 100644
--- a/java/src/com/google/idea/blaze/java/sync/importer/JavaSourceFilter.java
+++ b/java/src/com/google/idea/blaze/java/sync/importer/JavaSourceFilter.java
@@ -94,8 +94,8 @@
return sourceTargets;
}
- private boolean canImportAsSource(TargetIdeInfo target) {
- return !target.kindIsOneOf(Kind.JAVA_WRAP_CC, Kind.JAVA_IMPORT);
+ public static boolean canImportAsSource(TargetIdeInfo target) {
+ return !target.kindIsOneOf(Kind.JAVA_WRAP_CC, Kind.JAVA_IMPORT, Kind.SCALA_IMPORT);
}
private boolean anyNonGeneratedSources(Collection<ArtifactLocation> sources) {
diff --git a/java/src/com/google/idea/blaze/java/sync/jdeps/JdepsFileReader.java b/java/src/com/google/idea/blaze/java/sync/jdeps/JdepsFileReader.java
index 550c00c..1cd2cee 100644
--- a/java/src/com/google/idea/blaze/java/sync/jdeps/JdepsFileReader.java
+++ b/java/src/com/google/idea/blaze/java/sync/jdeps/JdepsFileReader.java
@@ -132,7 +132,7 @@
removedFiles);
ListenableFuture<?> fetchFuture =
- PrefetchService.getInstance().prefetchFiles(project, updatedFiles);
+ PrefetchService.getInstance().prefetchFiles(project, updatedFiles, true);
if (!FutureUtil.waitForFuture(context, fetchFuture)
.timed("FetchJdeps")
.withProgressMessage("Reading jdeps files...")
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
index 704f4c4..a6e59ce 100644
--- a/java/src/com/google/idea/blaze/java/sync/projectstructure/Jdks.java
+++ b/java/src/com/google/idea/blaze/java/sync/projectstructure/Jdks.java
@@ -15,9 +15,7 @@
*/
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;
@@ -29,33 +27,35 @@
import com.intellij.openapi.projectRoots.JavaSdkVersion;
import com.intellij.openapi.projectRoots.ProjectJdkTable;
import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil;
+import com.intellij.openapi.util.text.StringUtil;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.util.SystemProperties;
import java.io.File;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import javax.annotation.Nullable;
-import org.jetbrains.annotations.NonNls;
-import org.jetbrains.annotations.NotNull;
/** Utility methods related to IDEA JDKs. */
public class Jdks {
- @NonNls private static final LanguageLevel DEFAULT_LANG_LEVEL = LanguageLevel.JDK_1_7;
+
+ private static final Logger logger = Logger.getInstance(Jdks.class);
@Nullable
public static Sdk chooseOrCreateJavaSdk(LanguageLevel langLevel) {
- for (Sdk sdk : ProjectJdkTable.getInstance().getAllJdks()) {
- if (isApplicableJdk(sdk, langLevel)) {
- return sdk;
- }
+ Sdk existing = findClosestMatch(langLevel);
+ if (existing != null) {
+ return existing;
}
String jdkHomePath = null;
for (DefaultSdkProvider defaultSdkProvider : DefaultSdkProvider.EP_NAME.getExtensions()) {
- File sdk = defaultSdkProvider.provideSdkForLanguage(LanguageClass.JAVA);
- if (sdk != null) {
- jdkHomePath = sdk.getPath();
+ File sdkRoot = defaultSdkProvider.provideSdkForLanguage(LanguageClass.JAVA);
+ if (sdkRoot != null) {
+ jdkHomePath = sdkRoot.getPath();
break;
}
}
@@ -63,39 +63,43 @@
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;
+ return jdkHomePath != null ? createJdk(jdkHomePath) : null;
}
@Nullable
- public static String getJdkHomePath(@NotNull LanguageLevel langLevel) {
- Collection<String> jdkHomePaths =
- new ArrayList<String>(JavaSdk.getInstance().suggestHomePaths());
+ @VisibleForTesting
+ static Sdk findClosestMatch(LanguageLevel langLevel) {
+ return Arrays.stream(ProjectJdkTable.getInstance().getAllJdks())
+ .filter(
+ sdk -> {
+ LanguageLevel level = getJavaLanguageLevel(sdk);
+ return level != null && level.isAtLeast(langLevel);
+ })
+ .min(Comparator.comparing(Jdks::getJavaLanguageLevel))
+ .orElse(null);
+ }
+
+ /**
+ * Returns null if the SDK is not a java JDK, or doesn't have a recognized java langauge level.
+ */
+ @Nullable
+ private static LanguageLevel getJavaLanguageLevel(Sdk sdk) {
+ if (!(sdk.getSdkType() instanceof JavaSdk)) {
+ return null;
+ }
+ JavaSdkVersion version = JavaSdk.getInstance().getVersion(sdk);
+ return version != null ? version.getMaxLanguageLevel() : null;
+ }
+
+ @Nullable
+ private static String getJdkHomePath(LanguageLevel langLevel) {
+ Collection<String> jdkHomePaths = new ArrayList<>(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>();
+ final List<String> list = new ArrayList<>();
String javaHome = SystemProperties.getJavaHome();
if (javaHome != null && !javaHome.isEmpty()) {
@@ -112,14 +116,12 @@
return getBestJdkHomePath(list, langLevel);
}
- @VisibleForTesting
@Nullable
- static String getBestJdkHomePath(
- @NotNull Collection<String> jdkHomePaths, @NotNull LanguageLevel langLevel) {
+ private static String getBestJdkHomePath(List<String> jdkHomePaths, 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)) {
+ if (StringUtil.isNotEmpty(jdkHomePath)) {
roots.add(jdkHomePath);
roots.addAll(getChildrenPaths(jdkHomePath));
}
@@ -127,8 +129,7 @@
return getBestJdk(roots, langLevel);
}
- @NotNull
- private static List<String> getChildrenPaths(@NotNull String dirPath) {
+ private static List<String> getChildrenPaths(String dirPath) {
File dir = new File(dirPath);
if (!dir.isDirectory()) {
return emptyList();
@@ -144,48 +145,16 @@
}
@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;
+ private static String getBestJdk(List<String> jdkRoots, LanguageLevel langLevel) {
+ return jdkRoots
+ .stream()
+ .filter(root -> JavaSdk.getInstance().isValidSdkHome(root))
+ .filter(root -> getVersion(root).getMaxLanguageLevel().isAtLeast(langLevel))
+ .min(Comparator.comparing(o -> getVersion(o).getMaxLanguageLevel()))
+ .orElse(null);
}
- @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) {
+ private static JavaSdkVersion getVersion(String jdkRoot) {
String version = JavaSdk.getInstance().getVersionString(jdkRoot);
if (version == null) {
return JavaSdkVersion.JDK_1_0;
@@ -195,11 +164,10 @@
}
@Nullable
- public static Sdk createJdk(@NotNull String jdkHomePath) {
- Sdk jdk = createAndAddSDK(jdkHomePath, JavaSdk.getInstance());
+ private static Sdk createJdk(String jdkHomePath) {
+ Sdk jdk = SdkConfigurationUtil.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);
+ logger.error(String.format("Unable to create JDK from path '%1$s'", jdkHomePath));
}
return jdk;
}
diff --git a/java/src/com/google/idea/blaze/java/sync/source/PackageManifestReader.java b/java/src/com/google/idea/blaze/java/sync/source/PackageManifestReader.java
index cadef54..1ba5835 100644
--- a/java/src/com/google/idea/blaze/java/sync/source/PackageManifestReader.java
+++ b/java/src/com/google/idea/blaze/java/sync/source/PackageManifestReader.java
@@ -30,9 +30,9 @@
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;
-import com.google.repackaged.devtools.build.lib.ideinfo.androidstudio.PackageManifestOuterClass.JavaSourcePackage;
-import com.google.repackaged.devtools.build.lib.ideinfo.androidstudio.PackageManifestOuterClass.PackageManifest;
+import com.google.repackaged.devtools.intellij.ideinfo.IntellijIdeInfo;
+import com.google.repackaged.devtools.intellij.ideinfo.IntellijIdeInfo.JavaSourcePackage;
+import com.google.repackaged.devtools.intellij.ideinfo.IntellijIdeInfo.PackageManifest;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
@@ -78,7 +78,7 @@
FileDiffer.updateFiles(fileDiffState, fileToLabelMap.keySet(), updatedFiles, removedFiles);
ListenableFuture<?> fetchFuture =
- PrefetchService.getInstance().prefetchFiles(project, updatedFiles);
+ PrefetchService.getInstance().prefetchFiles(project, updatedFiles, true);
if (!FutureUtil.waitForFuture(context, fetchFuture)
.timed("FetchPackageManifests")
.withProgressMessage("Reading package manifests...")
@@ -132,7 +132,7 @@
}
}
- private static ArtifactLocation fromProto(PackageManifestOuterClass.ArtifactLocation location) {
+ private static ArtifactLocation fromProto(IntellijIdeInfo.ArtifactLocation location) {
String relativePath = location.getRelativePath();
String rootExecutionPathFragment = location.getRootExecutionPathFragment();
if (!location.getIsNewExternalVersion() && location.getIsExternal()) {
diff --git a/java/src/com/google/idea/blaze/java/sync/source/SourceDirectoryCalculator.java b/java/src/com/google/idea/blaze/java/sync/source/SourceDirectoryCalculator.java
index 14afec5..ff8f5f0 100644
--- a/java/src/com/google/idea/blaze/java/sync/source/SourceDirectoryCalculator.java
+++ b/java/src/com/google/idea/blaze/java/sync/source/SourceDirectoryCalculator.java
@@ -20,7 +20,6 @@
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;
@@ -51,6 +50,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -258,19 +258,21 @@
}
// Sort source roots into their respective directories
- Multimap<WorkspacePath, SourceRoot> sourceDirectoryToSourceRoots = HashMultimap.create();
+ Map<WorkspacePath, Multiset<SourceRoot>> sourceDirectoryToSourceRoots = new HashMap<>();
for (SourceRoot sourceRoot : sourceRootsPerFile) {
- sourceDirectoryToSourceRoots.put(sourceRoot.workspacePath, sourceRoot);
+ sourceDirectoryToSourceRoots
+ .computeIfAbsent(sourceRoot.workspacePath, k -> HashMultiset.create())
+ .add(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<SourceRoot> sources = sourceDirectoryToSourceRoots.get(workspacePath);
Multiset<String> packages = HashMultiset.create();
- for (SourceRoot source : sources) {
- packages.add(source.packagePrefix);
+ for (Multiset.Entry<SourceRoot> entry : sources.entrySet()) {
+ packages.setCount(entry.getElement().packagePrefix, entry.getCount());
}
final String directoryPackagePrefix;
diff --git a/java/src/com/google/idea/blaze/java/syncstatus/BlazeJavaSyncStatusEditorTabColorProvider.java b/java/src/com/google/idea/blaze/java/syncstatus/BlazeJavaSyncStatusEditorTabColorProvider.java
index 26b97fa..7d76ce7 100644
--- a/java/src/com/google/idea/blaze/java/syncstatus/BlazeJavaSyncStatusEditorTabColorProvider.java
+++ b/java/src/com/google/idea/blaze/java/syncstatus/BlazeJavaSyncStatusEditorTabColorProvider.java
@@ -21,7 +21,6 @@
import com.intellij.ui.JBColor;
import java.awt.Color;
import javax.annotation.Nullable;
-import org.jetbrains.annotations.NotNull;
/** Changes the color for unsynced files. */
public class BlazeJavaSyncStatusEditorTabColorProvider implements EditorTabColorProvider {
@@ -30,7 +29,7 @@
@Nullable
@Override
- public Color getEditorTabColor(@NotNull Project project, @NotNull VirtualFile file) {
+ public Color getEditorTabColor(Project project, VirtualFile file) {
if (file.getName().endsWith(".java") && SyncStatusHelper.isUnsynced(project, file)) {
return UNSYNCED_COLOR;
}
diff --git a/java/src/com/google/idea/blaze/java/syncstatus/SyncStatusHelper.java b/java/src/com/google/idea/blaze/java/syncstatus/SyncStatusHelper.java
index c79888d..5692558 100644
--- a/java/src/com/google/idea/blaze/java/syncstatus/SyncStatusHelper.java
+++ b/java/src/com/google/idea/blaze/java/syncstatus/SyncStatusHelper.java
@@ -27,6 +27,7 @@
import java.util.Set;
class SyncStatusHelper {
+ private SyncStatusHelper() {}
static boolean isUnsynced(Project project, VirtualFile virtualFile) {
if (!virtualFile.isInLocalFileSystem()) {
diff --git a/java/src/com/google/idea/blaze/java/wizard2/BlazeEditProjectViewImportWizardStep.java b/java/src/com/google/idea/blaze/java/wizard2/BlazeEditProjectViewImportWizardStep.java
index 3d57300..109d9e8 100644
--- a/java/src/com/google/idea/blaze/java/wizard2/BlazeEditProjectViewImportWizardStep.java
+++ b/java/src/com/google/idea/blaze/java/wizard2/BlazeEditProjectViewImportWizardStep.java
@@ -64,7 +64,8 @@
public boolean validate() throws ConfigurationException {
BlazeValidationResult validationResult = control.validate();
if (validationResult.error != null) {
- throw new ConfigurationException(validationResult.error.getError());
+ throw new ConfigurationException(
+ "<html><body>" + validationResult.error.getError() + "</body></html>");
}
return validationResult.success;
}
diff --git a/java/src/com/google/idea/blaze/java/wizard2/BlazeSelectWorkspaceImportWizardStep.java b/java/src/com/google/idea/blaze/java/wizard2/BlazeSelectWorkspaceImportWizardStep.java
index b5b5b6a..d91566a 100644
--- a/java/src/com/google/idea/blaze/java/wizard2/BlazeSelectWorkspaceImportWizardStep.java
+++ b/java/src/com/google/idea/blaze/java/wizard2/BlazeSelectWorkspaceImportWizardStep.java
@@ -50,7 +50,8 @@
}
private void init() {
- control = new BlazeSelectWorkspaceControl(getProjectBuilder());
+ control =
+ new BlazeSelectWorkspaceControl(getProjectBuilder(), getWizardContext().getDisposable());
this.component.add(control.getUiComponent());
settingsInitialised = true;
}
diff --git a/java/tests/integrationtests/com/google/idea/blaze/java/run/CombinedTestHeuristicTest.java b/java/tests/integrationtests/com/google/idea/blaze/java/run/CombinedTestHeuristicTest.java
new file mode 100644
index 0000000..3bf8255
--- /dev/null
+++ b/java/tests/integrationtests/com/google/idea/blaze/java/run/CombinedTestHeuristicTest.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.java.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.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TestIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TestIdeInfo.TestSize;
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.model.MockBlazeProjectDataBuilder;
+import com.google.idea.blaze.base.model.MockBlazeProjectDataManager;
+import com.google.idea.blaze.base.model.primitives.Label;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
+import com.google.idea.blaze.base.run.TestTargetHeuristic;
+import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
+import com.intellij.psi.PsiFile;
+import java.io.File;
+import java.util.Collection;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Integration tests for {@link TestTargetHeuristic} combinations. */
+@RunWith(JUnit4.class)
+public class CombinedTestHeuristicTest extends BlazeIntegrationTestCase {
+
+ @Before
+ public final void doSetup() {
+ BlazeProjectData blazeProjectData = MockBlazeProjectDataBuilder.builder(workspaceRoot).build();
+ registerProjectService(
+ BlazeProjectDataManager.class, new MockBlazeProjectDataManager(blazeProjectData));
+
+ // required for IntelliJ to recognize annotations, JUnit version, etc.
+ workspace.createPsiFile(
+ new WorkspacePath("org/junit/runner/RunWith.java"),
+ "package org.junit.runner;"
+ + "public @interface RunWith {"
+ + " Class<? extends Runner> value();"
+ + "}");
+ workspace.createPsiFile(
+ new WorkspacePath("org/junit/Test"), "package org.junit;", "public @interface Test {}");
+ workspace.createPsiFile(
+ new WorkspacePath("org/junit/runners/JUnit4"),
+ "package org.junit.runners;",
+ "public class JUnit4 {}");
+ }
+
+ @Test
+ public void testSizeAndJUnit4Combination() {
+ Collection<TargetIdeInfo> targets =
+ ImmutableList.of(
+ createTarget("//foo:SmallJUnit3Tests", TestSize.SMALL),
+ createTarget("//foo:MediumJUnit3Tests", TestSize.MEDIUM),
+ createTarget("//foo:LargeJUnit3Tests", TestSize.LARGE),
+ createTarget("//foo:SmallJUnit4Tests", TestSize.SMALL),
+ createTarget("//foo:MediumJUnit4Tests", TestSize.MEDIUM),
+ createTarget("//foo:LargeJUnit4Tests", TestSize.LARGE));
+
+ PsiFile psiFile =
+ workspace.createPsiFile(
+ new WorkspacePath("java/com/google/lib/JavaTest.java"),
+ "package com.google.lib;",
+ "import org.junit.Test;",
+ "import org.junit.runner.RunWith;",
+ "import org.junit.runners.JUnit4;",
+ "@RunWith(JUnit4.class)",
+ "public class JavaTest {",
+ " @Test",
+ " public void testMethod1() {}",
+ " @Test",
+ " public void testMethod2() {}",
+ "}");
+ File source = new File(psiFile.getVirtualFile().getPath());
+
+ Label match =
+ TestTargetHeuristic.chooseTestTargetForSourceFile(
+ getProject(), psiFile, source, targets, TestSize.LARGE);
+ assertThat(match).isEqualTo(Label.create("//foo:LargeJUnit4Tests"));
+
+ match =
+ TestTargetHeuristic.chooseTestTargetForSourceFile(
+ getProject(), psiFile, source, targets, TestSize.MEDIUM);
+ assertThat(match).isEqualTo(Label.create("//foo:MediumJUnit4Tests"));
+ }
+
+ private static TargetIdeInfo createTarget(String label, TestSize size) {
+ return TargetIdeInfo.builder()
+ .setLabel(label)
+ .setKind("java_test")
+ .setTestInfo(TestIdeInfo.builder().setTestSize(size))
+ .build();
+ }
+}
diff --git a/java/tests/integrationtests/com/google/idea/blaze/java/run/JUnitTestHeuristicTest.java b/java/tests/integrationtests/com/google/idea/blaze/java/run/JUnitTestHeuristicTest.java
index 5470d05..7243dbf 100644
--- a/java/tests/integrationtests/com/google/idea/blaze/java/run/JUnitTestHeuristicTest.java
+++ b/java/tests/integrationtests/com/google/idea/blaze/java/run/JUnitTestHeuristicTest.java
@@ -99,7 +99,7 @@
PsiFile psiFile = workspace.createPsiFile(new WorkspacePath("foo/script_test.py"));
File file = new File(psiFile.getVirtualFile().getPath());
TargetIdeInfo target =
- TargetIdeInfo.builder().setLabel("//foo:unrelatedName").setKind("python_test").build();
+ TargetIdeInfo.builder().setLabel("//foo:unrelatedName").setKind("py_test").build();
assertThat(new JUnitTestHeuristic().matchesSource(getProject(), target, psiFile, file, null))
.isFalse();
}
@@ -108,7 +108,7 @@
public void testNullPsiFileDoesNotMatch() {
File file = new File("foo/script_test.py");
TargetIdeInfo target =
- TargetIdeInfo.builder().setLabel("//foo:unrelatedName").setKind("python_test").build();
+ TargetIdeInfo.builder().setLabel("//foo:unrelatedName").setKind("py_test").build();
assertThat(new JUnitTestHeuristic().matchesSource(getProject(), target, null, file, null))
.isFalse();
}
diff --git a/java/tests/integrationtests/com/google/idea/blaze/java/run/QualifiedClassNameHeuristicTest.java b/java/tests/integrationtests/com/google/idea/blaze/java/run/QualifiedClassNameHeuristicTest.java
new file mode 100644
index 0000000..8b781b9
--- /dev/null
+++ b/java/tests/integrationtests/com/google/idea/blaze/java/run/QualifiedClassNameHeuristicTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.idea.blaze.base.BlazeIntegrationTestCase;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
+import com.intellij.psi.PsiFile;
+import java.io.File;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Integration tests for {@link QualifiedClassNameHeuristic}. */
+@RunWith(JUnit4.class)
+public class QualifiedClassNameHeuristicTest extends BlazeIntegrationTestCase {
+
+ @Test
+ public void testMatchesQualifiedClassNames() {
+ PsiFile psiFile =
+ workspace.createPsiFile(
+ new WorkspacePath("com/google/lib/JavaClass.java"),
+ "package com.google.lib;",
+ "public class JavaClass {}");
+ File file = new File(psiFile.getVirtualFile().getPath());
+
+ TargetIdeInfo target =
+ TargetIdeInfo.builder().setLabel("//foo:lib.JavaClass").setKind("java_test").build();
+ assertThat(
+ new QualifiedClassNameHeuristic()
+ .matchesSource(getProject(), target, psiFile, file, null))
+ .isTrue();
+
+ target =
+ TargetIdeInfo.builder().setLabel("//foo:google.lib.JavaClass").setKind("java_test").build();
+ assertThat(
+ new QualifiedClassNameHeuristic()
+ .matchesSource(getProject(), target, psiFile, file, null))
+ .isTrue();
+
+ target =
+ TargetIdeInfo.builder()
+ .setLabel("//foo:com.google.lib.JavaClass")
+ .setKind("java_test")
+ .build();
+ assertThat(
+ new QualifiedClassNameHeuristic()
+ .matchesSource(getProject(), target, psiFile, file, null))
+ .isTrue();
+ }
+
+ @Test
+ public void testDoesNotMatchUnqualifiedClassName() {
+ PsiFile psiFile =
+ workspace.createPsiFile(
+ new WorkspacePath("com/google/lib/JavaClass.java"),
+ "package com.google.lib;",
+ "public class JavaClass {}");
+ File file = new File(psiFile.getVirtualFile().getPath());
+
+ TargetIdeInfo target =
+ TargetIdeInfo.builder().setLabel("//foo:JavaClass").setKind("java_test").build();
+ assertThat(
+ new QualifiedClassNameHeuristic()
+ .matchesSource(getProject(), target, psiFile, file, null))
+ .isFalse();
+ }
+
+ @Test
+ public void testDoesNotMatchQualifiedClassNameWithAdditionalPathPrefix() {
+ PsiFile psiFile =
+ workspace.createPsiFile(
+ new WorkspacePath("com/google/lib/JavaClass.java"),
+ "package com.google.lib;",
+ "public class JavaClass {}");
+ File file = new File(psiFile.getVirtualFile().getPath());
+
+ TargetIdeInfo target =
+ TargetIdeInfo.builder()
+ .setLabel("//foo:foo.com.google.lib.JavaClass")
+ .setKind("java_test")
+ .build();
+ assertThat(
+ new QualifiedClassNameHeuristic()
+ .matchesSource(getProject(), target, psiFile, file, null))
+ .isFalse();
+ }
+}
diff --git a/java/tests/integrationtests/com/google/idea/blaze/java/run/producers/BlazeJavaTestClassConfigurationProducerTest.java b/java/tests/integrationtests/com/google/idea/blaze/java/run/producers/BlazeJavaTestClassConfigurationProducerTest.java
index 6c48f2f..5101f07 100644
--- a/java/tests/integrationtests/com/google/idea/blaze/java/run/producers/BlazeJavaTestClassConfigurationProducerTest.java
+++ b/java/tests/integrationtests/com/google/idea/blaze/java/run/producers/BlazeJavaTestClassConfigurationProducerTest.java
@@ -18,20 +18,24 @@
import static com.google.common.truth.Truth.assertThat;
import com.google.idea.blaze.base.command.BlazeCommandName;
+import com.google.idea.blaze.base.command.BlazeFlags;
import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
import com.google.idea.blaze.base.ideinfo.TargetMapBuilder;
import com.google.idea.blaze.base.model.MockBlazeProjectDataBuilder;
import com.google.idea.blaze.base.model.MockBlazeProjectDataManager;
+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.run.BlazeCommandRunConfiguration;
import com.google.idea.blaze.base.run.producer.BlazeRunConfigurationProducerTestCase;
+import com.google.idea.blaze.base.run.state.BlazeCommandRunConfigurationCommonState;
import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
import com.intellij.execution.actions.ConfigurationContext;
import com.intellij.execution.actions.ConfigurationFromContext;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiClassOwner;
import com.intellij.psi.PsiFile;
+import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -185,4 +189,116 @@
assertThat(config.getName()).isEqualTo("Blaze test OuterClass");
assertThat(getCommandType(config)).isEqualTo(BlazeCommandName.TEST);
}
+
+ @Test
+ public void testConfigFromContextRecognizesItsOwnConfig() {
+ PsiFile javaFile =
+ createAndIndexFile(
+ new WorkspacePath("java/com/google/test/TestClass.java"),
+ "package com.google.test;",
+ "@org.junit.runner.RunWith(org.junit.runners.JUnit4.class)",
+ "public class TestClass {",
+ " @org.junit.Test",
+ " public void testMethod() {}",
+ "}");
+
+ MockBlazeProjectDataBuilder builder = MockBlazeProjectDataBuilder.builder(workspaceRoot);
+ builder.setTargetMap(
+ TargetMapBuilder.builder()
+ .addTarget(
+ TargetIdeInfo.builder()
+ .setKind("java_test")
+ .setLabel("//java/com/google/test:TestClass")
+ .addSource(sourceRoot("java/com/google/test/TestClass.java"))
+ .build())
+ .build());
+ registerProjectService(
+ BlazeProjectDataManager.class, new MockBlazeProjectDataManager(builder.build()));
+
+ ConfigurationContext context = createContextFromPsi(javaFile);
+ BlazeCommandRunConfiguration config =
+ (BlazeCommandRunConfiguration) context.getConfiguration().getConfiguration();
+ assertThat(config).isNotNull();
+
+ assertThat(new BlazeJavaTestClassConfigurationProducer().doIsConfigFromContext(config, context))
+ .isTrue();
+ }
+
+ @Test
+ public void testConfigWithDifferentLabelIgnored() {
+ PsiFile javaFile =
+ createAndIndexFile(
+ new WorkspacePath("java/com/google/test/TestClass.java"),
+ "package com.google.test;",
+ "@org.junit.runner.RunWith(org.junit.runners.JUnit4.class)",
+ "public class TestClass {",
+ " @org.junit.Test",
+ " public void testMethod() {}",
+ "}");
+
+ MockBlazeProjectDataBuilder builder = MockBlazeProjectDataBuilder.builder(workspaceRoot);
+ builder.setTargetMap(
+ TargetMapBuilder.builder()
+ .addTarget(
+ TargetIdeInfo.builder()
+ .setKind("java_test")
+ .setLabel("//java/com/google/test:TestClass")
+ .addSource(sourceRoot("java/com/google/test/TestClass.java"))
+ .build())
+ .build());
+ registerProjectService(
+ BlazeProjectDataManager.class, new MockBlazeProjectDataManager(builder.build()));
+
+ ConfigurationContext context = createContextFromPsi(javaFile);
+ BlazeCommandRunConfiguration config =
+ (BlazeCommandRunConfiguration) context.getConfiguration().getConfiguration();
+ assertThat(config).isNotNull();
+
+ // modify the label, and check that is enough for the producer to class it as different.
+ config.setTarget(Label.create("//java/com/google/test:TestClass2"));
+
+ assertThat(new BlazeJavaTestClassConfigurationProducer().doIsConfigFromContext(config, context))
+ .isFalse();
+ }
+
+ @Test
+ public void testConfigWithDifferentFilterIgnored() {
+ PsiFile javaFile =
+ createAndIndexFile(
+ new WorkspacePath("java/com/google/test/TestClass.java"),
+ "package com.google.test;",
+ "@org.junit.runner.RunWith(org.junit.runners.JUnit4.class)",
+ "public class TestClass {",
+ " @org.junit.Test",
+ " public void testMethod() {}",
+ "}");
+
+ MockBlazeProjectDataBuilder builder = MockBlazeProjectDataBuilder.builder(workspaceRoot);
+ builder.setTargetMap(
+ TargetMapBuilder.builder()
+ .addTarget(
+ TargetIdeInfo.builder()
+ .setKind("java_test")
+ .setLabel("//java/com/google/test:TestClass")
+ .addSource(sourceRoot("java/com/google/test/TestClass.java"))
+ .build())
+ .build());
+ registerProjectService(
+ BlazeProjectDataManager.class, new MockBlazeProjectDataManager(builder.build()));
+
+ ConfigurationContext context = createContextFromPsi(javaFile);
+ BlazeCommandRunConfiguration config =
+ (BlazeCommandRunConfiguration) context.getConfiguration().getConfiguration();
+ BlazeCommandRunConfigurationCommonState handlerState =
+ config.getHandlerStateIfType(BlazeCommandRunConfigurationCommonState.class);
+
+ // modify the test filter, and check that is enough for the producer to class it as different.
+ List<String> flags = new ArrayList<>(handlerState.getBlazeFlagsState().getRawFlags());
+ flags.removeIf((flag) -> flag.startsWith(BlazeFlags.TEST_FILTER));
+ flags.add(BlazeFlags.TEST_FILTER + "=com.google.test.OtherTestClass#");
+ handlerState.getBlazeFlagsState().setRawFlags(flags);
+
+ assertThat(new BlazeJavaTestClassConfigurationProducer().doIsConfigFromContext(config, context))
+ .isFalse();
+ }
}
diff --git a/java/tests/integrationtests/com/google/idea/blaze/java/run/producers/BlazeJavaTestMethodConfigurationProducerTest.java b/java/tests/integrationtests/com/google/idea/blaze/java/run/producers/BlazeJavaTestMethodConfigurationProducerTest.java
index aa7d2a2..439ba6a 100644
--- a/java/tests/integrationtests/com/google/idea/blaze/java/run/producers/BlazeJavaTestMethodConfigurationProducerTest.java
+++ b/java/tests/integrationtests/com/google/idea/blaze/java/run/producers/BlazeJavaTestMethodConfigurationProducerTest.java
@@ -24,6 +24,7 @@
import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
import com.google.idea.blaze.base.model.MockBlazeProjectDataBuilder;
import com.google.idea.blaze.base.model.MockBlazeProjectDataManager;
+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.run.BlazeCommandRunConfiguration;
@@ -34,6 +35,7 @@
import com.intellij.execution.actions.ConfigurationFromContext;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiMethod;
+import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -46,6 +48,116 @@
@Test
public void testProducedFromPsiMethod() {
+ // Arrange
+ PsiFile javaFile =
+ createAndIndexFile(
+ new WorkspacePath("java/com/google/test/TestClass.java"),
+ "package com.google.test;",
+ "@org.junit.runner.RunWith(org.junit.runners.JUnit4.class)",
+ "public class TestClass {",
+ " @org.junit.Test",
+ " public void testMethod1() {}",
+ "}");
+
+ MockBlazeProjectDataBuilder builder = MockBlazeProjectDataBuilder.builder(workspaceRoot);
+ builder.setTargetMap(
+ TargetMapBuilder.builder()
+ .addTarget(
+ TargetIdeInfo.builder()
+ .setKind("java_test")
+ .setLabel("//java/com/google/test:TestClass")
+ .addSource(sourceRoot("java/com/google/test/TestClass.java"))
+ .build())
+ .build());
+ registerProjectService(
+ BlazeProjectDataManager.class, new MockBlazeProjectDataManager(builder.build()));
+ PsiMethod method = PsiUtils.findFirstChildOfClassRecursive(javaFile, PsiMethod.class);
+
+ // Act
+ ConfigurationContext context = createContextFromPsi(method);
+ List<ConfigurationFromContext> configurations = context.getConfigurationsFromContext();
+ ConfigurationFromContext fromContext = configurations.get(0);
+
+ // Assert
+ assertThat(configurations).hasSize(1);
+ assertThat(fromContext.isProducedBy(BlazeJavaTestMethodConfigurationProducer.class)).isTrue();
+ assertThat(fromContext.getConfiguration()).isInstanceOf(BlazeCommandRunConfiguration.class);
+
+ BlazeCommandRunConfiguration config =
+ (BlazeCommandRunConfiguration) fromContext.getConfiguration();
+ assertThat(config.getTarget())
+ .isEqualTo(TargetExpression.fromString("//java/com/google/test:TestClass"));
+ assertThat(getTestFilterContents(config))
+ .isEqualTo("--test_filter=com.google.test.TestClass#testMethod1$");
+ assertThat(config.getName()).isEqualTo("Blaze test TestClass.testMethod1");
+ assertThat(getCommandType(config)).isEqualTo(BlazeCommandName.TEST);
+
+ BlazeCommandRunConfigurationCommonState state =
+ config.getHandlerStateIfType(BlazeCommandRunConfigurationCommonState.class);
+ assertThat(state.getBlazeFlagsState().getRawFlags()).contains(BlazeFlags.DISABLE_TEST_SHARDING);
+ }
+
+ @Test
+ public void testConfigFromContextRecognizesItsOwnConfig() {
+ PsiMethod method = setupGenericJunitTestClassAndBlazeTarget();
+ ConfigurationContext context = createContextFromPsi(method);
+ BlazeCommandRunConfiguration config =
+ (BlazeCommandRunConfiguration) context.getConfiguration().getConfiguration();
+
+ boolean isConfigFromContext =
+ new BlazeJavaTestMethodConfigurationProducer().doIsConfigFromContext(config, context);
+
+ assertThat(isConfigFromContext).isTrue();
+ }
+
+ @Test
+ public void testConfigWithDifferentLabelIsIgnored() {
+ // Arrange
+ PsiMethod method = setupGenericJunitTestClassAndBlazeTarget();
+ ConfigurationContext context = createContextFromPsi(method);
+ BlazeCommandRunConfiguration config =
+ (BlazeCommandRunConfiguration) context.getConfiguration().getConfiguration();
+ // modify the label, and check that is enough for the producer to class it as different.
+ config.setTarget(Label.create("//java/com/google/test:DifferentTestTarget"));
+
+ // Act
+ boolean isConfigFromContext =
+ new BlazeJavaTestMethodConfigurationProducer().doIsConfigFromContext(config, context);
+
+ // Assert
+ assertThat(isConfigFromContext).isFalse();
+ }
+
+ @Test
+ public void testConfigWithDifferentFilterIgnored() {
+ // Arrange
+ PsiMethod method = setupGenericJunitTestClassAndBlazeTarget();
+ ConfigurationContext context = createContextFromPsi(method);
+ BlazeCommandRunConfiguration config =
+ (BlazeCommandRunConfiguration) context.getConfiguration().getConfiguration();
+ BlazeCommandRunConfigurationCommonState handlerState =
+ config.getHandlerStateIfType(BlazeCommandRunConfigurationCommonState.class);
+
+ // modify the test filter, and check that is enough for the producer to class it as different.
+ List<String> flags = new ArrayList<>(handlerState.getBlazeFlagsState().getRawFlags());
+ flags.removeIf((flag) -> flag.startsWith(BlazeFlags.TEST_FILTER));
+ flags.add(BlazeFlags.TEST_FILTER + "=com.google.test.DifferentTestClass#");
+ handlerState.getBlazeFlagsState().setRawFlags(flags);
+
+ // Act
+ boolean isConfigFromContext =
+ new BlazeJavaTestMethodConfigurationProducer().doIsConfigFromContext(config, context);
+
+ // Assert
+ assertThat(isConfigFromContext).isFalse();
+ }
+
+ /**
+ * Creates a JUnit test class and associated blaze target, and returns a PsiMethod from that
+ * class. Used when the implementation details (class name, target string, etc.) aren't relevant
+ * to the test.
+ */
+ private PsiMethod setupGenericJunitTestClassAndBlazeTarget() {
PsiFile javaFile =
createAndIndexFile(
new WorkspacePath("java/com/google/test/TestClass.java"),
@@ -69,28 +181,6 @@
registerProjectService(
BlazeProjectDataManager.class, new MockBlazeProjectDataManager(builder.build()));
- PsiMethod method = PsiUtils.findFirstChildOfClassRecursive(javaFile, PsiMethod.class);
- assertThat(method).isNotNull();
-
- ConfigurationContext context = createContextFromPsi(method);
- List<ConfigurationFromContext> configurations = context.getConfigurationsFromContext();
- assertThat(configurations).hasSize(1);
-
- ConfigurationFromContext fromContext = configurations.get(0);
- assertThat(fromContext.isProducedBy(BlazeJavaTestMethodConfigurationProducer.class)).isTrue();
- assertThat(fromContext.getConfiguration()).isInstanceOf(BlazeCommandRunConfiguration.class);
-
- BlazeCommandRunConfiguration config =
- (BlazeCommandRunConfiguration) fromContext.getConfiguration();
- assertThat(config.getTarget())
- .isEqualTo(TargetExpression.fromString("//java/com/google/test:TestClass"));
- assertThat(getTestFilterContents(config))
- .isEqualTo("--test_filter=com.google.test.TestClass#testMethod1$");
- assertThat(config.getName()).isEqualTo("Blaze test TestClass.testMethod1");
- assertThat(getCommandType(config)).isEqualTo(BlazeCommandName.TEST);
-
- BlazeCommandRunConfigurationCommonState state =
- config.getHandlerStateIfType(BlazeCommandRunConfigurationCommonState.class);
- assertThat(state.getBlazeFlagsState().getRawFlags()).contains(BlazeFlags.DISABLE_TEST_SHARDING);
+ return PsiUtils.findFirstChildOfClassRecursive(javaFile, PsiMethod.class);
}
}
diff --git a/java/tests/integrationtests/com/google/idea/blaze/java/sync/projectstructure/JdksTest.java b/java/tests/integrationtests/com/google/idea/blaze/java/sync/projectstructure/JdksTest.java
new file mode 100644
index 0000000..aad4425
--- /dev/null
+++ b/java/tests/integrationtests/com/google/idea/blaze/java/sync/projectstructure/JdksTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.base.BlazeIntegrationTestCase;
+import com.intellij.openapi.application.ReadAction;
+import com.intellij.openapi.application.WriteAction;
+import com.intellij.openapi.projectRoots.JavaSdk;
+import com.intellij.openapi.projectRoots.ProjectJdkTable;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.pom.java.LanguageLevel;
+import com.intellij.testFramework.IdeaTestUtil;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Integration tests for {@link Jdks}. */
+@RunWith(JUnit4.class)
+public class JdksTest extends BlazeIntegrationTestCase {
+
+ @Test
+ public void testLowerJdkIgnored() {
+ setJdks(ImmutableList.of(IdeaTestUtil.getMockJdk14()));
+ assertThat(Jdks.findClosestMatch(LanguageLevel.JDK_1_7)).isNull();
+ }
+
+ @Test
+ public void testEqualJdkChosen() {
+ Sdk jdk7 = IdeaTestUtil.getMockJdk17();
+ setJdks(ImmutableList.of(jdk7));
+ assertThat(Jdks.findClosestMatch(LanguageLevel.JDK_1_7)).isEqualTo(jdk7);
+ }
+
+ @Test
+ public void testHigherJdkChosen() {
+ Sdk jdk8 = IdeaTestUtil.getMockJdk18();
+ setJdks(ImmutableList.of(jdk8));
+ assertThat(Jdks.findClosestMatch(LanguageLevel.JDK_1_7)).isEqualTo(jdk8);
+ }
+
+ @Test
+ public void testClosestJdkOfAtLeastSpecifiedLevelChosen() {
+ Sdk jdk7 = IdeaTestUtil.getMockJdk17();
+ // Ordering retained in final list; add jdk7 last to ensure first Jdk of at least the specified
+ // language level isn't automatically chosen.
+ setJdks(ImmutableList.of(IdeaTestUtil.getMockJdk18(), IdeaTestUtil.getMockJdk14(), jdk7));
+ assertThat(Jdks.findClosestMatch(LanguageLevel.JDK_1_6)).isEqualTo(jdk7);
+ }
+
+ private void setJdks(List<Sdk> jdks) {
+ List<Sdk> currentJdks =
+ ReadAction.compute(
+ () -> ProjectJdkTable.getInstance().getSdksOfType(JavaSdk.getInstance()));
+ WriteAction.run(
+ () -> {
+ currentJdks.forEach(jdk -> ProjectJdkTable.getInstance().removeJdk(jdk));
+ jdks.forEach(jdk -> ProjectJdkTable.getInstance().addJdk(jdk));
+ });
+ }
+}
diff --git a/java/tests/unittests/com/google/idea/blaze/java/run/BlazeJavaRunProfileStateTest.java b/java/tests/unittests/com/google/idea/blaze/java/run/BlazeJavaRunProfileStateTest.java
index 612b946..a2ff2e9 100644
--- a/java/tests/unittests/com/google/idea/blaze/java/run/BlazeJavaRunProfileStateTest.java
+++ b/java/tests/unittests/com/google/idea/blaze/java/run/BlazeJavaRunProfileStateTest.java
@@ -19,7 +19,6 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-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.bazel.BuildSystemProvider;
@@ -46,6 +45,7 @@
import com.intellij.openapi.extensions.impl.ExtensionPointImpl;
import com.intellij.openapi.project.Project;
import java.util.List;
+import java.util.function.Predicate;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/java/tests/unittests/com/google/idea/blaze/java/sync/importer/BlazeJavaWorkspaceImporterTest.java b/java/tests/unittests/com/google/idea/blaze/java/sync/importer/BlazeJavaWorkspaceImporterTest.java
index c69b66b..a2a73db 100644
--- a/java/tests/unittests/com/google/idea/blaze/java/sync/importer/BlazeJavaWorkspaceImporterTest.java
+++ b/java/tests/unittests/com/google/idea/blaze/java/sync/importer/BlazeJavaWorkspaceImporterTest.java
@@ -77,13 +77,11 @@
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.function.Predicate;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
-import org.jetbrains.annotations.NotNull;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -127,14 +125,11 @@
private JavaWorkingSet workingSet = null;
private final WorkspaceLanguageSettings workspaceLanguageSettings =
new WorkspaceLanguageSettings(WorkspaceType.JAVA, ImmutableSet.of(LanguageClass.JAVA));
- private MockExperimentService experimentService;
@Override
@SuppressWarnings("FunctionalInterfaceClash") // False positive on getDeclaredPackageOfJavaFile.
- protected void initTest(
- @NotNull Container applicationServices, @NotNull Container projectServices) {
- experimentService = new MockExperimentService();
- applicationServices.register(ExperimentService.class, experimentService);
+ protected void initTest(Container applicationServices, Container projectServices) {
+ applicationServices.register(ExperimentService.class, new MockExperimentService());
BlazeExecutor blazeExecutor = new MockBlazeExecutor();
applicationServices.register(BlazeExecutor.class, blazeExecutor);
@@ -167,7 +162,7 @@
.registerExtension(new JavaLikeLanguage.Java());
}
- BlazeJavaImportResult importWorkspace(
+ private BlazeJavaImportResult importWorkspace(
WorkspaceRoot workspaceRoot, TargetMapBuilder targetMapBuilder, ProjectView projectView) {
ProjectViewSet projectViewSet = ProjectViewSet.builder().add(projectView).build();
@@ -1229,7 +1224,7 @@
assertThat(findLibrary(result.libraries, "liba-ijar.jar")).isNotNull();
// Second test
- // Put everything in the working set, which should expand to the full transitive closure
+ // Put everything in the working set, which should expand to include the direct deps
workingSet =
new JavaWorkingSet(
workspaceRoot,
@@ -1242,13 +1237,9 @@
result = importWorkspace(workspaceRoot, targetMapBuilder, projectView);
errorCollector.assertNoIssues();
- assertThat(result.libraries).hasSize(6);
+ assertThat(result.libraries).hasSize(2);
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. */
@@ -1325,19 +1316,11 @@
@Test
public void testSyncAugmenter() {
augmenters.registerExtension(
- new BlazeJavaSyncAugmenter() {
- @Override
- public void addJarsForSourceTarget(
- WorkspaceLanguageSettings workspaceLanguageSettings,
- ProjectViewSet projectViewSet,
- TargetIdeInfo target,
- Collection<BlazeJarLibrary> jars,
- Collection<BlazeJarLibrary> genJars) {
- if (target.key.label.equals(Label.create("//java/example:source"))) {
- jars.add(
- new BlazeJarLibrary(
- LibraryArtifact.builder().setInterfaceJar(gen("source.jar")).build()));
- }
+ (workspaceLanguageSettings, projectViewSet, target, jars, genJars) -> {
+ if (target.key.label.equals(Label.create("//java/example:source"))) {
+ jars.add(
+ new BlazeJarLibrary(
+ LibraryArtifact.builder().setInterfaceJar(gen("source.jar")).build()));
}
});
diff --git a/java/tests/unittests/com/google/idea/blaze/java/sync/source/SourceDirectoryCalculatorTest.java b/java/tests/unittests/com/google/idea/blaze/java/sync/source/SourceDirectoryCalculatorTest.java
index 0538155..38dd84c 100644
--- a/java/tests/unittests/com/google/idea/blaze/java/sync/source/SourceDirectoryCalculatorTest.java
+++ b/java/tests/unittests/com/google/idea/blaze/java/sync/source/SourceDirectoryCalculatorTest.java
@@ -16,7 +16,6 @@
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;
@@ -29,6 +28,7 @@
import com.google.idea.blaze.base.ideinfo.TargetKey;
import com.google.idea.blaze.base.io.FileAttributeProvider;
import com.google.idea.blaze.base.io.InputStreamProvider;
+import com.google.idea.blaze.base.io.MockInputStreamProvider;
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;
@@ -44,18 +44,12 @@
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 com.google.repackaged.devtools.intellij.ideinfo.IntellijIdeInfo;
+import com.google.repackaged.devtools.intellij.ideinfo.IntellijIdeInfo.JavaSourcePackage;
+import com.google.repackaged.devtools.intellij.ideinfo.IntellijIdeInfo.PackageManifest;
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;
@@ -80,8 +74,7 @@
artifactLocation -> new File("/root", artifactLocation.getRelativePath());
@Override
- protected void initTest(
- @NotNull Container applicationServices, @NotNull Container projectServices) {
+ protected void initTest(Container applicationServices, Container projectServices) {
super.initTest(applicationServices, projectServices);
mockInputStreamProvider = new MockInputStreamProvider();
@@ -678,11 +671,15 @@
@Test
public void testCompetingPackageDeclarationPicksMajority() throws Exception {
mockInputStreamProvider
+ .addFile("/root/java/com/google/Foo.java", "package com.google;\n public class Foo {}")
.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 {}");
+ "/root/java/com/google/Bla.java", "package com.google.different;\n public class Bla {}")
+ .addFile(
+ "/root/java/com/google/Bla2.java",
+ "package com.google.different;\n public class Bla2 {}")
+ .addFile(
+ "/root/java/com/google/Bla3.java",
+ "package com.google.different;\n public class Bla3 {}");
List<SourceArtifact> sourceArtifacts =
ImmutableList.of(
SourceArtifact.builder(TargetKey.forPlainTarget(LABEL))
@@ -724,6 +721,46 @@
BlazeContentEntry.builder("/root/java/com/google")
.addSource(
BlazeSourceDirectory.builder("/root/java/com/google")
+ .setPackagePrefix("com.google.different")
+ .build())
+ .build());
+ }
+
+ @Test
+ public void testCompetingPackageDeclarationWithEqualCountsPicksDefault() throws Exception {
+ mockInputStreamProvider
+ .addFile(
+ "/root/java/com/google/Bla.java", "package com.google.different;\n public class Bla {}")
+ .addFile("/root/java/com/google/Foo.java", "package com.google;\n public class Foo {}");
+ List<SourceArtifact> sourceArtifacts =
+ ImmutableList.of(
+ SourceArtifact.builder(TargetKey.forPlainTarget(LABEL))
+ .setArtifactLocation(
+ ArtifactLocation.builder()
+ .setRelativePath("java/com/google/Foo.java")
+ .setIsSource(true))
+ .build(),
+ SourceArtifact.builder(TargetKey.forPlainTarget(LABEL))
+ .setArtifactLocation(
+ ArtifactLocation.builder()
+ .setRelativePath("java/com/google/Bla.java")
+ .setIsSource(true))
+ .build());
+ ImmutableList<BlazeContentEntry> result =
+ sourceDirectoryCalculator.calculateContentEntries(
+ project,
+ context,
+ workspaceRoot,
+ 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());
@@ -890,7 +927,7 @@
setNewFormatPackageManifest(
"/root/java/com/test.manifest",
ImmutableList.of(
- PackageManifestOuterClass.ArtifactLocation.newBuilder()
+ IntellijIdeInfo.ArtifactLocation.newBuilder()
.setRelativePath("java/com/google/Bla.java")
.setIsSource(true)
.build()),
@@ -1062,8 +1099,8 @@
PackageManifest.Builder manifest = PackageManifest.newBuilder();
for (int i = 0; i < sourceRelativePaths.size(); i++) {
String sourceRelativePath = sourceRelativePaths.get(i);
- PackageManifestOuterClass.ArtifactLocation source =
- PackageManifestOuterClass.ArtifactLocation.newBuilder()
+ IntellijIdeInfo.ArtifactLocation source =
+ IntellijIdeInfo.ArtifactLocation.newBuilder()
.setRelativePath(sourceRelativePath)
.setIsSource(true)
.build();
@@ -1076,9 +1113,7 @@
}
private void setNewFormatPackageManifest(
- String manifestPath,
- List<PackageManifestOuterClass.ArtifactLocation> sources,
- List<String> packages) {
+ String manifestPath, List<IntellijIdeInfo.ArtifactLocation> sources, List<String> packages) {
PackageManifest.Builder manifest = PackageManifest.newBuilder();
for (int i = 0; i < sources.size(); i++) {
manifest.addSources(
@@ -1098,35 +1133,6 @@
return new ArtifactLocationDecoderImpl(roots, new WorkspacePathResolverImpl(workspaceRoot));
}
- private static class MockInputStreamProvider implements InputStreamProvider {
-
- private final Map<String, InputStream> javaFiles = new HashMap<>();
-
- 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<TargetKey, Map<ArtifactLocation, String>> readPackageManifestFiles(
Map<TargetKey, ArtifactLocation> manifests, ArtifactLocationDecoder decoder) {
return PackageManifestReader.getInstance()
@@ -1136,7 +1142,7 @@
static class MockFileAttributeProvider extends FileAttributeProvider {
@Override
- public long getFileModifiedTime(@NotNull File file) {
+ public long getFileModifiedTime(File file) {
return 1;
}
}
diff --git a/plugin_dev/BUILD b/plugin_dev/BUILD
index 1586378..1bb2c41 100644
--- a/plugin_dev/BUILD
+++ b/plugin_dev/BUILD
@@ -1,5 +1,13 @@
licenses(["notice"]) # Apache 2.0
+load(
+ "//build_defs:build_defs.bzl",
+ "intellij_plugin",
+ "merged_plugin_xml",
+ "optional_plugin_xml",
+ "stamped_plugin_xml",
+)
+
java_library(
name = "plugin_dev",
srcs = glob(["src/**/*.java"]),
@@ -15,19 +23,24 @@
],
)
+optional_plugin_xml(
+ name = "optional_xml",
+ module = "DevKit",
+ plugin_xml = "src/META-INF/blaze-plugin-dev.xml",
+ visibility = ["//visibility:public"],
+)
+
+OPTIONAL_PLUGIN_XMLS = [
+ ":optional_xml",
+ "//java:optional_xml",
+]
+
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 = [
@@ -48,6 +61,7 @@
intellij_plugin(
name = "plugin_dev_integration_test_plugin",
testonly = 1,
+ optional_plugin_xmls = OPTIONAL_PLUGIN_XMLS,
plugin_xml = ":plugin_dev_plugin_xml",
deps = [
":plugin_dev",
@@ -64,7 +78,7 @@
intellij_integration_test_suite(
name = "integration_tests",
srcs = glob(["tests/integrationtests/**/*.java"]),
- required_plugins = "com.google.idea.blaze.ijwb",
+ required_plugins = "com.google.idea.blaze.plugin_dev,DevKit",
test_package_root = "com.google.idea.blaze.plugin",
runtime_deps = [
":plugin_dev_integration_test_plugin",
diff --git a/plugin_dev/src/META-INF/blaze-plugin-dev.xml b/plugin_dev/src/META-INF/blaze-plugin-dev.xml
index af9bc68..c3c4ac2 100644
--- a/plugin_dev/src/META-INF/blaze-plugin-dev.xml
+++ b/plugin_dev/src/META-INF/blaze-plugin-dev.xml
@@ -17,7 +17,7 @@
<depends>DevKit</depends>
<extensions defaultExtensionNs="com.google.idea.blaze">
- <RunConfigurationFactory implementation="com.google.idea.blaze.plugin.run.BlazeIntellijPluginConfigurationType$BlazeIntellijPluginRunConfigurationFactory"/>
+ <RunConfigurationFactory implementation="com.google.idea.blaze.plugin.run.BlazeIntellijPluginConfigurationType$BlazeIntellijPluginRunConfigurationFactory" order="first"/>
<SyncPlugin implementation="com.google.idea.blaze.plugin.sync.IntellijPluginSyncPlugin"/>
</extensions>
diff --git a/plugin_dev/src/com/google/idea/blaze/plugin/run/BlazeIntellijPluginConfiguration.java b/plugin_dev/src/com/google/idea/blaze/plugin/run/BlazeIntellijPluginConfiguration.java
index 3621e36..f743cc9 100644
--- a/plugin_dev/src/com/google/idea/blaze/plugin/run/BlazeIntellijPluginConfiguration.java
+++ b/plugin_dev/src/com/google/idea/blaze/plugin/run/BlazeIntellijPluginConfiguration.java
@@ -188,54 +188,52 @@
}
String buildNumber = IdeaJdkHelper.getBuildNumber(ideaJdk);
final BlazeIntellijPluginDeployer deployer =
- new BlazeIntellijPluginDeployer(getProject(), sandboxHome, buildNumber);
- deployer.addTarget(getTarget());
+ new BlazeIntellijPluginDeployer(sandboxHome, buildNumber, getTarget());
env.putUserData(BlazeIntellijPluginDeployer.USER_DATA_KEY, deployer);
// copy license from running instance of idea
IdeaJdkHelper.copyIDEALicense(sandboxHome);
- final JavaCommandLineState state =
- new JavaCommandLineState(env) {
- @Override
- protected JavaParameters createJavaParameters() throws ExecutionException {
- List<String> pluginIds = deployer.deploy();
+ return new JavaCommandLineState(env) {
+ @Override
+ protected JavaParameters createJavaParameters() throws ExecutionException {
+ List<String> pluginIds = deployer.deployNonBlocking();
- final JavaParameters params = new JavaParameters();
+ final JavaParameters params = new JavaParameters();
- ParametersList vm = params.getVMParametersList();
+ ParametersList vm = params.getVMParametersList();
- fillParameterList(vm, vmParameters);
- fillParameterList(params.getProgramParametersList(), programParameters);
+ fillParameterList(vm, vmParameters);
+ fillParameterList(params.getProgramParametersList(), programParameters);
- IntellijWithPluginClasspathHelper.addRequiredVmParams(params, ideaJdk);
+ IntellijWithPluginClasspathHelper.addRequiredVmParams(params, ideaJdk);
- vm.defineProperty(
- JetBrainsProtocolHandler.REQUIRED_PLUGINS_KEY, Joiner.on(',').join(pluginIds));
+ 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);
+ 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 {
+ deployer.blockUntilDeployComplete();
+ final OSProcessHandler handler = super.startProcess();
+ handler.addProcessListener(
+ new ProcessAdapter() {
+ @Override
+ public void processTerminated(ProcessEvent event) {
+ deployer.deleteDeployment();
}
- }
- return params;
- }
-
- @Override
- protected OSProcessHandler startProcess() throws ExecutionException {
- final OSProcessHandler handler = super.startProcess();
- handler.addProcessListener(
- new ProcessAdapter() {
- @Override
- public void processTerminated(ProcessEvent event) {
- deployer.deleteDeployment();
- }
- });
- return handler;
- }
- };
- return state;
+ });
+ return handler;
+ }
+ };
}
private static void fillParameterList(ParametersList list, @Nullable String value) {
diff --git a/plugin_dev/src/com/google/idea/blaze/plugin/run/BlazeIntellijPluginDeployer.java b/plugin_dev/src/com/google/idea/blaze/plugin/run/BlazeIntellijPluginDeployer.java
index 2c366f9..93e4854 100644
--- a/plugin_dev/src/com/google/idea/blaze/plugin/run/BlazeIntellijPluginDeployer.java
+++ b/plugin_dev/src/com/google/idea/blaze/plugin/run/BlazeIntellijPluginDeployer.java
@@ -21,23 +21,16 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import com.google.idea.blaze.base.async.executor.BlazeExecutor;
import com.google.idea.blaze.base.command.buildresult.BuildResultHelper;
-import com.google.idea.blaze.base.ideinfo.JavaIdeInfo;
-import com.google.idea.blaze.base.ideinfo.LibraryArtifact;
-import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
-import com.google.idea.blaze.base.ideinfo.TargetKey;
-import com.google.idea.blaze.base.ideinfo.TargetMap;
-import com.google.idea.blaze.base.model.BlazeProjectData;
import com.google.idea.blaze.base.model.primitives.Label;
-import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
-import com.google.idea.blaze.plugin.IntellijPluginRule;
import com.google.repackaged.devtools.intellij.plugin.IntellijPluginTargetDeployInfo.IntellijPluginDeployFile;
import com.google.repackaged.devtools.intellij.plugin.IntellijPluginTargetDeployInfo.IntellijPluginDeployInfo;
import com.google.repackaged.protobuf.TextFormat;
+import com.intellij.concurrency.AsyncUtil;
import com.intellij.execution.ExecutionException;
import com.intellij.ide.plugins.IdeaPluginDescriptor;
import com.intellij.ide.plugins.PluginManagerCore;
-import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.BuildNumber;
import com.intellij.openapi.util.Key;
import java.io.BufferedInputStream;
@@ -52,6 +45,7 @@
import java.util.Collection;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.Future;
import javax.annotation.Nullable;
/** Handles finding files to deploy and copying these into the sandbox. */
@@ -62,48 +56,35 @@
private final String sandboxHome;
private final String buildNumber;
- private final TargetMap targetMap;
- private final List<Label> targetsToDeploy = new ArrayList<>();
+ private final Label pluginTarget;
private final List<File> deployInfoFiles = new ArrayList<>();
private final Map<File, File> filesToDeploy = Maps.newHashMap();
private File executionRoot;
- BlazeIntellijPluginDeployer(Project project, String sandboxHome, String buildNumber)
- throws ExecutionException {
- BlazeProjectData blazeProjectData =
- BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
- if (blazeProjectData == null) {
- throw new ExecutionException("Not synced yet, please sync project");
- }
+ private Future<Void> fileCopyingTask;
+
+ BlazeIntellijPluginDeployer(String sandboxHome, String buildNumber, Label pluginTarget) {
this.sandboxHome = sandboxHome;
this.buildNumber = buildNumber;
- this.targetMap = blazeProjectData.targetMap;
- }
-
- /** Adds an intellij plugin target to deploy */
- void addTarget(Label label) throws ExecutionException {
- targetsToDeploy.add(label);
+ this.pluginTarget = pluginTarget;
}
void reportBuildComplete(File executionRoot, BuildResultHelper buildResultHelper) {
this.executionRoot = executionRoot;
- for (File file : buildResultHelper.getBuildArtifacts()) {
+ for (File file : buildResultHelper.getBuildArtifactsForTarget(pluginTarget)) {
if (file.getName().endsWith(".intellij-plugin-debug-target-deploy-info")) {
deployInfoFiles.add(file);
}
}
}
- List<String> deploy() throws ExecutionException {
+ /**
+ * Returns a list of plugin IDs, and asynchronously copies the corresponding files to the sandbox.
+ */
+ List<String> deployNonBlocking() throws ExecutionException {
List<IntellijPluginDeployInfo> deployInfoList = Lists.newArrayList();
- if (!deployInfoFiles.isEmpty()) {
- for (File deployInfoFile : deployInfoFiles) {
- deployInfoList.addAll(readDeployInfoFromFile(deployInfoFile));
- }
- } else {
- for (Label label : targetsToDeploy) {
- deployInfoList.addAll(findDeployInfoFromBareIntelliJPluginTargets(label));
- }
+ for (File deployInfoFile : deployInfoFiles) {
+ deployInfoList.addAll(readDeployInfoFromFile(deployInfoFile));
}
ImmutableMap<File, File> filesToDeploy = getFilesToDeploy(executionRoot, deployInfoList);
this.filesToDeploy.putAll(filesToDeploy);
@@ -114,11 +95,24 @@
String.format("Plugin file '%s' not found. Did the build fail?", file.getName()));
}
}
- List<String> pluginIds = readPluginIds(filesToDeploy.keySet());
- for (Map.Entry<File, File> entry : filesToDeploy.entrySet()) {
- copyFileToSandbox(entry.getKey(), entry.getValue());
- }
- return pluginIds;
+ // kick off file copying task asynchronously, so it doesn't block the EDT.
+ fileCopyingTask =
+ BlazeExecutor.getInstance()
+ .submit(
+ () -> {
+ for (Map.Entry<File, File> entry : filesToDeploy.entrySet()) {
+ copyFileToSandbox(entry.getKey(), entry.getValue());
+ }
+ return null;
+ });
+
+ return readPluginIds(filesToDeploy.keySet());
+ }
+
+ /** Blocks until the plugin files have been copied to the sandbox */
+ void blockUntilDeployComplete() {
+ AsyncUtil.get(fileCopyingTask);
+ fileCopyingTask = null;
}
void deleteDeployment() {
@@ -144,40 +138,6 @@
return result.build();
}
- private ImmutableList<IntellijPluginDeployInfo> findDeployInfoFromBareIntelliJPluginTargets(
- Label label) throws ExecutionException {
- TargetIdeInfo target = targetMap.get(TargetKey.forPlainTarget(label));
- if (target == null) {
- throw new ExecutionException("Target '" + label + "' not imported during sync");
- }
- if (IntellijPluginRule.isSinglePluginTarget(target)) {
- return ImmutableList.of(deployInfoForIntellijPlugin(target));
- }
- throw new ExecutionException("Target is not a supported intellij plugin type.");
- }
-
- private static IntellijPluginDeployInfo deployInfoForIntellijPlugin(TargetIdeInfo target)
- throws ExecutionException {
- JavaIdeInfo javaIdeInfo = target.javaIdeInfo;
- if (!IntellijPluginRule.isSinglePluginTarget(target) || javaIdeInfo == null) {
- throw new ExecutionException("Target '" + target + "' is not a valid intellij_plugin target");
- }
- Collection<LibraryArtifact> jars = javaIdeInfo.jars;
- if (javaIdeInfo.jars.size() > 1) {
- throw new ExecutionException("Invalid IntelliJ plugin target: it has multiple output jars");
- }
- LibraryArtifact artifact = jars.isEmpty() ? null : jars.iterator().next();
- if (artifact == null || artifact.classJar == null) {
- throw new ExecutionException("No output plugin jar found for '" + target + "'");
- }
- return IntellijPluginDeployInfo.newBuilder()
- .addDeployFiles(
- IntellijPluginDeployFile.newBuilder()
- .setExecutionPath(artifact.classJar.getExecutionRootRelativePath())
- .setDeployLocation(new File(artifact.classJar.relativePath).getName()))
- .build();
- }
-
private ImmutableMap<File, File> getFilesToDeploy(
File executionRoot, Collection<IntellijPluginDeployInfo> deployInfos) {
ImmutableMap.Builder<File, File> result = ImmutableMap.builder();
@@ -229,6 +189,7 @@
try {
dest.getParentFile().mkdirs();
Files.copy(src.toPath(), dest.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ dest.deleteOnExit();
} catch (IOException e) {
throw new ExecutionException("Error copying plugin file to sandbox", e);
}
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
index 3c778fe..e2b9fa0 100644
--- a/plugin_dev/src/com/google/idea/blaze/plugin/run/BuildPluginBeforeRunTaskProvider.java
+++ b/plugin_dev/src/com/google/idea/blaze/plugin/run/BuildPluginBeforeRunTaskProvider.java
@@ -22,12 +22,14 @@
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.BlazeInvocationContext;
import com.google.idea.blaze.base.command.buildresult.BuildResultHelper;
import com.google.idea.blaze.base.command.info.BlazeInfo;
import com.google.idea.blaze.base.command.info.BlazeInfoRunner;
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.model.BlazeProjectData;
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;
@@ -40,6 +42,8 @@
import com.google.idea.blaze.base.scope.scopes.IssuesScope;
import com.google.idea.blaze.base.settings.Blaze;
import com.google.idea.blaze.base.settings.BlazeUserSettings;
+import com.google.idea.blaze.base.settings.BlazeUserSettings.BlazeConsolePopupBehavior;
+import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
import com.google.idea.blaze.base.util.SaveUtil;
import com.intellij.execution.BeforeRunTask;
import com.intellij.execution.BeforeRunTaskProvider;
@@ -141,7 +145,10 @@
if (!canExecuteTask(configuration, task)) {
return false;
}
- boolean suppressConsole = BlazeUserSettings.getInstance().getSuppressConsoleForRunAction();
+ BlazeConsolePopupBehavior consolePopupBehavior =
+ BlazeUserSettings.getInstance().getSuppressConsoleForRunAction()
+ ? BlazeConsolePopupBehavior.NEVER
+ : BlazeConsolePopupBehavior.ALWAYS;
return Scope.root(
context -> {
context
@@ -149,7 +156,7 @@
.push(new IssuesScope(project))
.push(
new BlazeConsoleScope.Builder(project)
- .setSuppressConsole(suppressConsole)
+ .setPopupBehavior(consolePopupBehavior)
.build())
.push(new IdeaLogScope());
@@ -202,12 +209,24 @@
IssueOutput.error("Could not determine execution root").submit(context);
return;
}
+ BlazeProjectData blazeProjectData =
+ BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
+ if (blazeProjectData == null) {
+ IssueOutput.error("Could not determine execution root").submit(context);
+ return;
+ }
- BuildResultHelper buildResultHelper = BuildResultHelper.forFiles(f -> true);
+ BuildResultHelper buildResultHelper =
+ BuildResultHelper.forFiles(blazeProjectData.blazeVersionData, f -> true);
BlazeCommand command =
BlazeCommand.builder(binaryPath, BlazeCommandName.BUILD)
.addTargets(config.getTarget())
- .addBlazeFlags(BlazeFlags.buildFlags(project, projectViewSet))
+ .addBlazeFlags(
+ BlazeFlags.blazeFlags(
+ project,
+ projectViewSet,
+ BlazeCommandName.BUILD,
+ BlazeInvocationContext.RunConfiguration))
.addBlazeFlags(config.getBlazeFlagsState().getExpandedFlags())
.addExeFlags(config.getExeFlagsState().getExpandedFlags())
.addBlazeFlags(buildResultHelper.getBuildFlags())
diff --git a/plugin_dev/src/com/google/idea/blaze/plugin/sync/IntellijPluginSyncPlugin.java b/plugin_dev/src/com/google/idea/blaze/plugin/sync/IntellijPluginSyncPlugin.java
index e2eeb8e..fa13047 100644
--- a/plugin_dev/src/com/google/idea/blaze/plugin/sync/IntellijPluginSyncPlugin.java
+++ b/plugin_dev/src/com/google/idea/blaze/plugin/sync/IntellijPluginSyncPlugin.java
@@ -29,7 +29,6 @@
import com.google.idea.blaze.java.sync.model.BlazeJavaSyncData;
import com.google.idea.blaze.java.sync.projectstructure.JavaSourceFolderProvider;
import com.google.idea.sdkcompat.transactions.Transactions;
-import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.module.ModuleType;
import com.intellij.openapi.module.StdModuleTypes;
import com.intellij.openapi.project.Project;
@@ -92,14 +91,10 @@
projectViewSet, blazeProjectData, LanguageLevel.JDK_1_7);
// Leave the SDK, but set the language level
- Transactions.submitTransactionAndWait(
- () ->
- ApplicationManager.getApplication()
- .runWriteAction(
- () -> {
- LanguageLevelProjectExtension ext =
- LanguageLevelProjectExtension.getInstance(project);
- ext.setLanguageLevel(javaLanguageLevel);
- }));
+ Transactions.submitWriteActionTransactionAndWait(
+ () -> {
+ LanguageLevelProjectExtension ext = LanguageLevelProjectExtension.getInstance(project);
+ ext.setLanguageLevel(javaLanguageLevel);
+ });
}
}
diff --git a/proto/BUILD b/proto/BUILD
index f90fbfa..1666e1c 100644
--- a/proto/BUILD
+++ b/proto/BUILD
@@ -10,3 +10,15 @@
jars = ["proto_deps.jar"],
visibility = ["//visibility:public"],
)
+
+proto_library(
+ name = "intellij_ide_info_proto",
+ srcs = ["intellij_ide_info.proto"],
+ visibility = ["//visibility:public"],
+)
+
+java_proto_library(
+ name = "intellij_ide_info_java_proto",
+ visibility = ["//visibility:public"],
+ deps = [":intellij_ide_info_proto"],
+)
diff --git a/proto/intellij_ide_info.proto b/proto/intellij_ide_info.proto
new file mode 100644
index 0000000..ad73c68
--- /dev/null
+++ b/proto/intellij_ide_info.proto
@@ -0,0 +1,207 @@
+// Copyright 2015 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+syntax = "proto3";
+
+package blaze;
+
+option java_api_version = 2;
+
+option java_package = "com.google.devtools.intellij.ideinfo";
+
+message ArtifactLocation {
+ string relative_path = 2;
+ bool is_source = 3;
+
+ // path from the execution root to the actual root:
+ // exec_root + root_execution_path_fragment + relative_path = absolute_path
+ string root_execution_path_fragment = 4;
+ // whether this artifact comes from an external repository (bazel only)
+ bool is_external = 5;
+
+ // The contents of relative_path and root_execution_path_fragment have changed
+ // for external workspaces. This is a temporary field to distinguish between
+ // the two versions.
+ bool is_new_external_version = 6;
+}
+
+message JavaSourcePackage {
+ string package_string = 2;
+ ArtifactLocation artifact_location = 3;
+}
+
+message PackageManifest {
+ repeated JavaSourcePackage sources = 1;
+}
+
+message LibraryArtifact {
+ ArtifactLocation jar = 1;
+ ArtifactLocation interface_jar = 2;
+ ArtifactLocation source_jar = 3;
+}
+
+message JavaIdeInfo {
+ repeated LibraryArtifact jars = 1;
+ repeated LibraryArtifact generated_jars = 2;
+ ArtifactLocation package_manifest = 3;
+ repeated ArtifactLocation sources = 4;
+ ArtifactLocation jdeps = 5;
+ LibraryArtifact filtered_gen_jar = 6;
+ string main_class = 7;
+}
+
+message CIdeInfo {
+ repeated ArtifactLocation source = 1;
+
+ repeated string transitive_include_directory = 3;
+ repeated string transitive_quote_include_directory = 4;
+ repeated string transitive_define = 5;
+ repeated string transitive_system_include_directory = 6;
+
+ repeated string target_copt = 7;
+ repeated string target_define = 8;
+ repeated string target_include = 9;
+ repeated ArtifactLocation header = 10;
+ repeated ArtifactLocation textual_header = 11;
+}
+
+message AndroidIdeInfo {
+ repeated ArtifactLocation resources = 1;
+ ArtifactLocation apk = 3;
+ repeated ArtifactLocation dependency_apk = 4;
+ ArtifactLocation manifest = 5;
+ string java_package = 7;
+ bool has_idl_sources = 8;
+ LibraryArtifact idl_jar = 9;
+ bool generate_resource_class = 10;
+ string legacy_resources = 11;
+ LibraryArtifact resource_jar = 12;
+ string idl_import_root = 13;
+}
+
+message AndroidSdkIdeInfo {
+ ArtifactLocation android_jar = 1;
+}
+
+message PyIdeInfo {
+ repeated ArtifactLocation sources = 1;
+}
+
+message GoIdeInfo {
+ repeated ArtifactLocation generated_sources = 1;
+}
+
+message JsIdeInfo {
+ repeated ArtifactLocation sources = 1;
+}
+
+message TsIdeInfo {
+ repeated ArtifactLocation sources = 1;
+}
+
+message CToolchainIdeInfo {
+ string target_name = 1;
+ repeated string base_compiler_option = 2;
+ repeated string cpp_option = 3;
+ repeated string c_option = 4;
+ string preprocessor_executable = 5;
+ string cpp_executable = 6;
+ repeated string link_option = 7;
+ repeated string built_in_include_directory = 8;
+ repeated string unfiltered_compiler_option = 9;
+}
+
+message ProtoLibraryLegacyJavaIdeInfo {
+ enum ApiFlavor {
+ NONE = 0;
+ IMMUTABLE = 1;
+ MUTABLE = 2;
+ BOTH = 3;
+ }
+
+ int32 api_version = 1;
+ ApiFlavor api_flavor = 2;
+ repeated LibraryArtifact jars1 = 3;
+ repeated LibraryArtifact jars_mutable = 4;
+ repeated LibraryArtifact jars_immutable = 5;
+}
+
+message TestInfo {
+ string size = 1;
+}
+
+message JavaToolchainIdeInfo {
+ string source_version = 1;
+ string target_version = 2;
+ ArtifactLocation javac_jar = 3;
+}
+
+message TargetKey {
+ string label = 1;
+ repeated string aspect_ids = 3;
+}
+
+message Dependency {
+ enum DependencyType {
+ COMPILE_TIME = 0;
+ RUNTIME = 1;
+ }
+
+ TargetKey target = 1;
+ DependencyType dependency_type = 2;
+}
+
+message TargetIdeInfo {
+ string label = 1 [deprecated = true];
+ repeated string dependencies = 4 [deprecated = true];
+
+ // kind is one of {JAVA,ANDROID}_{LIBRARY,BINARY,TEST} and JAVA_IMPORT
+ JavaIdeInfo java_ide_info = 7;
+ AndroidIdeInfo android_ide_info = 8;
+
+ repeated string tags = 9;
+ repeated string runtime_deps = 10 [deprecated = true];
+
+ ArtifactLocation build_file_artifact_location = 11;
+
+ CIdeInfo c_ide_info = 12;
+ CToolchainIdeInfo c_toolchain_ide_info = 13;
+
+ string kind_string = 14;
+
+ TestInfo test_info = 15;
+
+ ProtoLibraryLegacyJavaIdeInfo proto_library_legacy_java_ide_info = 16;
+ JavaToolchainIdeInfo java_toolchain_ide_info = 17;
+
+ PyIdeInfo py_ide_info = 18;
+
+ TargetKey key = 19;
+
+ repeated Dependency deps = 20;
+
+ reserved 21;
+
+ AndroidSdkIdeInfo android_sdk_ide_info = 22;
+
+ reserved 23;
+
+ repeated string features = 24;
+
+ GoIdeInfo go_ide_info = 25;
+ JsIdeInfo js_ide_info = 26;
+ TsIdeInfo ts_ide_info = 27;
+
+ // Next available: 28
+}
diff --git a/proto/proto_deps.jar b/proto/proto_deps.jar
index 2637779..5310fc0 100755
--- a/proto/proto_deps.jar
+++ b/proto/proto_deps.jar
Binary files differ
diff --git a/python/BUILD b/python/BUILD
index 51e2752..e2ac2dd 100644
--- a/python/BUILD
+++ b/python/BUILD
@@ -92,6 +92,7 @@
deps = [
":python",
"//base",
+ "//base:unit_test_utils",
"//intellij_platform_sdk:plugin_api_for_tests",
"@jsr305_annotations//jar",
"@junit//jar",
diff --git a/python/src/META-INF/python-contents.xml b/python/src/META-INF/python-contents.xml
index b2e1ab4..72b8b1f 100644
--- a/python/src/META-INF/python-contents.xml
+++ b/python/src/META-INF/python-contents.xml
@@ -19,8 +19,9 @@
<SyncPlugin implementation="com.google.idea.blaze.python.sync.BlazePythonSyncPlugin"/>
<PrefetchFileSource implementation="com.google.idea.blaze.python.sync.PythonPrefetchFileSource"/>
<BlazeCommandRunConfigurationHandlerProvider implementation="com.google.idea.blaze.python.run.BlazePyRunConfigurationHandlerProvider" order="first"/>
- <RunConfigurationFactory implementation="com.google.idea.blaze.python.run.BlazePyDebuggableRunConfigurationFactory"/>
<BlazeTestEventsHandler implementation="com.google.idea.blaze.python.run.smrunner.BlazePythonTestEventsHandler"/>
+ <PyParameterizedNameConverter implementation="com.google.idea.blaze.python.run.smrunner.PyBaseParameterizedNameConverter" order="first"/>
+ <BlazeIssueParserProvider implementation="com.google.idea.blaze.python.issueparser.PyIssueParserProvider"/>
</extensions>
<extensions defaultExtensionNs="com.intellij">
@@ -36,6 +37,8 @@
interface="com.google.idea.blaze.python.run.filter.BlazePyFilterProvider"/>
<extensionPoint qualifiedName="com.google.idea.blaze.BlazePyDebugFlagsProvider"
interface="com.google.idea.blaze.python.run.BlazePyDebugHelper"/>
+ <extensionPoint qualifiedName="com.google.idea.blaze.PyParameterizedNameConverter"
+ interface="com.google.idea.blaze.python.run.smrunner.PyParameterizedNameConverter"/>
</extensionPoints>
<project-components>
diff --git a/python/src/com/google/idea/blaze/python/issueparser/PyIssueParserProvider.java b/python/src/com/google/idea/blaze/python/issueparser/PyIssueParserProvider.java
new file mode 100644
index 0000000..a05aae4
--- /dev/null
+++ b/python/src/com/google/idea/blaze/python/issueparser/PyIssueParserProvider.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.python.issueparser;
+
+import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.base.issueparser.BlazeIssueParser.Parser;
+import com.google.idea.blaze.base.issueparser.BlazeIssueParser.SingleLineParser;
+import com.google.idea.blaze.base.issueparser.BlazeIssueParserProvider;
+import com.google.idea.blaze.base.scope.output.IssueOutput;
+import com.intellij.openapi.fileEditor.OpenFileDescriptor;
+import com.intellij.openapi.project.Project;
+import com.intellij.pom.Navigatable;
+import com.intellij.pom.NavigatableAdapter;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.search.FilenameIndex;
+import com.intellij.psi.search.GlobalSearchScope;
+import java.util.Arrays;
+import java.util.regex.Matcher;
+import javax.annotation.Nullable;
+
+/** Finds python-specific errors in blaze build output. */
+public class PyIssueParserProvider implements BlazeIssueParserProvider {
+
+ @Override
+ public ImmutableList<Parser> getIssueParsers(Project project) {
+ return ImmutableList.of(new PyTracebackIssueParser(project));
+ }
+
+ private static class PyTracebackIssueParser extends SingleLineParser {
+
+ final Project project;
+
+ PyTracebackIssueParser(Project project) {
+ super("File \"(.*?)\", line ([0-9]+), in (.*)");
+ this.project = project;
+ }
+
+ @Nullable
+ @Override
+ protected IssueOutput createIssue(Matcher matcher) {
+ String fileName = matcher.group(1);
+ if (fileName == null) {
+ return null;
+ }
+ return IssueOutput.error(matcher.group(0))
+ .navigatable(openFileNavigatable(project, fileName, parseLineNumber(matcher.group(2))))
+ .build();
+ }
+
+ private static Navigatable openFileNavigatable(Project project, String fileName, int line) {
+ return new NavigatableAdapter() {
+ @Override
+ public void navigate(boolean requestFocus) {
+ openFile(project, fileName, line, requestFocus);
+ }
+ };
+ }
+
+ private static void openFile(Project project, String fileName, int line, boolean requestFocus) {
+ PsiFile file = findFile(project, fileName);
+ if (file == null) {
+ return;
+ }
+ new OpenFileDescriptor(project, file.getViewProvider().getVirtualFile(), line - 1, -1)
+ .navigate(requestFocus);
+ }
+
+ @Nullable
+ private static PsiFile findFile(Project project, String fileName) {
+ return Arrays.stream(
+ FilenameIndex.getFilesByName(project, fileName, GlobalSearchScope.allScope(project)))
+ .findFirst()
+ .orElse(null);
+ }
+
+ /** defaults to -1 if no line number can be parsed. */
+ private static int parseLineNumber(@Nullable String string) {
+ try {
+ return string != null ? Integer.parseInt(string) : -1;
+ } catch (NumberFormatException e) {
+ return -1;
+ }
+ }
+ }
+}
diff --git a/python/src/com/google/idea/blaze/python/run/BlazePyDebuggableRunConfigurationFactory.java b/python/src/com/google/idea/blaze/python/run/BlazePyDebuggableRunConfigurationFactory.java
deleted file mode 100644
index 7536401..0000000
--- a/python/src/com/google/idea/blaze/python/run/BlazePyDebuggableRunConfigurationFactory.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright 2017 The Bazel Authors. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.idea.blaze.python.run;
-
-import com.google.idea.blaze.base.command.BlazeCommandName;
-import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
-import com.google.idea.blaze.base.ideinfo.TargetKey;
-import com.google.idea.blaze.base.model.BlazeProjectData;
-import com.google.idea.blaze.base.model.primitives.Kind;
-import com.google.idea.blaze.base.model.primitives.Label;
-import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
-import com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType;
-import com.google.idea.blaze.base.run.BlazeRunConfigurationFactory;
-import com.google.idea.blaze.base.run.state.BlazeCommandRunConfigurationCommonState;
-import com.intellij.execution.configurations.ConfigurationFactory;
-import com.intellij.execution.configurations.RunConfiguration;
-import com.intellij.openapi.project.Project;
-
-/** Creates run configurations for debuggable python targets. */
-public class BlazePyDebuggableRunConfigurationFactory extends BlazeRunConfigurationFactory {
- @Override
- public boolean handlesTarget(Project project, BlazeProjectData blazeProjectData, Label label) {
- TargetIdeInfo target = blazeProjectData.targetMap.get(TargetKey.forPlainTarget(label));
- return target != null && target.kind != null && PyDebugUtils.canUsePyDebugger(target.kind);
- }
-
- @Override
- protected ConfigurationFactory getConfigurationFactory() {
- return BlazeCommandRunConfigurationType.getInstance().getFactory();
- }
-
- @Override
- public void setupConfiguration(RunConfiguration configuration, Label target) {
- final BlazeCommandRunConfiguration blazeConfig = (BlazeCommandRunConfiguration) configuration;
- blazeConfig.setTarget(target);
-
- BlazeCommandRunConfigurationCommonState state =
- blazeConfig.getHandlerStateIfType(BlazeCommandRunConfigurationCommonState.class);
- Kind kind = blazeConfig.getKindForTarget();
- if (state != null) {
- BlazeCommandName command =
- kind != null && Kind.isTestRule(kind.toString())
- ? BlazeCommandName.TEST
- : BlazeCommandName.RUN;
- state.getCommandState().setCommand(command);
- }
- blazeConfig.setGeneratedName();
- }
-}
diff --git a/python/src/com/google/idea/blaze/python/run/BlazePyRunConfigurationRunner.java b/python/src/com/google/idea/blaze/python/run/BlazePyRunConfigurationRunner.java
index 08f2a22..adea754 100644
--- a/python/src/com/google/idea/blaze/python/run/BlazePyRunConfigurationRunner.java
+++ b/python/src/com/google/idea/blaze/python/run/BlazePyRunConfigurationRunner.java
@@ -25,6 +25,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.command.BlazeInvocationContext;
import com.google.idea.blaze.base.command.buildresult.BuildResultHelper;
import com.google.idea.blaze.base.io.FileAttributeProvider;
import com.google.idea.blaze.base.issueparser.IssueOutputLineProcessor;
@@ -46,14 +47,15 @@
import com.google.idea.blaze.base.scope.scopes.IssuesScope;
import com.google.idea.blaze.base.settings.Blaze;
import com.google.idea.blaze.base.settings.BlazeUserSettings;
-import com.google.idea.blaze.base.sync.data.BlazeDataStorage;
+import com.google.idea.blaze.base.settings.BlazeUserSettings.BlazeConsolePopupBehavior;
import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
import com.google.idea.blaze.base.util.SaveUtil;
+import com.google.idea.blaze.python.PySdkUtils;
import com.google.idea.blaze.python.run.filter.BlazePyFilterProvider;
-import com.google.idea.common.experiments.BoolExperiment;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.ExecutionResult;
import com.intellij.execution.Executor;
+import com.intellij.execution.RunCanceledByUserException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.configurations.RunProfile;
import com.intellij.execution.configurations.RunProfileState;
@@ -68,11 +70,9 @@
import com.intellij.execution.runners.ProgramRunner;
import com.intellij.execution.ui.ConsoleView;
import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.util.Key;
-import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.util.PathUtil;
@@ -85,7 +85,6 @@
import java.io.File;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
@@ -98,13 +97,6 @@
private static final Logger logger = Logger.getInstance(BlazePyRunConfigurationRunner.class);
- // Filter executables instead of files in the bin directory
- // This bin directory isn't the right one, because we don't know the blaze binary
- // or the config flags used to execute the build command
- // Introduced March 2017
- private static final BoolExperiment filterExecutableFiles =
- new BoolExperiment("filter.executable.files", true);
-
/** Converts to the native python plugin debug configuration state */
static class BlazePyDummyRunProfileState implements RunProfileState {
final BlazeCommandRunConfiguration configuration;
@@ -130,15 +122,12 @@
Strings.nullToEmpty(
getRunfilesPath(executable, WorkspaceRoot.fromProjectSafe(env.getProject()))));
- Module workspaceModule =
- nativeConfig.getConfigurationModule().findModule(BlazeDataStorage.WORKSPACE_MODULE_NAME);
- if (workspaceModule != null) {
- nativeConfig.setModule(workspaceModule);
- nativeConfig.setUseModuleSdk(true);
- } else {
- throw new ExecutionException(
- "Can't find the workspace module when debugging a python target");
+ Sdk sdk = PySdkUtils.getPythonSdk(env.getProject());
+ if (sdk == null) {
+ throw new ExecutionException("Can't find a Python SDK when debugging a python target.");
}
+ nativeConfig.setModule(null);
+ nativeConfig.setSdkHome(sdk.getHomePath());
BlazeCommandRunConfigurationCommonState handlerState =
configuration.getHandlerStateIfType(BlazeCommandRunConfigurationCommonState.class);
@@ -230,6 +219,7 @@
if (!isDebugging(env)) {
return true;
}
+ env.getCopyableUserData(EXECUTABLE_KEY).set(null);
try {
File executable = getExecutableToDebug(env);
env.getCopyableUserData(EXECUTABLE_KEY).set(executable);
@@ -297,8 +287,12 @@
final ProjectViewSet projectViewSet =
ProjectViewManager.getInstance(project).getProjectViewSet();
- BuildResultHelper buildResultHelper = BuildResultHelper.forFiles(file -> true);
- boolean suppressConsole = BlazeUserSettings.getInstance().getSuppressConsoleForRunAction();
+ BuildResultHelper buildResultHelper =
+ BuildResultHelper.forFiles(blazeProjectData.blazeVersionData, file -> true);
+ BlazeConsolePopupBehavior consolePopupBehavior =
+ BlazeUserSettings.getInstance().getSuppressConsoleForRunAction()
+ ? BlazeConsolePopupBehavior.NEVER
+ : BlazeConsolePopupBehavior.ALWAYS;
final ListenableFuture<Void> buildOperation =
BlazeExecutor.submitTask(
project,
@@ -309,7 +303,7 @@
.push(new IssuesScope(project))
.push(
new BlazeConsoleScope.Builder(project)
- .setSuppressConsole(suppressConsole)
+ .setPopupBehavior(consolePopupBehavior)
.build());
context.output(new StatusOutput("Building debug binary"));
@@ -319,7 +313,12 @@
Blaze.getBuildSystemProvider(project).getBinaryPath(),
BlazeCommandName.BUILD)
.addTargets(configuration.getTarget())
- .addBlazeFlags(BlazeFlags.buildFlags(project, projectViewSet))
+ .addBlazeFlags(
+ BlazeFlags.blazeFlags(
+ project,
+ projectViewSet,
+ BlazeCommandName.BUILD,
+ BlazeInvocationContext.RunConfiguration))
.addBlazeFlags(handlerState.getBlazeFlagsState().getExpandedFlags())
.addBlazeFlags(BlazePyDebugHelper.getAllBlazeDebugFlags())
.addBlazeFlags(buildResultHelper.getBuildFlags());
@@ -338,14 +337,16 @@
try {
SaveUtil.saveAllFiles();
buildOperation.get();
- } catch (InterruptedException | java.util.concurrent.ExecutionException e) {
+ } catch (InterruptedException e) {
+ throw new RunCanceledByUserException();
+ } catch (java.util.concurrent.ExecutionException e) {
throw new ExecutionException(e);
}
List<File> candidateFiles =
buildResultHelper
- .getBuildArtifacts()
+ .getBuildArtifactsForTarget((Label) configuration.getTarget())
.stream()
- .filter(fileFilter(blazeProjectData))
+ .filter(File::canExecute)
.collect(Collectors.toList());
if (candidateFiles.isEmpty()) {
throw new ExecutionException(
@@ -362,12 +363,6 @@
return file;
}
- private static Predicate<File> fileFilter(BlazeProjectData blazeProjectData) {
- return filterExecutableFiles.getValue()
- ? File::canExecute
- : f -> FileUtil.isAncestor(blazeProjectData.blazeInfo.getBlazeBinDirectory(), f, true);
- }
-
/**
* Basic heuristic for choosing between multiple output files. Currently just looks for a filename
* matching the target name.
diff --git a/python/src/com/google/idea/blaze/python/run/producers/BlazePyTestConfigurationProducer.java b/python/src/com/google/idea/blaze/python/run/producers/BlazePyTestConfigurationProducer.java
index 5d21fa8..2bf5216 100644
--- a/python/src/com/google/idea/blaze/python/run/producers/BlazePyTestConfigurationProducer.java
+++ b/python/src/com/google/idea/blaze/python/run/producers/BlazePyTestConfigurationProducer.java
@@ -15,7 +15,6 @@
*/
package com.google.idea.blaze.python.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.model.primitives.Label;
@@ -121,16 +120,14 @@
}
handlerState.getCommandState().setCommand(BlazeCommandName.TEST);
- ImmutableList.Builder<String> flags = ImmutableList.builder();
+ // remove conflicting flags from initial configuration
+ List<String> flags = new ArrayList<>(handlerState.getBlazeFlagsState().getRawFlags());
+ flags.removeIf((flag) -> flag.startsWith(BlazeFlags.TEST_FILTER));
String filter = testLocation.testFilter();
if (filter != null) {
flags.add(BlazeFlags.TEST_FILTER + "=" + filter);
}
- // remove conflicting flags from initial configuration
- List<String> oldFlags = new ArrayList<>(handlerState.getBlazeFlagsState().getRawFlags());
- oldFlags.removeIf((flag) -> flag.startsWith(BlazeFlags.TEST_FILTER));
- flags.addAll(oldFlags);
- handlerState.getBlazeFlagsState().setRawFlags(flags.build());
+ handlerState.getBlazeFlagsState().setRawFlags(flags);
BlazeConfigurationNameBuilder nameBuilder = new BlazeConfigurationNameBuilder(configuration);
nameBuilder.setTargetString(
diff --git a/python/src/com/google/idea/blaze/python/run/smrunner/BlazePythonTestEventsHandler.java b/python/src/com/google/idea/blaze/python/run/smrunner/BlazePythonTestEventsHandler.java
index f39eb76..339a1b8 100644
--- a/python/src/com/google/idea/blaze/python/run/smrunner/BlazePythonTestEventsHandler.java
+++ b/python/src/com/google/idea/blaze/python/run/smrunner/BlazePythonTestEventsHandler.java
@@ -25,17 +25,17 @@
import com.intellij.psi.PsiElement;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyFunction;
-import java.util.ArrayList;
-import java.util.EnumSet;
+import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Set;
import javax.annotation.Nullable;
/** Provides python-specific methods needed by the SM-runner test UI. */
-public class BlazePythonTestEventsHandler extends BlazeTestEventsHandler {
+public class BlazePythonTestEventsHandler implements BlazeTestEventsHandler {
@Override
- protected EnumSet<Kind> handledKinds() {
- return EnumSet.of(Kind.PY_TEST);
+ public boolean handlesKind(@Nullable Kind kind) {
+ return kind == Kind.PY_TEST;
}
@Override
@@ -53,7 +53,9 @@
@Override
public String getTestFilter(Project project, List<Location<?>> testLocations) {
// python test runner parses filters of the form "class1.method1 class2.method2 ..."
- List<String> filters = new ArrayList<>();
+ // parameterized test cases can cause the same class.method combination to be present
+ // multiple times, so we use a set
+ Set<String> filters = new LinkedHashSet<>();
for (Location<?> location : testLocations) {
String filter = getFilter(location.getPsiElement());
if (filter != null) {
diff --git a/python/src/com/google/idea/blaze/python/run/smrunner/BlazePythonTestLocator.java b/python/src/com/google/idea/blaze/python/run/smrunner/BlazePythonTestLocator.java
index 5bcfdc2..1582b12 100644
--- a/python/src/com/google/idea/blaze/python/run/smrunner/BlazePythonTestLocator.java
+++ b/python/src/com/google/idea/blaze/python/run/smrunner/BlazePythonTestLocator.java
@@ -29,7 +29,9 @@
import com.jetbrains.python.psi.PyFunction;
import com.jetbrains.python.psi.stubs.PyClassNameIndex;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
import javax.annotation.Nullable;
/** Locate python test classes / methods for test UI navigation. */
@@ -49,7 +51,7 @@
}
if (protocol.equals(SmRunnerUtils.GENERIC_TEST_PROTOCOL)) {
path = StringUtil.trimStart(path, PY_TESTCASE_PREFIX);
- String[] components = path.split("\\.");
+ String[] components = path.split("\\.|::");
if (components.length < 2) {
return ImmutableList.of();
}
@@ -68,7 +70,7 @@
for (PyClass pyClass : PyClassNameIndex.find(className, project, scope)) {
ProgressManager.checkCanceled();
if (PyTestUtils.isTestClass(pyClass)) {
- PyFunction method = pyClass.findMethodByName(methodName, true, null);
+ PyFunction method = findMethod(pyClass, methodName);
if (method != null && PyTestUtils.isTestFunction(method)) {
results.add(new PsiLocation<>(project, method));
}
@@ -89,4 +91,18 @@
}
return results;
}
+
+ @Nullable
+ private static PyFunction findMethod(PyClass pyClass, String methodName) {
+ PyFunction method = pyClass.findMethodByName(methodName, true, null);
+ if (method != null) {
+ return method;
+ }
+ return Arrays.stream(PyParameterizedNameConverter.EP_NAME.getExtensions())
+ .map(converter -> converter.toFunctionName(methodName))
+ .map(name -> pyClass.findMethodByName(name, true, null))
+ .filter(Objects::nonNull)
+ .findFirst()
+ .orElse(null);
+ }
}
diff --git a/python/src/com/google/idea/blaze/python/run/smrunner/PyBaseParameterizedNameConverter.java b/python/src/com/google/idea/blaze/python/run/smrunner/PyBaseParameterizedNameConverter.java
new file mode 100644
index 0000000..e86487a
--- /dev/null
+++ b/python/src/com/google/idea/blaze/python/run/smrunner/PyBaseParameterizedNameConverter.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.python.run.smrunner;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.annotation.Nullable;
+
+/** Converts from pybase/parameterized parameterized output format to plain testcase names. */
+public class PyBaseParameterizedNameConverter implements PyParameterizedNameConverter {
+
+ private static final Pattern PATTERN = Pattern.compile("^(\\w+)\\(.*\\)$");
+
+ @Override
+ @Nullable
+ public String toFunctionName(String testCaseName) {
+ Matcher match = PATTERN.matcher(testCaseName);
+ return match.matches() ? match.group(1) : null;
+ }
+}
diff --git a/python/src/com/google/idea/blaze/python/run/smrunner/PyParameterizedNameConverter.java b/python/src/com/google/idea/blaze/python/run/smrunner/PyParameterizedNameConverter.java
new file mode 100644
index 0000000..a1fc58d
--- /dev/null
+++ b/python/src/com/google/idea/blaze/python/run/smrunner/PyParameterizedNameConverter.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.python.run.smrunner;
+
+import com.intellij.openapi.extensions.ExtensionPointName;
+import javax.annotation.Nullable;
+
+/**
+ * Converts a possibly parameterized testcase name from test output XML to an unparameterized python
+ * function name.
+ */
+public interface PyParameterizedNameConverter {
+
+ ExtensionPointName<PyParameterizedNameConverter> EP_NAME =
+ ExtensionPointName.create("com.google.idea.blaze.PyParameterizedNameConverter");
+
+ @Nullable
+ String toFunctionName(String testCaseName);
+}
diff --git a/python/src/com/google/idea/blaze/python/sync/BlazePythonSyncPlugin.java b/python/src/com/google/idea/blaze/python/sync/BlazePythonSyncPlugin.java
index 2354f3c..e45b0d6 100644
--- a/python/src/com/google/idea/blaze/python/sync/BlazePythonSyncPlugin.java
+++ b/python/src/com/google/idea/blaze/python/sync/BlazePythonSyncPlugin.java
@@ -41,11 +41,11 @@
import com.google.idea.blaze.base.sync.SourceFolderProvider;
import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
import com.google.idea.common.experiments.BoolExperiment;
+import com.google.idea.sdkcompat.python.PythonFacetUtil;
import com.google.idea.sdkcompat.transactions.Transactions;
import com.intellij.facet.Facet;
import com.intellij.facet.FacetManager;
import com.intellij.facet.ModifiableFacetModel;
-import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleType;
@@ -62,8 +62,7 @@
import com.intellij.pom.NavigatableAdapter;
import com.intellij.util.PlatformUtils;
import com.jetbrains.python.PythonModuleTypeBase;
-import com.jetbrains.python.facet.PythonFacet;
-import com.jetbrains.python.facet.PythonFacetType;
+import com.jetbrains.python.facet.LibraryContributingFacet;
import com.jetbrains.python.sdk.PythonSdkType;
import java.util.List;
import java.util.Set;
@@ -134,6 +133,9 @@
@Override
public void refreshVirtualFileSystem(BlazeProjectData blazeProjectData) {
+ if (!blazeProjectData.workspaceLanguageSettings.isLanguageActive(LanguageClass.PYTHON)) {
+ return;
+ }
if (!refreshExecRoot.getValue()) {
return;
}
@@ -168,7 +170,7 @@
if (ModuleType.get(workspaceModule) instanceof PythonModuleTypeBase) {
return;
}
- PythonFacet pythonFacet = getOrCreatePythonFacet(context, workspaceModule);
+ LibraryContributingFacet<?> pythonFacet = getOrCreatePythonFacet(context, workspaceModule);
if (pythonFacet == null) {
return;
}
@@ -181,7 +183,7 @@
private static void removeFacet(Module workspaceModule) {
FacetManager manager = FacetManager.getInstance(workspaceModule);
ModifiableFacetModel facetModel = manager.createModifiableModel();
- PythonFacet facet = manager.findFacet(PythonFacet.ID, "Python");
+ LibraryContributingFacet<?> facet = manager.findFacet(PythonFacetUtil.getFacetId(), "Python");
if (facet != null) {
facetModel.removeFacet(facet);
facetModel.commit();
@@ -189,8 +191,9 @@
}
@Nullable
- private static PythonFacet getOrCreatePythonFacet(BlazeContext context, Module module) {
- PythonFacet facet = findPythonFacet(module);
+ private static LibraryContributingFacet<?> getOrCreatePythonFacet(
+ BlazeContext context, Module module) {
+ LibraryContributingFacet<?> facet = findPythonFacet(module);
if (facet != null && facetHasSdk(facet)) {
return facet;
}
@@ -210,35 +213,37 @@
IssueOutput.error(msg).submit(context);
return null;
}
- facet = manager.createFacet(PythonFacetType.getInstance(), "Python", null);
+ facet = manager.createFacet(PythonFacetUtil.getTypeInstance(), "Python", null);
facetModel.addFacet(facet);
facetModel.commit();
return facet;
}
- private static boolean facetHasSdk(PythonFacet facet) {
+ private static boolean facetHasSdk(LibraryContributingFacet<?> facet) {
// facets aren't properly updated when SDKs change (e.g. when they're deleted), so we need to
// manually check against the full list.
- Sdk sdk = facet.getConfiguration().getSdk();
+ Sdk sdk = PythonFacetUtil.getSdk(facet);
return sdk != null && PythonSdkType.getAllSdks().contains(sdk);
}
@Nullable
- private static Library getFacetLibrary(PythonFacet pythonFacet) {
- Sdk sdk = pythonFacet.getConfiguration().getSdk();
+ private static Library getFacetLibrary(LibraryContributingFacet<?> facet) {
+ Sdk sdk = PythonFacetUtil.getSdk(facet);
if (sdk == null) {
return null;
}
return LibraryTablesRegistrar.getInstance()
.getLibraryTable()
- .getLibraryByName(sdk.getName() + PythonFacet.PYTHON_FACET_LIBRARY_NAME_SUFFIX);
+ .getLibraryByName(
+ sdk.getName() + LibraryContributingFacet.PYTHON_FACET_LIBRARY_NAME_SUFFIX);
}
- private static PythonFacet findPythonFacet(Module module) {
+ private static LibraryContributingFacet<?> findPythonFacet(Module module) {
final Facet<?>[] allFacets = FacetManager.getInstance(module).getAllFacets();
for (Facet<?> facet : allFacets) {
- if (facet instanceof PythonFacet) {
- return (PythonFacet) facet;
+ if ((facet instanceof LibraryContributingFacet)
+ && (facet.getTypeId() == PythonFacetUtil.getFacetId())) {
+ return (LibraryContributingFacet<?>) facet;
}
}
return null;
@@ -274,10 +279,8 @@
}
private static void setProjectSdk(Project project, Sdk sdk) {
- Transactions.submitTransactionAndWait(
- () ->
- ApplicationManager.getApplication()
- .runWriteAction(() -> ProjectRootManager.getInstance(project).setProjectSdk(sdk)));
+ Transactions.submitWriteActionTransactionAndWait(
+ () -> ProjectRootManager.getInstance(project).setProjectSdk(sdk));
}
@Override
diff --git a/python/src/com/google/idea/blaze/python/sync/PythonPrefetchFileSource.java b/python/src/com/google/idea/blaze/python/sync/PythonPrefetchFileSource.java
index 29bde5e..0e5ae17 100644
--- a/python/src/com/google/idea/blaze/python/sync/PythonPrefetchFileSource.java
+++ b/python/src/com/google/idea/blaze/python/sync/PythonPrefetchFileSource.java
@@ -19,9 +19,9 @@
import com.google.idea.blaze.base.model.BlazeProjectData;
import com.google.idea.blaze.base.prefetch.PrefetchFileSource;
import com.google.idea.blaze.base.projectview.ProjectViewSet;
+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.Set;
/** Causes python files to become prefetched. */
@@ -30,11 +30,12 @@
public void addFilesToPrefetch(
Project project,
ProjectViewSet projectViewSet,
+ ImportRoots importRoots,
BlazeProjectData blazeProjectData,
- Collection<File> files) {}
+ Set<File> files) {}
@Override
- public Set<String> prefetchSrcFileExtensions() {
+ public Set<String> prefetchFileExtensions() {
return ImmutableSet.of("py", "pyw", "pyi");
}
}
diff --git a/python/tests/integrationtests/com/google/idea/blaze/python/run/smrunner/BlazePythonTestEventsHandlerTest.java b/python/tests/integrationtests/com/google/idea/blaze/python/run/smrunner/BlazePythonTestEventsHandlerTest.java
index c87025c..d56f656 100644
--- a/python/tests/integrationtests/com/google/idea/blaze/python/run/smrunner/BlazePythonTestEventsHandlerTest.java
+++ b/python/tests/integrationtests/com/google/idea/blaze/python/run/smrunner/BlazePythonTestEventsHandlerTest.java
@@ -57,7 +57,7 @@
}
@Test
- public void testFunctionLocationResolves() {
+ public void testFunctionLocationOldFormatResolves() {
PsiFile file =
workspace.createPsiFile(
new WorkspacePath("lib/app_unittest.py"),
@@ -74,6 +74,23 @@
}
@Test
+ public void testFunctionLocationResolves() {
+ PsiFile file =
+ workspace.createPsiFile(
+ new WorkspacePath("lib/app_unittest.py"),
+ "class AppUnitTest:",
+ " def testApp(self):",
+ " return");
+ PyClass pyClass = PsiUtils.findFirstChildOfClassRecursive(file, PyClass.class);
+ PyFunction function = pyClass.findMethodByName("testApp", false, null);
+ assertThat(function).isNotNull();
+
+ String url = handler.testLocationUrl(null, null, "__main__.AppUnitTest::testApp", null);
+ Location<?> location = getLocation(url);
+ assertThat(location.getPsiElement()).isEqualTo(function);
+ }
+
+ @Test
public void testFunctionWithoutMainPrefixResolves() {
PsiFile file =
workspace.createPsiFile(
diff --git a/python/tests/unittests/com/google/idea/blaze/python/issueparser/PyIssueParserProviderTest.java b/python/tests/unittests/com/google/idea/blaze/python/issueparser/PyIssueParserProviderTest.java
new file mode 100644
index 0000000..f09b741
--- /dev/null
+++ b/python/tests/unittests/com/google/idea/blaze/python/issueparser/PyIssueParserProviderTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.python.issueparser;
+
+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.issueparser.BlazeIssueParser;
+import com.google.idea.blaze.base.issueparser.BlazeIssueParserProvider;
+import com.google.idea.blaze.base.scope.output.IssueOutput;
+import com.google.idea.blaze.base.scope.output.IssueOutput.Category;
+import com.intellij.openapi.extensions.impl.ExtensionPointImpl;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link PyIssueParserProvider}. */
+@RunWith(JUnit4.class)
+public class PyIssueParserProviderTest extends BlazeTestCase {
+
+ private ImmutableList<BlazeIssueParser.Parser> parsers;
+
+ @Override
+ protected void initTest(Container applicationServices, Container projectServices) {
+ super.initTest(applicationServices, projectServices);
+
+ ExtensionPointImpl<BlazeIssueParserProvider> ep =
+ registerExtensionPoint(BlazeIssueParserProvider.EP_NAME, BlazeIssueParserProvider.class);
+ ep.registerExtension(new PyIssueParserProvider());
+
+ parsers = ImmutableList.copyOf(BlazeIssueParserProvider.getAllIssueParsers(project));
+ }
+
+ @Test
+ public void testParsePyTypeError() {
+ BlazeIssueParser blazeIssueParser = new BlazeIssueParser(parsers);
+ IssueOutput issue =
+ blazeIssueParser.parseIssue(
+ "File \"dataset.py\", line 109, in Dataset: "
+ + "Name 'function' is not defined [name-error]");
+ assertThat(issue).isNotNull();
+ assertThat(issue.getCategory()).isEqualTo(Category.ERROR);
+ assertThat(issue.getNavigatable()).isNotNull();
+ }
+}
diff --git a/python/tests/unittests/com/google/idea/blaze/python/run/smrunner/PyBaseParameterizedNameConverterTest.java b/python/tests/unittests/com/google/idea/blaze/python/run/smrunner/PyBaseParameterizedNameConverterTest.java
new file mode 100644
index 0000000..b4b13ba
--- /dev/null
+++ b/python/tests/unittests/com/google/idea/blaze/python/run/smrunner/PyBaseParameterizedNameConverterTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.python.run.smrunner;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link PyBaseParameterizedNameConverter}. */
+@RunWith(JUnit4.class)
+public class PyBaseParameterizedNameConverterTest {
+
+ private static final PyBaseParameterizedNameConverter CONVERTER =
+ new PyBaseParameterizedNameConverter();
+
+ @Test
+ public void testUnparameterizedTestCaseReturnsNull() {
+ assertThat(CONVERTER.toFunctionName("simpleName")).isNull();
+ assertThat(CONVERTER.toFunctionName("name_with_underscores")).isNull();
+ assertThat(CONVERTER.toFunctionName("name__with__double__underscores123")).isNull();
+ }
+
+ @Test
+ public void testSimpleParameterNames() {
+ assertThat(CONVERTER.toFunctionName("test(1)")).isEqualTo("test");
+ assertThat(CONVERTER.toFunctionName("test(param__name)")).isEqualTo("test");
+ assertThat(CONVERTER.toFunctionName("test(@#$%^&*,./)")).isEqualTo("test");
+ }
+
+ @Test
+ public void testSplitAtFirstOpenBracket() {
+ assertThat(CONVERTER.toFunctionName("test(param(1)")).isEqualTo("test");
+ assertThat(CONVERTER.toFunctionName("test(@#$;('(())))())")).isEqualTo("test");
+ }
+}
diff --git a/scala/BUILD b/scala/BUILD
index 1343c03..2f2dded 100644
--- a/scala/BUILD
+++ b/scala/BUILD
@@ -26,6 +26,11 @@
visibility = ["//visibility:public"],
)
+OPTIONAL_PLUGIN_XMLS = [
+ ":optional_xml",
+ "//java:optional_xml",
+]
+
merged_plugin_xml(
name = "merged_plugin_xml",
srcs = [
@@ -58,7 +63,7 @@
intellij_plugin(
name = "scala_integration_test_plugin",
testonly = 1,
- optional_plugin_xmls = [":optional_xml"],
+ optional_plugin_xmls = OPTIONAL_PLUGIN_XMLS,
plugin_xml = ":scala_plugin_xml",
deps = [":scala"],
)
diff --git a/scala/src/META-INF/scala-contents.xml b/scala/src/META-INF/scala-contents.xml
index 9c46cf1..d2fbd81 100644
--- a/scala/src/META-INF/scala-contents.xml
+++ b/scala/src/META-INF/scala-contents.xml
@@ -4,4 +4,28 @@
<JavaLikeLanguage
implementation="com.google.idea.blaze.scala.sync.source.ScalaJavaLikeLanguage"/>
</extensions>
+
+ <extensions defaultExtensionNs="com.intellij">
+ <runConfigurationProducer
+ implementation="com.google.idea.blaze.scala.run.producers.BlazeScalaMainClassRunConfigurationProducer"/>
+ <runConfigurationProducer
+ implementation="com.google.idea.blaze.scala.run.producers.BlazeScalaTestClassConfigurationProducer"/>
+ <!-- TODO: add configuration producer for infix expression test cases in scalatest and specs2. -->
+ <!-- Need to come before the one in the scala plugin to override the icon. -->
+ <runLineMarkerContributor
+ implementationClass="com.google.idea.blaze.scala.run.producers.BlazeScalaRunLineMarkerContributor"
+ language="Scala"
+ order="first"/>
+ <runLineMarkerContributor
+ implementationClass="com.google.idea.blaze.scala.run.producers.BlazeScalaTestRunLineMarkerContributor"
+ language="Scala"
+ order="first"/>
+ </extensions>
+
+ <project-components>
+ <component>
+ <implementation-class>com.google.idea.blaze.scala.run.producers.NonBlazeProducerSuppressor
+ </implementation-class>
+ </component>
+ </project-components>
</idea-plugin>
diff --git a/scala/src/com/google/idea/blaze/scala/run/producers/BlazeScalaMainClassRunConfigurationProducer.java b/scala/src/com/google/idea/blaze/scala/run/producers/BlazeScalaMainClassRunConfigurationProducer.java
new file mode 100644
index 0000000..933758e
--- /dev/null
+++ b/scala/src/com/google/idea/blaze/scala/run/producers/BlazeScalaMainClassRunConfigurationProducer.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.scala.run.producers;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.idea.blaze.base.command.BlazeCommandName;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.model.primitives.Kind;
+import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
+import com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType;
+import com.google.idea.blaze.base.run.producers.BlazeRunConfigurationProducer;
+import com.google.idea.blaze.base.run.state.BlazeCommandRunConfigurationCommonState;
+import com.google.idea.blaze.base.run.testmap.FilteredTargetMap;
+import com.google.idea.blaze.base.sync.SyncCache;
+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.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 java.io.File;
+import java.util.Collection;
+import java.util.Objects;
+import javax.annotation.Nullable;
+import org.jetbrains.plugins.scala.lang.psi.api.ScalaFile;
+import org.jetbrains.plugins.scala.lang.psi.api.toplevel.typedef.ScObject;
+import org.jetbrains.plugins.scala.runner.ScalaMainMethodUtil;
+import scala.Option;
+
+/** Creates run configurations for Scala main classes sourced by scala_binary targets. */
+public class BlazeScalaMainClassRunConfigurationProducer
+ extends BlazeRunConfigurationProducer<BlazeCommandRunConfiguration> {
+
+ private static final String SCALA_BINARY_MAP_KEY = "BlazeScalaBinaryMap";
+
+ public BlazeScalaMainClassRunConfigurationProducer() {
+ super(BlazeCommandRunConfigurationType.getInstance());
+ }
+
+ @Override
+ protected boolean doSetupConfigFromContext(
+ BlazeCommandRunConfiguration configuration,
+ ConfigurationContext context,
+ Ref<PsiElement> sourceElement) {
+ ScObject mainObject = getMainObject(context);
+ if (mainObject == null) {
+ return false;
+ }
+ Option<PsiMethod> mainMethod = ScalaMainMethodUtil.findMainMethod(mainObject);
+ if (mainMethod.isEmpty()) {
+ sourceElement.set(mainObject);
+ } else {
+ sourceElement.set(mainMethod.get());
+ }
+ TargetIdeInfo target = getTarget(context.getProject(), mainObject);
+ if (target == null) {
+ return false;
+ }
+ configuration.setTarget(target.key.label);
+ BlazeCommandRunConfigurationCommonState handlerState =
+ configuration.getHandlerStateIfType(BlazeCommandRunConfigurationCommonState.class);
+ if (handlerState == null) {
+ return false;
+ }
+ handlerState.getCommandState().setCommand(BlazeCommandName.RUN);
+ configuration.setGeneratedName();
+ return true;
+ }
+
+ @Override
+ protected boolean doIsConfigFromContext(
+ BlazeCommandRunConfiguration configuration, ConfigurationContext context) {
+ BlazeCommandRunConfigurationCommonState handlerState =
+ configuration.getHandlerStateIfType(BlazeCommandRunConfigurationCommonState.class);
+ if (handlerState == null) {
+ return false;
+ }
+ if (!Objects.equals(handlerState.getCommandState().getCommand(), BlazeCommandName.RUN)) {
+ return false;
+ }
+ ScObject mainObject = getMainObject(context);
+ if (mainObject == null) {
+ return false;
+ }
+ TargetIdeInfo target = getTarget(context.getProject(), mainObject);
+ return target != null && Objects.equals(configuration.getTarget(), target.key.label);
+ }
+
+ @Nullable
+ private static ScObject getMainObject(ConfigurationContext context) {
+ Location location = context.getLocation();
+ if (location == null) {
+ return null;
+ }
+ location = JavaExecutionUtil.stepIntoSingleClass(context.getLocation());
+ if (location == null) {
+ return null;
+ }
+ PsiElement element = location.getPsiElement();
+ if (!(element.getContainingFile() instanceof ScalaFile)) {
+ return null;
+ }
+ if (!element.isPhysical()) {
+ return null;
+ }
+ return getMainObjectFromElement(element);
+ }
+
+ @Nullable
+ private static ScObject getMainObjectFromElement(PsiElement element) {
+ for (; element != null; element = element.getParent()) {
+ if (element instanceof ScObject) {
+ ScObject obj = (ScObject) element;
+ if (ScalaMainMethodUtil.hasMainMethod(obj)) {
+ return obj;
+ }
+ } else if (element instanceof ScalaFile) {
+ return getMainObjectFromFile((ScalaFile) element);
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ private static ScObject getMainObjectFromFile(ScalaFile file) {
+ for (PsiClass aClass : file.getClasses()) {
+ if (!(aClass instanceof ScObject)) {
+ continue;
+ }
+ ScObject obj = (ScObject) aClass;
+ if (ScalaMainMethodUtil.hasMainMethod(obj)) {
+ // Potentially multiple matches, we'll pick the first one.
+ // TODO: prefer class with same name as file?
+ // TODO: skip if not main_class of a rule.
+ return obj;
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ private static TargetIdeInfo getTarget(Project project, ScObject mainObject) {
+ File mainObjectFile = RunUtil.getFileForClass(mainObject);
+ if (mainObjectFile == null) {
+ return null;
+ }
+ Collection<TargetIdeInfo> scalaBinaryTargets = findScalaBinaryTargets(project, mainObjectFile);
+
+ // Scala objects are basically singletons with a '$' appended to the class name.
+ // The trunced qualified name removes the '$',
+ // so it matches the main class specified in the scala_binary rule.
+ String qualifiedName = mainObject.getTruncedQualifiedName();
+
+ if (qualifiedName == null) {
+ // out of date psi element; just take the first match
+ return Iterables.getFirst(scalaBinaryTargets, null);
+ }
+
+ // Can't use getName because of the '$'.
+ String className = qualifiedName.substring(qualifiedName.lastIndexOf('.') + 1);
+
+ // first look for a matching main_class
+ TargetIdeInfo match =
+ scalaBinaryTargets
+ .stream()
+ .filter(
+ target ->
+ target.javaIdeInfo != null
+ && qualifiedName.equals(target.javaIdeInfo.javaBinaryMainClass))
+ .findFirst()
+ .orElse(null);
+ if (match != null) {
+ return match;
+ }
+
+ match =
+ scalaBinaryTargets
+ .stream()
+ .filter(target -> className.equals(target.key.label.targetName().toString()))
+ .findFirst()
+ .orElse(null);
+ if (match != null) {
+ return match;
+ }
+ return Iterables.getFirst(scalaBinaryTargets, null);
+ }
+
+ /** Returns all scala_binary targets reachable from the given source file. */
+ private static Collection<TargetIdeInfo> findScalaBinaryTargets(
+ Project project, File mainClassFile) {
+ FilteredTargetMap map =
+ SyncCache.getInstance(project)
+ .get(
+ SCALA_BINARY_MAP_KEY,
+ BlazeScalaMainClassRunConfigurationProducer::computeTargetMap);
+ return map != null ? map.targetsForSourceFile(mainClassFile) : ImmutableList.of();
+ }
+
+ private static FilteredTargetMap computeTargetMap(Project project, BlazeProjectData projectData) {
+ return new FilteredTargetMap(
+ project,
+ projectData.artifactLocationDecoder,
+ projectData.targetMap,
+ (target) -> target.kind == Kind.SCALA_BINARY && target.isPlainTarget());
+ }
+}
diff --git a/scala/src/com/google/idea/blaze/scala/run/producers/BlazeScalaRunLineMarkerContributor.java b/scala/src/com/google/idea/blaze/scala/run/producers/BlazeScalaRunLineMarkerContributor.java
new file mode 100644
index 0000000..12f36eb
--- /dev/null
+++ b/scala/src/com/google/idea/blaze/scala/run/producers/BlazeScalaRunLineMarkerContributor.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.scala.run.producers;
+
+import com.intellij.execution.lineMarker.RunLineMarkerContributor;
+import com.intellij.icons.AllIcons;
+import com.intellij.psi.PsiElement;
+import java.util.Arrays;
+import javax.annotation.Nullable;
+import javax.swing.Icon;
+import org.jetbrains.plugins.scala.runner.ScalaRunLineMarkerContributor;
+
+/** Replaces the icon for {@link ScalaRunLineMarkerContributor} to match other blaze plugins. */
+public class BlazeScalaRunLineMarkerContributor extends RunLineMarkerContributor {
+ @Nullable
+ @Override
+ public Info getInfo(PsiElement element) {
+ Info info = new ScalaRunLineMarkerContributor().getInfo(element);
+ if (info == null) {
+ return null;
+ }
+ return new ReplacementInfo(info, AllIcons.RunConfigurations.TestState.Run);
+ }
+
+ private static class ReplacementInfo extends Info {
+ ReplacementInfo(Info info, Icon icon) {
+ super(icon, info.actions, info.tooltipProvider);
+ }
+
+ @Override
+ public boolean shouldReplace(Info other) {
+ return Arrays.equals(actions, other.actions);
+ }
+ }
+}
diff --git a/scala/src/com/google/idea/blaze/scala/run/producers/BlazeScalaTestClassConfigurationProducer.java b/scala/src/com/google/idea/blaze/scala/run/producers/BlazeScalaTestClassConfigurationProducer.java
new file mode 100644
index 0000000..d3b46b2
--- /dev/null
+++ b/scala/src/com/google/idea/blaze/scala/run/producers/BlazeScalaTestClassConfigurationProducer.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.scala.run.producers;
+
+import com.google.idea.blaze.base.command.BlazeCommandName;
+import com.google.idea.blaze.base.command.BlazeFlags;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.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.producers.BlazeRunConfigurationProducer;
+import com.google.idea.blaze.base.run.smrunner.SmRunnerUtils;
+import com.google.idea.blaze.base.run.state.BlazeCommandRunConfigurationCommonState;
+import com.google.idea.blaze.java.run.RunUtil;
+import com.google.idea.blaze.java.run.producers.BlazeJavaTestClassConfigurationProducer;
+import com.google.idea.blaze.java.run.producers.TestSizeAnnotationMap;
+import com.intellij.codeInsight.TestFrameworks;
+import com.intellij.execution.JavaExecutionUtil;
+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.util.PsiTreeUtil;
+import com.intellij.testIntegration.TestFramework;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import javax.annotation.Nullable;
+import org.jetbrains.plugins.scala.lang.psi.api.toplevel.typedef.ScClass;
+import org.jetbrains.plugins.scala.testingSupport.test.scalatest.ScalaTestTestFramework;
+
+/**
+ * Producer for run configurations related to Scala test classes (not handled by JUnit) in Blaze.
+ * Handles only {@link ScalaTestTestFramework}. Other supported frameworks (junit and specs2) are
+ * handled by {@link BlazeJavaTestClassConfigurationProducer}.
+ */
+public class BlazeScalaTestClassConfigurationProducer
+ extends BlazeRunConfigurationProducer<BlazeCommandRunConfiguration> {
+
+ public BlazeScalaTestClassConfigurationProducer() {
+ super(BlazeCommandRunConfigurationType.getInstance());
+ }
+
+ @Override
+ protected boolean doSetupConfigFromContext(
+ BlazeCommandRunConfiguration configuration,
+ ConfigurationContext context,
+ Ref<PsiElement> sourceElement) {
+ ScClass testClass = getTestClass(context);
+ if (testClass == null) {
+ return false;
+ }
+ sourceElement.set(testClass);
+ TargetIdeInfo target =
+ RunUtil.targetForTestClass(testClass, TestSizeAnnotationMap.getTestSize(testClass));
+ if (target == null) {
+ return false;
+ }
+ configuration.setTarget(target.key.label);
+ BlazeCommandRunConfigurationCommonState handlerState =
+ configuration.getHandlerStateIfType(BlazeCommandRunConfigurationCommonState.class);
+ if (handlerState == null) {
+ return false;
+ }
+
+ List<String> flags = new ArrayList<>(handlerState.getBlazeFlagsState().getRawFlags());
+ flags.removeIf((flag) -> flag.startsWith(BlazeFlags.TEST_FILTER));
+ flags.add(getTestFilterFlag(testClass));
+ handlerState.getBlazeFlagsState().setRawFlags(flags);
+
+ handlerState.getCommandState().setCommand(BlazeCommandName.TEST);
+ BlazeConfigurationNameBuilder nameBuilder = new BlazeConfigurationNameBuilder(configuration);
+ nameBuilder.setTargetString(testClass.getName());
+ configuration.setName(nameBuilder.build());
+ configuration.setNameChangedByUser(true); // don't revert to generated name
+ return true;
+ }
+
+ @Override
+ protected boolean doIsConfigFromContext(
+ BlazeCommandRunConfiguration configuration, ConfigurationContext context) {
+ PsiClass testClass = getTestClass(context);
+ if (testClass == null) {
+ return false;
+ }
+ BlazeCommandRunConfigurationCommonState handlerState =
+ configuration.getHandlerStateIfType(BlazeCommandRunConfigurationCommonState.class);
+ if (handlerState == null
+ || !Objects.equals(handlerState.getCommandState().getCommand(), BlazeCommandName.TEST)
+ || !Objects.equals(handlerState.getTestFilterFlag(), getTestFilterFlag(testClass))) {
+ return false;
+ }
+ TargetIdeInfo target =
+ RunUtil.targetForTestClass(testClass, TestSizeAnnotationMap.getTestSize(testClass));
+ return target != null && Objects.equals(configuration.getTarget(), target.key.label);
+ }
+
+ @Nullable
+ private static ScClass getTestClass(ConfigurationContext context) {
+ Location<?> location = context.getLocation();
+ if (location == null) {
+ return null;
+ }
+ location = JavaExecutionUtil.stepIntoSingleClass(location);
+ if (location == null) {
+ return null;
+ }
+ if (!SmRunnerUtils.getSelectedSmRunnerTreeElements(context).isEmpty()) {
+ // handled by a different producer
+ return null;
+ }
+ return getTestClass(location);
+ }
+
+ @Nullable
+ private static ScClass getTestClass(Location<?> location) {
+ PsiElement element = location.getPsiElement();
+ ScClass testClass;
+ if (element instanceof ScClass) {
+ testClass = (ScClass) element;
+ } else {
+ testClass = PsiTreeUtil.getParentOfType(element, ScClass.class);
+ }
+ if (testClass != null && isTestClass(testClass)) {
+ return testClass;
+ }
+ return null;
+ }
+
+ private static boolean isTestClass(ScClass testClass) {
+ TestFramework framework = TestFrameworks.detectFramework(testClass);
+ return framework instanceof ScalaTestTestFramework && framework.isTestClass(testClass);
+ }
+
+ private static String getTestFilterFlag(PsiClass testClass) {
+ // TODO: may need to append '#' if implementation changes.
+ // https://github.com/bazelbuild/rules_scala/pull/216
+ return BlazeFlags.TEST_FILTER + "=" + testClass.getQualifiedName();
+ }
+}
diff --git a/scala/src/com/google/idea/blaze/scala/run/producers/BlazeScalaTestRunLineMarkerContributor.java b/scala/src/com/google/idea/blaze/scala/run/producers/BlazeScalaTestRunLineMarkerContributor.java
new file mode 100644
index 0000000..9133e7b
--- /dev/null
+++ b/scala/src/com/google/idea/blaze/scala/run/producers/BlazeScalaTestRunLineMarkerContributor.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.scala.run.producers;
+
+import com.intellij.codeInsight.TestFrameworks;
+import com.intellij.execution.TestStateStorage;
+import com.intellij.execution.lineMarker.ExecutorAction;
+import com.intellij.execution.lineMarker.RunLineMarkerContributor;
+import com.intellij.execution.testframework.TestIconMapper;
+import com.intellij.execution.testframework.sm.runner.states.TestStateInfo;
+import com.intellij.icons.AllIcons;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.util.PsiTreeUtil;
+import com.intellij.testIntegration.TestFramework;
+import com.intellij.testIntegration.TestRunLineMarkerProvider;
+import java.util.Arrays;
+import java.util.function.Function;
+import javax.annotation.Nullable;
+import javax.swing.Icon;
+import org.jetbrains.plugins.scala.lang.psi.api.expr.ScInfixExpr;
+import org.jetbrains.plugins.scala.lang.psi.api.expr.ScReferenceExpression;
+import org.jetbrains.plugins.scala.lang.psi.api.statements.ScFunctionDefinition;
+import org.jetbrains.plugins.scala.lang.psi.api.toplevel.typedef.ScClass;
+import org.jetbrains.plugins.scala.testingSupport.test.ScalaTestRunLineMarkerProvider;
+
+/**
+ * Generates run/debug gutter icons for scala_test, and scala_junit_tests.
+ *
+ * <p>{@link ScalaTestRunLineMarkerProvider} exists in the scala plugin, but it does not currently
+ * try to handle scalatest and specs2 at all. For JUnit tests, it has a bug that causes it to not
+ * generate icons for a test without run state (i.e., newly written tests).
+ */
+public class BlazeScalaTestRunLineMarkerContributor extends ScalaTestRunLineMarkerProvider {
+ @Nullable
+ @Override
+ public Info getInfo(PsiElement e) {
+ if (isIdentifier(e)) {
+ PsiElement element = e.getParent();
+ if (element instanceof ScClass) {
+ return getInfo((ScClass) element, null, super.getInfo(e));
+ } else if (element instanceof ScFunctionDefinition) {
+ ScClass testClass = PsiTreeUtil.getParentOfType(element, ScClass.class);
+ if (testClass != null) {
+ return getInfo(testClass, element, super.getInfo(e));
+ }
+ } else if (element instanceof ScReferenceExpression) {
+ // TODO: handle infix expressions. E.g., "foo" should "bar" in { baz }
+ return null;
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ private static Info getInfo(ScClass testClass, @Nullable PsiElement testCase, Info toReplace) {
+ TestFramework framework = TestFrameworks.detectFramework(testClass);
+ if (framework == null) {
+ return null;
+ }
+ boolean isClass = testCase == null;
+ String url;
+ if (isClass) {
+ if (!framework.isTestClass(testClass)) {
+ return null;
+ }
+ url = "java:suite://" + testClass.getQualifiedName();
+ } else if (testCase instanceof ScFunctionDefinition) {
+ ScFunctionDefinition method = (ScFunctionDefinition) testCase;
+ if (!framework.isTestMethod(method)) {
+ return null;
+ }
+ url = "java:test://" + testClass.getQualifiedName() + "." + method.getName();
+ } else if (testCase instanceof ScInfixExpr) {
+ // TODO: handle this case.
+ return null;
+ } else {
+ return null;
+ }
+
+ return getInfo(url, testClass.getProject(), isClass, toReplace);
+ }
+
+ private static Info getInfo(String url, Project project, boolean isClass, Info toReplace) {
+ Icon icon = getTestStateIcon(url, project, isClass);
+ return new ReplacementInfo(
+ icon,
+ ExecutorAction.getActions(1),
+ RunLineMarkerContributor.RUN_TEST_TOOLTIP_PROVIDER,
+ toReplace);
+ }
+
+ /** Copied from {@link TestRunLineMarkerProvider#getTestStateIcon(String, Project, boolean)} */
+ private static Icon getTestStateIcon(String url, Project project, boolean isClass) {
+ TestStateStorage.Record state = TestStateStorage.getInstance(project).getState(url);
+ if (state != null) {
+ TestStateInfo.Magnitude magnitude = TestIconMapper.getMagnitude(state.magnitude);
+ if (magnitude != null) {
+ switch (magnitude) {
+ case ERROR_INDEX:
+ case FAILED_INDEX:
+ return AllIcons.RunConfigurations.TestState.Red2;
+ case PASSED_INDEX:
+ case COMPLETE_INDEX:
+ return AllIcons.RunConfigurations.TestState.Green2;
+ default:
+ }
+ }
+ }
+ return isClass
+ ? AllIcons.RunConfigurations.TestState.Run_run
+ : AllIcons.RunConfigurations.TestState.Run;
+ }
+
+ private static class ReplacementInfo extends Info {
+ private final Info toReplace;
+
+ ReplacementInfo(
+ Icon icon,
+ AnAction[] actions,
+ Function<PsiElement, String> tooltipProvider,
+ Info toReplace) {
+ super(icon, actions, tooltipProvider);
+ this.toReplace = toReplace;
+ }
+
+ @Override
+ public boolean shouldReplace(Info other) {
+ return toReplace != null && Arrays.equals(toReplace.actions, other.actions);
+ }
+ }
+}
diff --git a/scala/src/com/google/idea/blaze/scala/run/producers/NonBlazeProducerSuppressor.java b/scala/src/com/google/idea/blaze/scala/run/producers/NonBlazeProducerSuppressor.java
new file mode 100644
index 0000000..1979631
--- /dev/null
+++ b/scala/src/com/google/idea/blaze/scala/run/producers/NonBlazeProducerSuppressor.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.scala.run.producers;
+
+import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.base.settings.Blaze;
+import com.intellij.execution.RunConfigurationProducerService;
+import com.intellij.execution.actions.RunConfigurationProducer;
+import com.intellij.openapi.components.AbstractProjectComponent;
+import com.intellij.openapi.project.Project;
+import org.jetbrains.plugins.scala.runner.ScalaApplicationConfigurationProducer;
+import org.jetbrains.plugins.scala.testingSupport.test.scalatest.ScalaTestConfigurationProducer;
+import org.jetbrains.plugins.scala.testingSupport.test.specs2.Specs2ConfigurationProducer;
+
+/** Suppresses certain non-Blaze configuration producers in Blaze projects. */
+public class NonBlazeProducerSuppressor extends AbstractProjectComponent {
+
+ private static final ImmutableList<Class<? extends RunConfigurationProducer<?>>>
+ PRODUCERS_TO_SUPPRESS =
+ ImmutableList.of(
+ ScalaApplicationConfigurationProducer.class,
+ Specs2ConfigurationProducer.class,
+ ScalaTestConfigurationProducer.class);
+
+ public NonBlazeProducerSuppressor(Project project) {
+ super(project);
+ }
+
+ @Override
+ public void projectOpened() {
+ if (Blaze.isBlazeProject(myProject)) {
+ suppressProducers(myProject);
+ }
+ }
+
+ private static void suppressProducers(Project project) {
+ RunConfigurationProducerService producerService =
+ RunConfigurationProducerService.getInstance(project);
+ PRODUCERS_TO_SUPPRESS.forEach(producerService::addIgnoredProducer);
+ }
+}
diff --git a/scala/src/com/google/idea/blaze/scala/sync/BlazeScalaLibrarySource.java b/scala/src/com/google/idea/blaze/scala/sync/BlazeScalaLibrarySource.java
index 86f276f..9cf373b 100644
--- a/scala/src/com/google/idea/blaze/scala/sync/BlazeScalaLibrarySource.java
+++ b/scala/src/com/google/idea/blaze/scala/sync/BlazeScalaLibrarySource.java
@@ -20,7 +20,7 @@
import com.google.idea.blaze.base.model.BlazeProjectData;
import com.google.idea.blaze.base.sync.libraries.LibrarySource;
import com.google.idea.blaze.scala.sync.model.BlazeScalaSyncData;
-import java.util.Collection;
+import java.util.List;
/** Provides libraries required by Scala rules. */
public class BlazeScalaLibrarySource extends LibrarySource.Adapter {
@@ -31,11 +31,11 @@
}
@Override
- public Collection<? extends BlazeLibrary> getLibraries() {
+ public List<? extends BlazeLibrary> getLibraries() {
BlazeScalaSyncData syncData = blazeProjectData.syncState.get(BlazeScalaSyncData.class);
if (syncData == null) {
return ImmutableList.of();
}
- return syncData.importResult.libraries.values();
+ return syncData.importResult.libraries.values().asList();
}
}
diff --git a/scala/src/com/google/idea/blaze/scala/sync/BlazeScalaSyncPlugin.java b/scala/src/com/google/idea/blaze/scala/sync/BlazeScalaSyncPlugin.java
index 20fa54c..475d374 100644
--- a/scala/src/com/google/idea/blaze/scala/sync/BlazeScalaSyncPlugin.java
+++ b/scala/src/com/google/idea/blaze/scala/sync/BlazeScalaSyncPlugin.java
@@ -40,7 +40,8 @@
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ModifiableRootModel;
-import com.intellij.openapi.roots.OrderEnumerator;
+import com.intellij.openapi.roots.impl.libraries.ProjectLibraryTable;
+import com.intellij.openapi.roots.libraries.Library;
import com.intellij.openapi.roots.ui.configuration.libraryEditor.ExistingLibraryEditor;
import java.util.Set;
import javax.annotation.Nullable;
@@ -70,20 +71,17 @@
if (!blazeProjectData.workspaceLanguageSettings.isLanguageActive(LanguageClass.SCALA)) {
return;
}
- OrderEnumerator.orderEntries(workspaceModule)
- .forEachLibrary(
- library -> {
- // Convert the type of the SDK library to prevent the scala plugin from
- // showing the missing SDK notification.
- // TODO: use a canonical class in the SDK (e.g., scala.App) instead of the name?
- if (library.getName() != null && library.getName().startsWith("scala-library")) {
- ExistingLibraryEditor editor = new ExistingLibraryEditor(library, null);
- editor.setType(ScalaLibraryType.instance());
- editor.commit();
- return false; // stop
- }
- return true; // continue
- });
+ for (Library library : ProjectLibraryTable.getInstance(project).getLibraries()) {
+ // Convert the type of the SDK library to prevent the scala plugin from
+ // showing the missing SDK notification.
+ // TODO: use a canonical class in the SDK (e.g., scala.App) instead of the name?
+ if (library.getName() != null && library.getName().startsWith("scala-library")) {
+ ExistingLibraryEditor editor = new ExistingLibraryEditor(library, null);
+ editor.setType(ScalaLibraryType.instance());
+ editor.commit();
+ return;
+ }
+ }
}
@Override
diff --git a/scala/src/com/google/idea/blaze/scala/sync/importer/BlazeScalaWorkspaceImporter.java b/scala/src/com/google/idea/blaze/scala/sync/importer/BlazeScalaWorkspaceImporter.java
index 5410f2c..b4fcee4 100644
--- a/scala/src/com/google/idea/blaze/scala/sync/importer/BlazeScalaWorkspaceImporter.java
+++ b/scala/src/com/google/idea/blaze/scala/sync/importer/BlazeScalaWorkspaceImporter.java
@@ -16,6 +16,7 @@
package com.google.idea.blaze.scala.sync.importer;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
import com.google.idea.blaze.base.ideinfo.TargetKey;
import com.google.idea.blaze.base.ideinfo.TargetMap;
@@ -26,11 +27,13 @@
import com.google.idea.blaze.base.projectview.ProjectViewSet;
import com.google.idea.blaze.base.sync.projectview.ProjectViewTargetImportFilter;
import com.google.idea.blaze.base.targetmaps.TransitiveDependencyMap;
+import com.google.idea.blaze.java.sync.importer.JavaSourceFilter;
import com.google.idea.blaze.java.sync.model.BlazeJarLibrary;
import com.google.idea.blaze.scala.sync.model.BlazeScalaImportResult;
import com.intellij.openapi.project.Project;
import java.util.Collection;
import java.util.List;
+import java.util.Map;
import java.util.stream.Collectors;
/** Builds a BlazeWorkspace. */
@@ -66,7 +69,7 @@
.map(target -> target.key)
.collect(Collectors.toList());
- ImmutableMap.Builder<LibraryKey, BlazeJarLibrary> libraries = ImmutableMap.builder();
+ Map<LibraryKey, BlazeJarLibrary> libraries = Maps.newHashMap();
// Add every jar in the transitive closure of dependencies.
// Direct dependencies of the working set will be double counted by BlazeJavaWorkspaceImporter,
@@ -78,7 +81,7 @@
continue;
}
// Except source targets.
- if (importFilter.isSourceTarget(target)) {
+ if (importFilter.isSourceTarget(target) && JavaSourceFilter.canImportAsSource(target)) {
continue;
}
if (target.javaIdeInfo != null) {
@@ -87,10 +90,10 @@
.jars
.stream()
.map(BlazeJarLibrary::new)
- .forEach(library -> libraries.put(library.key, library));
+ .forEach(library -> libraries.putIfAbsent(library.key, library));
}
}
- return new BlazeScalaImportResult(libraries.build());
+ return new BlazeScalaImportResult(ImmutableMap.copyOf(libraries));
}
}
diff --git a/scala/tests/integrationtests/com/google/idea/blaze/scala/run/producers/BlazeScalaMainClassConfigurationProducerTest.java b/scala/tests/integrationtests/com/google/idea/blaze/scala/run/producers/BlazeScalaMainClassConfigurationProducerTest.java
new file mode 100644
index 0000000..66d7e78
--- /dev/null
+++ b/scala/tests/integrationtests/com/google/idea/blaze/scala/run/producers/BlazeScalaMainClassConfigurationProducerTest.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.scala.run.producers;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.idea.blaze.base.ideinfo.JavaIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetMapBuilder;
+import com.google.idea.blaze.base.model.MockBlazeProjectDataBuilder;
+import com.google.idea.blaze.base.model.MockBlazeProjectDataManager;
+import com.google.idea.blaze.base.model.primitives.TargetExpression;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
+import com.google.idea.blaze.base.run.BlazeRunConfiguration;
+import com.google.idea.blaze.base.run.producer.BlazeRunConfigurationProducerTestCase;
+import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
+import com.intellij.execution.configurations.RunConfiguration;
+import com.intellij.psi.PsiFile;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Integration tests for {@link BlazeScalaMainClassRunConfigurationProducer}. */
+@RunWith(JUnit4.class)
+public class BlazeScalaMainClassConfigurationProducerTest
+ extends BlazeRunConfigurationProducerTestCase {
+
+ @Test
+ public void testUniqueScalaBinaryChosen() {
+ MockBlazeProjectDataBuilder builder = MockBlazeProjectDataBuilder.builder(workspaceRoot);
+ builder.setTargetMap(
+ TargetMapBuilder.builder()
+ .addTarget(
+ TargetIdeInfo.builder()
+ .setKind("scala_binary")
+ .setLabel("//com/google/binary:UnrelatedName")
+ .addSource(sourceRoot("com/google/binary/MainClass.scala"))
+ .build())
+ .build());
+ registerProjectService(
+ BlazeProjectDataManager.class, new MockBlazeProjectDataManager(builder.build()));
+
+ PsiFile scalaFile =
+ createAndIndexFile(
+ WorkspacePath.createIfValid("com/google/binary/MainClass.scala"),
+ "package com.google.binary {",
+ " object MainClass {",
+ " def main(args: Array[String]) {}",
+ " }",
+ "}",
+ "package scala { final class Array[T] {} }",
+ "package java.lang { public final class String {} }");
+
+ RunConfiguration config = createConfigurationFromLocation(scalaFile);
+
+ assertThat(config).isInstanceOf(BlazeRunConfiguration.class);
+ BlazeRunConfiguration blazeConfig = (BlazeRunConfiguration) config;
+ assertThat(blazeConfig).isNotNull();
+ assertThat(blazeConfig.getTarget())
+ .isEqualTo(TargetExpression.fromString("//com/google/binary:UnrelatedName"));
+ }
+
+ @Test
+ public void testNoScalaBinaryChosenIfNotInRDeps() {
+ MockBlazeProjectDataBuilder builder = MockBlazeProjectDataBuilder.builder(workspaceRoot);
+ builder.setTargetMap(
+ TargetMapBuilder.builder()
+ .addTarget(
+ TargetIdeInfo.builder()
+ .setKind("scala_binary")
+ .setLabel("//com/google/binary:MainClass")
+ .addSource(sourceRoot("com/google/binary/OtherClass.scala"))
+ .build())
+ .build());
+ registerProjectService(
+ BlazeProjectDataManager.class, new MockBlazeProjectDataManager(builder.build()));
+
+ PsiFile scalaFile =
+ createAndIndexFile(
+ WorkspacePath.createIfValid("com/google/binary/MainClass.scala"),
+ "package com.google.binary {",
+ " object MainClass {",
+ " def main(args: Array[String]) {}",
+ " }",
+ "}",
+ "package scala { final class Array[T] {} }",
+ "package java.lang { public final class String {} }");
+
+ assertThat(createConfigurationFromLocation(scalaFile))
+ .isNotInstanceOf(BlazeRunConfiguration.class);
+ }
+
+ @Test
+ public void testNoResultForObjectWithoutMainMethod() {
+ MockBlazeProjectDataBuilder builder = MockBlazeProjectDataBuilder.builder(workspaceRoot);
+ builder.setTargetMap(
+ TargetMapBuilder.builder()
+ .addTarget(
+ TargetIdeInfo.builder()
+ .setKind("scala_binary")
+ .setLabel("//com/google/binary:MainClass")
+ .addSource(sourceRoot("com/google/binary/MainClass.scala"))
+ .setJavaInfo(JavaIdeInfo.builder().setMainClass("com.google.binary.MainClass"))
+ .build())
+ .build());
+ registerProjectService(
+ BlazeProjectDataManager.class, new MockBlazeProjectDataManager(builder.build()));
+
+ PsiFile scalaFile =
+ createAndIndexFile(
+ WorkspacePath.createIfValid("com/google/binary/MainClass.scala"),
+ "package com.google.binary { object MainClass {} }",
+ "package scala { final class Array[T] {} }",
+ "package java.lang { public final class String {} }");
+
+ assertThat(createConfigurationFromLocation(scalaFile)).isNull();
+ }
+
+ @Test
+ public void testScalaBinaryWithMatchingNameChosen() {
+ MockBlazeProjectDataBuilder builder = MockBlazeProjectDataBuilder.builder(workspaceRoot);
+ builder.setTargetMap(
+ TargetMapBuilder.builder()
+ .addTarget(
+ TargetIdeInfo.builder()
+ .setKind("scala_binary")
+ .setLabel("//com/google/binary:UnrelatedName")
+ .addSource(sourceRoot("com/google/binary/MainClass.scala"))
+ .build())
+ .addTarget(
+ TargetIdeInfo.builder()
+ .setKind("scala_binary")
+ .setLabel("//com/google/binary:MainClass")
+ .addSource(sourceRoot("com/google/binary/MainClass.scala"))
+ .build())
+ .build());
+ registerProjectService(
+ BlazeProjectDataManager.class, new MockBlazeProjectDataManager(builder.build()));
+
+ PsiFile scalaFile =
+ createAndIndexFile(
+ WorkspacePath.createIfValid("com/google/binary/MainClass.scala"),
+ "package com.google.binary {",
+ " object MainClass {",
+ " def main(args: Array[String]) {}",
+ " }",
+ "}",
+ "package scala { final class Array[T] {} }",
+ "package java.lang { public final class String {} }");
+
+ RunConfiguration config = createConfigurationFromLocation(scalaFile);
+ assertThat(config).isInstanceOf(BlazeRunConfiguration.class);
+ BlazeRunConfiguration blazeConfig = (BlazeRunConfiguration) config;
+ assertThat(blazeConfig).isNotNull();
+ assertThat(blazeConfig.getTarget())
+ .isEqualTo(TargetExpression.fromString("//com/google/binary:MainClass"));
+ }
+
+ @Test
+ public void testScalaBinaryWithMatchingMainClassChosen() {
+ MockBlazeProjectDataBuilder builder = MockBlazeProjectDataBuilder.builder(workspaceRoot);
+ builder.setTargetMap(
+ TargetMapBuilder.builder()
+ .addTarget(
+ TargetIdeInfo.builder()
+ .setKind("scala_binary")
+ .setLabel("//com/google/binary:UnrelatedName")
+ .addSource(sourceRoot("com/google/binary/MainClass.scala"))
+ .build())
+ .addTarget(
+ TargetIdeInfo.builder()
+ .setKind("scala_binary")
+ .setLabel("//com/google/binary:OtherName")
+ .setJavaInfo(JavaIdeInfo.builder().setMainClass("com.google.binary.MainClass"))
+ .addSource(sourceRoot("com/google/binary/MainClass.scala"))
+ .build())
+ .build());
+ registerProjectService(
+ BlazeProjectDataManager.class, new MockBlazeProjectDataManager(builder.build()));
+
+ PsiFile scalaFile =
+ createAndIndexFile(
+ WorkspacePath.createIfValid("com/google/binary/MainClass.scala"),
+ "package com.google.binary {",
+ " object MainClass {",
+ " def main(args: Array[String]) {}",
+ " }",
+ "}",
+ "package scala { final class Array[T] {} }",
+ "package java.lang { public final class String {} }");
+
+ RunConfiguration config = createConfigurationFromLocation(scalaFile);
+
+ assertThat(config).isInstanceOf(BlazeRunConfiguration.class);
+ BlazeRunConfiguration blazeConfig = (BlazeRunConfiguration) config;
+ assertThat(blazeConfig).isNotNull();
+ assertThat(blazeConfig.getTarget())
+ .isEqualTo(TargetExpression.fromString("//com/google/binary:OtherName"));
+ }
+}
diff --git a/scala/tests/integrationtests/com/google/idea/blaze/scala/run/producers/BlazeScalaRunLineMarkerContributorTest.java b/scala/tests/integrationtests/com/google/idea/blaze/scala/run/producers/BlazeScalaRunLineMarkerContributorTest.java
new file mode 100644
index 0000000..f65f44b
--- /dev/null
+++ b/scala/tests/integrationtests/com/google/idea/blaze/scala/run/producers/BlazeScalaRunLineMarkerContributorTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.scala.run.producers;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.base.Objects;
+import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
+import com.google.idea.blaze.base.run.producer.BlazeRunConfigurationProducerTestCase;
+import com.intellij.execution.lineMarker.RunLineMarkerContributor.Info;
+import com.intellij.icons.AllIcons;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.impl.source.tree.LeafPsiElement;
+import java.util.List;
+import org.jetbrains.plugins.scala.runner.ScalaRunLineMarkerContributor;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Integration tests for {@link BlazeScalaRunLineMarkerContributor} */
+@RunWith(JUnit4.class)
+public class BlazeScalaRunLineMarkerContributorTest extends BlazeRunConfigurationProducerTestCase {
+
+ @Test
+ public void testGetMainInfo() {
+ BlazeScalaRunLineMarkerContributor markerContributor = new BlazeScalaRunLineMarkerContributor();
+ ScalaRunLineMarkerContributor replacedContributor = new ScalaRunLineMarkerContributor();
+
+ PsiFile scalaFile =
+ createAndIndexFile(
+ WorkspacePath.createIfValid("com/google/binary/MainClass.scala"),
+ "package com.google.binary {",
+ " object MainClass {",
+ " def main(args: Array[String]) {}",
+ " def foo() {}",
+ " }",
+ "}",
+ "package scala { final class Array[T] {} }",
+ "package java.lang { public final class String {} }");
+ List<LeafPsiElement> elements =
+ PsiUtils.findAllChildrenOfClassRecursive(scalaFile, LeafPsiElement.class);
+ LeafPsiElement objectIdentifier =
+ elements
+ .stream()
+ .filter(e -> Objects.equal(e.getText(), "MainClass"))
+ .findFirst()
+ .orElse(null);
+ LeafPsiElement methodIdentifier =
+ elements.stream().filter(e -> Objects.equal(e.getText(), "main")).findFirst().orElse(null);
+ assertThat(objectIdentifier).isNotNull();
+ assertThat(methodIdentifier).isNotNull();
+
+ // Have main object info.
+ Info objectInfo = markerContributor.getInfo(objectIdentifier);
+ assertThat(objectInfo).isNotNull();
+ assertThat(objectInfo.icon).isEqualTo(AllIcons.RunConfigurations.TestState.Run);
+ assertThat(objectInfo.actions).hasLength(2);
+ assertThat(objectInfo.actions[0].getTemplatePresentation().getText()).startsWith("Run ");
+ assertThat(objectInfo.actions[1].getTemplatePresentation().getText()).startsWith("Debug ");
+
+ // Main object info replaces the one from the scala plugin
+ Info replacedObjectInfo = replacedContributor.getInfo(objectIdentifier);
+ assertThat(replacedObjectInfo).isNotNull();
+ assertThat(objectInfo.shouldReplace(replacedObjectInfo)).isTrue();
+
+ // Hae main method info
+ Info methodInfo = markerContributor.getInfo(methodIdentifier);
+ assertThat(methodInfo).isNotNull();
+ assertThat(methodInfo.icon).isEqualTo(AllIcons.RunConfigurations.TestState.Run);
+ assertThat(methodInfo.actions).hasLength(2);
+ assertThat(methodInfo.actions[0].getTemplatePresentation().getText()).startsWith("Run ");
+ assertThat(methodInfo.actions[1].getTemplatePresentation().getText()).startsWith("Debug ");
+
+ // Main method info replaces the one from the scala plugin
+ Info replacedMethodInfo = replacedContributor.getInfo(methodIdentifier);
+ assertThat(replacedMethodInfo).isNotNull();
+ assertThat(methodInfo.shouldReplace(replacedMethodInfo)).isTrue();
+
+ // No other element should get an info
+ elements
+ .stream()
+ .filter(e -> !Objects.equal(e, objectIdentifier) && !Objects.equal(e, methodIdentifier))
+ .forEach(e -> assertThat(markerContributor.getInfo(e)).isNull());
+ }
+}
diff --git a/scala/tests/integrationtests/com/google/idea/blaze/scala/run/producers/BlazeScalaTestClassConfigurationProducerTest.java b/scala/tests/integrationtests/com/google/idea/blaze/scala/run/producers/BlazeScalaTestClassConfigurationProducerTest.java
new file mode 100644
index 0000000..1bbe794
--- /dev/null
+++ b/scala/tests/integrationtests/com/google/idea/blaze/scala/run/producers/BlazeScalaTestClassConfigurationProducerTest.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.scala.run.producers;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.idea.blaze.base.command.BlazeCommandName;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetMapBuilder;
+import com.google.idea.blaze.base.model.MockBlazeProjectDataBuilder;
+import com.google.idea.blaze.base.model.MockBlazeProjectDataManager;
+import com.google.idea.blaze.base.model.primitives.TargetExpression;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
+import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
+import com.google.idea.blaze.base.run.producer.BlazeRunConfigurationProducerTestCase;
+import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
+import com.google.idea.blaze.java.run.producers.BlazeJavaTestClassConfigurationProducer;
+import com.intellij.execution.actions.ConfigurationContext;
+import com.intellij.execution.actions.ConfigurationFromContext;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiFile;
+import java.util.List;
+import org.jetbrains.plugins.scala.lang.psi.api.ScalaFile;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Integration tests for {@link BlazeScalaTestClassConfigurationProducer} and {@link
+ * BlazeJavaTestClassConfigurationProducer}.
+ */
+@RunWith(JUnit4.class)
+public class BlazeScalaTestClassConfigurationProducerTest
+ extends BlazeRunConfigurationProducerTestCase {
+
+ @Test
+ public void testJunitTestProducedFromPsiClass() {
+ PsiFile file =
+ createAndIndexFile(
+ new WorkspacePath("scala/com/google/test/TestClass.scala"),
+ "package com.google.test {",
+ " class TestClass {",
+ " @org.junit.Test",
+ " def testMethod() {}",
+ " }",
+ "}",
+ "package org.junit { trait Test }");
+ assertThat(file).isInstanceOf(ScalaFile.class);
+ ScalaFile scalaFile = (ScalaFile) file;
+ PsiClass[] classes = scalaFile.getClasses();
+ assertThat(classes).isNotEmpty();
+ PsiClass testClass = classes[0];
+
+ MockBlazeProjectDataBuilder builder = MockBlazeProjectDataBuilder.builder(workspaceRoot);
+ builder.setTargetMap(
+ TargetMapBuilder.builder()
+ .addTarget(
+ TargetIdeInfo.builder()
+ .setKind("scala_junit_test")
+ .setLabel("//scala/com/google/test:TestClass")
+ .addSource(sourceRoot("scala/com/google/test/TestClass.scala"))
+ .build())
+ .build());
+ registerProjectService(
+ BlazeProjectDataManager.class, new MockBlazeProjectDataManager(builder.build()));
+
+ ConfigurationContext context = createContextFromPsi(testClass);
+ List<ConfigurationFromContext> configurations = context.getConfigurationsFromContext();
+ assertThat(configurations).isNotNull();
+ assertThat(configurations).hasSize(1);
+ ConfigurationFromContext fromContext = configurations.get(0);
+ assertThat(fromContext.isProducedBy(BlazeJavaTestClassConfigurationProducer.class)).isTrue();
+ assertThat(fromContext.getConfiguration()).isInstanceOf(BlazeCommandRunConfiguration.class);
+
+ BlazeCommandRunConfiguration config =
+ (BlazeCommandRunConfiguration) fromContext.getConfiguration();
+ assertThat(config.getTarget())
+ .isEqualTo(TargetExpression.fromString("//scala/com/google/test:TestClass"));
+ assertThat(getTestFilterContents(config)).isEqualTo("--test_filter=com.google.test.TestClass#");
+ assertThat(config.getName()).isEqualTo("Blaze test TestClass");
+ assertThat(getCommandType(config)).isEqualTo(BlazeCommandName.TEST);
+ }
+
+ @Test
+ public void testSpecs2TestProducedFromPsiClass() {
+ createAndIndexFile(
+ WorkspacePath.createIfValid("scala/org/junit/runner/RunWith.scala"),
+ "package org.junit.runner",
+ "class RunWith");
+ createAndIndexFile(
+ WorkspacePath.createIfValid("scala/org/specs2/runner/JUnitRunner.scala"),
+ "package org.specs2.runner",
+ "class JUnitRunner");
+ createAndIndexFile(
+ WorkspacePath.createIfValid("scala/org/specs2/mutable/SpecificationWithJUnit.scala"),
+ "package org.specs2.mutable",
+ "@org.junit.runner.RunWith(classOf[org.specs2.runner.JUnitRunner])",
+ "abstract class SpecificationWithJUnit extends org.specs2.mutable.Specification");
+ createAndIndexFile(
+ WorkspacePath.createIfValid("scala/org/specs2/mutable/Specification.scala"),
+ "package org.specs2.mutable",
+ "abstract class Specification extends org.specs2.mutable.SpecificationLike");
+ createAndIndexFile(
+ WorkspacePath.createIfValid("scala/org/specs2/mutable/SpecificationLike.scala"),
+ "package org.specs2.mutable",
+ "trait SpecificationLike extends",
+ "org.specs2.specification.core.mutable.SpecificationStructure");
+ createAndIndexFile(
+ WorkspacePath.createIfValid(
+ "scala/org/specs2/specification/core/mutable/SpecificationStructure.scala"),
+ "package org.specs2.specification.core.mutable",
+ "trait SpecificationStructure extends",
+ "org.specs2.specification.core.SpecificationStructure");
+ createAndIndexFile(
+ WorkspacePath.createIfValid(
+ "scala/org/specs2/specification/core/SpecificationStructure.scala"),
+ "package org.specs2.specification.core",
+ "trait SpecificationStructure");
+ PsiFile file =
+ createAndIndexFile(
+ WorkspacePath.createIfValid("scala/com/google/test/TestClass.scala"),
+ "package com.google.test",
+ "class TestClass extends org.specs2.mutable.SpecificationWithJUnit");
+ assertThat(file).isInstanceOf(ScalaFile.class);
+ ScalaFile scalaFile = (ScalaFile) file;
+ PsiClass[] classes = scalaFile.getClasses();
+ assertThat(classes).isNotEmpty();
+ PsiClass testClass = classes[0];
+
+ MockBlazeProjectDataBuilder builder = MockBlazeProjectDataBuilder.builder(workspaceRoot);
+ builder.setTargetMap(
+ TargetMapBuilder.builder()
+ .addTarget(
+ TargetIdeInfo.builder()
+ .setKind("scala_junit_test")
+ .setLabel("//scala/com/google/test:TestClass")
+ .addSource(sourceRoot("scala/com/google/test/TestClass.scala"))
+ .build())
+ .build());
+ registerProjectService(
+ BlazeProjectDataManager.class, new MockBlazeProjectDataManager(builder.build()));
+
+ ConfigurationContext context = createContextFromPsi(testClass);
+ List<ConfigurationFromContext> configurations = context.getConfigurationsFromContext();
+ assertThat(configurations).isNotNull();
+ assertThat(configurations).hasSize(1);
+ ConfigurationFromContext fromContext = configurations.get(0);
+ assertThat(fromContext.isProducedBy(BlazeJavaTestClassConfigurationProducer.class)).isTrue();
+ assertThat(fromContext.getConfiguration()).isInstanceOf(BlazeCommandRunConfiguration.class);
+
+ BlazeCommandRunConfiguration config =
+ (BlazeCommandRunConfiguration) fromContext.getConfiguration();
+ assertThat(config.getTarget())
+ .isEqualTo(TargetExpression.fromString("//scala/com/google/test:TestClass"));
+ assertThat(getTestFilterContents(config)).isEqualTo("--test_filter=com.google.test.TestClass#");
+ assertThat(config.getName()).isEqualTo("Blaze test TestClass");
+ assertThat(getCommandType(config)).isEqualTo(BlazeCommandName.TEST);
+ }
+
+ @Test
+ public void testScalaTestProducedFromPsiClass() {
+ PsiFile file =
+ createAndIndexFile(
+ WorkspacePath.createIfValid("scala/com/google/test/TestClass.scala"),
+ "package com.google.test {",
+ " class TestClass extends org.scalatest.FlatSpec {",
+ " \"this test\" should \"pass\" in {}",
+ " }",
+ "}",
+ "package org.scalatest {",
+ " trait FlatSpec extends Suite",
+ " trait Suite",
+ "}");
+ assertThat(file).isInstanceOf(ScalaFile.class);
+ ScalaFile scalaFile = (ScalaFile) file;
+ PsiClass[] classes = scalaFile.getClasses();
+ assertThat(classes).isNotEmpty();
+ PsiClass testClass = classes[0];
+
+ MockBlazeProjectDataBuilder builder = MockBlazeProjectDataBuilder.builder(workspaceRoot);
+ builder.setTargetMap(
+ TargetMapBuilder.builder()
+ .addTarget(
+ TargetIdeInfo.builder()
+ .setKind("scala_test")
+ .setLabel("//scala/com/google/test:TestClass")
+ .addSource(sourceRoot("scala/com/google/test/TestClass.scala"))
+ .build())
+ .build());
+ registerProjectService(
+ BlazeProjectDataManager.class, new MockBlazeProjectDataManager(builder.build()));
+
+ ConfigurationContext context = createContextFromPsi(testClass);
+ List<ConfigurationFromContext> configurations = context.getConfigurationsFromContext();
+ assertThat(configurations).isNotNull();
+ assertThat(configurations).hasSize(1);
+ ConfigurationFromContext fromContext = configurations.get(0);
+ assertThat(fromContext.isProducedBy(BlazeScalaTestClassConfigurationProducer.class)).isTrue();
+ assertThat(fromContext.getConfiguration()).isInstanceOf(BlazeCommandRunConfiguration.class);
+
+ BlazeCommandRunConfiguration config =
+ (BlazeCommandRunConfiguration) fromContext.getConfiguration();
+ assertThat(config.getTarget())
+ .isEqualTo(TargetExpression.fromString("//scala/com/google/test:TestClass"));
+ assertThat(getTestFilterContents(config)).isEqualTo("--test_filter=com.google.test.TestClass");
+ assertThat(config.getName()).isEqualTo("Blaze test TestClass");
+ assertThat(getCommandType(config)).isEqualTo(BlazeCommandName.TEST);
+ }
+}
diff --git a/scala/tests/integrationtests/com/google/idea/blaze/scala/run/producers/BlazeScalaTestRunLineMarkerContributorTest.java b/scala/tests/integrationtests/com/google/idea/blaze/scala/run/producers/BlazeScalaTestRunLineMarkerContributorTest.java
new file mode 100644
index 0000000..b0dc3f1
--- /dev/null
+++ b/scala/tests/integrationtests/com/google/idea/blaze/scala/run/producers/BlazeScalaTestRunLineMarkerContributorTest.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.scala.run.producers;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.base.Objects;
+import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
+import com.google.idea.blaze.base.run.producer.BlazeRunConfigurationProducerTestCase;
+import com.intellij.execution.lineMarker.RunLineMarkerContributor.Info;
+import com.intellij.icons.AllIcons;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.impl.source.tree.LeafPsiElement;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Integration tests for {@link BlazeScalaTestRunLineMarkerContributor} */
+@RunWith(JUnit4.class)
+public class BlazeScalaTestRunLineMarkerContributorTest
+ extends BlazeRunConfigurationProducerTestCase {
+ private final BlazeScalaTestRunLineMarkerContributor markerContributor =
+ new BlazeScalaTestRunLineMarkerContributor();
+
+ @Test
+ public void testIgnoreNonTest() {
+ PsiFile scalaFile =
+ createAndIndexFile(
+ WorkspacePath.createIfValid("src/main/scala/com/google/library/Library.scala"),
+ "package com.google.library",
+ "class Library {",
+ " def method() {}",
+ "}");
+ List<LeafPsiElement> elements =
+ PsiUtils.findAllChildrenOfClassRecursive(scalaFile, LeafPsiElement.class);
+ elements.forEach(e -> assertThat(markerContributor.getInfo(e)).isNull());
+ }
+
+ @Test
+ public void testGetJunitTestInfo() {
+ PsiFile junitTestFile =
+ createAndIndexFile(
+ WorkspacePath.createIfValid("src/test/scala/com/google/test/JunitTest.scala"),
+ "package com.google.test {",
+ " class JunitTest {",
+ " @org.junit.Test",
+ " def testMethod() {}",
+ " }",
+ "}",
+ "package org.junit { trait Test }");
+ List<LeafPsiElement> elements =
+ PsiUtils.findAllChildrenOfClassRecursive(junitTestFile, LeafPsiElement.class);
+ LeafPsiElement classIdentifier =
+ elements
+ .stream()
+ .filter(e -> Objects.equal(e.getText(), "JunitTest"))
+ .findFirst()
+ .orElse(null);
+ LeafPsiElement methodIdentifier =
+ elements
+ .stream()
+ .filter(e -> Objects.equal(e.getText(), "testMethod"))
+ .findFirst()
+ .orElse(null);
+ assertThat(classIdentifier).isNotNull();
+ assertThat(methodIdentifier).isNotNull();
+
+ Info classInfo = markerContributor.getInfo(classIdentifier);
+ assertThat(classInfo).isNotNull();
+ assertThat(classInfo.icon).isEqualTo(AllIcons.RunConfigurations.TestState.Run_run);
+ assertThat(classInfo.actions).hasLength(2);
+ assertThat(classInfo.actions[0].getTemplatePresentation().getText()).startsWith("Run ");
+ assertThat(classInfo.actions[1].getTemplatePresentation().getText()).startsWith("Debug ");
+
+ Info methodInfo = markerContributor.getInfo(methodIdentifier);
+ assertThat(methodInfo).isNotNull();
+ assertThat(methodInfo.icon).isEqualTo(AllIcons.RunConfigurations.TestState.Run);
+ assertThat(methodInfo.actions).hasLength(2);
+ assertThat(methodInfo.actions[0].getTemplatePresentation().getText()).startsWith("Run ");
+ assertThat(methodInfo.actions[1].getTemplatePresentation().getText()).startsWith("Debug ");
+
+ elements
+ .stream()
+ .filter(e -> !Objects.equal(e, classIdentifier) && !Objects.equal(e, methodIdentifier))
+ .forEach(e -> assertThat(markerContributor.getInfo(e)).isNull());
+ }
+
+ @Test
+ public void testGetSpecs2TestInfo() {
+ createAndIndexFile(
+ WorkspacePath.createIfValid("scala/org/junit/runner/RunWith.scala"),
+ "package org.junit.runner",
+ "class RunWith");
+ createAndIndexFile(
+ WorkspacePath.createIfValid("src/test/scala/org/specs2/runner/JUnitRunner.scala"),
+ "package org.specs2.runner",
+ "class JUnitRunner");
+ createAndIndexFile(
+ WorkspacePath.createIfValid(
+ "src/test/scala/org/specs2/mutable/SpecificationWithJUnit.scala"),
+ "package org.specs2.mutable",
+ "@org.junit.runner.RunWith(classOf[org.specs2.runner.JUnitRunner])",
+ "abstract class SpecificationWithJUnit extends org.specs2.mutable.Specification");
+ createAndIndexFile(
+ WorkspacePath.createIfValid("src/test/scala/org/specs2/mutable/Specification.scala"),
+ "package org.specs2.mutable",
+ "abstract class Specification extends org.specs2.mutable.SpecificationLike");
+ createAndIndexFile(
+ WorkspacePath.createIfValid("src/test/scala/org/specs2/mutable/SpecificationLike.scala"),
+ "package org.specs2.mutable",
+ "trait SpecificationLike extends",
+ "org.specs2.specification.core.mutable.SpecificationStructure");
+ createAndIndexFile(
+ WorkspacePath.createIfValid(
+ "src/test/scala/org/specs2/specification/core/mutable/SpecificationStructure.scala"),
+ "package org.specs2.specification.core.mutable",
+ "trait SpecificationStructure extends",
+ "org.specs2.specification.core.SpecificationStructure");
+ createAndIndexFile(
+ WorkspacePath.createIfValid(
+ "src/test/scala/org/specs2/specification/core/SpecificationStructure.scala"),
+ "package org.specs2.specification.core",
+ "trait SpecificationStructure");
+ PsiFile specs2TestFile =
+ createAndIndexFile(
+ WorkspacePath.createIfValid("src/test/scala/com/google/test/Specs2Test.scala"),
+ "package com.google.test",
+ "class Specs2Test extends org.specs2.mutable.SpecificationWithJUnit");
+ List<LeafPsiElement> elements =
+ PsiUtils.findAllChildrenOfClassRecursive(specs2TestFile, LeafPsiElement.class);
+ LeafPsiElement classIdentifier =
+ elements
+ .stream()
+ .filter(e -> Objects.equal(e.getText(), "Specs2Test"))
+ .findFirst()
+ .orElse(null);
+ assertThat(classIdentifier).isNotNull();
+
+ Info info = markerContributor.getInfo(classIdentifier);
+ assertThat(info).isNotNull();
+ assertThat(info.icon).isEqualTo(AllIcons.RunConfigurations.TestState.Run_run);
+ assertThat(info.actions).hasLength(2);
+ assertThat(info.actions[0].getTemplatePresentation().getText()).startsWith("Run ");
+ assertThat(info.actions[1].getTemplatePresentation().getText()).startsWith("Debug ");
+
+ elements
+ .stream()
+ .filter(e -> !Objects.equal(e, classIdentifier))
+ .forEach(e -> assertThat(markerContributor.getInfo(e)).isNull());
+ }
+
+ @Test
+ public void testGetScalaTestInfo() {
+ PsiFile scalaTestFile =
+ createAndIndexFile(
+ WorkspacePath.createIfValid("src/test/scala/com/google/test/ScalaTest.scala"),
+ "package com.google.test {",
+ " class ScalaTest extends org.scalatest.FlatSpec {",
+ " \"this test\" should \"pass\" in {}",
+ " }",
+ "}",
+ "package org.scalatest {",
+ " trait FlatSpec extends Suite",
+ " trait Suite",
+ "}");
+ List<LeafPsiElement> elements =
+ PsiUtils.findAllChildrenOfClassRecursive(scalaTestFile, LeafPsiElement.class);
+ LeafPsiElement classIdentifier =
+ elements
+ .stream()
+ .filter(e -> Objects.equal(e.getText(), "ScalaTest"))
+ .findFirst()
+ .orElse(null);
+ assertThat(classIdentifier).isNotNull();
+
+ Info info = markerContributor.getInfo(classIdentifier);
+ assertThat(info).isNotNull();
+ assertThat(info.icon).isEqualTo(AllIcons.RunConfigurations.TestState.Run_run);
+ assertThat(info.actions).hasLength(2);
+ assertThat(info.actions[0].getTemplatePresentation().getText()).startsWith("Run ");
+ assertThat(info.actions[1].getTemplatePresentation().getText()).startsWith("Debug ");
+
+ elements
+ .stream()
+ .filter(e -> !Objects.equal(e, classIdentifier))
+ .forEach(e -> assertThat(markerContributor.getInfo(e)).isNull());
+ }
+}
diff --git a/scala/tests/unittests/com/google/idea/blaze/java/sync/source/ScalaSourceDirectoryCalculatorTest.java b/scala/tests/unittests/com/google/idea/blaze/java/sync/source/ScalaSourceDirectoryCalculatorTest.java
index c364c85..b21cfd2 100644
--- a/scala/tests/unittests/com/google/idea/blaze/java/sync/source/ScalaSourceDirectoryCalculatorTest.java
+++ b/scala/tests/unittests/com/google/idea/blaze/java/sync/source/ScalaSourceDirectoryCalculatorTest.java
@@ -23,6 +23,7 @@
import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
import com.google.idea.blaze.base.ideinfo.TargetKey;
import com.google.idea.blaze.base.io.InputStreamProvider;
+import com.google.idea.blaze.base.io.MockInputStreamProvider;
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;
@@ -36,15 +37,8 @@
import com.google.idea.blaze.java.sync.model.BlazeSourceDirectory;
import com.google.idea.blaze.scala.sync.source.ScalaJavaLikeLanguage;
import com.intellij.openapi.extensions.ExtensionPoint;
-import com.intellij.util.containers.HashMap;
-import java.io.ByteArrayInputStream;
import java.io.File;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
import java.util.List;
-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;
@@ -63,8 +57,7 @@
artifactLocation -> new File("/root", artifactLocation.getRelativePath());
@Override
- protected void initTest(
- @NotNull Container applicationServices, @NotNull Container projectServices) {
+ protected void initTest(Container applicationServices, Container projectServices) {
super.initTest(applicationServices, projectServices);
mockInputStreamProvider = new MockInputStreamProvider();
@@ -244,24 +237,4 @@
.build());
issues.assertNoIssues();
}
-
- private static class MockInputStreamProvider implements InputStreamProvider {
-
- private final Map<String, InputStream> files = new HashMap<>();
-
- MockInputStreamProvider addFile(String filePath, String src) {
- try {
- files.put(filePath, new ByteArrayInputStream(src.getBytes("UTF-8")));
- } catch (UnsupportedEncodingException ignored) {
- // ignored
- }
- return this;
- }
-
- @Nullable
- @Override
- public InputStream getFile(@NotNull File path) {
- return files.get(path.getPath());
- }
- }
}
diff --git a/scala/tests/unittests/com/google/idea/blaze/scala/sync/importer/BlazeScalaWorkspaceImporterTest.java b/scala/tests/unittests/com/google/idea/blaze/scala/sync/importer/BlazeScalaWorkspaceImporterTest.java
index 363bcfb..6164d79 100644
--- a/scala/tests/unittests/com/google/idea/blaze/scala/sync/importer/BlazeScalaWorkspaceImporterTest.java
+++ b/scala/tests/unittests/com/google/idea/blaze/scala/sync/importer/BlazeScalaWorkspaceImporterTest.java
@@ -516,6 +516,59 @@
assertThat(scalaImportResult.libraries).isEmpty();
}
+ @Test
+ public void testDuplicateScalaLibraries() {
+ ProjectView projectView =
+ ProjectView.builder()
+ .add(
+ ListSection.builder(DirectorySection.KEY)
+ .add(DirectoryEntry.include(new WorkspacePath("src/main/scala/apps/example"))))
+ .build();
+
+ TargetMap targetMap =
+ TargetMapBuilder.builder()
+ .addTarget(
+ TargetIdeInfo.builder()
+ .setLabel("//src/main/scala/apps/example:example")
+ .setKind("scala_binary")
+ .addSource(source("src/main/scala/apps/example/Main.scala"))
+ .setJavaInfo(
+ JavaIdeInfo.builder()
+ .addJar(
+ LibraryArtifact.builder()
+ .setInterfaceJar(gen("src/main/scala/apps/example/example.jar"))
+ .setClassJar(gen("src/main/scala/apps/example/example.jar"))))
+ .addDependency("//src/main/scala/imports:import1")
+ .addDependency("//src/main/scala/imports:import2"))
+ .addTarget(
+ TargetIdeInfo.builder()
+ .setLabel("//src/main/scala/imports:import1")
+ .setKind("scala_import")
+ .setJavaInfo(
+ JavaIdeInfo.builder()
+ .addJar(
+ LibraryArtifact.builder()
+ .setInterfaceJar(gen("src/main/scala/imports/import.jar"))
+ .setClassJar(gen("src/main/scala/imports/import.jar")))))
+ .addTarget(
+ TargetIdeInfo.builder()
+ .setLabel("//src/main/scala/imports:import2")
+ .setKind("scala_import")
+ .setJavaInfo(
+ JavaIdeInfo.builder()
+ .addJar(
+ LibraryArtifact.builder()
+ .setInterfaceJar(gen("src/main/scala/imports/import.jar"))
+ .setClassJar(gen("src/main/scala/imports/import.jar")))))
+ .build();
+
+ BlazeScalaImportResult scalaImportResult = importScala(projectView, targetMap);
+ errorCollector.assertNoIssues();
+
+ assertThat(scalaImportResult.libraries).hasSize(1);
+ assertThat(hasLibrary(scalaImportResult.libraries, "import")).isTrue();
+ }
+
private static boolean hasLibrary(
Map<LibraryKey, BlazeJarLibrary> libraries, String libraryName) {
return libraries
diff --git a/sdkcompat/BUILD b/sdkcompat/BUILD
index a152a21..1d629d7 100644
--- a/sdkcompat/BUILD
+++ b/sdkcompat/BUILD
@@ -8,12 +8,13 @@
name = "sdkcompat",
visibility = ["//visibility:public"],
exports = select_for_plugin_api({
- "android-studio-2.3.0.8": ["//sdkcompat/v162"],
+ "android-studio-3.0.0.9": ["//sdkcompat/v171"],
"android-studio-2.3.1.0": ["//sdkcompat/v162"],
- "intellij-2017.1.1": ["//sdkcompat/v171"],
- "intellij-2016.3.1": ["//sdkcompat/v163"],
- "clion-162.1967.7": ["//sdkcompat/v162"],
- "clion-2016.3.2": ["//sdkcompat/v163"],
+ "intellij-2017.2.2": ["//sdkcompat/v172"],
+ "intellij-2017.1.5": ["//sdkcompat/v171"],
+ "intellij-ue-2017.2.2": ["//sdkcompat/v172"],
+ "intellij-ue-2017.1.5": ["//sdkcompat/v171"],
+ "clion-2017.2.1": ["//sdkcompat/v172"],
"clion-2017.1.1": ["//sdkcompat/v171"],
}),
)
diff --git a/sdkcompat/v162/BUILD b/sdkcompat/v162/BUILD
index 8eb53e7..1a26f68 100644
--- a/sdkcompat/v162/BUILD
+++ b/sdkcompat/v162/BUILD
@@ -8,25 +8,31 @@
name = "v162",
srcs = glob([
"com/google/idea/sdkcompat/codestyle/**",
+ "com/google/idea/sdkcompat/profile/**",
+ "com/google/idea/sdkcompat/run/**",
"com/google/idea/sdkcompat/smrunner/**",
"com/google/idea/sdkcompat/transactions/**",
+ "com/google/idea/sdkcompat/ui/**",
"com/google/idea/sdkcompat/vcs/**",
]) + select_for_ide(
android_studio = glob([
"com/google/idea/sdkcompat/cidr/**",
+ "com/google/idea/sdkcompat/java/**",
]),
clion = glob([
"com/google/idea/sdkcompat/cidr/**",
"com/google/idea/sdkcompat/clion/**",
]),
intellij = glob([
- "com/google/idea/sdkcompat/python/**",
"com/google/idea/sdkcompat/dart/**",
+ "com/google/idea/sdkcompat/java/**",
+ "com/google/idea/sdkcompat/python/**",
]),
),
visibility = ["//sdkcompat:__pkg__"],
deps = [
"//intellij_platform_sdk:plugin_api",
+ "//intellij_platform_sdk:junit",
"@jsr305_annotations//jar",
] + select_for_ide(
intellij = ["//third_party/python"],
diff --git a/sdkcompat/v162/com/google/idea/sdkcompat/cidr/OCCompilerMacrosAdapter.java b/sdkcompat/v162/com/google/idea/sdkcompat/cidr/OCCompilerMacrosAdapter.java
new file mode 100644
index 0000000..b330ffd
--- /dev/null
+++ b/sdkcompat/v162/com/google/idea/sdkcompat/cidr/OCCompilerMacrosAdapter.java
@@ -0,0 +1,16 @@
+package com.google.idea.sdkcompat.cidr;
+
+import com.intellij.openapi.vfs.VirtualFile;
+import com.jetbrains.cidr.lang.OCLanguageKind;
+import com.jetbrains.cidr.lang.workspace.compiler.OCCompilerMacros;
+import java.util.Map;
+
+/** Adapter to bridge different SDK versions. */
+public abstract class OCCompilerMacrosAdapter extends OCCompilerMacros {
+ // v171
+ public void addAllFeatures(Map<String, String> result, Map<String, String> features) {
+ result.putAll(features);
+ }
+ // v172
+ public abstract String getAllDefines(OCLanguageKind kind, VirtualFile vf);
+}
diff --git a/sdkcompat/v162/com/google/idea/sdkcompat/cidr/OCCompilerSettingsAdapter.java b/sdkcompat/v162/com/google/idea/sdkcompat/cidr/OCCompilerSettingsAdapter.java
new file mode 100644
index 0000000..f4fc622
--- /dev/null
+++ b/sdkcompat/v162/com/google/idea/sdkcompat/cidr/OCCompilerSettingsAdapter.java
@@ -0,0 +1,6 @@
+package com.google.idea.sdkcompat.cidr;
+
+import com.jetbrains.cidr.lang.workspace.compiler.OCCompilerSettings;
+
+/** Adapter to bridge different SDK versions. */
+public abstract class OCCompilerSettingsAdapter extends OCCompilerSettings {}
diff --git a/sdkcompat/v162/com/google/idea/sdkcompat/cidr/OCResolveConfigurationAdapter.java b/sdkcompat/v162/com/google/idea/sdkcompat/cidr/OCResolveConfigurationAdapter.java
index 912eb77..02f0fa7 100644
--- a/sdkcompat/v162/com/google/idea/sdkcompat/cidr/OCResolveConfigurationAdapter.java
+++ b/sdkcompat/v162/com/google/idea/sdkcompat/cidr/OCResolveConfigurationAdapter.java
@@ -1,5 +1,6 @@
package com.google.idea.sdkcompat.cidr;
+import com.intellij.openapi.util.UserDataHolderBase;
import com.intellij.openapi.vfs.VirtualFile;
import com.jetbrains.cidr.lang.OCLanguageKind;
import com.jetbrains.cidr.lang.workspace.OCResolveConfiguration;
@@ -8,13 +9,18 @@
import java.util.Set;
/** Adapter to bridge different SDK versions. */
-public interface OCResolveConfigurationAdapter extends OCResolveConfiguration {
+public abstract class OCResolveConfigurationAdapter extends UserDataHolderBase
+ implements OCResolveConfiguration {
/* v171 */
- public List<VirtualFile> getPrecompiledHeaders(OCLanguageKind kind, VirtualFile sourceFile);
+ public abstract List<VirtualFile> getPrecompiledHeaders(
+ OCLanguageKind kind, VirtualFile sourceFile);
/* v171 */
- public Collection<VirtualFile> getSources();
+ public abstract Collection<VirtualFile> getSources();
/* v171 */
- public Set<VirtualFile> getPrecompiledHeaders();
+ public abstract Set<VirtualFile> getPrecompiledHeaders();
+
+ /* v172 */
+ public abstract String getPreprocessorDefines(OCLanguageKind kind, VirtualFile virtualFile);
}
diff --git a/sdkcompat/v162/com/google/idea/sdkcompat/cidr/OCWorkspaceModificationTrackersCompatUtils.java b/sdkcompat/v162/com/google/idea/sdkcompat/cidr/OCWorkspaceModificationTrackersCompatUtils.java
index 4b17895..79efe83 100644
--- a/sdkcompat/v162/com/google/idea/sdkcompat/cidr/OCWorkspaceModificationTrackersCompatUtils.java
+++ b/sdkcompat/v162/com/google/idea/sdkcompat/cidr/OCWorkspaceModificationTrackersCompatUtils.java
@@ -14,12 +14,28 @@
return trackers.computeIfAbsent(project, OCWorkspaceModificationTrackers::new);
}
- /** Must be called inside a write action, on the EDT. */
+ /**
+ * Causes symbol tables to be rebuilt and invalidates cidr caches attached to resolve
+ * configurations.
+ *
+ * <p>Must be called inside a write action, on the EDT.
+ */
public static void incrementModificationCounts(Project project) {
+ partialIncModificationCounts(project);
+ OCWorkspaceModificationTrackers modTrackers = getTrackers(project);
+ modTrackers.getBuildSettingsChangesTracker().incModificationCount();
+ }
+
+ /**
+ * Does not trigger symbol table rebuilding, and only clears part of the cidr caches attached to
+ * resolve configurations.
+ *
+ * <p>Must be called inside a write action, on the EDT.
+ */
+ public static void partialIncModificationCounts(Project project) {
OCWorkspaceModificationTrackers modTrackers = getTrackers(project);
modTrackers.getProjectFilesListTracker().incModificationCount();
modTrackers.getSourceFilesListTracker().incModificationCount();
modTrackers.getBuildConfigurationChangesTracker().incModificationCount();
- modTrackers.getBuildSettingsChangesTracker().incModificationCount();
}
}
diff --git a/sdkcompat/v162/com/google/idea/sdkcompat/java/JavaConfigurationProducerList.java b/sdkcompat/v162/com/google/idea/sdkcompat/java/JavaConfigurationProducerList.java
new file mode 100644
index 0000000..c20e8bf
--- /dev/null
+++ b/sdkcompat/v162/com/google/idea/sdkcompat/java/JavaConfigurationProducerList.java
@@ -0,0 +1,24 @@
+package com.google.idea.sdkcompat.java;
+
+import com.google.common.collect.ImmutableList;
+import com.intellij.execution.actions.RunConfigurationProducer;
+
+/** List of java-related configuration producers for a given plugin version. */
+public class JavaConfigurationProducerList {
+
+ /**
+ * Returns a list of run configuration producers to suppress for Blaze projects.
+ *
+ * <p>These classes must all be accessible from the Blaze plugin's classpath (e.g. they shouldn't
+ * belong to any plugins not listed as dependencies of the Blaze plugin).
+ */
+ public static final ImmutableList<Class<? extends RunConfigurationProducer<?>>>
+ PRODUCERS_TO_SUPPRESS =
+ ImmutableList.of(
+ com.intellij.execution.junit.AllInDirectoryConfigurationProducer.class,
+ com.intellij.execution.junit.AllInPackageConfigurationProducer.class,
+ com.intellij.execution.junit.TestClassConfigurationProducer.class,
+ com.intellij.execution.junit.TestMethodConfigurationProducer.class,
+ com.intellij.execution.junit.PatternConfigurationProducer.class,
+ com.intellij.execution.application.ApplicationConfigurationProducer.class);
+}
diff --git a/sdkcompat/v162/com/google/idea/sdkcompat/profile/InspectionProfileUtil.java b/sdkcompat/v162/com/google/idea/sdkcompat/profile/InspectionProfileUtil.java
new file mode 100644
index 0000000..60bface
--- /dev/null
+++ b/sdkcompat/v162/com/google/idea/sdkcompat/profile/InspectionProfileUtil.java
@@ -0,0 +1,16 @@
+package com.google.idea.sdkcompat.profile;
+
+import com.intellij.codeInspection.ex.InspectionProfileImpl;
+import com.intellij.openapi.project.Project;
+import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
+
+/** Utility to bridge different SDK versions. */
+public class InspectionProfileUtil {
+
+ public static void disableTool(String toolName, Project project) {
+ InspectionProfileImpl profile =
+ (InspectionProfileImpl)
+ InspectionProjectProfileManager.getInstance(project).getInspectionProfile();
+ profile.disableTool(toolName, project);
+ }
+}
diff --git a/sdkcompat/v162/com/google/idea/sdkcompat/python/PyConfigurationProducersList.java b/sdkcompat/v162/com/google/idea/sdkcompat/python/PyConfigurationProducersList.java
index b3785da..27468aa 100644
--- a/sdkcompat/v162/com/google/idea/sdkcompat/python/PyConfigurationProducersList.java
+++ b/sdkcompat/v162/com/google/idea/sdkcompat/python/PyConfigurationProducersList.java
@@ -1,12 +1,11 @@
package com.google.idea.sdkcompat.python;
import com.google.common.collect.ImmutableList;
-import java.util.Collection;
/** List of python configuration producers for a given plugin version. */
public class PyConfigurationProducersList {
- public static final Collection<Class<?>> PRODUCERS_TO_SUPPRESS =
+ public static final ImmutableList<Class<?>> PRODUCERS_TO_SUPPRESS =
ImmutableList.of(
com.jetbrains.python.run.PythonRunConfigurationProducer.class,
com.jetbrains.python.testing.attest.PythonAtTestConfigurationProducer.class,
diff --git a/sdkcompat/v162/com/google/idea/sdkcompat/python/PythonFacetUtil.java b/sdkcompat/v162/com/google/idea/sdkcompat/python/PythonFacetUtil.java
new file mode 100644
index 0000000..d5a5206
--- /dev/null
+++ b/sdkcompat/v162/com/google/idea/sdkcompat/python/PythonFacetUtil.java
@@ -0,0 +1,31 @@
+package com.google.idea.sdkcompat.python;
+
+import com.intellij.facet.FacetTypeId;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.jetbrains.python.facet.LibraryContributingFacet;
+import com.jetbrains.python.facet.PythonFacet;
+import com.jetbrains.python.facet.PythonFacetType;
+import javax.annotation.Nullable;
+
+/**
+ * This class is a hack to get around Python SDK incompatibilities. Provides a consistent API for
+ * both IntelliJ and CLion.
+ */
+public class PythonFacetUtil {
+ public static FacetTypeId<PythonFacet> getFacetId() {
+ return PythonFacet.ID;
+ }
+
+ public static PythonFacetType getTypeInstance() {
+ return PythonFacetType.getInstance();
+ }
+
+ @Nullable
+ public static Sdk getSdk(LibraryContributingFacet<?> facet) {
+ if (!(facet instanceof PythonFacet)) {
+ return null;
+ }
+ PythonFacet pythonFacet = (PythonFacet) facet;
+ return pythonFacet.getConfiguration().getSdk();
+ }
+}
diff --git a/sdkcompat/v162/com/google/idea/sdkcompat/run/RunnerAndConfigurationSettingsCompatUtils.java b/sdkcompat/v162/com/google/idea/sdkcompat/run/RunnerAndConfigurationSettingsCompatUtils.java
new file mode 100644
index 0000000..09c1c12
--- /dev/null
+++ b/sdkcompat/v162/com/google/idea/sdkcompat/run/RunnerAndConfigurationSettingsCompatUtils.java
@@ -0,0 +1,14 @@
+package com.google.idea.sdkcompat.run;
+
+import com.intellij.execution.impl.RunnerAndConfigurationSettingsImpl;
+import com.intellij.openapi.util.InvalidDataException;
+import org.jdom.Element;
+
+/** SDK compatibility bridge for {@link RunnerAndConfigurationSettingsImpl}. */
+public class RunnerAndConfigurationSettingsCompatUtils {
+
+ public static void readConfiguration(RunnerAndConfigurationSettingsImpl settings, Element element)
+ throws InvalidDataException {
+ settings.readExternal(element);
+ }
+}
diff --git a/sdkcompat/v162/com/google/idea/sdkcompat/transactions/Transactions.java b/sdkcompat/v162/com/google/idea/sdkcompat/transactions/Transactions.java
index 94ab905..8267111 100644
--- a/sdkcompat/v162/com/google/idea/sdkcompat/transactions/Transactions.java
+++ b/sdkcompat/v162/com/google/idea/sdkcompat/transactions/Transactions.java
@@ -4,7 +4,7 @@
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
-/** Created by tomlu on 12/22/16. */
+/** SDK adapter to use transaction guards. */
public class Transactions {
public static void submitTransactionAndWait(Runnable runnable) {
ApplicationManager.getApplication().invokeAndWait(runnable, ModalityState.any());
@@ -13,4 +13,10 @@
public static void submitTransaction(Disposable disposable, Runnable runnable) {
ApplicationManager.getApplication().invokeLater(runnable);
}
+
+ /** Runs {@link Runnable} as a write action, inside a transaction. */
+ public static void submitWriteActionTransactionAndWait(Runnable runnable) {
+ submitTransactionAndWait(
+ (Runnable) () -> ApplicationManager.getApplication().runWriteAction(runnable));
+ }
}
diff --git a/sdkcompat/v162/com/google/idea/sdkcompat/ui/BreadcrumbsProviderSdkCompatAdapter.java b/sdkcompat/v162/com/google/idea/sdkcompat/ui/BreadcrumbsProviderSdkCompatAdapter.java
new file mode 100644
index 0000000..1b41d6d
--- /dev/null
+++ b/sdkcompat/v162/com/google/idea/sdkcompat/ui/BreadcrumbsProviderSdkCompatAdapter.java
@@ -0,0 +1,50 @@
+package com.google.idea.sdkcompat.ui;
+
+import com.intellij.lang.Language;
+import com.intellij.psi.PsiElement;
+import com.intellij.xml.breadcrumbs.BreadcrumbsInfoProvider;
+import java.util.Arrays;
+import org.jetbrains.annotations.Nullable;
+
+/** SDK adapter for {@link BreadcrumbsInfoProvider}, deprecated in 172. */
+public abstract class BreadcrumbsProviderSdkCompatAdapter extends BreadcrumbsInfoProvider {
+
+ public static BreadcrumbsProviderSdkCompatAdapter[] getBreadcrumbsProviders() {
+ return Arrays.stream(BreadcrumbsInfoProvider.EP_NAME.getExtensions())
+ .map(BreadcrumbsProviderSdkCompatAdapter::fromBreadcrumbsProvider)
+ .toArray(BreadcrumbsProviderSdkCompatAdapter[]::new);
+ }
+
+ private static BreadcrumbsProviderSdkCompatAdapter fromBreadcrumbsProvider(
+ BreadcrumbsInfoProvider delegate) {
+ return new BreadcrumbsProviderSdkCompatAdapter() {
+
+ @Override
+ public Language[] getLanguages() {
+ return delegate.getLanguages();
+ }
+
+ @Override
+ public boolean acceptElement(PsiElement psiElement) {
+ return delegate.acceptElement(psiElement);
+ }
+
+ @Override
+ public String getElementInfo(PsiElement psiElement) {
+ return delegate.getElementInfo(psiElement);
+ }
+
+ @Nullable
+ @Override
+ public String getElementTooltip(PsiElement element) {
+ return delegate.getElementTooltip(element);
+ }
+
+ @Nullable
+ @Override
+ public PsiElement getParent(PsiElement element) {
+ return delegate.getParent(element);
+ }
+ };
+ }
+}
diff --git a/sdkcompat/v162/com/google/idea/sdkcompat/ui/RequestFocusCompatUtils.java b/sdkcompat/v162/com/google/idea/sdkcompat/ui/RequestFocusCompatUtils.java
new file mode 100644
index 0000000..c962097
--- /dev/null
+++ b/sdkcompat/v162/com/google/idea/sdkcompat/ui/RequestFocusCompatUtils.java
@@ -0,0 +1,17 @@
+package com.google.idea.sdkcompat.ui;
+
+import java.awt.Component;
+
+/** Works around b/64290580 which affects certain SDK versions. */
+public final class RequestFocusCompatUtils {
+ private RequestFocusCompatUtils() {}
+
+ /**
+ * Focuses the given component.
+ *
+ * @see Component#requestFocus()
+ */
+ public static void requestFocus(Component component) {
+ component.requestFocus();
+ }
+}
diff --git a/sdkcompat/v162/com/google/idea/sdkcompat/vcs/ChangeListManagerGateCompatUtils.java b/sdkcompat/v162/com/google/idea/sdkcompat/vcs/ChangeListManagerGateCompatUtils.java
new file mode 100644
index 0000000..0aebb39
--- /dev/null
+++ b/sdkcompat/v162/com/google/idea/sdkcompat/vcs/ChangeListManagerGateCompatUtils.java
@@ -0,0 +1,32 @@
+package com.google.idea.sdkcompat.vcs;
+
+import com.intellij.openapi.vcs.changes.ChangeListManager;
+import com.intellij.openapi.vcs.changes.ChangeListManagerGate;
+
+/**
+ * Works around b/63888111 / <a href="https://youtrack.jetbrains.com/issue/IJSDK-284">IJSDK-284</a>,
+ * which breaks support for {@link ChangeListManagerGate#editName(String, String)} in some SDK
+ * versions. <br>
+ * <br>
+ * The methods in this class call the corresponding methods on the {@link ChangeListManagerGate}
+ * instead of the {@link ChangeListManager}. It is normal to use this during a VCS update.
+ */
+public final class ChangeListManagerGateCompatUtils {
+ private ChangeListManagerGateCompatUtils() {}
+
+ public static void editName(
+ ChangeListManagerGate addGate,
+ ChangeListManager changeListManager,
+ String oldName,
+ String newName) {
+ addGate.editName(oldName, newName);
+ }
+
+ public static void editComment(
+ ChangeListManagerGate addGate,
+ ChangeListManager changeListManager,
+ String name,
+ String newComment) {
+ addGate.editComment(name, newComment);
+ }
+}
diff --git a/sdkcompat/v162/com/google/idea/sdkcompat/vcs/VcsEditorConfigurationCompatUtils.java b/sdkcompat/v162/com/google/idea/sdkcompat/vcs/VcsEditorConfigurationCompatUtils.java
new file mode 100644
index 0000000..88834c7
--- /dev/null
+++ b/sdkcompat/v162/com/google/idea/sdkcompat/vcs/VcsEditorConfigurationCompatUtils.java
@@ -0,0 +1,21 @@
+package com.google.idea.sdkcompat.vcs;
+
+import com.google.common.collect.ImmutableList;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vcs.VcsConfiguration;
+import com.intellij.spellchecker.ui.SpellCheckingEditorCustomization;
+import com.intellij.ui.EditorCustomization;
+import com.intellij.ui.RightMarginEditorCustomization;
+import java.util.List;
+
+/** Handles VCS changelist editor customizations that differ between SDK versions. */
+public class VcsEditorConfigurationCompatUtils {
+
+ public static List<EditorCustomization> getVcsConfigurationCustomizations(
+ Project project, VcsConfiguration config) {
+ return ImmutableList.of(
+ SpellCheckingEditorCustomization.getInstance(config.CHECK_COMMIT_MESSAGE_SPELLING),
+ new RightMarginEditorCustomization(
+ config.USE_COMMIT_MESSAGE_MARGIN, config.COMMIT_MESSAGE_MARGIN_SIZE));
+ }
+}
diff --git a/sdkcompat/v163/com/google/idea/sdkcompat/cidr/CidrGoogleTestUtilAdapter.java b/sdkcompat/v163/com/google/idea/sdkcompat/cidr/CidrGoogleTestUtilAdapter.java
deleted file mode 100644
index cf3a258..0000000
--- a/sdkcompat/v163/com/google/idea/sdkcompat/cidr/CidrGoogleTestUtilAdapter.java
+++ /dev/null
@@ -1,6 +0,0 @@
-package com.google.idea.sdkcompat.cidr;
-
-import com.jetbrains.cidr.execution.testing.CidrTestUtil;
-
-/** Adapter to bridge different SDK versions. */
-public class CidrGoogleTestUtilAdapter extends CidrTestUtil {}
diff --git a/sdkcompat/v163/com/google/idea/sdkcompat/cidr/CidrSwitchBuilderAdapter.java b/sdkcompat/v163/com/google/idea/sdkcompat/cidr/CidrSwitchBuilderAdapter.java
deleted file mode 100644
index 68772fa..0000000
--- a/sdkcompat/v163/com/google/idea/sdkcompat/cidr/CidrSwitchBuilderAdapter.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.google.idea.sdkcompat.cidr;
-
-import com.google.common.base.Joiner;
-import com.jetbrains.cidr.lang.toolchains.CidrSwitchBuilder;
-import java.util.List;
-import java.util.stream.Collectors;
-
-/** Adapter to bridge different SDK versions. */
-public class CidrSwitchBuilderAdapter extends CidrSwitchBuilder {
- /**
- * Old CidrSwitchBuilder is unable to deal with options with spaces embedded. This is a hack to
- * preserve the old behaviour for 2016.3 Original hack explanation: - 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.
- */
- public CidrSwitchBuilderAdapter addAllRaw(List<String> switches) {
- switches = switches.stream().map(flag -> flag.replace("\\ ", " ")).collect(Collectors.toList());
- addAll(Joiner.on(" ").join(switches), CidrSwitchBuilder.Format.FILE_ARGS);
- return this;
- }
-}
diff --git a/sdkcompat/v163/com/google/idea/sdkcompat/cidr/OCResolveConfigurationAdapter.java b/sdkcompat/v163/com/google/idea/sdkcompat/cidr/OCResolveConfigurationAdapter.java
deleted file mode 100644
index 912eb77..0000000
--- a/sdkcompat/v163/com/google/idea/sdkcompat/cidr/OCResolveConfigurationAdapter.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.google.idea.sdkcompat.cidr;
-
-import com.intellij.openapi.vfs.VirtualFile;
-import com.jetbrains.cidr.lang.OCLanguageKind;
-import com.jetbrains.cidr.lang.workspace.OCResolveConfiguration;
-import java.util.Collection;
-import java.util.List;
-import java.util.Set;
-
-/** Adapter to bridge different SDK versions. */
-public interface OCResolveConfigurationAdapter extends OCResolveConfiguration {
- /* v171 */
- public List<VirtualFile> getPrecompiledHeaders(OCLanguageKind kind, VirtualFile sourceFile);
-
- /* v171 */
- public Collection<VirtualFile> getSources();
-
- /* v171 */
- public Set<VirtualFile> getPrecompiledHeaders();
-}
diff --git a/sdkcompat/v163/com/google/idea/sdkcompat/clion/CMakeConfigurationProducersList.java b/sdkcompat/v163/com/google/idea/sdkcompat/clion/CMakeConfigurationProducersList.java
deleted file mode 100644
index f402269..0000000
--- a/sdkcompat/v163/com/google/idea/sdkcompat/clion/CMakeConfigurationProducersList.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.google.idea.sdkcompat.clion;
-
-import com.google.common.collect.ImmutableList;
-import com.intellij.execution.actions.RunConfigurationProducer;
-import com.jetbrains.cidr.cpp.execution.testing.CMakeGoogleTestRunConfigurationProducer;
-
-/** List of C/C++ configuration producers for a given plugin version. */
-public class CMakeConfigurationProducersList {
- public static final ImmutableList<Class<? extends RunConfigurationProducer<?>>>
- PRODUCERS_TO_SUPPRESS = ImmutableList.of(CMakeGoogleTestRunConfigurationProducer.class);
-}
diff --git a/sdkcompat/v163/com/google/idea/sdkcompat/codestyle/DelegatingCodeStyleManagerSdkCompatAdapter.java b/sdkcompat/v163/com/google/idea/sdkcompat/codestyle/DelegatingCodeStyleManagerSdkCompatAdapter.java
deleted file mode 100644
index 8075c2e..0000000
--- a/sdkcompat/v163/com/google/idea/sdkcompat/codestyle/DelegatingCodeStyleManagerSdkCompatAdapter.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package com.google.idea.sdkcompat.codestyle;
-
-import com.intellij.openapi.util.TextRange;
-import com.intellij.psi.PsiFile;
-import com.intellij.psi.codeStyle.ChangedRangesInfo;
-import com.intellij.psi.codeStyle.CodeStyleManager;
-import com.intellij.util.IncorrectOperationException;
-import java.util.ArrayList;
-import java.util.List;
-import org.jetbrains.annotations.NotNull;
-
-/** Adapter to bridge different SDK versions. */
-public abstract class DelegatingCodeStyleManagerSdkCompatAdapter extends CodeStyleManager {
-
- protected CodeStyleManager delegate;
-
- protected DelegatingCodeStyleManagerSdkCompatAdapter(CodeStyleManager delegate) {
- this.delegate = delegate;
- }
-
- @Override
- public void reformatTextWithContext(@NotNull PsiFile file, @NotNull ChangedRangesInfo info)
- throws IncorrectOperationException {
- List<TextRange> ranges = new ArrayList<>();
- ranges.addAll(info.insertedRanges);
- ranges.addAll(info.allChangedRanges);
- this.reformatTextWithContext(file, ranges);
- }
-}
diff --git a/sdkcompat/v163/com/google/idea/sdkcompat/python/PyConfigurationProducersList.java b/sdkcompat/v163/com/google/idea/sdkcompat/python/PyConfigurationProducersList.java
deleted file mode 100644
index b3785da..0000000
--- a/sdkcompat/v163/com/google/idea/sdkcompat/python/PyConfigurationProducersList.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.google.idea.sdkcompat.python;
-
-import com.google.common.collect.ImmutableList;
-import java.util.Collection;
-
-/** List of python configuration producers for a given plugin version. */
-public class PyConfigurationProducersList {
-
- public static final Collection<Class<?>> PRODUCERS_TO_SUPPRESS =
- ImmutableList.of(
- com.jetbrains.python.run.PythonRunConfigurationProducer.class,
- com.jetbrains.python.testing.attest.PythonAtTestConfigurationProducer.class,
- com.jetbrains.python.testing.nosetest.PythonNoseTestConfigurationProducer.class,
- com.jetbrains.python.testing.doctest.PythonDocTestConfigurationProducer.class,
- com.jetbrains.python.testing.pytest.PyTestConfigurationProducer.class,
- com.jetbrains.python.testing.unittest.PythonUnitTestConfigurationProducer.class);
-}
diff --git a/sdkcompat/v163/com/google/idea/sdkcompat/python/PyQualifiedNameResolveContextAdapter.java b/sdkcompat/v163/com/google/idea/sdkcompat/python/PyQualifiedNameResolveContextAdapter.java
deleted file mode 100644
index 7330e11..0000000
--- a/sdkcompat/v163/com/google/idea/sdkcompat/python/PyQualifiedNameResolveContextAdapter.java
+++ /dev/null
@@ -1,77 +0,0 @@
-package com.google.idea.sdkcompat.python;
-
-import com.intellij.openapi.module.Module;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.projectRoots.Sdk;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiFile;
-import com.intellij.psi.PsiManager;
-import com.jetbrains.python.psi.resolve.QualifiedNameResolveContext;
-import javax.annotation.Nullable;
-
-/** Adapter to bridge different SDK versions. */
-public class PyQualifiedNameResolveContextAdapter extends QualifiedNameResolveContext {
-
- private final QualifiedNameResolveContext delegate;
-
- PyQualifiedNameResolveContextAdapter(QualifiedNameResolveContext delegate) {
- this.delegate = delegate;
- }
-
- @Override
- public void copyFrom(QualifiedNameResolveContext context) {
- delegate.copyFrom(context);
- }
-
- @Override
- public void setFromElement(PsiElement element) {
- delegate.setFromElement(element);
- }
-
- @Override
- public void setFromModule(Module module) {
- delegate.setFromModule(module);
- }
-
- @Override
- public void setFromSdk(Project project, Sdk sdk) {
- delegate.setFromSdk(project, sdk);
- }
-
- @Override
- public void setSdk(Sdk sdk) {
- delegate.setSdk(sdk);
- }
-
- @Override
- @Nullable
- public Module getModule() {
- return delegate.getModule();
- }
-
- @Override
- public boolean isValid() {
- return delegate.isValid();
- }
-
- @Override
- @Nullable
- public PsiFile getFootholdFile() {
- return delegate.getFootholdFile();
- }
-
- @Override
- public PsiManager getPsiManager() {
- return delegate.getPsiManager();
- }
-
- @Override
- public Project getProject() {
- return delegate.getProject();
- }
-
- @Override
- public Sdk getSdk() {
- return delegate.getSdk();
- }
-}
diff --git a/sdkcompat/v163/com/google/idea/sdkcompat/python/PyReferenceResolveProviderAdapter.java b/sdkcompat/v163/com/google/idea/sdkcompat/python/PyReferenceResolveProviderAdapter.java
deleted file mode 100644
index 36d4767..0000000
--- a/sdkcompat/v163/com/google/idea/sdkcompat/python/PyReferenceResolveProviderAdapter.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.google.idea.sdkcompat.python;
-
-import com.jetbrains.python.psi.PyQualifiedExpression;
-import com.jetbrains.python.psi.resolve.PyReferenceResolveProvider;
-import com.jetbrains.python.psi.resolve.RatedResolveResult;
-import com.jetbrains.python.psi.types.TypeEvalContext;
-import java.util.List;
-
-/** Adapter to bridge different SDK versions. */
-public interface PyReferenceResolveProviderAdapter extends PyReferenceResolveProvider {
-
- @Override
- default List<RatedResolveResult> resolveName(PyQualifiedExpression element) {
- TypeEvalContext context = TypeEvalContext.codeInsightFallback(element.getProject());
- return resolveName(element, context);
- }
-
- List<RatedResolveResult> resolveName(PyQualifiedExpression element, TypeEvalContext context);
-}
diff --git a/sdkcompat/v163/com/google/idea/sdkcompat/vcs/MergeDataBuilder.java b/sdkcompat/v163/com/google/idea/sdkcompat/vcs/MergeDataBuilder.java
deleted file mode 100644
index b0a7697..0000000
--- a/sdkcompat/v163/com/google/idea/sdkcompat/vcs/MergeDataBuilder.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package com.google.idea.sdkcompat.vcs;
-
-import com.intellij.openapi.vcs.FilePath;
-import com.intellij.openapi.vcs.history.VcsRevisionNumber;
-import com.intellij.openapi.vcs.merge.MergeData;
-import org.jetbrains.annotations.Nullable;
-
-/** SDK adapter for creating {@link MergeData}. */
-public final class MergeDataBuilder {
- private byte[] baseContent;
- private byte[] theirsContent;
- private byte[] yoursContent;
-
- @Nullable private VcsRevisionNumber theirsRevisionNumber;
-
- public void setBaseContent(byte[] baseContent) {
- this.baseContent = baseContent;
- }
-
- public void setTheirsContent(byte[] theirsContent) {
- this.theirsContent = theirsContent;
- }
-
- public void setYoursContent(byte[] yoursContent) {
- this.yoursContent = yoursContent;
- }
-
- public void setBaseRevisionNumber(@Nullable VcsRevisionNumber baseRevisionNumber) {}
-
- public void setTheirsRevisionNumber(@Nullable VcsRevisionNumber theirsRevisionNumber) {
- this.theirsRevisionNumber = theirsRevisionNumber;
- }
-
- public void setYoursRevisionNumber(@Nullable VcsRevisionNumber yoursRevisionNumber) {}
-
- public void setBaseFilePath(@Nullable FilePath baseFilePath) {}
-
- public void setTheirsFilePath(@Nullable FilePath theirsFilePath) {}
-
- public void setYoursFilePath(@Nullable FilePath yoursFilePath) {}
-
- public MergeData build() {
- MergeData mergeData = new MergeData();
-
- mergeData.ORIGINAL = baseContent;
-
- mergeData.LAST = theirsContent;
- mergeData.LAST_REVISION_NUMBER = theirsRevisionNumber;
-
- mergeData.CURRENT = yoursContent;
-
- return mergeData;
- }
-}
diff --git a/sdkcompat/v171/BUILD b/sdkcompat/v171/BUILD
index d8392a9..de67a83 100644
--- a/sdkcompat/v171/BUILD
+++ b/sdkcompat/v171/BUILD
@@ -8,26 +8,35 @@
name = "v171",
srcs = glob([
"com/google/idea/sdkcompat/codestyle/**",
- "com/google/idea/sdkcompat/python/**",
+ "com/google/idea/sdkcompat/profile/**",
+ "com/google/idea/sdkcompat/run/**",
"com/google/idea/sdkcompat/smrunner/**",
"com/google/idea/sdkcompat/transactions/**",
+ "com/google/idea/sdkcompat/ui/**",
"com/google/idea/sdkcompat/vcs/**",
]) + select_for_ide(
android_studio = glob([
"com/google/idea/sdkcompat/cidr/**",
+ "com/google/idea/sdkcompat/java/**",
]),
clion = glob([
"com/google/idea/sdkcompat/cidr/**",
"com/google/idea/sdkcompat/clion/**",
+ "com/google/idea/sdkcompat/python/**",
]),
intellij = glob([
"com/google/idea/sdkcompat/dart/**",
+ "com/google/idea/sdkcompat/java/**",
+ "com/google/idea/sdkcompat/python/**",
]),
),
visibility = ["//sdkcompat:__pkg__"],
deps = [
"//intellij_platform_sdk:plugin_api",
- "//third_party/python",
+ "//intellij_platform_sdk:junit",
"@jsr305_annotations//jar",
- ],
+ ] + select_for_ide(
+ clion = ["//third_party/python"],
+ intellij = ["//third_party/python"],
+ ),
)
diff --git a/sdkcompat/v171/com/google/idea/sdkcompat/cidr/OCCompilerMacrosAdapter.java b/sdkcompat/v171/com/google/idea/sdkcompat/cidr/OCCompilerMacrosAdapter.java
new file mode 100644
index 0000000..b330ffd
--- /dev/null
+++ b/sdkcompat/v171/com/google/idea/sdkcompat/cidr/OCCompilerMacrosAdapter.java
@@ -0,0 +1,16 @@
+package com.google.idea.sdkcompat.cidr;
+
+import com.intellij.openapi.vfs.VirtualFile;
+import com.jetbrains.cidr.lang.OCLanguageKind;
+import com.jetbrains.cidr.lang.workspace.compiler.OCCompilerMacros;
+import java.util.Map;
+
+/** Adapter to bridge different SDK versions. */
+public abstract class OCCompilerMacrosAdapter extends OCCompilerMacros {
+ // v171
+ public void addAllFeatures(Map<String, String> result, Map<String, String> features) {
+ result.putAll(features);
+ }
+ // v172
+ public abstract String getAllDefines(OCLanguageKind kind, VirtualFile vf);
+}
diff --git a/sdkcompat/v171/com/google/idea/sdkcompat/cidr/OCCompilerSettingsAdapter.java b/sdkcompat/v171/com/google/idea/sdkcompat/cidr/OCCompilerSettingsAdapter.java
new file mode 100644
index 0000000..f4fc622
--- /dev/null
+++ b/sdkcompat/v171/com/google/idea/sdkcompat/cidr/OCCompilerSettingsAdapter.java
@@ -0,0 +1,6 @@
+package com.google.idea.sdkcompat.cidr;
+
+import com.jetbrains.cidr.lang.workspace.compiler.OCCompilerSettings;
+
+/** Adapter to bridge different SDK versions. */
+public abstract class OCCompilerSettingsAdapter extends OCCompilerSettings {}
diff --git a/sdkcompat/v171/com/google/idea/sdkcompat/cidr/OCResolveConfigurationAdapter.java b/sdkcompat/v171/com/google/idea/sdkcompat/cidr/OCResolveConfigurationAdapter.java
index a6bf252..dc6a164 100644
--- a/sdkcompat/v171/com/google/idea/sdkcompat/cidr/OCResolveConfigurationAdapter.java
+++ b/sdkcompat/v171/com/google/idea/sdkcompat/cidr/OCResolveConfigurationAdapter.java
@@ -1,14 +1,19 @@
package com.google.idea.sdkcompat.cidr;
+import com.intellij.openapi.util.UserDataHolderBase;
import com.intellij.openapi.vfs.VirtualFile;
import com.jetbrains.cidr.lang.OCLanguageKind;
import com.jetbrains.cidr.lang.workspace.OCResolveConfiguration;
/** Adapter to bridge different SDK versions. */
-public interface OCResolveConfigurationAdapter extends OCResolveConfiguration {
+public abstract class OCResolveConfigurationAdapter extends UserDataHolderBase
+ implements OCResolveConfiguration {
/* v162/v163 */
- public VirtualFile getPrecompiledHeader();
+ public abstract VirtualFile getPrecompiledHeader();
/* v162/v163 */
- public OCLanguageKind getPrecompiledLanguageKind();
+ public abstract OCLanguageKind getPrecompiledLanguageKind();
+
+ /* v172 */
+ public abstract String getPreprocessorDefines(OCLanguageKind kind, VirtualFile virtualFile);
}
diff --git a/sdkcompat/v171/com/google/idea/sdkcompat/cidr/OCWorkspaceModificationTrackersCompatUtils.java b/sdkcompat/v171/com/google/idea/sdkcompat/cidr/OCWorkspaceModificationTrackersCompatUtils.java
index 1f38a39..651f529 100644
--- a/sdkcompat/v171/com/google/idea/sdkcompat/cidr/OCWorkspaceModificationTrackersCompatUtils.java
+++ b/sdkcompat/v171/com/google/idea/sdkcompat/cidr/OCWorkspaceModificationTrackersCompatUtils.java
@@ -10,12 +10,28 @@
return OCWorkspaceModificationTrackers.getInstance(project);
}
- /** Must be called inside a write action, on the EDT. */
+ /**
+ * Causes symbol tables to be rebuilt and invalidates cidr caches attached to resolve
+ * configurations.
+ *
+ * <p>Must be called inside a write action, on the EDT.
+ */
public static void incrementModificationCounts(Project project) {
+ partialIncModificationCounts(project);
+ OCWorkspaceModificationTrackers modTrackers = getTrackers(project);
+ modTrackers.getBuildSettingsChangesTracker().incModificationCount();
+ }
+
+ /**
+ * Does not trigger symbol table rebuilding, and only clears part of the cidr caches attached to
+ * resolve configurations.
+ *
+ * <p>Must be called inside a write action, on the EDT.
+ */
+ public static void partialIncModificationCounts(Project project) {
OCWorkspaceModificationTrackers modTrackers = getTrackers(project);
modTrackers.getProjectFilesListTracker().incModificationCount();
modTrackers.getSourceFilesListTracker().incModificationCount();
modTrackers.getSelectedResolveConfigurationTracker().incModificationCount();
- modTrackers.getBuildSettingsChangesTracker().incModificationCount();
}
}
diff --git a/sdkcompat/v171/com/google/idea/sdkcompat/codestyle/DelegatingCodeStyleManagerSdkCompatAdapter.java b/sdkcompat/v171/com/google/idea/sdkcompat/codestyle/DelegatingCodeStyleManagerSdkCompatAdapter.java
index d6cbc72..fad4ed5 100644
--- a/sdkcompat/v171/com/google/idea/sdkcompat/codestyle/DelegatingCodeStyleManagerSdkCompatAdapter.java
+++ b/sdkcompat/v171/com/google/idea/sdkcompat/codestyle/DelegatingCodeStyleManagerSdkCompatAdapter.java
@@ -26,7 +26,9 @@
public void reformatTextWithContext(@NotNull PsiFile file, @NotNull ChangedRangesInfo info)
throws IncorrectOperationException {
List<TextRange> ranges = new ArrayList<>();
- ranges.addAll(info.insertedRanges);
+ if (info.insertedRanges != null) {
+ ranges.addAll(info.insertedRanges);
+ }
ranges.addAll(info.allChangedRanges);
this.reformatTextWithContext(file, ranges);
}
diff --git a/sdkcompat/v171/com/google/idea/sdkcompat/java/JavaConfigurationProducerList.java b/sdkcompat/v171/com/google/idea/sdkcompat/java/JavaConfigurationProducerList.java
new file mode 100644
index 0000000..c20e8bf
--- /dev/null
+++ b/sdkcompat/v171/com/google/idea/sdkcompat/java/JavaConfigurationProducerList.java
@@ -0,0 +1,24 @@
+package com.google.idea.sdkcompat.java;
+
+import com.google.common.collect.ImmutableList;
+import com.intellij.execution.actions.RunConfigurationProducer;
+
+/** List of java-related configuration producers for a given plugin version. */
+public class JavaConfigurationProducerList {
+
+ /**
+ * Returns a list of run configuration producers to suppress for Blaze projects.
+ *
+ * <p>These classes must all be accessible from the Blaze plugin's classpath (e.g. they shouldn't
+ * belong to any plugins not listed as dependencies of the Blaze plugin).
+ */
+ public static final ImmutableList<Class<? extends RunConfigurationProducer<?>>>
+ PRODUCERS_TO_SUPPRESS =
+ ImmutableList.of(
+ com.intellij.execution.junit.AllInDirectoryConfigurationProducer.class,
+ com.intellij.execution.junit.AllInPackageConfigurationProducer.class,
+ com.intellij.execution.junit.TestClassConfigurationProducer.class,
+ com.intellij.execution.junit.TestMethodConfigurationProducer.class,
+ com.intellij.execution.junit.PatternConfigurationProducer.class,
+ com.intellij.execution.application.ApplicationConfigurationProducer.class);
+}
diff --git a/sdkcompat/v171/com/google/idea/sdkcompat/profile/InspectionProfileUtil.java b/sdkcompat/v171/com/google/idea/sdkcompat/profile/InspectionProfileUtil.java
new file mode 100644
index 0000000..483f0e7
--- /dev/null
+++ b/sdkcompat/v171/com/google/idea/sdkcompat/profile/InspectionProfileUtil.java
@@ -0,0 +1,15 @@
+package com.google.idea.sdkcompat.profile;
+
+import com.intellij.codeInspection.ex.InspectionProfileImpl;
+import com.intellij.openapi.project.Project;
+import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
+
+/** Utility to bridge different SDK versions. */
+public class InspectionProfileUtil {
+
+ public static void disableTool(String toolName, Project project) {
+ InspectionProfileImpl profile =
+ InspectionProjectProfileManager.getInstance(project).getCurrentProfile();
+ profile.setToolEnabled(toolName, false, project);
+ }
+}
diff --git a/sdkcompat/v171/com/google/idea/sdkcompat/python/PyConfigurationProducersList.java b/sdkcompat/v171/com/google/idea/sdkcompat/python/PyConfigurationProducersList.java
index e65ad6e..dd66800 100644
--- a/sdkcompat/v171/com/google/idea/sdkcompat/python/PyConfigurationProducersList.java
+++ b/sdkcompat/v171/com/google/idea/sdkcompat/python/PyConfigurationProducersList.java
@@ -1,12 +1,11 @@
package com.google.idea.sdkcompat.python;
import com.google.common.collect.ImmutableList;
-import java.util.Collection;
/** List of python configuration producers for a given plugin version. */
public class PyConfigurationProducersList {
- public static final Collection<Class<?>> PRODUCERS_TO_SUPPRESS =
+ public static final ImmutableList<Class<?>> PRODUCERS_TO_SUPPRESS =
ImmutableList.of(
com.jetbrains.python.run.PythonRunConfigurationProducer.class,
com.jetbrains.python.testing.universalTests.PyUniversalTestsConfigurationProducer.class,
diff --git a/sdkcompat/v171/com/google/idea/sdkcompat/python/PythonFacetUtil.java b/sdkcompat/v171/com/google/idea/sdkcompat/python/PythonFacetUtil.java
new file mode 100644
index 0000000..d5a5206
--- /dev/null
+++ b/sdkcompat/v171/com/google/idea/sdkcompat/python/PythonFacetUtil.java
@@ -0,0 +1,31 @@
+package com.google.idea.sdkcompat.python;
+
+import com.intellij.facet.FacetTypeId;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.jetbrains.python.facet.LibraryContributingFacet;
+import com.jetbrains.python.facet.PythonFacet;
+import com.jetbrains.python.facet.PythonFacetType;
+import javax.annotation.Nullable;
+
+/**
+ * This class is a hack to get around Python SDK incompatibilities. Provides a consistent API for
+ * both IntelliJ and CLion.
+ */
+public class PythonFacetUtil {
+ public static FacetTypeId<PythonFacet> getFacetId() {
+ return PythonFacet.ID;
+ }
+
+ public static PythonFacetType getTypeInstance() {
+ return PythonFacetType.getInstance();
+ }
+
+ @Nullable
+ public static Sdk getSdk(LibraryContributingFacet<?> facet) {
+ if (!(facet instanceof PythonFacet)) {
+ return null;
+ }
+ PythonFacet pythonFacet = (PythonFacet) facet;
+ return pythonFacet.getConfiguration().getSdk();
+ }
+}
diff --git a/sdkcompat/v171/com/google/idea/sdkcompat/run/RunnerAndConfigurationSettingsCompatUtils.java b/sdkcompat/v171/com/google/idea/sdkcompat/run/RunnerAndConfigurationSettingsCompatUtils.java
new file mode 100644
index 0000000..09c1c12
--- /dev/null
+++ b/sdkcompat/v171/com/google/idea/sdkcompat/run/RunnerAndConfigurationSettingsCompatUtils.java
@@ -0,0 +1,14 @@
+package com.google.idea.sdkcompat.run;
+
+import com.intellij.execution.impl.RunnerAndConfigurationSettingsImpl;
+import com.intellij.openapi.util.InvalidDataException;
+import org.jdom.Element;
+
+/** SDK compatibility bridge for {@link RunnerAndConfigurationSettingsImpl}. */
+public class RunnerAndConfigurationSettingsCompatUtils {
+
+ public static void readConfiguration(RunnerAndConfigurationSettingsImpl settings, Element element)
+ throws InvalidDataException {
+ settings.readExternal(element);
+ }
+}
diff --git a/sdkcompat/v171/com/google/idea/sdkcompat/transactions/Transactions.java b/sdkcompat/v171/com/google/idea/sdkcompat/transactions/Transactions.java
index 8862aa8..3ebee37 100644
--- a/sdkcompat/v171/com/google/idea/sdkcompat/transactions/Transactions.java
+++ b/sdkcompat/v171/com/google/idea/sdkcompat/transactions/Transactions.java
@@ -1,6 +1,7 @@
package com.google.idea.sdkcompat.transactions;
import com.intellij.openapi.Disposable;
+import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.TransactionGuard;
/** SDK adapter to use transaction guards. */
@@ -12,4 +13,10 @@
public static void submitTransaction(Disposable disposable, Runnable runnable) {
TransactionGuard.submitTransaction(disposable, runnable);
}
+
+ /** Runs {@link Runnable} as a write action, inside a transaction. */
+ public static void submitWriteActionTransactionAndWait(Runnable runnable) {
+ submitTransactionAndWait(
+ (Runnable) () -> ApplicationManager.getApplication().runWriteAction(runnable));
+ }
}
diff --git a/sdkcompat/v171/com/google/idea/sdkcompat/ui/BreadcrumbsProviderSdkCompatAdapter.java b/sdkcompat/v171/com/google/idea/sdkcompat/ui/BreadcrumbsProviderSdkCompatAdapter.java
new file mode 100644
index 0000000..1b41d6d
--- /dev/null
+++ b/sdkcompat/v171/com/google/idea/sdkcompat/ui/BreadcrumbsProviderSdkCompatAdapter.java
@@ -0,0 +1,50 @@
+package com.google.idea.sdkcompat.ui;
+
+import com.intellij.lang.Language;
+import com.intellij.psi.PsiElement;
+import com.intellij.xml.breadcrumbs.BreadcrumbsInfoProvider;
+import java.util.Arrays;
+import org.jetbrains.annotations.Nullable;
+
+/** SDK adapter for {@link BreadcrumbsInfoProvider}, deprecated in 172. */
+public abstract class BreadcrumbsProviderSdkCompatAdapter extends BreadcrumbsInfoProvider {
+
+ public static BreadcrumbsProviderSdkCompatAdapter[] getBreadcrumbsProviders() {
+ return Arrays.stream(BreadcrumbsInfoProvider.EP_NAME.getExtensions())
+ .map(BreadcrumbsProviderSdkCompatAdapter::fromBreadcrumbsProvider)
+ .toArray(BreadcrumbsProviderSdkCompatAdapter[]::new);
+ }
+
+ private static BreadcrumbsProviderSdkCompatAdapter fromBreadcrumbsProvider(
+ BreadcrumbsInfoProvider delegate) {
+ return new BreadcrumbsProviderSdkCompatAdapter() {
+
+ @Override
+ public Language[] getLanguages() {
+ return delegate.getLanguages();
+ }
+
+ @Override
+ public boolean acceptElement(PsiElement psiElement) {
+ return delegate.acceptElement(psiElement);
+ }
+
+ @Override
+ public String getElementInfo(PsiElement psiElement) {
+ return delegate.getElementInfo(psiElement);
+ }
+
+ @Nullable
+ @Override
+ public String getElementTooltip(PsiElement element) {
+ return delegate.getElementTooltip(element);
+ }
+
+ @Nullable
+ @Override
+ public PsiElement getParent(PsiElement element) {
+ return delegate.getParent(element);
+ }
+ };
+ }
+}
diff --git a/sdkcompat/v171/com/google/idea/sdkcompat/ui/RequestFocusCompatUtils.java b/sdkcompat/v171/com/google/idea/sdkcompat/ui/RequestFocusCompatUtils.java
new file mode 100644
index 0000000..c962097
--- /dev/null
+++ b/sdkcompat/v171/com/google/idea/sdkcompat/ui/RequestFocusCompatUtils.java
@@ -0,0 +1,17 @@
+package com.google.idea.sdkcompat.ui;
+
+import java.awt.Component;
+
+/** Works around b/64290580 which affects certain SDK versions. */
+public final class RequestFocusCompatUtils {
+ private RequestFocusCompatUtils() {}
+
+ /**
+ * Focuses the given component.
+ *
+ * @see Component#requestFocus()
+ */
+ public static void requestFocus(Component component) {
+ component.requestFocus();
+ }
+}
diff --git a/sdkcompat/v171/com/google/idea/sdkcompat/vcs/ChangeListManagerGateCompatUtils.java b/sdkcompat/v171/com/google/idea/sdkcompat/vcs/ChangeListManagerGateCompatUtils.java
new file mode 100644
index 0000000..0aebb39
--- /dev/null
+++ b/sdkcompat/v171/com/google/idea/sdkcompat/vcs/ChangeListManagerGateCompatUtils.java
@@ -0,0 +1,32 @@
+package com.google.idea.sdkcompat.vcs;
+
+import com.intellij.openapi.vcs.changes.ChangeListManager;
+import com.intellij.openapi.vcs.changes.ChangeListManagerGate;
+
+/**
+ * Works around b/63888111 / <a href="https://youtrack.jetbrains.com/issue/IJSDK-284">IJSDK-284</a>,
+ * which breaks support for {@link ChangeListManagerGate#editName(String, String)} in some SDK
+ * versions. <br>
+ * <br>
+ * The methods in this class call the corresponding methods on the {@link ChangeListManagerGate}
+ * instead of the {@link ChangeListManager}. It is normal to use this during a VCS update.
+ */
+public final class ChangeListManagerGateCompatUtils {
+ private ChangeListManagerGateCompatUtils() {}
+
+ public static void editName(
+ ChangeListManagerGate addGate,
+ ChangeListManager changeListManager,
+ String oldName,
+ String newName) {
+ addGate.editName(oldName, newName);
+ }
+
+ public static void editComment(
+ ChangeListManagerGate addGate,
+ ChangeListManager changeListManager,
+ String name,
+ String newComment) {
+ addGate.editComment(name, newComment);
+ }
+}
diff --git a/sdkcompat/v171/com/google/idea/sdkcompat/vcs/VcsEditorConfigurationCompatUtils.java b/sdkcompat/v171/com/google/idea/sdkcompat/vcs/VcsEditorConfigurationCompatUtils.java
new file mode 100644
index 0000000..88834c7
--- /dev/null
+++ b/sdkcompat/v171/com/google/idea/sdkcompat/vcs/VcsEditorConfigurationCompatUtils.java
@@ -0,0 +1,21 @@
+package com.google.idea.sdkcompat.vcs;
+
+import com.google.common.collect.ImmutableList;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vcs.VcsConfiguration;
+import com.intellij.spellchecker.ui.SpellCheckingEditorCustomization;
+import com.intellij.ui.EditorCustomization;
+import com.intellij.ui.RightMarginEditorCustomization;
+import java.util.List;
+
+/** Handles VCS changelist editor customizations that differ between SDK versions. */
+public class VcsEditorConfigurationCompatUtils {
+
+ public static List<EditorCustomization> getVcsConfigurationCustomizations(
+ Project project, VcsConfiguration config) {
+ return ImmutableList.of(
+ SpellCheckingEditorCustomization.getInstance(config.CHECK_COMMIT_MESSAGE_SPELLING),
+ new RightMarginEditorCustomization(
+ config.USE_COMMIT_MESSAGE_MARGIN, config.COMMIT_MESSAGE_MARGIN_SIZE));
+ }
+}
diff --git a/sdkcompat/v163/BUILD b/sdkcompat/v172/BUILD
similarity index 63%
rename from sdkcompat/v163/BUILD
rename to sdkcompat/v172/BUILD
index 8e2c272..8d2ee7a 100644
--- a/sdkcompat/v163/BUILD
+++ b/sdkcompat/v172/BUILD
@@ -5,30 +5,40 @@
load("//intellij_platform_sdk:build_defs.bzl", "select_for_ide")
java_library(
- name = "v163",
+ name = "v172",
srcs = glob([
"com/google/idea/sdkcompat/codestyle/**",
+ "com/google/idea/sdkcompat/profile/**",
"com/google/idea/sdkcompat/smrunner/**",
"com/google/idea/sdkcompat/transactions/**",
"com/google/idea/sdkcompat/vcs/**",
+ "com/google/idea/sdkcompat/ui/**",
+ "com/google/idea/sdkcompat/run/**",
]) + select_for_ide(
android_studio = glob([
"com/google/idea/sdkcompat/cidr/**",
+ "com/google/idea/sdkcompat/java/**",
]),
clion = glob([
- "com/google/idea/sdkcompat/cidr/**",
"com/google/idea/sdkcompat/clion/**",
+ "com/google/idea/sdkcompat/cidr/**",
+ "com/google/idea/sdkcompat/python/*",
+ "clion/com/google/idea/sdkcompat/python/*",
]),
intellij = glob([
- "com/google/idea/sdkcompat/python/**",
"com/google/idea/sdkcompat/dart/**",
+ "com/google/idea/sdkcompat/java/**",
+ "com/google/idea/sdkcompat/python/*",
+ "intellij/com/google/idea/sdkcompat/python/*",
]),
),
visibility = ["//sdkcompat:__pkg__"],
deps = [
"//intellij_platform_sdk:plugin_api",
+ "//intellij_platform_sdk:junit",
"@jsr305_annotations//jar",
] + select_for_ide(
+ clion = ["//third_party/python"],
intellij = ["//third_party/python"],
),
)
diff --git a/sdkcompat/v172/clion/com/google/idea/sdkcompat/python/PythonFacetUtil.java b/sdkcompat/v172/clion/com/google/idea/sdkcompat/python/PythonFacetUtil.java
new file mode 100644
index 0000000..e54822c
--- /dev/null
+++ b/sdkcompat/v172/clion/com/google/idea/sdkcompat/python/PythonFacetUtil.java
@@ -0,0 +1,31 @@
+package com.google.idea.sdkcompat.python;
+
+import com.intellij.facet.FacetTypeId;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.jetbrains.python.facet.LibraryContributingFacet;
+import com.jetbrains.python.minor.facet.PythonFacet;
+import com.jetbrains.python.minor.facet.PythonFacetType;
+import javax.annotation.Nullable;
+
+/**
+ * This class is a hack to get around Python SDK incompatibilities. Provides a consistent API for
+ * both IntelliJ and CLion.
+ */
+public class PythonFacetUtil {
+ public static FacetTypeId<PythonFacet> getFacetId() {
+ return PythonFacet.ID;
+ }
+
+ public static PythonFacetType getTypeInstance() {
+ return PythonFacetType.getInstance();
+ }
+
+ @Nullable
+ public static Sdk getSdk(LibraryContributingFacet<?> facet) {
+ if (!(facet instanceof PythonFacet)) {
+ return null;
+ }
+ PythonFacet pythonFacet = (PythonFacet) facet;
+ return pythonFacet.getConfiguration().getSdk();
+ }
+}
diff --git a/sdkcompat/v163/com/google/idea/sdkcompat/cidr/CidrCompilerSwitchesAdapter.java b/sdkcompat/v172/com/google/idea/sdkcompat/cidr/CidrCompilerSwitchesAdapter.java
similarity index 69%
rename from sdkcompat/v163/com/google/idea/sdkcompat/cidr/CidrCompilerSwitchesAdapter.java
rename to sdkcompat/v172/com/google/idea/sdkcompat/cidr/CidrCompilerSwitchesAdapter.java
index d60a1a6..c20ad1f 100644
--- a/sdkcompat/v163/com/google/idea/sdkcompat/cidr/CidrCompilerSwitchesAdapter.java
+++ b/sdkcompat/v172/com/google/idea/sdkcompat/cidr/CidrCompilerSwitchesAdapter.java
@@ -1,5 +1,6 @@
package com.google.idea.sdkcompat.cidr;
+import com.intellij.openapi.util.text.StringUtil;
import com.jetbrains.cidr.lang.toolchains.CidrCompilerSwitches;
import java.util.List;
@@ -7,14 +8,14 @@
public class CidrCompilerSwitchesAdapter {
/** Old interface does not know anything about CidrCompilerSwitches.Format */
public static List<String> getFileArgs(CidrCompilerSwitches switches) {
- return switches.getFileArgs();
+ return switches.getList(CidrCompilerSwitches.Format.RAW);
}
public static List<String> getCommandLineArgs(CidrCompilerSwitches switches) {
- return switches.getCommandLineArgs();
+ return switches.getList(CidrCompilerSwitches.Format.BASH_SHELL);
}
public static String getCommandLineString(CidrCompilerSwitches switches) {
- return switches.getCommandLineString();
+ return StringUtil.join(getCommandLineArgs(switches), " ");
}
}
diff --git a/sdkcompat/v163/com/google/idea/sdkcompat/cidr/CidrConsoleBuilderAdapter.java b/sdkcompat/v172/com/google/idea/sdkcompat/cidr/CidrConsoleBuilderAdapter.java
similarity index 100%
rename from sdkcompat/v163/com/google/idea/sdkcompat/cidr/CidrConsoleBuilderAdapter.java
rename to sdkcompat/v172/com/google/idea/sdkcompat/cidr/CidrConsoleBuilderAdapter.java
diff --git a/sdkcompat/v172/com/google/idea/sdkcompat/cidr/CidrGoogleTestUtilAdapter.java b/sdkcompat/v172/com/google/idea/sdkcompat/cidr/CidrGoogleTestUtilAdapter.java
new file mode 100644
index 0000000..efad3f4
--- /dev/null
+++ b/sdkcompat/v172/com/google/idea/sdkcompat/cidr/CidrGoogleTestUtilAdapter.java
@@ -0,0 +1,6 @@
+package com.google.idea.sdkcompat.cidr;
+
+import com.jetbrains.cidr.execution.testing.google.CidrGoogleTestUtil;
+
+/** Adapter to bridge different SDK versions. */
+public class CidrGoogleTestUtilAdapter extends CidrGoogleTestUtil {}
diff --git a/sdkcompat/v172/com/google/idea/sdkcompat/cidr/CidrSwitchBuilderAdapter.java b/sdkcompat/v172/com/google/idea/sdkcompat/cidr/CidrSwitchBuilderAdapter.java
new file mode 100644
index 0000000..40684be
--- /dev/null
+++ b/sdkcompat/v172/com/google/idea/sdkcompat/cidr/CidrSwitchBuilderAdapter.java
@@ -0,0 +1,6 @@
+package com.google.idea.sdkcompat.cidr;
+
+import com.jetbrains.cidr.lang.toolchains.CidrSwitchBuilder;
+
+/** Adapter to bridge different SDK versions. */
+public class CidrSwitchBuilderAdapter extends CidrSwitchBuilder {}
diff --git a/sdkcompat/v172/com/google/idea/sdkcompat/cidr/OCCompilerMacrosAdapter.java b/sdkcompat/v172/com/google/idea/sdkcompat/cidr/OCCompilerMacrosAdapter.java
new file mode 100644
index 0000000..fdccd53
--- /dev/null
+++ b/sdkcompat/v172/com/google/idea/sdkcompat/cidr/OCCompilerMacrosAdapter.java
@@ -0,0 +1,36 @@
+package com.google.idea.sdkcompat.cidr;
+
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiFile;
+import com.jetbrains.cidr.lang.OCLanguageKind;
+import com.jetbrains.cidr.lang.preprocessor.OCCompilerMacros;
+import com.jetbrains.cidr.lang.preprocessor.OCInclusionContext;
+import com.jetbrains.cidr.lang.workspace.compiler.OCCompilerFeatures;
+import java.util.Map;
+import org.jetbrains.annotations.NotNull;
+
+/** Adapter to bridge different SDK versions. */
+public abstract class OCCompilerMacrosAdapter extends OCCompilerMacros {
+
+ // v171
+ protected abstract void fillFileMacros(
+ @NotNull OCInclusionContext context, @NotNull PsiFile sourceFile);
+
+ // v171
+ protected void addAllFeatures(
+ Map<String, String> collection, Map<OCCompilerFeatures.Type<?>, ?> features) {}
+
+ // v171
+ public static void fillSubstitutions(OCInclusionContext context, String text) {}
+
+ // v171
+ public void enableClangFeatures(
+ @NotNull OCInclusionContext context, @NotNull Map<String, String> features) {}
+
+ // v171
+ public void enableClangExtensions(
+ @NotNull OCInclusionContext context, @NotNull Map<String, String> extensions) {}
+
+ // v172
+ public abstract String getAllDefines(OCLanguageKind kind, VirtualFile vf);
+}
diff --git a/sdkcompat/v172/com/google/idea/sdkcompat/cidr/OCCompilerSettingsAdapter.java b/sdkcompat/v172/com/google/idea/sdkcompat/cidr/OCCompilerSettingsAdapter.java
new file mode 100644
index 0000000..8ac7845
--- /dev/null
+++ b/sdkcompat/v172/com/google/idea/sdkcompat/cidr/OCCompilerSettingsAdapter.java
@@ -0,0 +1,6 @@
+package com.google.idea.sdkcompat.cidr;
+
+import com.jetbrains.cidr.toolchains.OCCompilerSettingsBackedByCompilerCache;
+
+/** Adapter to bridge different SDK versions. */
+public abstract class OCCompilerSettingsAdapter extends OCCompilerSettingsBackedByCompilerCache {}
diff --git a/sdkcompat/v172/com/google/idea/sdkcompat/cidr/OCResolveConfigurationAdapter.java b/sdkcompat/v172/com/google/idea/sdkcompat/cidr/OCResolveConfigurationAdapter.java
new file mode 100644
index 0000000..3cb3714
--- /dev/null
+++ b/sdkcompat/v172/com/google/idea/sdkcompat/cidr/OCResolveConfigurationAdapter.java
@@ -0,0 +1,45 @@
+package com.google.idea.sdkcompat.cidr;
+
+import com.intellij.openapi.util.UserDataHolderBase;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.jetbrains.cidr.lang.OCLanguageKind;
+import com.jetbrains.cidr.lang.preprocessor.OCCompilerMacros;
+import com.jetbrains.cidr.lang.workspace.OCIncludeMap;
+import com.jetbrains.cidr.lang.workspace.OCResolveConfiguration;
+import com.jetbrains.cidr.lang.workspace.compiler.OCCompilerFeatures.Type;
+import com.jetbrains.cidr.lang.workspace.compiler.OCCompilerSettings;
+import com.jetbrains.cidr.toolchains.OCCompilerSettingsBackedByCompilerCache;
+import java.util.Collections;
+import java.util.Map;
+import javax.annotation.Nullable;
+
+/** Adapter to bridge different SDK versions. */
+public abstract class OCResolveConfigurationAdapter extends UserDataHolderBase
+ implements OCResolveConfiguration {
+ /* v162/v163 */
+ public abstract VirtualFile getPrecompiledHeader();
+
+ /* v162/v163 */
+ public abstract OCLanguageKind getPrecompiledLanguageKind();
+
+ /* v171 */
+ public abstract OCCompilerMacros getCompilerMacros();
+
+ @Override
+ public Map<Type<?>, ?> getCompilerFeatures(
+ OCLanguageKind kind, @Nullable VirtualFile virtualFile) {
+ OCCompilerSettings compilerSettings = getCompilerSettings();
+ if (!(compilerSettings instanceof OCCompilerSettingsBackedByCompilerCache)) {
+ return Collections.emptyMap();
+ }
+
+ OCCompilerSettingsBackedByCompilerCache backedCompilerSettings =
+ (OCCompilerSettingsBackedByCompilerCache) compilerSettings;
+ return backedCompilerSettings.getCompilerFeatures(kind, virtualFile);
+ }
+
+ @Override
+ public OCIncludeMap getIncludeMap() {
+ return OCIncludeMap.EMPTY;
+ }
+}
diff --git a/sdkcompat/v163/com/google/idea/sdkcompat/cidr/OCWorkspaceAdapter.java b/sdkcompat/v172/com/google/idea/sdkcompat/cidr/OCWorkspaceAdapter.java
similarity index 100%
rename from sdkcompat/v163/com/google/idea/sdkcompat/cidr/OCWorkspaceAdapter.java
rename to sdkcompat/v172/com/google/idea/sdkcompat/cidr/OCWorkspaceAdapter.java
diff --git a/sdkcompat/v163/com/google/idea/sdkcompat/cidr/OCWorkspaceModificationTrackersCompatUtils.java b/sdkcompat/v172/com/google/idea/sdkcompat/cidr/OCWorkspaceModificationTrackersCompatUtils.java
similarity index 60%
rename from sdkcompat/v163/com/google/idea/sdkcompat/cidr/OCWorkspaceModificationTrackersCompatUtils.java
rename to sdkcompat/v172/com/google/idea/sdkcompat/cidr/OCWorkspaceModificationTrackersCompatUtils.java
index 1f38a39..651f529 100644
--- a/sdkcompat/v163/com/google/idea/sdkcompat/cidr/OCWorkspaceModificationTrackersCompatUtils.java
+++ b/sdkcompat/v172/com/google/idea/sdkcompat/cidr/OCWorkspaceModificationTrackersCompatUtils.java
@@ -10,12 +10,28 @@
return OCWorkspaceModificationTrackers.getInstance(project);
}
- /** Must be called inside a write action, on the EDT. */
+ /**
+ * Causes symbol tables to be rebuilt and invalidates cidr caches attached to resolve
+ * configurations.
+ *
+ * <p>Must be called inside a write action, on the EDT.
+ */
public static void incrementModificationCounts(Project project) {
+ partialIncModificationCounts(project);
+ OCWorkspaceModificationTrackers modTrackers = getTrackers(project);
+ modTrackers.getBuildSettingsChangesTracker().incModificationCount();
+ }
+
+ /**
+ * Does not trigger symbol table rebuilding, and only clears part of the cidr caches attached to
+ * resolve configurations.
+ *
+ * <p>Must be called inside a write action, on the EDT.
+ */
+ public static void partialIncModificationCounts(Project project) {
OCWorkspaceModificationTrackers modTrackers = getTrackers(project);
modTrackers.getProjectFilesListTracker().incModificationCount();
modTrackers.getSourceFilesListTracker().incModificationCount();
modTrackers.getSelectedResolveConfigurationTracker().incModificationCount();
- modTrackers.getBuildSettingsChangesTracker().incModificationCount();
}
}
diff --git a/sdkcompat/v163/com/google/idea/sdkcompat/clion/CMakeActionList.java b/sdkcompat/v172/com/google/idea/sdkcompat/clion/CMakeActionList.java
similarity index 71%
rename from sdkcompat/v163/com/google/idea/sdkcompat/clion/CMakeActionList.java
rename to sdkcompat/v172/com/google/idea/sdkcompat/clion/CMakeActionList.java
index 55f4936..1230fd0 100644
--- a/sdkcompat/v163/com/google/idea/sdkcompat/clion/CMakeActionList.java
+++ b/sdkcompat/v172/com/google/idea/sdkcompat/clion/CMakeActionList.java
@@ -3,7 +3,6 @@
import com.google.common.collect.ImmutableSet;
import com.jetbrains.cidr.cpp.cmake.actions.ChangeCMakeProjectContentRootAction;
import com.jetbrains.cidr.cpp.cmake.actions.ClearCMakeCacheAndReloadAction;
-import com.jetbrains.cidr.cpp.cmake.actions.OpenCMakeSettingsAction;
import com.jetbrains.cidr.cpp.cmake.actions.ReloadCMakeProjectAction;
import com.jetbrains.cidr.cpp.cmake.actions.ToggleCMakeAutoReloadAction;
@@ -14,9 +13,11 @@
ImmutableSet.of(
ChangeCMakeProjectContentRootAction.ID,
ClearCMakeCacheAndReloadAction.ID,
- OpenCMakeSettingsAction.ID,
+ // 'CMake' -> 'CMake Settings' action: com.cidr.cpp.cmake.actions.OpenCMakeSettingsAction
+ "CMake.OpenCMakeSettings",
ReloadCMakeProjectAction.ID,
ToggleCMakeAutoReloadAction.ID,
- // 'CMake' > 'Show Generated CMake Files' action
- "CMake.ShowGeneratedDir");
+ // 'CMake' > 'Show Generated CMake Files' action:
+ // com.cidr.cpp.cmake.actions.ShowCMakeGeneratedDirAction
+ "CMake.ShowCMakeGeneratedDir");
}
diff --git a/sdkcompat/v172/com/google/idea/sdkcompat/clion/CMakeConfigurationProducersList.java b/sdkcompat/v172/com/google/idea/sdkcompat/clion/CMakeConfigurationProducersList.java
new file mode 100644
index 0000000..abc58af
--- /dev/null
+++ b/sdkcompat/v172/com/google/idea/sdkcompat/clion/CMakeConfigurationProducersList.java
@@ -0,0 +1,16 @@
+package com.google.idea.sdkcompat.clion;
+
+import com.google.common.collect.ImmutableList;
+import com.intellij.execution.actions.RunConfigurationProducer;
+import com.jetbrains.cidr.cpp.execution.testing.google.CMakeGoogleTestRunConfigurationProducer;
+import com.jetbrains.cidr.cpp.execution.testing.tcatch.CMakeCatchTestRunConfigurationProducer;
+
+/** List of C/C++ configuration producers for a given plugin version. */
+public class CMakeConfigurationProducersList {
+
+ public static final ImmutableList<Class<? extends RunConfigurationProducer<?>>>
+ PRODUCERS_TO_SUPPRESS =
+ ImmutableList.of(
+ CMakeGoogleTestRunConfigurationProducer.class,
+ CMakeCatchTestRunConfigurationProducer.class);
+}
diff --git a/sdkcompat/v172/com/google/idea/sdkcompat/codestyle/DelegatingCodeStyleManagerSdkCompatAdapter.java b/sdkcompat/v172/com/google/idea/sdkcompat/codestyle/DelegatingCodeStyleManagerSdkCompatAdapter.java
new file mode 100644
index 0000000..0509343
--- /dev/null
+++ b/sdkcompat/v172/com/google/idea/sdkcompat/codestyle/DelegatingCodeStyleManagerSdkCompatAdapter.java
@@ -0,0 +1,65 @@
+package com.google.idea.sdkcompat.codestyle;
+
+import com.intellij.formatting.FormattingMode;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.codeStyle.ChangedRangesInfo;
+import com.intellij.psi.codeStyle.CodeStyleManager;
+import com.intellij.psi.codeStyle.FormattingModeAwareIndentAdjuster;
+import com.intellij.util.IncorrectOperationException;
+import java.util.ArrayList;
+import java.util.List;
+import org.jetbrains.annotations.NotNull;
+
+/** Adapter to bridge different SDK versions. */
+public abstract class DelegatingCodeStyleManagerSdkCompatAdapter extends CodeStyleManager
+ implements FormattingModeAwareIndentAdjuster {
+
+ protected CodeStyleManager delegate;
+
+ protected DelegatingCodeStyleManagerSdkCompatAdapter(CodeStyleManager delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public void reformatTextWithContext(@NotNull PsiFile file, @NotNull ChangedRangesInfo info)
+ throws IncorrectOperationException {
+ List<TextRange> ranges = new ArrayList<>();
+ if (info.insertedRanges != null) {
+ ranges.addAll(info.insertedRanges);
+ }
+ ranges.addAll(info.allChangedRanges);
+ this.reformatTextWithContext(file, ranges);
+ }
+
+ @Override
+ public int getSpacing(@NotNull PsiFile file, int offset) {
+ return delegate.getSpacing(file, offset);
+ }
+
+ @Override
+ public int getMinLineFeeds(@NotNull PsiFile file, int offset) {
+ return delegate.getMinLineFeeds(file, offset);
+ }
+
+ /** Uses same fallback as {@link CodeStyleManager#getCurrentFormattingMode}. */
+ @Override
+ public FormattingMode getCurrentFormattingMode() {
+ if (delegate instanceof FormattingModeAwareIndentAdjuster) {
+ return ((FormattingModeAwareIndentAdjuster) delegate).getCurrentFormattingMode();
+ }
+ return FormattingMode.REFORMAT;
+ }
+
+ @Override
+ public int adjustLineIndent(
+ @NotNull final Document document, final int offset, FormattingMode mode)
+ throws IncorrectOperationException {
+ if (delegate instanceof FormattingModeAwareIndentAdjuster) {
+ return ((FormattingModeAwareIndentAdjuster) delegate)
+ .adjustLineIndent(document, offset, mode);
+ }
+ return offset;
+ }
+}
diff --git a/sdkcompat/v163/com/google/idea/sdkcompat/dart/DartSdkCompatUtils.java b/sdkcompat/v172/com/google/idea/sdkcompat/dart/DartSdkCompatUtils.java
similarity index 85%
rename from sdkcompat/v163/com/google/idea/sdkcompat/dart/DartSdkCompatUtils.java
rename to sdkcompat/v172/com/google/idea/sdkcompat/dart/DartSdkCompatUtils.java
index 58f48e4..c2d1a3e 100644
--- a/sdkcompat/v163/com/google/idea/sdkcompat/dart/DartSdkCompatUtils.java
+++ b/sdkcompat/v172/com/google/idea/sdkcompat/dart/DartSdkCompatUtils.java
@@ -16,7 +16,7 @@
package com.google.idea.sdkcompat.dart;
import com.intellij.openapi.project.Project;
-import com.intellij.openapi.roots.impl.libraries.ApplicationLibraryTable;
+import com.intellij.openapi.roots.impl.libraries.ProjectLibraryTable;
import com.intellij.openapi.roots.libraries.Library;
import javax.annotation.Nullable;
@@ -27,6 +27,6 @@
@Nullable
public static Library findDartLibrary(Project project) {
- return ApplicationLibraryTable.getApplicationTable().getLibraryByName(DART_SDK_LIBRARY_NAME);
+ return ProjectLibraryTable.getInstance(project).getLibraryByName(DART_SDK_LIBRARY_NAME);
}
}
diff --git a/sdkcompat/v172/com/google/idea/sdkcompat/java/JavaConfigurationProducerList.java b/sdkcompat/v172/com/google/idea/sdkcompat/java/JavaConfigurationProducerList.java
new file mode 100644
index 0000000..977c744
--- /dev/null
+++ b/sdkcompat/v172/com/google/idea/sdkcompat/java/JavaConfigurationProducerList.java
@@ -0,0 +1,25 @@
+package com.google.idea.sdkcompat.java;
+
+import com.google.common.collect.ImmutableList;
+import com.intellij.execution.actions.RunConfigurationProducer;
+
+/** List of java-related configuration producers for a given plugin version. */
+public class JavaConfigurationProducerList {
+
+ /**
+ * Returns a list of run configuration producers to suppress for Blaze projects.
+ *
+ * <p>These classes must all be accessible from the Blaze plugin's classpath (e.g. they shouldn't
+ * belong to any plugins not listed as dependencies of the Blaze plugin).
+ */
+ public static final ImmutableList<Class<? extends RunConfigurationProducer<?>>>
+ PRODUCERS_TO_SUPPRESS =
+ ImmutableList.of(
+ com.intellij.execution.junit.AllInDirectoryConfigurationProducer.class,
+ com.intellij.execution.junit.AllInPackageConfigurationProducer.class,
+ com.intellij.execution.junit.TestInClassConfigurationProducer.class,
+ com.intellij.execution.junit.TestClassConfigurationProducer.class,
+ com.intellij.execution.junit.TestMethodConfigurationProducer.class,
+ com.intellij.execution.junit.PatternConfigurationProducer.class,
+ com.intellij.execution.application.ApplicationConfigurationProducer.class);
+}
diff --git a/sdkcompat/v172/com/google/idea/sdkcompat/profile/InspectionProfileUtil.java b/sdkcompat/v172/com/google/idea/sdkcompat/profile/InspectionProfileUtil.java
new file mode 100644
index 0000000..483f0e7
--- /dev/null
+++ b/sdkcompat/v172/com/google/idea/sdkcompat/profile/InspectionProfileUtil.java
@@ -0,0 +1,15 @@
+package com.google.idea.sdkcompat.profile;
+
+import com.intellij.codeInspection.ex.InspectionProfileImpl;
+import com.intellij.openapi.project.Project;
+import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
+
+/** Utility to bridge different SDK versions. */
+public class InspectionProfileUtil {
+
+ public static void disableTool(String toolName, Project project) {
+ InspectionProfileImpl profile =
+ InspectionProjectProfileManager.getInstance(project).getCurrentProfile();
+ profile.setToolEnabled(toolName, false, project);
+ }
+}
diff --git a/sdkcompat/v172/com/google/idea/sdkcompat/python/PyConfigurationProducersList.java b/sdkcompat/v172/com/google/idea/sdkcompat/python/PyConfigurationProducersList.java
new file mode 100644
index 0000000..1a85deb
--- /dev/null
+++ b/sdkcompat/v172/com/google/idea/sdkcompat/python/PyConfigurationProducersList.java
@@ -0,0 +1,18 @@
+package com.google.idea.sdkcompat.python;
+
+import com.google.common.collect.ImmutableList;
+
+/** List of python configuration producers for a given plugin version. */
+public class PyConfigurationProducersList {
+
+ public static final ImmutableList<Class<?>> PRODUCERS_TO_SUPPRESS =
+ ImmutableList.of(
+ com.jetbrains.python.run.PythonRunConfigurationProducer.class,
+ com.jetbrains.python.testing.PyTestsConfigurationProducer.class,
+ com.jetbrains.python.testing.PythonTestLegacyConfigurationProducer.class,
+ com.jetbrains.python.testing.doctest.PythonDocTestConfigurationProducer.class,
+ com.jetbrains.python.testing.nosetestLegacy.PythonNoseTestConfigurationProducer.class,
+ com.jetbrains.python.testing.pytestLegacy.PyTestConfigurationProducer.class,
+ com.jetbrains.python.testing.tox.PyToxConfigurationProducer.class,
+ com.jetbrains.python.testing.unittestLegacy.PythonUnitTestConfigurationProducer.class);
+}
diff --git a/sdkcompat/v163/com/google/idea/sdkcompat/python/PyImportResolverAdapter.java b/sdkcompat/v172/com/google/idea/sdkcompat/python/PyImportResolverAdapter.java
similarity index 80%
rename from sdkcompat/v163/com/google/idea/sdkcompat/python/PyImportResolverAdapter.java
rename to sdkcompat/v172/com/google/idea/sdkcompat/python/PyImportResolverAdapter.java
index 9ccba03..7edba6c 100644
--- a/sdkcompat/v163/com/google/idea/sdkcompat/python/PyImportResolverAdapter.java
+++ b/sdkcompat/v172/com/google/idea/sdkcompat/python/PyImportResolverAdapter.java
@@ -3,7 +3,7 @@
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.QualifiedName;
import com.jetbrains.python.psi.impl.PyImportResolver;
-import com.jetbrains.python.psi.resolve.QualifiedNameResolveContext;
+import com.jetbrains.python.psi.resolve.PyQualifiedNameResolveContext;
import javax.annotation.Nullable;
/** Adapter to bridge different SDK versions. */
@@ -16,7 +16,7 @@
@Override
@Nullable
default PsiElement resolveImportReference(
- QualifiedName name, QualifiedNameResolveContext context, boolean withRoots) {
+ QualifiedName name, PyQualifiedNameResolveContext context, boolean withRoots) {
return resolveImportReference(
name, new PyQualifiedNameResolveContextAdapter(context), withRoots);
}
diff --git a/sdkcompat/v172/com/google/idea/sdkcompat/python/PyQualifiedNameResolveContextAdapter.java b/sdkcompat/v172/com/google/idea/sdkcompat/python/PyQualifiedNameResolveContextAdapter.java
new file mode 100644
index 0000000..7311580
--- /dev/null
+++ b/sdkcompat/v172/com/google/idea/sdkcompat/python/PyQualifiedNameResolveContextAdapter.java
@@ -0,0 +1,152 @@
+package com.google.idea.sdkcompat.python;
+
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiManager;
+import com.jetbrains.python.psi.resolve.PyQualifiedNameResolveContext;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/** Adapter to bridge different SDK versions. */
+public class PyQualifiedNameResolveContextAdapter implements PyQualifiedNameResolveContext {
+
+ private final PyQualifiedNameResolveContext delegate;
+
+ PyQualifiedNameResolveContextAdapter(PyQualifiedNameResolveContext delegate) {
+ this.delegate = delegate;
+ }
+
+ @Nullable
+ @Override
+ public PsiElement getFoothold() {
+ return delegate.getFoothold();
+ }
+
+ @Override
+ public int getRelativeLevel() {
+ return delegate.getRelativeLevel();
+ }
+
+ @Nullable
+ @Override
+ public Sdk getSdk() {
+ return delegate.getSdk();
+ }
+
+ @Nullable
+ @Override
+ public Module getModule() {
+ return delegate.getModule();
+ }
+
+ @NotNull
+ @Override
+ public Project getProject() {
+ return delegate.getProject();
+ }
+
+ @Override
+ public boolean getWithoutRoots() {
+ return delegate.getWithoutRoots();
+ }
+
+ @Override
+ public boolean getWithoutForeign() {
+ return delegate.getWithoutForeign();
+ }
+
+ @Override
+ public boolean getWithoutStubs() {
+ return delegate.getWithoutStubs();
+ }
+
+ @NotNull
+ @Override
+ public PsiManager getPsiManager() {
+ return delegate.getPsiManager();
+ }
+
+ @Override
+ public boolean getWithMembers() {
+ return delegate.getWithMembers();
+ }
+
+ @Override
+ public boolean getWithPlainDirectories() {
+ return delegate.getWithPlainDirectories();
+ }
+
+ @Override
+ public boolean getVisitAllModules() {
+ return delegate.getVisitAllModules();
+ }
+
+ @Nullable
+ @Override
+ public Sdk getEffectiveSdk() {
+ return delegate.getEffectiveSdk();
+ }
+
+ @Override
+ public boolean isValid() {
+ return delegate.isValid();
+ }
+
+ @Nullable
+ @Override
+ public PsiFile getFootholdFile() {
+ return delegate.getFootholdFile();
+ }
+
+ @Nullable
+ @Override
+ public PsiDirectory getContainingDirectory() {
+ return delegate.getContainingDirectory();
+ }
+
+ @NotNull
+ @Override
+ public PyQualifiedNameResolveContext copyWithoutForeign() {
+ return delegate.copyWithoutForeign();
+ }
+
+ @NotNull
+ @Override
+ public PyQualifiedNameResolveContext copyWithMembers() {
+ return delegate.copyWithMembers();
+ }
+
+ @NotNull
+ @Override
+ public PyQualifiedNameResolveContext copyWithPlainDirectories() {
+ return delegate.copyWithPlainDirectories();
+ }
+
+ @NotNull
+ @Override
+ public PyQualifiedNameResolveContext copyWithRelative(int i) {
+ return delegate.copyWithRelative(i);
+ }
+
+ @NotNull
+ @Override
+ public PyQualifiedNameResolveContext copyWithoutRoots() {
+ return delegate.copyWithoutRoots();
+ }
+
+ @NotNull
+ @Override
+ public PyQualifiedNameResolveContext copyWithRoots() {
+ return delegate.copyWithRoots();
+ }
+
+ @NotNull
+ @Override
+ public PyQualifiedNameResolveContext copyWithoutStubs() {
+ return delegate.copyWithoutStubs();
+ }
+}
diff --git a/sdkcompat/v172/com/google/idea/sdkcompat/python/PyReferenceResolveProviderAdapter.java b/sdkcompat/v172/com/google/idea/sdkcompat/python/PyReferenceResolveProviderAdapter.java
new file mode 100644
index 0000000..9f8b5c3
--- /dev/null
+++ b/sdkcompat/v172/com/google/idea/sdkcompat/python/PyReferenceResolveProviderAdapter.java
@@ -0,0 +1,6 @@
+package com.google.idea.sdkcompat.python;
+
+import com.jetbrains.python.psi.resolve.PyReferenceResolveProvider;
+
+/** Adapter to bridge different SDK versions. */
+public interface PyReferenceResolveProviderAdapter extends PyReferenceResolveProvider {}
diff --git a/sdkcompat/v163/com/google/idea/sdkcompat/python/ResolveImportCompatUtils.java b/sdkcompat/v172/com/google/idea/sdkcompat/python/ResolveImportCompatUtils.java
similarity index 96%
rename from sdkcompat/v163/com/google/idea/sdkcompat/python/ResolveImportCompatUtils.java
rename to sdkcompat/v172/com/google/idea/sdkcompat/python/ResolveImportCompatUtils.java
index 5a6bef2..0329801 100644
--- a/sdkcompat/v163/com/google/idea/sdkcompat/python/ResolveImportCompatUtils.java
+++ b/sdkcompat/v172/com/google/idea/sdkcompat/python/ResolveImportCompatUtils.java
@@ -17,6 +17,6 @@
boolean checkForPackage,
boolean withoutStubs) {
return ResolveImportUtil.resolveChild(
- parent, referencedName, containingFile, fileOnly, checkForPackage);
+ parent, referencedName, containingFile, fileOnly, checkForPackage, withoutStubs);
}
}
diff --git a/sdkcompat/v172/com/google/idea/sdkcompat/run/RunnerAndConfigurationSettingsCompatUtils.java b/sdkcompat/v172/com/google/idea/sdkcompat/run/RunnerAndConfigurationSettingsCompatUtils.java
new file mode 100644
index 0000000..df88400
--- /dev/null
+++ b/sdkcompat/v172/com/google/idea/sdkcompat/run/RunnerAndConfigurationSettingsCompatUtils.java
@@ -0,0 +1,13 @@
+package com.google.idea.sdkcompat.run;
+
+import com.intellij.execution.impl.RunnerAndConfigurationSettingsImpl;
+import org.jdom.Element;
+
+/** SDK compatibility bridge for {@link RunnerAndConfigurationSettingsImpl}. */
+public class RunnerAndConfigurationSettingsCompatUtils {
+
+ public static void readConfiguration(
+ RunnerAndConfigurationSettingsImpl settings, Element element) {
+ settings.readExternal(element, false);
+ }
+}
diff --git a/sdkcompat/v163/com/google/idea/sdkcompat/smrunner/SmRunnerCompatUtils.java b/sdkcompat/v172/com/google/idea/sdkcompat/smrunner/SmRunnerCompatUtils.java
similarity index 77%
rename from sdkcompat/v163/com/google/idea/sdkcompat/smrunner/SmRunnerCompatUtils.java
rename to sdkcompat/v172/com/google/idea/sdkcompat/smrunner/SmRunnerCompatUtils.java
index 1e9b474..8555ad8 100644
--- a/sdkcompat/v163/com/google/idea/sdkcompat/smrunner/SmRunnerCompatUtils.java
+++ b/sdkcompat/v172/com/google/idea/sdkcompat/smrunner/SmRunnerCompatUtils.java
@@ -8,6 +8,7 @@
public static TestFailedEvent getTestFailedEvent(
String name, @Nullable String message, @Nullable String content, long duration) {
- return new TestFailedEvent(name, null, message, content, true, null, null, null, duration);
+ return new TestFailedEvent(
+ name, null, message, content, true, null, null, null, null, false, false, duration);
}
}
diff --git a/sdkcompat/v163/com/google/idea/sdkcompat/transactions/Transactions.java b/sdkcompat/v172/com/google/idea/sdkcompat/transactions/Transactions.java
similarity index 60%
rename from sdkcompat/v163/com/google/idea/sdkcompat/transactions/Transactions.java
rename to sdkcompat/v172/com/google/idea/sdkcompat/transactions/Transactions.java
index 8862aa8..3ebee37 100644
--- a/sdkcompat/v163/com/google/idea/sdkcompat/transactions/Transactions.java
+++ b/sdkcompat/v172/com/google/idea/sdkcompat/transactions/Transactions.java
@@ -1,6 +1,7 @@
package com.google.idea.sdkcompat.transactions;
import com.intellij.openapi.Disposable;
+import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.TransactionGuard;
/** SDK adapter to use transaction guards. */
@@ -12,4 +13,10 @@
public static void submitTransaction(Disposable disposable, Runnable runnable) {
TransactionGuard.submitTransaction(disposable, runnable);
}
+
+ /** Runs {@link Runnable} as a write action, inside a transaction. */
+ public static void submitWriteActionTransactionAndWait(Runnable runnable) {
+ submitTransactionAndWait(
+ (Runnable) () -> ApplicationManager.getApplication().runWriteAction(runnable));
+ }
}
diff --git a/sdkcompat/v172/com/google/idea/sdkcompat/ui/BreadcrumbsProviderSdkCompatAdapter.java b/sdkcompat/v172/com/google/idea/sdkcompat/ui/BreadcrumbsProviderSdkCompatAdapter.java
new file mode 100644
index 0000000..6174578
--- /dev/null
+++ b/sdkcompat/v172/com/google/idea/sdkcompat/ui/BreadcrumbsProviderSdkCompatAdapter.java
@@ -0,0 +1,63 @@
+package com.google.idea.sdkcompat.ui;
+
+import com.intellij.lang.Language;
+import com.intellij.psi.PsiElement;
+import com.intellij.ui.breadcrumbs.BreadcrumbsProvider;
+import java.util.Arrays;
+import java.util.List;
+import javax.swing.Icon;
+import org.jetbrains.annotations.Nullable;
+
+/** SDK adapter for {@link BreadcrumbsProvider}, added in 172. */
+public abstract class BreadcrumbsProviderSdkCompatAdapter implements BreadcrumbsProvider {
+
+ public static BreadcrumbsProviderSdkCompatAdapter[] getBreadcrumbsProviders() {
+ return Arrays.stream(BreadcrumbsProvider.EP_NAME.getExtensions())
+ .map(BreadcrumbsProviderSdkCompatAdapter::fromBreadcrumbsProvider)
+ .toArray(BreadcrumbsProviderSdkCompatAdapter[]::new);
+ }
+
+ private static BreadcrumbsProviderSdkCompatAdapter fromBreadcrumbsProvider(
+ BreadcrumbsProvider delegate) {
+ return new BreadcrumbsProviderSdkCompatAdapter() {
+
+ @Override
+ public Language[] getLanguages() {
+ return delegate.getLanguages();
+ }
+
+ @Override
+ public boolean acceptElement(PsiElement psiElement) {
+ return delegate.acceptElement(psiElement);
+ }
+
+ @Override
+ public String getElementInfo(PsiElement psiElement) {
+ return delegate.getElementInfo(psiElement);
+ }
+
+ @Nullable
+ @Override
+ public Icon getElementIcon(PsiElement element) {
+ return delegate.getElementIcon(element);
+ }
+
+ @Nullable
+ @Override
+ public String getElementTooltip(PsiElement element) {
+ return delegate.getElementTooltip(element);
+ }
+
+ @Nullable
+ @Override
+ public PsiElement getParent(PsiElement element) {
+ return delegate.getParent(element);
+ }
+
+ @Override
+ public List<PsiElement> getChildren(PsiElement element) {
+ return delegate.getChildren(element);
+ }
+ };
+ }
+}
diff --git a/sdkcompat/v172/com/google/idea/sdkcompat/ui/RequestFocusCompatUtils.java b/sdkcompat/v172/com/google/idea/sdkcompat/ui/RequestFocusCompatUtils.java
new file mode 100644
index 0000000..ff53498
--- /dev/null
+++ b/sdkcompat/v172/com/google/idea/sdkcompat/ui/RequestFocusCompatUtils.java
@@ -0,0 +1,23 @@
+package com.google.idea.sdkcompat.ui;
+
+import com.intellij.ui.EditorTextField;
+import java.awt.Component;
+
+/** Works around b/64290580 which affects certain SDK versions. */
+public final class RequestFocusCompatUtils {
+ private RequestFocusCompatUtils() {}
+
+ /**
+ * Focuses the given component.
+ *
+ * @see Component#requestFocus()
+ */
+ public static void requestFocus(Component component) {
+ if (component instanceof EditorTextField && ((EditorTextField) component).getEditor() == null) {
+ // If the editor is null, requestFocus() will just indirectly call requestFocus(),
+ // until the stack overflows. Instead, just don't support focusing the editor.
+ return;
+ }
+ component.requestFocus();
+ }
+}
diff --git a/sdkcompat/v172/com/google/idea/sdkcompat/vcs/ChangeListManagerGateCompatUtils.java b/sdkcompat/v172/com/google/idea/sdkcompat/vcs/ChangeListManagerGateCompatUtils.java
new file mode 100644
index 0000000..a179908
--- /dev/null
+++ b/sdkcompat/v172/com/google/idea/sdkcompat/vcs/ChangeListManagerGateCompatUtils.java
@@ -0,0 +1,42 @@
+package com.google.idea.sdkcompat.vcs;
+
+import com.intellij.openapi.vcs.changes.ChangeListManager;
+import com.intellij.openapi.vcs.changes.ChangeListManagerGate;
+
+/**
+ * Works around b/63888111 / <a href="https://youtrack.jetbrains.com/issue/IJSDK-284">IJSDK-284</a>,
+ * which breaks support for {@link ChangeListManagerGate#editName(String, String)} in some SDK
+ * versions. <br>
+ * <br>
+ * The methods in this class call the corresponding methods on the {@link ChangeListManager} instead
+ * of the {@link ChangeListManagerGate}. Normally in a VCS update, the latter should be used. Key
+ * known differences between the two are:
+ *
+ * <ul>
+ * <li>During the update, the manager and gate have independent state, and thus modifications made
+ * via one are not visible to the other.
+ * <li>If the VCS update completes successfully, the gate's state becomes authoritative, and any
+ * changes made via the manager during the update are applied on top of the gate's state.
+ * <li>If the VCS update fails, the manager's state remains authoritative, and the gate's state
+ * (and modifications) are discarded.
+ * </ul>
+ */
+public final class ChangeListManagerGateCompatUtils {
+ private ChangeListManagerGateCompatUtils() {}
+
+ public static void editName(
+ ChangeListManagerGate addGate,
+ ChangeListManager changeListManager,
+ String oldName,
+ String newName) {
+ changeListManager.editName(oldName, newName);
+ }
+
+ public static void editComment(
+ ChangeListManagerGate addGate,
+ ChangeListManager changeListManager,
+ String name,
+ String newComment) {
+ changeListManager.editComment(name, newComment);
+ }
+}
diff --git a/sdkcompat/v163/com/google/idea/sdkcompat/vcs/ChangeListManagerSdkCompatAdapter.java b/sdkcompat/v172/com/google/idea/sdkcompat/vcs/ChangeListManagerSdkCompatAdapter.java
similarity index 100%
rename from sdkcompat/v163/com/google/idea/sdkcompat/vcs/ChangeListManagerSdkCompatAdapter.java
rename to sdkcompat/v172/com/google/idea/sdkcompat/vcs/ChangeListManagerSdkCompatAdapter.java
diff --git a/sdkcompat/v172/com/google/idea/sdkcompat/vcs/MergeDataBuilder.java b/sdkcompat/v172/com/google/idea/sdkcompat/vcs/MergeDataBuilder.java
new file mode 100644
index 0000000..49b4665
--- /dev/null
+++ b/sdkcompat/v172/com/google/idea/sdkcompat/vcs/MergeDataBuilder.java
@@ -0,0 +1,78 @@
+package com.google.idea.sdkcompat.vcs;
+
+import com.intellij.openapi.vcs.FilePath;
+import com.intellij.openapi.vcs.history.VcsRevisionNumber;
+import com.intellij.openapi.vcs.merge.MergeData;
+import org.jetbrains.annotations.Nullable;
+
+/** SDK adapter for creating {@link MergeData}. */
+// TODO(grl): Move to com.google.devtools.intellij.piper.resolve and make package-private
+// once versions less than v171 have been deleted. We may as well keep the builder around,
+// since it uses piper-relevant terminology and complies with Java style conventions.
+public final class MergeDataBuilder {
+ private byte[] baseContent;
+ private byte[] theirsContent;
+ private byte[] yoursContent;
+
+ @Nullable private VcsRevisionNumber baseRevisionNumber;
+ @Nullable private VcsRevisionNumber theirsRevisionNumber;
+ @Nullable private VcsRevisionNumber yoursRevisionNumber;
+
+ @Nullable private FilePath baseFilePath;
+ @Nullable private FilePath theirsFilePath;
+ @Nullable private FilePath yoursFilePath;
+
+ public void setBaseContent(byte[] baseContent) {
+ this.baseContent = baseContent;
+ }
+
+ public void setTheirsContent(byte[] theirsContent) {
+ this.theirsContent = theirsContent;
+ }
+
+ public void setYoursContent(byte[] yoursContent) {
+ this.yoursContent = yoursContent;
+ }
+
+ public void setBaseRevisionNumber(@Nullable VcsRevisionNumber baseRevisionNumber) {
+ this.baseRevisionNumber = baseRevisionNumber;
+ }
+
+ public void setTheirsRevisionNumber(@Nullable VcsRevisionNumber theirsRevisionNumber) {
+ this.theirsRevisionNumber = theirsRevisionNumber;
+ }
+
+ public void setYoursRevisionNumber(@Nullable VcsRevisionNumber yoursRevisionNumber) {
+ this.yoursRevisionNumber = yoursRevisionNumber;
+ }
+
+ public void setBaseFilePath(@Nullable FilePath baseFilePath) {
+ this.baseFilePath = baseFilePath;
+ }
+
+ public void setTheirsFilePath(@Nullable FilePath theirsFilePath) {
+ this.theirsFilePath = theirsFilePath;
+ }
+
+ public void setYoursFilePath(@Nullable FilePath yoursFilePath) {
+ this.yoursFilePath = yoursFilePath;
+ }
+
+ public MergeData build() {
+ MergeData mergeData = new MergeData();
+
+ mergeData.ORIGINAL = baseContent;
+ mergeData.ORIGINAL_REVISION_NUMBER = baseRevisionNumber;
+ mergeData.ORIGINAL_FILE_PATH = baseFilePath;
+
+ mergeData.LAST = theirsContent;
+ mergeData.LAST_REVISION_NUMBER = theirsRevisionNumber;
+ mergeData.LAST_FILE_PATH = theirsFilePath;
+
+ mergeData.CURRENT = yoursContent;
+ mergeData.CURRENT_REVISION_NUMBER = yoursRevisionNumber;
+ mergeData.CURRENT_FILE_PATH = yoursFilePath;
+
+ return mergeData;
+ }
+}
diff --git a/sdkcompat/v172/com/google/idea/sdkcompat/vcs/VcsEditorConfigurationCompatUtils.java b/sdkcompat/v172/com/google/idea/sdkcompat/vcs/VcsEditorConfigurationCompatUtils.java
new file mode 100644
index 0000000..64ea32f
--- /dev/null
+++ b/sdkcompat/v172/com/google/idea/sdkcompat/vcs/VcsEditorConfigurationCompatUtils.java
@@ -0,0 +1,21 @@
+package com.google.idea.sdkcompat.vcs;
+
+import com.google.common.collect.ImmutableList;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vcs.VcsConfiguration;
+import com.intellij.ui.EditorCustomization;
+import com.intellij.ui.RightMarginEditorCustomization;
+import com.intellij.vcs.commit.CommitMessageInspectionProfile;
+import java.util.List;
+
+/** Handles VCS changelist editor customizations that differ between SDK versions. */
+public class VcsEditorConfigurationCompatUtils {
+
+ public static List<EditorCustomization> getVcsConfigurationCustomizations(
+ Project project, VcsConfiguration config) {
+ return ImmutableList.of(
+ new RightMarginEditorCustomization(
+ config.USE_COMMIT_MESSAGE_MARGIN,
+ CommitMessageInspectionProfile.getBodyRightMargin(project)));
+ }
+}
diff --git a/sdkcompat/v172/intellij/com/google/idea/sdkcompat/python/PythonFacetUtil.java b/sdkcompat/v172/intellij/com/google/idea/sdkcompat/python/PythonFacetUtil.java
new file mode 100644
index 0000000..d5a5206
--- /dev/null
+++ b/sdkcompat/v172/intellij/com/google/idea/sdkcompat/python/PythonFacetUtil.java
@@ -0,0 +1,31 @@
+package com.google.idea.sdkcompat.python;
+
+import com.intellij.facet.FacetTypeId;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.jetbrains.python.facet.LibraryContributingFacet;
+import com.jetbrains.python.facet.PythonFacet;
+import com.jetbrains.python.facet.PythonFacetType;
+import javax.annotation.Nullable;
+
+/**
+ * This class is a hack to get around Python SDK incompatibilities. Provides a consistent API for
+ * both IntelliJ and CLion.
+ */
+public class PythonFacetUtil {
+ public static FacetTypeId<PythonFacet> getFacetId() {
+ return PythonFacet.ID;
+ }
+
+ public static PythonFacetType getTypeInstance() {
+ return PythonFacetType.getInstance();
+ }
+
+ @Nullable
+ public static Sdk getSdk(LibraryContributingFacet<?> facet) {
+ if (!(facet instanceof PythonFacet)) {
+ return null;
+ }
+ PythonFacet pythonFacet = (PythonFacet) facet;
+ return pythonFacet.getConfiguration().getSdk();
+ }
+}
diff --git a/testing/cidr/src/com/google/idea/testing/cidr/StubOCResolveConfiguration.java b/testing/cidr/src/com/google/idea/testing/cidr/StubOCResolveConfiguration.java
index 6d6c083..3be405f 100644
--- a/testing/cidr/src/com/google/idea/testing/cidr/StubOCResolveConfiguration.java
+++ b/testing/cidr/src/com/google/idea/testing/cidr/StubOCResolveConfiguration.java
@@ -1,8 +1,8 @@
package com.google.idea.testing.cidr;
import com.google.common.collect.ImmutableList;
+import com.google.idea.sdkcompat.cidr.OCResolveConfigurationAdapter;
import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.UserDataHolderBase;
import com.intellij.openapi.vfs.VirtualFile;
import com.jetbrains.cidr.lang.OCFileTypeHelpers;
import com.jetbrains.cidr.lang.OCLanguageKind;
@@ -13,10 +13,14 @@
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 java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
import javax.annotation.Nullable;
/** Stub {@link OCResolveConfiguration} for testing. */
-class StubOCResolveConfiguration extends UserDataHolderBase implements OCResolveConfiguration {
+class StubOCResolveConfiguration extends OCResolveConfigurationAdapter {
private final Project project;
private final HeaderRoots projectIncludeRoots;
@@ -40,10 +44,19 @@
return "Stub resolve configuration";
}
- @Nullable
@Override
- public VirtualFile getPrecompiledHeader() {
- return null;
+ public Set<VirtualFile> getPrecompiledHeaders() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public List<VirtualFile> getPrecompiledHeaders(OCLanguageKind kind, VirtualFile sourceFile) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public Collection<VirtualFile> getSources() {
+ return Collections.emptyList();
}
@Nullable
@@ -67,11 +80,6 @@
}
@Override
- public OCLanguageKind getPrecompiledLanguageKind() {
- return getMaximumLanguageKind();
- }
-
- @Override
public HeaderRoots getProjectHeadersRoots() {
return projectIncludeRoots;
}
@@ -101,4 +109,24 @@
public int compareTo(OCResolveConfiguration o) {
return OCWorkspaceUtil.compareConfigurations(this, o);
}
+
+ /* v162/v163 */
+ @Nullable
+ @Override
+ public VirtualFile getPrecompiledHeader() {
+ return null;
+ }
+
+ /* v162/v163 */
+ @Override
+ public OCLanguageKind getPrecompiledLanguageKind() {
+ return getMaximumLanguageKind();
+ }
+
+ /* v172 */
+ @Nullable
+ @Override
+ public String getPreprocessorDefines(OCLanguageKind kind, VirtualFile virtualFile) {
+ return null;
+ }
}
diff --git a/testing/cidr/src/com/google/idea/testing/cidr/StubOCWorkspaceManager.java b/testing/cidr/src/com/google/idea/testing/cidr/StubOCWorkspaceManager.java
index 05921b7..0a98a26 100644
--- a/testing/cidr/src/com/google/idea/testing/cidr/StubOCWorkspaceManager.java
+++ b/testing/cidr/src/com/google/idea/testing/cidr/StubOCWorkspaceManager.java
@@ -1,5 +1,7 @@
package com.google.idea.testing.cidr;
+import com.google.idea.sdkcompat.cidr.OCWorkspaceModificationTrackersCompatUtils;
+import com.google.idea.sdkcompat.transactions.Transactions;
import com.intellij.lang.Language;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.fileTypes.PlainTextLanguage;
@@ -75,16 +77,14 @@
}
private static void rebuildSymbols(Project project, OCWorkspace workspace) {
- ApplicationManager.getApplication()
- .runReadAction(
- () -> {
- if (project.isDisposed()) {
- return;
- }
- workspace
- .getModificationTrackers()
- .getBuildSettingsChangesTracker()
- .incModificationCount();
- });
+ Transactions.submitTransaction(
+ project,
+ () ->
+ ApplicationManager.getApplication()
+ .runReadAction(
+ () ->
+ OCWorkspaceModificationTrackersCompatUtils.getTrackers(project)
+ .getBuildSettingsChangesTracker()
+ .incModificationCount()));
}
}
diff --git a/testing/src/com/google/idea/testing/BlazeTestSystemPropertiesRule.java b/testing/src/com/google/idea/testing/BlazeTestSystemPropertiesRule.java
index f8d7f19..fe78fbd 100644
--- a/testing/src/com/google/idea/testing/BlazeTestSystemPropertiesRule.java
+++ b/testing/src/com/google/idea/testing/BlazeTestSystemPropertiesRule.java
@@ -111,8 +111,30 @@
return null;
}
File jarFile = new File(platformJar).getAbsoluteFile();
- File libDir = jarFile.getParentFile();
- return libDir != null ? libDir.getParent() : null;
+ File jarDir = jarFile.getParentFile();
+ if (jarDir == null) {
+ return null;
+ }
+ if (jarDir.getName().equals("lib")) {
+ // Building against IDE distribution.
+ // root/ <- we want this
+ // |-lib/
+ // | `-openapi.jar (jarFile)
+ // `-plugins/
+ return jarDir.getParent();
+ } else if (jarDir.getName().equals("core-api")) {
+ // Building against source.
+ // tools/idea/ <- we want this
+ // |-platform/
+ // | `-core-api/
+ // | `-libcore-api.jar (jarFile)
+ // `-plugins/
+ File platformDir = jarDir.getParentFile();
+ if (platformDir != null && platformDir.getName().equals("platform")) {
+ return platformDir.getParent();
+ }
+ }
+ return null;
}
private static void addArchiveFile(URL url, List<String> files) {
diff --git a/testing/src/com/google/idea/testing/DisablePluginsTestRule.java b/testing/src/com/google/idea/testing/DisablePluginsTestRule.java
new file mode 100644
index 0000000..5399482
--- /dev/null
+++ b/testing/src/com/google/idea/testing/DisablePluginsTestRule.java
@@ -0,0 +1,56 @@
+package com.google.idea.testing;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.intellij.ide.plugins.PluginManagerCore;
+import com.intellij.openapi.application.Application;
+import com.intellij.openapi.application.ApplicationManager;
+import java.lang.reflect.Field;
+import java.util.List;
+import org.junit.rules.ExternalResource;
+
+/**
+ * Test rule to disable a specified list of plugins during a test class.
+ *
+ * <p>Disabled plugins only take effect when initializing the {@link Application}, which can only
+ * happen once per integration test target.
+ *
+ * <p>As such, all users of this test rule must be in their own `java_test` target.
+ */
+public class DisablePluginsTestRule extends ExternalResource {
+
+ private final ImmutableList<String> disabledPluginIds;
+
+ public DisablePluginsTestRule(ImmutableList<String> disabledPluginIds) {
+ this.disabledPluginIds = Preconditions.checkNotNull(disabledPluginIds);
+ }
+
+ @Override
+ protected void before() throws Throwable {
+ if (ApplicationManager.getApplication() != null) {
+ // We may be able to relax this constraint if we check that the desired
+ // disabledPluginIds matches the existing value of ourDisabledPlugins.
+ throw new RuntimeException("Cannot disable plugins; they've already been loaded.");
+ }
+ forceSetDisabledPluginsField(disabledPluginIds);
+ }
+
+ @Override
+ protected void after() {
+ // no point resetting the list of disabled plugins to its prior value -- subsequent tests can't
+ // reinitialize the {@link Application} anyway.
+ }
+
+ /**
+ * Access the 'ourDisabledPlugins' field in {@link PluginManagerCore} via reflection, and set it.
+ * We can't simply populate a 'disabled_plugins.txt' file (the normal mechanism for disabling
+ * plugins), because that is ignored during tests.
+ */
+ private static void forceSetDisabledPluginsField(List<String> disabledPlugins)
+ throws NoSuchFieldException, IllegalAccessException {
+ Field ourDisabledPlugins = PluginManagerCore.class.getDeclaredField("ourDisabledPlugins");
+ ourDisabledPlugins.setAccessible(true);
+ ourDisabledPlugins.set(null, disabledPlugins);
+ ourDisabledPlugins.setAccessible(false);
+ }
+}
diff --git a/testing/src/com/google/idea/testing/VerifyRequiredPluginsEnabled.java b/testing/src/com/google/idea/testing/VerifyRequiredPluginsEnabled.java
new file mode 100644
index 0000000..e26c16a
--- /dev/null
+++ b/testing/src/com/google/idea/testing/VerifyRequiredPluginsEnabled.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2017 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.testing;
+
+import com.intellij.ide.plugins.IdeaPluginDescriptor;
+import com.intellij.ide.plugins.PluginManager;
+import com.intellij.openapi.extensions.PluginId;
+
+/** Check that plugins flagged by the test runner as required are actually loaded. */
+public class VerifyRequiredPluginsEnabled {
+
+ /**
+ * Checks that the specified plugins are installed and enabled. Throws a {@link RuntimeException}
+ * if any are missing.
+ */
+ public static void runCheck(String[] requiredPlugins) {
+ for (String pluginId : requiredPlugins) {
+ if (pluginEnabled(pluginId)) {
+ continue;
+ }
+ String msg =
+ String.format(
+ "Required plugin '%s' is not %s",
+ pluginId, pluginInstalled(pluginId) ? "enabled" : "available");
+ throw new RuntimeException(msg);
+ }
+ }
+
+ private static boolean pluginEnabled(String pluginId) {
+ IdeaPluginDescriptor descriptor = PluginManager.getPlugin(PluginId.getId(pluginId));
+ return descriptor != null && descriptor.isEnabled();
+ }
+
+ private static boolean pluginInstalled(String pluginId) {
+ return PluginManager.getPlugin(PluginId.getId(pluginId)) != null;
+ }
+}
diff --git a/testing/test_defs.bzl b/testing/test_defs.bzl
index 8ee1992..53b107e 100644
--- a/testing/test_defs.bzl
+++ b/testing/test_defs.bzl
@@ -92,7 +92,6 @@
test_package_root,
deps,
size="medium",
- shard_count=None,
jvm_flags = [],
runtime_deps = [],
platform_prefix="Idea",
@@ -110,7 +109,6 @@
test_package_root: only tests under this package root will be run.
deps: the required deps.
size: the test size.
- shard_count: the number of shards to use.
jvm_flags: extra flags to be passed to the test vm.
runtime_deps: the required runtime_deps.
platform_prefix: Specifies the JetBrains product these tests are run against. Examples are
@@ -160,7 +158,6 @@
size = size,
srcs = srcs + [suite_class_name],
data = data,
- shard_count = shard_count,
jvm_flags = jvm_flags,
test_class = suite_class,
runtime_deps = runtime_deps,
@@ -181,7 +178,7 @@
def _get_test_srcs(targets):
"""Returns all files of the given targets that end with Test.java."""
- files = set()
+ files = depset()
for target in targets:
files += target.files
return [f for f in files if f.basename.endswith("Test.java")]
diff --git a/third_party/BUILD b/third_party/BUILD
index 06e9b19..1d79030 100644
--- a/third_party/BUILD
+++ b/third_party/BUILD
@@ -6,16 +6,6 @@
jars = ["jdk8/jpda-jdi.jar"],
)
-sh_binary(
- name = "zip",
- srcs = ["zip-wrap/zip.sh"],
-)
-
-sh_binary(
- name = "unzip",
- srcs = ["zip-wrap/unzip.sh"],
-)
-
java_library(
name = "python",
exports = ["//third_party/python"],
diff --git a/third_party/auto_value/BUILD b/third_party/auto_value/BUILD
new file mode 100644
index 0000000..bbf4bb0
--- /dev/null
+++ b/third_party/auto_value/BUILD
@@ -0,0 +1,16 @@
+licenses(["notice"])
+
+java_plugin(
+ name = "autovalue-plugin",
+ generates_api = 1,
+ processor_class = "com.google.auto.value.processor.AutoValueProcessor",
+ deps = ["@auto_value//jar"],
+)
+
+# provides both the jar for compilation and the java_plugin.
+java_library(
+ name = "auto_value",
+ exported_plugins = [":autovalue-plugin"],
+ visibility = ["//visibility:public"],
+ exports = ["@auto_value//jar"],
+)
diff --git a/third_party/auto_value/LICENSE b/third_party/auto_value/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/third_party/auto_value/LICENSE
@@ -0,0 +1,202 @@
+
+ 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/go/BUILD b/third_party/go/BUILD
new file mode 100644
index 0000000..9ed479c
--- /dev/null
+++ b/third_party/go/BUILD
@@ -0,0 +1,30 @@
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])
+
+load("//intellij_platform_sdk:build_defs.bzl", "select_for_plugin_api")
+
+java_library(
+ name = "go_internal",
+ visibility = ["//visibility:private"],
+ exports = select_for_plugin_api({
+ "intellij-2017.2.2": ["@go_2017_2//:go"],
+ "intellij-2017.1.5": ["@go_2017_1//:go"],
+ "intellij-ue-2017.2.2": ["@go_2017_2//:go"],
+ "intellij-ue-2017.1.5": ["@go_2017_1//:go"],
+ "clion-2017.2.1": ["@go_2017_2//:go"],
+ "clion-2017.1.1": ["@go_2017_1//:go"],
+ }),
+)
+
+java_library(
+ name = "go_for_tests",
+ testonly = 1,
+ exports = [":go_internal"],
+)
+
+java_library(
+ name = "go",
+ neverlink = 1,
+ exports = [":go_internal"],
+)
diff --git a/third_party/python/BUILD b/third_party/python/BUILD
index 2f69d16..7863e0c 100644
--- a/third_party/python/BUILD
+++ b/third_party/python/BUILD
@@ -8,9 +8,12 @@
name = "python_internal",
visibility = ["//visibility:private"],
exports = select_for_plugin_api({
- "intellij-2016.3.1": ["@python_2016_3//:python"],
- "intellij-2017.1.1": ["@python_2017_1//:python"],
+ "intellij-2017.1.5": ["@python_2017_1//:python"],
+ "intellij-2017.2.2": ["@python_2017_2//:python"],
+ "intellij-ue-2017.1.5": ["@python_2017_1//:python"],
+ "intellij-ue-2017.2.2": ["@python_2017_2//:python"],
"clion-2017.1.1": ["@clion_2017_1_1//:python"],
+ "clion-2017.2.1": ["@clion_2017_2_1//:python"],
}),
)
diff --git a/third_party/scala/BUILD b/third_party/scala/BUILD
index 9786523..b23c3ab 100644
--- a/third_party/scala/BUILD
+++ b/third_party/scala/BUILD
@@ -8,7 +8,8 @@
name = "scala_internal",
visibility = ["//visibility:private"],
exports = select_for_plugin_api({
- "intellij-2017.1.1": ["@scala_2017_1//:scala"],
+ "intellij-2017.1.5": ["@scala_2017_1//:scala"],
+ "intellij-2017.2.2": ["@scala_2017_2//:scala"],
}),
)
diff --git a/third_party/zip-wrap/unzip.sh b/third_party/zip-wrap/unzip.sh
deleted file mode 100755
index f1be34d..0000000
--- a/third_party/zip-wrap/unzip.sh
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright 2015 The Bazel Authors. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-unzip "$@"
diff --git a/third_party/zip-wrap/zip.sh b/third_party/zip-wrap/zip.sh
deleted file mode 100755
index daed8f6..0000000
--- a/third_party/zip-wrap/zip.sh
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright 2015 The Bazel Authors. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-zip "$@"
diff --git a/version.bzl b/version.bzl
index 667e07a..d3dddd7 100644
--- a/version.bzl
+++ b/version.bzl
@@ -1,3 +1,7 @@
"""Version of the blaze plugin."""
-VERSION = "2017.05.17.1"
+# This version will be overwritten in our rapid builds to the actual version number. We set the
+# default version to 9999 so that a dev plugin built from Piper HEAD will override any production
+# plugin (because IntelliJ will choose the highest version when it sees two conflicting plugins, so
+# 9999 > 2017.06.05.0.1).
+VERSION = "9999"