diff --git a/.bazelci/android-studio.yml b/.bazelci/android-studio.yml index 4cde6c593cd..a599b916a74 100644 --- a/.bazelci/android-studio.yml +++ b/.bazelci/android-studio.yml @@ -2,7 +2,7 @@ tasks: Android-Studio-internal-stable: name: Android Studio Internal Stable - platform: ubuntu1804 + platform: ubuntu2204 build_flags: - --define=ij_product=android-studio-latest build_targets: @@ -14,7 +14,7 @@ tasks: - //:aswb_tests Android-Studio-internal-beta: name: Android Studio Internal Beta - platform: ubuntu1804 + platform: ubuntu2204 build_flags: - --define=ij_product=android-studio-beta build_targets: @@ -26,7 +26,7 @@ tasks: - //:aswb_tests Android-Studio-internal-canary: name: Android Studio Internal Canary - platform: ubuntu1804 + platform: ubuntu2204 build_flags: - --define=ij_product=android-studio-canary build_targets: @@ -36,35 +36,33 @@ tasks: - --test_output=errors test_targets: - //:aswb_tests - soft_fail: - - exit_status: 1 - Android-Studio-OSS-stable: - name: Android Studio OSS Stable - platform: ubuntu1804 + Android-Studio-OSS-oldest-stable: + name: Android Studio OSS Oldest Stable + platform: ubuntu2204 build_flags: - - --define=ij_product=android-studio-oss-stable + - --define=ij_product=android-studio-oss-oldest-stable build_targets: - //aswb/... test_flags: - - --define=ij_product=android-studio-oss-stable + - --define=ij_product=android-studio-oss-oldest-stable - --test_output=errors test_targets: - //:aswb_tests - Android-Studio-OSS-beta: - name: Android Studio OSS Beta - platform: ubuntu1804 + Android-Studio-OSS-latest-stable: + name: Android Studio OSS Latest Stable + platform: ubuntu2204 build_flags: - - --define=ij_product=android-studio-oss-beta + - --define=ij_product=android-studio-oss-latest-stable build_targets: - //aswb/... test_flags: - - --define=ij_product=android-studio-oss-beta + - --define=ij_product=android-studio-oss-latest-stable - --test_output=errors test_targets: - //:aswb_tests Android-Studio-OSS-under-dev: name: Android Studio OSS Under Development - platform: ubuntu1804 + platform: ubuntu2204 build_flags: - --define=ij_product=android-studio-oss-under-dev build_targets: @@ -74,6 +72,3 @@ tasks: - --test_output=errors test_targets: - //:aswb_tests - soft_fail: - - exit_status: 1 - diff --git a/.bazelci/aspect.yml b/.bazelci/aspect.yml index 41809729a15..be4fa26a482 100644 --- a/.bazelci/aspect.yml +++ b/.bazelci/aspect.yml @@ -2,7 +2,7 @@ tasks: Aspect-internal-stable: name: Aspect Tests for IJ Internal Stable - platform: ubuntu1804 + platform: ubuntu2204 build_flags: - --define=ij_product=intellij-latest build_targets: @@ -13,9 +13,10 @@ tasks: - --notrim_test_configuration test_targets: - //aspect/testing/... + skip_use_bazel_version_for_test: true Aspect-internal-beta: name: Aspect Tests for IJ Internal Beta - platform: ubuntu1804 + platform: ubuntu2204 build_flags: - --define=ij_product=intellij-beta build_targets: @@ -26,9 +27,10 @@ tasks: - --notrim_test_configuration test_targets: - //aspect/testing/... + skip_use_bazel_version_for_test: true Aspect-internal-under-dev: name: Aspect Tests for IJ Internal Under Development - platform: ubuntu1804 + platform: ubuntu2204 build_flags: - --define=ij_product=intellij-under-dev build_targets: @@ -41,35 +43,38 @@ tasks: - //aspect/testing/... soft_fail: - exit_status: 1 - Aspect-oss-stable: - name: Aspect Tests for IJ OSS Stable - platform: ubuntu1804 + skip_use_bazel_version_for_test: true + Aspect-oss-oldest-stable: + name: Aspect Tests for IJ OSS Oldest Stable + platform: ubuntu2204 build_flags: - - --define=ij_product=intellij-oss-stable + - --define=ij_product=intellij-oss-oldest-stable build_targets: - //aspect:aspect_files test_flags: - - --define=ij_product=intellij-oss-stable + - --define=ij_product=intellij-oss-oldest-stable - --test_output=errors - --notrim_test_configuration test_targets: - //aspect/testing/... - Aspect-oss-beta: - name: Aspect Tests for IJ OSS Beta - platform: ubuntu1804 + skip_use_bazel_version_for_test: true + Aspect-oss-latest-stable: + name: Aspect Tests for IJ OSS Latest Stable + platform: ubuntu2204 build_flags: - - --define=ij_product=intellij-oss-beta + - --define=ij_product=intellij-oss-latest-stable build_targets: - //aspect:aspect_files test_flags: - - --define=ij_product=intellij-oss-beta + - --define=ij_product=intellij-oss-latest-stable - --test_output=errors - --notrim_test_configuration test_targets: - //aspect/testing/... + skip_use_bazel_version_for_test: true Aspect-oss-under-dev: name: Aspect Tests for IJ OSS Under Development - platform: ubuntu1804 + platform: ubuntu2204 build_flags: - --define=ij_product=intellij-oss-under-dev build_targets: @@ -82,3 +87,4 @@ tasks: - //aspect/testing/... soft_fail: - exit_status: 1 + skip_use_bazel_version_for_test: true diff --git a/.bazelci/clion.yml b/.bazelci/clion.yml index a4534ad2ab3..0672b9c551c 100644 --- a/.bazelci/clion.yml +++ b/.bazelci/clion.yml @@ -2,7 +2,7 @@ tasks: CLion-internal-stable: name: CLion Internal Stable - platform: ubuntu1804 + platform: ubuntu2204 build_flags: - --define=ij_product=clion-latest build_targets: @@ -14,7 +14,7 @@ tasks: - //:clwb_tests CLion-internal-beta: name: CLion Internal Beta - platform: ubuntu1804 + platform: ubuntu2204 build_flags: - --define=ij_product=clion-beta build_targets: @@ -26,7 +26,7 @@ tasks: - //:clwb_tests CLion-internal-under-dev: name: CLion Internal Under Development - platform: ubuntu1804 + platform: ubuntu2204 build_flags: - --define=ij_product=clion-under-dev build_targets: @@ -38,33 +38,33 @@ tasks: - //:clwb_tests soft_fail: - exit_status: 1 - CLion-OSS-stable: - name: CLion OSS Stable - platform: ubuntu1804 + CLion-OSS-oldest-stable: + name: CLion OSS Oldest Stable + platform: ubuntu2204 build_flags: - - --define=ij_product=clion-oss-stable + - --define=ij_product=clion-oss-oldest-stable build_targets: - //clwb/... test_flags: - - --define=ij_product=clion-oss-stable + - --define=ij_product=clion-oss-oldest-stable - --test_output=errors test_targets: - //:clwb_tests - CLion-OSS-beta: - name: CLion OSS Beta - platform: ubuntu1804 + CLion-OSS-latest-stable: + name: CLion OSS Latest Stable + platform: ubuntu2204 build_flags: - - --define=ij_product=clion-oss-beta + - --define=ij_product=clion-oss-latest-stable build_targets: - //clwb/... test_flags: - - --define=ij_product=clion-oss-beta + - --define=ij_product=clion-oss-latest-stable - --test_output=errors test_targets: - //:clwb_tests CLion-OSS-under-dev: name: CLion OSS Under Development - platform: ubuntu1804 + platform: ubuntu2204 build_flags: - --define=ij_product=clion-oss-under-dev build_targets: diff --git a/.bazelci/intellij-ue.yml b/.bazelci/intellij-ue.yml index 5175d667aa9..28f470caf2a 100644 --- a/.bazelci/intellij-ue.yml +++ b/.bazelci/intellij-ue.yml @@ -2,7 +2,7 @@ tasks: IntelliJ-UE-internal-stable: name: IntelliJ UE Internal Stable - platform: ubuntu1804 + platform: ubuntu2204 build_flags: - --define=ij_product=intellij-ue-latest build_targets: @@ -14,7 +14,7 @@ tasks: - //:ijwb_ue_tests IntelliJ-UE-internal-beta: name: IntelliJ UE Internal Beta - platform: ubuntu1804 + platform: ubuntu2204 build_flags: - --define=ij_product=intellij-ue-beta build_targets: @@ -26,7 +26,7 @@ tasks: - //:ijwb_ue_tests IntelliJ-UE-internal-under-dev: name: IntelliJ UE Internal Under Development - platform: ubuntu1804 + platform: ubuntu2204 build_flags: - --define=ij_product=intellij-ue-under-dev build_targets: @@ -38,33 +38,33 @@ tasks: - //:ijwb_ue_tests soft_fail: - exit_status: 1 - IntelliJ-UE-OSS-stable: - name: IntelliJ UE OSS Stable - platform: ubuntu1804 + IntelliJ-UE-OSS-oldest-stable: + name: IntelliJ UE OSS Oldest Stable + platform: ubuntu2204 build_flags: - - --define=ij_product=intellij-ue-oss-stable + - --define=ij_product=intellij-ue-oss-oldest-stable build_targets: - //ijwb/... test_flags: - - --define=ij_product=intellij-ue-oss-stable + - --define=ij_product=intellij-ue-oss-oldest-stable - --test_output=errors test_targets: - //:ijwb_ue_tests - IntelliJ-UE-OSS-beta: - name: IntelliJ UE OSS Beta - platform: ubuntu1804 + IntelliJ-UE-OSS-latest-stable: + name: IntelliJ UE OSS Latest Stable + platform: ubuntu2204 build_flags: - - --define=ij_product=intellij-ue-oss-beta + - --define=ij_product=intellij-ue-oss-latest-stable build_targets: - //ijwb/... test_flags: - - --define=ij_product=intellij-ue-oss-beta + - --define=ij_product=intellij-ue-oss-latest-stable - --test_output=errors test_targets: - //:ijwb_ue_tests IntelliJ-UE-OSS-under-dev: name: IntelliJ UE OSS Under Development - platform: ubuntu1804 + platform: ubuntu2204 build_flags: - --define=ij_product=intellij-ue-oss-under-dev build_targets: @@ -76,4 +76,3 @@ tasks: - //:ijwb_ue_tests soft_fail: - exit_status: 1 - diff --git a/.bazelci/intellij.yml b/.bazelci/intellij.yml index 2ebc69aa24c..a4e70ac8adc 100644 --- a/.bazelci/intellij.yml +++ b/.bazelci/intellij.yml @@ -2,7 +2,7 @@ tasks: IntelliJ-CE-internal-stable: name: IntelliJ CE Internal Stable - platform: ubuntu1804 + platform: ubuntu2204 build_flags: - --define=ij_product=intellij-latest build_targets: @@ -14,7 +14,7 @@ tasks: - //:ijwb_ce_tests IntelliJ-CE-internal-beta: name: IntelliJ CE Internal Beta - platform: ubuntu1804 + platform: ubuntu2204 build_flags: - --define=ij_product=intellij-beta build_targets: @@ -26,7 +26,7 @@ tasks: - //:ijwb_ce_tests IntelliJ-CE-internal-under-dev: name: IntelliJ CE Internal Under Development - platform: ubuntu1804 + platform: ubuntu2204 build_flags: - --define=ij_product=intellij-under-dev build_targets: @@ -38,33 +38,33 @@ tasks: - //:ijwb_ce_tests soft_fail: - exit_status: 1 - IntelliJ-CE-OSS-stable: - name: IntelliJ CE OSS Stable - platform: ubuntu1804 + IntelliJ-CE-OSS-oldest-stable: + name: IntelliJ CE OSS Oldest Stable + platform: ubuntu2204 build_flags: - - --define=ij_product=intellij-oss-stable + - --define=ij_product=intellij-oss-oldest-stable build_targets: - //ijwb/... test_flags: - - --define=ij_product=intellij-oss-stable + - --define=ij_product=intellij-oss-oldest-stable - --test_output=errors test_targets: - //:ijwb_ce_tests - IntelliJ-CE-OSS-beta: - name: IntelliJ CE OSS Beta - platform: ubuntu1804 + IntelliJ-CE-OSS-latest-stable: + name: IntelliJ CE OSS Latest Stable + platform: ubuntu2204 build_flags: - - --define=ij_product=intellij-oss-beta + - --define=ij_product=intellij-oss-latest-stable build_targets: - //ijwb/... test_flags: - - --define=ij_product=intellij-oss-beta + - --define=ij_product=intellij-oss-latest-stable - --test_output=errors test_targets: - //:ijwb_ce_tests IntelliJ-CE-OSS-under-dev: name: IntelliJ CE OSS Under Development - platform: ubuntu1804 + platform: ubuntu2204 build_flags: - --define=ij_product=intellij-oss-under-dev build_targets: @@ -76,4 +76,3 @@ tasks: - //:ijwb_ce_tests soft_fail: - exit_status: 1 - diff --git a/.bazelrc b/.bazelrc new file mode 100644 index 00000000000..5e811013437 --- /dev/null +++ b/.bazelrc @@ -0,0 +1,11 @@ +build --java_language_version=17 --java_runtime_version=17 + +# delete testdata package needed for bazel integration tests +build --deleted_packages=//aspect/testing/tests/src/com/google/idea/blaze/aspect/integration/testdata +query --deleted_packages=//aspect/testing/tests/src/com/google/idea/blaze/aspect/integration/testdata + +# TODO: migrate all dependencies from WORKSPACE to MODULE.bazel +common --noenable_bzlmod +common --enable_workspace +common --experimental_google_legacy_api +common --noincompatible_disallow_empty_glob \ No newline at end of file diff --git a/BUILD b/BUILD index ccefc718112..ea8a9006e31 100644 --- a/BUILD +++ b/BUILD @@ -1,9 +1,13 @@ +load("@bazel_tools//tools/jdk:default_java_toolchain.bzl", "default_java_toolchain") +load("@rules_java//java:defs.bzl", "java_package_configuration") + # # Description: Blaze plugin for various IntelliJ products. # load( "//:build-visibility.bzl", "BAZEL_PLUGIN_SUBPACKAGES", + "DEFAULT_TEST_VISIBILITY", "create_plugin_packages_group", ) @@ -18,6 +22,7 @@ filegroup( visibility = BAZEL_PLUGIN_SUBPACKAGES, ) +# BEGIN-EXTERNAL # IJwB tests, run with an IntelliJ plugin SDK test_suite( name = "ijwb_common_tests", @@ -62,6 +67,19 @@ test_suite( ], ) +# CLwB tests, run with a CLion plugin SDK +test_suite( + name = "clwb_tests", + tests = [ + "//base:unit_tests", + "//clwb:unit_tests", + "//cpp:unit_tests", + "//dart:unit_tests", + "//python:unit_tests", + ], +) +# END-EXTERNAL + # ASwB tests, run with an Android Studio plugin SDK test_suite( name = "aswb_tests", @@ -76,24 +94,32 @@ test_suite( "//java:integration_tests", "//java:unit_tests", ], + visibility = DEFAULT_TEST_VISIBILITY, ) -test_suite( - name = "aswb_python_tests", - tests = [ - "//python:integration_tests", - "//python:unit_tests", +default_java_toolchain( + name = "custom_java_17_toolchain", + configuration = dict(), + java_runtime = "@rules_java//toolchains:remotejdk_17", + package_configuration = [ + ":java_8", ], + source_version = "17", + target_version = "17", ) -# CLwB tests, run with a CLion plugin SDK -test_suite( - name = "clwb_tests", - tests = [ - "//base:unit_tests", - "//clwb:unit_tests", - "//cpp:unit_tests", - "//dart:unit_tests", - "//python:unit_tests", +# this associates a set of javac flags with a set of packages +java_package_configuration( + name = "java_8", + javacopts = ["-source 8 -target 8"], + packages = ["java_8_packages"], +) + +# this is a regular package_group, which is used to specify a set of packages to apply flags to +package_group( + name = "java_8_packages", + packages = [ + "//proto/...", + "//third_party/bazel/src/main/protobuf/...", ], ) diff --git a/CHANGELOG b/CHANGELOG index 12668069987..6fede1ab6fd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,220 @@ +v2024.08.27 +=========== +* Internal cleanup + +v2024.08.13 +=========== +* Internal cleanup + +v2024.07.30 +=========== +* Internal cleanup + +v2024.07.16 +=========== +* Internal cleanup + +v2024.07.02 +=========== +* Internal cleanup + +v2024.06.18 +=========== +* Internal cleanup + +v2024.06.04-RC-2 +================ +* Address CVE-2024-5899 +* Internal cleanup + +v2024.06.04 +=========== +* Internal cleanup + +v2024.05.21 +=========== +* Internal cleanup + +v2024.05.07 +=========== +* Internal cleanup + +v2024.04.23 +=========== +* Plugin aspect now depends on @rules_java. If this breaks you, please update your @rules_java version to 5.3.5 or later. +* Internal Cleanup + +v2024.04.09 +=========== +* Internal cleanup + +v2024.03.26 +=========== +* Plugin is now compatible with Iguana RC 2 2023.2 and Jellyfish Beta 1 2023.3 +* Internal cleanup + +v2024.03.12 +=========== +* Internal cleanup + +v2024.02.27 +=========== +* Internal cleanup + +v2024.02.13 +=========== +* Internal cleanup + +v2024.01.30 +=========== +* Internal Cleanup + +v2024.01.16 +=========== +* Internal Cleanup + +v2024.01.02 +=========== +* Internal Cleanup + +v2023.12.19 +=========== +* Internal Cleanup + +v2023.12.05 +=========== +* Internal Cleanup + +v2023.11.21 +=========== +* Internal Cleanup + +v2023.11.07 +=========== +* Internal Cleanup + +v2023.10.24 +=========== +* Internal Cleanup + +v2023.10.10 +=========== +* Internal Cleanup + +v2023.09.26 +=========== +* Internal cleanup + +v2023.09.12 +=========== +* Internal Cleanup + +v2023.08.29 +=========== +* Support Android Studio 2023.1 +* Internal Cleanup + +v2023.08.15 +=========== +* Internal cleanup + +v2023.08.01 +=========== +* Internal cleanup + +v2023.07.18 +=========== +* [query-sync] "Build dependecies for open files" action +* Internal cleanup + +v2023.07.04 +=========== +* [query-sync] Support the "Build dependencies" action for directories and BUILD files. +* Internal cleanup + + +v2023.06.13 +=========== +* Move oldest supported Bazel version to 4.0.0 +* Internal cleanup + +v2023.05.30 +=========== +* Internal cleanup + +v2023.05.16 +=========== +* Internal cleanup + +v2023.05.02 +=========== +* Internal cleanup +* Plugin compatible with Android Studio 2022.3 + +v2023.04.18 +=========== +* Fix mobile install step: https://github.com/bazelbuild/intellij/pull/4669 +* Fix partial query fails to read input stream: https://github.com/bazelbuild/intellij/pull/4739 +* Internal cleanup + +v2023.04.04 +=========== +Internal cleanup. + +v2023.03.21 +=========== +* Internal cleanup. +* Plugin compatible with Android Studio 2022.2. + +v2023.03.07 +=========== +* Internal cleanup. +* Fix (Failed Android apk target build: https://github.com/bazelbuild/intellij/issues/4462) +* Support leading `@` in instrumentation test targets labels (https://github.com/bazelbuild/intellij/pull/4530) + +v2023.02.22 +=========== +* Internal cleanup. + +v2023.02.07 +=========== +* Internal cleanup. + +v2023.01.24 +=========== +* Internal cleanup. +* Plugin compatible with Android Studio 2022.1 + +v2023.01.10 +=========== +* `BlazeAndroidSyncBuildFlagsProvider` is removed in (https://github.com/bazelbuild/intellij/commit/583537c63866eac13169ce128bb53aa0017bc6e0) (addresses issues: #3787 and #3703) +* Internal cleanup. + +v2022.12.13 +=========== +* Internal cleanup. + +v2022.11.29 +=========== +* Bug fixes and internal cleanup. + +v2022.11.15 +=========== +* Invoke Live Edit from ASwB (56913bc). +* Internal cleanup. + +v2022.11.01 +=========== +* Internal cleanup. + +v2022.07.26 +=========== +* Fix Blaze target highlighting in the integrated terminal. + +v2022.07.12 +=========== +* Bug fixes and internal cleanup. + v2022.06.14 =========== * Kotlin: Fix the detection of which test target to run for a newly created diff --git a/WORKSPACE b/WORKSPACE index 3a7cdab66a8..164279e483e 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -35,8 +35,16 @@ http_archive( http_archive( name = "intellij_ce_2022_2", build_file = "@//intellij_platform_sdk:BUILD.idea222", - sha256 = "a935cd7832613d223c711cf2c509bbc8399f29d2b36c2da10bf5fdfb0dbd2788", - url = "https://www.jetbrains.com/intellij-repository/snapshots/com/jetbrains/intellij/idea/ideaIC/222.2964.55-EAP-SNAPSHOT/ideaIC-222.2964.55-EAP-SNAPSHOT.zip", + sha256 = "19cf087718400dbc5a90c6423aa71ebfbfe1c504e8fc399034b864cb6d2e7275", + url = "https://www.jetbrains.com/intellij-repository/releases/com/jetbrains/intellij/idea/ideaIC/2022.2.5/ideaIC-2022.2.5.zip", +) + +# The plugin api for intellij_ce_2022_3 idea. This is required to build IJwB and run integration tests. +http_archive( + name = "intellij_ce_2022_3", + build_file = "@//intellij_platform_sdk:BUILD.idea223", + sha256 = "5fe178e7a52ed3efa72f0761f17cbf065ebfc7e6c91c4366cd2e87d1bb032794", + url = "https://www.jetbrains.com/intellij-repository/releases/com/jetbrains/intellij/idea/ideaIC/2022.3.3/ideaIC-2022.3.3.zip", ) # The plugin api for IntelliJ UE 2021.2. This is required to run UE-specific @@ -68,8 +76,16 @@ http_archive( http_archive( name = "intellij_ue_2022_2", build_file = "@//intellij_platform_sdk:BUILD.ue222", - sha256 = "eb085ef7b95e86f2525c268252dcdb4edeb03584fa6aa0284584fe4faf2401bc", - url = "https://www.jetbrains.com/intellij-repository/snapshots/com/jetbrains/intellij/idea/ideaIU/222.2964.55-EAP-SNAPSHOT/ideaIU-222.2964.55-EAP-SNAPSHOT.zip", + sha256 = "557eb6ddab79894ea3b96f072b7ab797b7733329c0ae03b3701fb098e0ebb63a", + url = "https://www.jetbrains.com/intellij-repository/releases/com/jetbrains/intellij/idea/ideaIU/2022.2.5/ideaIU-2022.2.5.zip", +) + +# The plugin api for intellij_ue_2022_3 ue. This is required to run UE-specific integration tests. +http_archive( + name = "intellij_ue_2022_3", + build_file = "@//intellij_platform_sdk:BUILD.ue223", + sha256 = "c4711503aed650d12c2d3014721384d7c223d23591400bae7e94286e511d3220", + url = "https://www.jetbrains.com/intellij-repository/releases/com/jetbrains/intellij/idea/ideaIU/2022.3.3/ideaIU-2022.3.3.zip", ) # The plugin api for clion_2021_2. This is required to build CLwB, and run integration tests. @@ -100,8 +116,16 @@ http_archive( http_archive( name = "clion_2022_2", build_file = "@//intellij_platform_sdk:BUILD.clion222", - sha256 = "12edae92d2fdb2a6234963f329fb65241a0aba12ec96a8302571affa0e3edd26", - url = "https://download.jetbrains.com/cpp/CLion-222.2964.48.tar.gz", + sha256 = "94ffbdf82606f2f90618c1fdb89432d627e7f24ae158b36a591da2c303047436", + url = "https://download.jetbrains.com/cpp/CLion-2022.2.tar.gz", +) + +# The plugin api for clion_2022_3 clion. This is required to build CLwB\, and run integration tests. +http_archive( + name = "clion_2022_3", + build_file = "@//intellij_platform_sdk:BUILD.clion223", + sha256 = "1b46ff0791bcb38ecb39c5f4a99941f99ed73d4f6d924a2042fdb55afc5fc03d", + url = "https://download.jetbrains.com/cpp/CLion-2022.3.3.tar.gz", ) _PYTHON_CE_BUILD_FILE = """ @@ -153,14 +177,42 @@ http_archive( http_archive( name = "python_2022_2", build_file_content = _PYTHON_CE_BUILD_FILE, - sha256 = "19f54957e89be0e9c2e4af67aeefa357ec4d2801b353cf912df65ce9a8fc6732", - url = "https://plugins.jetbrains.com/files/7322/183142/python-ce-222.2964.16.zip", + sha256 = "aaae5ea44b5ad18793f8de63c00dce0371d91c14f7381260d19c4adaf4f9c9bf", + url = "https://plugins.jetbrains.com/files/7322/305491/python-ce-222.4554.5.zip", +) + +http_archive( + name = "python_2022_3", + build_file_content = _PYTHON_CE_BUILD_FILE, + sha256 = "bfbde44928f446b2706df875bfa56cc64ee490875341c79aebba32ff7bb0f9d4", + url = "https://plugins.jetbrains.com/files/7322/300704/python-ce-223.8836.26.zip", +) + +http_archive( + name = "python_2023_1", + build_file_content = _PYTHON_CE_BUILD_FILE, + sha256 = "825c30d2cbcce405fd18fddf356eb1f425607e9c780f8eff95d21ac23f8d90fd", + url = "https://plugins.jetbrains.com/maven/com/jetbrains/plugins/PythonCore/231.8770.65/PythonCore-231.8770.65.zip", +) + +http_archive( + name = "python_2023_2", + build_file_content = _PYTHON_CE_BUILD_FILE, + sha256 = "e744349f353568c18a9e11ec5e3a205f62bbdc1b65c9abc96783c479fe2aa51b", + url = "https://plugins.jetbrains.com/maven/com/jetbrains/plugins/PythonCore/232.9921.47/PythonCore-232.9921.47.zip", +) + +http_archive( + name = "python_2023_3", + build_file_content = _PYTHON_CE_BUILD_FILE, + sha256 = "48ef31f29e40ab3824027299b6bd7a0267aaad8175ebb1a5f10841122f5e9513", + url = "https://plugins.jetbrains.com/maven/com/jetbrains/plugins/PythonCore/233.13135.65/PythonCore-233.13135.65.zip", ) _GO_BUILD_FILE = """ java_import( name = "go", - jars = glob(["go/lib/*.jar"]), + jars = glob(["go*/lib/*.jar"]), visibility = ["//visibility:public"], ) """ @@ -185,15 +237,22 @@ http_archive( http_archive( name = "go_2022_1", build_file_content = _GO_BUILD_FILE, - sha256 = "50b96a18d3bf95b2742fb01d8d8ae9d7faef7113b882011afea4a912699ac0f9", - url = "https://plugins.jetbrains.com/files/9568/167522/go-221.5080.210.zip", + sha256 = "4219a3b76c985ad1066d4ff99f516422bcbbfda2feba6a950e8bb6c5e544e3ea", + url = "https://plugins.jetbrains.com/files/9568/185980/go-221.5921.16.zip", ) http_archive( name = "go_2022_2", build_file_content = _GO_BUILD_FILE, - sha256 = "eaab17db1de510ab28df8c65c07c9c35252a67a89253fa99660c703c88c1ec68", - url = "https://plugins.jetbrains.com/files/9568/183564/go-222.2964.55.zip", + sha256 = "cc19cc418b512420643c8c94eaf2cf1775de3183b1a8d0c2703959fcc4275afd", + url = "https://plugins.jetbrains.com/files/9568/256314/go-222.4459.24.zip", +) + +http_archive( + name = "go_2022_3", + build_file_content = _GO_BUILD_FILE, + sha256 = "c762d2ff0253b8a996b8225344b345ae30b518dd20b3fd23bc1f417a90756c79", + url = "https://plugins.jetbrains.com/files/9568/293630/go-plugin-223.8836.7.zip", ) _SCALA_BUILD_FILE = """ @@ -231,28 +290,107 @@ http_archive( http_archive( name = "scala_2022_2", build_file_content = _SCALA_BUILD_FILE, - sha256 = "7fb67d1baa3dea7ffcf5ea3c6e06796aa00144062e6895b2422fa0cdac32a876", - url = "https://plugins.jetbrains.com/files/1347/183692/scala-intellij-bin-2022.2.4.zip", + sha256 = "9f668afa80e5cff90667fca7e7737d47a9ec73f39ff59b3488881371349afcff", + url = "https://plugins.jetbrains.com/files/1347/258933/scala-intellij-bin-2022.2.19.zip", ) -# The plugin api for android_studio_2021_2. This is required to build ASwB, -# and run integration tests. http_archive( - name = "android_studio_2021_2", - build_file = "@//intellij_platform_sdk:BUILD.android_studio212", - sha256 = "0018e0dfc0dd2921700516f7a2c443377c557788da7fb0a45243ecb4300745be", - url = "https://dl.google.com/dl/android/studio/ide-zips/2021.2.1.15/android-studio-2021.2.1.15-linux.tar.gz", + name = "scala_2022_3", + build_file_content = _SCALA_BUILD_FILE, + sha256 = "e3b49e40153f441a3e0f8bf28aa09fccf7b5bf92b58f53c72aa546d74bfeefbb", + url = "https://plugins.jetbrains.com/files/1347/301506/scala-intellij-bin-2022.3.20.zip", ) -# The plugin api for android_studio_2021_3. This is required to build ASwB, -# and run integration tests. +# The plugin api for android_studio_2022_2 android_studio. This is required to build ASwB and run integration tests +http_archive( + name = "android_studio_2022_2", + build_file = "@//intellij_platform_sdk:BUILD.android_studio222", + sha256 = "cdd852c4499b5f7402df44dfc69e8ca418ffc9a684caab34047476fd2cb24efc", + url = "https://dl.google.com/dl/android/studio/ide-zips/2022.2.1.18/android-studio-2022.2.1.18-linux.tar.gz", +) + +# The plugin api for android_studio_dev android_studio. This is required to build ASwB and run integration tests +http_archive( + name = "android_studio_dev", + build_file = "@//intellij_platform_sdk:BUILD.android_studiodev", + sha256 = "cb3f0494220f92dd85399adfb8655a1a2bd81b238d26e63a8bbd8bde95a0fccf", + url = "https://android-build", +) + +# The plugin api for android_studio_2023_3 android_studio. This is required to build ASwB and run integration tests http_archive( - name = "android_studio_2021_3", - build_file = "@//intellij_platform_sdk:BUILD.android_studio213", - sha256 = "fdfa6acc089934d747d3af56a4fb83534384e0e2869b09c221c30856efbb5fb2", - url = "https://dl.google.com/dl/android/studio/ide-zips/2021.3.1.13/android-studio-2021.3.1.13-linux.tar.gz", + name = "android_studio_2023_3", + build_file = "@//intellij_platform_sdk:BUILD.android_studio233", + sha256 = "3e786a48a831b345d0e5c9bf3c1d753a6b36a7568089b5ca7b9d900bef255fc7", + url = "https://dl.google.com/dl/android/studio/ide-zips/2023.3.1.7/android-studio-2023.3.1.7-linux.tar.gz", ) +# The plugin api for android_studio_2023_2 android_studio. This is required to build ASwB and run integration tests +http_archive( + name = "android_studio_2023_2", + build_file = "@//intellij_platform_sdk:BUILD.android_studio232", + sha256 = "0026427572849c9cbb0c94d6f9718ea08bc345dccfe3b372b54100a95fff99b5", + url = "https://dl.google.com/dl/android/studio/ide-zips/2023.2.1.24/android-studio-2023.2.1.24-linux.tar.gz", +) + +# The plugin api for android_studio_2023_1 android_studio. This is required to build ASwB and run integration tests +http_archive( + name = "android_studio_2023_1", + build_file = "@//intellij_platform_sdk:BUILD.android_studio231", + sha256 = "139d0dbb4909353b68fbf55c09b6d31a34512044a9d4f02ce0f1a9accca128f9", + url = "https://dl.google.com/dl/android/studio/ide-zips/2023.1.1.28/android-studio-2023.1.1.28-linux.tar.gz", +) + +# The plugin api for android_studio_2022_3 android_studio. This is required to build ASwB and run integration tests +http_archive( + name = "android_studio_2022_3", + build_file = "@//intellij_platform_sdk:BUILD.android_studio223", + sha256 = "250625dcab183e0c68ebf12ef8a522af7369527d76f1efc704f93c05b02ffa9e", + url = "https://dl.google.com/dl/android/studio/ide-zips/2022.3.1.19/android-studio-2022.3.1.19-linux.tar.gz", +) + +http_archive( + name = "rules_java", + urls = [ + "https://github.com/bazelbuild/rules_java/releases/download/8.6.2/rules_java-8.6.2.tar.gz", + ], + sha256 = "a64ab04616e76a448c2c2d8165d836f0d2fb0906200d0b7c7376f46dd62e59cc", +) + +_protobuf_version = "29.0" + +_protobuf_sha256 = "10a0d58f39a1a909e95e00e8ba0b5b1dc64d02997f741151953a2b3659f6e78c" + +http_archive( + name = "com_google_protobuf", + sha256 = _protobuf_sha256, + strip_prefix = "protobuf-%s" % _protobuf_version, + urls = ["https://github.com/protocolbuffers/protobuf/archive/v%s.tar.gz" % _protobuf_version], +) + +http_archive( + name = "rules_python", + sha256 = "690e0141724abb568267e003c7b6d9a54925df40c275a870a4d934161dc9dd53", + strip_prefix = "rules_python-0.40.0", + url = "https://github.com/bazelbuild/rules_python/releases/download/0.40.0/rules_python-0.40.0.tar.gz", +) + +http_archive( + name = "rules_proto", + sha256 = "0e5c64a2599a6e26c6a03d6162242d231ecc0de219534c38cb4402171def21e8", + strip_prefix = "rules_proto-7.0.2", + url = "https://github.com/bazelbuild/rules_proto/releases/download/7.0.2/rules_proto-7.0.2.tar.gz", +) + +load("@rules_java//java:rules_java_deps.bzl", "rules_java_dependencies") +rules_java_dependencies() + +load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps") +protobuf_deps() + +load("@rules_java//java:repositories.bzl", "rules_java_toolchains") +rules_java_toolchains() + # LICENSE: Common Public License 1.0 jvm_maven_import_external( name = "junit", @@ -334,6 +472,72 @@ jvm_maven_import_external( server_urls = ["https://repo1.maven.org/maven2"], ) +jvm_maven_import_external( + name = "com_google_guava_guava", + artifact = "com.google.guava:guava:31.1-jre", + artifact_sha256 = "a42edc9cab792e39fe39bb94f3fca655ed157ff87a8af78e1d6ba5b07c4a00ab", + server_urls = [ + "https://repo1.maven.org/maven2", + ], +) + +jvm_maven_import_external( + name = "com_google_guava_failureaccess", + artifact = "com.google.guava:failureaccess:1.0.2", + artifact_sha256 = "8a8f81cf9b359e3f6dfa691a1e776985c061ef2f223c9b2c80753e1b458e8064", + server_urls = [ + "https://repo1.maven.org/maven2", + ], +) + +jvm_maven_import_external( + name = "gson", + artifact = "com.google.code.gson:gson:2.9.1", + artifact_sha256 = "378534e339e6e6d50b1736fb3abb76f1c15d1be3f4c13cec6d536412e23da603", + server_urls = [ + "https://repo1.maven.org/maven2", + ], +) + +jvm_maven_import_external( + name = "flogger", + artifact = "com.google.flogger:flogger:0.7.4", + artifact_sha256 = "77aac11b3c26e1e184dcfe79c55ac6e27967a6dfe1c04146125176940bc64a55", + server_urls = [ + "https://repo1.maven.org/maven2", + ], +) + +jvm_maven_import_external( + name = "flogger_system_backend", + artifact = "com.google.flogger:flogger-system-backend:0.7.4", + artifact_sha256 = "fd66f2615a9d8fe1b2274f1b5005a5555a0cd63cdfdab2ca9500e6eb81dc5f63", + server_urls = [ + "https://repo1.maven.org/maven2", + ], +) + +http_archive( + name = "rules_android", + sha256 = "af84b69ab3d16dd1a41056286e6511f147a94ccea995603e13e934c915c1631c", + strip_prefix = "rules_android-0.6.0", + urls = ["https://github.com/bazelbuild/rules_android/releases/download/v0.6.0/rules_android-v0.6.0.tar.gz"], +) +load("@rules_android//:prereqs.bzl", "rules_android_prereqs") +rules_android_prereqs() +load("@rules_android//:defs.bzl", "rules_android_workspace") +rules_android_workspace() + +load("@rules_android//rules:rules.bzl", "android_sdk_repository") +android_sdk_repository( + name = "androidsdk", +) + +register_toolchains( + "@rules_android//toolchains/android:android_default_toolchain", + "@rules_android//toolchains/android_sdk:android_sdk_tools", +) + _JARJAR_BUILD_FILE = """ java_binary( name = "jarjar_bin", @@ -373,10 +577,17 @@ new_git_repository( shallow_since = "1518210648 -0800", ) +load("@rules_python//python:repositories.bzl", "py_repositories") + +py_repositories() + http_archive( name = "bazel_skylib", - sha256 = "2ef429f5d7ce7111263289644d233707dba35e39696377ebab8b0bc701f7818e", - url = "https://github.com/bazelbuild/bazel-skylib/releases/download/0.8.0/bazel-skylib.0.8.0.tar.gz", + sha256 = "74d544d96f4a5bb630d465ca8bbcfe231e3594e5aae57e1edbf17a6eb3ca2506", + urls = [ + "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.3.0/bazel-skylib-1.3.0.tar.gz", + "https://github.com/bazelbuild/bazel-skylib/releases/download/1.3.0/bazel-skylib-1.3.0.tar.gz", + ], ) # specify a minimum version for bazel otherwise users on old versions may see @@ -385,63 +596,39 @@ load("@bazel_skylib//lib:versions.bzl", "versions") versions.check(minimum_bazel_version = "5.2.0") -http_archive( - name = "build_bazel_integration_testing", - sha256 = "e055ff971787a27d6942a83ffd182953988c88dfa82e89138ccc83bf410a65d6", - strip_prefix = "bazel-integration-testing-2a4f6c244312c036e0f3a125ee6086637ee7723b", - url = "https://github.com/bazelbuild/bazel-integration-testing/archive/2a4f6c244312c036e0f3a125ee6086637ee7723b.zip", -) - -load("@build_bazel_integration_testing//tools:bazel_java_integration_test.bzl", "bazel_java_integration_test_deps") - -bazel_java_integration_test_deps(versions = [ - "0.28.1", - "0.27.2", -]) - -load("@build_bazel_integration_testing//tools:import.bzl", "bazel_external_dependency_archive") +load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace") -bazel_external_dependency_archive( - name = "integration_test_deps", - srcs = { - # Bazel 0.28.1, 0.27.2 - "cc470e529fafb6165b5be3929ff2d99b38429b386ac100878687416603a67889": [ - "https://mirror.bazel.build/bazel_coverage_output_generator/releases/coverage_output_generator-v1.0.zip", - ], - # Bazel 0.28.1 - "96e223094a12c842a66db0bb7bb6866e88e26e678f045842911f9bd6b47161f5": [ - "https://mirror.bazel.build/bazel_java_tools/releases/javac11/v4.0/java_tools_javac11_linux-v4.0.zip", - ], - # Bazel 0.27.2 - "074d624fb34441df369afdfd454e75dba821d5d54932fcfee5ba598d17dc1b99": [ - "https://mirror.bazel.build/bazel_java_tools/releases/javac11/v2.0/java_tools_javac11_linux-v2.0.zip", - ], - }, -) +bazel_skylib_workspace() -# LICENSE: The Apache Software License, Version 2.0 http_archive( - name = "rules_proto", - sha256 = "aa1ee19226f707d44bee44c720915199c20c84a23318bb0597ed4e5c873ccbd5", - strip_prefix = "rules_proto-40298556293ae502c66579620a7ce867d5f57311", + name = "contrib_rules_bazel_integration_test", + sha256 = "20d670bb614d311a2a0fc8af53760439214731c3d5be2d9b0a197dccc19583f5", + strip_prefix = "rules_bazel_integration_test-0.9.0", urls = [ - "https://mirror.bazel.build/github.com/bazelbuild/rules_proto/archive/40298556293ae502c66579620a7ce867d5f57311.tar.gz", - "https://github.com/bazelbuild/rules_proto/archive/40298556293ae502c66579620a7ce867d5f57311.tar.gz", + "http://github.com/bazel-contrib/rules_bazel_integration_test/archive/v0.9.0.tar.gz", ], ) -load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies", "rules_proto_toolchains") +load("@contrib_rules_bazel_integration_test//bazel_integration_test:deps.bzl", "bazel_integration_test_rules_dependencies") -rules_proto_dependencies() +bazel_integration_test_rules_dependencies() -rules_proto_toolchains() +load("@contrib_rules_bazel_integration_test//bazel_integration_test:defs.bzl", "bazel_binaries") + +bazel_binaries(versions = [ + "4.0.0", + "6.0.0", +]) # LICENSE: The Apache Software License, Version 2.0 +rules_scala_version = "8f255cd1fecfe4d43934b161b3edda58bdb2e8f4" + http_archive( name = "io_bazel_rules_scala", - sha256 = "ccf19e8f966022eaaca64da559c6140b23409829cb315f2eff5dc3e757fb6ad8", - strip_prefix = "rules_scala-e4560ac332e9da731c1e50a76af2579c55836a5c", - urls = ["https://github.com/bazelbuild/rules_scala/archive/e4560ac332e9da731c1e50a76af2579c55836a5c.zip"], + sha256 = "14797e907c5614387452c42412d755ad7e343ea12540a53da1430be3301c8b4b", + strip_prefix = "rules_scala-%s" % rules_scala_version, + type = "zip", + url = "https://github.com/bazelbuild/rules_scala/archive/%s.zip" % rules_scala_version, ) load("@io_bazel_rules_scala//:scala_config.bzl", "scala_config") @@ -463,9 +650,9 @@ scalatest_repositories() scalatest_toolchain() # LICENSE: The Apache Software License, Version 2.0 -rules_kotlin_version = "1.6.0" +rules_kotlin_version = "1.7.0-RC-1" -rules_kotlin_sha = "a57591404423a52bd6b18ebba7979e8cd2243534736c5c94d35c89718ea38f94" +rules_kotlin_sha = "68b910730026921814d3a504ccbe9adaac9938983d940e626523e6e4ecfb0355" http_archive( name = "io_bazel_rules_kotlin", @@ -508,3 +695,65 @@ jvm_maven_import_external( licenses = ["notice"], # Apache 2.0 server_urls = ["https://repo1.maven.org/maven2"], ) + +http_archive( + name = "io_bazel_rules_go", + sha256 = "6b65cb7917b4d1709f9410ffe00ecf3e160edf674b78c54a894471320862184f", + urls = [ + "https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.39.0/rules_go-v0.39.0.zip", + "https://github.com/bazelbuild/rules_go/releases/download/v0.39.0/rules_go-v0.39.0.zip", + ], +) + +# needed for cpp tests +http_archive( + name = "com_google_absl", + sha256 = "987ce98f02eefbaf930d6e38ab16aa05737234d7afbab2d5c4ea7adbe50c28ed", + strip_prefix = "abseil-cpp-20230802.1", + urls = [ + "https://github.com/abseil/abseil-cpp/archive/refs/tags/20230802.1.tar.gz", + ], +) + +jvm_maven_import_external( + name = "io_netty_netty_common", + artifact = "io.netty:netty-common:4.1.96.Final", + artifact_sha256 = "da104e80db830922eaf860eb1c5e957cd1d124068253d02e9c7a7843bc66427a", + licenses = ["notice"], # Apache 2.0 + server_urls = ["https://repo1.maven.org/maven2"], +) + +jvm_maven_import_external( + name = "io_netty_netty_transport", + artifact = "io.netty:netty-transport:4.1.96.Final", + artifact_sha256 = "8fe3afbe8b094a7b9f1eb27becf1cf017e5572343c1744d2b6040d5f331e84e3", + licenses = ["notice"], # Apache 2.0 + server_urls = ["https://repo1.maven.org/maven2"], +) + +jvm_maven_import_external( + name = "io_netty_netty_transport_native_epoll", + artifact = "io.netty:netty-transport-classes-epoll:4.1.96.Final", + artifact_sha256 = "1591b3ea061932677dc2bab6cb7d82e8f1837a52b3c781f4daa99984ec87a9cd", + licenses = ["notice"], # Apache 2.0 + server_urls = ["https://repo1.maven.org/maven2"], +) + +jvm_maven_import_external( + name = "io_netty_netty_transport_native_unix_common", + artifact = "io.netty:netty-transport-native-unix-common:4.1.96.Final", + artifact_sha256 = "4f96297a06a544a4cdb6fe6af8b868640f100fa96969e2196be216bd41adef13", + licenses = ["notice"], # Apache 2.0 + server_urls = ["https://repo1.maven.org/maven2"], +) + +jvm_maven_import_external( + name = "io_netty_netty_transport_classes_kqueue", + artifact = "io.netty:netty-transport-classes-kqueue:4.1.96.Final", + artifact_sha256 = "f2f1fab3b297aee20a3922c79b548c8b4b72bb10b635375434c108ee05f29430", + licenses = ["notice"], # Apache 2.0 + server_urls = ["https://repo1.maven.org/maven2"], +) + +# Register custom java 17 toolchain +register_toolchains("//:custom_java_17_toolchain_definition") diff --git a/aspect/BUILD b/aspect/BUILD index bfc18c42d0a..7b857a764ae 100644 --- a/aspect/BUILD +++ b/aspect/BUILD @@ -7,7 +7,7 @@ load( "define_flag_hack", ) -licenses(["notice"]) # Apache 2.0 +licenses(["notice"]) # Files needed at runtime for blaze-invoking integration tests filegroup( @@ -31,9 +31,11 @@ filegroup( srcs = [ "WORKSPACE", "artifacts.bzl", - "fast_build_info.bzl", + "build_dependencies.bzl", + "fast_build_info_bundled.bzl", + "intellij_info.bzl", "intellij_info_bundled.bzl", - "intellij_info_impl.bzl", + "intellij_info_impl_bundled.bzl", "java_classpath.bzl", "make_variables.bzl", ":BUILD.bazel", @@ -61,13 +63,35 @@ _dev_aspect_path = package_name() # Changes: # //.../aspect/tools:tool_name -> //:tool_name_bin # //.../aspect:flag_hack -> //:flag_hack +# :intellij_info_impl.bzl -> :intellij_info_impl_bundled.bzl genrule( name = "modify_tools_path", srcs = ["intellij_info.bzl"], outs = ["intellij_info_bundled.bzl"], cmd = "cat $(SRCS) >$@ && " + "sed -i -e 's,//%s/tools:\" + tool_name,//:\" + tool_name + \"_bin\",g' $@ && " % _dev_aspect_path + - "sed -i -e 's,//%s:flag_hack,//:flag_hack,g' $@" % _dev_aspect_path, + "sed -i -e 's,//%s:flag_hack,//:flag_hack,g' $@ && " % _dev_aspect_path + + "sed -i -e 's,:intellij_info_impl.bzl,:intellij_info_impl_bundled.bzl,g' $@", +) + +# Removes internal only content from bundled aspect files as they cannot access +# by bazel no matter it's build from git repo or internal repo. +genrule( + name = "create_intellij_info_impl_bundle", + srcs = ["intellij_info_impl.bzl"], + outs = ["intellij_info_impl_bundled.bzl"], + cmd = "cat $(SRCS) >$@ && " + + "sed -i -e '/BUNDLED-IGNORE-BEGIN/,/BUNDLED-IGNORE-END/d' $@ && " + + "sed -i -e 's,load(\".*/rules_java/,load(\"@rules_java//', $@", +) + +# Makes bundled fast_build_info.bzl use bundled intellij_info_impl.bzl +genrule( + name = "create_fast_build_info_bundle", + srcs = ["fast_build_info.bzl"], + outs = ["fast_build_info_bundled.bzl"], + cmd = "cat $(SRCS) >$@ && " + + "sed -i -e 's,:intellij_info_impl.bzl,:intellij_info_impl_bundled.bzl,g' $@", ) define_flag_hack() diff --git a/aspect/BUILD.aspect b/aspect/BUILD.aspect index cbcadb0c6ea..8369d08edf1 100644 --- a/aspect/BUILD.aspect +++ b/aspect/BUILD.aspect @@ -3,16 +3,19 @@ # The final form of the BUILD file accessed at runtime as an external WORKSPACE. # -licenses(["notice"]) # Apache 2.0 +load("@rules_java//java:defs.bzl", "java_binary", "java_import") +load( + ":intellij_info_impl_bundled.bzl", + "define_flag_hack", +) -load(":intellij_info_impl.bzl", - "define_flag_hack") +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"], + runtime_deps = [":jar_filter_lib"], ) java_import( @@ -23,8 +26,8 @@ java_import( java_binary( name = "PackageParser_bin", main_class = "com.google.idea.blaze.aspect.PackageParser", - runtime_deps = [":package_parser_lib"], visibility = ["//visibility:public"], + runtime_deps = [":package_parser_lib"], ) java_import( @@ -35,11 +38,10 @@ java_import( java_binary( name = "CreateAar_bin", main_class = "com.google.idea.blaze.aspect.CreateAar", - runtime_deps = [":jar_filter_lib"], visibility = ["//visibility:public"], + runtime_deps = [":jar_filter_lib"], ) - java_import( name = "create_aar_lib", jars = ["tools/CreateAar_deploy.jar"], diff --git a/aspect/build_dependencies.bzl b/aspect/build_dependencies.bzl new file mode 100644 index 00000000000..778d91c775f --- /dev/null +++ b/aspect/build_dependencies.bzl @@ -0,0 +1,810 @@ +"""Aspects to build and collect project dependencies.""" + +load( + "@bazel_tools//tools/build_defs/cc:action_names.bzl", + "CPP_COMPILE_ACTION_NAME", + "C_COMPILE_ACTION_NAME", +) +load("@rules_java//java:defs.bzl", "JavaInfo") + +ALWAYS_BUILD_RULES = "java_proto_library,java_lite_proto_library,java_mutable_proto_library,kt_proto_library_helper,_java_grpc_library,_java_lite_grpc_library,kt_grpc_library_helper,java_stubby_library,kt_stubby_library_helper,aar_import,java_import" + +PROTO_RULE_KINDS = [ + "java_proto_library", + "java_lite_proto_library", + "java_mutable_proto_library", + "kt_proto_library_helper", +] + +def _package_dependencies_impl(target, ctx): + java_info_file = _write_java_target_info(target, ctx) + cc_info_file = _write_cc_target_info(target, ctx) + + return [OutputGroupInfo( + qsync_jars = target[DependenciesInfo].compile_time_jars.to_list(), + artifact_info_file = java_info_file, + qsync_aars = target[DependenciesInfo].aars.to_list(), + qsync_gensrcs = target[DependenciesInfo].gensrcs.to_list(), + cc_headers = target[DependenciesInfo].cc_headers.to_list(), + cc_info_file = cc_info_file + [target[DependenciesInfo].cc_toolchain_info.file] if target[DependenciesInfo].cc_toolchain_info else [], + )] + +def _write_java_target_info(target, ctx): + if not target[DependenciesInfo].target_to_artifacts: + return [] + file_name = target.label.name + ".java-info.txt" + artifact_info_file = ctx.actions.declare_file(file_name) + ctx.actions.write( + artifact_info_file, + _encode_target_info_proto(target[DependenciesInfo].target_to_artifacts), + ) + return [artifact_info_file] + +def _write_cc_target_info(target, ctx): + if not target[DependenciesInfo].cc_info: + return [] + cc_info_file_name = target.label.name + ".cc-info.txt" + cc_info_file = ctx.actions.declare_file(cc_info_file_name) + ctx.actions.write( + cc_info_file, + _encode_cc_info_proto(target.label, target[DependenciesInfo].cc_info), + ) + return [cc_info_file] + +DependenciesInfo = provider( + "The out-of-project dependencies", + fields = { + "compile_time_jars": "a list of jars generated by targets", + "target_to_artifacts": "a map between a target and all its artifacts", + "aars": "a list of aars with resource files", + "gensrcs": "a list of sources generated by project targets", + "expand_sources": "boolean, true if the sources for this target should be expanded when it appears inside another rules srcs list", + "cc_info": "a structure containing info required to compile cc sources", + "cc_headers": "a depset of generated headers required to compile cc sources", + "cc_toolchain_info": "struct containing cc toolchain info, with keys file (the output file) and id (unique ID for the toolchain info, referred to from elsewhere)", + "test_mode_own_files": "a structure describing Java artifacts required when the target is requested within the project scope", + "test_mode_cc_src_deps": "a list of sources (e.g. headers) required to compile cc sources in integratrion tests", + }, +) + +def create_dependencies_info( + compile_time_jars = depset(), + target_to_artifacts = {}, + aars = depset(), + gensrcs = depset(), + expand_sources = False, + cc_info = None, + cc_headers = depset(), + cc_toolchain_info = None, + test_mode_own_files = None, + test_mode_cc_src_deps = depset()): + """A helper function to create a DependenciesInfo provider instance.""" + return DependenciesInfo( + compile_time_jars = compile_time_jars, + target_to_artifacts = target_to_artifacts, + aars = aars, + gensrcs = gensrcs, + expand_sources = expand_sources, + cc_info = cc_info, + cc_headers = cc_headers, + cc_toolchain_info = cc_toolchain_info, + test_mode_own_files = test_mode_own_files, + test_mode_cc_src_deps = test_mode_cc_src_deps, + ) + +def _encode_target_info_proto(target_to_artifacts): + contents = [] + for label, target_info in target_to_artifacts.items(): + contents.append( + struct( + target = label, + jars = target_info["jars"], + ide_aars = target_info["ide_aars"], + gen_srcs = target_info["gen_srcs"], + srcs = target_info["srcs"], + srcjars = target_info["srcjars"], + android_resources_package = target_info["android_resources_package"], + ), + ) + return proto.encode_text(struct(artifacts = contents)) + +def _encode_cc_info_proto(label, cc_info): + return proto.encode_text( + struct(targets = [ + struct( + label = str(label), + defines = cc_info.transitive_defines, + include_directories = cc_info.transitive_include_directory, + quote_include_directories = cc_info.transitive_quote_include_directory, + system_include_directories = cc_info.transitive_system_include_directory, + framework_include_directories = cc_info.framework_include_directory, + gen_hdrs = cc_info.gen_headers, + toolchain_id = cc_info.toolchain_id, + ), + ]), + ) + +package_dependencies = aspect( + implementation = _package_dependencies_impl, + required_aspect_providers = [[DependenciesInfo]], +) + +def declares_android_resources(target, ctx): + """ + Returns true if the target has resource files and an android provider. + + The IDE needs aars from targets that declare resources. AndroidIdeInfo + has a defined_android_resources flag, but this returns true for additional + cases (aidl files, etc), so we check if the target has resource files. + + Args: + target: the target. + ctx: the context. + Returns: + True if the target has resource files and an android provider. + """ + if _get_android_provider(target) == None: + return False + return hasattr(ctx.rule.attr, "resource_files") and len(ctx.rule.attr.resource_files) > 0 + +def _get_android_provider(target): + if hasattr(android_common, "AndroidIdeInfo"): + if android_common.AndroidIdeInfo in target: + return target[android_common.AndroidIdeInfo] + else: + return None + elif hasattr(target, "android"): + # Backwards compatibility: supports android struct provider + legacy_android = getattr(target, "android") + + # Transform into AndroidIdeInfo form + return struct( + aar = legacy_android.aar, + java_package = legacy_android.java_package, + manifest = legacy_android.manifest, + idl_source_jar = getattr(legacy_android.idl.output, "source_jar", None), + idl_class_jar = getattr(legacy_android.idl.output, "class_jar", None), + defines_android_resources = legacy_android.defines_resources, + idl_import_root = getattr(legacy_android.idl, "import_root", None), + idl_generated_java_files = getattr(legacy_android.idl, "generated_java_files", []), + resource_jar = legacy_android.resource_jar, + signed_apk = legacy_android.apk, + apks_under_test = legacy_android.apks_under_test, + ) + return None + +def declares_aar_import(ctx): + """ + Returns true if the target has aar and is aar_import rule. + + Args: + ctx: the context. + Returns: + True if the target has aar and is aar_import rule. + """ + return ctx.rule.kind == "aar_import" and hasattr(ctx.rule.attr, "aar") + +def _collect_dependencies_impl(target, ctx): + return _collect_dependencies_core_impl( + target, + ctx, + ctx.attr.include, + ctx.attr.exclude, + ctx.attr.always_build_rules, + ctx.attr.generate_aidl_classes, + ctx.attr.use_generated_srcjars, + test_mode = False, + ) + +def _collect_all_dependencies_for_tests_impl(target, ctx): + return _collect_dependencies_core_impl( + target, + ctx, + include = None, + exclude = None, + always_build_rules = ALWAYS_BUILD_RULES, + generate_aidl_classes = None, + use_generated_srcjars = False, + test_mode = True, + ) + +def _target_within_project_scope(label, include, exclude): + result = False + if include: + for inc in include.split(","): + if label.startswith(inc): + if label[len(inc)] in [":", "/"]: + result = True + break + if result and len(exclude) > 0: + for exc in exclude.split(","): + if label.startswith(exc): + if label[len(exc)] in [":", "/"]: + result = False + break + return result + +def _get_followed_kotlin_toolchain_dependency_info(rule): + return [] + +def _get_followed_java_dependency_infos(rule): + deps = [] + for (attr, kinds) in FOLLOW_JAVA_ATTRIBUTES_BY_RULE_KIND: + if hasattr(rule.attr, attr) and (not kinds or rule.kind in kinds): + to_add = getattr(rule.attr, attr) + if type(to_add) == "list": + deps += [t for t in to_add if type(t) == "Target"] + elif type(to_add) == "Target": + deps.append(to_add) + + deps.extend(_get_followed_kotlin_toolchain_dependency_info(rule)) + + return { + str(dep.label): dep[DependenciesInfo] + for dep in deps + if DependenciesInfo in dep and dep[DependenciesInfo].target_to_artifacts + } + +def _collect_own_java_artifacts( + target, + ctx, + dependency_infos, + always_build_rules, + generate_aidl_classes, + use_generated_srcjars, + target_is_within_project_scope): + rule = ctx.rule + + # Toolchains are collected for proto targets via aspect traversal, but jars + # produced for proto deps of the underlying proto_library are not + can_follow_dependencies = bool(dependency_infos) and not ctx.rule.kind in PROTO_RULE_KINDS + + must_build_main_artifacts = ( + not target_is_within_project_scope or rule.kind in always_build_rules.split(",") + ) + + own_jar_files = [] + own_jar_depsets = [] + own_ide_aar_files = [] + own_gensrc_files = [] + own_src_files = [] + own_srcjar_files = [] + resource_package = "" + + if must_build_main_artifacts: + # For rules that we do not follow dependencies of (either because they don't + # have further dependencies with JavaInfo or do so in attributes we don't care) + # we gather all their transitive dependencies. If they have dependencies, we + # only gather their own compile jars and continue down the tree. + # This is done primarily for rules like proto, whose toolchain classes + # are collected via attribute traversal, but still requires jars for any + # proto deps of the underlying proto_library. + if JavaInfo in target: + if can_follow_dependencies: + own_jar_depsets.append(target[JavaInfo].compile_jars) + else: + own_jar_depsets.append(target[JavaInfo].transitive_compile_time_jars) + + if declares_android_resources(target, ctx): + ide_aar = _get_ide_aar_file(target, ctx) + if ide_aar: + own_ide_aar_files.append(ide_aar) + elif declares_aar_import(ctx): + own_ide_aar_files.append(rule.attr.aar.files.to_list()[0]) + + else: + android = _get_android_provider(target) + if android != None: + resource_package = android.java_package + + if generate_aidl_classes: + add_base_idl_jar = False + idl_jar = android.idl_class_jar + if idl_jar != None: + own_jar_files.append(idl_jar) + add_base_idl_jar = True + + generated_java_files = android.idl_generated_java_files + if generated_java_files: + own_gensrc_files += generated_java_files + add_base_idl_jar = True + + # An AIDL base jar needed for resolving base classes for aidl generated stubs. + if add_base_idl_jar: + if hasattr(rule.attr, "_aidl_lib"): + own_jar_depsets.append(rule.attr._aidl_lib.files) + elif hasattr(rule.attr, "_android_sdk") and hasattr(android_common, "AndroidSdkInfo"): + android_sdk_info = getattr(rule.attr, "_android_sdk")[android_common.AndroidSdkInfo] + own_jar_depsets.append(android_sdk_info.aidl_lib.files) + + # Add generated java_outputs (e.g. from annotation processing) + generated_class_jars = [] + if JavaInfo in target: + for java_output in target[JavaInfo].java_outputs: + # Prefer source jars if they exist: + if use_generated_srcjars and java_output.generated_source_jar: + own_gensrc_files.append(java_output.generated_source_jar) + elif java_output.generated_class_jar: + generated_class_jars.append(java_output.generated_class_jar) + + if generated_class_jars: + own_jar_files += generated_class_jars + + # Add generated sources for included targets + if hasattr(rule.attr, "srcs"): + for src in rule.attr.srcs: + for file in src.files.to_list(): + if not file.is_source: + expand_sources = False + if str(file.owner) in dependency_infos: + src_depinfo = dependency_infos[str(file.owner)] + expand_sources = src_depinfo.expand_sources + + # If the target that generates this source specifies that + # the sources should be expanded, we ignore the generated + # sources - the IDE will substitute the target sources + # themselves instead. + if not expand_sources: + own_gensrc_files.append(file) + + if not target_is_within_project_scope: + if hasattr(rule.attr, "srcs"): + for src in rule.attr.srcs: + for file in src.files.to_list(): + if file.is_source: + own_src_files.append(file.path) + else: + own_gensrc_files.append(file) + if hasattr(rule.attr, "srcjar"): + if rule.attr.srcjar and type(rule.attr.srcjar) == "Target": + for file in rule.attr.srcjar.files.to_list(): + if file.is_source: + own_srcjar_files.append(file.path) + else: + own_gensrc_files.append(file) + + return struct( + jars = own_jar_files, + jar_depsets = own_jar_depsets, + ide_aars = own_ide_aar_files, + gensrcs = own_gensrc_files, + srcs = own_src_files, + srcjars = own_srcjar_files, + android_resources_package = resource_package, + ) + +def _collect_own_and_dependency_java_artifacts( + target, + ctx, + dependency_infos, + always_build_rules, + generate_aidl_classes, + use_generated_srcjars, + target_is_within_project_scope): + own_files = _collect_own_java_artifacts( + target, + ctx, + dependency_infos, + always_build_rules, + generate_aidl_classes, + use_generated_srcjars, + target_is_within_project_scope, + ) + + has_own_artifacts = ( + len(own_files.jars) + + len(own_files.jar_depsets) + + len(own_files.ide_aars) + + len(own_files.gensrcs) + + len(own_files.srcs) + + len(own_files.srcjars) + + (1 if own_files.android_resources_package else 0) + ) > 0 + + target_to_artifacts = {} + if has_own_artifacts: + jars = depset(own_files.jars, transitive = own_files.jar_depsets).to_list() + + # Pass the following lists through depset() too to remove any duplicates. + ide_aars = depset(own_files.ide_aars).to_list() + gen_srcs = depset(own_files.gensrcs).to_list() + target_to_artifacts[str(target.label)] = { + "jars": [_output_relative_path(file.path) for file in jars], + "ide_aars": [_output_relative_path(file.path) for file in ide_aars], + "gen_srcs": [_output_relative_path(file.path) for file in gen_srcs], + "srcs": own_files.srcs, + "srcjars": own_files.srcjars, + "android_resources_package": own_files.android_resources_package, + } + + own_and_transitive_jar_depsets = list(own_files.jar_depsets) # Copy to prevent changes to own_jar_depsets. + own_and_transitive_ide_aar_depsets = [] + own_and_transitive_gensrc_depsets = [] + + for info in dependency_infos.values(): + target_to_artifacts.update(info.target_to_artifacts) + own_and_transitive_jar_depsets.append(info.compile_time_jars) + own_and_transitive_ide_aar_depsets.append(info.aars) + own_and_transitive_gensrc_depsets.append(info.gensrcs) + + return ( + target_to_artifacts, + depset(own_files.jars, transitive = own_and_transitive_jar_depsets), + depset(own_files.ide_aars, transitive = own_and_transitive_ide_aar_depsets), + depset(own_files.gensrcs, transitive = own_and_transitive_gensrc_depsets), + ) + +def _get_followed_cc_toolchain_dependency_info(rule): + return None + +def _get_followed_cc_dependency_info(rule): + if hasattr(rule.attr, "_cc_toolchain"): + cc_toolchain_target = getattr(rule.attr, "_cc_toolchain") + if DependenciesInfo in cc_toolchain_target: + return cc_toolchain_target[DependenciesInfo] + return _get_followed_cc_toolchain_dependency_info(rule) + +def _collect_own_and_dependency_cc_info(target, dependency_info, test_mode): + compilation_context = target[CcInfo].compilation_context + cc_toolchain_info = None + test_mode_cc_src_deps = depset() + if dependency_info: + cc_toolchain_info = dependency_info.cc_toolchain_info + if test_mode: + test_mode_cc_src_deps = dependency_info.test_mode_cc_src_deps + + gen_headers = depset() + compilation_info = None + if compilation_context: + gen_headers = depset([f for f in compilation_context.headers.to_list() if not f.is_source]) + + if test_mode: + test_mode_cc_src_deps = depset( + [f for f in compilation_context.headers.to_list() if f.is_source], + transitive = [test_mode_cc_src_deps], + ) + + compilation_info = struct( + transitive_defines = compilation_context.defines.to_list(), + transitive_include_directory = compilation_context.includes.to_list(), + transitive_quote_include_directory = compilation_context.quote_includes.to_list(), + transitive_system_include_directory = compilation_context.system_includes.to_list() + compilation_context.external_includes.to_list(), + framework_include_directory = compilation_context.framework_includes.to_list(), + gen_headers = [f.path for f in gen_headers.to_list()], + toolchain_id = cc_toolchain_info.id if cc_toolchain_info else None, + ) + return struct( + compilation_info = compilation_info, + gen_headers = gen_headers, + test_mode_cc_src_deps = test_mode_cc_src_deps, + cc_toolchain_info = cc_toolchain_info, + ) + +def _collect_dependencies_core_impl( + target, + ctx, + include, + exclude, + always_build_rules, + generate_aidl_classes, + use_generated_srcjars, + test_mode): + dep_infos = _collect_java_dependencies_core_impl( + target, + ctx, + include, + exclude, + always_build_rules, + generate_aidl_classes, + use_generated_srcjars, + test_mode, + ) + if CcInfo in target: + dep_infos.append(_collect_cc_dependencies_core_impl(target, ctx, test_mode)) + if cc_common.CcToolchainInfo in target: + dep_infos.append(_collect_cc_toolchain_info(target, ctx)) + return dep_infos + +def _collect_java_dependencies_core_impl( + target, + ctx, + include, + exclude, + always_build_rules, + generate_aidl_classes, + use_generated_srcjars, + test_mode): + target_is_within_project_scope = _target_within_project_scope(str(target.label), include, exclude) and not test_mode + dependency_infos = _get_followed_java_dependency_infos(ctx.rule) + + target_to_artifacts, compile_jars, aars, gensrcs = _collect_own_and_dependency_java_artifacts( + target, + ctx, + dependency_infos, + always_build_rules, + generate_aidl_classes, + use_generated_srcjars, + target_is_within_project_scope, + ) + + test_mode_own_files = None + if test_mode: + within_scope_own_files = _collect_own_java_artifacts( + target, + ctx, + dependency_infos, + always_build_rules, + generate_aidl_classes, + use_generated_srcjars, + target_is_within_project_scope = True, + ) + test_mode_own_files = struct( + test_mode_within_scope_own_jar_files = depset(within_scope_own_files.jars, transitive = within_scope_own_files.jar_depsets).to_list(), + test_mode_within_scope_own_ide_aar_files = within_scope_own_files.ide_aars, + test_mode_within_scope_own_gensrc_files = within_scope_own_files.gensrcs, + ) + + expand_sources = False + if hasattr(ctx.rule.attr, "tags"): + if "ij-ignore-source-transform" in ctx.rule.attr.tags: + expand_sources = True + + return [ + create_dependencies_info( + target_to_artifacts = target_to_artifacts, + compile_time_jars = compile_jars, + aars = aars, + gensrcs = gensrcs, + expand_sources = expand_sources, + test_mode_own_files = test_mode_own_files, + ), + ] + +def _collect_cc_dependencies_core_impl(target, ctx, test_mode): + dependency_info = _get_followed_cc_dependency_info(ctx.rule) + + cc_info = _collect_own_and_dependency_cc_info(target, dependency_info, test_mode) + + return create_dependencies_info( + cc_info = cc_info.compilation_info, + cc_headers = cc_info.gen_headers, + cc_toolchain_info = cc_info.cc_toolchain_info, + test_mode_cc_src_deps = cc_info.test_mode_cc_src_deps, + ) + +def _collect_cc_toolchain_info(target, ctx): + toolchain_info = target[cc_common.CcToolchainInfo] + + cpp_fragment = ctx.fragments.cpp + + # TODO(b/301235884): This logic is not quite right. `ctx` here is the context for the + # cc_toolchain target itself, so the `features` and `disabled_features` were using here are + # for the cc_toolchain, not the individual targets that this information will ultimately be + # used for. Instead, we should attach `toolchain_info` itself to the `DependenciesInfo` + # provider, and execute this logic once per top level cc target that we're building, to ensure + # that the right features are used. + feature_config = cc_common.configure_features( + ctx = ctx, + cc_toolchain = toolchain_info, + requested_features = ctx.features, + unsupported_features = ctx.disabled_features + [ + # Note: module_maps appears to be necessary here to ensure the API works + # in all cases, and to avoid the error: + # Invalid toolchain configuration: Cannot find variable named 'module_name' + # yaqs/3227912151964319744 + "module_maps", + ], + ) + c_variables = cc_common.create_compile_variables( + feature_configuration = feature_config, + cc_toolchain = toolchain_info, + user_compile_flags = cpp_fragment.copts + cpp_fragment.conlyopts, + ) + cpp_variables = cc_common.create_compile_variables( + feature_configuration = feature_config, + cc_toolchain = toolchain_info, + user_compile_flags = cpp_fragment.copts + cpp_fragment.cxxopts, + ) + c_options = cc_common.get_memory_inefficient_command_line( + feature_configuration = feature_config, + action_name = C_COMPILE_ACTION_NAME, + variables = c_variables, + ) + cpp_options = cc_common.get_memory_inefficient_command_line( + feature_configuration = feature_config, + action_name = CPP_COMPILE_ACTION_NAME, + variables = cpp_variables, + ) + toolchain_id = str(target.label) + "%" + toolchain_info.target_gnu_system_name + + cc_toolchain_info = struct( + id = toolchain_id, + compiler_executable = toolchain_info.compiler_executable, + cpu = toolchain_info.cpu, + compiler = toolchain_info.compiler, + target_name = toolchain_info.target_gnu_system_name, + built_in_include_directories = toolchain_info.built_in_include_directories, + c_options = c_options, + cpp_options = cpp_options, + ) + + cc_toolchain_file_name = target.label.name + "." + cc_toolchain_info.target_name + ".txt" + cc_toolchain_file = ctx.actions.declare_file(cc_toolchain_file_name) + ctx.actions.write( + cc_toolchain_file, + proto.encode_text( + struct(toolchains = cc_toolchain_info), + ), + ) + + return create_dependencies_info( + cc_toolchain_info = struct(file = cc_toolchain_file, id = toolchain_id), + test_mode_cc_src_deps = depset([f for f in toolchain_info.all_files.to_list() if f.is_source]), + ) + +def _get_ide_aar_file(target, ctx): + """ + Builds a resource only .aar file for the ide. + + The IDE requires just resource files and the manifest from the IDE. + Moreover, there are cases when the existing rules fail to build a full .aar + file from a library, on which other targets can still depend. + + The function builds a minimalistic .aar file that contains resources and the + manifest only. + """ + android = _get_android_provider(target) + full_aar = android.aar + if full_aar: + resource_files = _collect_resource_files(ctx) + resource_map = _build_ide_aar_file_map(android.manifest, resource_files) + aar = ctx.actions.declare_file(full_aar.short_path.removesuffix(".aar") + "_ide/" + full_aar.basename) + _package_ide_aar(ctx, aar, resource_map) + return aar + else: + return None + +def _collect_resource_files(ctx): + """ + Collects the list of resource files from the target rule attributes. + """ + + # Unfortunately, there are no suitable bazel providers that describe + # resource files used a target. + # However, AndroidIdeInfo returns a reference to a so-called resource APK + # file, which contains everything the IDE needs to load resources from a + # given library. However, this format is currently supported by Android + # Studio in the namespaced resource mode. We should consider conditionally + # enabling support in Android Studio and use them in ASwB, instead of + # building special .aar files for the IDE. + resource_files = [] + for t in ctx.rule.attr.resource_files: + for f in t.files.to_list(): + resource_files.append(f) + return resource_files + +def _build_ide_aar_file_map(manifest_file, resource_files): + """ + Build the list of files and their paths as they have to appear in .aar. + """ + file_map = {} + file_map["AndroidManifest.xml"] = manifest_file + for f in resource_files: + res_dir_path = f.short_path \ + .removeprefix(android_common.resource_source_directory(f)) \ + .removeprefix("/") + if res_dir_path: + res_dir_path = "res/" + res_dir_path + file_map[res_dir_path] = f + return file_map + +def _package_ide_aar(ctx, aar, file_map): + """ + Declares a file and defines actions to build .aar according to file_map. + """ + files_map_args = [] + files = [] + for aar_dir_path, f in file_map.items(): + files.append(f) + files_map_args.append("%s=%s" % (aar_dir_path, f.path)) + + ctx.actions.run( + mnemonic = "GenerateIdeAar", + executable = ctx.executable._build_zip, + inputs = files, + outputs = [aar], + arguments = ["c", aar.path] + files_map_args, + ) + +def _output_relative_path(path): + """Get file path relative to the output path. + + Args: + path: path of artifact path = (../repo_name)? + (root_fragment)? + relative_path + + Returns: + path relative to the output path + """ + if (path.startswith("blaze-out/")) or (path.startswith("bazel-out/")): + # len("blaze-out/") or len("bazel-out/") + path = path[10:] + return path + +# List of tuples containing: +# 1. An attribute for the aspect to traverse +# 2. A list of rule kinds to specify which rules for which the attribute labels +# need to be added as dependencies. If empty, the attribute is followed for +# all rules. +FOLLOW_JAVA_ATTRIBUTES_BY_RULE_KIND = [ + ("deps", []), + ("exports", []), + ("srcs", []), + ("_junit", []), + ("_aspect_proto_toolchain_for_javalite", []), + ("_aspect_java_proto_toolchain", []), + ("runtime", ["proto_lang_toolchain", "java_rpc_toolchain"]), + ("_toolchain", ["_java_grpc_library", "_java_lite_grpc_library", "kt_jvm_library_helper", "android_library", "kt_android_library"]), + ("kotlin_libs", ["kt_jvm_toolchain"]), +] + +FOLLOW_CC_ATTRIBUTES = ["_cc_toolchain"] + +FOLLOW_ATTRIBUTES = [attr for (attr, _) in FOLLOW_JAVA_ATTRIBUTES_BY_RULE_KIND] + FOLLOW_CC_ATTRIBUTES + +collect_dependencies = aspect( + implementation = _collect_dependencies_impl, + provides = [DependenciesInfo], + attr_aspects = FOLLOW_ATTRIBUTES, + attrs = { + "include": attr.string( + doc = "Comma separated list of workspace paths included in the project as source. Any targets inside here will not be built.", + mandatory = True, + ), + "exclude": attr.string( + doc = "Comma separated list of exclusions to 'include'.", + default = "", + ), + "always_build_rules": attr.string( + doc = "Comma separated list of rules. Any targets belonging to these rules will be built, regardless of location", + default = "", + ), + "generate_aidl_classes": attr.bool( + doc = "If True, generates classes for aidl files included as source for the project targets", + default = False, + ), + "use_generated_srcjars": attr.bool( + doc = "If True, collects generated source jars for a target instead of compiled jar", + default = False, + ), + "_build_zip": attr.label( + allow_files = True, + cfg = "exec", + executable = True, + default = "@@bazel_tools//tools/zip:zipper", + ), + }, + fragments = ["cpp"], +) + +collect_all_dependencies_for_tests = aspect( + doc = """ + A variant of collect_dependencies aspect used by query sync integration + tests. + + The difference is that collect_all_dependencies does not apply + include/exclude directory filtering, which is applied in the test framework + instead. See: test_project.bzl for more details. + """, + implementation = _collect_all_dependencies_for_tests_impl, + provides = [DependenciesInfo], + attr_aspects = FOLLOW_ATTRIBUTES, + attrs = { + "_build_zip": attr.label( + allow_files = True, + cfg = "exec", + executable = True, + default = "@@bazel_tools//tools/zip:zipper", + ), + }, + fragments = ["cpp"], +) diff --git a/aspect/fast_build_info.bzl b/aspect/fast_build_info.bzl index 054954b8c65..c764d67e5e1 100644 --- a/aspect/fast_build_info.bzl +++ b/aspect/fast_build_info.bzl @@ -1,14 +1,26 @@ """An aspect to gather info needed by the FastBuildService.""" +load("@rules_java//java:defs.bzl", "JavaInfo", "java_common") load( ":artifacts.bzl", "artifact_location", "sources_from_target", "struct_omit_none", ) +load( + ":intellij_info_impl.bzl", + "stringify_label", +) _DEP_ATTRS = ["deps", "exports", "runtime_deps", "_java_toolchain"] +def _get_android_ide_info(target): + if hasattr(android_common, "AndroidIdeInfo") and android_common.AndroidIdeInfo in target: + return target[android_common.AndroidIdeInfo] + if hasattr(target, "android"): + return target.android + return None + def _fast_build_info_impl(target, ctx): dep_targets = _get_all_dep_targets(target, ctx) dep_outputs = _get_all_dep_outputs(dep_targets) @@ -17,8 +29,8 @@ def _fast_build_info_impl(target, ctx): info = { "workspace_name": ctx.workspace_name, - "label": str(target.label), - "dependencies": [str(t.label) for t in dep_targets], + "label": stringify_label(target.label), + "dependencies": [stringify_label(t.label) for t in dep_targets], } write_output = False @@ -28,7 +40,7 @@ def _fast_build_info_impl(target, ctx): write_output = True info["data"] = [ struct( - label = str(datadep.label), + label = stringify_label(datadep.label), artifacts = [artifact_location(file) for file in datadep.files.to_list()], ) for datadep in ctx.rule.attr.data @@ -60,10 +72,13 @@ def _fast_build_info_impl(target, ctx): launcher = None if hasattr(ctx.rule.attr, "use_launcher") and not ctx.rule.attr.use_launcher: launcher = None + elif hasattr(ctx.rule.attr, "launcher") and ctx.rule.attr.launcher: + launcher = stringify_label(ctx.rule.attr.launcher.label) elif hasattr(ctx.rule.attr, "_java_launcher") and ctx.rule.attr._java_launcher: - launcher = str(ctx.rule.attr._java_launcher.label) + # TODO: b/295221112 - remove _java_launcher when it's removed from Java rules + launcher = stringify_label(ctx.rule.attr._java_launcher.label) elif hasattr(ctx.rule.attr, "_javabase") and ctx.rule.attr._javabase: - launcher = str(ctx.rule.attr._javabase.label) + launcher = stringify_label(ctx.rule.attr._javabase.label) java_info = { "sources": sources_from_target(ctx), "test_class": getattr(ctx.rule.attr, "test_class", None), @@ -80,17 +95,22 @@ def _fast_build_info_impl(target, ctx): for t in annotation_processing.processor_classpath.to_list() ] info["java_info"] = struct_omit_none(**java_info) - if hasattr(target, "android"): + + android_ide_info = _get_android_ide_info(target) + if android_ide_info: write_output = True android_info = struct_omit_none( - aar = artifact_location(target.android.aar), - merged_manifest = artifact_location(target.android.merged_manifest), + aar = artifact_location(android_ide_info.aar), + merged_manifest = artifact_location( + getattr(android_ide_info, "generated_manifest", None) or + getattr(android_ide_info, "merged_manifest", None), + ), ) info["android_info"] = android_info if write_output: output_file = ctx.actions.declare_file(target.label.name + ".ide-fast-build-info.txt") - ctx.actions.write(output_file, struct_omit_none(**info).to_proto()) + ctx.actions.write(output_file, proto.encode_text(struct_omit_none(**info))) output_files += [output_file] output_groups = depset(output_files, transitive = dep_outputs) diff --git a/aspect/intellij_info.bzl b/aspect/intellij_info.bzl index f0852547713..652b83c95a4 100644 --- a/aspect/intellij_info.bzl +++ b/aspect/intellij_info.bzl @@ -8,6 +8,8 @@ load( EXTRA_DEPS = [ "embed", # From go rules (bazel only) + "_cc_toolchain", # From rules_cc (bazel only) + "_kt_toolchain", # From rules_kotlin (bazel only) ] def tool_label(tool_name): @@ -31,6 +33,13 @@ def get_go_import_path(ctx): import_path += "/" + ctx.label.name return import_path +def is_go_proto_library(target, _ctx): + return hasattr(target[OutputGroupInfo], "go_generated_srcs") + +def get_go_proto_library_generated_srcs(target): + files = target[OutputGroupInfo].go_generated_srcs.to_list() + return [f for f in files if f.basename.endswith(".go")] + def get_py_launcher(target, ctx): """Returns the python launcher for a given rule.""" @@ -44,8 +53,11 @@ def get_py_launcher(target, ctx): semantics = struct( tool_label = tool_label, extra_deps = EXTRA_DEPS, + extra_required_aspect_providers = [], go = struct( get_import_path = get_go_import_path, + is_proto_library = is_go_proto_library, + get_proto_library_generated_srcs = get_go_proto_library_generated_srcs, ), py = struct( get_launcher = get_py_launcher, diff --git a/aspect/intellij_info_impl.bzl b/aspect/intellij_info_impl.bzl index 2192d7fd4e2..536bc00bc90 100644 --- a/aspect/intellij_info_impl.bzl +++ b/aspect/intellij_info_impl.bzl @@ -1,5 +1,6 @@ """Implementation of IntelliJ-specific information collecting aspect.""" +load("@rules_java//java:defs.bzl", "JavaInfo", "java_common") load( ":artifacts.bzl", "artifact_location", @@ -14,6 +15,16 @@ load( "expand_make_variables", ) +IntelliJInfo = provider( + doc = "Collected information about the targets visited by the aspect.", + fields = [ + "export_deps", + "kind", + "output_groups", + "target_key", + ], +) + # Defensive list of features that can appear in the C++ toolchain, but which we # definitely don't want to enable (when enabled, they'd contribute command line # flags that don't make sense in the context of intellij info). @@ -27,7 +38,6 @@ UNSUPPORTED_FEATURES = [ # 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 @@ -36,7 +46,7 @@ DEPS = [ "exports", "java_lib", # From old proto_library rules "_android_sdk", # from android rules - "aidl_lib", # from android_sdk + "_aidl_lib", # from android_library "_scala_toolchain", # From scala rules "test_app", # android_instrumentation_test "instruments", # android_instrumentation_test @@ -136,15 +146,21 @@ def get_res_artifacts(resources): 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, + ctx.label.package + "/BUILD", + ctx.label.package + "/BUILD", True, is_external_artifact(ctx.label), ) +# https://github.com/bazelbuild/bazel/issues/18966 +def _list_or_depset_to_list(list_or_depset): + if hasattr(list_or_depset, "to_list"): + return list_or_depset.to_list() + return list_or_depset + def get_source_jars(output): if hasattr(output, "source_jars"): - return output.source_jars + return _list_or_depset_to_list(output.source_jars) if hasattr(output, "source_jar"): return [output.source_jar] return [] @@ -208,20 +224,18 @@ def list_omit_none(value): def is_valid_aspect_target(target): """Returns whether the target has had the aspect run on it.""" - return hasattr(target, "intellij_info") + return IntelliJInfo in target -def get_aspect_ids(ctx, target): +def get_aspect_ids(ctx): """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 _is_language_specific_proto_library(ctx, target): +def _is_language_specific_proto_library(ctx, target, semantics): """Returns True if the target is a proto library with attached language-specific aspect.""" if ctx.rule.kind != "proto_library": return False @@ -229,22 +243,30 @@ def _is_language_specific_proto_library(ctx, target): return True if CcInfo in target: return True - if hasattr(target, "aspect_proto_go_api_info"): + if semantics.go.is_proto_library(target, ctx): return True return False +def stringify_label(label): + """Stringifies a label, making sure any leading '@'s are stripped from main repo labels.""" + s = str(label) + + # If the label is in the main repo, make sure any leading '@'s are stripped so that tests are + # okay with the fixture setups. + return s.lstrip("@") if s.startswith("@@//") or s.startswith("@//") else s + def make_target_key(label, aspect_ids): """Returns a TargetKey proto struct from a target.""" return struct_omit_none( aspect_ids = tuple(aspect_ids) if aspect_ids else None, - label = str(label), + label = stringify_label(label), ) def make_dep(dep, dependency_type): """Returns a Dependency proto struct.""" return struct( dependency_type = dependency_type, - target = dep.intellij_info.target_key, + target = dep[IntelliJInfo].target_key, ) def make_deps(deps, dependency_type): @@ -255,7 +277,7 @@ def make_dep_from_label(label, dependency_type): """Returns a Dependency proto struct from a label.""" return struct( dependency_type = dependency_type, - target = struct(label = str(label)), + target = struct(label = stringify_label(label)), ) def update_sync_output_groups(groups_dict, key, new_set): @@ -279,7 +301,7 @@ def update_set_in_dict(input_dict, key, other_set): def _get_output_mnemonic(ctx): """Gives the output directory mnemonic for some target context.""" - return ctx.configuration.bin_dir.path.split("/")[1] + return ctx.bin_dir.path.split("/")[1] def _get_python_version(ctx): if ctx.attr._flag_hack[FlagHackInfo].incompatible_py2_outputs_are_suffixed: @@ -316,7 +338,7 @@ def _do_starlark_string_expansion(ctx, name, strings, extra_targets = []): def collect_py_info(target, ctx, semantics, ide_info, ide_info_file, output_groups): """Updates Python-specific output groups, returns false if not a Python target.""" - if not PyInfo in target or _is_language_specific_proto_library(ctx, target): + if not PyInfo in target or _is_language_specific_proto_library(ctx, target, semantics): return False py_semantics = getattr(semantics, "py", None) @@ -326,6 +348,7 @@ def collect_py_info(target, ctx, semantics, ide_info, ide_info_file, output_grou py_launcher = None sources = sources_from_target(ctx) + to_build = target[PyInfo].transitive_sources args = getattr(ctx.rule.attr, "args", []) data_deps = getattr(ctx.rule.attr, "data", []) @@ -344,15 +367,10 @@ def collect_py_info(target, ctx, semantics, ide_info, ide_info_file, output_grou update_sync_output_groups(output_groups, "intellij-resolve-py", to_build) return True -def _collect_generated_go_sources(target): +def _collect_generated_go_sources(target, ctx, semantics): """Returns a depset of go source files generated by this target.""" - if hasattr(target, "aspect_proto_go_api_info"): - go_proto_info = target.aspect_proto_go_api_info - files = getattr(go_proto_info, "files_to_build", depset()).to_list() - return [f for f in files if f.basename.endswith(".pb.go")] - elif hasattr(target[OutputGroupInfo], "go_generated_srcs"): - files = target[OutputGroupInfo].go_generated_srcs.to_list() - return [f for f in files if f.basename.endswith(".go")] + if semantics.go.is_proto_library(target, ctx): + return semantics.go.get_proto_library_generated_srcs(target) else: return None @@ -382,7 +400,7 @@ def collect_go_info(target, ctx, semantics, ide_info, ide_info_file, output_grou # if the .go file isn't in 'files', build the .a and .x files instead generated = genfiles else: - generated_sources = _collect_generated_go_sources(target) + generated_sources = _collect_generated_go_sources(target, ctx, semantics) if not generated_sources: return False sources = generated_sources @@ -508,7 +526,6 @@ def collect_c_toolchain_info(target, ctx, semantics, ide_info, ide_info_file, ou cpp_variables = cc_common.create_compile_variables( feature_configuration = feature_configuration, cc_toolchain = cpp_toolchain, - add_legacy_cxx_options = True, user_compile_flags = copts + cxxopts, ) c_options = cc_common.get_memory_inefficient_command_line( @@ -543,11 +560,9 @@ def collect_c_toolchain_info(target, ctx, semantics, ide_info, ide_info_file, ou def get_java_provider(target): """Find a provider exposing java compilation/outputs data.""" - # Check for scala and kt providers before JavaInfo. e.g. scala targets have - # JavaInfo, but their data lives in the "scala" provider and not JavaInfo. + # Check for kt providers before JavaInfo. e.g. kt targets have + # JavaInfo, but their data lives in the "kt" provider and not JavaInfo. # See https://github.com/bazelbuild/intellij/pull/1202 - if hasattr(target, "scala"): - return target.scala if hasattr(target, "kt") and hasattr(target.kt, "outputs"): return target.kt if JavaInfo in target: @@ -629,7 +644,7 @@ def collect_java_info(target, ctx, semantics, ide_info, ide_info_file, output_gr package_manifest = None if java_sources: package_manifest = build_java_package_manifest(ctx, target, java_sources, ".java-manifest") - ide_info_files += [package_manifest] + ide_info_files.append(package_manifest) filtered_gen_jar = None if java_sources and (gen_java_sources or srcjars): @@ -645,7 +660,11 @@ def collect_java_info(target, ctx, semantics, ide_info, ide_info_file, output_gr # Custom lint checks are incorporated as java plugins. We collect them here and register them with the IDE so that the IDE can also run the same checks. plugin_processor_jar_files = [] if hasattr(ctx.rule.attr, "_android_lint_plugins"): - plugin_processor_jar_files += [jar for p in getattr(ctx.rule.attr, "_android_lint_plugins", []) for jar in p[JavaInfo].transitive_runtime_jars.to_list()] + plugin_processor_jar_files += [ + jar + for p in getattr(ctx.rule.attr, "_android_lint_plugins", []) + for jar in _android_lint_plugin_jars(p) + ] if hasattr(java, "annotation_processing") and java.annotation_processing and hasattr(java.annotation_processing, "processor_classpath"): plugin_processor_jar_files += java.annotation_processing.processor_classpath.to_list() @@ -665,7 +684,7 @@ def collect_java_info(target, ctx, semantics, ide_info, ide_info_file, output_gr ) ide_info["java_ide_info"] = java_info - ide_info_files += [ide_info_file] + ide_info_files.append(ide_info_file) update_sync_output_groups(output_groups, "intellij-info-java", depset(ide_info_files)) update_sync_output_groups(output_groups, "intellij-compile-java", depset(compile_files)) update_sync_output_groups(output_groups, "intellij-resolve-java", depset(resolve_files)) @@ -676,6 +695,12 @@ def collect_java_info(target, ctx, semantics, ide_info, ide_info_file, output_gr update_set_in_dict(output_groups, "intellij-resolve-java-direct-deps", java.transitive_source_jars) return True +def _android_lint_plugin_jars(target): + if JavaInfo in target: + return target[JavaInfo].transitive_runtime_jars.to_list() + else: + return [] + def _package_manifest_file_argument(f): artifact = artifact_location(f) is_external = "1" if is_external_artifact(f.owner) else "0" @@ -717,7 +742,7 @@ def _build_filtered_gen_jar(ctx, target, java_outputs, gen_java_sources, srcjars elif jar.class_jar: jar_artifacts.append(jar.class_jar) if hasattr(jar, "source_jars") and jar.source_jars: - source_jar_artifacts.extend(jar.source_jars) + source_jar_artifacts.extend(_list_or_depset_to_list(jar.source_jars)) elif hasattr(jar, "source_jar") and jar.source_jar: source_jar_artifacts.append(jar.source_jar) @@ -785,18 +810,53 @@ def collect_android_info(target, ctx, semantics, ide_info, ide_info_file, output update_sync_output_groups(output_groups, "intellij-info-android", depset([ide_info_file])) return handled +def _get_android_ide_info(target): + """Returns the AndroidIdeInfo provider for the given target.""" + + if hasattr(android_common, "AndroidIdeInfo"): + return target[android_common.AndroidIdeInfo] + + # Backwards compatibility: supports android struct provider + legacy_android = getattr(target, "android") + + # Transform into AndroidIdeInfo form + return struct( + java_package = legacy_android.java_package, + manifest = legacy_android.manifest, + idl_source_jar = getattr(legacy_android.idl.output, "source_jar", None), + idl_class_jar = getattr(legacy_android.idl.output, "class_jar", None), + defines_android_resources = legacy_android.defines_resources, + idl_import_root = getattr(legacy_android.idl, "import_root", None), + resource_jar = legacy_android.resource_jar, + signed_apk = legacy_android.apk, + apks_under_test = legacy_android.apks_under_test, + ) + def _collect_android_ide_info(target, ctx, semantics, ide_info, ide_info_file, output_groups): - """Updates ide_info proto with android_ide_info, and intellij_resolve_android with android resolve files. Returns false if target doesn't contain android attribute.""" - if not hasattr(target, "android"): + """Populates ide_info proto and intellij_resolve_android output group + + Updates ide_info proto with android_ide_info, and intellij_resolve_android with android + resolve files. It returns false on android_library and android_binary targets, as this preserves + consistent functionality with the previous condition of the presence of the .android legacy + provider. + """ + if ctx.rule.kind not in ["android_library", "android_binary", "kt_android_library"]: 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 = _get_android_ide_info(target) + + output_jar = struct( + class_jar = android.idl_class_jar, + ijar = None, + source_jar = android.idl_source_jar, + ) if android.idl_class_jar else None + resources = [] res_folders = [] - resolve_files = jars_from_output(android.idl.output) + resolve_files = jars_from_output(output_jar) if hasattr(ctx.rule.attr, "resource_files"): for artifact_path_fragments, res_files in get_res_artifacts(ctx.rule.attr.resource_files).items(): # Generate unique ArtifactLocation for resource directories. @@ -835,7 +895,7 @@ def _collect_android_ide_info(target, ctx, semantics, ide_info, ide_info_file, o instruments = None if hasattr(ctx.rule.attr, "instruments") and ctx.rule.attr.instruments: - instruments = str(ctx.rule.attr.instruments.label) + instruments = stringify_label(ctx.rule.attr.instruments.label) render_resolve_jar = None if android_semantics and hasattr(android_semantics, "build_render_resolve_jar"): @@ -846,14 +906,14 @@ def _collect_android_ide_info(target, ctx, semantics, ide_info, ide_info_file, o 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, + idl_import_root = getattr(android, "idl_import_root", None), manifest = artifact_location(android.manifest), manifest_values = [struct_omit_none(key = key, value = value) for key, value in ctx.rule.attr.manifest_values.items()] if hasattr(ctx.rule.attr, "manifest_values") else None, - apk = artifact_location(android.apk), + apk = artifact_location(android.signed_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, + has_idl_sources = android.idl_class_jar != None, + idl_jar = library_artifact(output_jar), + generate_resource_class = android.defines_android_resources, resources = resources, res_folders = res_folders, resource_jar = library_artifact(android.resource_jar), @@ -863,7 +923,7 @@ def _collect_android_ide_info(target, ctx, semantics, ide_info, ide_info_file, o ) if android.manifest and not android.manifest.is_source: - resolve_files += [android.manifest] + resolve_files.append(android.manifest) # b/176209293: expose resource jar to make sure empty library # knows they are remote output artifact @@ -880,7 +940,7 @@ def _collect_android_instrumentation_info(target, ctx, semantics, ide_info, ide_ return False android_instrumentation_info = struct_omit_none( - test_app = str(ctx.rule.attr.test_app.label), + test_app = stringify_label(ctx.rule.attr.test_app.label), target_device = str(ctx.rule.attr.target_device.label), ) ide_info["android_instrumentation_info"] = android_instrumentation_info @@ -949,13 +1009,15 @@ def collect_java_toolchain_info(target, ide_info, ide_info_file, output_groups): def artifact_to_path(artifact): return artifact.root_execution_path_fragment + "/" + artifact.relative_path -def collect_kotlin_toolchain_info(target, ide_info, ide_info_file, output_groups): +def collect_kotlin_toolchain_info(target, ctx, ide_info, ide_info_file, output_groups): """Updates kotlin_toolchain-relevant output groups, returns false if not a kotlin_toolchain target.""" - if not hasattr(target, "kt"): - return False - kt = target.kt - if not hasattr(kt, "language_version"): + if ctx.rule.kind == "_kt_toolchain" and platform_common.ToolchainInfo in target: + kt = target[platform_common.ToolchainInfo] + elif hasattr(target, "kt") and hasattr(target.kt, "language_version"): + kt = target.kt # Legacy struct provider mechanism + else: return False + ide_info["kt_toolchain_ide_info"] = struct( language_version = kt.language_version, ) @@ -969,7 +1031,7 @@ def _is_proto_library_wrapper(target, ctx): # treat any *proto_library rule with a single proto_library dep as a shim deps = collect_targets_from_attrs(ctx.rule.attr, ["deps"]) - return len(deps) == 1 and deps[0].intellij_info and deps[0].intellij_info.kind == "proto_library" + return len(deps) == 1 and IntelliJInfo in deps[0] and deps[0][IntelliJInfo].kind == "proto_library" def _get_forwarded_deps(target, ctx): """Returns the list of deps of this target to forward. @@ -1008,12 +1070,22 @@ def intellij_info_aspect_impl(target, ctx, semantics): rule_attrs, semantics_extra_deps(DEPS, semantics, "extra_deps"), ) + + # Collect direct toolchain type-based dependencies + if hasattr(semantics, "toolchains_propagation"): + direct_dep_targets.extend( + semantics.toolchains_propagation.collect_toolchain_deps( + ctx, + semantics.toolchains_propagation.toolchain_types, + ), + ) + 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 + exported_deps_from_deps = exported_deps_from_deps + dep[IntelliJInfo].export_deps # Combine into all compile time deps compiletime_deps = direct_deps + exported_deps_from_deps @@ -1027,9 +1099,9 @@ def intellij_info_aspect_impl(target, ctx, semantics): # Collect transitive exports for export in direct_exports: - export_deps.extend(export.intellij_info.export_deps) + export_deps.extend(export[IntelliJInfo].export_deps) - if ctx.rule.kind == "android_library": + if ctx.rule.kind == "android_library" or ctx.rule.kind == "kt_android_library": # Empty android libraries export all their dependencies. if not hasattr(rule_attrs, "srcs") or not ctx.rule.attr.srcs: export_deps.extend(compiletime_deps) @@ -1057,7 +1129,7 @@ def intellij_info_aspect_impl(target, ctx, semantics): prerequisites = direct_dep_targets + runtime_dep_targets + extra_prerequisite_targets + direct_exports output_groups = dict() for dep in prerequisites: - for k, v in dep.intellij_info.output_groups.items(): + for k, v in dep[IntelliJInfo].output_groups.items(): if dep in forwarded_deps: # unconditionally roll up deps for these targets output_groups[k] = output_groups[k] + [v] if k in output_groups else [v] @@ -1087,7 +1159,7 @@ def intellij_info_aspect_impl(target, ctx, semantics): # bazel allows target names differing only by case, so append a hash to support # case-insensitive file systems file_name = file_name + "-" + str(hash(file_name)) - aspect_ids = get_aspect_ids(ctx, target) + aspect_ids = get_aspect_ids(ctx) if aspect_ids: aspect_hash = hash(".".join(aspect_ids)) file_name = file_name + "-" + str(aspect_hash) @@ -1115,7 +1187,7 @@ def intellij_info_aspect_impl(target, ctx, semantics): 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_kotlin_toolchain_info(target, ide_info, ide_info_file, output_groups) or handled + handled = collect_kotlin_toolchain_info(target, ctx, ide_info, ide_info_file, output_groups) or handled # Any extra ide info if hasattr(semantics, "extra_ide_info"): @@ -1127,18 +1199,18 @@ def intellij_info_aspect_impl(target, ctx, semantics): # Output the ide information file. info = struct_omit_none(**ide_info) - ctx.actions.write(ide_info_file, info.to_proto()) + ctx.actions.write(ide_info_file, proto.encode_text(info)) # Return providers. - return struct_omit_none( - intellij_info = struct( + return [ + IntelliJInfo( export_deps = export_deps, kind = ctx.rule.kind, output_groups = output_groups, target_key = target_key, ), - output_groups = output_groups, - ) + OutputGroupInfo(**output_groups), + ] def semantics_extra_deps(base, semantics, name): if not hasattr(semantics, name): @@ -1146,7 +1218,7 @@ def semantics_extra_deps(base, semantics, name): extra_deps = getattr(semantics, name) return base + extra_deps -def make_intellij_info_aspect(aspect_impl, semantics): +def make_intellij_info_aspect(aspect_impl, semantics, **kwargs): """Creates the aspect given the semantics.""" tool_label = semantics.tool_label flag_hack_label = semantics.flag_hack_label @@ -1159,13 +1231,13 @@ def make_intellij_info_aspect(aspect_impl, semantics): attrs = { "_package_parser": attr.label( default = tool_label("PackageParser"), - cfg = "host", + cfg = "exec", executable = True, allow_files = True, ), "_jar_filter": attr.label( default = tool_label("JarFilter"), - cfg = "host", + cfg = "exec", executable = True, allow_files = True, ), @@ -1174,7 +1246,7 @@ def make_intellij_info_aspect(aspect_impl, semantics): ), "_create_aar": attr.label( default = tool_label("CreateAar"), - cfg = "host", + cfg = "exec", executable = True, allow_files = True, ), @@ -1188,6 +1260,7 @@ def make_intellij_info_aspect(aspect_impl, semantics): attr_aspects = attr_aspects, attrs = attrs, fragments = ["cpp"], - required_aspect_providers = [[JavaInfo], [CcInfo], ["dart"], ["aspect_proto_go_api_info"]], + required_aspect_providers = [[JavaInfo], [CcInfo]] + semantics.extra_required_aspect_providers, implementation = aspect_impl, + **kwargs ) diff --git a/aspect/java_classpath.bzl b/aspect/java_classpath.bzl index be3dfb648c6..1a47bd9448a 100644 --- a/aspect/java_classpath.bzl +++ b/aspect/java_classpath.bzl @@ -1,5 +1,7 @@ """An aspect which extracts the runtime classpath from a java target.""" +load("@rules_java//java:defs.bzl", "JavaInfo") + def _runtime_classpath_impl(target, ctx): """The top level aspect implementation function. diff --git a/aspect/testing/BUILD b/aspect/testing/BUILD index 7225e2391cb..b4831afd6e1 100644 --- a/aspect/testing/BUILD +++ b/aspect/testing/BUILD @@ -1,6 +1,8 @@ # Integration tests for the plugin's Skylark aspect -licenses(["notice"]) # Apache 2.0 +load("@rules_java//java:defs.bzl", "java_library") + +licenses(["notice"]) # To prevent versioning conflicts when developing internally, we always use the same # guava version bundled with the IntelliJ plugin API. diff --git a/aspect/testing/rules/BUILD b/aspect/testing/rules/BUILD index 34df9444371..40666505324 100644 --- a/aspect/testing/rules/BUILD +++ b/aspect/testing/rules/BUILD @@ -1,12 +1,15 @@ # Testing infrastructure for the plugin's Skylark aspect +load("@com_google_protobuf//bazel:java_proto_library.bzl", "java_proto_library") +load("@com_google_protobuf//bazel:proto_library.bzl", "proto_library") +load("@rules_java//java:defs.bzl", "java_binary", "java_library") load( "//:build-visibility.bzl", "ASPECT_TEST_RULES_VISIBILITY_TO_ALL", "ASPECT_TEST_RULES_VISIBILITY_TO_TESTS", ) -licenses(["notice"]) # Apache 2.0 +licenses(["notice"]) proto_library( name = "intellij_aspect_test_fixture_proto", diff --git a/aspect/testing/rules/fast_build_aspect_test_fixture.bzl b/aspect/testing/rules/fast_build_aspect_test_fixture.bzl index 9768ff687a7..6db5767419f 100644 --- a/aspect/testing/rules/fast_build_aspect_test_fixture.bzl +++ b/aspect/testing/rules/fast_build_aspect_test_fixture.bzl @@ -29,7 +29,7 @@ def _impl(ctx): mnemonic = "FastBuildAspectTestFixtureBuilder", progress_message = "Building Fast Build Aspect Test Fixture", ) - return struct( + return DefaultInfo( files = depset([output]), runfiles = ctx.runfiles( files = [output], @@ -43,7 +43,7 @@ _fast_build_aspect_test_fixture = rule( "output": attr.string(mandatory = True), "_fast_build_aspect_test_fixture_builder": attr.label( default = Label("//aspect/testing/rules:FastBuildAspectTestFixtureBuilder"), - cfg = "host", + cfg = "exec", executable = True, allow_files = True, ), diff --git a/aspect/testing/rules/intellij_aspect_test_fixture.bzl b/aspect/testing/rules/intellij_aspect_test_fixture.bzl index 091e2c94fe7..cb0bccebb7f 100644 --- a/aspect/testing/rules/intellij_aspect_test_fixture.bzl +++ b/aspect/testing/rules/intellij_aspect_test_fixture.bzl @@ -6,6 +6,7 @@ load( ) load( "//aspect:intellij_info_impl.bzl", + "IntelliJInfo", "update_set_in_dict", ) @@ -13,9 +14,9 @@ def _impl(ctx): """Implementation method for _intellij_aspect_test_fixture.""" output_groups = dict() inputs = depset() - deps = [dep for dep in ctx.attr.deps if hasattr(dep, "intellij_info")] + deps = [dep for dep in ctx.attr.deps if IntelliJInfo in dep] for dep in deps: - for k, v in dep.intellij_info.output_groups.items(): + for k, v in dep[IntelliJInfo].output_groups.items(): update_set_in_dict(output_groups, k, v) inputs = depset( [f for f in v.to_list() if f.short_path.endswith(".intellij-info.txt")], @@ -43,7 +44,7 @@ def _impl(ctx): mnemonic = "IntellijAspectTestFixtureBuilder", progress_message = "Building Intellij Aspect Test Fixture", ) - return struct( + return DefaultInfo( files = depset([output]), runfiles = ctx.runfiles( files = [output], @@ -57,19 +58,20 @@ _intellij_aspect_test_fixture = rule( "output": attr.string(mandatory = True), "_intellij_aspect_test_fixture_builder": attr.label( default = Label("//aspect/testing/rules:IntellijAspectTestFixtureBuilder"), - cfg = "host", + cfg = "exec", executable = True, allow_files = True, ), }, ) -def intellij_aspect_test_fixture(name, deps): +def intellij_aspect_test_fixture(name, deps, transitive_configs = []): _intellij_aspect_test_fixture( name = name, output = name + ".intellij-aspect-test-fixture", deps = deps, testonly = 1, + transitive_configs = transitive_configs, ) def test_sources(outs): diff --git a/aspect/testing/tests/src/com/google/idea/blaze/aspect/cpp/ccbinary/BUILD b/aspect/testing/tests/src/com/google/idea/blaze/aspect/cpp/ccbinary/BUILD index f88ab3dd232..cea04ab9275 100644 --- a/aspect/testing/tests/src/com/google/idea/blaze/aspect/cpp/ccbinary/BUILD +++ b/aspect/testing/tests/src/com/google/idea/blaze/aspect/cpp/ccbinary/BUILD @@ -1,10 +1,11 @@ -licenses(["notice"]) # Apache 2.0 - +load("@rules_java//java:defs.bzl", "java_test") load( "//aspect/testing/rules:intellij_aspect_test_fixture.bzl", "intellij_aspect_test_fixture", ) +licenses(["notice"]) + cc_binary( name = "simple", srcs = ["simple/simple.cc"], diff --git a/aspect/testing/tests/src/com/google/idea/blaze/aspect/cpp/cclibrary/BUILD b/aspect/testing/tests/src/com/google/idea/blaze/aspect/cpp/cclibrary/BUILD index 8e63a2da997..960e4caf30e 100644 --- a/aspect/testing/tests/src/com/google/idea/blaze/aspect/cpp/cclibrary/BUILD +++ b/aspect/testing/tests/src/com/google/idea/blaze/aspect/cpp/cclibrary/BUILD @@ -1,10 +1,11 @@ -licenses(["notice"]) # Apache 2.0 - +load("@rules_java//java:defs.bzl", "java_test") load( "//aspect/testing/rules:intellij_aspect_test_fixture.bzl", "intellij_aspect_test_fixture", ) +licenses(["notice"]) + cc_library( name = "simple", srcs = ["simple/simple.cc"], diff --git a/aspect/testing/tests/src/com/google/idea/blaze/aspect/cpp/cctest/BUILD b/aspect/testing/tests/src/com/google/idea/blaze/aspect/cpp/cctest/BUILD index 69e39c9dae5..45f5f4bff95 100644 --- a/aspect/testing/tests/src/com/google/idea/blaze/aspect/cpp/cctest/BUILD +++ b/aspect/testing/tests/src/com/google/idea/blaze/aspect/cpp/cctest/BUILD @@ -1,10 +1,11 @@ -licenses(["notice"]) # Apache 2.0 - +load("@rules_java//java:defs.bzl", "java_test") load( "//aspect/testing/rules:intellij_aspect_test_fixture.bzl", "intellij_aspect_test_fixture", ) +licenses(["notice"]) + cc_test( name = "simple", srcs = ["simple/simple.cc"], @@ -12,6 +13,7 @@ cc_test( intellij_aspect_test_fixture( name = "simple_fixture", + transitive_configs = ["//command_line_option/fragment:test"], deps = [":simple"], ) diff --git a/aspect/testing/tests/src/com/google/idea/blaze/aspect/cpp/cctoolchain/BUILD b/aspect/testing/tests/src/com/google/idea/blaze/aspect/cpp/cctoolchain/BUILD index b12f695aef5..4f902b89dc1 100644 --- a/aspect/testing/tests/src/com/google/idea/blaze/aspect/cpp/cctoolchain/BUILD +++ b/aspect/testing/tests/src/com/google/idea/blaze/aspect/cpp/cctoolchain/BUILD @@ -1,10 +1,11 @@ -licenses(["notice"]) # Apache 2.0 - +load("@rules_java//java:defs.bzl", "java_test") load( "//aspect/testing/rules:intellij_aspect_test_fixture.bzl", "intellij_aspect_test_fixture", ) +licenses(["notice"]) + cc_library( name = "simple", srcs = ["simple.cc"], diff --git a/aspect/testing/tests/src/com/google/idea/blaze/aspect/cpp/cctoolchain/CcToolchainTest.java b/aspect/testing/tests/src/com/google/idea/blaze/aspect/cpp/cctoolchain/CcToolchainTest.java index 208d6ff9783..2f4e19efe22 100644 --- a/aspect/testing/tests/src/com/google/idea/blaze/aspect/cpp/cctoolchain/CcToolchainTest.java +++ b/aspect/testing/tests/src/com/google/idea/blaze/aspect/cpp/cctoolchain/CcToolchainTest.java @@ -78,10 +78,6 @@ public void testCcToolchain() throws Exception { && !dir.contains("clang/") && dir.endsWith("include"))) .isTrue(); - assertThat( - toolchainInfo.getBuiltInIncludeDirectoryList().stream() - .anyMatch(dir -> dir.contains("c++"))) - .isTrue(); } } diff --git a/aspect/testing/tests/src/com/google/idea/blaze/aspect/cpp/coptsmakevars/BUILD b/aspect/testing/tests/src/com/google/idea/blaze/aspect/cpp/coptsmakevars/BUILD index aa109c070c4..ec62fa8dd2b 100644 --- a/aspect/testing/tests/src/com/google/idea/blaze/aspect/cpp/coptsmakevars/BUILD +++ b/aspect/testing/tests/src/com/google/idea/blaze/aspect/cpp/coptsmakevars/BUILD @@ -1,10 +1,11 @@ -licenses(["notice"]) # Apache 2.0 - +load("@rules_java//java:defs.bzl", "java_test") load( "//aspect/testing/rules:intellij_aspect_test_fixture.bzl", "intellij_aspect_test_fixture", ) +licenses(["notice"]) + cc_binary( name = "simple_prefined", srcs = ["simple/simple.cc"], diff --git a/aspect/testing/tests/src/com/google/idea/blaze/aspect/general/alias/BUILD b/aspect/testing/tests/src/com/google/idea/blaze/aspect/general/alias/BUILD index c2261c39ff3..f53e2da963b 100644 --- a/aspect/testing/tests/src/com/google/idea/blaze/aspect/general/alias/BUILD +++ b/aspect/testing/tests/src/com/google/idea/blaze/aspect/general/alias/BUILD @@ -1,10 +1,11 @@ -licenses(["notice"]) # Apache 2.0 - +load("@rules_java//java:defs.bzl", "java_library", "java_test") load( "//aspect/testing/rules:intellij_aspect_test_fixture.bzl", "intellij_aspect_test_fixture", ) +licenses(["notice"]) + java_library( name = "test", srcs = ["Foo.java"], diff --git a/aspect/testing/tests/src/com/google/idea/blaze/aspect/general/analysistest/BUILD b/aspect/testing/tests/src/com/google/idea/blaze/aspect/general/analysistest/BUILD index f10ee99e9ca..dc23f814058 100644 --- a/aspect/testing/tests/src/com/google/idea/blaze/aspect/general/analysistest/BUILD +++ b/aspect/testing/tests/src/com/google/idea/blaze/aspect/general/analysistest/BUILD @@ -1,3 +1,4 @@ +load("@rules_java//java:defs.bzl", "java_library", "java_test") load( "//aspect/testing/rules:intellij_aspect_test_fixture.bzl", "intellij_aspect_test_fixture", diff --git a/aspect/testing/tests/src/com/google/idea/blaze/aspect/general/artifacts/BUILD b/aspect/testing/tests/src/com/google/idea/blaze/aspect/general/artifacts/BUILD index 5be27ce8fc5..8d9de8be671 100644 --- a/aspect/testing/tests/src/com/google/idea/blaze/aspect/general/artifacts/BUILD +++ b/aspect/testing/tests/src/com/google/idea/blaze/aspect/general/artifacts/BUILD @@ -1,9 +1,10 @@ +load("@rules_java//java:defs.bzl", "java_library", "java_test") load( "//aspect/testing/rules:intellij_aspect_test_fixture.bzl", "intellij_aspect_test_fixture", ) -licenses(["notice"]) # Apache 2.0 +licenses(["notice"]) genrule( name = "gen_source", diff --git a/aspect/testing/tests/src/com/google/idea/blaze/aspect/general/build/BUILD b/aspect/testing/tests/src/com/google/idea/blaze/aspect/general/build/BUILD index 5d164c1fb7c..d2c38a9a14d 100644 --- a/aspect/testing/tests/src/com/google/idea/blaze/aspect/general/build/BUILD +++ b/aspect/testing/tests/src/com/google/idea/blaze/aspect/general/build/BUILD @@ -1,10 +1,11 @@ -licenses(["notice"]) # Apache 2.0 - +load("@rules_java//java:defs.bzl", "java_library", "java_test") load( "//aspect/testing/rules:intellij_aspect_test_fixture.bzl", "intellij_aspect_test_fixture", ) +licenses(["notice"]) + java_library( name = "simple", srcs = ["Foo.java"], diff --git a/aspect/testing/tests/src/com/google/idea/blaze/aspect/general/noide/BUILD b/aspect/testing/tests/src/com/google/idea/blaze/aspect/general/noide/BUILD index 860743ba1d1..7ac0636cea0 100644 --- a/aspect/testing/tests/src/com/google/idea/blaze/aspect/general/noide/BUILD +++ b/aspect/testing/tests/src/com/google/idea/blaze/aspect/general/noide/BUILD @@ -1,10 +1,11 @@ -licenses(["notice"]) # Apache 2.0 - +load("@rules_java//java:defs.bzl", "java_library", "java_test") load( "//aspect/testing/rules:intellij_aspect_test_fixture.bzl", "intellij_aspect_test_fixture", ) +licenses(["notice"]) + java_library( name = "foo", srcs = ["Foo.java"], diff --git a/aspect/testing/tests/src/com/google/idea/blaze/aspect/general/tags/BUILD b/aspect/testing/tests/src/com/google/idea/blaze/aspect/general/tags/BUILD index 7158a5ddd78..5125523cc07 100644 --- a/aspect/testing/tests/src/com/google/idea/blaze/aspect/general/tags/BUILD +++ b/aspect/testing/tests/src/com/google/idea/blaze/aspect/general/tags/BUILD @@ -1,10 +1,11 @@ -licenses(["notice"]) # Apache 2.0 - +load("@rules_java//java:defs.bzl", "java_library", "java_test") load( "//aspect/testing/rules:intellij_aspect_test_fixture.bzl", "intellij_aspect_test_fixture", ) +licenses(["notice"]) + java_library( name = "foo", srcs = ["Foo.java"], diff --git a/aspect/testing/tests/src/com/google/idea/blaze/aspect/integration/BUILD b/aspect/testing/tests/src/com/google/idea/blaze/aspect/integration/BUILD index 88b6136bb7a..8504bc8cd1d 100644 --- a/aspect/testing/tests/src/com/google/idea/blaze/aspect/integration/BUILD +++ b/aspect/testing/tests/src/com/google/idea/blaze/aspect/integration/BUILD @@ -1,24 +1,30 @@ -load("@build_bazel_integration_testing//tools:bazel_java_integration_test.bzl", "bazel_java_integration_test") +load( + "@contrib_rules_bazel_integration_test//bazel_integration_test:defs.bzl", + "bazel_integration_tests", +) -bazel_java_integration_test( - name = "BazelInvokingIntegrationTest", - srcs = [ - "BazelInvokingIntegrationTest.java", - ], +java_binary( + name = "BazelInvokingIntegrationTestRunner", + testonly = True, + srcs = ["BazelInvokingIntegrationTestRunner.java"], data = [ "//aspect:aspect_files", ], - external_deps = [ - "@integration_test_deps", - ], - tags = ["block-network"], - versions = [ - "0.28.1", - "0.27.2", - ], + main_class = "com.google.idea.blaze.aspect.integration.BazelInvokingIntegrationTestRunner", deps = [ "//aspect/testing:guava", "//base", - "//base:integration_test_utils", ], ) + +bazel_integration_tests( + name = "bazel_invocation_integration_tests", + bazel_versions = [ + "6.0.0", + ], + # set tags = [] because otherwise bazel_integration_tests sets + # tags = ["manual"] and the target is not be detected via test //pkg/... + tags = [], + test_runner = ":BazelInvokingIntegrationTestRunner", + workspace_path = "testdata", +) diff --git a/aspect/testing/tests/src/com/google/idea/blaze/aspect/integration/BazelInvokingIntegrationTest.java b/aspect/testing/tests/src/com/google/idea/blaze/aspect/integration/BazelInvokingIntegrationTest.java deleted file mode 100644 index 70c26a23fed..00000000000 --- a/aspect/testing/tests/src/com/google/idea/blaze/aspect/integration/BazelInvokingIntegrationTest.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright 2019 The Bazel Authors. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.integration; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import build.bazel.tests.integration.BazelCommand; -import build.bazel.tests.integration.WorkspaceDriver; -import com.google.common.collect.ImmutableList; -import com.google.idea.blaze.base.model.primitives.LanguageClass; -import com.google.idea.blaze.base.sync.aspects.strategy.AspectStrategy; -import com.google.idea.blaze.base.sync.aspects.strategy.AspectStrategy.OutputGroup; -import com.google.idea.blaze.base.sync.aspects.strategy.AspectStrategyBazel; -import java.io.IOException; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; -import java.util.stream.Collectors; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; - -/** - * A Bazel-invoking integration test for the bundled IntelliJ aspect. - * - *

These tests assert the end-to-end behavior of the plugin's aspect during a sync, and ensure - * that it generates the correct IDE info files. - */ -public class BazelInvokingIntegrationTest { - - // Flags for wiring up the plugin aspect from the external @intellij_aspect repository. - public static final ImmutableList ASPECT_FLAGS = - ImmutableList.of( - AspectStrategyBazel.ASPECT_FLAG, - String.format( - "%s=%s/%s/aspect", - AspectStrategyBazel.OVERRIDE_REPOSITORY_FLAG, - System.getenv("TEST_SRCDIR"), - System.getenv("TEST_WORKSPACE"))); - private WorkspaceDriver driver = new WorkspaceDriver(); - - @BeforeClass - public static void setUpClass() throws IOException { - WorkspaceDriver.setUpClass(); - } - - @Before - public void setUp() throws Exception { - driver.setUp(); - } - - @Test - public void aspect_genericOutputGroup_generatesInfoTxt() throws Exception { - driver.scratchFile("foo/BUILD", "sh_test(name = \"bar\",\n" + "srcs = [\"bar.sh\"])"); - driver.scratchExecutableFile("foo/bar.sh", "echo \"bar\"", "exit 0"); - - ImmutableList.Builder args = ImmutableList.builder(); - args.add("build"); - args.add("//foo:bar"); - args.add("--define=ij_product=intellij-latest"); - args.addAll(ASPECT_FLAGS); - args.add( - getOutputGroupsFlag( - ImmutableList.of(OutputGroup.INFO), ImmutableList.of(LanguageClass.GENERIC))); - - BazelCommand cmd = driver.bazel(args.build()).runVerbose(); - - assertEquals("return code is 0", 0, cmd.exitCode()); - - // Bazel's output goes into stderr by default, even on success. - assertTrue( - "stderr contains up-to-date message", - cmd.errorLines().stream().anyMatch(line -> line.endsWith("//foo:bar up-to-date:"))); - assertTrue( - "stderr contains intellij-info.txt filename", - cmd.errorLines().stream() - .anyMatch(line -> line.endsWith(getIntelliJInfoTxtFilename("bar")))); - } - - private String getIntelliJInfoTxtFilename(String targetName) { - // e.g. bar-97299.intellij-info.txt - // The hashCode() call here is the implementation function underlying the Starlark hash() - // function in used in intellij-info-impl.bzl. - return String.format("%s-%s.intellij-info.txt", targetName, targetName.hashCode()); - } - - private String getOutputGroupsFlag( - Collection outputGroups, Collection languageClassList) { - // e.g. --output_groups=intellij-info-generic,intellij-resolve-java,intellij-compile-java - Set languageClasses = new HashSet<>(languageClassList); - AspectStrategy strategy = new AspectStrategyBazel(); - String outputGroupNames = - outputGroups.stream() - .flatMap( - g -> - strategy - .getBaseOutputGroups(g, languageClasses, /* directDepsOnly= */ false) - .stream()) - .collect(Collectors.joining(",")); - return "--output_groups=" + outputGroupNames; - } -} diff --git a/aspect/testing/tests/src/com/google/idea/blaze/aspect/integration/BazelInvokingIntegrationTestRunner.java b/aspect/testing/tests/src/com/google/idea/blaze/aspect/integration/BazelInvokingIntegrationTestRunner.java new file mode 100644 index 00000000000..8cce1825556 --- /dev/null +++ b/aspect/testing/tests/src/com/google/idea/blaze/aspect/integration/BazelInvokingIntegrationTestRunner.java @@ -0,0 +1,152 @@ +/* + * Copyright 2019 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.integration; + +import com.google.common.collect.ImmutableList; +import com.google.idea.blaze.base.bazel.BazelVersion; +import com.google.idea.blaze.base.model.BlazeVersionData; +import com.google.idea.blaze.base.model.primitives.LanguageClass; +import com.google.idea.blaze.base.sync.aspects.strategy.AspectStrategy; +import com.google.idea.blaze.base.sync.aspects.strategy.AspectStrategy.OutputGroup; +import com.google.idea.blaze.base.sync.aspects.strategy.AspectStrategyBazel; +import java.io.File; +import java.nio.file.Paths; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * A Bazel-invoking integration test for the bundled IntelliJ aspect. + * + *

These tests assert the end-to-end behavior of the plugin's aspect during a sync, and ensure + * that it generates the correct IDE info files. + */ +public class BazelInvokingIntegrationTestRunner { + + public static void main(String[] a) throws Exception { + BazelVersion bazelVersion = getBazelVersion(); + if (bazelVersion == null) { + exitWithError( + String.format( + "Failed to get Bazel version from Bazel path (%s)", + System.getenv("BIT_BAZEL_BINARY"))); + } + AspectStrategyBazel aspectStrategyBazel = + new AspectStrategyBazel(BlazeVersionData.builder().setBazelVersion(bazelVersion).build()); + + // Flags for wiring up the plugin aspect from the external @intellij_aspect repository. + ImmutableList aspectFlags = + ImmutableList.of( + aspectStrategyBazel.getAspectFlag(), + String.format( + "%s=%s/%s/aspect", + AspectStrategyBazel.OVERRIDE_REPOSITORY_FLAG, + System.getenv("TEST_SRCDIR"), + System.getenv("TEST_WORKSPACE"))); + + if (bazelVersion.isAtLeast(6, 0, 0) + && !aspectFlags.contains( + "--aspects=@@intellij_aspect//:intellij_info_bundled.bzl%intellij_info_aspect")) { + exitWithError( + String.format("Incorrect/Missing aspects flag in command args (%s)", aspectFlags)); + } + + ImmutableList.Builder args = ImmutableList.builder(); + args.add(System.getenv("BIT_BAZEL_BINARY")); + args.add("build"); + args.add("//:foo"); + args.add("--define=ij_product=intellij-latest"); + args.addAll(aspectFlags); + args.add( + getOutputGroupsFlag( + ImmutableList.of(OutputGroup.INFO), + ImmutableList.of(LanguageClass.GENERIC), + aspectStrategyBazel)); + + ProcessBuilder processBuilder = + new ProcessBuilder() + .command(args.build()) + .directory(Paths.get(System.getenv("BIT_WORKSPACE_DIR")).toFile()); + Process bazelInvocation = processBuilder.start(); + int exitCode = bazelInvocation.waitFor(); + String invocationOutput = new String(bazelInvocation.getErrorStream().readAllBytes()); + + if (exitCode != 0) { + exitWithError( + String.format( + "Bazel invocation failed: exit code (%d), invocation result (%s).", + exitCode, invocationOutput)); + } + + // Bazel's output goes into stderr by default, even on success. + if (!invocationOutput.contains("//:foo up-to-date:") + || !invocationOutput.contains(getIntelliJInfoTxtFilename("foo"))) { + exitWithError(String.format("Missing output in invocation result (%s)", invocationOutput)); + } + } + + private static void exitWithError(String message) { + System.err.println(message); + System.exit(1); + } + + private static String getIntelliJInfoTxtFilename(String targetName) { + // e.g. foo-97299.intellij-info.txt + // The hashCode() call here is the implementation function underlying the Starlark hash() + // function in used in intellij-info-impl.bzl. + return String.format("%s-%s.intellij-info.txt", targetName, targetName.hashCode()); + } + + private static String getOutputGroupsFlag( + Collection outputGroups, + Collection languageClassList, + AspectStrategy strategy) { + // e.g. --output_groups=intellij-info-generic,intellij-resolve-java,intellij-compile-java + Set languageClasses = new HashSet<>(languageClassList); + String outputGroupNames = + outputGroups.stream() + .flatMap( + g -> + strategy + .getBaseOutputGroups(g, languageClasses, /* directDepsOnly= */ false) + .stream()) + .collect(Collectors.joining(",")); + return "--output_groups=" + outputGroupNames; + } + + private static BazelVersion getBazelVersion() { + // The name of the directory containing the bazel binary is formatted as + // build_bazel_bazel_{major}_{minor}_{bugfix}[-pre] for a bazel binary of version + // {major}.{minor}.{bugfix} + String bazelBinaryPath = System.getenv("BIT_BAZEL_BINARY"); + if (bazelBinaryPath == null) { + return null; + } + String bazelDir = new File(bazelBinaryPath).getParentFile().getName(); + String[] parts = bazelDir.split("_|-"); + if (parts.length < 6) { + return null; + } + try { + return new BazelVersion( + Integer.parseInt(parts[3]), Integer.parseInt(parts[4]), Integer.parseInt(parts[5])); + } catch (NumberFormatException e) { + // invalid version + return null; + } + } +} diff --git a/aspect/testing/tests/src/com/google/idea/blaze/aspect/integration/testdata/BUILD b/aspect/testing/tests/src/com/google/idea/blaze/aspect/integration/testdata/BUILD new file mode 100644 index 00000000000..b1403111b10 --- /dev/null +++ b/aspect/testing/tests/src/com/google/idea/blaze/aspect/integration/testdata/BUILD @@ -0,0 +1,4 @@ +sh_binary( + name = "foo", + srcs = ["foo.sh"], +) diff --git a/aspect/testing/tests/src/com/google/idea/blaze/aspect/integration/testdata/WORKSPACE b/aspect/testing/tests/src/com/google/idea/blaze/aspect/integration/testdata/WORKSPACE new file mode 100644 index 00000000000..eae2adb1da5 --- /dev/null +++ b/aspect/testing/tests/src/com/google/idea/blaze/aspect/integration/testdata/WORKSPACE @@ -0,0 +1,8 @@ +workspace(name = "testdata") + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +http_archive( + name = "rules_java", + url = "https://github.com/bazelbuild/rules_java/releases/download/5.3.5/rules_java-5.3.5.tar.gz", + sha256 = "c73336802d0b4882e40770666ad055212df4ea62cfa6edf9cb0f9d29828a0934", +) diff --git a/aspect/testing/tests/src/com/google/idea/blaze/aspect/integration/testdata/foo.sh b/aspect/testing/tests/src/com/google/idea/blaze/aspect/integration/testdata/foo.sh new file mode 100644 index 00000000000..ff4c040a250 --- /dev/null +++ b/aspect/testing/tests/src/com/google/idea/blaze/aspect/integration/testdata/foo.sh @@ -0,0 +1,2 @@ +echo "foo" +exit 0 \ No newline at end of file diff --git a/aspect/testing/tests/src/com/google/idea/blaze/aspect/java/dependencies/BUILD b/aspect/testing/tests/src/com/google/idea/blaze/aspect/java/dependencies/BUILD index 2774be8df6b..05e81e9cd5b 100644 --- a/aspect/testing/tests/src/com/google/idea/blaze/aspect/java/dependencies/BUILD +++ b/aspect/testing/tests/src/com/google/idea/blaze/aspect/java/dependencies/BUILD @@ -1,10 +1,11 @@ -licenses(["notice"]) # Apache 2.0 - +load("@rules_java//java:defs.bzl", "java_library", "java_test") load( "//aspect/testing/rules:intellij_aspect_test_fixture.bzl", "intellij_aspect_test_fixture", ) +licenses(["notice"]) + java_library( name = "foo", srcs = ["testsrc/Foo.java"], diff --git a/aspect/testing/tests/src/com/google/idea/blaze/aspect/java/filteredgenjar/BUILD b/aspect/testing/tests/src/com/google/idea/blaze/aspect/java/filteredgenjar/BUILD index cc0584b40ae..4a2b4ccbcb1 100644 --- a/aspect/testing/tests/src/com/google/idea/blaze/aspect/java/filteredgenjar/BUILD +++ b/aspect/testing/tests/src/com/google/idea/blaze/aspect/java/filteredgenjar/BUILD @@ -1,9 +1,10 @@ +load("@rules_java//java:defs.bzl", "java_library", "java_test") load( "//aspect/testing/rules:intellij_aspect_test_fixture.bzl", "intellij_aspect_test_fixture", ) -licenses(["notice"]) # Apache 2.0 +licenses(["notice"]) java_library( name = "source_only", diff --git a/aspect/testing/tests/src/com/google/idea/blaze/aspect/java/genjars/BUILD b/aspect/testing/tests/src/com/google/idea/blaze/aspect/java/genjars/BUILD index 393ab0a00e5..1dbfe3092b1 100644 --- a/aspect/testing/tests/src/com/google/idea/blaze/aspect/java/genjars/BUILD +++ b/aspect/testing/tests/src/com/google/idea/blaze/aspect/java/genjars/BUILD @@ -1,10 +1,11 @@ -licenses(["notice"]) # Apache 2.0 - +load("@rules_java//java:defs.bzl", "java_library", "java_test") load( "//aspect/testing/rules:intellij_aspect_test_fixture.bzl", "intellij_aspect_test_fixture", ) +licenses(["notice"]) + java_library( name = "no_plugin", srcs = ["Foo.java"], diff --git a/aspect/testing/tests/src/com/google/idea/blaze/aspect/java/javabinary/BUILD b/aspect/testing/tests/src/com/google/idea/blaze/aspect/java/javabinary/BUILD index b98d1e97f2f..32b42bc3753 100644 --- a/aspect/testing/tests/src/com/google/idea/blaze/aspect/java/javabinary/BUILD +++ b/aspect/testing/tests/src/com/google/idea/blaze/aspect/java/javabinary/BUILD @@ -1,10 +1,11 @@ -licenses(["notice"]) # Apache 2.0 - +load("@rules_java//java:defs.bzl", "java_binary", "java_library", "java_test") load( "//aspect/testing/rules:intellij_aspect_test_fixture.bzl", "intellij_aspect_test_fixture", ) +licenses(["notice"]) + java_library( name = "foolib", srcs = ["Foo.java"], diff --git a/aspect/testing/tests/src/com/google/idea/blaze/aspect/java/javalibrary/BUILD b/aspect/testing/tests/src/com/google/idea/blaze/aspect/java/javalibrary/BUILD index 4499022271a..42b17066639 100644 --- a/aspect/testing/tests/src/com/google/idea/blaze/aspect/java/javalibrary/BUILD +++ b/aspect/testing/tests/src/com/google/idea/blaze/aspect/java/javalibrary/BUILD @@ -1,9 +1,10 @@ +load("@rules_java//java:defs.bzl", "java_library", "java_test") load( "//aspect/testing/rules:intellij_aspect_test_fixture.bzl", "intellij_aspect_test_fixture", ) -licenses(["notice"]) # Apache 2.0 +licenses(["notice"]) java_library( name = "foo", diff --git a/aspect/testing/tests/src/com/google/idea/blaze/aspect/java/javaplugin/BUILD b/aspect/testing/tests/src/com/google/idea/blaze/aspect/java/javaplugin/BUILD index a3e3fe1eae2..314545e433c 100644 --- a/aspect/testing/tests/src/com/google/idea/blaze/aspect/java/javaplugin/BUILD +++ b/aspect/testing/tests/src/com/google/idea/blaze/aspect/java/javaplugin/BUILD @@ -1,10 +1,11 @@ -licenses(["notice"]) # Apache 2.0 - +load("@rules_java//java:defs.bzl", "java_plugin", "java_test") load( "//aspect/testing/rules:intellij_aspect_test_fixture.bzl", "intellij_aspect_test_fixture", ) +licenses(["notice"]) + java_plugin( name = "plugin", srcs = ["Plugin.java"], diff --git a/aspect/testing/tests/src/com/google/idea/blaze/aspect/java/javatest/BUILD b/aspect/testing/tests/src/com/google/idea/blaze/aspect/java/javatest/BUILD index ee376821396..9cb128c2d31 100644 --- a/aspect/testing/tests/src/com/google/idea/blaze/aspect/java/javatest/BUILD +++ b/aspect/testing/tests/src/com/google/idea/blaze/aspect/java/javatest/BUILD @@ -1,13 +1,14 @@ -licenses(["notice"]) # Apache 2.0 - -load( - "//aspect/testing/rules:intellij_aspect_test_fixture.bzl", - "intellij_aspect_test_fixture", -) +load("@rules_java//java:defs.bzl", "java_test") load( "//aspect/testing/rules:fast_build_aspect_test_fixture.bzl", "fast_build_aspect_test_fixture", ) +load( + "//aspect/testing/rules:intellij_aspect_test_fixture.bzl", + "intellij_aspect_test_fixture", +) + +licenses(["notice"]) java_test( name = "FooTest", diff --git a/aspect/testing/tests/src/com/google/idea/blaze/aspect/java/javatest/JavaTestFastBuildAspectTest.java b/aspect/testing/tests/src/com/google/idea/blaze/aspect/java/javatest/JavaTestFastBuildAspectTest.java index 7a5c361d751..68763a08bfb 100644 --- a/aspect/testing/tests/src/com/google/idea/blaze/aspect/java/javatest/JavaTestFastBuildAspectTest.java +++ b/aspect/testing/tests/src/com/google/idea/blaze/aspect/java/javatest/JavaTestFastBuildAspectTest.java @@ -31,6 +31,7 @@ public final class JavaTestFastBuildAspectTest { private static final String NO_LAUNCHER_TARGET = ""; + private static final String LAUNCHER_ALIAS = "@bazel_tools//tools/jdk:launcher_flag_alias"; @Rule public FastBuildAspectRule aspectLoader = @@ -45,7 +46,8 @@ public void testNoJavaLauncherSpecified() throws Exception { aspectLoader.loadTestFixture(":footest_no_launcher_fast_build_fixture"); FastBuildBlazeData data = getDataForTarget(aspectLoader.testRelative(":FooTestNoLauncher"), fixture); - assertThat(data.getJavaInfo().getLauncher()).isEqualTo(NO_LAUNCHER_TARGET); + // TODO: b/295221112 - remove LAUNCHER_ALIAS once label_flag is used + assertThat(data.getJavaInfo().getLauncher()).isAnyOf(NO_LAUNCHER_TARGET, LAUNCHER_ALIAS); } @Test diff --git a/aspect/testing/tests/src/com/google/idea/blaze/aspect/proto/jpl/BUILD b/aspect/testing/tests/src/com/google/idea/blaze/aspect/proto/jpl/BUILD index afd5d449475..6dade648605 100644 --- a/aspect/testing/tests/src/com/google/idea/blaze/aspect/proto/jpl/BUILD +++ b/aspect/testing/tests/src/com/google/idea/blaze/aspect/proto/jpl/BUILD @@ -1,10 +1,13 @@ -licenses(["notice"]) # Apache 2.0 - +load("@com_google_protobuf//bazel:java_proto_library.bzl", "java_proto_library") +load("@com_google_protobuf//bazel:proto_library.bzl", "proto_library") +load("@rules_java//java:defs.bzl", "java_library", "java_test") load( "//aspect/testing/rules:intellij_aspect_test_fixture.bzl", "intellij_aspect_test_fixture", ) +licenses(["notice"]) + proto_library( name = "foo_proto", srcs = ["foo.proto"], diff --git a/aspect/testing/tests/src/com/google/idea/blaze/aspect/python/pybinary/BUILD b/aspect/testing/tests/src/com/google/idea/blaze/aspect/python/pybinary/BUILD index bf2b8ee67ea..9062e117250 100644 --- a/aspect/testing/tests/src/com/google/idea/blaze/aspect/python/pybinary/BUILD +++ b/aspect/testing/tests/src/com/google/idea/blaze/aspect/python/pybinary/BUILD @@ -1,9 +1,11 @@ +load("@rules_java//java:defs.bzl", "java_test") +load("@rules_python//python:defs.bzl", "py_binary") load( "//aspect/testing/rules:intellij_aspect_test_fixture.bzl", "intellij_aspect_test_fixture", ) -licenses(["notice"]) # Apache 2.0 +licenses(["notice"]) py_binary( name = "simple", diff --git a/aspect/testing/tests/src/com/google/idea/blaze/aspect/python/pylibrary/BUILD b/aspect/testing/tests/src/com/google/idea/blaze/aspect/python/pylibrary/BUILD index a7c591eb5b3..9a050e016b1 100644 --- a/aspect/testing/tests/src/com/google/idea/blaze/aspect/python/pylibrary/BUILD +++ b/aspect/testing/tests/src/com/google/idea/blaze/aspect/python/pylibrary/BUILD @@ -1,3 +1,5 @@ +load("@rules_java//java:defs.bzl", "java_test") +load("@rules_python//python:defs.bzl", "py_library") load( "//aspect/testing/rules:intellij_aspect_test_fixture.bzl", "intellij_aspect_test_fixture", diff --git a/aspect/testing/tests/src/com/google/idea/blaze/aspect/python/pytest/BUILD b/aspect/testing/tests/src/com/google/idea/blaze/aspect/python/pytest/BUILD index 6b1ef473f3c..27b929db763 100644 --- a/aspect/testing/tests/src/com/google/idea/blaze/aspect/python/pytest/BUILD +++ b/aspect/testing/tests/src/com/google/idea/blaze/aspect/python/pytest/BUILD @@ -1,9 +1,11 @@ +load("@rules_java//java:defs.bzl", "java_test") +load("@rules_python//python:defs.bzl", "py_test") load( "//aspect/testing/rules:intellij_aspect_test_fixture.bzl", "intellij_aspect_test_fixture", ) -licenses(["notice"]) # Apache 2.0 +licenses(["notice"]) py_test( name = "simple", diff --git a/aspect/testing/tests/src/com/google/idea/blaze/aspect/scala/scalabinary/BUILD b/aspect/testing/tests/src/com/google/idea/blaze/aspect/scala/scalabinary/BUILD index 485aacdd2b2..9f41d205f77 100644 --- a/aspect/testing/tests/src/com/google/idea/blaze/aspect/scala/scalabinary/BUILD +++ b/aspect/testing/tests/src/com/google/idea/blaze/aspect/scala/scalabinary/BUILD @@ -1,12 +1,12 @@ -load( - "//aspect/testing/rules:intellij_aspect_test_fixture.bzl", - "intellij_aspect_test_fixture", -) load( "@io_bazel_rules_scala//scala:scala.bzl", "scala_binary", "scala_library", ) +load( + "//aspect/testing/rules:intellij_aspect_test_fixture.bzl", + "intellij_aspect_test_fixture", +) licenses(["notice"]) # Apache 2.0 diff --git a/aspect/testing/tests/src/com/google/idea/blaze/aspect/scala/scalalibrary/BUILD b/aspect/testing/tests/src/com/google/idea/blaze/aspect/scala/scalalibrary/BUILD index 19b71b77b12..44fee56d8a0 100644 --- a/aspect/testing/tests/src/com/google/idea/blaze/aspect/scala/scalalibrary/BUILD +++ b/aspect/testing/tests/src/com/google/idea/blaze/aspect/scala/scalalibrary/BUILD @@ -1,11 +1,11 @@ -load( - "//aspect/testing/rules:intellij_aspect_test_fixture.bzl", - "intellij_aspect_test_fixture", -) load( "@io_bazel_rules_scala//scala:scala.bzl", "scala_library", ) +load( + "//aspect/testing/rules:intellij_aspect_test_fixture.bzl", + "intellij_aspect_test_fixture", +) licenses(["notice"]) # Apache 2.0 diff --git a/aspect/testing/tests/src/com/google/idea/blaze/aspect/scala/scalamacrolibrary/BUILD b/aspect/testing/tests/src/com/google/idea/blaze/aspect/scala/scalamacrolibrary/BUILD index 3e77908a74c..ab6254b0475 100644 --- a/aspect/testing/tests/src/com/google/idea/blaze/aspect/scala/scalamacrolibrary/BUILD +++ b/aspect/testing/tests/src/com/google/idea/blaze/aspect/scala/scalamacrolibrary/BUILD @@ -1,11 +1,11 @@ -load( - "//aspect/testing/rules:intellij_aspect_test_fixture.bzl", - "intellij_aspect_test_fixture", -) load( "@io_bazel_rules_scala//scala:scala.bzl", "scala_macro_library", ) +load( + "//aspect/testing/rules:intellij_aspect_test_fixture.bzl", + "intellij_aspect_test_fixture", +) licenses(["notice"]) # Apache 2.0 diff --git a/aspect/testing/tests/src/com/google/idea/blaze/aspect/scala/scalatest/BUILD b/aspect/testing/tests/src/com/google/idea/blaze/aspect/scala/scalatest/BUILD index 058fbdde30d..b15cce19aee 100644 --- a/aspect/testing/tests/src/com/google/idea/blaze/aspect/scala/scalatest/BUILD +++ b/aspect/testing/tests/src/com/google/idea/blaze/aspect/scala/scalatest/BUILD @@ -1,11 +1,11 @@ -load( - "//aspect/testing/rules:intellij_aspect_test_fixture.bzl", - "intellij_aspect_test_fixture", -) load( "@io_bazel_rules_scala//scala:scala.bzl", "scala_test", ) +load( + "//aspect/testing/rules:intellij_aspect_test_fixture.bzl", + "intellij_aspect_test_fixture", +) licenses(["notice"]) # Apache 2.0 diff --git a/aspect/testing/tests/src/com/google/idea/blaze/aspect/scala/scalatest/FooTest.scala b/aspect/testing/tests/src/com/google/idea/blaze/aspect/scala/scalatest/FooTest.scala index 68857b805a4..8b621808404 100644 --- a/aspect/testing/tests/src/com/google/idea/blaze/aspect/scala/scalatest/FooTest.scala +++ b/aspect/testing/tests/src/com/google/idea/blaze/aspect/scala/scalatest/FooTest.scala @@ -1,8 +1,8 @@ package com.google.idea.blaze.aspect.scala.scalatest; -import org.scalatest.FlatSpec +import org.scalatest.flatspec.AnyFlatSpec /** Dummy test class. */ -class FooTest extends FlatSpec { +class FooTest extends AnyFlatSpec { "test" should "pass" in {} } diff --git a/aspect/tools/BUILD b/aspect/tools/BUILD index ef9eb8068da..cfefd3a5579 100644 --- a/aspect/tools/BUILD +++ b/aspect/tools/BUILD @@ -3,9 +3,11 @@ # Tools needed by the bazel plugin's aspect. # +load("@rules_java//java:defs.bzl", "java_binary", "java_library", "java_test") + package(default_visibility = ["//aspect:__pkg__"]) -licenses(["notice"]) # Apache 2.0 +licenses(["notice"]) # To prevent versioning conflicts when developing internally, we always use the same # guava version bundled with the IntelliJ plugin API. @@ -18,10 +20,11 @@ java_library( java_library( name = "lib", srcs = glob(["src/**/*.java"]), + javacopts = ["-source 8 -target 8"], deps = [ ":guava", - "//intellij_platform_sdk:jsr305", "//proto:proto_deps", + "@jsr305_annotations//jar", ], ) @@ -79,7 +82,10 @@ java_test( size = "small", srcs = ["tests/unittests/com/google/idea/blaze/aspect/PackageParserTest.java"], test_class = "com.google.idea.blaze.aspect.PackageParserTest", - deps = [":test_lib"], + deps = [ + ":test_lib", + "@error_prone_annotations//jar", + ], ) java_test( diff --git a/aspect/tools/src/com/google/idea/blaze/aspect/PackageParserIoProvider.java b/aspect/tools/src/com/google/idea/blaze/aspect/PackageParserIoProvider.java index 5106b28e700..26be71aaf31 100644 --- a/aspect/tools/src/com/google/idea/blaze/aspect/PackageParserIoProvider.java +++ b/aspect/tools/src/com/google/idea/blaze/aspect/PackageParserIoProvider.java @@ -16,7 +16,7 @@ package com.google.idea.blaze.aspect; import com.google.common.annotations.VisibleForTesting; -import com.google.repackaged.bazel.protobuf.MessageLite; +import com.google.protobuf.MessageLite; import java.io.BufferedReader; import java.io.IOException; import java.io.OutputStream; 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 index 3f0e338e90d..f7178b2b06e 100644 --- a/aspect/tools/tests/unittests/com/google/idea/blaze/aspect/ArtifactLocationParserTest.java +++ b/aspect/tools/tests/unittests/com/google/idea/blaze/aspect/ArtifactLocationParserTest.java @@ -116,7 +116,7 @@ private void assertFails(String input, String expectedError) { ArtifactLocationParser.parse(input); fail(); } catch (IllegalArgumentException e) { - assertThat(e).hasMessage(expectedError); + assertThat(e).hasMessageThat().isEqualTo(expectedError); } } } 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 index 37f18c49c8d..e91ccd08055 100644 --- a/aspect/tools/tests/unittests/com/google/idea/blaze/aspect/OptionParserTest.java +++ b/aspect/tools/tests/unittests/com/google/idea/blaze/aspect/OptionParserTest.java @@ -49,7 +49,7 @@ public void testParseSingleOption() throws Exception { OptionParser.parseSingleOption(new String[] {"--foo", "1", "--bar"}, "bar", String::toString); fail("Expected failure"); } catch (IllegalArgumentException e) { - assertThat(e).hasMessage("Expected value after --bar"); + assertThat(e).hasMessageThat().isEqualTo("Expected value after --bar"); } // Test that a single-value flag should not appear multiple times. try { @@ -57,7 +57,7 @@ public void testParseSingleOption() throws Exception { 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"); + assertThat(e).hasMessageThat().isEqualTo("Expected --foo to appear at most once"); } } @@ -89,7 +89,7 @@ public void testParseMultiOption() throws Exception { new String[] {"--foo", "1", "--bar", "2", "--foo"}, "foo", String::toString); fail("Expected failure"); } catch (IllegalArgumentException e) { - assertThat(e).hasMessage("Expected value after --foo"); + assertThat(e).hasMessageThat().isEqualTo("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 index 9c89d8f2579..18a6b0ee88e 100644 --- a/aspect/tools/tests/unittests/com/google/idea/blaze/aspect/PackageParserTest.java +++ b/aspect/tools/tests/unittests/com/google/idea/blaze/aspect/PackageParserTest.java @@ -23,7 +23,8 @@ import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.devtools.intellij.aspect.Common.ArtifactLocation; -import com.google.repackaged.bazel.protobuf.MessageLite; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.protobuf.MessageLite; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -51,6 +52,7 @@ private static class MockPackageParserIoProvider extends PackageParserIoProvider private final List sourceLocations = Lists.newArrayList(); private StringWriter writer = new StringWriter(); + @CanIgnoreReturnValue public MockPackageParserIoProvider addSource(ArtifactLocation source, String javaSrc) { try { Path path = Paths.get(source.getRootExecutionPathFragment(), source.getRelativePath()); diff --git a/aswb/BUILD b/aswb/BUILD index 74609ee2260..7b6f74c5332 100644 --- a/aswb/BUILD +++ b/aswb/BUILD @@ -2,6 +2,16 @@ # Description: Builds ASwB for blaze and bazel # +load("@rules_java//java:defs.bzl", "java_library") +load( + "//:build-visibility.bzl", + "ASWB_PACKAGES_VISIBILITY", + "ASWB_PLUGIN_PACKAGES_VISIBILITY", + "ASWB_SUBPACKAGES_VISIBILITY", + "DEFAULT_TEST_VISIBILITY", + "TEST_ASWB_SUBPACKAGES_VISIBILITY", +) +load("//:version.bzl", "VERSION") load( "//build_defs:build_defs.bzl", "intellij_plugin", @@ -15,7 +25,6 @@ load( "//build_defs:intellij_plugin_debug_target.bzl", "intellij_plugin_debug_target", ) -load("//:version.bzl", "VERSION") load( "//intellij_platform_sdk:build_defs.bzl", "combine_visibilities", @@ -26,15 +35,6 @@ load( "intellij_integration_test_suite", "intellij_unit_test_suite", ) -load( - "//:build-visibility.bzl", - "ASWB_PACKAGES_VISIBILITY", - "ASWB_PLUGIN_PACKAGES_VISIBILITY", - "ASWB_SUBPACKAGES_VISIBILITY", - "TEST_ASWB_SUBPACKAGES_VISIBILITY", -) - -licenses(["notice"]) # Apache 2.0 optional_plugin_xml( name = "optional_ndk_xml", @@ -45,7 +45,17 @@ optional_plugin_xml( intellij_plugin_library( name = "plugin_library", optional_plugin_xmls = [":optional_ndk_xml"], - plugin_xmls = ["src/META-INF/aswb.xml"], + plugin_xmls = [ + "src/META-INF/aswb.xml", + ] + select_for_plugin_api({ + "android-studio-2023.2": [ + "sdkcompat/as232/com/META-INF/aswb.xml", + ], + "android-studio-2023.3": [ + "sdkcompat/as233/com/META-INF/aswb.xml", + ], + "default": [], + }), visibility = ASWB_PLUGIN_PACKAGES_VISIBILITY, deps = [":aswb_lib"], ) @@ -55,7 +65,7 @@ stamped_plugin_xml( changelog_file = "//:changelog", description_file = "src/META-INF/description.html", plugin_id = "com.google.idea.bazel.aswb", - plugin_name = "Bazel", + plugin_name = "Bazel for Android Studio", # #api212: We depend on an API which is only contained in 2021.2.1+. since_build_numbers = {"212": "212.5080.55"}, stamp_since_build = True, @@ -66,13 +76,27 @@ stamped_plugin_xml( java_library( name = "unit_test_utils", testonly = 1, - srcs = glob(["tests/utils/unit/**/*.java"]), - visibility = TEST_ASWB_SUBPACKAGES_VISIBILITY, + srcs = glob(["tests/utils/unit/**/*.java"]) + select_for_plugin_api({ + "android-studio-2022.3": [ + "tests/utils/sdkcompat/as223/com/android/ide/common/repository/GoogleMavenArtifactIdCompat.java", + ], + "android-studio-2023.1": [ + "tests/utils/sdkcompat/as231/com/android/ide/common/repository/GoogleMavenArtifactIdCompat.java", + ], + "android-studio-2023.2": [ + "tests/utils/sdkcompat/as232/com/android/ide/common/repository/GoogleMavenArtifactIdCompat.java", + ], + "android-studio-2023.3": [ + "tests/utils/sdkcompat/as233/com/android/ide/common/repository/GoogleMavenArtifactIdCompat.java", + ], + }), + visibility = combine_visibilities(TEST_ASWB_SUBPACKAGES_VISIBILITY, DEFAULT_TEST_VISIBILITY), deps = [ ":aswb_lib", "//base", "//intellij_platform_sdk:plugin_api_for_tests", "//intellij_platform_sdk:test_libs", + "@com_google_guava_guava//jar", ], ) @@ -80,12 +104,33 @@ java_library( name = "integration_test_utils", testonly = 1, srcs = glob(["tests/utils/integration/**/*.java"]) + select_for_plugin_api({ - "android-studio-2021.2": glob([ - "tests/utils/sdkcompat/as212/com/google/idea/blaze/android/functional/*.java", - ]), - "android-studio-2021.3": glob([ - "tests/utils/sdkcompat/as213/com/google/idea/blaze/android/functional/*.java", - ]), + "android-studio-2022.3": [ + "sdkcompat/as223/com/android/tools/rendering/RenderResultCompat.java", + "tests/utils/sdkcompat/as223/com/android/tools/idea/sdk/IdeSdksCompat.java", + "tests/utils/sdkcompat/as223/com/google/idea/blaze/android/functional/AndroidDeviceCompat.java", + "tests/utils/sdkcompat/as223/com/google/idea/blaze/android/tools/idea/run/editor/AndroidDebuggerCompat.java", + ], + "android-studio-2023.1": [ + "sdkcompat/as231/com/android/tools/rendering/RenderResultCompat.java", + "tests/utils/sdkcompat/as231/com/android/tools/idea/sdk/IdeSdksCompat.java", + "tests/utils/sdkcompat/as231/com/google/idea/blaze/android/MobileInstallBuildStepTestCase.java", + "tests/utils/sdkcompat/as231/com/google/idea/blaze/android/functional/AndroidDeviceCompat.java", + "tests/utils/sdkcompat/as231/com/google/idea/blaze/android/tools/idea/run/editor/AndroidDebuggerCompat.java", + ], + "android-studio-2023.2": [ + "sdkcompat/as232/com/android/tools/rendering/RenderResultCompat.java", + "tests/utils/sdkcompat/as232/com/android/tools/idea/sdk/IdeSdksCompat.java", + "tests/utils/sdkcompat/as232/com/google/idea/blaze/android/MobileInstallBuildStepTestCase.java", + "tests/utils/sdkcompat/as232/com/google/idea/blaze/android/functional/AndroidDeviceCompat.java", + "tests/utils/sdkcompat/as232/com/google/idea/blaze/android/tools/idea/run/editor/AndroidDebuggerCompat.java", + ], + "android-studio-2023.3": [ + "sdkcompat/as233/com/android/tools/rendering/RenderResultCompat.java", + "tests/utils/sdkcompat/as233/com/android/tools/idea/sdk/IdeSdksCompat.java", + "tests/utils/sdkcompat/as233/com/google/idea/blaze/android/MobileInstallBuildStepTestCase.java", + "tests/utils/sdkcompat/as233/com/google/idea/blaze/android/functional/AndroidDeviceCompat.java", + "tests/utils/sdkcompat/as233/com/google/idea/blaze/android/tools/idea/run/editor/AndroidDebuggerCompat.java", + ], }), visibility = TEST_ASWB_SUBPACKAGES_VISIBILITY, deps = [ @@ -95,19 +140,34 @@ java_library( "//base:unit_test_utils", "//cpp", "//intellij_platform_sdk:jsr305", - "//intellij_platform_sdk:plugin_api_for_tests", + "//intellij_platform_sdk:plugin_api_for_tests", # unuseddeps: keep "//intellij_platform_sdk:test_libs", "//java", + "//shared:artifact", "//testing:lib", + "@com_google_guava_guava//jar", + "@error_prone_annotations//jar", "@junit//jar", ], ) java_library( name = "aswb_lib", - srcs = glob(["src/**/*.java"]) + select_for_plugin_api({ - "android-studio-2021.2": glob(["sdkcompat/as212/**/*.java"]), - "android-studio-2021.3": glob(["sdkcompat/as213/**/*.java"]), + srcs = glob( + ["src/**/*.java"], + # These source files below should be omitted for versions < 2023.1.1.11 + exclude = ["src/com/google/idea/blaze/android/run/binary/tasks/*.java"], + ) + select_for_plugin_api({ + "android-studio-2022.3": glob(["sdkcompat/as223/**/*.java"]), + "android-studio-2023.1": glob(["sdkcompat/as231/**/*.java"] + [ + "src/com/google/idea/blaze/android/run/binary/tasks/*.java", + ]), + "android-studio-2023.2": glob(["sdkcompat/as232/**/*.java"] + [ + "src/com/google/idea/blaze/android/run/binary/tasks/*.java", + ]), + "android-studio-2023.3": glob(["sdkcompat/as233/**/*.java"] + [ + "src/com/google/idea/blaze/android/run/binary/tasks/*.java", + ]), }), resources = glob(["resources/**/*"]), visibility = combine_visibilities( @@ -117,12 +177,17 @@ java_library( deps = [ "//base", "//common/experiments", - "//intellij_platform_sdk:jsr305", + "//cpp", + "//intellij_platform_sdk:jsr305", # unuseddeps: keep "//intellij_platform_sdk:kotlin", "//intellij_platform_sdk:plugin_api", "//java", "//proto:proto_deps", + "//querysync", + "//shared", + "//shared:artifact", "//third_party/auto_value", + "@gson//jar", ], ) @@ -130,9 +195,17 @@ intellij_unit_test_suite( name = "unit_tests", srcs = glob( ["tests/unittests/**/*.java"], - exclude = ["tests/unittests/com/google/idea/blaze/android/sync/model/idea/BlazeClassJarProviderTest.java"], # b/145809318 - ), + exclude = [ + "tests/unittests/com/google/idea/blaze/android/sync/model/idea/BlazeClassJarProviderTest.java", # b/145809318 + ] + glob(["tests/unittests/sdkcompat/**"]), + ) + select_for_plugin_api({ + "android-studio-2022.3": glob(["tests/unittests/sdkcompat/as223/**/*.java"]), + "android-studio-2023.1": glob(["tests/unittests/sdkcompat/as231/**/*.java"]), + "android-studio-2023.2": glob(["tests/unittests/sdkcompat/as232/**/*.java"]), + "android-studio-2023.3": glob(["tests/unittests/sdkcompat/as232/**/*.java"]), + }), test_package_root = "com.google.idea.blaze.android", + visibility = DEFAULT_TEST_VISIBILITY, deps = [ ":aswb_lib", ":unit_test_utils", @@ -145,7 +218,10 @@ intellij_unit_test_suite( "//intellij_platform_sdk:test_libs", "//java", "//proto:proto_deps", + "//shared", + "//shared:artifact", "//testing:lib", + "@com_google_guava_guava//jar", "@junit//jar", ], ) @@ -156,25 +232,38 @@ test_suite( #b/139825934 ":NdkDependenciesTest", ":normal_integration_tests", ], + visibility = DEFAULT_TEST_VISIBILITY, ) intellij_integration_test_suite( name = "normal_integration_tests", srcs = glob( [ - "tests/integrationtests/**/*.java", + "tests/integrationtests/com/google/idea/blaze/android/**/*.java", ], exclude = [ "tests/integrationtests/com/google/idea/blaze/android/plugin/NdkDependenciesTest.java", # Extracted to separate target - "tests/integrationtests/com/google/idea/blaze/android/functional/AswbRenderTaskTest.java", # b/177371104 "tests/integrationtests/com/google/idea/blaze/android/functional/AswbMergedManifestTest.java", #b/222322106 - ], - ), + ] + glob(["tests/integrationtests/sdkcompat/**"]), + ) + select_for_plugin_api({ + "android-studio-2022.3": glob(["tests/integrationtests/sdkcompat/as223/**/*.java"]), + "android-studio-2023.1": glob(["tests/integrationtests/sdkcompat/as231/**/*.java"]), + "android-studio-2023.2": glob(["tests/integrationtests/sdkcompat/as232/**/*.java"]), + "default": [], + }), + additional_class_rules = select_for_plugin_api({ + "android-studio-2022.2": ["com.google.idea.blaze.android.NormalIntegrationTestSetupRule"], + "android-studio-2022.3": ["com.google.idea.blaze.android.NormalIntegrationTestSetupRule"], + "android-studio-2023.1": ["com.google.idea.blaze.android.NormalIntegrationTestSetupRule"], + "android-studio-2023.2": ["com.google.idea.blaze.android.NormalIntegrationTestSetupRule"], + "default": [], + }), data = [ "testdata/golden.png", "testdata/ic_banner.png", + "tools/adt/idea/android/annotations/empty_file", ], - required_plugins = "com.google.idea.bazel.aswb", + required_plugins = "com.google.idea.bazel.aswb,com.android.tools.ndk", test_package_root = "com.google.idea.blaze.android", runtime_deps = [ ":aswb_bazel", @@ -191,11 +280,14 @@ intellij_integration_test_suite( "//common/experiments", "//common/experiments:unit_test_utils", "//cpp", + "//intellij_platform_sdk:jsr305", "//intellij_platform_sdk:plugin_api_for_tests", "//intellij_platform_sdk:test_libs", "//java", "//proto:proto_deps", - "@error_prone_annotations//jar", + "//shared:artifact", + "@com_google_guava_guava//jar", + "@gson//jar", "@junit//jar", ], ) @@ -225,6 +317,9 @@ intellij_integration_test_suite( intellij_plugin( name = "aswb_bazel", plugin_xml = ":stamped_plugin_xml", + tags = [ + "incomplete-deps", # remove this suppression and add any missing deps, see go/java-import-deps-checking-lsc + ], deps = [ ":plugin_library", "//base:plugin_library", @@ -232,7 +327,10 @@ intellij_plugin( "//dart:plugin_library", "//java:plugin_library", "//kotlin:plugin_library", + "//plugin_dev:plugin_library", + # BEGIN-EXTERNAL "//python:plugin_library", + # END-EXTERNAL "//skylark:plugin_library", "//terminal:plugin_library", ], @@ -278,3 +376,9 @@ plugin_deploy_zip( visibility = ["//visibility:public"], zip_filename = "aswb_bazel.zip", ) + +genrule( + name = "create_empty_annotations_folder", + outs = ["tools/adt/idea/android/annotations/empty_file"], + cmd = "echo \"No contents\" > $@", +) diff --git a/aswb/sdkcompat/as212/com/android/tools/idea/run/tasks/DeployTasksCompat.java b/aswb/sdkcompat/as212/com/android/tools/idea/run/tasks/DeployTasksCompat.java deleted file mode 100644 index 3365e2bc5e0..00000000000 --- a/aswb/sdkcompat/as212/com/android/tools/idea/run/tasks/DeployTasksCompat.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2020 The Bazel Authors. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR 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.run.tasks; - -import com.android.tools.idea.deploy.DeploymentConfiguration; -import com.android.tools.idea.run.ApkInfo; -import com.android.tools.idea.run.LaunchOptions; -import com.android.tools.idea.run.util.SwapInfo; -import com.android.tools.idea.run.util.SwapInfo.SwapType; -import com.google.idea.blaze.android.run.BlazeAndroidDeploymentService; -import com.google.idea.common.experiments.BoolExperiment; -import com.intellij.execution.runners.ExecutionEnvironment; -import com.intellij.openapi.project.Project; -import java.util.Collection; - -/** Compat class for {@link DeployTask} */ -public class DeployTasksCompat { - private static final BoolExperiment updateCodeViaJvmti = - new BoolExperiment("android.apply.changes", false); - - private DeployTasksCompat() {} - - public static LaunchTask createDeployTask( - Project project, Collection packages, LaunchOptions launchOptions) { - // We don't have a device information, fallback to the most conservative - // install option. - return new DeployTask( - project, - packages, - launchOptions.getPmInstallOptions(/*device=*/ null), - launchOptions.getInstallOnAllUsers(), - launchOptions.getAlwaysInstallWithPm()); - } - - public static LaunchTask getDeployTask( - Project project, - ExecutionEnvironment env, - LaunchOptions launchOptions, - Collection packages) { - if (updateCodeViaJvmti.getValue()) { - // Set the appropriate action based on which deployment we're doing. - SwapInfo swapInfo = env.getUserData(SwapInfo.SWAP_INFO_KEY); - SwapInfo.SwapType swapType = swapInfo == null ? null : swapInfo.getType(); - if (swapType == SwapType.APPLY_CHANGES) { - return new ApplyChangesTask( - project, - packages, - DeploymentConfiguration.getInstance().APPLY_CHANGES_FALLBACK_TO_RUN, - false); - } else if (swapType == SwapType.APPLY_CODE_CHANGES) { - return new ApplyCodeChangesTask( - project, - packages, - DeploymentConfiguration.getInstance().APPLY_CODE_CHANGES_FALLBACK_TO_RUN, - false); - } - } - return BlazeAndroidDeploymentService.getInstance(project) - .getDeployTask(packages, launchOptions); - } -} diff --git a/aswb/sdkcompat/as212/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeAndroidDebugger.java b/aswb/sdkcompat/as212/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeAndroidDebugger.java deleted file mode 100644 index 32472baf85e..00000000000 --- a/aswb/sdkcompat/as212/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeAndroidDebugger.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2018 The Bazel Authors. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.cppimpl.debug; - -import com.android.ddmlib.Client; -import com.android.tools.idea.run.editor.AndroidDebuggerState; -import com.android.tools.ndk.run.attach.AndroidNativeAttachConfiguration; -import com.android.tools.ndk.run.editor.NativeAndroidDebuggerState; -import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; -import com.google.idea.blaze.base.settings.Blaze; -import com.intellij.execution.RunManager; -import com.intellij.execution.RunnerAndConfigurationSettings; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.project.Project; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * API compat of {@link BlazeNativeAndroidDebuggerBase} with the following additions: - * - *

- * - * #api4.0 - */ -public class BlazeNativeAndroidDebugger extends BlazeNativeAndroidDebuggerBase { - @NotNull - @Override - protected RunnerAndConfigurationSettings createRunnerAndConfigurationSettings( - @NotNull Project project, - @NotNull Module module, - @NotNull Client client, - @Nullable AndroidDebuggerState inputState) { - String runConfigurationName = - String.format( - "%s %s Debugger (%d)", - Blaze.getBuildSystemName(project).getName(), - getDisplayName(), - client.getClientData().getPid()); - RunnerAndConfigurationSettings runSettings = - RunManager.getInstance(project) - .createConfiguration( - runConfigurationName, new BlazeAndroidNativeAttachConfigurationType.Factory()); - BlazeAndroidNativeAttachConfiguration configuration = - (BlazeAndroidNativeAttachConfiguration) runSettings.getConfiguration(); - configuration.setClient(client); - configuration.getAndroidDebuggerContext().setDebuggerType(getId()); - configuration.getConfigurationModule().setModule(module); - configuration.setConsoleProvider(getConsoleProvider()); - - // TODO(b/145707569): Copy debugger settings from inputState to state. See - // NativeAndroidDebugger. - AndroidDebuggerState state = - configuration.getAndroidDebuggerContext().getAndroidDebuggerState(); - if (state instanceof NativeAndroidDebuggerState) { - NativeAndroidDebuggerState nativeState = (NativeAndroidDebuggerState) state; - nativeState.setWorkingDir(WorkspaceRoot.fromProject(project).directory().getPath()); - } - return runSettings; - } -} diff --git a/aswb/sdkcompat/as212/com/google/idea/blaze/android/projectsystem/BlazeModuleSystem.java b/aswb/sdkcompat/as212/com/google/idea/blaze/android/projectsystem/BlazeModuleSystem.java deleted file mode 100644 index 2703983d02f..00000000000 --- a/aswb/sdkcompat/as212/com/google/idea/blaze/android/projectsystem/BlazeModuleSystem.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright 2020 The Bazel Authors. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.projectsystem; - -import com.android.ide.common.util.PathString; -import com.android.projectmodel.ExternalAndroidLibrary; -import com.android.projectmodel.ExternalLibraryImpl; -import com.android.projectmodel.SelectiveResourceFolder; -import com.android.tools.idea.projectsystem.AndroidModuleSystem; -import com.android.tools.idea.projectsystem.DependencyScopeType; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.idea.blaze.android.libraries.UnpackedAars; -import com.google.idea.blaze.android.sync.model.AarLibrary; -import com.google.idea.blaze.android.sync.model.AndroidResourceModuleRegistry; -import com.google.idea.blaze.android.sync.model.BlazeAndroidSyncData; -import com.google.idea.blaze.base.ideinfo.TargetIdeInfo; -import com.google.idea.blaze.base.model.BlazeLibrary; -import com.google.idea.blaze.base.model.BlazeProjectData; -import com.google.idea.blaze.base.projectview.ProjectViewManager; -import com.google.idea.blaze.base.sync.SyncCache; -import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; -import com.google.idea.blaze.base.sync.libraries.BlazeLibraryCollector; -import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.project.Project; -import java.io.File; -import java.util.Collection; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** Blaze implementation of {@link AndroidModuleSystem}. */ -public class BlazeModuleSystem extends BlazeModuleSystemBase { - BlazeModuleSystem(Module module) { - super(module); - } - - public Collection getDependentLibraries() { - BlazeProjectData blazeProjectData = - BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); - - if (blazeProjectData == null) { - return ImmutableList.of(); - } - - if (isWorkspaceModule) { - return SyncCache.getInstance(project) - .get(BlazeModuleSystem.class, BlazeModuleSystem::getLibrariesForWorkspaceModule); - } - - AndroidResourceModuleRegistry registry = AndroidResourceModuleRegistry.getInstance(project); - TargetIdeInfo target = blazeProjectData.getTargetMap().get(registry.getTargetKey(module)); - if (target == null) { - // this can happen if the module points to the , - // does not contain any resource - // contains all external resources as module's local resources, so there's - // no dependent libraries - return ImmutableList.of(); - } - - BlazeAndroidSyncData androidSyncData = - blazeProjectData.getSyncState().get(BlazeAndroidSyncData.class); - if (androidSyncData == null) { - return ImmutableList.of(); - } - - ImmutableList.Builder libraries = ImmutableList.builder(); - ArtifactLocationDecoder decoder = blazeProjectData.getArtifactLocationDecoder(); - ExternalLibraryInterner externalLibraryInterner = ExternalLibraryInterner.getInstance(project); - for (String libraryKey : registry.get(module).resourceLibraryKeys) { - ImmutableMap aarLibraries = androidSyncData.importResult.aarLibraries; - ExternalAndroidLibrary externalLibrary = - toExternalLibrary(project, aarLibraries.get(libraryKey), decoder); - if (externalLibrary != null) { - libraries.add(externalLibraryInterner.intern(externalLibrary)); - } - } - return libraries.build(); - } - - private static ImmutableList getLibrariesForWorkspaceModule( - Project project, BlazeProjectData blazeProjectData) { - ArtifactLocationDecoder decoder = blazeProjectData.getArtifactLocationDecoder(); - ExternalLibraryInterner externalLibraryInterner = ExternalLibraryInterner.getInstance(project); - ImmutableList.Builder libraries = ImmutableList.builder(); - for (BlazeLibrary library : - BlazeLibraryCollector.getLibraries( - ProjectViewManager.getInstance(project).getProjectViewSet(), blazeProjectData)) { - if (library instanceof AarLibrary) { - ExternalAndroidLibrary externalLibrary = - toExternalLibrary(project, (AarLibrary) library, decoder); - if (externalLibrary != null) { - libraries.add(externalLibraryInterner.intern(externalLibrary)); - } - } - } - return libraries.build(); - } - - @Nullable - static ExternalAndroidLibrary toExternalLibrary( - Project project, @Nullable AarLibrary library, ArtifactLocationDecoder decoder) { - if (library == null) { - return null; - } - UnpackedAars unpackedAars = UnpackedAars.getInstance(project); - File aarFile = unpackedAars.getAarDir(decoder, library); - if (aarFile == null) { - logger.warn( - String.format( - "Fail to locate AAR file %s. Re-sync the project may solve the problem", - library.aarArtifact)); - return null; - } - File resFolder = unpackedAars.getResourceDirectory(decoder, library); - PathString resFolderPathString = resFolder == null ? null : new PathString(resFolder); - return new ExternalLibraryImpl(library.key.toString()) - .withLocation(new PathString(aarFile)) - .withManifestFile( - resFolderPathString == null - ? null - : resFolderPathString.getParentOrRoot().resolve("AndroidManifest.xml")) - .withResFolder( - resFolderPathString == null - ? null - : new SelectiveResourceFolder(resFolderPathString, null)) - .withSymbolFile( - resFolderPathString == null - ? null - : resFolderPathString.getParentOrRoot().resolve("R.txt")) - .withPackageName(library.resourcePackage); - } - - @NotNull - @Override - public Collection getAndroidLibraryDependencies() { - return getDependentLibraries(); - } - - @NotNull - @Override - public Collection getAndroidLibraryDependencies( - @NotNull DependencyScopeType dependencyScopeType) { - return getDependentLibraries(); - } -} diff --git a/aswb/sdkcompat/as212/com/google/idea/blaze/android/projectsystem/ManifestValueProcessor.java b/aswb/sdkcompat/as212/com/google/idea/blaze/android/projectsystem/ManifestValueProcessor.java deleted file mode 100644 index c87829ed348..00000000000 --- a/aswb/sdkcompat/as212/com/google/idea/blaze/android/projectsystem/ManifestValueProcessor.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2022 The Bazel Authors. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.projectsystem; - -import com.android.manifmerger.ManifestSystemProperty; -import com.google.common.collect.ImmutableMap; -import java.util.Map; - -class ManifestValueProcessor { - /** - * Puts the key-value pair from a target's manifest_values map into either {@code directOverrides} - * if the key corresponds to a manifest attribute that Blaze allows you to override directly, or - * {@code placeholders} otherwise. - * - * @see manifest_values - */ - static void processManifestValue( - String key, - String value, - ImmutableMap.Builder directOverrides, - ImmutableMap.Builder placeholders) { - switch (key) { - case "applicationId": - directOverrides.put(ManifestSystemProperty.PACKAGE, value); - break; - case "versionCode": - directOverrides.put(ManifestSystemProperty.VERSION_CODE, value); - break; - case "versionName": - directOverrides.put(ManifestSystemProperty.VERSION_NAME, value); - break; - case "minSdkVersion": - directOverrides.put(ManifestSystemProperty.MIN_SDK_VERSION, value); - break; - case "targetSdkVersion": - directOverrides.put(ManifestSystemProperty.TARGET_SDK_VERSION, value); - break; - case "maxSdkVersion": - directOverrides.put(ManifestSystemProperty.MAX_SDK_VERSION, value); - break; - case "packageName": - // From the doc: "packageName will be ignored and will be set from either applicationId if - // specified or the package in manifest" - break; - default: - placeholders.put(key, value); - } - } - - static String getPackageOverride(Map overrides) { - return overrides.get(ManifestSystemProperty.PACKAGE); - } - - private ManifestValueProcessor() {} -} diff --git a/aswb/sdkcompat/as212/com/google/idea/blaze/android/run/BlazeAndroidDeploymentService.java b/aswb/sdkcompat/as212/com/google/idea/blaze/android/run/BlazeAndroidDeploymentService.java deleted file mode 100644 index 032ec26e8bb..00000000000 --- a/aswb/sdkcompat/as212/com/google/idea/blaze/android/run/BlazeAndroidDeploymentService.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2020 The Bazel Authors. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.android.tools.idea.run.ApkInfo; -import com.android.tools.idea.run.LaunchOptions; -import com.android.tools.idea.run.tasks.DeployTask; -import com.android.tools.idea.run.tasks.DeployTasksCompat; -import com.android.tools.idea.run.tasks.LaunchTask; -import com.intellij.openapi.components.ServiceManager; -import com.intellij.openapi.project.Project; -import java.util.Collection; - -/** A service that provides {@link DeployTask}. */ -public interface BlazeAndroidDeploymentService { - static BlazeAndroidDeploymentService getInstance(Project project) { - return ServiceManager.getService(project, BlazeAndroidDeploymentService.class); - } - - /** Returns a {@link DeployTask} to deploy the given files and launch options. */ - LaunchTask getDeployTask(Collection packages, LaunchOptions launchOptions); - - /** A default implementation that uses {@link DeployTasksCompat#createDeployTask}. */ - class DefaultDeploymentService implements BlazeAndroidDeploymentService { - private final Project project; - - public DefaultDeploymentService(Project project) { - this.project = project; - } - - @Override - public LaunchTask getDeployTask(Collection packages, LaunchOptions launchOptions) { - return DeployTasksCompat.createDeployTask(project, packages, launchOptions); - } - } -} diff --git a/aswb/sdkcompat/as212/com/google/idea/blaze/android/run/runner/BlazeAndroidLaunchTasksProvider.java b/aswb/sdkcompat/as212/com/google/idea/blaze/android/run/runner/BlazeAndroidLaunchTasksProvider.java deleted file mode 100644 index aaf5a9897b6..00000000000 --- a/aswb/sdkcompat/as212/com/google/idea/blaze/android/run/runner/BlazeAndroidLaunchTasksProvider.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright 2018 The Bazel Authors. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.runner; - -import static com.android.tools.idea.profilers.AndroidProfilerLaunchTaskContributor.isProfilerLaunch; - -import com.android.ddmlib.IDevice; -import com.android.sdklib.AndroidVersion; -import com.android.tools.deployer.ApkVerifierTracker; -import com.android.tools.idea.profilers.AndroidProfilerLaunchTaskContributor; -import com.android.tools.idea.run.ApkProvisionException; -import com.android.tools.idea.run.ApplicationIdProvider; -import com.android.tools.idea.run.ConsolePrinter; -import com.android.tools.idea.run.LaunchOptions; -import com.android.tools.idea.run.editor.AndroidDebugger; -import com.android.tools.idea.run.editor.AndroidDebuggerState; -import com.android.tools.idea.run.tasks.ClearLogcatTask; -import com.android.tools.idea.run.tasks.ConnectDebuggerTask; -import com.android.tools.idea.run.tasks.DismissKeyguardTask; -import com.android.tools.idea.run.tasks.LaunchTask; -import com.android.tools.idea.run.tasks.LaunchTasksProvider; -import com.android.tools.idea.run.tasks.ShowLogcatTask; -import com.android.tools.idea.run.util.LaunchStatus; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Lists; -import com.google.idea.blaze.android.run.binary.UserIdHelper; -import com.intellij.execution.ExecutionException; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import java.util.List; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** Normal launch tasks provider. #api4.1 */ -public class BlazeAndroidLaunchTasksProvider implements LaunchTasksProvider { - public static final String NATIVE_DEBUGGING_ENABLED = "NATIVE_DEBUGGING_ENABLED"; - private static final Logger LOG = Logger.getInstance(BlazeAndroidLaunchTasksProvider.class); - - private final Project project; - private final BlazeAndroidRunContext runContext; - private final ApplicationIdProvider applicationIdProvider; - private final LaunchOptions.Builder launchOptionsBuilder; - - public BlazeAndroidLaunchTasksProvider( - Project project, - BlazeAndroidRunContext runContext, - ApplicationIdProvider applicationIdProvider, - LaunchOptions.Builder launchOptionsBuilder) { - this.project = project; - this.runContext = runContext; - this.applicationIdProvider = applicationIdProvider; - this.launchOptionsBuilder = launchOptionsBuilder; - } - - @NotNull - @Override - public List getTasks( - @NotNull IDevice device, - @NotNull LaunchStatus launchStatus, - @NotNull ConsolePrinter consolePrinter) - throws ExecutionException { - final List launchTasks = Lists.newArrayList(); - - String packageName; - try { - packageName = applicationIdProvider.getPackageName(); - } catch (ApkProvisionException e) { - LOG.error(e); - launchStatus.terminateLaunch("Unable to determine application id: " + e, true); - return ImmutableList.of(); - } - - Integer userId = runContext.getUserId(device, consolePrinter); - String userIdFlags = UserIdHelper.getFlagsFromUserId(userId); - String skipVerification = - ApkVerifierTracker.getSkipVerificationInstallationFlag(device, packageName); - String pmInstallOption; - if (skipVerification != null) { - pmInstallOption = userIdFlags + " " + skipVerification; - } else { - pmInstallOption = userIdFlags; - } - launchOptionsBuilder.setPmInstallOptions(d -> pmInstallOption); - - LaunchOptions launchOptions = launchOptionsBuilder.build(); - - // NOTE: Task for opening the profiler tool-window should come before deployment - // to ensure the tool-window opens correctly. This is required because starting - // the profiler session requires the tool-window to be open. - if (isProfilerLaunch(runContext.getExecutor())) { - launchTasks.add(new BlazeAndroidOpenProfilerWindowTask(project)); - } - - if (launchOptions.isClearLogcatBeforeStart()) { - launchTasks.add(new ClearLogcatTask(project)); - } - - launchTasks.add(new DismissKeyguardTask()); - - if (launchOptions.isDeploy()) { - ImmutableList deployTasks = runContext.getDeployTasks(device, launchOptions); - launchTasks.addAll(deployTasks); - } - if (launchStatus.isLaunchTerminated()) { - return ImmutableList.copyOf(launchTasks); - } - - try { - if (launchOptions.isDebug()) { - launchTasks.add(new CheckApkDebuggableTask(runContext.getBuildStep().getDeployInfo())); - } - - ImmutableList.Builder amStartOptions = ImmutableList.builder(); - amStartOptions.add(runContext.getAmStartOptions()); - if (isProfilerLaunch(runContext.getExecutor())) { - amStartOptions.add( - AndroidProfilerLaunchTaskContributor.getAmStartOptions( - project, - packageName, - runContext.getProfileState(), - device, - runContext.getExecutor())); - launchTasks.add( - new AndroidProfilerLaunchTaskContributor.AndroidProfilerToolWindowLaunchTask( - project, packageName)); - } - - // Do not get debugger state directly from the debugger itself. - // See BlazeAndroidDebuggerService#getDebuggerState for an explanation. - BlazeAndroidDebuggerService debuggerService = - BlazeAndroidDebuggerService.getInstance(project); - AndroidDebugger debugger = - debuggerService.getDebugger(isNativeDebuggingEnabled(launchOptions)); - AndroidDebuggerState debuggerState = debuggerService.getDebuggerState(debugger); - LaunchTask appLaunchTask = - runContext.getApplicationLaunchTask( - launchOptions, - userId, - String.join(" ", amStartOptions.build()), - debugger, - debuggerState, - launchStatus); - if (appLaunchTask != null) { - launchTasks.add(appLaunchTask); - } - } catch (ApkProvisionException e) { - LOG.error(e); - launchStatus.terminateLaunch("Unable to determine application id: " + e, true); - return ImmutableList.of(); - } catch (ExecutionException e) { - launchStatus.terminateLaunch(e.getMessage(), true); - return ImmutableList.of(); - } - - if (!launchOptions.isDebug() && launchOptions.isOpenLogcatAutomatically()) { - launchTasks.add(new ShowLogcatTask(project, packageName)); - } - - return ImmutableList.copyOf(launchTasks); - } - - @Nullable - @Override - public ConnectDebuggerTask getConnectDebuggerTask( - @NotNull LaunchStatus launchStatus, @Nullable AndroidVersion version) { - LaunchOptions launchOptions = launchOptionsBuilder.build(); - if (!launchOptions.isDebug()) { - return null; - } - - // Do not get debugger state directly from the debugger itself. - // See BlazeAndroidDebuggerService#getDebuggerState for an explanation. - BlazeAndroidDebuggerService debuggerService = BlazeAndroidDebuggerService.getInstance(project); - AndroidDebugger debugger = debuggerService.getDebugger(isNativeDebuggingEnabled(launchOptions)); - AndroidDebuggerState debuggerState = debuggerService.getDebuggerState(debugger); - if (debugger == null || debuggerState == null) { - return null; - } - - try { - return runContext.getDebuggerTask(debugger, debuggerState); - } catch (ExecutionException e) { - launchStatus.terminateLaunch(e.getMessage(), true); - return null; - } - } - - @Override - public String getLaunchTypeDisplayName() { - return "Launch"; - } - - private boolean isNativeDebuggingEnabled(LaunchOptions launchOptions) { - Object flag = launchOptions.getExtraOption(NATIVE_DEBUGGING_ENABLED); - return flag instanceof Boolean && (Boolean) flag; - } -} diff --git a/aswb/sdkcompat/as212/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunContext.java b/aswb/sdkcompat/as212/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunContext.java deleted file mode 100644 index 8f536daf68a..00000000000 --- a/aswb/sdkcompat/as212/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunContext.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2021 The Bazel Authors. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.idea.blaze.android.run.test; - -import static com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryNormalBuildRunContext.getApkInfoToInstall; - -import com.android.ddmlib.IDevice; -import com.android.tools.idea.run.LaunchOptions; -import com.android.tools.idea.run.editor.AndroidDebugger; -import com.android.tools.idea.run.editor.AndroidDebuggerState; -import com.android.tools.idea.run.tasks.ConnectDebuggerTask; -import com.android.tools.idea.run.tasks.LaunchTask; -import com.google.common.collect.ImmutableList; -import com.google.idea.blaze.android.run.BlazeAndroidDeploymentService; -import com.google.idea.blaze.base.model.primitives.Label; -import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration; -import com.intellij.execution.ExecutionException; -import com.intellij.execution.Executor; -import com.intellij.execution.runners.ExecutionEnvironment; -import com.intellij.openapi.project.Project; -import org.jetbrains.android.facet.AndroidFacet; - -/** - * Run context for android_test. - * - *

#api203 - */ -public class BlazeAndroidTestRunContext extends BlazeAndroidTestRunContextBase { - BlazeAndroidTestRunContext( - Project project, - AndroidFacet facet, - BlazeCommandRunConfiguration runConfiguration, - ExecutionEnvironment env, - BlazeAndroidTestRunConfigurationState configState, - Label label, - ImmutableList blazeFlags, - ImmutableList exeFlags, - String launchId) { - super( - project, facet, runConfiguration, env, configState, label, blazeFlags, exeFlags, launchId); - } - - @Override - public ImmutableList getDeployTasks(IDevice device, LaunchOptions launchOptions) - throws ExecutionException { - switch (configState.getLaunchMethod()) { - case NON_BLAZE: - // fall through - case BLAZE_TEST: - return ImmutableList.of( - BlazeAndroidDeploymentService.getInstance(project) - .getDeployTask( - getApkInfoToInstall(device, launchOptions, apkProvider), launchOptions)); - case MOBILE_INSTALL: - return ImmutableList.of(); - } - throw new AssertionError(); - } - - @Override - @SuppressWarnings({"unchecked", "rawtypes"}) // Raw type from upstream. - public ConnectDebuggerTask getDebuggerTask( - AndroidDebugger androidDebugger, AndroidDebuggerState androidDebuggerState) - throws ExecutionException { - switch (configState.getLaunchMethod()) { - case BLAZE_TEST: - return new ConnectBlazeTestDebuggerTask( - env.getProject(), androidDebugger, null, applicationIdProvider, this); - case NON_BLAZE: - case MOBILE_INSTALL: - return androidDebugger.getConnectDebuggerTask( - env, - null, - applicationIdProvider, - facet, - androidDebuggerState, - runConfiguration.getType().getId()); - } - throw new AssertionError(); - } - - @Override - public Executor getExecutor() { - return env.getExecutor(); - } -} diff --git a/aswb/sdkcompat/as212/com/google/idea/blaze/android/run/test/ConnectBlazeTestDebuggerTask.java b/aswb/sdkcompat/as212/com/google/idea/blaze/android/run/test/ConnectBlazeTestDebuggerTask.java deleted file mode 100644 index 437cdd425f6..00000000000 --- a/aswb/sdkcompat/as212/com/google/idea/blaze/android/run/test/ConnectBlazeTestDebuggerTask.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright 2016 The Bazel Authors. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.idea.blaze.android.run.test; - -import com.android.ddmlib.AndroidDebugBridge; -import com.android.ddmlib.Client; -import com.android.ddmlib.ClientData; -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.LaunchInfo; -import com.android.tools.idea.run.ProcessHandlerConsolePrinter; -import com.android.tools.idea.run.editor.AndroidDebugger; -import com.android.tools.idea.run.tasks.ConnectDebuggerTaskBase; -import com.android.tools.idea.run.tasks.ConnectJavaDebuggerTask; -import com.android.tools.idea.run.util.ProcessHandlerLaunchStatus; -import com.intellij.execution.process.ProcessHandler; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import java.util.Set; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** Connects the blaze debugger during execution. */ -class ConnectBlazeTestDebuggerTask extends ConnectDebuggerTaskBase { - private static final Logger LOG = Logger.getInstance(ConnectBlazeTestDebuggerTask.class); - - private final Project project; - private final ApplicationIdProvider applicationIdProvider; - private final BlazeAndroidTestRunContext runContext; - - public ConnectBlazeTestDebuggerTask( - Project project, - AndroidDebugger debugger, - @Nullable Set applicationIds, // parameter present only for #api42 compat - ApplicationIdProvider applicationIdProvider, - BlazeAndroidTestRunContext runContext) { - super(applicationIdProvider, debugger, project, true); - this.project = project; - this.applicationIdProvider = applicationIdProvider; - this.runContext = runContext; - } - - @Nullable - @Override - public ProcessHandler perform( - @NotNull LaunchInfo launchInfo, - @NotNull IDevice device, - @NotNull ProcessHandlerLaunchStatus state, - @NotNull ProcessHandlerConsolePrinter printer) { - try { - String packageName = applicationIdProvider.getPackageName(); - setUpForReattachingDebugger(packageName, launchInfo, state, printer); - } catch (ApkProvisionException e) { - LOG.error(e); - } - - // The return value for this task is not used - return null; - } - - /** - * Wires up listeners to automatically reconnect the debugger for each test method. When you - * `blaze test` an android_test in debug mode, it kills the instrumentation process between each - * test method, disconnecting the debugger. We listen for the start of a new method waiting for a - * debugger, and reconnect. TODO: Support stopping Blaze from the UI. This is hard because we have - * no way to distinguish process handler termination/debug session ending initiated by the user. - */ - private void setUpForReattachingDebugger( - String targetPackage, - LaunchInfo launchInfo, - ProcessHandlerLaunchStatus launchStatus, - ProcessHandlerConsolePrinter printer) { - final AndroidDebugBridge.IClientChangeListener reattachingListener = - new AndroidDebugBridge.IClientChangeListener() { - // The target application can either - // 1. Match our target name, and become available for debugging. - // 2. Be available for debugging, and suddenly have its name changed to match. - static final int CHANGE_MASK = Client.CHANGE_DEBUGGER_STATUS | Client.CHANGE_NAME; - - @Override - public void clientChanged(@NotNull Client client, int changeMask) { - ClientData data = client.getClientData(); - String clientDescription = data.getClientDescription(); - if (clientDescription != null - && clientDescription.equals(targetPackage) - && (changeMask & CHANGE_MASK) != 0 - && data.getDebuggerConnectionStatus().equals(ClientData.DebuggerStatus.WAITING)) { - reattachDebugger(launchInfo, client, launchStatus, printer); - } - } - }; - - AndroidDebugBridge.addClientChangeListener(reattachingListener); - runContext.addLaunchTaskCompleteListener( - () -> { - AndroidDebugBridge.removeClientChangeListener(reattachingListener); - launchStatus.terminateLaunch("Test run completed.\n", true); - }); - } - - private void reattachDebugger( - LaunchInfo launchInfo, - final Client client, - ProcessHandlerLaunchStatus launchStatus, - ProcessHandlerConsolePrinter printer) { - ApplicationManager.getApplication() - .invokeLater(() -> launchDebugger(launchInfo, client, launchStatus, printer)); - } - - /** - * Nearly a clone of {@link ConnectJavaDebuggerTask#launchDebugger}. There are a few changes to - * account for null variables that could occur in our implementation. - */ - @Override - public ProcessHandler launchDebugger( - @NotNull LaunchInfo currentLaunchInfo, - @NotNull Client client, - @NotNull ProcessHandlerLaunchStatus launchStatus, - @NotNull ProcessHandlerConsolePrinter printer) { - return ConnectBlazeTestDebuggerTaskHelper.launchDebugger( - project, currentLaunchInfo, client, launchStatus, printer); - } -} diff --git a/aswb/sdkcompat/as213/com/android/tools/idea/run/tasks/DeployTasksCompat.java b/aswb/sdkcompat/as213/com/android/tools/idea/run/tasks/DeployTasksCompat.java deleted file mode 100644 index 3365e2bc5e0..00000000000 --- a/aswb/sdkcompat/as213/com/android/tools/idea/run/tasks/DeployTasksCompat.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2020 The Bazel Authors. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR 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.run.tasks; - -import com.android.tools.idea.deploy.DeploymentConfiguration; -import com.android.tools.idea.run.ApkInfo; -import com.android.tools.idea.run.LaunchOptions; -import com.android.tools.idea.run.util.SwapInfo; -import com.android.tools.idea.run.util.SwapInfo.SwapType; -import com.google.idea.blaze.android.run.BlazeAndroidDeploymentService; -import com.google.idea.common.experiments.BoolExperiment; -import com.intellij.execution.runners.ExecutionEnvironment; -import com.intellij.openapi.project.Project; -import java.util.Collection; - -/** Compat class for {@link DeployTask} */ -public class DeployTasksCompat { - private static final BoolExperiment updateCodeViaJvmti = - new BoolExperiment("android.apply.changes", false); - - private DeployTasksCompat() {} - - public static LaunchTask createDeployTask( - Project project, Collection packages, LaunchOptions launchOptions) { - // We don't have a device information, fallback to the most conservative - // install option. - return new DeployTask( - project, - packages, - launchOptions.getPmInstallOptions(/*device=*/ null), - launchOptions.getInstallOnAllUsers(), - launchOptions.getAlwaysInstallWithPm()); - } - - public static LaunchTask getDeployTask( - Project project, - ExecutionEnvironment env, - LaunchOptions launchOptions, - Collection packages) { - if (updateCodeViaJvmti.getValue()) { - // Set the appropriate action based on which deployment we're doing. - SwapInfo swapInfo = env.getUserData(SwapInfo.SWAP_INFO_KEY); - SwapInfo.SwapType swapType = swapInfo == null ? null : swapInfo.getType(); - if (swapType == SwapType.APPLY_CHANGES) { - return new ApplyChangesTask( - project, - packages, - DeploymentConfiguration.getInstance().APPLY_CHANGES_FALLBACK_TO_RUN, - false); - } else if (swapType == SwapType.APPLY_CODE_CHANGES) { - return new ApplyCodeChangesTask( - project, - packages, - DeploymentConfiguration.getInstance().APPLY_CODE_CHANGES_FALLBACK_TO_RUN, - false); - } - } - return BlazeAndroidDeploymentService.getInstance(project) - .getDeployTask(packages, launchOptions); - } -} diff --git a/aswb/sdkcompat/as213/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeAndroidDebugger.java b/aswb/sdkcompat/as213/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeAndroidDebugger.java deleted file mode 100644 index 096b2e327d7..00000000000 --- a/aswb/sdkcompat/as213/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeAndroidDebugger.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2018 The Bazel Authors. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.cppimpl.debug; - -import com.android.ddmlib.Client; -import com.android.tools.ndk.run.attach.AndroidNativeAttachConfiguration; -import com.android.tools.ndk.run.editor.NativeAndroidDebuggerState; -import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; -import com.google.idea.blaze.base.settings.Blaze; -import com.intellij.execution.RunManager; -import com.intellij.execution.RunnerAndConfigurationSettings; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.project.Project; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * API compat of {@link BlazeNativeAndroidDebuggerBase} with the following additions: - * - *

    - *
  • Creates a run-config setting using {@link BlazeAndroidNativeAttachConfiguration} instead of - * {@link AndroidNativeAttachConfiguration} to override counterproductive validations. - *
- * - * #api4.0 - */ -public class BlazeNativeAndroidDebugger extends BlazeNativeAndroidDebuggerBase { - @NotNull - @Override - protected RunnerAndConfigurationSettings createRunnerAndConfigurationSettings( - @NotNull Project project, - @NotNull Module module, - @NotNull Client client, - @Nullable NativeAndroidDebuggerState state) { - String runConfigurationName = - String.format( - "%s %s Debugger (%d)", - Blaze.getBuildSystemName(project).getName(), - getDisplayName(), - client.getClientData().getPid()); - RunnerAndConfigurationSettings runSettings = - RunManager.getInstance(project) - .createConfiguration( - runConfigurationName, new BlazeAndroidNativeAttachConfigurationType.Factory()); - BlazeAndroidNativeAttachConfiguration configuration = - (BlazeAndroidNativeAttachConfiguration) runSettings.getConfiguration(); - configuration.setClient(client); - configuration.getAndroidDebuggerContext().setDebuggerType(getId()); - configuration.getConfigurationModule().setModule(module); - configuration.setConsoleProvider(getConsoleProvider()); - - // TODO(b/145707569): Copy debugger settings from inputState to state. See - // NativeAndroidDebugger. - NativeAndroidDebuggerState targetState = - configuration.getAndroidDebuggerContext().getAndroidDebuggerState(); - if (state != null) { - // Copy the settings of the state provided as the input onto the targetState. - assert targetState != null; - targetState.setSymbolDirs(state.getSymbolDirs()); - targetState.setUserStartupCommands(state.getUserStartupCommands()); - targetState.setUserPostAttachCommands(state.getUserPostAttachCommands()); - targetState.setWorkingDir(WorkspaceRoot.fromProject(project).directory().getPath()); - } - return runSettings; - } -} diff --git a/aswb/sdkcompat/as213/com/google/idea/blaze/android/projectsystem/BlazeModuleSystem.java b/aswb/sdkcompat/as213/com/google/idea/blaze/android/projectsystem/BlazeModuleSystem.java deleted file mode 100644 index 8a6efabe9e1..00000000000 --- a/aswb/sdkcompat/as213/com/google/idea/blaze/android/projectsystem/BlazeModuleSystem.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright 2020 The Bazel Authors. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.projectsystem; - -import com.android.ide.common.util.PathString; -import com.android.projectmodel.ExternalAndroidLibrary; -import com.android.projectmodel.ExternalLibraryImpl; -import com.android.projectmodel.SelectiveResourceFolder; -import com.android.tools.idea.projectsystem.AndroidModuleSystem; -import com.android.tools.idea.projectsystem.DependencyScopeType; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.idea.blaze.android.libraries.UnpackedAars; -import com.google.idea.blaze.android.sync.model.AarLibrary; -import com.google.idea.blaze.android.sync.model.AndroidResourceModuleRegistry; -import com.google.idea.blaze.android.sync.model.BlazeAndroidSyncData; -import com.google.idea.blaze.base.ideinfo.TargetIdeInfo; -import com.google.idea.blaze.base.model.BlazeLibrary; -import com.google.idea.blaze.base.model.BlazeProjectData; -import com.google.idea.blaze.base.projectview.ProjectViewManager; -import com.google.idea.blaze.base.sync.SyncCache; -import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; -import com.google.idea.blaze.base.sync.libraries.BlazeLibraryCollector; -import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.project.Project; -import java.io.File; -import java.util.Collection; -import java.util.Collections; -import org.jetbrains.annotations.Nullable; - -/** Blaze implementation of {@link AndroidModuleSystem}. */ -public class BlazeModuleSystem extends BlazeModuleSystemBase { - BlazeModuleSystem(Module module) { - super(module); - } - - public Collection getDependentLibraries() { - BlazeProjectData blazeProjectData = - BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); - - if (blazeProjectData == null) { - return ImmutableList.of(); - } - - if (isWorkspaceModule) { - return SyncCache.getInstance(project) - .get(BlazeModuleSystem.class, BlazeModuleSystem::getLibrariesForWorkspaceModule); - } - - AndroidResourceModuleRegistry registry = AndroidResourceModuleRegistry.getInstance(project); - TargetIdeInfo target = blazeProjectData.getTargetMap().get(registry.getTargetKey(module)); - if (target == null) { - // this can happen if the module points to the , - // does not contain any resource - // contains all external resources as module's local resources, so there's - // no dependent libraries - return ImmutableList.of(); - } - - BlazeAndroidSyncData androidSyncData = - blazeProjectData.getSyncState().get(BlazeAndroidSyncData.class); - if (androidSyncData == null) { - return ImmutableList.of(); - } - - ImmutableList.Builder libraries = ImmutableList.builder(); - ArtifactLocationDecoder decoder = blazeProjectData.getArtifactLocationDecoder(); - ExternalLibraryInterner externalLibraryInterner = ExternalLibraryInterner.getInstance(project); - for (String libraryKey : registry.get(module).resourceLibraryKeys) { - ImmutableMap aarLibraries = androidSyncData.importResult.aarLibraries; - ExternalAndroidLibrary externalLibrary = - toExternalLibrary(project, aarLibraries.get(libraryKey), decoder); - if (externalLibrary != null) { - libraries.add(externalLibraryInterner.intern(externalLibrary)); - } - } - return libraries.build(); - } - - private static ImmutableList getLibrariesForWorkspaceModule( - Project project, BlazeProjectData blazeProjectData) { - ArtifactLocationDecoder decoder = blazeProjectData.getArtifactLocationDecoder(); - ExternalLibraryInterner externalLibraryInterner = ExternalLibraryInterner.getInstance(project); - ImmutableList.Builder libraries = ImmutableList.builder(); - for (BlazeLibrary library : - BlazeLibraryCollector.getLibraries( - ProjectViewManager.getInstance(project).getProjectViewSet(), blazeProjectData)) { - if (library instanceof AarLibrary) { - ExternalAndroidLibrary externalLibrary = - toExternalLibrary(project, (AarLibrary) library, decoder); - if (externalLibrary != null) { - libraries.add(externalLibraryInterner.intern(externalLibrary)); - } - } - } - return libraries.build(); - } - - @Nullable - static ExternalAndroidLibrary toExternalLibrary( - Project project, @Nullable AarLibrary library, ArtifactLocationDecoder decoder) { - if (library == null) { - return null; - } - UnpackedAars unpackedAars = UnpackedAars.getInstance(project); - File aarFile = unpackedAars.getAarDir(decoder, library); - if (aarFile == null) { - logger.warn( - String.format( - "Fail to locate AAR file %s. Re-sync the project may solve the problem", - library.aarArtifact)); - return null; - } - File resFolder = unpackedAars.getResourceDirectory(decoder, library); - PathString resFolderPathString = resFolder == null ? null : new PathString(resFolder); - return new ExternalLibraryImpl(library.key.toString()) - .withLocation(new PathString(aarFile)) - .withManifestFile( - resFolderPathString == null - ? null - : resFolderPathString.getParentOrRoot().resolve("AndroidManifest.xml")) - .withResFolder( - resFolderPathString == null - ? null - : new SelectiveResourceFolder(resFolderPathString, null)) - .withSymbolFile( - resFolderPathString == null - ? null - : resFolderPathString.getParentOrRoot().resolve("R.txt")) - .withPackageName(library.resourcePackage); - } - - @Override - public Collection getAndroidLibraryDependencies( - DependencyScopeType dependencyScopeType) { - if (dependencyScopeType == DependencyScopeType.MAIN) { - return getDependentLibraries(); - } else { - return Collections.emptyList(); - } - } -} diff --git a/aswb/sdkcompat/as213/com/google/idea/blaze/android/projectsystem/ExternalLibraryInterner.java b/aswb/sdkcompat/as213/com/google/idea/blaze/android/projectsystem/ExternalLibraryInterner.java deleted file mode 100644 index 428579821e8..00000000000 --- a/aswb/sdkcompat/as213/com/google/idea/blaze/android/projectsystem/ExternalLibraryInterner.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.projectsystem; - -import com.android.projectmodel.ExternalAndroidLibrary; -import com.google.common.collect.Interner; -import com.google.common.collect.Interners; -import com.intellij.openapi.components.ServiceManager; -import com.intellij.openapi.project.Project; - -/** Project Level service to dedup instances of {@link ExternalAndroidLibrary}. */ -public class ExternalLibraryInterner { - private Interner externalLibraryInterner = Interners.newWeakInterner(); - - public static ExternalLibraryInterner getInstance(Project project) { - return ServiceManager.getService(project, ExternalLibraryInterner.class); - } - - public ExternalAndroidLibrary intern(ExternalAndroidLibrary externalLibrary) { - return externalLibraryInterner.intern(externalLibrary); - } -} diff --git a/aswb/sdkcompat/as213/com/google/idea/blaze/android/run/BlazeAndroidDeploymentService.java b/aswb/sdkcompat/as213/com/google/idea/blaze/android/run/BlazeAndroidDeploymentService.java deleted file mode 100644 index 032ec26e8bb..00000000000 --- a/aswb/sdkcompat/as213/com/google/idea/blaze/android/run/BlazeAndroidDeploymentService.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2020 The Bazel Authors. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.android.tools.idea.run.ApkInfo; -import com.android.tools.idea.run.LaunchOptions; -import com.android.tools.idea.run.tasks.DeployTask; -import com.android.tools.idea.run.tasks.DeployTasksCompat; -import com.android.tools.idea.run.tasks.LaunchTask; -import com.intellij.openapi.components.ServiceManager; -import com.intellij.openapi.project.Project; -import java.util.Collection; - -/** A service that provides {@link DeployTask}. */ -public interface BlazeAndroidDeploymentService { - static BlazeAndroidDeploymentService getInstance(Project project) { - return ServiceManager.getService(project, BlazeAndroidDeploymentService.class); - } - - /** Returns a {@link DeployTask} to deploy the given files and launch options. */ - LaunchTask getDeployTask(Collection packages, LaunchOptions launchOptions); - - /** A default implementation that uses {@link DeployTasksCompat#createDeployTask}. */ - class DefaultDeploymentService implements BlazeAndroidDeploymentService { - private final Project project; - - public DefaultDeploymentService(Project project) { - this.project = project; - } - - @Override - public LaunchTask getDeployTask(Collection packages, LaunchOptions launchOptions) { - return DeployTasksCompat.createDeployTask(project, packages, launchOptions); - } - } -} diff --git a/aswb/sdkcompat/as213/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunContext.java b/aswb/sdkcompat/as213/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunContext.java deleted file mode 100644 index 01c6a1a7885..00000000000 --- a/aswb/sdkcompat/as213/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunContext.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2021 The Bazel Authors. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.idea.blaze.android.run.test; - -import static com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryNormalBuildRunContext.getApkInfoToInstall; - -import com.android.ddmlib.IDevice; -import com.android.tools.idea.run.LaunchOptions; -import com.android.tools.idea.run.editor.AndroidDebugger; -import com.android.tools.idea.run.editor.AndroidDebuggerState; -import com.android.tools.idea.run.tasks.ConnectDebuggerTask; -import com.android.tools.idea.run.tasks.LaunchTask; -import com.google.common.collect.ImmutableList; -import com.google.idea.blaze.android.run.BlazeAndroidDeploymentService; -import com.google.idea.blaze.base.model.primitives.Label; -import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration; -import com.intellij.execution.ExecutionException; -import com.intellij.execution.Executor; -import com.intellij.execution.runners.ExecutionEnvironment; -import com.intellij.openapi.project.Project; -import org.jetbrains.android.facet.AndroidFacet; - -/** - * Run context for android_test. - * - *

#api203 - */ -public class BlazeAndroidTestRunContext extends BlazeAndroidTestRunContextBase { - BlazeAndroidTestRunContext( - Project project, - AndroidFacet facet, - BlazeCommandRunConfiguration runConfiguration, - ExecutionEnvironment env, - BlazeAndroidTestRunConfigurationState configState, - Label label, - ImmutableList blazeFlags, - ImmutableList exeFlags, - String launchId) { - super( - project, facet, runConfiguration, env, configState, label, blazeFlags, exeFlags, launchId); - } - - @Override - public ImmutableList getDeployTasks(IDevice device, LaunchOptions launchOptions) - throws ExecutionException { - switch (configState.getLaunchMethod()) { - case NON_BLAZE: - // fall through - case BLAZE_TEST: - return ImmutableList.of( - BlazeAndroidDeploymentService.getInstance(project) - .getDeployTask( - getApkInfoToInstall(device, launchOptions, apkProvider), launchOptions)); - case MOBILE_INSTALL: - return ImmutableList.of(); - } - throw new AssertionError(); - } - - @Override - @SuppressWarnings({"unchecked", "rawtypes"}) // Raw type from upstream. - public ConnectDebuggerTask getDebuggerTask( - AndroidDebugger androidDebugger, AndroidDebuggerState androidDebuggerState) - throws ExecutionException { - switch (configState.getLaunchMethod()) { - case BLAZE_TEST: - return new ConnectBlazeTestDebuggerTask(env.getProject(), applicationIdProvider, this); - case NON_BLAZE: - case MOBILE_INSTALL: - return androidDebugger.getConnectDebuggerTask( - env, applicationIdProvider, facet, androidDebuggerState); - } - throw new AssertionError(); - } - - @Override - public Executor getExecutor() { - return env.getExecutor(); - } -} diff --git a/aswb/sdkcompat/as213/com/google/idea/blaze/android/run/test/ConnectBlazeTestDebuggerTask.java b/aswb/sdkcompat/as213/com/google/idea/blaze/android/run/test/ConnectBlazeTestDebuggerTask.java deleted file mode 100644 index c164398f30a..00000000000 --- a/aswb/sdkcompat/as213/com/google/idea/blaze/android/run/test/ConnectBlazeTestDebuggerTask.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright 2016 The Bazel Authors. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.idea.blaze.android.run.test; - -import com.android.ddmlib.AndroidDebugBridge; -import com.android.ddmlib.Client; -import com.android.ddmlib.ClientData; -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.LaunchInfo; -import com.android.tools.idea.run.ProcessHandlerConsolePrinter; -import com.android.tools.idea.run.tasks.ConnectDebuggerTaskBase; -import com.android.tools.idea.run.tasks.ConnectJavaDebuggerTask; -import com.android.tools.idea.run.util.ProcessHandlerLaunchStatus; -import com.intellij.execution.process.ProcessHandler; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** Connects the blaze debugger during execution. */ -class ConnectBlazeTestDebuggerTask extends ConnectDebuggerTaskBase { - private static final Logger LOG = Logger.getInstance(ConnectBlazeTestDebuggerTask.class); - - private final Project project; - private final ApplicationIdProvider applicationIdProvider; - private final BlazeAndroidTestRunContext runContext; - - public ConnectBlazeTestDebuggerTask( - Project project, - ApplicationIdProvider applicationIdProvider, - BlazeAndroidTestRunContext runContext) { - super(applicationIdProvider, project, true); - this.project = project; - this.applicationIdProvider = applicationIdProvider; - this.runContext = runContext; - } - - @Nullable - @Override - public ProcessHandler perform( - @NotNull LaunchInfo launchInfo, - @NotNull IDevice device, - @NotNull ProcessHandlerLaunchStatus state, - @NotNull ProcessHandlerConsolePrinter printer) { - try { - String packageName = applicationIdProvider.getPackageName(); - setUpForReattachingDebugger(packageName, launchInfo, state, printer); - } catch (ApkProvisionException e) { - LOG.error(e); - } - - // The return value for this task is not used - return null; - } - - /** - * Wires up listeners to automatically reconnect the debugger for each test method. When you - * `blaze test` an android_test in debug mode, it kills the instrumentation process between each - * test method, disconnecting the debugger. We listen for the start of a new method waiting for a - * debugger, and reconnect. TODO: Support stopping Blaze from the UI. This is hard because we have - * no way to distinguish process handler termination/debug session ending initiated by the user. - */ - private void setUpForReattachingDebugger( - String targetPackage, - LaunchInfo launchInfo, - ProcessHandlerLaunchStatus launchStatus, - ProcessHandlerConsolePrinter printer) { - final AndroidDebugBridge.IClientChangeListener reattachingListener = - new AndroidDebugBridge.IClientChangeListener() { - // The target application can either - // 1. Match our target name, and become available for debugging. - // 2. Be available for debugging, and suddenly have its name changed to match. - static final int CHANGE_MASK = Client.CHANGE_DEBUGGER_STATUS | Client.CHANGE_NAME; - - @Override - public void clientChanged(@NotNull Client client, int changeMask) { - ClientData data = client.getClientData(); - String clientDescription = data.getClientDescription(); - if (clientDescription != null - && clientDescription.equals(targetPackage) - && (changeMask & CHANGE_MASK) != 0 - && data.getDebuggerConnectionStatus().equals(ClientData.DebuggerStatus.WAITING)) { - reattachDebugger(launchInfo, client, launchStatus, printer); - } - } - }; - - AndroidDebugBridge.addClientChangeListener(reattachingListener); - runContext.addLaunchTaskCompleteListener( - () -> { - AndroidDebugBridge.removeClientChangeListener(reattachingListener); - launchStatus.terminateLaunch("Test run completed.\n", true); - }); - } - - private void reattachDebugger( - LaunchInfo launchInfo, - final Client client, - ProcessHandlerLaunchStatus launchStatus, - ProcessHandlerConsolePrinter printer) { - ApplicationManager.getApplication() - .invokeLater(() -> launchDebugger(launchInfo, client, launchStatus, printer)); - } - - /** - * Nearly a clone of {@link ConnectJavaDebuggerTask#launchDebugger}. There are a few changes to - * account for null variables that could occur in our implementation. - */ - @Override - public ProcessHandler launchDebugger( - @NotNull LaunchInfo currentLaunchInfo, - @NotNull Client client, - @NotNull ProcessHandlerLaunchStatus launchStatus, - @NotNull ProcessHandlerConsolePrinter printer) { - return ConnectBlazeTestDebuggerTaskHelper.launchDebugger( - project, currentLaunchInfo, client, launchStatus, printer); - } -} diff --git a/aswb/sdkcompat/as223/com/android/tools/configurations/ConfigurationCompat.java b/aswb/sdkcompat/as223/com/android/tools/configurations/ConfigurationCompat.java new file mode 100644 index 00000000000..f4c78781600 --- /dev/null +++ b/aswb/sdkcompat/as223/com/android/tools/configurations/ConfigurationCompat.java @@ -0,0 +1,33 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR 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.configurations; + +import com.android.sdklib.devices.Device; +import com.android.tools.idea.configurations.Configuration; + +/** Compat class for Configuration. */ +public class ConfigurationCompat { + + private Configuration configuration; + + public ConfigurationCompat(Configuration configuration) { + this.configuration = configuration; + } + + public void setDevice(Device device, boolean preserveState) { + configuration.setDevice(device, preserveState); + } +} diff --git a/aswb/sdkcompat/as212/com/android/tools/idea/model/AndroidManifestIndexCompat.java b/aswb/sdkcompat/as223/com/android/tools/idea/model/AndroidManifestIndexCompat.java similarity index 100% rename from aswb/sdkcompat/as212/com/android/tools/idea/model/AndroidManifestIndexCompat.java rename to aswb/sdkcompat/as223/com/android/tools/idea/model/AndroidManifestIndexCompat.java diff --git a/aswb/sdkcompat/as212/com/android/tools/idea/progress/StudioLoggerProgressIndicatorCompat.java b/aswb/sdkcompat/as223/com/android/tools/idea/progress/StudioLoggerProgressIndicatorCompat.java similarity index 100% rename from aswb/sdkcompat/as212/com/android/tools/idea/progress/StudioLoggerProgressIndicatorCompat.java rename to aswb/sdkcompat/as223/com/android/tools/idea/progress/StudioLoggerProgressIndicatorCompat.java diff --git a/aswb/sdkcompat/as223/com/android/tools/idea/rendering/RenderErrorContributorCompat.java b/aswb/sdkcompat/as223/com/android/tools/idea/rendering/RenderErrorContributorCompat.java new file mode 100644 index 00000000000..cb3193e1072 --- /dev/null +++ b/aswb/sdkcompat/as223/com/android/tools/idea/rendering/RenderErrorContributorCompat.java @@ -0,0 +1,29 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR 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.rendering; + +import com.android.tools.idea.ui.designer.EditorDesignSurface; +import com.android.tools.rendering.RenderResultCompat; +import com.intellij.openapi.actionSystem.DataContext; +import org.jetbrains.annotations.Nullable; + +/** Contribute blaze specific render errors. */ +public class RenderErrorContributorCompat extends RenderErrorContributor { + public RenderErrorContributorCompat( + EditorDesignSurface surface, RenderResultCompat result, @Nullable DataContext dataContext) { + super(surface, result.get(), dataContext); + } +} diff --git a/aswb/sdkcompat/as223/com/android/tools/idea/run/tasks/DeployTasksCompat.java b/aswb/sdkcompat/as223/com/android/tools/idea/run/tasks/DeployTasksCompat.java new file mode 100644 index 00000000000..500a6786340 --- /dev/null +++ b/aswb/sdkcompat/as223/com/android/tools/idea/run/tasks/DeployTasksCompat.java @@ -0,0 +1,46 @@ +/* + * Copyright 2020 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR 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.run.tasks; + +import com.android.tools.idea.run.ApkInfo; +import com.android.tools.idea.run.LaunchOptions; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; +import com.android.tools.idea.run.blaze.BlazeLaunchTaskWrapper; +import com.google.idea.common.experiments.BoolExperiment; +import com.intellij.openapi.project.Project; +import java.util.Collection; + +/** Compat class for {@link DeployTask} */ +public class DeployTasksCompat { + private static final BoolExperiment updateCodeViaJvmti = + new BoolExperiment("android.apply.changes", false); + + private DeployTasksCompat() {} + + public static BlazeLaunchTask createDeployTask( + Project project, Collection packages, LaunchOptions launchOptions) { + // We don't have a device information, fallback to the most conservative + // install option. + return new BlazeLaunchTaskWrapper( + new DeployTask( + project, + packages, + launchOptions.getPmInstallOptions(/* device= */ null), + launchOptions.getInstallOnAllUsers(), + launchOptions.getAlwaysInstallWithPm())); + } +} + diff --git a/aswb/sdkcompat/as223/com/android/tools/rendering/HtmlLinkManagerCompat.java b/aswb/sdkcompat/as223/com/android/tools/rendering/HtmlLinkManagerCompat.java new file mode 100644 index 00000000000..594132f69af --- /dev/null +++ b/aswb/sdkcompat/as223/com/android/tools/rendering/HtmlLinkManagerCompat.java @@ -0,0 +1,29 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR 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.rendering; + +import com.android.tools.idea.rendering.HtmlLinkManager; +import java.io.File; +import org.jetbrains.annotations.NotNull; + +/** Compat class for {@link HtmlLinkManager} */ +public final class HtmlLinkManagerCompat { + public static String createFilePositionUrl(@NotNull File file, int line, int column) { + return HtmlLinkManager.createFilePositionUrl(file, line, column); + } + + private HtmlLinkManagerCompat() {} +} diff --git a/aswb/sdkcompat/as223/com/android/tools/rendering/RenderLoggerCompat.java b/aswb/sdkcompat/as223/com/android/tools/rendering/RenderLoggerCompat.java new file mode 100644 index 00000000000..e1a83953327 --- /dev/null +++ b/aswb/sdkcompat/as223/com/android/tools/rendering/RenderLoggerCompat.java @@ -0,0 +1,45 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR 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.rendering; + +import com.android.tools.idea.rendering.RenderLogger; +import java.util.Map; +import java.util.Set; + +/** Compat class for RenderLogger. */ +public class RenderLoggerCompat { + private final RenderLogger renderLogger; + + public RenderLoggerCompat(RenderResultCompat result) { + renderLogger = result.getLogger(); + } + + public boolean hasErrors() { + return renderLogger.hasErrors(); + } + + public Map getBrokenClasses() { + return renderLogger.getBrokenClasses(); + } + + public Set getMissingClasses() { + return renderLogger.getMissingClasses(); + } + + public static void resetFidelityErrorsFilters() { + RenderLogger.resetFidelityErrorsFilters(); + } +} diff --git a/aswb/sdkcompat/as223/com/android/tools/rendering/RenderResultCompat.java b/aswb/sdkcompat/as223/com/android/tools/rendering/RenderResultCompat.java new file mode 100644 index 00000000000..8892c2fe813 --- /dev/null +++ b/aswb/sdkcompat/as223/com/android/tools/rendering/RenderResultCompat.java @@ -0,0 +1,78 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR 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.rendering; + +import com.android.tools.idea.rendering.RenderErrorContributor; +import com.android.tools.idea.rendering.RenderErrorModelFactory; +import com.android.tools.idea.rendering.RenderLogger; +import com.android.tools.idea.rendering.RenderResult; +import com.android.tools.idea.rendering.RenderResults; +import com.android.tools.idea.rendering.errors.ui.RenderErrorModel; +import com.android.tools.idea.ui.designer.EditorDesignSurface; +import com.google.idea.blaze.android.rendering.BlazeRenderErrorContributor; +import com.google.idea.blaze.base.settings.Blaze; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiFile; +import org.jetbrains.annotations.Nullable; + +/** Compat class for {@link RenderResult} */ +public final class RenderResultCompat { + private RenderResult result; + + public static RenderResultCompat createBlank(PsiFile file) { + return new RenderResultCompat(RenderResults.createBlank(file)); + } + + public RenderErrorModel createErrorModel() { + return RenderErrorModelFactory.createErrorModel(null, result, null); + } + + public RenderResult get() { + return result; + } + + public RenderLogger getLogger() { + return result.getLogger(); + } + + public Module getModule() { + return result.getModule(); + } + + public RenderResultCompat(RenderResult result) { + this.result = result; + } + + private RenderResultCompat() {} + + /** Extension to provide {@link BlazeRenderErrorContributor}. */ + public static class BlazeProvider extends RenderErrorContributor.Provider { + @Override + public boolean isApplicable(Project project) { + return Blaze.isBlazeProject(project); + } + + @Override + public RenderErrorContributor getContributor( + @Nullable EditorDesignSurface surface, + RenderResult result, + @Nullable DataContext dataContext) { + return new BlazeRenderErrorContributor(surface, new RenderResultCompat(result), dataContext); + } + } +} diff --git a/aswb/sdkcompat/as223/com/android/tools/rendering/RenderServiceCompat.java b/aswb/sdkcompat/as223/com/android/tools/rendering/RenderServiceCompat.java new file mode 100644 index 00000000000..4802d5ebf45 --- /dev/null +++ b/aswb/sdkcompat/as223/com/android/tools/rendering/RenderServiceCompat.java @@ -0,0 +1,31 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR 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.rendering; + +import com.android.tools.idea.rendering.RenderService; + +/** Compat class for {@link RenderService} */ +public final class RenderServiceCompat { + public static void shutdownRenderExecutor(long l) { + RenderService.shutdownRenderExecutor(l); + } + + public static void initializeRenderExecutor() { + RenderService.initializeRenderExecutor(); + } + + private RenderServiceCompat() {} +} diff --git a/aswb/sdkcompat/as213/com/google/idea/blaze/android/cppimpl/debug/BlazeAutoAndroidDebugger.java b/aswb/sdkcompat/as223/com/google/idea/blaze/android/cppimpl/debug/BlazeAutoAndroidDebugger.java similarity index 66% rename from aswb/sdkcompat/as213/com/google/idea/blaze/android/cppimpl/debug/BlazeAutoAndroidDebugger.java rename to aswb/sdkcompat/as223/com/google/idea/blaze/android/cppimpl/debug/BlazeAutoAndroidDebugger.java index 9aa08f911e9..7aa210d0595 100644 --- a/aswb/sdkcompat/as213/com/google/idea/blaze/android/cppimpl/debug/BlazeAutoAndroidDebugger.java +++ b/aswb/sdkcompat/as223/com/google/idea/blaze/android/cppimpl/debug/BlazeAutoAndroidDebugger.java @@ -17,17 +17,26 @@ import com.android.ddmlib.Client; import com.android.tools.ndk.run.editor.AutoAndroidDebuggerState; +import com.intellij.execution.ExecutionException; +import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; +import com.intellij.xdebugger.XDebugSession; /** Shim for #api212 compat. */ public class BlazeAutoAndroidDebugger extends BlazeAutoAndroidDebuggerBase { @Override - public void attachToClient(Project project, Client client, AutoAndroidDebuggerState state) { + public XDebugSession attachToClient( + Project project, Client client, AutoAndroidDebuggerState state) throws ExecutionException { if (isNativeProject(project)) { log.info("Project has native development enabled. Attaching native debugger."); - nativeDebugger.attachToClient(project, client, state); + return nativeDebugger.attachToClient(project, client, state); } else { - super.attachToClient(project, client, state); + return super.attachToClient(project, client, state); } } + + @Override + protected boolean isNativeDeployment(Project project, Module debuggeeModule) { + return isNativeProject(project); + } } diff --git a/aswb/sdkcompat/as223/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeAndroidDebugger.java b/aswb/sdkcompat/as223/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeAndroidDebugger.java new file mode 100644 index 00000000000..e92afddc27c --- /dev/null +++ b/aswb/sdkcompat/as223/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeAndroidDebugger.java @@ -0,0 +1,28 @@ +/* + * Copyright 2018 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.cppimpl.debug; + +/** + * API compat of {@link BlazeNativeAndroidDebuggerBase} with the following additions: + * + *

    + *
  • Creates a run-config setting using {@link BlazeAndroidNativeAttachConfiguration} instead of + * {@link AndroidNativeAttachConfiguration} to override counterproductive validations. + *
+ * + * #api4.0 + */ +public class BlazeNativeAndroidDebugger extends BlazeNativeAndroidDebuggerBase {} diff --git a/aswb/sdkcompat/as223/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeAndroidDebuggerBase.java b/aswb/sdkcompat/as223/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeAndroidDebuggerBase.java new file mode 100644 index 00000000000..911d90b7711 --- /dev/null +++ b/aswb/sdkcompat/as223/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeAndroidDebuggerBase.java @@ -0,0 +1,59 @@ +/* + * Copyright 2020 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.cppimpl.debug; + +import com.android.tools.ndk.run.editor.NativeAndroidDebugger; +import com.google.idea.blaze.base.model.BlazeProjectData; +import com.google.idea.blaze.base.model.primitives.LanguageClass; +import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; +import com.intellij.openapi.project.Project; + +/** + * Extension of {@link NativeAndroidDebugger} with the following key differences compared to {@link + * NativeAndroidDebugger}. + * + *
    + *
  • Overrides {@link #supportsProject} so native debugger is only enabled for native support is + * enabled. + *
+ */ +public class BlazeNativeAndroidDebuggerBase extends NativeAndroidDebugger { + /** + * This ID needs to be lexicographically larger than "Java" so it come after the "Java" debugger + * when sorted lexicographically in the "Attach Debugger to Android Process" dialog. See {@link + * org.jetbrains.android.actions.AndroidProcessChooserDialog#populateDebuggerTypeCombo}. + */ + public static final String ID = "Native" + Blaze.defaultBuildSystemName(); + + @Override + public String getId() { + return ID; + } + + @Override + public String getDisplayName() { + return "Native Only"; + } + + @Override + public boolean supportsProject(Project project) { + BlazeProjectData blazeProjectData = + BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); + return blazeProjectData != null + && blazeProjectData.getWorkspaceLanguageSettings().isLanguageActive(LanguageClass.C); + } +} diff --git a/aswb/sdkcompat/as223/com/google/idea/blaze/android/projectsystem/BlazeModuleSystem.java b/aswb/sdkcompat/as223/com/google/idea/blaze/android/projectsystem/BlazeModuleSystem.java new file mode 100644 index 00000000000..162fd8f1793 --- /dev/null +++ b/aswb/sdkcompat/as223/com/google/idea/blaze/android/projectsystem/BlazeModuleSystem.java @@ -0,0 +1,27 @@ +/* + * Copyright 2020 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.projectsystem; + +import com.android.tools.idea.projectsystem.AndroidModuleSystem; +import com.intellij.openapi.module.Module; + +/** Blaze implementation of {@link AndroidModuleSystem}. */ +public class BlazeModuleSystem extends BlazeModuleSystemBase { + + BlazeModuleSystem(Module module) { + super(module); + } +} diff --git a/aswb/src/com/google/idea/blaze/android/projectsystem/BlazeProjectSystem.java b/aswb/sdkcompat/as223/com/google/idea/blaze/android/projectsystem/BlazeProjectSystem.java similarity index 75% rename from aswb/src/com/google/idea/blaze/android/projectsystem/BlazeProjectSystem.java rename to aswb/sdkcompat/as223/com/google/idea/blaze/android/projectsystem/BlazeProjectSystem.java index 036bdc41e3a..4aa6e3b854d 100755 --- a/aswb/src/com/google/idea/blaze/android/projectsystem/BlazeProjectSystem.java +++ b/aswb/sdkcompat/as223/com/google/idea/blaze/android/projectsystem/BlazeProjectSystem.java @@ -15,6 +15,7 @@ */ package com.google.idea.blaze.android.projectsystem; +import static com.android.tools.idea.projectsystem.SourceProvidersKt.emptySourceProvider; import static org.jetbrains.android.facet.SourceProviderUtil.createSourceProvidersForLegacyModule; import com.android.tools.apk.analyzer.AaptInvoker; @@ -26,8 +27,11 @@ import com.android.tools.idea.projectsystem.NamedIdeaSourceProvider; import com.android.tools.idea.projectsystem.ProjectSystemBuildManager; import com.android.tools.idea.projectsystem.ProjectSystemSyncManager; +import com.android.tools.idea.projectsystem.ScopeType; +import com.android.tools.idea.projectsystem.SourceProviderManager; import com.android.tools.idea.projectsystem.SourceProviders; import com.android.tools.idea.projectsystem.SourceProvidersFactory; +import com.android.tools.idea.projectsystem.SourceProvidersImpl; import com.android.tools.idea.res.AndroidInnerClassFinder; import com.android.tools.idea.res.AndroidResourceClassPsiElementFinder; import com.android.tools.idea.sdk.AndroidSdks; @@ -35,7 +39,8 @@ import com.google.idea.blaze.android.resources.BlazeLightResourceClassService; import com.google.idea.blaze.android.sync.model.idea.BlazeAndroidModel; import com.google.idea.blaze.android.sync.model.idea.BlazeClassJarProvider; -import com.google.idea.blaze.base.build.BlazeBuildService; +import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.settings.BlazeImportSettings.ProjectType; import com.intellij.facet.ProjectFacetManager; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; @@ -49,8 +54,7 @@ import java.util.stream.Collectors; import javax.annotation.Nullable; import org.jetbrains.android.facet.AndroidFacet; -import org.jetbrains.android.facet.SourceProviderManager; -import org.jetbrains.android.facet.SourceProvidersImpl; +import org.jetbrains.annotations.NotNull; /** * Base class to implement common methods in {@link AndroidProjectSystem} for blaze with different @@ -73,6 +77,10 @@ public BlazeProjectSystem(Project project) { new AndroidResourceClassPsiElementFinder(getLightResourceClassService())); } + public Project getProject() { + return project; + } + @Override public boolean allowsFileCreation() { return true; @@ -91,12 +99,7 @@ public Path getPathToAapt() { new LogWrapper(BlazeProjectSystem.class)); } - // @Override #api42 - public void buildProject() { - BlazeBuildService.getInstance(project).buildProject(); - } - - // @Override #api42 + @Override public ProjectSystemBuildManager getBuildManager() { return buildManager; } @@ -136,18 +139,38 @@ public SourceProviders createSourceProvidersFor(AndroidFacet facet) { private SourceProviders createForModel(BlazeAndroidModel model) { NamedIdeaSourceProvider mainSourceProvider = model.getDefaultSourceProvider(); - return new SourceProvidersImpl( - mainSourceProvider, - ImmutableList.of(mainSourceProvider), - ImmutableList.of(mainSourceProvider), - ImmutableList.of(mainSourceProvider), - ImmutableList.of(mainSourceProvider), - ImmutableList.of(mainSourceProvider)); + if (Blaze.getProjectType(project).equals(ProjectType.QUERY_SYNC)) { + return new SourceProvidersImpl( + mainSourceProvider, + ImmutableList.of(mainSourceProvider), + ImmutableList.of(), + ImmutableList.of(), + ImmutableList.of(), + ImmutableList.of(mainSourceProvider), + ImmutableList.of(mainSourceProvider), + emptySourceProvider(ScopeType.MAIN), + emptySourceProvider(ScopeType.UNIT_TEST), + emptySourceProvider(ScopeType.ANDROID_TEST), + emptySourceProvider(ScopeType.TEST_FIXTURES)); + } else { + return new SourceProvidersImpl( + mainSourceProvider, + ImmutableList.of(mainSourceProvider), + ImmutableList.of(mainSourceProvider), + ImmutableList.of(mainSourceProvider), + ImmutableList.of(mainSourceProvider), + ImmutableList.of(mainSourceProvider), + ImmutableList.of(mainSourceProvider), + emptySourceProvider(ScopeType.MAIN), + emptySourceProvider(ScopeType.UNIT_TEST), + emptySourceProvider(ScopeType.ANDROID_TEST), + emptySourceProvider(ScopeType.TEST_FIXTURES)); + } } }; } - // @Override #api212 + @Override public ClassJarProvider getClassJarProvider() { return new BlazeClassJarProvider(project); } @@ -159,8 +182,7 @@ public Collection getAndroidFacetsWithPackageName( project, packageName, GlobalSearchScope.projectScope(project)); } - // @Override #api203 - public Collection getAndroidFacetsWithPackageName( + private Collection getAndroidFacetsWithPackageName( Project project, String packageName, GlobalSearchScope scope) { List facets = ProjectFacetManager.getInstance(project).getFacets(AndroidFacet.ID); return facets.stream() @@ -182,6 +204,12 @@ public Collection getSubmodules() { return ImmutableList.of(); } + @Override + public Collection getBootClasspath(@NotNull Module module) { + // TODO: b/266586669 + return ImmutableList.of(); + } + private static boolean hasPackageName(AndroidFacet facet, String packageName) { String nameFromFacet = PackageNameUtils.getPackageName(facet.getModule()); if (nameFromFacet == null) { diff --git a/aswb/src/com/google/idea/blaze/android/projectsystem/RenderJarClassFileFinder.java b/aswb/sdkcompat/as223/com/google/idea/blaze/android/projectsystem/RenderJarClassFileFinder.java similarity index 88% rename from aswb/src/com/google/idea/blaze/android/projectsystem/RenderJarClassFileFinder.java rename to aswb/sdkcompat/as223/com/google/idea/blaze/android/projectsystem/RenderJarClassFileFinder.java index 8c1a383a496..81504173f22 100644 --- a/aswb/src/com/google/idea/blaze/android/projectsystem/RenderJarClassFileFinder.java +++ b/aswb/sdkcompat/as223/com/google/idea/blaze/android/projectsystem/RenderJarClassFileFinder.java @@ -30,6 +30,11 @@ import com.google.idea.blaze.base.ideinfo.TargetKey; import com.google.idea.blaze.base.io.VirtualFileSystemProvider; import com.google.idea.blaze.base.model.BlazeProjectData; +import com.google.idea.blaze.base.qsync.QuerySync; +import com.google.idea.blaze.base.qsync.QuerySyncManager; +import com.google.idea.blaze.base.qsync.RenderJarArtifactTracker; +import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.settings.BlazeImportSettings.ProjectType; import com.google.idea.blaze.base.sync.BlazeSyncModificationTracker; import com.google.idea.blaze.base.sync.data.BlazeDataStorage; import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; @@ -136,6 +141,28 @@ public VirtualFile findClass(String fqcn) { return null; } + if (Blaze.getProjectType(project).equals(ProjectType.QUERY_SYNC)) { + if (QuerySync.isComposeEnabled(project)) { + RenderJarArtifactTracker renderJarArtifactTracker = + QuerySyncManager.getInstance(project).getRenderJarArtifactTracker(); + // TODO(b/283280194): Setup fqcn -> target and target -> Render jar mappings to avoid + // iterating over all render jars when trying to locate class for fqcn. + // TODO(b/284002836): Collect metrics on time taken to iterate over the jars + for (File renderJar : renderJarArtifactTracker.getRenderJars()) { + VirtualFile renderResolveJarVf = + VirtualFileSystemProvider.getInstance().getSystem().findFileByIoFile(renderJar); + if (renderResolveJarVf != null) { + return findClassInJar(renderResolveJarVf, fqcn); + } + log.warn(String.format("Could not find class `%1$s` with Query Sync", fqcn)); + return null; + } + } else { + // Disable this class for Query Sync if Compose is not enabled + return null; + } + } + BlazeProjectData projectData = BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); if (projectData == null) { diff --git a/aswb/src/com/google/idea/blaze/android/rendering/BlazeRenderErrorContributor.java b/aswb/sdkcompat/as223/com/google/idea/blaze/android/rendering/BlazeRenderErrorContributor.java similarity index 91% rename from aswb/src/com/google/idea/blaze/android/rendering/BlazeRenderErrorContributor.java rename to aswb/sdkcompat/as223/com/google/idea/blaze/android/rendering/BlazeRenderErrorContributor.java index e45d473a4f2..827e5975a35 100644 --- a/aswb/src/com/google/idea/blaze/android/rendering/BlazeRenderErrorContributor.java +++ b/aswb/sdkcompat/as223/com/google/idea/blaze/android/rendering/BlazeRenderErrorContributor.java @@ -16,19 +16,21 @@ package com.google.idea.blaze.android.rendering; import static com.android.SdkConstants.ANDROID_MANIFEST_XML; +import static com.google.common.collect.ImmutableMap.toImmutableMap; -import com.android.tools.idea.rendering.HtmlLinkManager; -import com.android.tools.idea.rendering.RenderErrorContributor; -import com.android.tools.idea.rendering.RenderLogger; -import com.android.tools.idea.rendering.RenderResult; +import com.android.tools.idea.rendering.RenderErrorContributorCompat; import com.android.tools.idea.rendering.errors.ui.RenderErrorModel; import com.android.tools.idea.ui.designer.EditorDesignSurface; +import com.android.tools.rendering.HtmlLinkManagerCompat; +import com.android.tools.rendering.RenderLoggerCompat; +import com.android.tools.rendering.RenderResultCompat; import com.android.utils.HtmlBuilder; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.Maps; import com.google.common.collect.SortedSetMultimap; import com.google.common.collect.TreeMultimap; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.idea.blaze.android.sync.model.AndroidResourceModule; import com.google.idea.blaze.android.sync.model.AndroidResourceModuleRegistry; import com.google.idea.blaze.base.command.buildresult.OutputArtifactResolver; @@ -40,6 +42,7 @@ import com.google.idea.blaze.base.lang.buildfile.references.BuildReferenceManager; import com.google.idea.blaze.base.model.BlazeProjectData; import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.settings.BlazeImportSettings.ProjectType; import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder; import com.google.idea.blaze.base.targetmaps.SourceToTargetMap; @@ -62,21 +65,21 @@ import java.util.Collection; import java.util.Collections; import java.util.Map; +import java.util.Set; import java.util.SortedMap; import java.util.function.Function; -import java.util.stream.Collectors; import org.jetbrains.annotations.Nullable; /** Contribute blaze specific render errors. */ -public class BlazeRenderErrorContributor extends RenderErrorContributor { - private RenderLogger logger; - private Module module; - private Project project; +public class BlazeRenderErrorContributor extends RenderErrorContributorCompat { + private final RenderLoggerCompat logger; + private final Module module; + private final Project project; public BlazeRenderErrorContributor( - EditorDesignSurface surface, RenderResult result, @Nullable DataContext dataContext) { + EditorDesignSurface surface, RenderResultCompat result, @Nullable DataContext dataContext) { super(surface, result, dataContext); - logger = result.getLogger(); + logger = new RenderLoggerCompat(result); module = result.getModule(); project = module.getProject(); } @@ -90,6 +93,11 @@ public Collection reportIssues() { return getIssues(); } + if (Blaze.getProjectType(project).equals(ProjectType.QUERY_SYNC)) { + // TODO(b/284002829): Setup resource-module specific issue reporting + return getIssues(); + } + TargetMap targetMap = blazeProjectData.getTargetMap(); ArtifactLocationDecoder decoder = blazeProjectData.getArtifactLocationDecoder(); AndroidResourceModule resourceModule = @@ -162,7 +170,7 @@ private static SortedMap getGeneratedResources( target.getAndroidIdeInfo().getResources().stream() .map(AndroidResFolder::getRoot) .filter(ArtifactLocation::isGenerated) - .collect(Collectors.toMap(Function.identity(), resource -> target))); + .collect(toImmutableMap(Function.identity(), resource -> target))); return generatedResources; } @@ -195,7 +203,7 @@ private void reportNonStandardAndroidManifestName( HtmlBuilder builder = new HtmlBuilder(); addTargetLink(builder, target, decoder) .add(" uses a non-standard name for the Android manifest: "); - String linkToManifest = HtmlLinkManager.createFilePositionUrl(manifest, -1, 0); + String linkToManifest = HtmlLinkManagerCompat.createFilePositionUrl(manifest, -1, 0); if (linkToManifest != null) { builder.addLink(manifest.getName(), linkToManifest); } else { @@ -220,9 +228,10 @@ private void reportNonStandardAndroidManifestName( * android_binary, so we could end up with resources that ultimately build correctly, but fail to * find their class dependencies during rendering in the layout editor. */ + @SuppressWarnings("rawtypes") private void reportResourceTargetShouldDependOnClassTarget( TargetIdeInfo target, TargetMap targetMap, ArtifactLocationDecoder decoder) { - Collection missingClasses = logger.getMissingClasses(); + Set missingClasses = logger.getMissingClasses(); if (missingClasses == null || missingClasses.isEmpty()) { return; } @@ -307,6 +316,7 @@ private File getSourceFileForClass(String className) { }); } + @CanIgnoreReturnValue private HtmlBuilder addTargetLink( HtmlBuilder builder, TargetIdeInfo target, ArtifactLocationDecoder decoder) { File buildFile = @@ -332,26 +342,10 @@ private HtmlBuilder addTargetLink( return StringUtil.offsetToLineNumber( psiFile.getText(), buildTargetPsi.getTextOffset()); }); - String url = HtmlLinkManager.createFilePositionUrl(buildFile, line, 0); + String url = HtmlLinkManagerCompat.createFilePositionUrl(buildFile, line, 0); if (url != null) { return builder.addLink(target.toString(), url); } return builder.add(target.toString()); } - - /** Extension to provide {@link BlazeRenderErrorContributor}. */ - public static class BlazeProvider extends Provider { - @Override - public boolean isApplicable(Project project) { - return Blaze.isBlazeProject(project); - } - - @Override - public RenderErrorContributor getContributor( - @Nullable EditorDesignSurface surface, - RenderResult result, - @Nullable DataContext dataContext) { - return new BlazeRenderErrorContributor(surface, result, dataContext); - } - } } diff --git a/aswb/src/com/google/idea/blaze/android/rendering/BlazeRenderSecurityManagerOverrides.java b/aswb/sdkcompat/as223/com/google/idea/blaze/android/rendering/BlazeRenderSecurityManagerOverrides.java similarity index 100% rename from aswb/src/com/google/idea/blaze/android/rendering/BlazeRenderSecurityManagerOverrides.java rename to aswb/sdkcompat/as223/com/google/idea/blaze/android/rendering/BlazeRenderSecurityManagerOverrides.java diff --git a/aswb/sdkcompat/as223/com/google/idea/blaze/android/resources/BlazeLightResourceClassServiceBase.java b/aswb/sdkcompat/as223/com/google/idea/blaze/android/resources/BlazeLightResourceClassServiceBase.java new file mode 100644 index 00000000000..21516076cc2 --- /dev/null +++ b/aswb/sdkcompat/as223/com/google/idea/blaze/android/resources/BlazeLightResourceClassServiceBase.java @@ -0,0 +1,66 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.resources; + +import com.android.tools.idea.projectsystem.LightResourceClassService; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.google.idea.blaze.base.sync.data.BlazeDataStorage; +import com.google.idea.common.experiments.FeatureRolloutExperiment; +import com.intellij.openapi.module.Module; +import com.intellij.psi.PsiClass; +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +/** Implementation of {@link LightResourceClassService} set up at Blaze sync time. */ +public abstract class BlazeLightResourceClassServiceBase implements LightResourceClassService { + + @VisibleForTesting + public static final FeatureRolloutExperiment workspaceResourcesFeature = + new FeatureRolloutExperiment("aswb.workspace.light.class.enabled"); + + Map rClasses = Maps.newHashMap(); + Map rClassesByModule = Maps.newHashMap(); + final Set allRClasses = Sets.newHashSet(); + + @Override + public Collection getLightRClassesAccessibleFromModule( + Module module, boolean includeTest) { + if (workspaceResourcesFeature.isEnabled() + && module.getName().equals(BlazeDataStorage.WORKSPACE_MODULE_NAME)) { + // Returns all the packages in resource modules, and all the workspace packages that + // have previously been asked for. All `res/` directories in our project should belong to a + // resource module. For java sources, IntelliJ will ask for explicit resource package by + // calling `getLightRClasses` at which point we can create the package. This is not completely + // correct and the autocomplete will be slightly off when initial `R` is typed in the editor, + // but this workaround is being used to mitigate issues (b/136685602) while resources + // are re-worked. + return allRClasses; + } else { + return rClasses.values(); + } + } + + @Override + public Collection getLightRClassesDefinedByModule( + Module module, boolean includeTestClasses) { + BlazeRClass rClass = rClassesByModule.get(module); + return rClass == null ? ImmutableSet.of() : ImmutableSet.of(rClass); + } +} diff --git a/aswb/sdkcompat/as212/com/google/idea/blaze/android/resources/BlazeRClass.java b/aswb/sdkcompat/as223/com/google/idea/blaze/android/resources/BlazeRClass.java similarity index 89% rename from aswb/sdkcompat/as212/com/google/idea/blaze/android/resources/BlazeRClass.java rename to aswb/sdkcompat/as223/com/google/idea/blaze/android/resources/BlazeRClass.java index 2c123e0a66f..5a8cbf8e450 100644 --- a/aswb/sdkcompat/as212/com/google/idea/blaze/android/resources/BlazeRClass.java +++ b/aswb/sdkcompat/as223/com/google/idea/blaze/android/resources/BlazeRClass.java @@ -20,8 +20,8 @@ import com.android.ide.common.rendering.api.ResourceNamespace; import com.android.tools.idea.res.LocalResourceRepository; -import com.android.tools.idea.res.ResourceRepositoryManager; import com.android.tools.idea.res.ResourceRepositoryRClass; +import com.android.tools.idea.res.StudioResourceRepositoryManager; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModulePointerManager; import com.intellij.openapi.vfs.VirtualFile; @@ -43,19 +43,19 @@ public String getPackageName() { return packageName; } - // @Override #api4.1 + @Override public Transitivity getTransitivity() { return Transitivity.TRANSITIVE; } @Override - public ResourceRepositoryManager getResourceRepositoryManager() { - return ResourceRepositoryManager.getInstance(androidFacet); + public StudioResourceRepositoryManager getResourceRepositoryManager() { + return StudioResourceRepositoryManager.getInstance(androidFacet); } @Override public LocalResourceRepository getResourceRepository() { - return ResourceRepositoryManager.getAppResources(androidFacet); + return StudioResourceRepositoryManager.getAppResources(androidFacet); } @Override diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryApplicationLaunchTaskProvider.java b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryApplicationLaunchTaskProvider.java similarity index 78% rename from aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryApplicationLaunchTaskProvider.java rename to aswb/sdkcompat/as223/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryApplicationLaunchTaskProvider.java index abfe50ccd8d..8a74f3d458f 100644 --- a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryApplicationLaunchTaskProvider.java +++ b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryApplicationLaunchTaskProvider.java @@ -18,12 +18,13 @@ import com.android.tools.idea.run.ApkProvisionException; import com.android.tools.idea.run.ApplicationIdProvider; import com.android.tools.idea.run.activity.StartActivityFlagsProvider; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; +import com.android.tools.idea.run.blaze.BlazeLaunchTaskWrapper; import com.android.tools.idea.run.tasks.AndroidDeepLinkLaunchTask; import com.android.tools.idea.run.tasks.DefaultActivityLaunchTask; -import com.android.tools.idea.run.tasks.LaunchTask; import com.android.tools.idea.run.tasks.SpecificActivityLaunchTask; -import com.android.tools.idea.run.util.LaunchStatus; import com.google.idea.blaze.android.manifest.ManifestParser; +import com.intellij.execution.ExecutionException; import com.intellij.openapi.diagnostic.Logger; /** Provides the launch task for android_binary */ @@ -31,44 +32,35 @@ public class BlazeAndroidBinaryApplicationLaunchTaskProvider { private static final Logger LOG = Logger.getInstance(BlazeAndroidBinaryApplicationLaunchTaskProvider.class); - public static LaunchTask getApplicationLaunchTask( + public static BlazeLaunchTask getApplicationLaunchTask( ApplicationIdProvider applicationIdProvider, ManifestParser.ParsedManifest mergedManifestParsedManifest, BlazeAndroidBinaryRunConfigurationState configState, - StartActivityFlagsProvider startActivityFlagsProvider, - LaunchStatus launchStatus) { + StartActivityFlagsProvider startActivityFlagsProvider) + throws ExecutionException { try { String applicationId = applicationIdProvider.getPackageName(); - final LaunchTask launchTask; - switch (configState.getMode()) { case BlazeAndroidBinaryRunConfigurationState.LAUNCH_DEFAULT_ACTIVITY: BlazeDefaultActivityLocator activityLocator = new BlazeDefaultActivityLocator(mergedManifestParsedManifest); - launchTask = + return new BlazeLaunchTaskWrapper( new DefaultActivityLaunchTask( - applicationId, activityLocator, startActivityFlagsProvider); - break; + applicationId, activityLocator, startActivityFlagsProvider)); case BlazeAndroidBinaryRunConfigurationState.LAUNCH_SPECIFIC_ACTIVITY: - launchTask = + return new BlazeLaunchTaskWrapper( new SpecificActivityLaunchTask( - applicationId, configState.getActivityClass(), startActivityFlagsProvider); - break; + applicationId, configState.getActivityClass(), startActivityFlagsProvider)); case BlazeAndroidBinaryRunConfigurationState.LAUNCH_DEEP_LINK: - launchTask = - new AndroidDeepLinkLaunchTask(configState.getDeepLink(), startActivityFlagsProvider); - break; + return new BlazeLaunchTaskWrapper( + new AndroidDeepLinkLaunchTask(configState.getDeepLink(), startActivityFlagsProvider)); case BlazeAndroidBinaryRunConfigurationState.DO_NOTHING: default: - launchTask = null; - break; + return null; } - return launchTask; } catch (ApkProvisionException e) { - LOG.error(e); - launchStatus.terminateLaunch("Unable to identify application id", true); - return null; + throw new ExecutionException("Unable to identify application id"); } } } diff --git a/aswb/sdkcompat/as213/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContext.java b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContext.java similarity index 80% rename from aswb/sdkcompat/as213/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContext.java rename to aswb/sdkcompat/as223/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContext.java index 9bda7c9d0de..6b2d10e2dac 100644 --- a/aswb/sdkcompat/as213/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContext.java +++ b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContext.java @@ -15,15 +15,16 @@ */ package com.google.idea.blaze.android.run.binary; +import static com.android.tools.idea.run.tasks.DefaultConnectDebuggerTaskKt.getBaseDebuggerTask; + +import com.android.tools.idea.execution.common.debug.AndroidDebugger; +import com.android.tools.idea.execution.common.debug.AndroidDebuggerState; import com.android.tools.idea.run.ApkProvisionException; import com.android.tools.idea.run.LaunchOptions; import com.android.tools.idea.run.activity.DefaultStartActivityFlagsProvider; import com.android.tools.idea.run.activity.StartActivityFlagsProvider; -import com.android.tools.idea.run.editor.AndroidDebugger; -import com.android.tools.idea.run.editor.AndroidDebuggerState; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; import com.android.tools.idea.run.tasks.ConnectDebuggerTask; -import com.android.tools.idea.run.tasks.LaunchTask; -import com.android.tools.idea.run.util.LaunchStatus; import com.google.idea.blaze.android.run.deployinfo.BlazeAndroidDeployInfo; import com.google.idea.blaze.android.run.runner.ApkBuildStep; import com.intellij.execution.ExecutionException; @@ -49,11 +50,8 @@ public class BlazeAndroidBinaryNormalBuildRunContext } @Override - public LaunchTask getApplicationLaunchTask( - LaunchOptions launchOptions, - @Nullable Integer userId, - String contributorsAmStartOptions, - LaunchStatus launchStatus) + public BlazeLaunchTask getApplicationLaunchTask( + LaunchOptions launchOptions, @Nullable Integer userId, String contributorsAmStartOptions) throws ExecutionException { String extraFlags = UserIdHelper.getFlagsFromUserId(userId); if (!contributorsAmStartOptions.isEmpty()) { @@ -61,7 +59,7 @@ public LaunchTask getApplicationLaunchTask( } final StartActivityFlagsProvider startActivityFlagsProvider = - new DefaultStartActivityFlagsProvider(launchOptions.isDebug(), extraFlags); + new DefaultStartActivityFlagsProvider(project, launchOptions.isDebug(), extraFlags); BlazeAndroidDeployInfo deployInfo; try { @@ -74,18 +72,15 @@ public LaunchTask getApplicationLaunchTask( applicationIdProvider, deployInfo.getMergedManifest(), configState, - startActivityFlagsProvider, - launchStatus); + startActivityFlagsProvider); } @Nullable @Override @SuppressWarnings("unchecked") public ConnectDebuggerTask getDebuggerTask( - AndroidDebugger androidDebugger, AndroidDebuggerState androidDebuggerState) - throws ExecutionException { - return androidDebugger.getConnectDebuggerTask( - env, applicationIdProvider, facet, androidDebuggerState); + AndroidDebugger androidDebugger, AndroidDebuggerState androidDebuggerState) { + return getBaseDebuggerTask(androidDebugger, androidDebuggerState, env, facet); } @Override diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContextBase.java b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContextBase.java similarity index 87% rename from aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContextBase.java rename to aswb/sdkcompat/as223/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContextBase.java index f78be5f1929..0dac5606ada 100644 --- a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContextBase.java +++ b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContextBase.java @@ -24,13 +24,12 @@ import com.android.tools.idea.run.ApkProvider; import com.android.tools.idea.run.ApkProvisionException; import com.android.tools.idea.run.ApplicationIdProvider; -import com.android.tools.idea.run.ConsolePrinter; import com.android.tools.idea.run.ConsoleProvider; import com.android.tools.idea.run.LaunchOptions; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; +import com.android.tools.idea.run.blaze.BlazeLaunchTasksProvider; import com.android.tools.idea.run.editor.ProfilerState; import com.android.tools.idea.run.tasks.DeployTasksCompat; -import com.android.tools.idea.run.tasks.LaunchTask; -import com.android.tools.idea.run.tasks.LaunchTasksProvider; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.idea.blaze.android.run.deployinfo.BlazeApkProviderService; @@ -105,7 +104,7 @@ public ConsoleProvider getConsoleProvider() { } @Override - public ApplicationIdProvider getApplicationIdProvider() throws ExecutionException { + public ApplicationIdProvider getApplicationIdProvider() { return applicationIdProvider; } @@ -116,13 +115,12 @@ public ApkBuildStep getBuildStep() { @Nullable @Override - public Integer getUserId(IDevice device, ConsolePrinter consolePrinter) - throws ExecutionException { - return UserIdHelper.getUserIdFromConfigurationState(device, consolePrinter, configState); + public Integer getUserId(IDevice device) throws ExecutionException { + return UserIdHelper.getUserIdFromConfigurationState(project, device, configState); } @Override - public LaunchTasksProvider getLaunchTasksProvider(LaunchOptions.Builder launchOptionsBuilder) + public BlazeLaunchTasksProvider getLaunchTasksProvider(LaunchOptions.Builder launchOptionsBuilder) throws ExecutionException { return new BlazeAndroidLaunchTasksProvider( project, this, applicationIdProvider, launchOptionsBuilder); @@ -133,18 +131,18 @@ public String getAmStartOptions() { return configState.getAmStartOptions(); } - // @Override #api211 + @Override public ProfilerState getProfileState() { return configState.getProfilerState(); } @Nullable @Override - public ImmutableList getDeployTasks(IDevice device, LaunchOptions launchOptions) + public ImmutableList getDeployTasks(IDevice device, LaunchOptions launchOptions) throws ExecutionException { - LaunchTask deployTask = - DeployTasksCompat.getDeployTask( - project, env, launchOptions, getApkInfoToInstall(device, launchOptions, apkProvider)); + BlazeLaunchTask deployTask = + DeployTasksCompat.createDeployTask( + project, getApkInfoToInstall(device, launchOptions, apkProvider), launchOptions); return ImmutableList.of(new DeploymentTimingReporterTask(launchId, deployTask)); } diff --git a/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContextCompat.java b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContextCompat.java new file mode 100644 index 00000000000..d54c472c84b --- /dev/null +++ b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContextCompat.java @@ -0,0 +1,52 @@ +/* + * Copyright 2022 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.binary; + +import static com.android.tools.idea.run.tasks.DefaultConnectDebuggerTaskKt.getBaseDebuggerTask; + +import com.android.tools.idea.execution.common.debug.AndroidDebugger; +import com.android.tools.idea.execution.common.debug.AndroidDebuggerState; +import com.android.tools.idea.run.tasks.ConnectDebuggerTask; +import com.google.idea.blaze.android.run.runner.ApkBuildStep; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.openapi.project.Project; +import javax.annotation.Nullable; +import org.jetbrains.android.facet.AndroidFacet; + +/** Compat class for {@link BlazeAndroidBinaryNormalBuildRunContext}. */ +public class BlazeAndroidBinaryNormalBuildRunContextCompat + extends BlazeAndroidBinaryNormalBuildRunContext { + + BlazeAndroidBinaryNormalBuildRunContextCompat( + Project project, + AndroidFacet facet, + RunConfiguration runConfiguration, + ExecutionEnvironment env, + BlazeAndroidBinaryRunConfigurationState configState, + ApkBuildStep buildStep, + String launchId) { + super(project, facet, runConfiguration, env, configState, buildStep, launchId); + } + + @Nullable + @Override + @SuppressWarnings("unchecked") + public ConnectDebuggerTask getDebuggerTask( + AndroidDebugger androidDebugger, AndroidDebuggerState androidDebuggerState) { + return getBaseDebuggerTask(androidDebugger, androidDebuggerState, env, facet); + } +} diff --git a/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryProgramRunner.java b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryProgramRunner.java new file mode 100644 index 00000000000..50f42d4b2dd --- /dev/null +++ b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryProgramRunner.java @@ -0,0 +1,114 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.binary; + +import static com.intellij.openapi.application.ModalityState.NON_MODAL; + +import com.android.tools.idea.profilers.ProfileRunExecutor; +import com.android.tools.idea.run.configuration.execution.AndroidConfigurationExecutor; +import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationHandler; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.ExecutionResult; +import com.intellij.execution.Executor; +import com.intellij.execution.configurations.RunProfile; +import com.intellij.execution.configurations.RunProfileState; +import com.intellij.execution.configurations.RunnerSettings; +import com.intellij.execution.executors.DefaultDebugExecutor; +import com.intellij.execution.executors.DefaultRunExecutor; +import com.intellij.execution.runners.AsyncProgramRunner; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.runners.RunContentBuilder; +import com.intellij.execution.ui.RunContentDescriptor; +import com.intellij.openapi.application.ActionsKt; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; +import org.jetbrains.concurrency.AsyncPromise; +import org.jetbrains.concurrency.Promise; + +/** Program runner for configurations from {@link BlazeAndroidBinaryRunConfigurationHandler}. */ +public class BlazeAndroidBinaryProgramRunner extends AsyncProgramRunner { + @Override + public boolean canRun(String executorId, RunProfile profile) { + BlazeAndroidRunConfigurationHandler handler = + BlazeAndroidRunConfigurationHandler.getHandlerFrom(profile); + if (!(handler instanceof BlazeAndroidBinaryRunConfigurationHandler)) { + return false; + } + return (DefaultDebugExecutor.EXECUTOR_ID.equals(executorId) + || DefaultRunExecutor.EXECUTOR_ID.equals(executorId) + || ProfileRunExecutor.EXECUTOR_ID.equals(executorId)); + } + + @Override + protected Promise execute( + ExecutionEnvironment environment, RunProfileState state) { + FileDocumentManager.getInstance().saveAllDocuments(); + + AsyncPromise promise = new AsyncPromise<>(); + + ProgressManager.getInstance() + .run( + new Task.Backgroundable(environment.getProject(), "Launching ${runProfile.name}") { + @Override + public void run(ProgressIndicator indicator) { + try { + RunContentDescriptor descriptor; + if (state instanceof AndroidConfigurationExecutor) { + AndroidConfigurationExecutor configurationExecutor = + (AndroidConfigurationExecutor) state; + Executor executor = environment.getExecutor(); + if (executor.getId().equals(DefaultDebugExecutor.EXECUTOR_ID)) { + descriptor = configurationExecutor.debug(indicator); + } else if (executor.getId().equals(DefaultRunExecutor.EXECUTOR_ID) + || executor.getId().equals(ProfileRunExecutor.EXECUTOR_ID)) { + descriptor = configurationExecutor.run(indicator); + } else { + throw new ExecutionException("Unsupported executor"); + } + } else { + descriptor = doExecute(state, environment); + } + promise.setResult(descriptor); + } catch (ExecutionException e) { + boolean unused = promise.setError(e); + } + } + + @Override + public void onCancel() { + super.onCancel(); + promise.setResult(null); + } + }); + + return promise; + } + + private RunContentDescriptor doExecute( + final RunProfileState state, final ExecutionEnvironment env) throws ExecutionException { + ExecutionResult result = state.execute(env.getExecutor(), this); + return ActionsKt.invokeAndWaitIfNeeded( + NON_MODAL, + () -> new RunContentBuilder(result, env).showRunContent(env.getContentToReuse())); + } + + @Override + public String getRunnerId() { + return "AndroidBinaryProgramRunner"; + } +} diff --git a/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationHandlerCompat.java b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationHandlerCompat.java new file mode 100644 index 00000000000..296c3ea57af --- /dev/null +++ b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationHandlerCompat.java @@ -0,0 +1,39 @@ +/* + * Copyright 2022 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.binary; + +import static com.android.tools.idea.run.deployment.DeviceAndSnapshotComboBoxAction.DEPLOYS_TO_LOCAL_DEVICE; + +import com.google.common.annotations.VisibleForTesting; +import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration; +import com.google.idea.blaze.base.settings.Blaze; +import com.intellij.openapi.project.Project; + +/** Compat class for {@link BlazeAndroidBinaryRunConfigurationHandlerCompat}. */ +public class BlazeAndroidBinaryRunConfigurationHandlerCompat { + protected final Project project; + protected final BlazeAndroidBinaryRunConfigurationState configState; + + @VisibleForTesting + protected BlazeAndroidBinaryRunConfigurationHandlerCompat( + BlazeCommandRunConfiguration configuration) { + project = configuration.getProject(); + configState = + new BlazeAndroidBinaryRunConfigurationState( + Blaze.buildSystemName(configuration.getProject())); + configuration.putUserData(DEPLOYS_TO_LOCAL_DEVICE, true); + } +} diff --git a/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/binary/DeploymentTimingReporterTask.java b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/binary/DeploymentTimingReporterTask.java new file mode 100644 index 00000000000..81696ad5b1e --- /dev/null +++ b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/binary/DeploymentTimingReporterTask.java @@ -0,0 +1,45 @@ +/* + * Copyright 2021 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.binary; + +import com.android.tools.idea.run.blaze.BlazeLaunchContext; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; +import com.google.common.base.Stopwatch; +import com.google.idea.blaze.android.run.LaunchMetrics; +import com.intellij.execution.ExecutionException; + +/** A wrapper launch task that wraps the given deployment task and logs the deployment latency. */ +public class DeploymentTimingReporterTask implements BlazeLaunchTask { + private final BlazeLaunchTask deployTask; + private final String launchId; + + public DeploymentTimingReporterTask(String launchId, BlazeLaunchTask deployTask) { + this.launchId = launchId; + this.deployTask = deployTask; + } + + @Override + public void run(BlazeLaunchContext launchContext) throws ExecutionException { + Stopwatch s = Stopwatch.createStarted(); + try { + deployTask.run(launchContext); + LaunchMetrics.logDeploymentTime(launchId, s.elapsed(), true); + } catch (ExecutionException e) { + LaunchMetrics.logDeploymentTime(launchId, s.elapsed(), false); + throw e; + } + } +} diff --git a/aswb/sdkcompat/as213/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContext.java b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContext.java similarity index 81% rename from aswb/sdkcompat/as213/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContext.java rename to aswb/sdkcompat/as223/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContext.java index 00aa0e82561..c49082cfdaa 100644 --- a/aswb/sdkcompat/as213/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContext.java +++ b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContext.java @@ -15,15 +15,16 @@ */ package com.google.idea.blaze.android.run.binary.mobileinstall; +import static com.android.tools.idea.run.tasks.DefaultConnectDebuggerTaskKt.getBaseDebuggerTask; + +import com.android.tools.idea.execution.common.debug.AndroidDebugger; +import com.android.tools.idea.execution.common.debug.AndroidDebuggerState; import com.android.tools.idea.run.ApkProvisionException; import com.android.tools.idea.run.LaunchOptions; import com.android.tools.idea.run.activity.DefaultStartActivityFlagsProvider; import com.android.tools.idea.run.activity.StartActivityFlagsProvider; -import com.android.tools.idea.run.editor.AndroidDebugger; -import com.android.tools.idea.run.editor.AndroidDebuggerState; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; import com.android.tools.idea.run.tasks.ConnectDebuggerTask; -import com.android.tools.idea.run.tasks.LaunchTask; -import com.android.tools.idea.run.util.LaunchStatus; import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryApplicationLaunchTaskProvider; import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryRunConfigurationState; import com.google.idea.blaze.android.run.binary.UserIdHelper; @@ -51,12 +52,10 @@ public BlazeAndroidBinaryMobileInstallRunContext( super(project, facet, runConfiguration, env, configState, buildStep, launchId); } + @SuppressWarnings("unchecked") // upstream API @Override - public LaunchTask getApplicationLaunchTask( - LaunchOptions launchOptions, - @Nullable Integer userId, - String contributorsAmStartOptions, - LaunchStatus launchStatus) + public BlazeLaunchTask getApplicationLaunchTask( + LaunchOptions launchOptions, @Nullable Integer userId, String contributorsAmStartOptions) throws ExecutionException { String extraFlags = UserIdHelper.getFlagsFromUserId(userId); @@ -65,7 +64,7 @@ public LaunchTask getApplicationLaunchTask( } final StartActivityFlagsProvider startActivityFlagsProvider = - new DefaultStartActivityFlagsProvider(launchOptions.isDebug(), extraFlags); + new DefaultStartActivityFlagsProvider(project, launchOptions.isDebug(), extraFlags); BlazeAndroidDeployInfo deployInfo; try { deployInfo = buildStep.getDeployInfo(); @@ -77,18 +76,15 @@ public LaunchTask getApplicationLaunchTask( applicationIdProvider, deployInfo.getMergedManifest(), configState, - startActivityFlagsProvider, - launchStatus); + startActivityFlagsProvider); } @Nullable @Override @SuppressWarnings("unchecked") public ConnectDebuggerTask getDebuggerTask( - AndroidDebugger androidDebugger, AndroidDebuggerState androidDebuggerState) - throws ExecutionException { - return androidDebugger.getConnectDebuggerTask( - env, applicationIdProvider, facet, androidDebuggerState); + AndroidDebugger androidDebugger, AndroidDebuggerState androidDebuggerState) { + return getBaseDebuggerTask(androidDebugger, androidDebuggerState, env, facet); } @Override diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContextBase.java b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContextBase.java similarity index 84% rename from aswb/src/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContextBase.java rename to aswb/sdkcompat/as223/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContextBase.java index 3469d8cbaf5..b18f117f80f 100644 --- a/aswb/src/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContextBase.java +++ b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContextBase.java @@ -20,15 +20,14 @@ import com.android.tools.idea.run.ApkInfo; import com.android.tools.idea.run.ApkProvisionException; import com.android.tools.idea.run.ApplicationIdProvider; -import com.android.tools.idea.run.ConsolePrinter; import com.android.tools.idea.run.ConsoleProvider; import com.android.tools.idea.run.LaunchOptions; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; +import com.android.tools.idea.run.blaze.BlazeLaunchTasksProvider; import com.android.tools.idea.run.editor.ProfilerState; -import com.android.tools.idea.run.tasks.LaunchTask; -import com.android.tools.idea.run.tasks.LaunchTasksProvider; +import com.android.tools.idea.run.tasks.DeployTasksCompat; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.google.idea.blaze.android.run.BlazeAndroidDeploymentService; import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryApplicationIdProvider; import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryConsoleProvider; import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryRunConfigurationState; @@ -88,7 +87,7 @@ public BlazeAndroidDeviceSelector getDeviceSelector() { @Override public void augmentLaunchOptions(LaunchOptions.Builder options) { options - .setDeploy(StudioDeployerExperiment.isEnabled()) + .setDeploy(buildStep.needsIdeDeploy()) .setOpenLogcatAutomatically(configState.showLogcatAutomatically()); // This is needed for compatibility with #api211 options.addExtraOptions( @@ -101,7 +100,7 @@ public ConsoleProvider getConsoleProvider() { } @Override - public ApplicationIdProvider getApplicationIdProvider() throws ExecutionException { + public ApplicationIdProvider getApplicationIdProvider() { return applicationIdProvider; } @@ -110,15 +109,15 @@ public ApkBuildStep getBuildStep() { return buildStep; } - // @Override #api211 + @Override public ProfilerState getProfileState() { return configState.getProfilerState(); } @Override - public ImmutableList getDeployTasks(IDevice device, LaunchOptions launchOptions) + public ImmutableList getDeployTasks(IDevice device, LaunchOptions launchOptions) throws ExecutionException { - if (!StudioDeployerExperiment.isEnabled()) { + if (!buildStep.needsIdeDeploy()) { return ImmutableList.of(); } @@ -141,21 +140,19 @@ public ImmutableList getDeployTasks(IDevice device, LaunchOptions la .collect(Collectors.toList()), packageName); - LaunchTask deployTask = - BlazeAndroidDeploymentService.getInstance(project) - .getDeployTask(Collections.singletonList(info), launchOptions); + BlazeLaunchTask deployTask = + DeployTasksCompat.createDeployTask(project, Collections.singletonList(info), launchOptions); return ImmutableList.of(new DeploymentTimingReporterTask(launchId, deployTask)); } @Nullable @Override - public Integer getUserId(IDevice device, ConsolePrinter consolePrinter) - throws ExecutionException { - return UserIdHelper.getUserIdFromConfigurationState(device, consolePrinter, configState); + public Integer getUserId(IDevice device) throws ExecutionException { + return UserIdHelper.getUserIdFromConfigurationState(project, device, configState); } @Override - public LaunchTasksProvider getLaunchTasksProvider(LaunchOptions.Builder launchOptionsBuilder) + public BlazeLaunchTasksProvider getLaunchTasksProvider(LaunchOptions.Builder launchOptionsBuilder) throws ExecutionException { return new BlazeAndroidLaunchTasksProvider( project, this, applicationIdProvider, launchOptionsBuilder); diff --git a/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContextCompat.java b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContextCompat.java new file mode 100644 index 00000000000..29411ebad5a --- /dev/null +++ b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContextCompat.java @@ -0,0 +1,53 @@ +/* + * Copyright 2022 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.mobileinstall; + +import static com.android.tools.idea.run.tasks.DefaultConnectDebuggerTaskKt.getBaseDebuggerTask; + +import com.android.tools.idea.execution.common.debug.AndroidDebugger; +import com.android.tools.idea.execution.common.debug.AndroidDebuggerState; +import com.android.tools.idea.run.tasks.ConnectDebuggerTask; +import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryRunConfigurationState; +import com.google.idea.blaze.android.run.runner.ApkBuildStep; +import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.openapi.project.Project; +import javax.annotation.Nullable; +import org.jetbrains.android.facet.AndroidFacet; + +/** Compatct class for {@link BlazeAndroidBinaryMobileInstallRunContext}. */ +public class BlazeAndroidBinaryMobileInstallRunContextCompat + extends BlazeAndroidBinaryMobileInstallRunContext { + + public BlazeAndroidBinaryMobileInstallRunContextCompat( + Project project, + AndroidFacet facet, + BlazeCommandRunConfiguration configuration, + ExecutionEnvironment env, + BlazeAndroidBinaryRunConfigurationState configState, + ApkBuildStep buildStep, + String launchId) { + super(project, facet, configuration, env, configState, buildStep, launchId); + } + + @Nullable + @Override + @SuppressWarnings("unchecked") + public ConnectDebuggerTask getDebuggerTask( + AndroidDebugger androidDebugger, AndroidDebuggerState androidDebuggerState) { + return getBaseDebuggerTask(androidDebugger, androidDebuggerState, env, facet); + } +} diff --git a/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/runner/BlazeAndroidDebuggerService.java b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/runner/BlazeAndroidDebuggerService.java new file mode 100644 index 00000000000..c8658223baa --- /dev/null +++ b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/runner/BlazeAndroidDebuggerService.java @@ -0,0 +1,111 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.runner; + +import com.android.tools.idea.execution.common.debug.AndroidDebugger; +import com.android.tools.idea.execution.common.debug.AndroidDebuggerState; +import com.android.tools.idea.execution.common.debug.impl.java.AndroidJavaDebugger; +import com.android.tools.ndk.run.editor.AutoAndroidDebuggerState; +import com.google.common.collect.ImmutableList; +import com.google.idea.blaze.android.cppimpl.debug.BlazeAutoAndroidDebugger; +import com.google.idea.blaze.android.run.deployinfo.BlazeAndroidDeployInfo; +import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; +import com.intellij.ide.plugins.PluginManagerCore; +import com.intellij.openapi.project.Project; +import javax.annotation.Nullable; + +/** Provides android debuggers and debugger states for blaze projects. */ +public interface BlazeAndroidDebuggerService { + + static BlazeAndroidDebuggerService getInstance(Project project) { + return project.getService(BlazeAndroidDebuggerService.class); + } + + /** Returns the standard debugger for non-native (Java) debugging. */ + AndroidDebugger getDebugger(); + + /** Returns the standard debugger for native (C++) debugging. */ + AndroidDebugger getNativeDebugger(); + + /** + * Performs additional necessary setup for native debugging, incorporating info from {@link + * BlazeAndroidDeployInfo}. + */ + void configureNativeDebugger( + AndroidDebuggerState state, @Nullable BlazeAndroidDeployInfo deployInfo); + + /** Default debugger service. */ + class DefaultDebuggerService implements BlazeAndroidDebuggerService { + private final Project project; + + public DefaultDebuggerService(Project project) { + this.project = project; + } + + @Override + public AndroidDebugger getDebugger() { + return new AndroidJavaDebugger(); + } + + @Override + public AndroidDebugger getNativeDebugger() { + return new BlazeAutoAndroidDebugger(); + } + + @Override + public void configureNativeDebugger( + AndroidDebuggerState rawState, @Nullable BlazeAndroidDeployInfo deployInfo) { + if (!isNdkPluginLoaded() && !(rawState instanceof AutoAndroidDebuggerState)) { + return; + } + AutoAndroidDebuggerState state = (AutoAndroidDebuggerState) rawState; + + // Source code is always relative to the workspace root in a blaze project. + String workingDirPath = WorkspaceRoot.fromProject(project).directory().getPath(); + state.setWorkingDir(workingDirPath); + + // Remote built binaries may use /proc/self/cwd to represent the working directory, + // so we manually map /proc/self/cwd to the workspace root. We used to use + // `plugin.symbol-file.dwarf.comp-dir-symlink-paths = "/proc/self/cwd"` + // to automatically resolve this, but it's no longer supported in newer versions of + // LLDB. + String sourceMapToWorkspaceRootCommand = + "settings append target.source-map /proc/self/cwd/ " + workingDirPath; + + ImmutableList startupCommands = + ImmutableList.builder() + .addAll(state.getUserStartupCommands()) + .add(sourceMapToWorkspaceRootCommand) + .build(); + state.setUserStartupCommands(startupCommands); + + // NDK plugin will pass symbol directories to LLDB as `settings append + // target.exec-search-paths`. + if (deployInfo != null) { + state.setSymbolDirs( + deployInfo.getSymbolFiles().stream() + .map(symbol -> symbol.getParentFile().getAbsolutePath()) + .collect(ImmutableList.toImmutableList())); + } + } + } + + static boolean isNdkPluginLoaded() { + return PluginManagerCore.getLoadedPlugins().stream() + .anyMatch( + d -> d.isEnabled() && d.getPluginId().getIdString().equals("com.android.tools.ndk")); + } +} diff --git a/aswb/sdkcompat/as213/com/google/idea/blaze/android/run/runner/BlazeAndroidLaunchTasksProvider.java b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/runner/BlazeAndroidLaunchTasksProvider.java similarity index 59% rename from aswb/sdkcompat/as213/com/google/idea/blaze/android/run/runner/BlazeAndroidLaunchTasksProvider.java rename to aswb/sdkcompat/as223/com/google/idea/blaze/android/run/runner/BlazeAndroidLaunchTasksProvider.java index a694833e05d..9ede8d06e50 100644 --- a/aswb/sdkcompat/as213/com/google/idea/blaze/android/run/runner/BlazeAndroidLaunchTasksProvider.java +++ b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/runner/BlazeAndroidLaunchTasksProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 The Bazel Authors. All rights reserved. + * Copyright 2022 The Bazel Authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,25 +18,25 @@ import static com.android.tools.idea.profilers.AndroidProfilerLaunchTaskContributor.isProfilerLaunch; import com.android.ddmlib.IDevice; -import com.android.sdklib.AndroidVersion; import com.android.tools.deployer.ApkVerifierTracker; +import com.android.tools.idea.execution.common.debug.AndroidDebugger; +import com.android.tools.idea.execution.common.debug.AndroidDebuggerState; import com.android.tools.idea.profilers.AndroidProfilerLaunchTaskContributor; import com.android.tools.idea.run.ApkProvisionException; import com.android.tools.idea.run.ApplicationIdProvider; -import com.android.tools.idea.run.ConsolePrinter; import com.android.tools.idea.run.LaunchOptions; -import com.android.tools.idea.run.editor.AndroidDebugger; -import com.android.tools.idea.run.editor.AndroidDebuggerState; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; +import com.android.tools.idea.run.blaze.BlazeLaunchTaskWrapper; +import com.android.tools.idea.run.blaze.BlazeLaunchTasksProvider; import com.android.tools.idea.run.tasks.ClearLogcatTask; import com.android.tools.idea.run.tasks.ConnectDebuggerTask; import com.android.tools.idea.run.tasks.DismissKeyguardTask; -import com.android.tools.idea.run.tasks.LaunchTask; -import com.android.tools.idea.run.tasks.LaunchTasksProvider; import com.android.tools.idea.run.tasks.ShowLogcatTask; -import com.android.tools.idea.run.util.LaunchStatus; +import com.android.tools.ndk.run.editor.AutoAndroidDebuggerState; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.idea.blaze.android.run.binary.UserIdHelper; +import com.google.idea.blaze.android.run.deployinfo.BlazeAndroidDeployInfo; import com.intellij.execution.ExecutionException; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; @@ -45,7 +45,7 @@ import org.jetbrains.annotations.Nullable; /** Normal launch tasks provider. #api4.1 */ -public class BlazeAndroidLaunchTasksProvider implements LaunchTasksProvider { +public class BlazeAndroidLaunchTasksProvider implements BlazeLaunchTasksProvider { public static final String NATIVE_DEBUGGING_ENABLED = "NATIVE_DEBUGGING_ENABLED"; private static final Logger LOG = Logger.getInstance(BlazeAndroidLaunchTasksProvider.class); @@ -67,23 +67,17 @@ public BlazeAndroidLaunchTasksProvider( @NotNull @Override - public List getTasks( - @NotNull IDevice device, - @NotNull LaunchStatus launchStatus, - @NotNull ConsolePrinter consolePrinter) - throws ExecutionException { - final List launchTasks = Lists.newArrayList(); + public List getTasks(@NotNull IDevice device) throws ExecutionException { + final List launchTasks = Lists.newArrayList(); String packageName; try { packageName = applicationIdProvider.getPackageName(); } catch (ApkProvisionException e) { - LOG.error(e); - launchStatus.terminateLaunch("Unable to determine application id: " + e, true); - return ImmutableList.of(); + throw new ExecutionException("Unable to determine application id: " + e); } - Integer userId = runContext.getUserId(device, consolePrinter); + Integer userId = runContext.getUserId(device); String userIdFlags = UserIdHelper.getFlagsFromUserId(userId); String skipVerification = ApkVerifierTracker.getSkipVerificationInstallationFlag(device, packageName); @@ -104,23 +98,23 @@ public List getTasks( launchTasks.add(new BlazeAndroidOpenProfilerWindowTask(project)); } + // TODO(kovalp): Check if there's any drawback to add these tasks with BlazeLaunchTaskWrapper + // since it's different with ag/21610897 if (launchOptions.isClearLogcatBeforeStart()) { - launchTasks.add(new ClearLogcatTask(project)); + launchTasks.add(new BlazeLaunchTaskWrapper(new ClearLogcatTask(project))); } - launchTasks.add(new DismissKeyguardTask()); + launchTasks.add(new BlazeLaunchTaskWrapper(new DismissKeyguardTask())); if (launchOptions.isDeploy()) { - ImmutableList deployTasks = runContext.getDeployTasks(device, launchOptions); + ImmutableList deployTasks = runContext.getDeployTasks(device, launchOptions); launchTasks.addAll(deployTasks); } - if (launchStatus.isLaunchTerminated()) { - return ImmutableList.copyOf(launchTasks); - } try { if (launchOptions.isDebug()) { - launchTasks.add(new CheckApkDebuggableTask(runContext.getBuildStep().getDeployInfo())); + launchTasks.add( + new CheckApkDebuggableTask(project, runContext.getBuildStep().getDeployInfo())); } ImmutableList.Builder amStartOptions = ImmutableList.builder(); @@ -134,71 +128,55 @@ public List getTasks( device, runContext.getExecutor())); launchTasks.add( - new AndroidProfilerLaunchTaskContributor.AndroidProfilerToolWindowLaunchTask( - project, packageName)); + new BlazeLaunchTaskWrapper( + new AndroidProfilerLaunchTaskContributor.AndroidProfilerToolWindowLaunchTask( + project, packageName))); } - - // Do not get debugger state directly from the debugger itself. - // See BlazeAndroidDebuggerService#getDebuggerState for an explanation. - BlazeAndroidDebuggerService debuggerService = - BlazeAndroidDebuggerService.getInstance(project); - AndroidDebugger debugger = - debuggerService.getDebugger(isNativeDebuggingEnabled(launchOptions)); - AndroidDebuggerState debuggerState = debuggerService.getDebuggerState(debugger); - LaunchTask appLaunchTask = + BlazeLaunchTask appLaunchTask = runContext.getApplicationLaunchTask( - launchOptions, - userId, - String.join(" ", amStartOptions.build()), - launchStatus); + launchOptions, userId, String.join(" ", amStartOptions.build())); if (appLaunchTask != null) { launchTasks.add(appLaunchTask); } } catch (ApkProvisionException e) { - LOG.error(e); - launchStatus.terminateLaunch("Unable to determine application id: " + e, true); - return ImmutableList.of(); - } catch (ExecutionException e) { - launchStatus.terminateLaunch(e.getMessage(), true); - return ImmutableList.of(); + throw new ExecutionException("Unable to determine application id: " + e); } - if (!launchOptions.isDebug() && launchOptions.isOpenLogcatAutomatically()) { - launchTasks.add(new ShowLogcatTask(project, packageName)); + if (launchOptions.isOpenLogcatAutomatically()) { + launchTasks.add(new BlazeLaunchTaskWrapper(new ShowLogcatTask(project, packageName))); } return ImmutableList.copyOf(launchTasks); } - @Nullable @Override - public ConnectDebuggerTask getConnectDebuggerTask( - @NotNull LaunchStatus launchStatus, @Nullable AndroidVersion version) { + @Nullable + public ConnectDebuggerTask getConnectDebuggerTask() { LaunchOptions launchOptions = launchOptionsBuilder.build(); if (!launchOptions.isDebug()) { return null; } - // Do not get debugger state directly from the debugger itself. - // See BlazeAndroidDebuggerService#getDebuggerState for an explanation. - BlazeAndroidDebuggerService debuggerService = BlazeAndroidDebuggerService.getInstance(project); - AndroidDebugger debugger = debuggerService.getDebugger(isNativeDebuggingEnabled(launchOptions)); - AndroidDebuggerState debuggerState = debuggerService.getDebuggerState(debugger); - if (debugger == null || debuggerState == null) { - return null; - } - + BlazeAndroidDeployInfo deployInfo; try { - return runContext.getDebuggerTask(debugger, debuggerState); - } catch (ExecutionException e) { - launchStatus.terminateLaunch(e.getMessage(), true); - return null; + deployInfo = runContext.getBuildStep().getDeployInfo(); + } catch (ApkProvisionException e) { + LOG.error(e); + deployInfo = null; } - } - @Override - public String getLaunchTypeDisplayName() { - return "Launch"; + BlazeAndroidDebuggerService debuggerService = BlazeAndroidDebuggerService.getInstance(project); + if (isNativeDebuggingEnabled(launchOptions)) { + AndroidDebugger debugger = debuggerService.getNativeDebugger(); + // The below state type should be AutoAndroidDebuggerState, but referencing it will crash the + // task if the NDK plugin is not loaded. + AndroidDebuggerState state = debugger.createState(); + debuggerService.configureNativeDebugger(state, deployInfo); + return runContext.getDebuggerTask(debugger, state); + } else { + AndroidDebugger debugger = debuggerService.getDebugger(); + return runContext.getDebuggerTask(debugger, debugger.createState()); + } } private boolean isNativeDebuggingEnabled(LaunchOptions launchOptions) { diff --git a/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidRunConfigurationRunner.java b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/runner/BlazeAndroidRunConfigurationRunner.java similarity index 63% rename from aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidRunConfigurationRunner.java rename to aswb/sdkcompat/as223/com/google/idea/blaze/android/run/runner/BlazeAndroidRunConfigurationRunner.java index 71a297a1f4b..78728d9c46f 100644 --- a/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidRunConfigurationRunner.java +++ b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/runner/BlazeAndroidRunConfigurationRunner.java @@ -17,21 +17,34 @@ package com.google.idea.blaze.android.run.runner; import com.android.ddmlib.IDevice; -import com.android.tools.idea.run.AndroidSessionInfo; +import com.android.tools.idea.execution.common.AppRunSettings; +import com.android.tools.idea.execution.common.ApplicationDeployer; +import com.android.tools.idea.execution.common.ComponentLaunchOptions; +import com.android.tools.idea.execution.common.DeployOptions; +import com.android.tools.idea.run.ApkProvider; +import com.android.tools.idea.run.ApplicationIdProvider; import com.android.tools.idea.run.DeviceFutures; import com.android.tools.idea.run.LaunchOptions; +import com.android.tools.idea.run.blaze.BlazeAndroidConfigurationExecutor; +import com.android.tools.idea.run.configuration.execution.AndroidComplicationConfigurationExecutor; +import com.android.tools.idea.run.configuration.execution.AndroidConfigurationExecutor; +import com.android.tools.idea.run.configuration.execution.AndroidConfigurationExecutorRunProfileState; +import com.android.tools.idea.run.configuration.execution.AndroidTileConfigurationExecutor; +import com.android.tools.idea.run.configuration.execution.AndroidWatchFaceConfigurationExecutor; +import com.android.tools.idea.run.configuration.execution.ComplicationLaunchOptions; +import com.android.tools.idea.run.configuration.execution.TileLaunchOptions; +import com.android.tools.idea.run.configuration.execution.WatchFaceLaunchOptions; import com.android.tools.idea.run.editor.DeployTarget; import com.android.tools.idea.run.editor.DeployTargetState; import com.android.tools.idea.run.util.LaunchUtils; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; -import com.google.idea.blaze.android.run.BlazeAndroidRunState; +import com.google.idea.blaze.android.run.binary.mobileinstall.MobileInstallBuildStep; +import com.google.idea.blaze.android.run.deployinfo.BlazeApkProviderService; import com.google.idea.blaze.base.async.executor.ProgressiveTaskWithProgressIndicator; -import com.google.idea.blaze.base.command.BlazeInvocationContext; import com.google.idea.blaze.base.command.BlazeInvocationContext.ContextType; import com.google.idea.blaze.base.experiments.ExperimentScope; import com.google.idea.blaze.base.issueparser.BlazeIssueParser; -import com.google.idea.blaze.base.issueparser.IssueOutputFilter; import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration; import com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationRunner; @@ -39,7 +52,6 @@ import com.google.idea.blaze.base.scope.Scope; import com.google.idea.blaze.base.scope.ScopedTask; import com.google.idea.blaze.base.scope.output.IssueOutput; -import com.google.idea.blaze.base.scope.scopes.BlazeConsoleScope; import com.google.idea.blaze.base.scope.scopes.IdeaLogScope; import com.google.idea.blaze.base.scope.scopes.ProblemsViewScope; import com.google.idea.blaze.base.scope.scopes.ToolWindowScope; @@ -51,14 +63,17 @@ import com.intellij.execution.configurations.RunProfileState; import com.intellij.execution.executors.DefaultDebugExecutor; import com.intellij.execution.runners.ExecutionEnvironment; +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.util.Key; +import java.util.Collections; import java.util.concurrent.CancellationException; import javax.annotation.Nullable; import org.jetbrains.android.facet.AndroidFacet; import org.jetbrains.android.util.AndroidBundle; +import org.jetbrains.annotations.NotNull; /** * Supports the execution. Used by both android_binary and android_test. @@ -99,13 +114,11 @@ public final RunProfileState getRunProfileState(final Executor executor, Executi final Project project = env.getProject(); boolean isDebug = executor instanceof DefaultDebugExecutor; - AndroidSessionInfo info = - AndroidSessionInfo.findOldSession(project, null, runConfig, env.getExecutionTarget()); BlazeAndroidDeviceSelector deviceSelector = runContext.getDeviceSelector(); BlazeAndroidDeviceSelector.DeviceSession deviceSession = - deviceSelector.getDevice( - project, facet, executor, env, info, isDebug, runConfig.getUniqueID()); + deviceSelector.getDevice(project, executor, env, isDebug, runConfig.getUniqueID()); + if (deviceSession == null) { return null; } @@ -140,9 +153,99 @@ public final RunProfileState getRunProfileState(final Executor executor, Executi env.putCopyableUserData(RUN_CONTEXT_KEY, runContext); env.putCopyableUserData(DEVICE_SESSION_KEY, deviceSession); - return new BlazeAndroidRunState(env, launchOptionsBuilder, deviceSession, runContext); + BlazeAndroidConfigurationExecutor runner = + new BlazeAndroidConfigurationExecutor( + runContext.getConsoleProvider(), + runContext.getApplicationIdProvider(), + env, + deviceFutures, + runContext.getLaunchTasksProvider(launchOptionsBuilder), + LaunchOptions.builder().build()); + return new AndroidConfigurationExecutorRunProfileState(runner); + } + + private RunProfileState getWearExecutor( + ComponentLaunchOptions launchOptions, ExecutionEnvironment env, DeployTarget deployTarget) + throws ExecutionException { + + AppRunSettings settings = + new AppRunSettings() { + @NotNull + @Override + public DeployOptions getDeployOptions() { + return new DeployOptions(Collections.emptyList(), "", true, true); + } + + @NotNull + @Override + public ComponentLaunchOptions getComponentLaunchOptions() { + return launchOptions; + } + + @Override + public Module getModule() { + return runConfig.getModules()[0]; + } + }; + + AndroidConfigurationExecutor configurationExecutor; + ApplicationIdProvider appIdProvider = runContext.getApplicationIdProvider(); + ApkProvider apkProvider = + BlazeApkProviderService.getInstance() + .getApkProvider(env.getProject(), runContext.getBuildStep()); + DeviceFutures deviceFutures = deployTarget.getDevices(env.getProject()); + + if (launchOptions instanceof TileLaunchOptions) { + configurationExecutor = + new AndroidTileConfigurationExecutor( + env, deviceFutures, settings, appIdProvider, apkProvider) { + @NotNull + @Override + public ApplicationDeployer getApplicationDeployer(@NotNull ConsoleView console) + throws ExecutionException { + if (runContext.getBuildStep() instanceof MobileInstallBuildStep) { + return new MobileInstallApplicationDeployer(console); + } + return super.getApplicationDeployer(console); + } + }; + } else if (launchOptions instanceof WatchFaceLaunchOptions) { + configurationExecutor = + new AndroidWatchFaceConfigurationExecutor( + env, deviceFutures, settings, appIdProvider, apkProvider) { + @NotNull + @Override + public ApplicationDeployer getApplicationDeployer(@NotNull ConsoleView console) + throws ExecutionException { + if (runContext.getBuildStep() instanceof MobileInstallBuildStep) { + return new MobileInstallApplicationDeployer(console); + } + return super.getApplicationDeployer(console); + } + }; + } else if (launchOptions instanceof ComplicationLaunchOptions) { + configurationExecutor = + new AndroidComplicationConfigurationExecutor( + env, deviceFutures, settings, appIdProvider, apkProvider) { + @NotNull + @Override + public ApplicationDeployer getApplicationDeployer(@NotNull ConsoleView console) + throws ExecutionException { + if (runContext.getBuildStep() instanceof MobileInstallBuildStep) { + return new MobileInstallApplicationDeployer(console); + } + return super.getApplicationDeployer(console); + } + }; + } else { + throw new RuntimeException("Unknown launch options " + launchOptions.getClass().getName()); + } + + return new AndroidConfigurationExecutorRunProfileState( + new BlazeWrapperForAndroidConfigurationExecutor(configurationExecutor)); } + @Nullable private static String canDebug( DeviceFutures deviceFutures, AndroidFacet facet, String moduleName) { // If we are debugging on a device, then the app needs to be debuggable @@ -173,16 +276,6 @@ public boolean executeBeforeRunTask(ExecutionEnvironment env) { context .push(new ProblemsViewScope(project, settings.getShowProblemsViewOnRun())) .push(new ExperimentScope()) - .push( - new BlazeConsoleScope.Builder(project) - .setPopupBehavior(settings.getShowBlazeConsoleOnRun()) - .addConsoleFilters( - new IssueOutputFilter( - project, - WorkspaceRoot.fromProject(project), - BlazeInvocationContext.ContextType.BeforeRunTask, - true)) - .build()) .push( new ToolWindowScope.Builder( project, new Task(project, "Build apk", Task.Type.BEFORE_LAUNCH)) diff --git a/aswb/sdkcompat/as213/com/google/idea/blaze/android/run/runner/BlazeAndroidRunContext.java b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/runner/BlazeAndroidRunContext.java similarity index 70% rename from aswb/sdkcompat/as213/com/google/idea/blaze/android/run/runner/BlazeAndroidRunContext.java rename to aswb/sdkcompat/as223/com/google/idea/blaze/android/run/runner/BlazeAndroidRunContext.java index a879851c38a..327d7f4aeae 100644 --- a/aswb/sdkcompat/as213/com/google/idea/blaze/android/run/runner/BlazeAndroidRunContext.java +++ b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/runner/BlazeAndroidRunContext.java @@ -16,17 +16,15 @@ package com.google.idea.blaze.android.run.runner; import com.android.ddmlib.IDevice; +import com.android.tools.idea.execution.common.debug.AndroidDebugger; +import com.android.tools.idea.execution.common.debug.AndroidDebuggerState; import com.android.tools.idea.run.ApplicationIdProvider; -import com.android.tools.idea.run.ConsolePrinter; import com.android.tools.idea.run.ConsoleProvider; import com.android.tools.idea.run.LaunchOptions; -import com.android.tools.idea.run.editor.AndroidDebugger; -import com.android.tools.idea.run.editor.AndroidDebuggerState; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; +import com.android.tools.idea.run.blaze.BlazeLaunchTasksProvider; import com.android.tools.idea.run.editor.ProfilerState; import com.android.tools.idea.run.tasks.ConnectDebuggerTask; -import com.android.tools.idea.run.tasks.LaunchTask; -import com.android.tools.idea.run.tasks.LaunchTasksProvider; -import com.android.tools.idea.run.util.LaunchStatus; import com.google.common.collect.ImmutableList; import com.intellij.execution.ExecutionException; import com.intellij.execution.Executor; @@ -44,32 +42,30 @@ public interface BlazeAndroidRunContext { ApkBuildStep getBuildStep(); - ApplicationIdProvider getApplicationIdProvider() throws ExecutionException; + ApplicationIdProvider getApplicationIdProvider(); - LaunchTasksProvider getLaunchTasksProvider(LaunchOptions.Builder launchOptionsBuilder) + BlazeLaunchTasksProvider getLaunchTasksProvider(LaunchOptions.Builder launchOptionsBuilder) throws ExecutionException; /** Returns the tasks to deploy the application. */ - ImmutableList getDeployTasks(IDevice device, LaunchOptions launchOptions) + ImmutableList getDeployTasks(IDevice device, LaunchOptions launchOptions) throws ExecutionException; /** Returns the task to launch the application. */ @Nullable - LaunchTask getApplicationLaunchTask( + BlazeLaunchTask getApplicationLaunchTask( LaunchOptions launchOptions, @Nullable Integer userId, - @NotNull String contributorsAmStartOptions, - LaunchStatus launchStatus) + @NotNull String contributorsAmStartOptions) throws ExecutionException; /** Returns the task to connect the debugger. */ @Nullable ConnectDebuggerTask getDebuggerTask( - AndroidDebugger androidDebugger, AndroidDebuggerState androidDebuggerState) - throws ExecutionException; + AndroidDebugger androidDebugger, AndroidDebuggerState androidDebuggerState); @Nullable - Integer getUserId(IDevice device, ConsolePrinter consolePrinter) throws ExecutionException; + Integer getUserId(IDevice device) throws ExecutionException; String getAmStartOptions(); diff --git a/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/runner/BlazeWrapperForAndroidConfigurationExecutor.java b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/runner/BlazeWrapperForAndroidConfigurationExecutor.java new file mode 100644 index 00000000000..be6f1f8552c --- /dev/null +++ b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/runner/BlazeWrapperForAndroidConfigurationExecutor.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.runner; + +import com.android.tools.idea.run.DeviceFutures; +import com.android.tools.idea.run.configuration.execution.AndroidConfigurationExecutor; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.execution.ui.RunContentDescriptor; +import com.intellij.openapi.progress.ProgressIndicator; +import org.jetbrains.annotations.NotNull; + +/** Implementation of {@code AndroidConfigurationExecutor} specific for Blaze project. */ +public class BlazeWrapperForAndroidConfigurationExecutor implements AndroidConfigurationExecutor { + private final AndroidConfigurationExecutor delegateExecutor; + + BlazeWrapperForAndroidConfigurationExecutor(@NotNull AndroidConfigurationExecutor executor) { + delegateExecutor = executor; + } + + @NotNull + @Override + public RunConfiguration getConfiguration() { + return delegateExecutor.getConfiguration(); + } + + @NotNull + @Override + public DeviceFutures getDeviceFutures() { + return delegateExecutor.getDeviceFutures(); + } + + @NotNull + @Override + public RunContentDescriptor run(@NotNull ProgressIndicator indicator) throws ExecutionException { + return delegateExecutor.run(indicator); + } + + @NotNull + @Override + public RunContentDescriptor debug(@NotNull ProgressIndicator indicator) + throws ExecutionException { + return delegateExecutor.debug(indicator); + } + + @NotNull + @Override + public RunContentDescriptor applyChanges(@NotNull ProgressIndicator indicator) { + throw new RuntimeException("Apply code changes is not supported for blaze"); + } + + @NotNull + @Override + public RunContentDescriptor applyCodeChanges(@NotNull ProgressIndicator indicator) { + throw new RuntimeException("Apply changes is not supported for blaze"); + } +} diff --git a/aswb/sdkcompat/as212/com/google/idea/blaze/android/run/runner/LaunchOptionsCompat.java b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/runner/LaunchOptionsCompat.java similarity index 100% rename from aswb/sdkcompat/as212/com/google/idea/blaze/android/run/runner/LaunchOptionsCompat.java rename to aswb/sdkcompat/as223/com/google/idea/blaze/android/run/runner/LaunchOptionsCompat.java diff --git a/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/runner/MobileInstallApplicationDeployer.java b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/runner/MobileInstallApplicationDeployer.java new file mode 100644 index 00000000000..dad762309be --- /dev/null +++ b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/runner/MobileInstallApplicationDeployer.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.runner; + +import com.android.ddmlib.IDevice; +import com.android.tools.deployer.ApkParser; +import com.android.tools.deployer.Deployer; +import com.android.tools.deployer.DeployerException; +import com.android.tools.deployer.model.Apk; +import com.android.tools.deployer.model.App; +import com.android.tools.idea.execution.common.ApplicationDeployer; +import com.android.tools.idea.execution.common.DeployOptions; +import com.android.tools.idea.run.ApkFileUnit; +import com.android.tools.idea.run.ApkInfo; +import com.android.tools.idea.run.configuration.execution.AdbCommandCaptureLoggerWithConsole; +import com.intellij.execution.ui.ConsoleView; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.ProgressIndicator; +import java.nio.file.Path; +import java.util.List; +import java.util.stream.Collectors; +import org.jetbrains.annotations.NotNull; + +/** Deploys mobile install application. */ +public class MobileInstallApplicationDeployer implements ApplicationDeployer { + private static final Logger LOG = Logger.getInstance(MobileInstallApplicationDeployer.class); + private final ConsoleView myConsole; + + public MobileInstallApplicationDeployer(ConsoleView console) { + myConsole = console; + } + + @NotNull + @Override + public Deployer.Result fullDeploy( + @NotNull IDevice device, + @NotNull ApkInfo apkInfo, + @NotNull DeployOptions deployOptions, + ProgressIndicator indicator) + throws DeployerException { + final List apkPaths = + apkInfo.getFiles().stream() + .map(ApkFileUnit::getApkPath) + .map(Path::toString) + .collect(Collectors.toList()); + final List apks = new ApkParser().parsePaths(apkPaths); + App app = + new App( + apkInfo.getApplicationId(), + apks, + device, + new AdbCommandCaptureLoggerWithConsole(LOG, myConsole)); + return new Deployer.Result(false, false, false, app); + } + + @NotNull + @Override + public Deployer.Result applyChangesDeploy( + @NotNull IDevice device, + @NotNull ApkInfo app, + @NotNull DeployOptions deployOptions, + ProgressIndicator indicator) + throws DeployerException { + throw new RuntimeException("Apply changes is not supported for mobile-install"); + } + + @NotNull + @Override + public Deployer.Result applyCodeChangesDeploy( + @NotNull IDevice device, + @NotNull ApkInfo app, + @NotNull DeployOptions deployOptions, + ProgressIndicator indicator) + throws DeployerException { + throw new RuntimeException("Apply code changes is not supported for mobile-install"); + } +} diff --git a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestProgramRunner.java b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/test/BlazeAndroidTestProgramRunner.java similarity index 54% rename from aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestProgramRunner.java rename to aswb/sdkcompat/as223/com/google/idea/blaze/android/run/test/BlazeAndroidTestProgramRunner.java index bf54ebf2c6c..eab7b24890f 100644 --- a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestProgramRunner.java +++ b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/test/BlazeAndroidTestProgramRunner.java @@ -15,25 +15,25 @@ */ package com.google.idea.blaze.android.run.test; -import com.android.tools.idea.run.AndroidProgramRunner; -import com.android.tools.idea.run.AndroidSessionInfo; +import com.android.tools.idea.run.configuration.AndroidConfigurationProgramRunner; +import com.android.tools.idea.run.configuration.execution.AndroidConfigurationExecutor; import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationHandler; import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration; +import com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType; import com.intellij.execution.ExecutionException; -import com.intellij.execution.ExecutionResult; -import com.intellij.execution.configurations.RunConfiguration; import com.intellij.execution.configurations.RunProfile; import com.intellij.execution.configurations.RunProfileState; import com.intellij.execution.executors.DefaultDebugExecutor; import com.intellij.execution.executors.DefaultRunExecutor; -import com.intellij.execution.process.ProcessHandler; import com.intellij.execution.runners.ExecutionEnvironment; -import com.intellij.execution.runners.RunContentBuilder; import com.intellij.execution.ui.RunContentDescriptor; -import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.progress.ProgressIndicator; +import java.util.Collections; +import java.util.List; +import org.jetbrains.annotations.NotNull; /** Program runner for configurations from {@link BlazeAndroidTestRunConfigurationHandler}. */ -public class BlazeAndroidTestProgramRunner extends AndroidProgramRunner { +public class BlazeAndroidTestProgramRunner extends AndroidConfigurationProgramRunner { @Override public boolean canRun(String executorId, RunProfile profile) { BlazeAndroidRunConfigurationHandler handler = @@ -49,37 +49,35 @@ public boolean canRun(String executorId, RunProfile profile) { } @Override - protected boolean canRunWithMultipleDevices(String executorId) { - return false; + public String getRunnerId() { + return "AndroidTestProgramRunner"; } @Override - protected RunContentDescriptor doExecute( - final RunProfileState state, final ExecutionEnvironment env) throws ExecutionException { - FileDocumentManager.getInstance().saveAllDocuments(); - ExecutionResult result = state.execute(env.getExecutor(), this); - RunContentDescriptor descriptor = - new RunContentBuilder(result, env).showRunContent(env.getContentToReuse()); - if (descriptor != null) { - ProcessHandler processHandler = descriptor.getProcessHandler(); - assert processHandler != null; - RunProfile runProfile = env.getRunProfile(); - RunConfiguration runConfiguration = - (runProfile instanceof RunConfiguration) ? (RunConfiguration) runProfile : null; - AndroidSessionInfo sessionInfo = - AndroidSessionInfo.create( - processHandler, - descriptor, - runConfiguration, - env.getExecutor().getId(), - env.getExecutor().getActionName(), - env.getExecutionTarget()); - processHandler.putUserData(AndroidSessionInfo.KEY, sessionInfo); - } - return descriptor; + protected boolean canRunWithMultipleDevices(@NotNull String executorId) { + return true; } + + @NotNull @Override - public String getRunnerId() { - return "AndroidTestProgramRunner"; + protected List getSupportedConfigurationTypeIds() { + return Collections.singletonList(BlazeCommandRunConfigurationType.getInstance().getId()); + } + + @NotNull + @Override + protected RunContentDescriptor run( + @NotNull ExecutionEnvironment environment, + @NotNull RunProfileState state, + @NotNull ProgressIndicator indicator) + throws ExecutionException { + final AndroidConfigurationExecutor state1 = (AndroidConfigurationExecutor) state; + if (DefaultDebugExecutor.EXECUTOR_ID.equals(environment.getExecutor().getId())) { + return state1.debug(indicator); + } + if (DefaultRunExecutor.EXECUTOR_ID.equals(environment.getExecutor().getId())) { + return state1.run(indicator); + } + throw new RuntimeException("Unsupported executor"); } } diff --git a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationHandler.java b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationHandler.java similarity index 81% rename from aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationHandler.java rename to aswb/sdkcompat/as223/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationHandler.java index 755eedeb5ab..050ddc03fee 100644 --- a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationHandler.java +++ b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationHandler.java @@ -20,12 +20,15 @@ import com.android.tools.idea.run.ValidationError; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; +import com.google.idea.blaze.android.run.ApkBuildStepProvider; import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationCommonState; import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationHandler; import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationValidationUtil; import com.google.idea.blaze.android.run.LaunchMetrics; +import com.google.idea.blaze.android.run.runner.ApkBuildStep; import com.google.idea.blaze.android.run.runner.BlazeAndroidRunConfigurationRunner; import com.google.idea.blaze.android.run.runner.BlazeAndroidRunContext; +import com.google.idea.blaze.android.run.runner.FullApkBuildStep; import com.google.idea.blaze.android.run.test.BlazeAndroidTestLaunchMethodsProvider.AndroidTestLaunchMethod; import com.google.idea.blaze.base.command.BlazeCommandName; import com.google.idea.blaze.base.command.BlazeInvocationContext; @@ -40,6 +43,7 @@ import com.google.idea.blaze.base.settings.Blaze; import com.google.idea.blaze.base.sync.data.BlazeDataStorage; import com.google.idea.blaze.base.sync.projectstructure.ModuleFinder; +import com.google.idea.blaze.java.AndroidBlazeRules; import com.intellij.execution.ExecutionException; import com.intellij.execution.Executor; import com.intellij.execution.JavaExecutionUtil; @@ -108,18 +112,15 @@ public BlazeCommandRunConfigurationRunner createRunner( // We collect metrics from a few different locations. In order to tie them all // together, we create a unique launch id. String launchId = LaunchMetrics.newLaunchId(); + Label label = Label.create(configuration.getSingleTarget().toString()); + + ApkBuildStep buildStep = + getTestBuildStep( + project, configState, configuration, blazeFlags, exeFlags, launchId, label); BlazeAndroidRunContext runContext = new BlazeAndroidTestRunContext( - project, - facet, - configuration, - env, - configState, - Label.create(configuration.getSingleTarget().toString()), - blazeFlags, - exeFlags, - launchId); + project, facet, configuration, env, configState, label, blazeFlags, buildStep); LaunchMetrics.logTestLaunch( launchId, configState.getLaunchMethod().name(), env.getExecutor().getId()); @@ -127,6 +128,35 @@ public BlazeCommandRunConfigurationRunner createRunner( return new BlazeAndroidRunConfigurationRunner(module, runContext, configuration); } + private static ApkBuildStep getTestBuildStep( + Project project, + BlazeAndroidTestRunConfigurationState configState, + BlazeCommandRunConfiguration configuration, + ImmutableList blazeFlags, + ImmutableList exeFlags, + String launchId, + Label label) + throws ExecutionException { + if (configuration.getTargetKind() + == AndroidBlazeRules.RuleTypes.ANDROID_INSTRUMENTATION_TEST.getKind()) { + boolean useMobileInstall = + AndroidTestLaunchMethod.MOBILE_INSTALL.equals(configState.getLaunchMethod()); + return ApkBuildStepProvider.getInstance(Blaze.getBuildSystemName(project)) + .getAitBuildStep( + project, + useMobileInstall, + /* nativeDebuggingEnabled= */ false, + label, + blazeFlags, + exeFlags, + launchId); + } else { + // TODO(b/248317444): This path is only invoked for the deprecated {@code android_test} + // targets, and should eventually be removed. + return new FullApkBuildStep(project, label, blazeFlags, /* nativeDebuggingEnabled= */ false); + } + } + @Override public final void checkConfiguration() throws RuntimeConfigurationException { BlazeAndroidRunConfigurationValidationUtil.throwTopConfigurationError(validate()); diff --git a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunContextBase.java b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunContext.java similarity index 59% rename from aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunContextBase.java rename to aswb/sdkcompat/as223/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunContext.java index 066c9615043..3caea8d6d81 100644 --- a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunContextBase.java +++ b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunContext.java @@ -15,39 +15,37 @@ */ package com.google.idea.blaze.android.run.test; +import static com.android.tools.idea.run.tasks.DefaultConnectDebuggerTaskKt.getBaseDebuggerTask; +import static com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryNormalBuildRunContextBase.getApkInfoToInstall; + import com.android.ddmlib.IDevice; +import com.android.tools.idea.execution.common.debug.AndroidDebugger; +import com.android.tools.idea.execution.common.debug.AndroidDebuggerState; import com.android.tools.idea.run.ApkProvider; import com.android.tools.idea.run.ApkProvisionException; import com.android.tools.idea.run.ApplicationIdProvider; -import com.android.tools.idea.run.ConsolePrinter; import com.android.tools.idea.run.ConsoleProvider; import com.android.tools.idea.run.LaunchOptions; -import com.android.tools.idea.run.editor.AndroidDebugger; -import com.android.tools.idea.run.editor.AndroidDebuggerState; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; +import com.android.tools.idea.run.blaze.BlazeLaunchTasksProvider; import com.android.tools.idea.run.editor.ProfilerState; -import com.android.tools.idea.run.tasks.LaunchTask; -import com.android.tools.idea.run.tasks.LaunchTasksProvider; -import com.android.tools.idea.run.util.LaunchStatus; +import com.android.tools.idea.run.tasks.ConnectDebuggerTask; +import com.android.tools.idea.run.tasks.DeployTasksCompat; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; -import com.google.idea.blaze.android.run.binary.mobileinstall.MobileInstallBuildStep; import com.google.idea.blaze.android.run.deployinfo.BlazeAndroidDeployInfo; import com.google.idea.blaze.android.run.deployinfo.BlazeApkProviderService; import com.google.idea.blaze.android.run.runner.ApkBuildStep; import com.google.idea.blaze.android.run.runner.BlazeAndroidDeviceSelector; import com.google.idea.blaze.android.run.runner.BlazeAndroidLaunchTasksProvider; import com.google.idea.blaze.android.run.runner.BlazeAndroidRunContext; -import com.google.idea.blaze.android.run.runner.BlazeInstrumentationTestApkBuildStep; -import com.google.idea.blaze.android.run.runner.FullApkBuildStep; 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.BlazeTestUiSession; -import com.google.idea.blaze.base.run.smrunner.TestUiSessionProvider; -import com.google.idea.blaze.java.AndroidBlazeRules; +import com.google.idea.blaze.base.run.testlogs.BlazeTestResultHolder; import com.intellij.execution.ExecutionException; import com.intellij.execution.Executor; -import com.intellij.execution.executors.DefaultDebugExecutor; import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.openapi.project.Project; import java.util.List; @@ -55,7 +53,7 @@ import org.jetbrains.android.facet.AndroidFacet; /** Run context for android_test. */ -abstract class BlazeAndroidTestRunContextBase implements BlazeAndroidRunContext { +public class BlazeAndroidTestRunContext implements BlazeAndroidRunContext { protected final Project project; protected final AndroidFacet facet; protected final BlazeCommandRunConfiguration runConfiguration; @@ -68,8 +66,9 @@ abstract class BlazeAndroidTestRunContextBase implements BlazeAndroidRunContext protected final ApkBuildStep buildStep; protected final ApplicationIdProvider applicationIdProvider; protected final ApkProvider apkProvider; + private final BlazeTestResultHolder testResultsHolder = new BlazeTestResultHolder(); - BlazeAndroidTestRunContextBase( + public BlazeAndroidTestRunContext( Project project, AndroidFacet facet, BlazeCommandRunConfiguration runConfiguration, @@ -77,52 +76,31 @@ abstract class BlazeAndroidTestRunContextBase implements BlazeAndroidRunContext BlazeAndroidTestRunConfigurationState configState, Label label, ImmutableList blazeFlags, - ImmutableList exeFlags, - String launchId) { + ApkBuildStep buildStep) { this.project = project; this.facet = facet; this.runConfiguration = runConfiguration; this.env = env; this.label = label; this.configState = configState; - - if (configState.getLaunchMethod().equals(AndroidTestLaunchMethod.MOBILE_INSTALL)) { - this.buildStep = new MobileInstallBuildStep(project, label, blazeFlags, exeFlags, launchId); - } else if (runConfiguration.getTargetKind() - == AndroidBlazeRules.RuleTypes.ANDROID_INSTRUMENTATION_TEST.getKind()) { - // android_instrumentation_test builds both test and app target APKs. - this.buildStep = new BlazeInstrumentationTestApkBuildStep(project, label, blazeFlags); - } else { - this.buildStep = new FullApkBuildStep(project, label, blazeFlags); - } - - this.applicationIdProvider = new BlazeAndroidTestApplicationIdProvider(buildStep); - this.apkProvider = BlazeApkProviderService.getInstance().getApkProvider(project, buildStep); - - BlazeTestUiSession testUiSession = - canUseTestUi(env.getExecutor()) - ? TestUiSessionProvider.getInstance(env.getProject()) - .getTestUiSession(ImmutableList.of(label)) - : null; - if (testUiSession != null) { - this.blazeFlags = - ImmutableList.builder() - .addAll(testUiSession.getBlazeFlags()) - .addAll(blazeFlags) - .build(); - } else { - this.blazeFlags = blazeFlags; + this.buildStep = buildStep; + this.blazeFlags = blazeFlags; + switch (configState.getLaunchMethod()) { + case MOBILE_INSTALL: + case NON_BLAZE: + consoleProvider = new AitIdeTestConsoleProvider(runConfiguration, configState); + break; + case BLAZE_TEST: + BlazeTestUiSession session = + BlazeTestUiSession.create(ImmutableList.of(), testResultsHolder); + this.consoleProvider = new AitBlazeTestConsoleProvider(project, runConfiguration, session); + break; + default: + throw new IllegalStateException( + "Unsupported launch method " + configState.getLaunchMethod()); } - this.consoleProvider = - new AndroidTestConsoleProvider(project, runConfiguration, configState, testUiSession); - } - - private static boolean canUseTestUi(Executor executor) { - return !isDebugging(executor); - } - - private static boolean isDebugging(Executor executor) { - return executor instanceof DefaultDebugExecutor; + applicationIdProvider = new BlazeAndroidTestApplicationIdProvider(buildStep); + apkProvider = BlazeApkProviderService.getInstance().getApkProvider(project, buildStep); } @Override @@ -141,7 +119,7 @@ public ConsoleProvider getConsoleProvider() { } @Override - public ApplicationIdProvider getApplicationIdProvider() throws ExecutionException { + public ApplicationIdProvider getApplicationIdProvider() { return applicationIdProvider; } @@ -151,53 +129,50 @@ public ApkBuildStep getBuildStep() { return buildStep; } - // @Override #api211 + @Override public ProfilerState getProfileState() { return null; } @Override - public LaunchTasksProvider getLaunchTasksProvider(LaunchOptions.Builder launchOptionsBuilder) + public BlazeLaunchTasksProvider getLaunchTasksProvider(LaunchOptions.Builder launchOptionsBuilder) throws ExecutionException { return new BlazeAndroidLaunchTasksProvider( project, this, applicationIdProvider, launchOptionsBuilder); } - // @Override #api212 - @Nullable - public LaunchTask getApplicationLaunchTask( - LaunchOptions launchOptions, - @Nullable Integer userId, - String contributorsAmStartOptions, - LaunchStatus launchStatus) + @Override + public ImmutableList getDeployTasks(IDevice device, LaunchOptions launchOptions) throws ExecutionException { - return getApplicationLaunchTask( - launchOptions, userId, contributorsAmStartOptions, null, null, launchStatus); + if (configState.getLaunchMethod() != AndroidTestLaunchMethod.NON_BLAZE) { + return ImmutableList.of(); + } + return ImmutableList.of( + DeployTasksCompat.createDeployTask( + project, getApkInfoToInstall(device, launchOptions, apkProvider), launchOptions)); } - @SuppressWarnings({"rawtypes"}) // Raw type from upstream. + @Override @Nullable - public LaunchTask getApplicationLaunchTask( - LaunchOptions launchOptions, - @Nullable Integer userId, - String contributorsAmStartOptions, - AndroidDebugger androidDebugger, - AndroidDebuggerState androidDebuggerState, - LaunchStatus launchStatus) + public BlazeLaunchTask getApplicationLaunchTask( + LaunchOptions launchOptions, @Nullable Integer userId, String contributorsAmStartOptions) throws ExecutionException { switch (configState.getLaunchMethod()) { case BLAZE_TEST: - return new BlazeAndroidTestLaunchTask( - project, - label, - blazeFlags, + BlazeAndroidTestFilter testFilter = new BlazeAndroidTestFilter( configState.getTestingType(), configState.getClassName(), configState.getMethodName(), - configState.getPackageName()), + configState.getPackageName()); + return new BlazeAndroidTestLaunchTask( + project, + label, + blazeFlags, + testFilter, this, - launchOptions.isDebug()); + launchOptions.isDebug(), + testResultsHolder); case NON_BLAZE: case MOBILE_INSTALL: BlazeAndroidDeployInfo deployInfo; @@ -207,7 +182,21 @@ public LaunchTask getApplicationLaunchTask( throw new ExecutionException(e); } return StockAndroidTestLaunchTask.getStockTestLaunchTask( - configState, applicationIdProvider, launchOptions.isDebug(), deployInfo, launchStatus); + configState, applicationIdProvider, launchOptions.isDebug(), deployInfo, project); + } + throw new AssertionError(); + } + + @Override + @SuppressWarnings({"unchecked", "rawtypes"}) // Raw type from upstream. + public ConnectDebuggerTask getDebuggerTask( + AndroidDebugger androidDebugger, AndroidDebuggerState androidDebuggerState) { + switch (configState.getLaunchMethod()) { + case BLAZE_TEST: + return new ConnectBlazeTestDebuggerTask(this, androidDebugger, androidDebuggerState); + case NON_BLAZE: + case MOBILE_INSTALL: + return getBaseDebuggerTask(androidDebugger, androidDebuggerState, env, facet, 30); } throw new AssertionError(); } @@ -222,9 +211,14 @@ void addLaunchTaskCompleteListener(Runnable runnable) { launchTaskCompleteListeners.add(runnable); } + @Override + public Executor getExecutor() { + return env.getExecutor(); + } + @Nullable @Override - public Integer getUserId(IDevice device, ConsolePrinter consolePrinter) { + public Integer getUserId(IDevice device) { return null; } diff --git a/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/test/ConnectBlazeTestDebuggerTask.java b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/test/ConnectBlazeTestDebuggerTask.java new file mode 100644 index 00000000000..3ef2a6c9493 --- /dev/null +++ b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/test/ConnectBlazeTestDebuggerTask.java @@ -0,0 +1,105 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.test; + +import com.android.ddmlib.IDevice; +import com.android.tools.idea.execution.common.debug.AndroidDebugger; +import com.android.tools.idea.execution.common.debug.AndroidDebuggerState; +import com.android.tools.idea.execution.common.debug.DebugSessionStarter; +import com.android.tools.idea.run.tasks.ConnectDebuggerTask; +import com.intellij.execution.process.ProcessHandler; +import com.intellij.execution.process.ProcessOutputTypes; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.ui.ConsoleView; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.xdebugger.impl.XDebugSessionImpl; +import java.io.OutputStream; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** Connects the blaze debugger during execution. */ +class ConnectBlazeTestDebuggerTask implements ConnectDebuggerTask { + + private final BlazeAndroidTestRunContext runContext; + private final AndroidDebugger myAndroidDebugger; + private final S myAndroidDebuggerState; + + public ConnectBlazeTestDebuggerTask( + BlazeAndroidTestRunContext runContext, + AndroidDebugger androidDebugger, + S androidDebuggerState) { + this.runContext = runContext; + myAndroidDebugger = androidDebugger; + myAndroidDebuggerState = androidDebuggerState; + } + + /** + * Wires up listeners to automatically reconnect the debugger for each test method. When you + * `blaze test` an android_test in debug mode, it kills the instrumentation process between each + * test method, disconnecting the debugger. We listen for the start of a new method waiting for a + * debugger, and reconnect. TODO: Support stopping Blaze from the UI. This is hard because we have + * no way to distinguish process handler termination/debug session ending initiated by the user. + * + * @return Promise with debug session or error + */ + @Override + public @NotNull XDebugSessionImpl perform( + @NotNull IDevice device, + @NotNull String applicationId, + @NotNull ExecutionEnvironment environment, + @NotNull ProgressIndicator progressIndicator, + ConsoleView console) { + final ProcessHandler masterProcessHandler = + new ProcessHandler() { + + @Override + protected void destroyProcessImpl() { + notifyProcessTerminated(0); + } + + @Override + protected void detachProcessImpl() { + notifyProcessDetached(); + } + + @Override + public boolean detachIsDefault() { + return false; + } + + @Override + public @Nullable OutputStream getProcessInput() { + return null; + } + }; + runContext.addLaunchTaskCompleteListener( + () -> { + masterProcessHandler.notifyTextAvailable( + "Test run completed.\n", ProcessOutputTypes.STDOUT); + masterProcessHandler.detachProcess(); + }); + return DebugSessionStarter.INSTANCE.attachReattachingDebuggerToStartedProcess( + device, + applicationId, + masterProcessHandler, + environment, + myAndroidDebugger, + myAndroidDebuggerState, + progressIndicator, + console, + Long.MAX_VALUE); + } +} diff --git a/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/testrecorder/TestRecorderBlazeCommandRunConfigurationProxy.java b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/testrecorder/TestRecorderBlazeCommandRunConfigurationProxy.java new file mode 100644 index 00000000000..1a7f9a4f621 --- /dev/null +++ b/aswb/sdkcompat/as223/com/google/idea/blaze/android/run/testrecorder/TestRecorderBlazeCommandRunConfigurationProxy.java @@ -0,0 +1,43 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.testrecorder; + +import com.android.annotations.Nullable; +import com.android.ddmlib.IDevice; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.idea.blaze.android.run.runner.BlazeAndroidRunConfigurationRunner; +import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration; +import com.intellij.execution.runners.ExecutionEnvironment; +import java.util.List; + +/** Compat class for implementing TestRecorderBlazeCommandRunConfigurationProxy. */ +public class TestRecorderBlazeCommandRunConfigurationProxy + extends TestRecorderBlazeCommandRunConfigurationProxyBase { + + public TestRecorderBlazeCommandRunConfigurationProxy( + BlazeCommandRunConfiguration baseConfiguration) { + super(baseConfiguration); + } + + @Override + @Nullable + public List> getDeviceFutures(ExecutionEnvironment environment) { + return environment + .getCopyableUserData(BlazeAndroidRunConfigurationRunner.DEVICE_SESSION_KEY) + .deviceFutures + .get(); + } +} diff --git a/aswb/sdkcompat/as213/com/google/idea/blaze/android/sync/model/idea/BlazeAndroidModel.java b/aswb/sdkcompat/as223/com/google/idea/blaze/android/sync/model/idea/BlazeAndroidModel.java similarity index 100% rename from aswb/sdkcompat/as213/com/google/idea/blaze/android/sync/model/idea/BlazeAndroidModel.java rename to aswb/sdkcompat/as223/com/google/idea/blaze/android/sync/model/idea/BlazeAndroidModel.java diff --git a/aswb/sdkcompat/as231/com/android/tools/configurations/ConfigurationCompat.java b/aswb/sdkcompat/as231/com/android/tools/configurations/ConfigurationCompat.java new file mode 100644 index 00000000000..3d51e831ad7 --- /dev/null +++ b/aswb/sdkcompat/as231/com/android/tools/configurations/ConfigurationCompat.java @@ -0,0 +1,32 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR 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.configurations; + +import com.android.sdklib.devices.Device; + +/** Compat class for Configuration. */ +public class ConfigurationCompat { + + private Configuration configuration; + + public ConfigurationCompat(Configuration configuration) { + this.configuration = configuration; + } + + public void setDevice(Device device, boolean preserveState) { + configuration.setDevice(device, preserveState); + } +} diff --git a/aswb/sdkcompat/as231/com/android/tools/idea/model/AndroidManifestIndexCompat.java b/aswb/sdkcompat/as231/com/android/tools/idea/model/AndroidManifestIndexCompat.java new file mode 100644 index 00000000000..27f94f054c6 --- /dev/null +++ b/aswb/sdkcompat/as231/com/android/tools/idea/model/AndroidManifestIndexCompat.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR 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.model; + +/** Compat class for {@link com.android.tools.idea.model.AndroidManifestIndex}. */ +public class AndroidManifestIndexCompat { + + private AndroidManifestIndexCompat() {} + + /** {@code indexEnabled} was removed in Studio 2021.2. #api211 */ + public static boolean indexEnabled() { + return true; + } +} diff --git a/aswb/sdkcompat/as231/com/android/tools/idea/progress/StudioLoggerProgressIndicatorCompat.java b/aswb/sdkcompat/as231/com/android/tools/idea/progress/StudioLoggerProgressIndicatorCompat.java new file mode 100644 index 00000000000..e581bd190d8 --- /dev/null +++ b/aswb/sdkcompat/as231/com/android/tools/idea/progress/StudioLoggerProgressIndicatorCompat.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR 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.progress; + +/** Compat layer for StudioLoggerProgressIndicator which moved packages in 2021.2. #api211 */ +public class StudioLoggerProgressIndicatorCompat extends StudioLoggerProgressIndicator { + public StudioLoggerProgressIndicatorCompat(Class c) { + super(c); + } +} diff --git a/aswb/sdkcompat/as231/com/android/tools/idea/rendering/RenderErrorContributorCompat.java b/aswb/sdkcompat/as231/com/android/tools/idea/rendering/RenderErrorContributorCompat.java new file mode 100644 index 00000000000..f869496110c --- /dev/null +++ b/aswb/sdkcompat/as231/com/android/tools/idea/rendering/RenderErrorContributorCompat.java @@ -0,0 +1,29 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR 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.rendering; + +import com.android.tools.idea.ui.designer.EditorDesignSurface; +import com.android.tools.rendering.RenderResultCompat; +import com.intellij.openapi.actionSystem.DataContext; +import groovyjarjarantlr4.v4.runtime.misc.Nullable; + +/** Contribute blaze specific render errors. */ +public class RenderErrorContributorCompat extends RenderErrorContributor { + public RenderErrorContributorCompat( + EditorDesignSurface surface, RenderResultCompat result, @Nullable DataContext dataContext) { + super(surface, result.get()); + } +} diff --git a/aswb/sdkcompat/as231/com/android/tools/idea/run/tasks/DeployTasksCompat.java b/aswb/sdkcompat/as231/com/android/tools/idea/run/tasks/DeployTasksCompat.java new file mode 100644 index 00000000000..8c5bde9785e --- /dev/null +++ b/aswb/sdkcompat/as231/com/android/tools/idea/run/tasks/DeployTasksCompat.java @@ -0,0 +1,52 @@ +/* + * Copyright 2020 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR 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.run.tasks; + +import com.android.tools.deployer.DeployerException; +import com.android.tools.idea.execution.common.AndroidExecutionException; +import com.android.tools.idea.execution.common.DeployOptions; +import com.android.tools.idea.run.ApkInfo; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; +import com.google.idea.common.experiments.BoolExperiment; +import com.intellij.openapi.project.Project; +import java.util.Collection; +import java.util.List; + +/** Compat class for {@link DeployTask} */ +public class DeployTasksCompat { + private static final BoolExperiment updateCodeViaJvmti = + new BoolExperiment("android.apply.changes", false); + + private DeployTasksCompat() {} + + public static BlazeLaunchTask createDeployTask( + Project project, Collection packages, DeployOptions deployOptions) { + return launchContext -> { + try { + List unused = + new DeployTask( + project, + packages, + deployOptions.getPmInstallFlags(), + deployOptions.getInstallOnAllUsers(), + deployOptions.getAlwaysInstallWithPm()) + .run(launchContext.getDevice(), launchContext.getProgressIndicator()); + } catch (DeployerException e) { + throw new AndroidExecutionException(e.getId(), e.getMessage()); + } + }; + } +} diff --git a/aswb/sdkcompat/as231/com/android/tools/rendering/HtmlLinkManagerCompat.java b/aswb/sdkcompat/as231/com/android/tools/rendering/HtmlLinkManagerCompat.java new file mode 100644 index 00000000000..28ca9471258 --- /dev/null +++ b/aswb/sdkcompat/as231/com/android/tools/rendering/HtmlLinkManagerCompat.java @@ -0,0 +1,28 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR 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.rendering; + +import java.io.File; +import org.jetbrains.annotations.NotNull; + +/** Compat class for {@link HtmlLinkManager} */ +public final class HtmlLinkManagerCompat { + public static String createFilePositionUrl(@NotNull File file, int line, int column) { + return HtmlLinkManager.createFilePositionUrl(file, line, column); + } + + private HtmlLinkManagerCompat() {} +} diff --git a/aswb/sdkcompat/as231/com/android/tools/rendering/RenderLoggerCompat.java b/aswb/sdkcompat/as231/com/android/tools/rendering/RenderLoggerCompat.java new file mode 100644 index 00000000000..96af24f48d3 --- /dev/null +++ b/aswb/sdkcompat/as231/com/android/tools/rendering/RenderLoggerCompat.java @@ -0,0 +1,48 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR 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.rendering; + +import java.util.Map; +import java.util.Set; + +/** Compat class for RenderLogger. */ +public class RenderLoggerCompat { + private final RenderLogger renderLogger; + + public RenderLoggerCompat(RenderResultCompat result) { + renderLogger = result.getLogger(); + } + + public RenderLogger get() { + return renderLogger; + } + + public boolean hasErrors() { + return renderLogger.hasErrors(); + } + + public Map getBrokenClasses() { + return renderLogger.getBrokenClasses(); + } + + public Set getMissingClasses() { + return renderLogger.getMissingClasses(); + } + + public static void resetFidelityErrorsFilters() { + RenderLogger.resetFidelityErrorsFilters(); + } +} diff --git a/aswb/sdkcompat/as231/com/android/tools/rendering/RenderResultCompat.java b/aswb/sdkcompat/as231/com/android/tools/rendering/RenderResultCompat.java new file mode 100644 index 00000000000..75fc9c912b9 --- /dev/null +++ b/aswb/sdkcompat/as231/com/android/tools/rendering/RenderResultCompat.java @@ -0,0 +1,83 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR 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.rendering; + +import com.android.ide.common.rendering.api.Result; +import com.android.tools.idea.rendering.RenderErrorContributor; +import com.android.tools.idea.rendering.RenderErrorModelFactory; +import com.android.tools.idea.rendering.RenderResults; +import com.android.tools.idea.rendering.errors.ui.RenderErrorModel; +import com.android.tools.idea.ui.designer.EditorDesignSurface; +import com.android.tools.rendering.imagepool.ImagePool.Image; +import com.google.idea.blaze.android.rendering.BlazeRenderErrorContributor; +import com.google.idea.blaze.base.settings.Blaze; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiFile; +import org.jetbrains.annotations.Nullable; + +/** Compat class for {@link RenderResult} */ +public final class RenderResultCompat { + private RenderResult result; + + public RenderResultCompat(RenderResult result) { + this.result = result; + } + + public static RenderResultCompat createBlank(PsiFile file) { + return new RenderResultCompat(RenderResults.createBlank(file)); + } + + public RenderErrorModel createErrorModel() { + return RenderErrorModelFactory.createErrorModel(null, result, null); + } + + public RenderResult get() { + return result; + } + + public RenderLogger getLogger() { + return result.getLogger(); + } + + public Module getModule() { + return result.getModule(); + } + + public Result getRenderResult() { + return result.getRenderResult(); + } + + public Image getRenderedImage() { + return result.getRenderedImage(); + } + + private RenderResultCompat() {} + + /** Extension to provide {@link BlazeRenderErrorContributor}. */ + public static class BlazeProvider extends RenderErrorContributor.Provider { + @Override + public boolean isApplicable(Project project) { + return Blaze.isBlazeProject(project); + } + + @Override + public RenderErrorContributor getContributor( + @Nullable EditorDesignSurface surface, RenderResult result) { + return new BlazeRenderErrorContributor(surface, new RenderResultCompat(result), null); + } + } +} diff --git a/aswb/sdkcompat/as231/com/android/tools/rendering/RenderServiceCompat.java b/aswb/sdkcompat/as231/com/android/tools/rendering/RenderServiceCompat.java new file mode 100644 index 00000000000..de22b6df36d --- /dev/null +++ b/aswb/sdkcompat/as231/com/android/tools/rendering/RenderServiceCompat.java @@ -0,0 +1,27 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR 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.rendering; + +/** Compat class for {@link RenderService} */ +public class RenderServiceCompat { + public static void shutdownRenderExecutor(long l) { + RenderService.shutdownRenderExecutor(l); + } + + public static void initializeRenderExecutor() { + RenderService.initializeRenderExecutor(); + } +} diff --git a/aswb/sdkcompat/as231/com/google/idea/blaze/android/cppimpl/debug/BlazeAutoAndroidDebugger.java b/aswb/sdkcompat/as231/com/google/idea/blaze/android/cppimpl/debug/BlazeAutoAndroidDebugger.java new file mode 100644 index 00000000000..7aa210d0595 --- /dev/null +++ b/aswb/sdkcompat/as231/com/google/idea/blaze/android/cppimpl/debug/BlazeAutoAndroidDebugger.java @@ -0,0 +1,42 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.cppimpl.debug; + +import com.android.ddmlib.Client; +import com.android.tools.ndk.run.editor.AutoAndroidDebuggerState; +import com.intellij.execution.ExecutionException; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import com.intellij.xdebugger.XDebugSession; + +/** Shim for #api212 compat. */ +public class BlazeAutoAndroidDebugger extends BlazeAutoAndroidDebuggerBase { + @Override + public XDebugSession attachToClient( + Project project, Client client, AutoAndroidDebuggerState state) throws ExecutionException { + if (isNativeProject(project)) { + log.info("Project has native development enabled. Attaching native debugger."); + return nativeDebugger.attachToClient(project, client, state); + } else { + return super.attachToClient(project, client, state); + } + } + + @Override + protected boolean isNativeDeployment(Project project, Module debuggeeModule) { + return isNativeProject(project); + } +} diff --git a/aswb/sdkcompat/as231/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeAndroidDebugger.java b/aswb/sdkcompat/as231/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeAndroidDebugger.java new file mode 100644 index 00000000000..bfa296e12e2 --- /dev/null +++ b/aswb/sdkcompat/as231/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeAndroidDebugger.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.cppimpl.debug; + +/** + * API compat of {@link BlazeNativeAndroidDebuggerBase} with the following additions: + * + *
    + *
  • Creates a run-config setting using {@link BlazeAndroidNativeAttachConfiguration} instead of + * {@link AndroidNativeAttachConfiguration} to override counterproductive validations. + *
+ * + * #api4.0 + */ +public class BlazeNativeAndroidDebugger extends BlazeNativeAndroidDebuggerBase {} diff --git a/aswb/sdkcompat/as231/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeAndroidDebuggerBase.java b/aswb/sdkcompat/as231/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeAndroidDebuggerBase.java new file mode 100644 index 00000000000..911d90b7711 --- /dev/null +++ b/aswb/sdkcompat/as231/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeAndroidDebuggerBase.java @@ -0,0 +1,59 @@ +/* + * Copyright 2020 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.cppimpl.debug; + +import com.android.tools.ndk.run.editor.NativeAndroidDebugger; +import com.google.idea.blaze.base.model.BlazeProjectData; +import com.google.idea.blaze.base.model.primitives.LanguageClass; +import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; +import com.intellij.openapi.project.Project; + +/** + * Extension of {@link NativeAndroidDebugger} with the following key differences compared to {@link + * NativeAndroidDebugger}. + * + *
    + *
  • Overrides {@link #supportsProject} so native debugger is only enabled for native support is + * enabled. + *
+ */ +public class BlazeNativeAndroidDebuggerBase extends NativeAndroidDebugger { + /** + * This ID needs to be lexicographically larger than "Java" so it come after the "Java" debugger + * when sorted lexicographically in the "Attach Debugger to Android Process" dialog. See {@link + * org.jetbrains.android.actions.AndroidProcessChooserDialog#populateDebuggerTypeCombo}. + */ + public static final String ID = "Native" + Blaze.defaultBuildSystemName(); + + @Override + public String getId() { + return ID; + } + + @Override + public String getDisplayName() { + return "Native Only"; + } + + @Override + public boolean supportsProject(Project project) { + BlazeProjectData blazeProjectData = + BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); + return blazeProjectData != null + && blazeProjectData.getWorkspaceLanguageSettings().isLanguageActive(LanguageClass.C); + } +} diff --git a/aswb/sdkcompat/as231/com/google/idea/blaze/android/projectsystem/BlazeModuleSystem.java b/aswb/sdkcompat/as231/com/google/idea/blaze/android/projectsystem/BlazeModuleSystem.java new file mode 100644 index 00000000000..162fd8f1793 --- /dev/null +++ b/aswb/sdkcompat/as231/com/google/idea/blaze/android/projectsystem/BlazeModuleSystem.java @@ -0,0 +1,27 @@ +/* + * Copyright 2020 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.projectsystem; + +import com.android.tools.idea.projectsystem.AndroidModuleSystem; +import com.intellij.openapi.module.Module; + +/** Blaze implementation of {@link AndroidModuleSystem}. */ +public class BlazeModuleSystem extends BlazeModuleSystemBase { + + BlazeModuleSystem(Module module) { + super(module); + } +} diff --git a/aswb/sdkcompat/as231/com/google/idea/blaze/android/projectsystem/BlazeProjectSystem.java b/aswb/sdkcompat/as231/com/google/idea/blaze/android/projectsystem/BlazeProjectSystem.java new file mode 100755 index 00000000000..df7e6246ef5 --- /dev/null +++ b/aswb/sdkcompat/as231/com/google/idea/blaze/android/projectsystem/BlazeProjectSystem.java @@ -0,0 +1,246 @@ +/* + * 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.projectsystem; + +import static com.android.tools.idea.projectsystem.SourceProvidersKt.emptySourceProvider; +import static org.jetbrains.android.facet.SourceProviderUtil.createSourceProvidersForLegacyModule; + +import com.android.tools.apk.analyzer.AaptInvoker; +import com.android.tools.idea.log.LogWrapper; +import com.android.tools.idea.model.AndroidModel; +import com.android.tools.idea.model.ClassJarProvider; +import com.android.tools.idea.projectsystem.AndroidProjectSystem; +import com.android.tools.idea.projectsystem.NamedIdeaSourceProvider; +import com.android.tools.idea.projectsystem.ProjectSystemBuildManager; +import com.android.tools.idea.projectsystem.ProjectSystemSyncManager; +import com.android.tools.idea.projectsystem.ScopeType; +import com.android.tools.idea.projectsystem.SourceProviderManager; +import com.android.tools.idea.projectsystem.SourceProviders; +import com.android.tools.idea.projectsystem.SourceProvidersFactory; +import com.android.tools.idea.projectsystem.SourceProvidersImpl; +import com.android.tools.idea.res.AndroidInnerClassFinder; +import com.android.tools.idea.res.AndroidResourceClassPsiElementFinder; +import com.android.tools.idea.sdk.AndroidSdks; +import com.google.common.collect.ImmutableList; +import com.google.idea.blaze.android.resources.BlazeLightResourceClassService; +import com.google.idea.blaze.android.sync.model.idea.BlazeAndroidModel; +import com.google.idea.blaze.android.sync.model.idea.BlazeClassJarProvider; +import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.settings.BlazeImportSettings.ProjectType; +import com.intellij.facet.ProjectFacetManager; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiElementFinder; +import com.intellij.psi.search.GlobalSearchScope; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import org.jetbrains.android.facet.AndroidFacet; +import org.jetbrains.annotations.NotNull; + +/** + * Base class to implement common methods in {@link AndroidProjectSystem} for blaze with different + * sdk + */ +public class BlazeProjectSystem implements AndroidProjectSystem { + protected final Project project; + protected final ProjectSystemSyncManager syncManager; + protected final List myFinders; + private final BlazeProjectSystemBuildManager buildManager; + + public BlazeProjectSystem(Project project) { + this.project = project; + syncManager = new BlazeProjectSystemSyncManager(project); + buildManager = new BlazeProjectSystemBuildManager(project); + + myFinders = + Arrays.asList( + AndroidInnerClassFinder.INSTANCE, + new AndroidResourceClassPsiElementFinder(getLightResourceClassService())); + } + + public Project getProject() { + return project; + } + + @Override + public boolean allowsFileCreation() { + return true; + } + + @Nullable + @Override + public VirtualFile getDefaultApkFile() { + return null; + } + + @Override + public Path getPathToAapt() { + return AaptInvoker.getPathToAapt( + AndroidSdks.getInstance().tryToChooseSdkHandler(), + new LogWrapper(BlazeProjectSystem.class)); + } + + @Override + public ProjectSystemBuildManager getBuildManager() { + return buildManager; + } + + @Override + public BlazeModuleSystem getModuleSystem(Module module) { + return BlazeModuleSystem.getInstance(module); + } + + @Override + public ProjectSystemSyncManager getSyncManager() { + return syncManager; + } + + @Override + public Collection getPsiElementFinders() { + return myFinders; + } + + @Override + public BlazeLightResourceClassService getLightResourceClassService() { + return BlazeLightResourceClassService.getInstance(project); + } + + @Override + public SourceProvidersFactory getSourceProvidersFactory() { + return new SourceProvidersFactory() { + @Override + public SourceProviders createSourceProvidersFor(AndroidFacet facet) { + BlazeAndroidModel model = ((BlazeAndroidModel) AndroidModel.get(facet)); + if (model != null) { + return createForModel(model); + } else { + return createSourceProvidersForLegacyModule(facet); + } + } + + private SourceProviders createForModel(BlazeAndroidModel model) { + NamedIdeaSourceProvider mainSourceProvider = model.getDefaultSourceProvider(); + if (Blaze.getProjectType(project).equals(ProjectType.QUERY_SYNC)) { + return new SourceProvidersImpl( + mainSourceProvider, + ImmutableList.of(mainSourceProvider), + ImmutableList.of(), + ImmutableList.of(), + ImmutableList.of(), + ImmutableList.of(mainSourceProvider), + ImmutableList.of(mainSourceProvider), + emptySourceProvider(ScopeType.MAIN), + emptySourceProvider(ScopeType.UNIT_TEST), + emptySourceProvider(ScopeType.ANDROID_TEST), + emptySourceProvider(ScopeType.TEST_FIXTURES)); + } else { + return new SourceProvidersImpl( + mainSourceProvider, + ImmutableList.of(mainSourceProvider), + ImmutableList.of(mainSourceProvider), + ImmutableList.of(mainSourceProvider), + ImmutableList.of(mainSourceProvider), + ImmutableList.of(mainSourceProvider), + ImmutableList.of(mainSourceProvider), + emptySourceProvider(ScopeType.MAIN), + emptySourceProvider(ScopeType.UNIT_TEST), + emptySourceProvider(ScopeType.ANDROID_TEST), + emptySourceProvider(ScopeType.TEST_FIXTURES)); + } + } + }; + } + + @Override + public ClassJarProvider getClassJarProvider() { + return new BlazeClassJarProvider(project); + } + + @Override + public Collection getAndroidFacetsWithPackageName( + Project project, String packageName) { + return getAndroidFacetsWithPackageName( + project, packageName, GlobalSearchScope.projectScope(project)); + } + + private Collection getAndroidFacetsWithPackageName( + Project project, String packageName, GlobalSearchScope scope) { + List facets = ProjectFacetManager.getInstance(project).getFacets(AndroidFacet.ID); + return facets.stream() + .filter(facet -> hasPackageName(facet, packageName)) + .filter( + facet -> { + VirtualFile file = SourceProviderManager.getInstance(facet).getMainManifestFile(); + if (file == null) { + return false; + } else { + return scope.contains(file); + } + }) + .collect(Collectors.toList()); + } + + @Override + public boolean isNamespaceOrParentPackage(@NotNull String packageName) { + List facets = ProjectFacetManager.getInstance(project).getFacets(AndroidFacet.ID); + GlobalSearchScope scope = GlobalSearchScope.projectScope(project); + for (AndroidFacet facet : facets) { + String moduleNamespace = PackageNameUtils.getPackageName(facet.getModule()); + if (moduleNamespace == null) { + continue; + } + // Check if the moduleNamespace is exactly the package name, or is a subpackage + if (!moduleNamespace.startsWith(packageName)) { + continue; + } + // packageName=com.example should not match moduleNamespace=com.example2 + if (moduleNamespace.length() > packageName.length() + && moduleNamespace.charAt(packageName.length()) != '.') { + continue; + } + VirtualFile file = SourceProviderManager.getInstance(facet).getMainManifestFile(); + if (file == null || !scope.contains(file)) { + continue; + } + return true; + } + return false; + } + + @Override + public Collection getSubmodules() { + return ImmutableList.of(); + } + + @Override + public Collection getBootClasspath(@NotNull Module module) { + // TODO: b/266586669 + return ImmutableList.of(); + } + + private static boolean hasPackageName(AndroidFacet facet, String packageName) { + String nameFromFacet = PackageNameUtils.getPackageName(facet.getModule()); + if (nameFromFacet == null) { + return false; + } + return nameFromFacet.equals(packageName); + } +} diff --git a/aswb/sdkcompat/as231/com/google/idea/blaze/android/projectsystem/RenderJarClassFileFinder.java b/aswb/sdkcompat/as231/com/google/idea/blaze/android/projectsystem/RenderJarClassFileFinder.java new file mode 100644 index 00000000000..81504173f22 --- /dev/null +++ b/aswb/sdkcompat/as231/com/google/idea/blaze/android/projectsystem/RenderJarClassFileFinder.java @@ -0,0 +1,292 @@ +/* + * Copyright 2020 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.projectsystem; + +import static java.util.stream.Collectors.joining; + +import com.android.tools.idea.projectsystem.ClassFileFinder; +import com.android.tools.idea.projectsystem.ClassFileFinderUtil; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableSet; +import com.google.idea.blaze.android.libraries.RenderJarCache; +import com.google.idea.blaze.android.sync.model.AndroidResourceModule; +import com.google.idea.blaze.android.sync.model.AndroidResourceModuleRegistry; +import com.google.idea.blaze.android.targetmaps.TargetToBinaryMap; +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.model.BlazeProjectData; +import com.google.idea.blaze.base.qsync.QuerySync; +import com.google.idea.blaze.base.qsync.QuerySyncManager; +import com.google.idea.blaze.base.qsync.RenderJarArtifactTracker; +import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.settings.BlazeImportSettings.ProjectType; +import com.google.idea.blaze.base.sync.BlazeSyncModificationTracker; +import com.google.idea.blaze.base.sync.data.BlazeDataStorage; +import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; +import com.google.idea.common.experiments.BoolExperiment; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.JarFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import java.io.File; +import java.util.regex.Pattern; +import org.jetbrains.annotations.Nullable; + +/** + * A {@link ClassFileFinder} that uses deploy JAR like artifacts (called render jar henceforth) for + * class files. + * + *

The render JAR contains all runtime dependencies of a binary target. + * + *

The Blaze targets that go into creating a resource module is known. Consequently, it is + * possible to determine which binaries in the projectview depend on the resource declaring blaze + * targets that constitutes the module. This class calculates the binary targets and attempts to + * find classes from the render JARs. + * + *

This only works for resource modules (i.e. not the .workspace module). For .workspace module, + * we try to find the class in all binary targets in projectview + * + *

NOTE: Blaze targets that constitutes the resource module will be called "resource target(s)" + * in comments below. + */ +public class RenderJarClassFileFinder implements ClassFileFinder { + /** Experiment to control whether class file finding from render jars should be enabled. */ + private static final BoolExperiment enabled = + new BoolExperiment("aswb.renderjar.cff.enabled.3", true); + + /** + * Experiment to toggle whether resource resolution is allowed from Render JARs. Render JARs + * should not resolve resources by default. + */ + @VisibleForTesting + static final BoolExperiment resolveResourceClasses = + new BoolExperiment("aswb.resolve.resources.render.jar", false); + + private static final Logger log = Logger.getInstance(RenderJarClassFileFinder.class); + + private static final String INTERNAL_PACKAGE = "_layoutlib_._internal_."; + + // matches foo.bar.R or foo.bar.R$baz + private static final Pattern RESOURCE_CLASS_NAME = Pattern.compile(".+\\.R(\\$[^.]+)?$"); + + private final Module module; + private final Project project; + + // tracks the binary targets that depend resource targets + // will be recalculated after every sync + private ImmutableSet binaryTargets = ImmutableSet.of(); + + // tracks the value of {@link BlazeSyncModificationTracker} when binaryTargets is calculated + // binaryTargets is calculated when the value of {@link BlazeSyncModificationTracker} does not + // equal lastSyncCount + long lastSyncCount = -1; + + // true if the current module is the .workspace Module + private final boolean isWorkspaceModule; + + public RenderJarClassFileFinder(Module module) { + this.module = module; + this.project = module.getProject(); + this.isWorkspaceModule = BlazeDataStorage.WORKSPACE_MODULE_NAME.equals(module.getName()); + } + + @Nullable + @Override + public VirtualFile findClassFile(String fqcn) { + if (!isEnabled()) { + return null; + } + + // Ever since Compose support was introduced in AS, finding class files is invoked during the + // normal course of opening an editor. The contract for this method requires that it shouldn't + // throw any exceptions, but we've had a few bugs where this method threw an exception, which + // resulted in users not being able to open Kotlin files at all. In order to avoid this + // scenario, we wrap the underlying call and ensure that no exceptions are thrown. + try { + return findClass(fqcn); + } catch (Error e) { + log.warn( + String.format( + "Unexpected error while finding the class file for `%1$s`: %2$s", + fqcn, Throwables.getRootCause(e).getMessage())); + return null; + } + } + + @Nullable + public VirtualFile findClass(String fqcn) { + // Render JAR should not resolve any resources. All resources should be available to the IDE + // through ResourceRepository. Attempting to resolve resources from Render JAR indicates that + // ASwB hasn't properly set up resources for the project. + if (isResourceClass(fqcn) && !resolveResourceClasses.getValue()) { + log.warn(String.format("Attempting to load resource '%s' from RenderJAR.", fqcn)); + return null; + } + + if (Blaze.getProjectType(project).equals(ProjectType.QUERY_SYNC)) { + if (QuerySync.isComposeEnabled(project)) { + RenderJarArtifactTracker renderJarArtifactTracker = + QuerySyncManager.getInstance(project).getRenderJarArtifactTracker(); + // TODO(b/283280194): Setup fqcn -> target and target -> Render jar mappings to avoid + // iterating over all render jars when trying to locate class for fqcn. + // TODO(b/284002836): Collect metrics on time taken to iterate over the jars + for (File renderJar : renderJarArtifactTracker.getRenderJars()) { + VirtualFile renderResolveJarVf = + VirtualFileSystemProvider.getInstance().getSystem().findFileByIoFile(renderJar); + if (renderResolveJarVf != null) { + return findClassInJar(renderResolveJarVf, fqcn); + } + log.warn(String.format("Could not find class `%1$s` with Query Sync", fqcn)); + return null; + } + } else { + // Disable this class for Query Sync if Compose is not enabled + return null; + } + } + + BlazeProjectData projectData = + BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); + if (projectData == null) { + log.warn("Could not find BlazeProjectData for project " + project.getName()); + return null; + } + + ImmutableSet binaryTargets = getBinaryTargets(); + if (binaryTargets.isEmpty()) { + log.warn( + String.format( + "No binaries for module %s. Adding a binary target to the projectview and resyncing" + + " might fix the issue.", + module.getName())); + return null; + } + + // Remove internal package prefix if present + fqcn = StringUtil.trimStart(fqcn, INTERNAL_PACKAGE); + + // Look through render resolve JARs of the binaries that depend on the given + // androidResourceModule. One androidResourceModule can comprise of multiple resource targets. + // The binaries can depend on any subset of these resource targets. Generally, we only + // expect one, or a small number of binaries here. + for (TargetKey binaryTarget : binaryTargets) { + VirtualFile classFile = getClassFromRenderResolveJar(projectData, fqcn, binaryTarget); + if (classFile != null) { + return classFile; + } + } + + log.warn(String.format("Could not find class `%1$s` (module: `%2$s`)", fqcn, module.getName())); + return null; + } + + @VisibleForTesting + static boolean isResourceClass(String fqcn) { + return RESOURCE_CLASS_NAME.matcher(fqcn).matches(); + } + + /** + * Returns the cached list of binary targets that depend on resource targets. The cache is + * recalculated if the project has been synced since last calculation + */ + private ImmutableSet getBinaryTargets() { + long currentSyncCount = + BlazeSyncModificationTracker.getInstance(project).getModificationCount(); + if (currentSyncCount == lastSyncCount) { + // Return the cached set if there hasn't been a sync since last calculation + return binaryTargets; + } + lastSyncCount = currentSyncCount; + + AndroidResourceModule androidResourceModule = + AndroidResourceModuleRegistry.getInstance(project).get(module); + if (androidResourceModule != null) { + binaryTargets = + TargetToBinaryMap.getInstance(project) + .getBinariesDependingOn(androidResourceModule.sourceTargetKeys); + } else if (isWorkspaceModule) { + binaryTargets = TargetToBinaryMap.getInstance(project).getSourceBinaryTargets(); + } else { + binaryTargets = ImmutableSet.of(); + log.warn("Could not find AndroidResourceModule for " + module.getName()); + } + log.info( + String.format( + "Binary targets for module `%1$s`: %2$s", + module.getName(), + binaryTargets.stream() + .limit(5) + .map(t -> t.getLabel().toString()) + .collect(joining(", ")))); + return binaryTargets; + } + + /** + * Returns class file for fqcn if found in the render JAR corresponding to {@code binaryTarget}. + * Returns null if something goes wrong or if render JAR does not contain fqcn + */ + @Nullable + private VirtualFile getClassFromRenderResolveJar( + BlazeProjectData projectData, String fqcn, TargetKey binaryTarget) { + TargetIdeInfo ideInfo = projectData.getTargetMap().get(binaryTarget); + if (ideInfo == null) { + return null; + } + + File renderResolveJarFile = + RenderJarCache.getInstance(project) + .getCachedJarForBinaryTarget(projectData.getArtifactLocationDecoder(), ideInfo); + + if (renderResolveJarFile == null) { + return null; + } + + VirtualFile renderResolveJarVF = + VirtualFileSystemProvider.getInstance().getSystem().findFileByIoFile(renderResolveJarFile); + if (renderResolveJarVF == null) { + return null; + } + + return findClassInJar(renderResolveJarVF, fqcn); + } + + @Nullable + private static VirtualFile findClassInJar(final VirtualFile classJar, String fqcn) { + VirtualFile jarRoot = getJarRootForLocalFile(classJar); + if (jarRoot == null) { + return null; + } + return ClassFileFinderUtil.findClassFileInOutputRoot(jarRoot, fqcn); + } + + /** Test aware method to redirect JARs to {@link VirtualFileSystemProvider} for tests */ + private static VirtualFile getJarRootForLocalFile(VirtualFile file) { + return ApplicationManager.getApplication().isUnitTestMode() + ? VirtualFileSystemProvider.getInstance() + .getSystem() + .findFileByPath(file.getPath() + JarFileSystem.JAR_SEPARATOR) + : JarFileSystem.getInstance().getJarRootForLocalFile(file); + } + + public static boolean isEnabled() { + return enabled.getValue(); + } +} diff --git a/aswb/sdkcompat/as231/com/google/idea/blaze/android/rendering/BlazeRenderErrorContributor.java b/aswb/sdkcompat/as231/com/google/idea/blaze/android/rendering/BlazeRenderErrorContributor.java new file mode 100644 index 00000000000..827e5975a35 --- /dev/null +++ b/aswb/sdkcompat/as231/com/google/idea/blaze/android/rendering/BlazeRenderErrorContributor.java @@ -0,0 +1,351 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.rendering; + +import static com.android.SdkConstants.ANDROID_MANIFEST_XML; +import static com.google.common.collect.ImmutableMap.toImmutableMap; + +import com.android.tools.idea.rendering.RenderErrorContributorCompat; +import com.android.tools.idea.rendering.errors.ui.RenderErrorModel; +import com.android.tools.idea.ui.designer.EditorDesignSurface; +import com.android.tools.rendering.HtmlLinkManagerCompat; +import com.android.tools.rendering.RenderLoggerCompat; +import com.android.tools.rendering.RenderResultCompat; +import com.android.utils.HtmlBuilder; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.Maps; +import com.google.common.collect.SortedSetMultimap; +import com.google.common.collect.TreeMultimap; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.idea.blaze.android.sync.model.AndroidResourceModule; +import com.google.idea.blaze.android.sync.model.AndroidResourceModuleRegistry; +import com.google.idea.blaze.base.command.buildresult.OutputArtifactResolver; +import com.google.idea.blaze.base.ideinfo.AndroidResFolder; +import com.google.idea.blaze.base.ideinfo.ArtifactLocation; +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.lang.buildfile.references.BuildReferenceManager; +import com.google.idea.blaze.base.model.BlazeProjectData; +import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.settings.BlazeImportSettings.ProjectType; +import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; +import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder; +import com.google.idea.blaze.base.targetmaps.SourceToTargetMap; +import com.google.idea.blaze.base.targetmaps.TransitiveDependencyMap; +import com.intellij.lang.annotation.HighlightSeverity; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.IndexNotReadyException; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VfsUtilCore; +import com.intellij.psi.JavaPsiFacade; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.search.GlobalSearchScope; +import java.io.File; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.function.Function; +import org.jetbrains.annotations.Nullable; + +/** Contribute blaze specific render errors. */ +public class BlazeRenderErrorContributor extends RenderErrorContributorCompat { + private final RenderLoggerCompat logger; + private final Module module; + private final Project project; + + public BlazeRenderErrorContributor( + EditorDesignSurface surface, RenderResultCompat result, @Nullable DataContext dataContext) { + super(surface, result, dataContext); + logger = new RenderLoggerCompat(result); + module = result.getModule(); + project = module.getProject(); + } + + @Override + public Collection reportIssues() { + BlazeProjectData blazeProjectData = + BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); + + if (blazeProjectData == null || !logger.hasErrors()) { + return getIssues(); + } + + if (Blaze.getProjectType(project).equals(ProjectType.QUERY_SYNC)) { + // TODO(b/284002829): Setup resource-module specific issue reporting + return getIssues(); + } + + TargetMap targetMap = blazeProjectData.getTargetMap(); + ArtifactLocationDecoder decoder = blazeProjectData.getArtifactLocationDecoder(); + AndroidResourceModule resourceModule = + AndroidResourceModuleRegistry.getInstance(project).get(module); + if (resourceModule == null) { + return getIssues(); + } + + TargetIdeInfo target = targetMap.get(resourceModule.targetKey); + if (target == null) { + return getIssues(); + } + + reportGeneratedResources(resourceModule, targetMap, decoder); + reportNonStandardAndroidManifestName(target, decoder); + reportResourceTargetShouldDependOnClassTarget(target, targetMap, decoder); + return getIssues(); + } + + /** + * We can't find generated resources. If a layout uses them, the layout won't render correctly. + */ + private void reportGeneratedResources( + AndroidResourceModule resourceModule, TargetMap targetMap, ArtifactLocationDecoder decoder) { + Map brokenClasses = logger.getBrokenClasses(); + if (brokenClasses == null || brokenClasses.isEmpty()) { + return; + } + + // Sorted entries for deterministic error message. + SortedMap generatedResources = + Maps.newTreeMap(getGeneratedResources(targetMap.get(resourceModule.targetKey))); + + for (TargetKey dependency : resourceModule.transitiveResourceDependencies) { + generatedResources.putAll(getGeneratedResources(targetMap.get(dependency))); + } + + if (generatedResources.isEmpty()) { + return; + } + + HtmlBuilder builder = new HtmlBuilder(); + builder.add("Generated resources will not be discovered by the IDE:"); + builder.beginList(); + for (Map.Entry entry : generatedResources.entrySet()) { + ArtifactLocation resource = entry.getKey(); + TargetIdeInfo target = entry.getValue(); + builder.listItem().add(resource.getRelativePath()).add(" from "); + addTargetLink(builder, target, decoder); + } + builder + .endList() + .add("Please avoid using generated resources, ") + .addLink("then ", "sync the project", " ", getLinkManager().createSyncProjectUrl()) + .addLink("and ", "refresh the layout", ".", getLinkManager().createRefreshRenderUrl()); + addIssue() + .setSeverity(HighlightSeverity.ERROR, HIGH_PRIORITY + 1) // Reported above broken classes + .setSummary("Generated resources") + .setHtmlContent(builder) + .build(); + } + + private static SortedMap getGeneratedResources( + TargetIdeInfo target) { + if (target == null || target.getAndroidIdeInfo() == null) { + return Collections.emptySortedMap(); + } + SortedMap generatedResources = Maps.newTreeMap(); + generatedResources.putAll( + target.getAndroidIdeInfo().getResources().stream() + .map(AndroidResFolder::getRoot) + .filter(ArtifactLocation::isGenerated) + .collect(toImmutableMap(Function.identity(), resource -> target))); + return generatedResources; + } + + /** + * When the Android manifest isn't AndroidManifest.xml, resolving resource IDs would fail. This + * doesn't seem to be an issue if the manifest belongs to one of the target's dependencies. + */ + private void reportNonStandardAndroidManifestName( + TargetIdeInfo target, ArtifactLocationDecoder decoder) { + if (target.getAndroidIdeInfo() == null || target.getAndroidIdeInfo().getManifest() == null) { + return; + } + + Map brokenClasses = logger.getBrokenClasses(); + if (brokenClasses == null || brokenClasses.isEmpty()) { + return; + } + + ArtifactLocation maniftestArtifactLocation = target.getAndroidIdeInfo().getManifest(); + + File manifest = + Preconditions.checkNotNull( + OutputArtifactResolver.resolve(project, decoder, maniftestArtifactLocation), + "Fail to find file %s", + maniftestArtifactLocation.getRelativePath()); + if (manifest.getName().equals(ANDROID_MANIFEST_XML)) { + return; + } + + HtmlBuilder builder = new HtmlBuilder(); + addTargetLink(builder, target, decoder) + .add(" uses a non-standard name for the Android manifest: "); + String linkToManifest = HtmlLinkManagerCompat.createFilePositionUrl(manifest, -1, 0); + if (linkToManifest != null) { + builder.addLink(manifest.getName(), linkToManifest); + } else { + builder.newline().add(manifest.getPath()); + } + // TODO: add a link to automatically rename the file and refactor all references. + builder + .newline() + .add("Please rename it to ") + .add(ANDROID_MANIFEST_XML) + .addLink(", then ", "sync the project", "", getLinkManager().createSyncProjectUrl()) + .addLink(" and ", "refresh the layout", ".", getLinkManager().createRefreshRenderUrl()); + addIssue() + .setSeverity(HighlightSeverity.ERROR, HIGH_PRIORITY + 1) // Reported above broken classes. + .setSummary("Non-standard manifest name") + .setHtmlContent(builder) + .build(); + } + + /** + * Blaze doesn't resolve class dependencies from resources until building the final + * android_binary, so we could end up with resources that ultimately build correctly, but fail to + * find their class dependencies during rendering in the layout editor. + */ + @SuppressWarnings("rawtypes") + private void reportResourceTargetShouldDependOnClassTarget( + TargetIdeInfo target, TargetMap targetMap, ArtifactLocationDecoder decoder) { + Set missingClasses = logger.getMissingClasses(); + if (missingClasses == null || missingClasses.isEmpty()) { + return; + } + + // Sorted entries for deterministic error message. + SortedSetMultimap missingClassToTargetMap = TreeMultimap.create(); + + SourceToTargetMap sourceToTargetMap = SourceToTargetMap.getInstance(project); + ImmutableCollection transitiveDependencies = + TransitiveDependencyMap.getInstance(project).getTransitiveDependencies(target.getKey()); + + for (String missingClass : missingClasses) { + File sourceFile = getSourceFileForClass(missingClass); + if (sourceFile == null) { + continue; + } + ImmutableCollection sourceTargets = + sourceToTargetMap.getRulesForSourceFile(sourceFile); + if (sourceTargets.stream() + .noneMatch( + sourceTarget -> + sourceTarget.equals(target.getKey()) + || transitiveDependencies.contains(sourceTarget))) { + missingClassToTargetMap.putAll(missingClass, sourceTargets); + } + } + + if (missingClassToTargetMap.isEmpty()) { + return; + } + + HtmlBuilder builder = new HtmlBuilder(); + addTargetLink(builder, target, decoder) + .add(" contains resource files that reference these classes:") + .beginList(); + for (String missingClass : missingClassToTargetMap.keySet()) { + builder + .listItem() + .addLink(missingClass, getLinkManager().createOpenClassUrl(missingClass)) + .add(" from "); + for (TargetKey targetKey : missingClassToTargetMap.get(missingClass)) { + addTargetLink(builder, targetMap.get(targetKey), decoder).add(" "); + } + } + builder.endList().add("Please fix your dependencies so that "); + addTargetLink(builder, target, decoder) + .add(" correctly depends on these classes, ") + .addLink("then ", "sync the project", " ", getLinkManager().createSyncProjectUrl()) + .addLink("and ", "refresh the layout", ".", getLinkManager().createRefreshRenderUrl()) + .newline() + .newline() + .addBold( + "NOTE: blaze can still build with the incorrect dependencies " + + "due to the way it handles resources, " + + "but the layout editor needs them to be correct."); + + addIssue() + .setSeverity(HighlightSeverity.ERROR, HIGH_PRIORITY + 1) // Reported above missing classes. + .setSummary("Missing class dependencies") + .setHtmlContent(builder) + .build(); + } + + private File getSourceFileForClass(String className) { + return ApplicationManager.getApplication() + .runReadAction( + (Computable) + () -> { + try { + PsiClass psiClass = + JavaPsiFacade.getInstance(project) + .findClass(className, GlobalSearchScope.projectScope(project)); + if (psiClass == null) { + return null; + } + return VfsUtilCore.virtualToIoFile( + psiClass.getContainingFile().getVirtualFile()); + } catch (IndexNotReadyException ignored) { + // We're in dumb mode. Abort! Abort! + return null; + } + }); + } + + @CanIgnoreReturnValue + private HtmlBuilder addTargetLink( + HtmlBuilder builder, TargetIdeInfo target, ArtifactLocationDecoder decoder) { + File buildFile = + Preconditions.checkNotNull( + OutputArtifactResolver.resolve(project, decoder, target.getBuildFile()), + "Fail to find file %s", + target.getBuildFile().getRelativePath()); + int line = + ApplicationManager.getApplication() + .runReadAction( + (Computable) + () -> { + PsiElement buildTargetPsi = + BuildReferenceManager.getInstance(project) + .resolveLabel(target.getKey().getLabel()); + if (buildTargetPsi == null) { + return -1; + } + PsiFile psiFile = buildTargetPsi.getContainingFile(); + if (psiFile == null) { + return -1; + } + return StringUtil.offsetToLineNumber( + psiFile.getText(), buildTargetPsi.getTextOffset()); + }); + String url = HtmlLinkManagerCompat.createFilePositionUrl(buildFile, line, 0); + if (url != null) { + return builder.addLink(target.toString(), url); + } + return builder.add(target.toString()); + } +} diff --git a/aswb/sdkcompat/as231/com/google/idea/blaze/android/rendering/BlazeRenderSecurityManagerOverrides.java b/aswb/sdkcompat/as231/com/google/idea/blaze/android/rendering/BlazeRenderSecurityManagerOverrides.java new file mode 100644 index 00000000000..39d3edd195f --- /dev/null +++ b/aswb/sdkcompat/as231/com/google/idea/blaze/android/rendering/BlazeRenderSecurityManagerOverrides.java @@ -0,0 +1,27 @@ +/* + * Copyright 2018 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.rendering; + +import com.android.tools.rendering.security.RenderSecurityManagerOverrides; + +/** Overrides some security restrictions used by the render sandbox. */ +public class BlazeRenderSecurityManagerOverrides implements RenderSecurityManagerOverrides { + @Override + public boolean allowsPropertiesAccess() { + // System properties access is needed for SystemPropertyExperimentLoader + return true; + } +} diff --git a/aswb/sdkcompat/as231/com/google/idea/blaze/android/resources/BlazeLightResourceClassServiceBase.java b/aswb/sdkcompat/as231/com/google/idea/blaze/android/resources/BlazeLightResourceClassServiceBase.java new file mode 100644 index 00000000000..21516076cc2 --- /dev/null +++ b/aswb/sdkcompat/as231/com/google/idea/blaze/android/resources/BlazeLightResourceClassServiceBase.java @@ -0,0 +1,66 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.resources; + +import com.android.tools.idea.projectsystem.LightResourceClassService; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.google.idea.blaze.base.sync.data.BlazeDataStorage; +import com.google.idea.common.experiments.FeatureRolloutExperiment; +import com.intellij.openapi.module.Module; +import com.intellij.psi.PsiClass; +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +/** Implementation of {@link LightResourceClassService} set up at Blaze sync time. */ +public abstract class BlazeLightResourceClassServiceBase implements LightResourceClassService { + + @VisibleForTesting + public static final FeatureRolloutExperiment workspaceResourcesFeature = + new FeatureRolloutExperiment("aswb.workspace.light.class.enabled"); + + Map rClasses = Maps.newHashMap(); + Map rClassesByModule = Maps.newHashMap(); + final Set allRClasses = Sets.newHashSet(); + + @Override + public Collection getLightRClassesAccessibleFromModule( + Module module, boolean includeTest) { + if (workspaceResourcesFeature.isEnabled() + && module.getName().equals(BlazeDataStorage.WORKSPACE_MODULE_NAME)) { + // Returns all the packages in resource modules, and all the workspace packages that + // have previously been asked for. All `res/` directories in our project should belong to a + // resource module. For java sources, IntelliJ will ask for explicit resource package by + // calling `getLightRClasses` at which point we can create the package. This is not completely + // correct and the autocomplete will be slightly off when initial `R` is typed in the editor, + // but this workaround is being used to mitigate issues (b/136685602) while resources + // are re-worked. + return allRClasses; + } else { + return rClasses.values(); + } + } + + @Override + public Collection getLightRClassesDefinedByModule( + Module module, boolean includeTestClasses) { + BlazeRClass rClass = rClassesByModule.get(module); + return rClass == null ? ImmutableSet.of() : ImmutableSet.of(rClass); + } +} diff --git a/aswb/sdkcompat/as213/com/google/idea/blaze/android/resources/BlazeRClass.java b/aswb/sdkcompat/as231/com/google/idea/blaze/android/resources/BlazeRClass.java similarity index 88% rename from aswb/sdkcompat/as213/com/google/idea/blaze/android/resources/BlazeRClass.java rename to aswb/sdkcompat/as231/com/google/idea/blaze/android/resources/BlazeRClass.java index 2c123e0a66f..3c64683d517 100644 --- a/aswb/sdkcompat/as213/com/google/idea/blaze/android/resources/BlazeRClass.java +++ b/aswb/sdkcompat/as231/com/google/idea/blaze/android/resources/BlazeRClass.java @@ -19,9 +19,9 @@ import static org.jetbrains.android.AndroidResolveScopeEnlarger.MODULE_POINTER_KEY; import com.android.ide.common.rendering.api.ResourceNamespace; -import com.android.tools.idea.res.LocalResourceRepository; -import com.android.tools.idea.res.ResourceRepositoryManager; import com.android.tools.idea.res.ResourceRepositoryRClass; +import com.android.tools.idea.res.StudioResourceRepositoryManager; +import com.android.tools.res.LocalResourceRepository; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModulePointerManager; import com.intellij.openapi.vfs.VirtualFile; @@ -49,13 +49,13 @@ public Transitivity getTransitivity() { } @Override - public ResourceRepositoryManager getResourceRepositoryManager() { - return ResourceRepositoryManager.getInstance(androidFacet); + public StudioResourceRepositoryManager getResourceRepositoryManager() { + return StudioResourceRepositoryManager.getInstance(androidFacet); } @Override public LocalResourceRepository getResourceRepository() { - return ResourceRepositoryManager.getAppResources(androidFacet); + return StudioResourceRepositoryManager.getAppResources(androidFacet); } @Override diff --git a/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryApplicationLaunchTaskProvider.java b/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryApplicationLaunchTaskProvider.java new file mode 100644 index 00000000000..948dae75f36 --- /dev/null +++ b/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryApplicationLaunchTaskProvider.java @@ -0,0 +1,72 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.binary; + +import com.android.tools.idea.run.ApkProvisionException; +import com.android.tools.idea.run.ApplicationIdProvider; +import com.android.tools.idea.run.activity.StartActivityFlagsProvider; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; +import com.google.idea.blaze.android.manifest.ManifestParser; +import com.google.idea.blaze.android.run.binary.tasks.AndroidDeepLinkLaunchTask; +import com.google.idea.blaze.android.run.binary.tasks.BlazeDefaultActivityLaunchTask; +import com.google.idea.blaze.android.run.binary.tasks.SpecificActivityLaunchTask; +import com.intellij.execution.ExecutionException; +import com.intellij.openapi.diagnostic.Logger; + +/** Provides the launch task for android_binary */ +public class BlazeAndroidBinaryApplicationLaunchTaskProvider { + private static final Logger LOG = + Logger.getInstance(BlazeAndroidBinaryApplicationLaunchTaskProvider.class); + + public static BlazeLaunchTask getApplicationLaunchTask( + ApplicationIdProvider applicationIdProvider, + ManifestParser.ParsedManifest mergedManifestParsedManifest, + BlazeAndroidBinaryRunConfigurationState configState, + StartActivityFlagsProvider startActivityFlagsProvider) + throws ExecutionException { + String applicationId; + try { + applicationId = applicationIdProvider.getPackageName(); + } catch (ApkProvisionException e) { + throw new ExecutionException("Unable to identify application id"); + } + + switch (configState.getMode()) { + case BlazeAndroidBinaryRunConfigurationState.LAUNCH_DEFAULT_ACTIVITY: + BlazeDefaultActivityLocator activityLocator = + new BlazeDefaultActivityLocator(mergedManifestParsedManifest); + return new BlazeDefaultActivityLaunchTask( + applicationId, activityLocator, startActivityFlagsProvider); + case BlazeAndroidBinaryRunConfigurationState.LAUNCH_SPECIFIC_ACTIVITY: + return launchContext -> + new SpecificActivityLaunchTask( + applicationId, configState.getActivityClass(), startActivityFlagsProvider) + .run( + launchContext.getDevice(), + launchContext.getProgressIndicator(), + launchContext.getConsoleView()); + case BlazeAndroidBinaryRunConfigurationState.LAUNCH_DEEP_LINK: + return launchContext -> + new AndroidDeepLinkLaunchTask(configState.getDeepLink(), startActivityFlagsProvider) + .run( + launchContext.getDevice(), + launchContext.getProgressIndicator(), + launchContext.getConsoleView()); + default: + return null; + } + } +} diff --git a/aswb/sdkcompat/as212/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContext.java b/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContext.java similarity index 59% rename from aswb/sdkcompat/as212/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContext.java rename to aswb/sdkcompat/as231/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContext.java index a15be047fbf..96c4e9e34b3 100644 --- a/aswb/sdkcompat/as212/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContext.java +++ b/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContext.java @@ -15,23 +15,29 @@ */ package com.google.idea.blaze.android.run.binary; +import com.android.ddmlib.IDevice; +import com.android.tools.idea.execution.common.debug.AndroidDebugger; +import com.android.tools.idea.execution.common.debug.AndroidDebuggerState; +import com.android.tools.idea.execution.common.debug.DebugSessionStarter; import com.android.tools.idea.run.ApkProvisionException; -import com.android.tools.idea.run.LaunchOptions; import com.android.tools.idea.run.activity.DefaultStartActivityFlagsProvider; import com.android.tools.idea.run.activity.StartActivityFlagsProvider; -import com.android.tools.idea.run.editor.AndroidDebugger; -import com.android.tools.idea.run.editor.AndroidDebuggerState; -import com.android.tools.idea.run.tasks.ConnectDebuggerTask; -import com.android.tools.idea.run.tasks.LaunchTask; -import com.android.tools.idea.run.util.LaunchStatus; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; import com.google.idea.blaze.android.run.deployinfo.BlazeAndroidDeployInfo; import com.google.idea.blaze.android.run.runner.ApkBuildStep; import com.intellij.execution.ExecutionException; import com.intellij.execution.Executor; import com.intellij.execution.configurations.RunConfiguration; import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.ui.ConsoleView; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; +import com.intellij.xdebugger.XDebugSession; import javax.annotation.Nullable; +import kotlin.Unit; +import kotlin.coroutines.EmptyCoroutineContext; +import kotlinx.coroutines.BuildersKt; import org.jetbrains.android.facet.AndroidFacet; /** Compat for #api212 */ @@ -48,24 +54,20 @@ public class BlazeAndroidBinaryNormalBuildRunContext super(project, facet, runConfiguration, env, configState, buildStep, launchId); } - @SuppressWarnings("unchecked") // upstream API @Override - public LaunchTask getApplicationLaunchTask( - LaunchOptions launchOptions, - @Nullable Integer userId, - String contributorsAmStartOptions, - AndroidDebugger androidDebugger, - AndroidDebuggerState androidDebuggerState, - LaunchStatus launchStatus) + public BlazeLaunchTask getApplicationLaunchTask( + boolean isDebug, @Nullable Integer userId, String contributorsAmStartOptions) throws ExecutionException { String extraFlags = UserIdHelper.getFlagsFromUserId(userId); if (!contributorsAmStartOptions.isEmpty()) { extraFlags += (extraFlags.isEmpty() ? "" : " ") + contributorsAmStartOptions; } + if (isDebug) { + extraFlags += (extraFlags.isEmpty() ? "" : " ") + "-D"; + } final StartActivityFlagsProvider startActivityFlagsProvider = - new DefaultStartActivityFlagsProvider( - androidDebugger, androidDebuggerState, project, launchOptions.isDebug(), extraFlags); + new DefaultStartActivityFlagsProvider(project, isDebug, extraFlags); BlazeAndroidDeployInfo deployInfo; try { @@ -78,23 +80,40 @@ public LaunchTask getApplicationLaunchTask( applicationIdProvider, deployInfo.getMergedManifest(), configState, - startActivityFlagsProvider, - launchStatus); + startActivityFlagsProvider); } @Nullable @Override - @SuppressWarnings("unchecked") - public ConnectDebuggerTask getDebuggerTask( - AndroidDebugger androidDebugger, AndroidDebuggerState androidDebuggerState) - throws ExecutionException { - return androidDebugger.getConnectDebuggerTask( - env, - null, - applicationIdProvider, - facet, - androidDebuggerState, - runConfiguration.getType().getId()); + public XDebugSession startDebuggerSession( + AndroidDebugger androidDebugger, + AndroidDebuggerState androidDebuggerState, + ExecutionEnvironment env, + IDevice device, + ConsoleView consoleView, + ProgressIndicator indicator, + String packageName) { + try { + return BuildersKt.runBlocking( + EmptyCoroutineContext.INSTANCE, + (scope, continuation) -> + DebugSessionStarter.INSTANCE.attachDebuggerToStartedProcess( + device, + packageName, + env, + androidDebugger, + androidDebuggerState, + /*destroyRunningProcess*/ d -> { + d.forceStop(packageName); + return Unit.INSTANCE; + }, + indicator, + consoleView, + /* timeout= */ 15L, + continuation)); + } catch (InterruptedException e) { + throw new ProcessCanceledException(); + } } @Override diff --git a/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContextBase.java b/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContextBase.java new file mode 100644 index 00000000000..96f3f8be324 --- /dev/null +++ b/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContextBase.java @@ -0,0 +1,186 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.binary; + +import static com.google.idea.blaze.android.run.runner.BlazeAndroidLaunchTasksProvider.NATIVE_DEBUGGING_ENABLED; + +import com.android.ddmlib.IDevice; +import com.android.tools.idea.execution.common.DeployOptions; +import com.android.tools.idea.gradle.util.DynamicAppUtils; +import com.android.tools.idea.run.ApkFileUnit; +import com.android.tools.idea.run.ApkInfo; +import com.android.tools.idea.run.ApkProvider; +import com.android.tools.idea.run.ApkProvisionException; +import com.android.tools.idea.run.ApplicationIdProvider; +import com.android.tools.idea.run.ConsoleProvider; +import com.android.tools.idea.run.LaunchOptions; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; +import com.android.tools.idea.run.blaze.BlazeLaunchTasksProvider; +import com.android.tools.idea.run.editor.ProfilerState; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.idea.blaze.android.run.deployinfo.BlazeApkProviderService; +import com.google.idea.blaze.android.run.runner.ApkBuildStep; +import com.google.idea.blaze.android.run.runner.BlazeAndroidDeviceSelector; +import com.google.idea.blaze.android.run.runner.BlazeAndroidLaunchTasksProvider; +import com.google.idea.blaze.android.run.runner.BlazeAndroidRunContext; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.Executor; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.openapi.project.Project; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import org.jetbrains.android.facet.AndroidFacet; +import org.jetbrains.annotations.NotNull; + +/** Run context for android_binary. */ +public abstract class BlazeAndroidBinaryNormalBuildRunContextBase + implements BlazeAndroidRunContext { + protected final Project project; + protected final AndroidFacet facet; + protected final RunConfiguration runConfiguration; + protected final ExecutionEnvironment env; + protected final BlazeAndroidBinaryRunConfigurationState configState; + protected final ConsoleProvider consoleProvider; + protected final ApkBuildStep buildStep; + protected final ApkProvider apkProvider; + protected final ApplicationIdProvider applicationIdProvider; + private final String launchId; + + BlazeAndroidBinaryNormalBuildRunContextBase( + Project project, + AndroidFacet facet, + RunConfiguration runConfiguration, + ExecutionEnvironment env, + BlazeAndroidBinaryRunConfigurationState configState, + ApkBuildStep buildStep, + String launchId) { + this.project = project; + this.facet = facet; + this.runConfiguration = runConfiguration; + this.env = env; + this.configState = configState; + this.consoleProvider = new BlazeAndroidBinaryConsoleProvider(project); + this.buildStep = buildStep; + this.apkProvider = BlazeApkProviderService.getInstance().getApkProvider(project, buildStep); + this.applicationIdProvider = new BlazeAndroidBinaryApplicationIdProvider(buildStep); + this.launchId = launchId; + } + + @Override + public BlazeAndroidDeviceSelector getDeviceSelector() { + return new BlazeAndroidDeviceSelector.NormalDeviceSelector(); + } + + @Override + public void augmentLaunchOptions(LaunchOptions.Builder options) { + LaunchOptions.Builder unused = + options + .setDeploy(true) + .setOpenLogcatAutomatically(configState.showLogcatAutomatically()) + .addExtraOptions( + ImmutableMap.of( + NATIVE_DEBUGGING_ENABLED, + configState.getCommonState().isNativeDebuggingEnabled())) + .setClearAppStorage(configState.getClearAppStorage()); + } + + @Override + public ConsoleProvider getConsoleProvider() { + return consoleProvider; + } + + @Override + public ApplicationIdProvider getApplicationIdProvider() { + return applicationIdProvider; + } + + @Override + public ApkBuildStep getBuildStep() { + return buildStep; + } + + @Nullable + @Override + public Integer getUserId(IDevice device) throws ExecutionException { + return UserIdHelper.getUserIdFromConfigurationState(project, device, configState); + } + + @Override + public BlazeLaunchTasksProvider getLaunchTasksProvider(LaunchOptions launchOptions) + throws ExecutionException { + return new BlazeAndroidLaunchTasksProvider(project, this, applicationIdProvider, launchOptions); + } + + @Override + public String getAmStartOptions() { + return configState.getAmStartOptions(); + } + + @Nullable + @Override + public ImmutableList getDeployTasks(IDevice device, DeployOptions deployOptions) + throws ExecutionException { + return ImmutableList.of( + new DeploymentTimingReporterTask( + launchId, + project, + getApkInfoToInstall(device, deployOptions, apkProvider), + deployOptions)); + } + + /** Returns a list of APKs excluding any APKs for features that are disabled. */ + public static List getApkInfoToInstall( + IDevice device, DeployOptions deployOptions, ApkProvider apkProvider) + throws ExecutionException { + Collection apks; + try { + apks = apkProvider.getApks(device); + } catch (ApkProvisionException e) { + throw new ExecutionException(e); + } + List disabledFeatures = deployOptions.getDisabledDynamicFeatures(); + return apks.stream() + .map(apk -> getApkInfoToInstall(apk, disabledFeatures)) + .collect(Collectors.toList()); + } + + @NotNull + private static ApkInfo getApkInfoToInstall(ApkInfo apkInfo, List disabledFeatures) { + if (apkInfo.getFiles().size() > 1) { + List filteredApks = + apkInfo.getFiles().stream() + .filter(feature -> DynamicAppUtils.isFeatureEnabled(disabledFeatures, feature)) + .collect(Collectors.toList()); + return new ApkInfo(filteredApks, apkInfo.getApplicationId()); + } else { + return apkInfo; + } + } + + @Override + public Executor getExecutor() { + return env.getExecutor(); + } + + @Override + public ProfilerState getProfileState() { + return configState.getProfilerState(); + } +} diff --git a/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContextCompat.java b/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContextCompat.java new file mode 100644 index 00000000000..af20febc771 --- /dev/null +++ b/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContextCompat.java @@ -0,0 +1,83 @@ +/* + * Copyright 2022 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.binary; + +import com.android.ddmlib.IDevice; +import com.android.tools.idea.execution.common.debug.AndroidDebugger; +import com.android.tools.idea.execution.common.debug.AndroidDebuggerState; +import com.android.tools.idea.execution.common.debug.DebugSessionStarter; +import com.google.idea.blaze.android.run.runner.ApkBuildStep; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.ui.ConsoleView; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.project.Project; +import com.intellij.xdebugger.XDebugSession; +import javax.annotation.Nullable; +import kotlin.Unit; +import kotlin.coroutines.EmptyCoroutineContext; +import kotlinx.coroutines.BuildersKt; +import org.jetbrains.android.facet.AndroidFacet; + +/** Compat class for {@link BlazeAndroidBinaryNormalBuildRunContext}. */ +public class BlazeAndroidBinaryNormalBuildRunContextCompat + extends BlazeAndroidBinaryNormalBuildRunContext { + + BlazeAndroidBinaryNormalBuildRunContextCompat( + Project project, + AndroidFacet facet, + RunConfiguration runConfiguration, + ExecutionEnvironment env, + BlazeAndroidBinaryRunConfigurationState configState, + ApkBuildStep buildStep, + String launchId) { + super(project, facet, runConfiguration, env, configState, buildStep, launchId); + } + + @Nullable + @Override + public XDebugSession startDebuggerSession( + AndroidDebugger androidDebugger, + AndroidDebuggerState androidDebuggerState, + ExecutionEnvironment env, + IDevice device, + ConsoleView consoleView, + ProgressIndicator indicator, + String packageName) { + try { + return BuildersKt.runBlocking( + EmptyCoroutineContext.INSTANCE, + (scope, continuation) -> + DebugSessionStarter.INSTANCE.attachDebuggerToStartedProcess( + device, + packageName, + env, + androidDebugger, + androidDebuggerState, + /*destroyRunningProcess*/ d -> { + d.forceStop(packageName); + return Unit.INSTANCE; + }, + indicator, + consoleView, + 15L, + continuation)); + } catch (InterruptedException e) { + throw new ProcessCanceledException(); + } + } +} diff --git a/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryProgramRunner.java b/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryProgramRunner.java new file mode 100644 index 00000000000..60bd11debaf --- /dev/null +++ b/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryProgramRunner.java @@ -0,0 +1,114 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.binary; + +import static com.intellij.openapi.application.ModalityState.NON_MODAL; + +import com.android.tools.idea.execution.common.AndroidConfigurationExecutor; +import com.android.tools.idea.profilers.ProfileRunExecutor; +import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationHandler; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.ExecutionResult; +import com.intellij.execution.Executor; +import com.intellij.execution.configurations.RunProfile; +import com.intellij.execution.configurations.RunProfileState; +import com.intellij.execution.configurations.RunnerSettings; +import com.intellij.execution.executors.DefaultDebugExecutor; +import com.intellij.execution.executors.DefaultRunExecutor; +import com.intellij.execution.runners.AsyncProgramRunner; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.runners.RunContentBuilder; +import com.intellij.execution.ui.RunContentDescriptor; +import com.intellij.openapi.application.ActionsKt; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; +import org.jetbrains.concurrency.AsyncPromise; +import org.jetbrains.concurrency.Promise; + +/** Program runner for configurations from {@link BlazeAndroidBinaryRunConfigurationHandler}. */ +public class BlazeAndroidBinaryProgramRunner extends AsyncProgramRunner { + @Override + public boolean canRun(String executorId, RunProfile profile) { + BlazeAndroidRunConfigurationHandler handler = + BlazeAndroidRunConfigurationHandler.getHandlerFrom(profile); + if (!(handler instanceof BlazeAndroidBinaryRunConfigurationHandler)) { + return false; + } + return (DefaultDebugExecutor.EXECUTOR_ID.equals(executorId) + || DefaultRunExecutor.EXECUTOR_ID.equals(executorId) + || ProfileRunExecutor.EXECUTOR_ID.equals(executorId)); + } + + @Override + protected Promise execute( + ExecutionEnvironment environment, RunProfileState state) { + FileDocumentManager.getInstance().saveAllDocuments(); + + AsyncPromise promise = new AsyncPromise<>(); + + ProgressManager.getInstance() + .run( + new Task.Backgroundable(environment.getProject(), "Launching ${runProfile.name}") { + @Override + public void run(ProgressIndicator indicator) { + try { + RunContentDescriptor descriptor; + if (state instanceof AndroidConfigurationExecutor) { + AndroidConfigurationExecutor configurationExecutor = + (AndroidConfigurationExecutor) state; + Executor executor = environment.getExecutor(); + if (executor.getId().equals(DefaultDebugExecutor.EXECUTOR_ID)) { + descriptor = configurationExecutor.debug(indicator); + } else if (executor.getId().equals(DefaultRunExecutor.EXECUTOR_ID) + || executor.getId().equals(ProfileRunExecutor.EXECUTOR_ID)) { + descriptor = configurationExecutor.run(indicator); + } else { + throw new ExecutionException("Unsupported executor"); + } + } else { + descriptor = doExecute(state, environment); + } + promise.setResult(descriptor); + } catch (ExecutionException e) { + boolean unused = promise.setError(e); + } + } + + @Override + public void onCancel() { + super.onCancel(); + promise.setResult(null); + } + }); + + return promise; + } + + private RunContentDescriptor doExecute( + final RunProfileState state, final ExecutionEnvironment env) throws ExecutionException { + ExecutionResult result = state.execute(env.getExecutor(), this); + return ActionsKt.invokeAndWaitIfNeeded( + NON_MODAL, + () -> new RunContentBuilder(result, env).showRunContent(env.getContentToReuse())); + } + + @Override + public String getRunnerId() { + return "AndroidBinaryProgramRunner"; + } +} diff --git a/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationHandlerCompat.java b/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationHandlerCompat.java new file mode 100644 index 00000000000..188f6e6b844 --- /dev/null +++ b/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationHandlerCompat.java @@ -0,0 +1,38 @@ +/* + * Copyright 2022 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.binary; + +import com.android.tools.idea.execution.common.DeployableToDevice; +import com.google.common.annotations.VisibleForTesting; +import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration; +import com.google.idea.blaze.base.settings.Blaze; +import com.intellij.openapi.project.Project; + +/** Compat class for {@link BlazeAndroidBinaryRunConfigurationHandlerCompat}. */ +public class BlazeAndroidBinaryRunConfigurationHandlerCompat { + protected final Project project; + protected final BlazeAndroidBinaryRunConfigurationState configState; + + @VisibleForTesting + protected BlazeAndroidBinaryRunConfigurationHandlerCompat( + BlazeCommandRunConfiguration configuration) { + project = configuration.getProject(); + configState = + new BlazeAndroidBinaryRunConfigurationState( + Blaze.buildSystemName(configuration.getProject())); + configuration.putUserData(DeployableToDevice.getKEY(), true); + } +} diff --git a/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/binary/DeploymentTimingReporterTask.java b/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/binary/DeploymentTimingReporterTask.java new file mode 100644 index 00000000000..bc567e3ff30 --- /dev/null +++ b/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/binary/DeploymentTimingReporterTask.java @@ -0,0 +1,60 @@ +/* + * Copyright 2021 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.binary; + +import com.android.tools.idea.execution.common.DeployOptions; +import com.android.tools.idea.run.ApkInfo; +import com.android.tools.idea.run.blaze.BlazeLaunchContext; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; +import com.android.tools.idea.run.tasks.DeployTasksCompat; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Stopwatch; +import com.google.common.collect.ImmutableList; +import com.google.idea.blaze.android.run.LaunchMetrics; +import com.intellij.execution.ExecutionException; +import com.intellij.openapi.project.Project; +import java.util.Collection; + +/** A wrapper launch task that wraps the given deployment task and logs the deployment latency. */ +public class DeploymentTimingReporterTask implements BlazeLaunchTask { + private final BlazeLaunchTask deployTask; + private final String launchId; + private final ImmutableList packages; + + public DeploymentTimingReporterTask( + String launchId, Project project, Collection packages, DeployOptions deployOptions) { + this.launchId = launchId; + this.deployTask = DeployTasksCompat.createDeployTask(project, packages, deployOptions); + this.packages = ImmutableList.copyOf(packages); + } + + @VisibleForTesting + public ImmutableList getPackages() { + return packages; + } + + @Override + public void run(BlazeLaunchContext launchContext) throws ExecutionException { + Stopwatch s = Stopwatch.createStarted(); + try { + deployTask.run(launchContext); + LaunchMetrics.logDeploymentTime(launchId, s.elapsed(), true); + } catch (ExecutionException e) { + LaunchMetrics.logDeploymentTime(launchId, s.elapsed(), false); + throw e; + } + } +} diff --git a/aswb/sdkcompat/as212/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContext.java b/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContext.java similarity index 62% rename from aswb/sdkcompat/as212/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContext.java rename to aswb/sdkcompat/as231/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContext.java index 8382dba2bb3..b2308bb55a2 100644 --- a/aswb/sdkcompat/as212/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContext.java +++ b/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContext.java @@ -15,15 +15,14 @@ */ package com.google.idea.blaze.android.run.binary.mobileinstall; +import com.android.ddmlib.IDevice; +import com.android.tools.idea.execution.common.debug.AndroidDebugger; +import com.android.tools.idea.execution.common.debug.AndroidDebuggerState; +import com.android.tools.idea.execution.common.debug.DebugSessionStarter; import com.android.tools.idea.run.ApkProvisionException; -import com.android.tools.idea.run.LaunchOptions; import com.android.tools.idea.run.activity.DefaultStartActivityFlagsProvider; import com.android.tools.idea.run.activity.StartActivityFlagsProvider; -import com.android.tools.idea.run.editor.AndroidDebugger; -import com.android.tools.idea.run.editor.AndroidDebuggerState; -import com.android.tools.idea.run.tasks.ConnectDebuggerTask; -import com.android.tools.idea.run.tasks.LaunchTask; -import com.android.tools.idea.run.util.LaunchStatus; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryApplicationLaunchTaskProvider; import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryRunConfigurationState; import com.google.idea.blaze.android.run.binary.UserIdHelper; @@ -33,8 +32,15 @@ import com.intellij.execution.Executor; import com.intellij.execution.configurations.RunConfiguration; import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.ui.ConsoleView; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; +import com.intellij.xdebugger.XDebugSession; import javax.annotation.Nullable; +import kotlin.Unit; +import kotlin.coroutines.EmptyCoroutineContext; +import kotlinx.coroutines.BuildersKt; import org.jetbrains.android.facet.AndroidFacet; /** Run Context for mobile install launches, #api4.0 compat. */ @@ -53,23 +59,20 @@ public BlazeAndroidBinaryMobileInstallRunContext( @SuppressWarnings("unchecked") // upstream API @Override - public LaunchTask getApplicationLaunchTask( - LaunchOptions launchOptions, - @Nullable Integer userId, - String contributorsAmStartOptions, - AndroidDebugger androidDebugger, - AndroidDebuggerState androidDebuggerState, - LaunchStatus launchStatus) + public BlazeLaunchTask getApplicationLaunchTask( + boolean isDebug, @Nullable Integer userId, String contributorsAmStartOptions) throws ExecutionException { String extraFlags = UserIdHelper.getFlagsFromUserId(userId); if (!contributorsAmStartOptions.isEmpty()) { extraFlags += (extraFlags.isEmpty() ? "" : " ") + contributorsAmStartOptions; } + if (isDebug) { + extraFlags += (extraFlags.isEmpty() ? "" : " ") + "-D"; + } final StartActivityFlagsProvider startActivityFlagsProvider = - new DefaultStartActivityFlagsProvider( - androidDebugger, androidDebuggerState, project, launchOptions.isDebug(), extraFlags); + new DefaultStartActivityFlagsProvider(project, isDebug, extraFlags); BlazeAndroidDeployInfo deployInfo; try { deployInfo = buildStep.getDeployInfo(); @@ -81,23 +84,40 @@ public LaunchTask getApplicationLaunchTask( applicationIdProvider, deployInfo.getMergedManifest(), configState, - startActivityFlagsProvider, - launchStatus); + startActivityFlagsProvider); } @Nullable @Override - @SuppressWarnings("unchecked") - public ConnectDebuggerTask getDebuggerTask( - AndroidDebugger androidDebugger, AndroidDebuggerState androidDebuggerState) - throws ExecutionException { - return androidDebugger.getConnectDebuggerTask( - env, - null, - applicationIdProvider, - facet, - androidDebuggerState, - runConfiguration.getType().getId()); + public XDebugSession startDebuggerSession( + AndroidDebugger androidDebugger, + AndroidDebuggerState androidDebuggerState, + ExecutionEnvironment env, + IDevice device, + ConsoleView consoleView, + ProgressIndicator indicator, + String packageName) { + try { + return BuildersKt.runBlocking( + EmptyCoroutineContext.INSTANCE, + (scope, continuation) -> + DebugSessionStarter.INSTANCE.attachDebuggerToStartedProcess( + device, + packageName, + env, + androidDebugger, + androidDebuggerState, + /*destroyRunningProcess*/ d -> { + d.forceStop(packageName); + return Unit.INSTANCE; + }, + indicator, + consoleView, + 15L, + continuation)); + } catch (InterruptedException e) { + throw new ProcessCanceledException(e); + } } @Override diff --git a/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContextBase.java b/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContextBase.java new file mode 100644 index 00000000000..e0de1841828 --- /dev/null +++ b/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContextBase.java @@ -0,0 +1,163 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.binary.mobileinstall; + +import com.android.ddmlib.IDevice; +import com.android.tools.idea.execution.common.DeployOptions; +import com.android.tools.idea.run.ApkFileUnit; +import com.android.tools.idea.run.ApkInfo; +import com.android.tools.idea.run.ApkProvisionException; +import com.android.tools.idea.run.ApplicationIdProvider; +import com.android.tools.idea.run.ConsoleProvider; +import com.android.tools.idea.run.LaunchOptions; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; +import com.android.tools.idea.run.blaze.BlazeLaunchTasksProvider; +import com.android.tools.idea.run.editor.ProfilerState; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryApplicationIdProvider; +import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryConsoleProvider; +import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryRunConfigurationState; +import com.google.idea.blaze.android.run.binary.DeploymentTimingReporterTask; +import com.google.idea.blaze.android.run.binary.UserIdHelper; +import com.google.idea.blaze.android.run.deployinfo.BlazeAndroidDeployInfo; +import com.google.idea.blaze.android.run.runner.ApkBuildStep; +import com.google.idea.blaze.android.run.runner.BlazeAndroidDeviceSelector; +import com.google.idea.blaze.android.run.runner.BlazeAndroidLaunchTasksProvider; +import com.google.idea.blaze.android.run.runner.BlazeAndroidRunContext; +import com.google.idea.blaze.base.sync.data.BlazeDataStorage; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.openapi.project.Project; +import java.util.Collections; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import org.jetbrains.android.facet.AndroidFacet; + +/** Run context for android_binary. */ +abstract class BlazeAndroidBinaryMobileInstallRunContextBase implements BlazeAndroidRunContext { + protected final Project project; + protected final AndroidFacet facet; + protected final RunConfiguration runConfiguration; + protected final ExecutionEnvironment env; + protected final BlazeAndroidBinaryRunConfigurationState configState; + protected final ConsoleProvider consoleProvider; + protected final ApplicationIdProvider applicationIdProvider; + protected final ApkBuildStep buildStep; + private final String launchId; + + public BlazeAndroidBinaryMobileInstallRunContextBase( + Project project, + AndroidFacet facet, + RunConfiguration runConfiguration, + ExecutionEnvironment env, + BlazeAndroidBinaryRunConfigurationState configState, + ApkBuildStep buildStep, + String launchId) { + this.project = project; + this.facet = facet; + this.runConfiguration = runConfiguration; + this.env = env; + this.configState = configState; + this.consoleProvider = new BlazeAndroidBinaryConsoleProvider(project); + this.buildStep = buildStep; + this.applicationIdProvider = new BlazeAndroidBinaryApplicationIdProvider(buildStep); + this.launchId = launchId; + } + + @Override + public BlazeAndroidDeviceSelector getDeviceSelector() { + return new BlazeAndroidDeviceSelector.NormalDeviceSelector(); + } + + @Override + public void augmentLaunchOptions(LaunchOptions.Builder options) { + options + .setDeploy(buildStep.needsIdeDeploy()) + .setOpenLogcatAutomatically(configState.showLogcatAutomatically()); + // This is needed for compatibility with #api211 + options.addExtraOptions( + ImmutableMap.of("android.profilers.state", configState.getProfilerState())); + } + + @Override + public ConsoleProvider getConsoleProvider() { + return consoleProvider; + } + + @Override + public ApplicationIdProvider getApplicationIdProvider() { + return applicationIdProvider; + } + + @Override + public ApkBuildStep getBuildStep() { + return buildStep; + } + + @Override + public ProfilerState getProfileState() { + return configState.getProfilerState(); + } + + @Override + public ImmutableList getDeployTasks(IDevice device, DeployOptions deployOptions) + throws ExecutionException { + if (!buildStep.needsIdeDeploy()) { + return ImmutableList.of(); + } + + BlazeAndroidDeployInfo deployInfo; + try { + deployInfo = buildStep.getDeployInfo(); + } catch (ApkProvisionException e) { + throw new ExecutionException(e); + } + + String packageName = deployInfo.getMergedManifest().packageName; + if (packageName == null) { + throw new ExecutionException("Could not determine package name from deploy info"); + } + + ApkInfo info = + new ApkInfo( + deployInfo.getApksToDeploy().stream() + .map(file -> new ApkFileUnit(BlazeDataStorage.WORKSPACE_MODULE_NAME, file)) + .collect(Collectors.toList()), + packageName); + return ImmutableList.of( + new DeploymentTimingReporterTask( + launchId, project, Collections.singletonList(info), deployOptions)); + } + + @Nullable + @Override + public Integer getUserId(IDevice device) throws ExecutionException { + return UserIdHelper.getUserIdFromConfigurationState(project, device, configState); + } + + @Override + public BlazeLaunchTasksProvider getLaunchTasksProvider(LaunchOptions launchOptions) + throws ExecutionException { + return new BlazeAndroidLaunchTasksProvider(project, this, applicationIdProvider, launchOptions); + } + + @Override + public String getAmStartOptions() { + return configState.getAmStartOptions(); + } +} diff --git a/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContextCompat.java b/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContextCompat.java new file mode 100644 index 00000000000..c69146a143d --- /dev/null +++ b/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContextCompat.java @@ -0,0 +1,84 @@ +/* + * Copyright 2022 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.mobileinstall; + +import com.android.ddmlib.IDevice; +import com.android.tools.idea.execution.common.debug.AndroidDebugger; +import com.android.tools.idea.execution.common.debug.AndroidDebuggerState; +import com.android.tools.idea.execution.common.debug.DebugSessionStarter; +import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryRunConfigurationState; +import com.google.idea.blaze.android.run.runner.ApkBuildStep; +import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.ui.ConsoleView; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.project.Project; +import com.intellij.xdebugger.XDebugSession; +import javax.annotation.Nullable; +import kotlin.Unit; +import kotlin.coroutines.EmptyCoroutineContext; +import kotlinx.coroutines.BuildersKt; +import org.jetbrains.android.facet.AndroidFacet; + +/** Compatct class for {@link BlazeAndroidBinaryMobileInstallRunContext}. */ +public class BlazeAndroidBinaryMobileInstallRunContextCompat + extends BlazeAndroidBinaryMobileInstallRunContext { + + public BlazeAndroidBinaryMobileInstallRunContextCompat( + Project project, + AndroidFacet facet, + BlazeCommandRunConfiguration configuration, + ExecutionEnvironment env, + BlazeAndroidBinaryRunConfigurationState configState, + ApkBuildStep buildStep, + String launchId) { + super(project, facet, configuration, env, configState, buildStep, launchId); + } + + @Nullable + @Override + public XDebugSession startDebuggerSession( + AndroidDebugger androidDebugger, + AndroidDebuggerState androidDebuggerState, + ExecutionEnvironment env, + IDevice device, + ConsoleView consoleView, + ProgressIndicator indicator, + String packageName) { + try { + return BuildersKt.runBlocking( + EmptyCoroutineContext.INSTANCE, + (scope, continuation) -> + DebugSessionStarter.INSTANCE.attachDebuggerToStartedProcess( + device, + packageName, + env, + androidDebugger, + androidDebuggerState, + /*destroyRunningProcess*/ d -> { + d.forceStop(packageName); + return Unit.INSTANCE; + }, + indicator, + consoleView, + 15L, + continuation)); + } catch (InterruptedException e) { + throw new ProcessCanceledException(e); + } + } +} diff --git a/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidDebuggerService.java b/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/runner/BlazeAndroidDebuggerService.java similarity index 61% rename from aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidDebuggerService.java rename to aswb/sdkcompat/as231/com/google/idea/blaze/android/run/runner/BlazeAndroidDebuggerService.java index 7bc133ab9e7..5715392a171 100644 --- a/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidDebuggerService.java +++ b/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/runner/BlazeAndroidDebuggerService.java @@ -15,16 +15,19 @@ */ package com.google.idea.blaze.android.run.runner; -import com.android.tools.idea.run.editor.AndroidDebugger; -import com.android.tools.idea.run.editor.AndroidDebuggerState; -import com.android.tools.idea.run.editor.AndroidJavaDebugger; +import com.android.tools.idea.execution.common.debug.AndroidDebugger; +import com.android.tools.idea.execution.common.debug.AndroidDebuggerState; +import com.android.tools.idea.execution.common.debug.impl.java.AndroidJavaDebugger; +import com.android.tools.ndk.run.editor.AutoAndroidDebuggerState; import com.android.tools.ndk.run.editor.NativeAndroidDebuggerState; import com.google.common.collect.ImmutableList; import com.google.idea.blaze.android.cppimpl.debug.BlazeAutoAndroidDebugger; +import com.google.idea.blaze.android.run.deployinfo.BlazeAndroidDeployInfo; import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; import com.intellij.ide.plugins.PluginManagerCore; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.project.Project; +import javax.annotation.Nullable; /** Provides android debuggers and debugger states for blaze projects. */ public interface BlazeAndroidDebuggerService { @@ -45,6 +48,9 @@ static BlazeAndroidDebuggerService getInstance(Project project) { */ AndroidDebuggerState getDebuggerState(AndroidDebugger debugger); + void configureNativeDebugger( + AndroidDebuggerState state, @Nullable BlazeAndroidDeployInfo deployInfo); + /** Default debugger service. */ class DefaultDebuggerService implements BlazeAndroidDebuggerService { private final Project project; @@ -85,10 +91,47 @@ public AndroidDebuggerState getDebuggerState(AndroidDebugger debugger) { return debuggerState; } - private static boolean isNdkPluginLoaded() { - return PluginManagerCore.getLoadedPlugins().stream() - .anyMatch( - d -> d.isEnabled() && d.getPluginId().getIdString().equals("com.android.tools.ndk")); + @Override + public void configureNativeDebugger( + AndroidDebuggerState rawState, @Nullable BlazeAndroidDeployInfo deployInfo) { + if (!isNdkPluginLoaded() && !(rawState instanceof AutoAndroidDebuggerState)) { + return; + } + AutoAndroidDebuggerState state = (AutoAndroidDebuggerState) rawState; + + // Source code is always relative to the workspace root in a blaze project. + String workingDirPath = WorkspaceRoot.fromProject(project).directory().getPath(); + state.setWorkingDir(workingDirPath); + + // Remote built binaries may use /proc/self/cwd to represent the working directory, + // so we manually map /proc/self/cwd to the workspace root. We used to use + // `plugin.symbol-file.dwarf.comp-dir-symlink-paths = "/proc/self/cwd"` + // to automatically resolve this, but it's no longer supported in newer versions of + // LLDB. + String sourceMapToWorkspaceRootCommand = + "settings append target.source-map /proc/self/cwd/ " + workingDirPath; + + ImmutableList startupCommands = + ImmutableList.builder() + .addAll(state.getUserStartupCommands()) + .add(sourceMapToWorkspaceRootCommand) + .build(); + state.setUserStartupCommands(startupCommands); + + // NDK plugin will pass symbol directories to LLDB as `settings append + // target.exec-search-paths`. + if (deployInfo != null) { + state.setSymbolDirs( + deployInfo.getSymbolFiles().stream() + .map(symbol -> symbol.getParentFile().getAbsolutePath()) + .collect(ImmutableList.toImmutableList())); + } } } + + static boolean isNdkPluginLoaded() { + return PluginManagerCore.getLoadedPlugins().stream() + .anyMatch( + d -> d.isEnabled() && d.getPluginId().getIdString().equals("com.android.tools.ndk")); + } } diff --git a/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/runner/BlazeAndroidLaunchTasksProvider.java b/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/runner/BlazeAndroidLaunchTasksProvider.java new file mode 100644 index 00000000000..a1528a9c4d9 --- /dev/null +++ b/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/runner/BlazeAndroidLaunchTasksProvider.java @@ -0,0 +1,177 @@ +/* + * Copyright 2022 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.runner; + +import static com.android.tools.idea.profilers.AndroidProfilerLaunchTaskContributor.isProfilerLaunch; + +import com.android.ddmlib.IDevice; +import com.android.tools.deployer.ApkVerifierTracker; +import com.android.tools.idea.execution.common.DeployOptions; +import com.android.tools.idea.execution.common.debug.AndroidDebugger; +import com.android.tools.idea.execution.common.debug.AndroidDebuggerState; +import com.android.tools.idea.profilers.AndroidProfilerLaunchTaskContributor; +import com.android.tools.idea.run.ApkProvisionException; +import com.android.tools.idea.run.ApplicationIdProvider; +import com.android.tools.idea.run.LaunchOptions; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; +import com.android.tools.idea.run.blaze.BlazeLaunchTasksProvider; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.idea.blaze.android.run.binary.UserIdHelper; +import com.google.idea.blaze.android.run.deployinfo.BlazeAndroidDeployInfo; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.ui.ConsoleView; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.project.Project; +import com.intellij.xdebugger.XDebugSession; +import java.util.Collections; +import java.util.List; +import org.jetbrains.annotations.NotNull; + +/** Normal launch tasks provider. #api4.1 */ +public class BlazeAndroidLaunchTasksProvider implements BlazeLaunchTasksProvider { + public static final String NATIVE_DEBUGGING_ENABLED = "NATIVE_DEBUGGING_ENABLED"; + private static final Logger LOG = Logger.getInstance(BlazeAndroidLaunchTasksProvider.class); + + private final Project project; + private final BlazeAndroidRunContext runContext; + private final ApplicationIdProvider applicationIdProvider; + private final LaunchOptions launchOptions; + + public BlazeAndroidLaunchTasksProvider( + Project project, + BlazeAndroidRunContext runContext, + ApplicationIdProvider applicationIdProvider, + LaunchOptions launchOptions) { + this.project = project; + this.runContext = runContext; + this.applicationIdProvider = applicationIdProvider; + this.launchOptions = launchOptions; + } + + @NotNull + @Override + public List getTasks(@NotNull IDevice device, boolean isDebug) + throws ExecutionException { + final List launchTasks = Lists.newArrayList(); + + String packageName; + try { + packageName = applicationIdProvider.getPackageName(); + } catch (ApkProvisionException e) { + throw new ExecutionException("Unable to determine application id: " + e); + } + + Integer userId = runContext.getUserId(device); + + // NOTE: Task for opening the profiler tool-window should come before deployment + // to ensure the tool-window opens correctly. This is required because starting + // the profiler session requires the tool-window to be open. + if (isProfilerLaunch(runContext.getExecutor())) { + launchTasks.add(new BlazeAndroidOpenProfilerWindowTask(project)); + } + + if (launchOptions.isDeploy()) { + String userIdFlags = UserIdHelper.getFlagsFromUserId(userId); + String skipVerification = + ApkVerifierTracker.getSkipVerificationInstallationFlag(device, packageName); + String pmInstallOption; + if (skipVerification != null) { + pmInstallOption = userIdFlags + " " + skipVerification; + } else { + pmInstallOption = userIdFlags; + } + DeployOptions deployOptions = + new DeployOptions(Collections.emptyList(), pmInstallOption, false, false); + ImmutableList deployTasks = runContext.getDeployTasks(device, deployOptions); + launchTasks.addAll(deployTasks); + } + + try { + if (isDebug) { + launchTasks.add( + new CheckApkDebuggableTask(project, runContext.getBuildStep().getDeployInfo())); + } + + ImmutableList.Builder amStartOptions = ImmutableList.builder(); + amStartOptions.add(runContext.getAmStartOptions()); + if (isProfilerLaunch(runContext.getExecutor())) { + amStartOptions.add( + AndroidProfilerLaunchTaskContributor.getAmStartOptions( + project, + packageName, + runContext.getProfileState(), + device, + runContext.getExecutor())); + } + BlazeLaunchTask appLaunchTask = + runContext.getApplicationLaunchTask( + isDebug, userId, String.join(" ", amStartOptions.build())); + if (appLaunchTask != null) { + launchTasks.add(appLaunchTask); + // TODO(arvindanekal): the live edit api changed and we cannot get the apk here to create + // live + // edit; the live edit team or Arvind need to fix this + } + } catch (ApkProvisionException e) { + throw new ExecutionException("Unable to determine application id: " + e); + } + + return ImmutableList.copyOf(launchTasks); + } + + @NotNull + @Override + public XDebugSession startDebugSession( + @NotNull ExecutionEnvironment environment, + @NotNull IDevice device, + @NotNull ConsoleView console, + @NotNull ProgressIndicator indicator, + @NotNull String packageName) + throws ExecutionException { + // Do not get debugger state directly from the debugger itself. + // See BlazeAndroidDebuggerService#getDebuggerState for an explanation. + boolean isNativeDebuggingEnabled = isNativeDebuggingEnabled(launchOptions); + BlazeAndroidDebuggerService debuggerService = BlazeAndroidDebuggerService.getInstance(project); + AndroidDebugger debugger = debuggerService.getDebugger(isNativeDebuggingEnabled); + if (debugger == null) { + throw new ExecutionException("Can't find AndroidDebugger for launch"); + } + AndroidDebuggerState debuggerState = debuggerService.getDebuggerState(debugger); + if (debuggerState == null) { + throw new ExecutionException("Can't find AndroidDebuggerState for launch"); + } + if (isNativeDebuggingEnabled) { + BlazeAndroidDeployInfo deployInfo = null; + try { + deployInfo = runContext.getBuildStep().getDeployInfo(); + } catch (ApkProvisionException e) { + LOG.error(e); + } + debuggerService.configureNativeDebugger(debuggerState, deployInfo); + } + + return runContext.startDebuggerSession( + debugger, debuggerState, environment, device, console, indicator, packageName); + } + + private boolean isNativeDebuggingEnabled(LaunchOptions launchOptions) { + Object flag = launchOptions.getExtraOption(NATIVE_DEBUGGING_ENABLED); + return flag instanceof Boolean && (Boolean) flag; + } +} diff --git a/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/runner/BlazeAndroidRunConfigurationRunner.java b/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/runner/BlazeAndroidRunConfigurationRunner.java new file mode 100644 index 00000000000..e0634874fff --- /dev/null +++ b/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/runner/BlazeAndroidRunConfigurationRunner.java @@ -0,0 +1,320 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.runner; + +import com.android.ddmlib.IDevice; +import com.android.tools.idea.editors.literals.LiveEditService; +import com.android.tools.idea.execution.common.AndroidConfigurationExecutor; +import com.android.tools.idea.execution.common.AndroidConfigurationExecutorRunProfileState; +import com.android.tools.idea.execution.common.AppRunSettings; +import com.android.tools.idea.execution.common.ApplicationDeployer; +import com.android.tools.idea.execution.common.ComponentLaunchOptions; +import com.android.tools.idea.execution.common.DeployOptions; +import com.android.tools.idea.execution.common.stats.RunStats; +import com.android.tools.idea.run.ApkProvider; +import com.android.tools.idea.run.ApplicationIdProvider; +import com.android.tools.idea.run.DeviceFutures; +import com.android.tools.idea.run.LaunchOptions; +import com.android.tools.idea.run.blaze.BlazeAndroidConfigurationExecutor; +import com.android.tools.idea.run.configuration.execution.AndroidComplicationConfigurationExecutor; +import com.android.tools.idea.run.configuration.execution.AndroidTileConfigurationExecutor; +import com.android.tools.idea.run.configuration.execution.AndroidWatchFaceConfigurationExecutor; +import com.android.tools.idea.run.configuration.execution.ApplicationDeployerImpl; +import com.android.tools.idea.run.configuration.execution.ComplicationLaunchOptions; +import com.android.tools.idea.run.configuration.execution.TileLaunchOptions; +import com.android.tools.idea.run.configuration.execution.WatchFaceLaunchOptions; +import com.android.tools.idea.run.editor.DeployTarget; +import com.android.tools.idea.run.editor.DeployTargetState; +import com.android.tools.idea.run.util.LaunchUtils; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryRunConfigurationState; +import com.google.idea.blaze.android.run.binary.mobileinstall.MobileInstallBuildStep; +import com.google.idea.blaze.android.run.deployinfo.BlazeApkProviderService; +import com.google.idea.blaze.base.async.executor.ProgressiveTaskWithProgressIndicator; +import com.google.idea.blaze.base.command.BlazeInvocationContext.ContextType; +import com.google.idea.blaze.base.experiments.ExperimentScope; +import com.google.idea.blaze.base.issueparser.BlazeIssueParser; +import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; +import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration; +import com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationRunner; +import com.google.idea.blaze.base.run.state.RunConfigurationState; +import com.google.idea.blaze.base.scope.BlazeContext; +import com.google.idea.blaze.base.scope.Scope; +import com.google.idea.blaze.base.scope.ScopedTask; +import com.google.idea.blaze.base.scope.output.IssueOutput; +import com.google.idea.blaze.base.scope.scopes.IdeaLogScope; +import com.google.idea.blaze.base.scope.scopes.ProblemsViewScope; +import com.google.idea.blaze.base.scope.scopes.ToolWindowScope; +import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.settings.BlazeUserSettings; +import com.google.idea.blaze.base.toolwindow.Task; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.Executor; +import com.intellij.execution.configurations.RunProfileState; +import com.intellij.execution.executors.DefaultDebugExecutor; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Key; +import java.util.Collections; +import java.util.concurrent.CancellationException; +import javax.annotation.Nullable; +import org.jetbrains.android.facet.AndroidFacet; +import org.jetbrains.android.util.AndroidBundle; +import org.jetbrains.annotations.NotNull; + +/** + * Supports the execution. Used by both android_binary and android_test. + * + *

Builds the APK and installs it, launches and debug tasks, etc. + * + *

Any indirection between android_binary/android_test, mobile-install, InstantRun etc. should + * come via the strategy class. + */ +public final class BlazeAndroidRunConfigurationRunner + implements BlazeCommandRunConfigurationRunner { + + private static final Logger LOG = Logger.getInstance(BlazeAndroidRunConfigurationRunner.class); + + private static final Key RUN_CONTEXT_KEY = + Key.create("blaze.run.context"); + public static final Key DEVICE_SESSION_KEY = + Key.create("blaze.device.session"); + + private final Module module; + private final BlazeAndroidRunContext runContext; + private final BlazeCommandRunConfiguration runConfig; + + public BlazeAndroidRunConfigurationRunner( + Module module, BlazeAndroidRunContext runContext, BlazeCommandRunConfiguration runConfig) { + this.module = module; + this.runContext = runContext; + this.runConfig = runConfig; + } + + @Override + @Nullable + public final RunProfileState getRunProfileState(final Executor executor, ExecutionEnvironment env) + throws ExecutionException { + + final AndroidFacet facet = AndroidFacet.getInstance(module); + assert facet != null : "Enforced by fatal validation check in createRunner."; + final Project project = env.getProject(); + + boolean isDebug = executor instanceof DefaultDebugExecutor; + + BlazeAndroidDeviceSelector deviceSelector = runContext.getDeviceSelector(); + BlazeAndroidDeviceSelector.DeviceSession deviceSession = + deviceSelector.getDevice(project, executor, env, isDebug, runConfig.getUniqueID()); + if (deviceSession == null) { + return null; + } + + DeployTarget deployTarget = deviceSession.deployTarget; + if (deployTarget != null && deployTarget.hasCustomRunProfileState(executor)) { + return deployTarget.getRunProfileState(executor, env, DeployTargetState.DEFAULT_STATE); + } + + DeviceFutures deviceFutures = deviceSession.deviceFutures; + if (deviceFutures == null) { + // The user deliberately canceled, or some error was encountered and exposed by the chooser. + // Quietly exit. + return null; + } + + if (deviceFutures.get().isEmpty()) { + throw new ExecutionException(AndroidBundle.message("deployment.target.not.found")); + } + + if (isDebug) { + String error = canDebug(deviceFutures, facet, module.getName()); + if (error != null) { + throw new ExecutionException(error); + } + } + + LaunchOptions.Builder launchOptionsBuilder = getDefaultLaunchOptions(); + runContext.augmentLaunchOptions(launchOptionsBuilder); + + // Store the run context on the execution environment so before-run tasks can access it. + env.putCopyableUserData(RUN_CONTEXT_KEY, runContext); + env.putCopyableUserData(DEVICE_SESSION_KEY, deviceSession); + + RunConfigurationState state = runConfig.getHandler().getState(); + + if (state instanceof BlazeAndroidBinaryRunConfigurationState + && ((BlazeAndroidBinaryRunConfigurationState) state).getCurrentWearLaunchOptions() + != null) { + ComponentLaunchOptions launchOptions = + ((BlazeAndroidBinaryRunConfigurationState) state).getCurrentWearLaunchOptions(); + + return getWearExecutor(launchOptions, env, deployTarget); + } + + ApkProvider apkProvider = + BlazeApkProviderService.getInstance() + .getApkProvider(env.getProject(), runContext.getBuildStep()); + final LaunchOptions launchOptions = launchOptionsBuilder.build(); + BlazeAndroidConfigurationExecutor runner = + new BlazeAndroidConfigurationExecutor( + runContext.getConsoleProvider(), + runContext.getApplicationIdProvider(), + env, + deviceFutures, + runContext.getLaunchTasksProvider(launchOptions), + launchOptions, + apkProvider, + LiveEditService.getInstance(env.getProject())); + return new AndroidConfigurationExecutorRunProfileState(runner); + } + + private RunProfileState getWearExecutor( + ComponentLaunchOptions launchOptions, ExecutionEnvironment env, DeployTarget deployTarget) + throws ExecutionException { + + AppRunSettings settings = + new AppRunSettings() { + @NotNull + @Override + public DeployOptions getDeployOptions() { + return new DeployOptions(Collections.emptyList(), "", true, true); + } + + @NotNull + @Override + public ComponentLaunchOptions getComponentLaunchOptions() { + return launchOptions; + } + + @Override + public Module getModule() { + return runConfig.getModules()[0]; + } + }; + + AndroidConfigurationExecutor configurationExecutor; + ApplicationIdProvider appIdProvider = runContext.getApplicationIdProvider(); + ApkProvider apkProvider = + BlazeApkProviderService.getInstance() + .getApkProvider(env.getProject(), runContext.getBuildStep()); + DeviceFutures deviceFutures = deployTarget.getDevices(env.getProject()); + + ApplicationDeployer deployer = + runContext.getBuildStep() instanceof MobileInstallBuildStep + ? new MobileInstallApplicationDeployer() + : new ApplicationDeployerImpl(env.getProject(), RunStats.from(env)); + + if (launchOptions instanceof TileLaunchOptions) { + configurationExecutor = + new AndroidTileConfigurationExecutor( + env, deviceFutures, settings, appIdProvider, apkProvider, deployer); + } else if (launchOptions instanceof WatchFaceLaunchOptions) { + configurationExecutor = + new AndroidWatchFaceConfigurationExecutor( + env, deviceFutures, settings, appIdProvider, apkProvider, deployer); + } else if (launchOptions instanceof ComplicationLaunchOptions) { + configurationExecutor = + new AndroidComplicationConfigurationExecutor( + env, deviceFutures, settings, appIdProvider, apkProvider, deployer); + } else { + throw new RuntimeException("Unknown launch options " + launchOptions.getClass().getName()); + } + + return new AndroidConfigurationExecutorRunProfileState(configurationExecutor); + } + + private static String canDebug( + DeviceFutures deviceFutures, AndroidFacet facet, String moduleName) { + // If we are debugging on a device, then the app needs to be debuggable + for (ListenableFuture future : deviceFutures.get()) { + if (!future.isDone()) { + // this is an emulator, and we assume that all emulators are debuggable + continue; + } + IDevice device = Futures.getUnchecked(future); + if (!LaunchUtils.canDebugAppOnDevice(facet, device)) { + return AndroidBundle.message( + "android.cannot.debug.noDebugPermissions", moduleName, device.getName()); + } + } + return null; + } + + private static LaunchOptions.Builder getDefaultLaunchOptions() { + return LaunchOptions.builder(); + } + + @Override + public boolean executeBeforeRunTask(ExecutionEnvironment env) { + final Project project = env.getProject(); + BlazeUserSettings settings = BlazeUserSettings.getInstance(); + return Scope.root( + context -> { + context + .push(new ProblemsViewScope(project, settings.getShowProblemsViewOnRun())) + .push(new ExperimentScope()) + .push( + new ToolWindowScope.Builder( + project, new Task(project, "Build apk", Task.Type.BEFORE_LAUNCH)) + .setPopupBehavior(settings.getShowBlazeConsoleOnRun()) + .setIssueParsers( + BlazeIssueParser.defaultIssueParsers( + project, + WorkspaceRoot.fromProject(project), + ContextType.BeforeRunTask)) + .build()) + .push(new IdeaLogScope()); + + BlazeAndroidRunContext runContext = env.getCopyableUserData(RUN_CONTEXT_KEY); + if (runContext == null) { + IssueOutput.error("Could not find run context. Please try again").submit(context); + return false; + } + BlazeAndroidDeviceSelector.DeviceSession deviceSession = + env.getCopyableUserData(DEVICE_SESSION_KEY); + + ApkBuildStep buildStep = runContext.getBuildStep(); + ScopedTask buildTask = + new ScopedTask(context) { + @Override + protected Void execute(BlazeContext context) { + buildStep.build(context, deviceSession); + return null; + } + }; + + try { + ListenableFuture buildFuture = + ProgressiveTaskWithProgressIndicator.builder( + project, + String.format("Executing %s apk build", Blaze.buildSystemName(project))) + .submitTaskWithResult(buildTask); + Futures.getChecked(buildFuture, ExecutionException.class); + } catch (ExecutionException e) { + context.setHasError(); + } catch (CancellationException e) { + context.setCancelled(); + } catch (Exception e) { + LOG.error(e); + return false; + } + return context.shouldContinue(); + }); + } +} diff --git a/aswb/sdkcompat/as212/com/google/idea/blaze/android/run/runner/BlazeAndroidRunContext.java b/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/runner/BlazeAndroidRunContext.java similarity index 58% rename from aswb/sdkcompat/as212/com/google/idea/blaze/android/run/runner/BlazeAndroidRunContext.java rename to aswb/sdkcompat/as231/com/google/idea/blaze/android/run/runner/BlazeAndroidRunContext.java index 04546989cd5..a484e1735a3 100644 --- a/aswb/sdkcompat/as212/com/google/idea/blaze/android/run/runner/BlazeAndroidRunContext.java +++ b/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/runner/BlazeAndroidRunContext.java @@ -1,11 +1,11 @@ /* - * Copyright 2016 The Bazel Authors. All rights reserved. + * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this 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, @@ -16,20 +16,22 @@ package com.google.idea.blaze.android.run.runner; import com.android.ddmlib.IDevice; +import com.android.tools.idea.execution.common.DeployOptions; +import com.android.tools.idea.execution.common.debug.AndroidDebugger; +import com.android.tools.idea.execution.common.debug.AndroidDebuggerState; import com.android.tools.idea.run.ApplicationIdProvider; -import com.android.tools.idea.run.ConsolePrinter; import com.android.tools.idea.run.ConsoleProvider; import com.android.tools.idea.run.LaunchOptions; -import com.android.tools.idea.run.editor.AndroidDebugger; -import com.android.tools.idea.run.editor.AndroidDebuggerState; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; +import com.android.tools.idea.run.blaze.BlazeLaunchTasksProvider; import com.android.tools.idea.run.editor.ProfilerState; -import com.android.tools.idea.run.tasks.ConnectDebuggerTask; -import com.android.tools.idea.run.tasks.LaunchTask; -import com.android.tools.idea.run.tasks.LaunchTasksProvider; -import com.android.tools.idea.run.util.LaunchStatus; import com.google.common.collect.ImmutableList; import com.intellij.execution.ExecutionException; import com.intellij.execution.Executor; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.ui.ConsoleView; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.xdebugger.XDebugSession; import javax.annotation.Nullable; import org.jetbrains.annotations.NotNull; @@ -44,34 +46,34 @@ public interface BlazeAndroidRunContext { ApkBuildStep getBuildStep(); - ApplicationIdProvider getApplicationIdProvider() throws ExecutionException; + ApplicationIdProvider getApplicationIdProvider(); - LaunchTasksProvider getLaunchTasksProvider(LaunchOptions.Builder launchOptionsBuilder) + BlazeLaunchTasksProvider getLaunchTasksProvider(LaunchOptions launchOptions) throws ExecutionException; /** Returns the tasks to deploy the application. */ - ImmutableList getDeployTasks(IDevice device, LaunchOptions launchOptions) + ImmutableList getDeployTasks(IDevice device, DeployOptions deployOptions) throws ExecutionException; /** Returns the task to launch the application. */ @Nullable - LaunchTask getApplicationLaunchTask( - LaunchOptions launchOptions, - @Nullable Integer userId, - @NotNull String contributorsAmStartOptions, - AndroidDebugger androidDebugger, - AndroidDebuggerState androidDebuggerState, - LaunchStatus launchStatus) + BlazeLaunchTask getApplicationLaunchTask( + boolean isDebug, @Nullable Integer userId, @NotNull String contributorsAmStartOptions) throws ExecutionException; /** Returns the task to connect the debugger. */ @Nullable - ConnectDebuggerTask getDebuggerTask( - AndroidDebugger androidDebugger, AndroidDebuggerState androidDebuggerState) - throws ExecutionException; + XDebugSession startDebuggerSession( + AndroidDebugger androidDebugger, + AndroidDebuggerState androidDebuggerState, + ExecutionEnvironment env, + IDevice device, + ConsoleView consoleView, + ProgressIndicator indicator, + String packageName); @Nullable - Integer getUserId(IDevice device, ConsolePrinter consolePrinter) throws ExecutionException; + Integer getUserId(IDevice device) throws ExecutionException; String getAmStartOptions(); diff --git a/aswb/sdkcompat/as213/com/google/idea/blaze/android/run/runner/LaunchOptionsCompat.java b/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/runner/LaunchOptionsCompat.java similarity index 93% rename from aswb/sdkcompat/as213/com/google/idea/blaze/android/run/runner/LaunchOptionsCompat.java rename to aswb/sdkcompat/as231/com/google/idea/blaze/android/run/runner/LaunchOptionsCompat.java index 5a606a8a2ea..f31959514c5 100644 --- a/aswb/sdkcompat/as213/com/google/idea/blaze/android/run/runner/LaunchOptionsCompat.java +++ b/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/runner/LaunchOptionsCompat.java @@ -24,6 +24,6 @@ private LaunchOptionsCompat() {} /** Create default launch options to maintain compatibility with #api211. */ public static LaunchOptions.Builder getDefaultLaunchOptions() { - return LaunchOptions.builder().setClearLogcatBeforeStart(false); + return LaunchOptions.builder(); } } diff --git a/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/runner/MobileInstallApplicationDeployer.java b/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/runner/MobileInstallApplicationDeployer.java new file mode 100644 index 00000000000..f637ec7c654 --- /dev/null +++ b/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/runner/MobileInstallApplicationDeployer.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.runner; + +import com.android.ddmlib.IDevice; +import com.android.tools.deployer.ApkParser; +import com.android.tools.deployer.Deployer; +import com.android.tools.deployer.DeployerException; +import com.android.tools.deployer.model.Apk; +import com.android.tools.deployer.model.App; +import com.android.tools.idea.execution.common.ApplicationDeployer; +import com.android.tools.idea.execution.common.DeployOptions; +import com.android.tools.idea.log.LogWrapper; +import com.android.tools.idea.run.ApkFileUnit; +import com.android.tools.idea.run.ApkInfo; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.ProgressIndicator; +import java.nio.file.Path; +import java.util.List; +import java.util.stream.Collectors; +import org.jetbrains.annotations.NotNull; + +/** Deploys mobile install application. */ +public class MobileInstallApplicationDeployer implements ApplicationDeployer { + final Logger log = Logger.getInstance(this.getClass()); + + public MobileInstallApplicationDeployer() {} + + @NotNull + @Override + public Deployer.Result fullDeploy( + @NotNull IDevice device, + @NotNull ApkInfo apkInfo, + @NotNull DeployOptions deployOptions, + ProgressIndicator indicator) + throws DeployerException { + final List apkPaths = + apkInfo.getFiles().stream() + .map(ApkFileUnit::getApkPath) + .map(Path::toString) + .collect(Collectors.toList()); + final List apks = new ApkParser().parsePaths(apkPaths); + App app = new App(apkInfo.getApplicationId(), apks, device, new LogWrapper(log)); + return new Deployer.Result(false, false, false, app); + } + + @NotNull + @Override + public Deployer.Result applyChangesDeploy( + @NotNull IDevice device, + @NotNull ApkInfo app, + @NotNull DeployOptions deployOptions, + ProgressIndicator indicator) + throws DeployerException { + throw new RuntimeException("Apply changes is not supported for mobile-install"); + } + + @NotNull + @Override + public Deployer.Result applyCodeChangesDeploy( + @NotNull IDevice device, + @NotNull ApkInfo app, + @NotNull DeployOptions deployOptions, + ProgressIndicator indicator) + throws DeployerException { + throw new RuntimeException("Apply code changes is not supported for mobile-install"); + } +} diff --git a/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/test/BlazeAndroidTestProgramRunner.java b/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/test/BlazeAndroidTestProgramRunner.java new file mode 100644 index 00000000000..b294b565455 --- /dev/null +++ b/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/test/BlazeAndroidTestProgramRunner.java @@ -0,0 +1,77 @@ +/* + * Copyright 2018 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.test; + +import com.android.tools.idea.execution.common.AndroidConfigurationExecutor; +import com.android.tools.idea.execution.common.AndroidConfigurationProgramRunner; +import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationHandler; +import com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.configurations.RunProfile; +import com.intellij.execution.executors.DefaultDebugExecutor; +import com.intellij.execution.executors.DefaultRunExecutor; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.ui.RunContentDescriptor; +import com.intellij.openapi.progress.ProgressIndicator; +import java.util.Collections; +import java.util.List; +import org.jetbrains.annotations.NotNull; + +/** Program runner for configurations from {@link BlazeAndroidTestRunConfigurationHandler}. */ +public class BlazeAndroidTestProgramRunner extends AndroidConfigurationProgramRunner { + @Override + public boolean canRun(String executorId, RunProfile profile) { + BlazeAndroidRunConfigurationHandler handler = + BlazeAndroidRunConfigurationHandler.getHandlerFrom(profile); + if (!(handler instanceof BlazeAndroidTestRunConfigurationHandler)) { + return false; + } + return super.canRun(executorId, profile) && DefaultRunExecutor.EXECUTOR_ID.equals(executorId) + || DefaultDebugExecutor.EXECUTOR_ID.equals(executorId); + } + + @Override + public String getRunnerId() { + return "AndroidTestProgramRunner"; + } + + @Override + protected boolean canRunWithMultipleDevices(@NotNull String executorId) { + return true; + } + + @NotNull + @Override + protected List getSupportedConfigurationTypeIds() { + return Collections.singletonList(BlazeCommandRunConfigurationType.getInstance().getId()); + } + + @NotNull + @Override + protected RunContentDescriptor run( + @NotNull ExecutionEnvironment environment, + @NotNull AndroidConfigurationExecutor executor, + @NotNull ProgressIndicator indicator) + throws ExecutionException { + if (DefaultDebugExecutor.EXECUTOR_ID.equals(environment.getExecutor().getId())) { + return executor.debug(indicator); + } + if (DefaultRunExecutor.EXECUTOR_ID.equals(environment.getExecutor().getId())) { + return executor.run(indicator); + } + throw new RuntimeException("Unsupported executor"); + } +} diff --git a/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationHandler.java b/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationHandler.java new file mode 100644 index 00000000000..67e2ad4dfb7 --- /dev/null +++ b/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationHandler.java @@ -0,0 +1,223 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.test; + +import com.android.tools.idea.execution.common.DeployableToDevice; +import com.android.tools.idea.run.ValidationError; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.idea.blaze.android.run.ApkBuildStepProvider; +import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationCommonState; +import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationHandler; +import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationValidationUtil; +import com.google.idea.blaze.android.run.LaunchMetrics; +import com.google.idea.blaze.android.run.runner.ApkBuildStep; +import com.google.idea.blaze.android.run.runner.BlazeAndroidRunConfigurationRunner; +import com.google.idea.blaze.android.run.runner.BlazeAndroidRunContext; +import com.google.idea.blaze.android.run.runner.FullApkBuildStep; +import com.google.idea.blaze.android.run.test.BlazeAndroidTestLaunchMethodsProvider.AndroidTestLaunchMethod; +import com.google.idea.blaze.base.command.BlazeCommandName; +import com.google.idea.blaze.base.command.BlazeInvocationContext; +import com.google.idea.blaze.base.model.primitives.Label; +import com.google.idea.blaze.base.model.primitives.TargetExpression; +import com.google.idea.blaze.base.projectview.ProjectViewManager; +import com.google.idea.blaze.base.projectview.ProjectViewSet; +import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration; +import com.google.idea.blaze.base.run.BlazeConfigurationNameBuilder; +import com.google.idea.blaze.base.run.ExecutorType; +import com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationRunner; +import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.sync.data.BlazeDataStorage; +import com.google.idea.blaze.base.sync.projectstructure.ModuleFinder; +import com.google.idea.blaze.java.AndroidBlazeRules; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.Executor; +import com.intellij.execution.JavaExecutionUtil; +import com.intellij.execution.configurations.RuntimeConfigurationException; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import java.util.List; +import javax.annotation.Nullable; +import org.jetbrains.android.facet.AndroidFacet; + +/** + * {@link com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationHandler} for + * android_test targets. + */ +public class BlazeAndroidTestRunConfigurationHandler + implements BlazeAndroidRunConfigurationHandler { + private final Project project; + private final BlazeAndroidTestRunConfigurationState configState; + + BlazeAndroidTestRunConfigurationHandler(BlazeCommandRunConfiguration configuration) { + this.project = configuration.getProject(); + this.configState = + new BlazeAndroidTestRunConfigurationState( + Blaze.buildSystemName(configuration.getProject())); + configuration.putUserData(DeployableToDevice.getKEY(), true); + } + + @Override + public BlazeAndroidTestRunConfigurationState getState() { + return configState; + } + + @Override + public BlazeAndroidRunConfigurationCommonState getCommonState() { + return configState.getCommonState(); + } + + @Override + public BlazeCommandRunConfigurationRunner createRunner( + Executor executor, ExecutionEnvironment env) throws ExecutionException { + Project project = env.getProject(); + BlazeCommandRunConfiguration configuration = + BlazeAndroidRunConfigurationHandler.getCommandConfig(env); + + BlazeAndroidRunConfigurationValidationUtil.validate(project); + Module module = + ModuleFinder.getInstance(env.getProject()) + .findModuleByName(BlazeDataStorage.WORKSPACE_MODULE_NAME); + AndroidFacet facet = module != null ? AndroidFacet.getInstance(module) : null; + ProjectViewSet projectViewSet = ProjectViewManager.getInstance(project).getProjectViewSet(); + + ImmutableList blazeFlags = + configState + .getCommonState() + .getExpandedBuildFlags( + project, + projectViewSet, + BlazeCommandName.TEST, + BlazeInvocationContext.runConfigContext( + ExecutorType.fromExecutor(env.getExecutor()), configuration.getType(), false)); + ImmutableList exeFlags = + ImmutableList.copyOf( + configState.getCommonState().getExeFlagsState().getFlagsForExternalProcesses()); + + // We collect metrics from a few different locations. In order to tie them all + // together, we create a unique launch id. + String launchId = LaunchMetrics.newLaunchId(); + Label label = Label.create(configuration.getSingleTarget().toString()); + + ApkBuildStep buildStep = + getTestBuildStep( + project, configState, configuration, blazeFlags, exeFlags, launchId, label); + + BlazeAndroidRunContext runContext = + new BlazeAndroidTestRunContext( + project, facet, configuration, env, configState, label, blazeFlags, buildStep); + + LaunchMetrics.logTestLaunch( + launchId, configState.getLaunchMethod().name(), env.getExecutor().getId()); + + return new BlazeAndroidRunConfigurationRunner(module, runContext, configuration); + } + + private static ApkBuildStep getTestBuildStep( + Project project, + BlazeAndroidTestRunConfigurationState configState, + BlazeCommandRunConfiguration configuration, + ImmutableList blazeFlags, + ImmutableList exeFlags, + String launchId, + Label label) + throws ExecutionException { + if (configuration.getTargetKind() + == AndroidBlazeRules.RuleTypes.ANDROID_INSTRUMENTATION_TEST.getKind()) { + boolean useMobileInstall = + AndroidTestLaunchMethod.MOBILE_INSTALL.equals(configState.getLaunchMethod()); + return ApkBuildStepProvider.getInstance(Blaze.getBuildSystemName(project)) + .getAitBuildStep( + project, + useMobileInstall, + /* nativeDebuggingEnabled= */ false, + label, + blazeFlags, + exeFlags, + launchId); + } else { + // TODO(b/248317444): This path is only invoked for the deprecated {@code android_test} + // targets, and should eventually be removed. + return new FullApkBuildStep(project, label, blazeFlags, /* nativeDebuggingEnabled= */ false); + } + } + + @Override + public final void checkConfiguration() throws RuntimeConfigurationException { + BlazeAndroidRunConfigurationValidationUtil.throwTopConfigurationError(validate()); + } + + /** + * We collect errors rather than throwing to avoid missing fatal errors by exiting early for a + * warning. We use a separate method for the collection so the compiler prevents us from + * accidentally throwing. + */ + private List validate() { + List errors = Lists.newArrayList(); + errors.addAll(BlazeAndroidRunConfigurationValidationUtil.validateWorkspaceModule(project)); + errors.addAll(configState.validate(project)); + return errors; + } + + @Override + @Nullable + public String suggestedName(BlazeCommandRunConfiguration configuration) { + TargetExpression target = configuration.getSingleTarget(); + if (target == null) { + return null; + } + BlazeConfigurationNameBuilder nameBuilder = new BlazeConfigurationNameBuilder(configuration); + + boolean isClassTest = + configState.getTestingType() == BlazeAndroidTestRunConfigurationState.TEST_CLASS; + boolean isMethodTest = + configState.getTestingType() == BlazeAndroidTestRunConfigurationState.TEST_METHOD; + if ((isClassTest || isMethodTest) && configState.getClassName() != null) { + // Get the class name without the package. + String className = JavaExecutionUtil.getPresentableClassName(configState.getClassName()); + if (className != null) { + String targetString = className; + if (isMethodTest) { + targetString += "#" + configState.getMethodName(); + } + + if (getState().getLaunchMethod().equals(AndroidTestLaunchMethod.NON_BLAZE)) { + return targetString; + } else { + return nameBuilder.setTargetString(targetString).build(); + } + } + } + return nameBuilder.build(); + } + + @Override + @Nullable + public BlazeCommandName getCommandName() { + if (getState().getLaunchMethod().equals(AndroidTestLaunchMethod.BLAZE_TEST)) { + return BlazeCommandName.TEST; + } else if (getState().getLaunchMethod().equals(AndroidTestLaunchMethod.MOBILE_INSTALL)) { + return BlazeCommandName.MOBILE_INSTALL; + } + return null; + } + + @Override + public String getHandlerName() { + return "Android Test Handler"; + } +} diff --git a/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunContext.java b/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunContext.java new file mode 100644 index 00000000000..149af277a6b --- /dev/null +++ b/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunContext.java @@ -0,0 +1,285 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.test; + +import static com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryNormalBuildRunContextBase.getApkInfoToInstall; + +import com.android.ddmlib.IDevice; +import com.android.tools.idea.execution.common.DeployOptions; +import com.android.tools.idea.execution.common.debug.AndroidDebugger; +import com.android.tools.idea.execution.common.debug.AndroidDebuggerState; +import com.android.tools.idea.execution.common.debug.DebugSessionStarter; +import com.android.tools.idea.run.ApkProvider; +import com.android.tools.idea.run.ApkProvisionException; +import com.android.tools.idea.run.ApplicationIdProvider; +import com.android.tools.idea.run.ConsoleProvider; +import com.android.tools.idea.run.LaunchOptions; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; +import com.android.tools.idea.run.blaze.BlazeLaunchTasksProvider; +import com.android.tools.idea.run.editor.ProfilerState; +import com.android.tools.idea.run.tasks.DeployTasksCompat; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.idea.blaze.android.run.deployinfo.BlazeAndroidDeployInfo; +import com.google.idea.blaze.android.run.deployinfo.BlazeApkProviderService; +import com.google.idea.blaze.android.run.runner.ApkBuildStep; +import com.google.idea.blaze.android.run.runner.BlazeAndroidDeviceSelector; +import com.google.idea.blaze.android.run.runner.BlazeAndroidLaunchTasksProvider; +import com.google.idea.blaze.android.run.runner.BlazeAndroidRunContext; +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.BlazeTestUiSession; +import com.google.idea.blaze.base.run.testlogs.BlazeTestResultHolder; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.Executor; +import com.intellij.execution.process.NopProcessHandler; +import com.intellij.execution.process.ProcessHandler; +import com.intellij.execution.process.ProcessOutputTypes; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.ui.ConsoleView; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.project.Project; +import com.intellij.xdebugger.XDebugSession; +import java.util.List; +import javax.annotation.Nullable; +import kotlin.Unit; +import kotlin.coroutines.EmptyCoroutineContext; +import kotlinx.coroutines.BuildersKt; +import org.jetbrains.android.facet.AndroidFacet; + +/** Run context for android_test. */ +public class BlazeAndroidTestRunContext implements BlazeAndroidRunContext { + protected final Project project; + protected final AndroidFacet facet; + protected final BlazeCommandRunConfiguration runConfiguration; + protected final ExecutionEnvironment env; + protected final BlazeAndroidTestRunConfigurationState configState; + protected final Label label; + protected final ImmutableList blazeFlags; + protected final List launchTaskCompleteListeners = Lists.newArrayList(); + protected final ConsoleProvider consoleProvider; + protected final ApkBuildStep buildStep; + protected final ApplicationIdProvider applicationIdProvider; + protected final ApkProvider apkProvider; + private final BlazeTestResultHolder testResultsHolder = new BlazeTestResultHolder(); + + public BlazeAndroidTestRunContext( + Project project, + AndroidFacet facet, + BlazeCommandRunConfiguration runConfiguration, + ExecutionEnvironment env, + BlazeAndroidTestRunConfigurationState configState, + Label label, + ImmutableList blazeFlags, + ApkBuildStep buildStep) { + this.project = project; + this.facet = facet; + this.runConfiguration = runConfiguration; + this.env = env; + this.label = label; + this.configState = configState; + this.buildStep = buildStep; + this.blazeFlags = blazeFlags; + switch (configState.getLaunchMethod()) { + case MOBILE_INSTALL: + case NON_BLAZE: + consoleProvider = new AitIdeTestConsoleProvider(runConfiguration, configState); + break; + case BLAZE_TEST: + BlazeTestUiSession session = + BlazeTestUiSession.create(ImmutableList.of(), testResultsHolder); + this.consoleProvider = new AitBlazeTestConsoleProvider(project, runConfiguration, session); + break; + default: + throw new IllegalStateException( + "Unsupported launch method " + configState.getLaunchMethod()); + } + applicationIdProvider = new BlazeAndroidTestApplicationIdProvider(buildStep); + apkProvider = BlazeApkProviderService.getInstance().getApkProvider(project, buildStep); + } + + @Override + public BlazeAndroidDeviceSelector getDeviceSelector() { + return new BlazeAndroidDeviceSelector.NormalDeviceSelector(); + } + + @Override + public void augmentLaunchOptions(LaunchOptions.Builder options) { + options.setDeploy(!configState.getLaunchMethod().equals(AndroidTestLaunchMethod.BLAZE_TEST)); + } + + @Override + public ConsoleProvider getConsoleProvider() { + return consoleProvider; + } + + @Override + public ApplicationIdProvider getApplicationIdProvider() { + return applicationIdProvider; + } + + @Nullable + @Override + public ApkBuildStep getBuildStep() { + return buildStep; + } + + @Override + public ProfilerState getProfileState() { + return null; + } + + @Override + public BlazeLaunchTasksProvider getLaunchTasksProvider(LaunchOptions launchOptions) + throws ExecutionException { + return new BlazeAndroidLaunchTasksProvider(project, this, applicationIdProvider, launchOptions); + } + + @Override + public ImmutableList getDeployTasks(IDevice device, DeployOptions deployOptions) + throws ExecutionException { + if (configState.getLaunchMethod() != AndroidTestLaunchMethod.NON_BLAZE) { + return ImmutableList.of(); + } + return ImmutableList.of( + DeployTasksCompat.createDeployTask( + project, getApkInfoToInstall(device, deployOptions, apkProvider), deployOptions)); + } + + @Override + @Nullable + public BlazeLaunchTask getApplicationLaunchTask( + boolean isDebug, @Nullable Integer userId, String contributorsAmStartOptions) + throws ExecutionException { + switch (configState.getLaunchMethod()) { + case BLAZE_TEST: + BlazeAndroidTestFilter testFilter = + new BlazeAndroidTestFilter( + configState.getTestingType(), + configState.getClassName(), + configState.getMethodName(), + configState.getPackageName()); + return new BlazeAndroidTestLaunchTask( + project, label, blazeFlags, testFilter, this, isDebug, testResultsHolder); + case NON_BLAZE: + case MOBILE_INSTALL: + BlazeAndroidDeployInfo deployInfo; + try { + deployInfo = buildStep.getDeployInfo(); + } catch (ApkProvisionException e) { + throw new ExecutionException(e); + } + return StockAndroidTestLaunchTask.getStockTestLaunchTask( + configState, applicationIdProvider, isDebug, deployInfo, project); + } + throw new AssertionError(); + } + + @Override + @SuppressWarnings({"unchecked", "rawtypes"}) // Raw type from upstream. + public XDebugSession startDebuggerSession( + AndroidDebugger androidDebugger, + AndroidDebuggerState androidDebuggerState, + ExecutionEnvironment env, + IDevice device, + ConsoleView consoleView, + ProgressIndicator indicator, + String packageName) { + try { + return BuildersKt.runBlocking( + EmptyCoroutineContext.INSTANCE, + (scope, continuation) -> { + switch (configState.getLaunchMethod()) { + case BLAZE_TEST: + + /** + * Wires up listeners to automatically reconnect the debugger for each test method. + * When you `blaze test` an android_test in debug mode, it kills the instrumentation + * process between each test method, disconnecting the debugger. We listen for the + * start of a new method waiting for a debugger, and reconnect. TODO: Support + * stopping Blaze from the UI. This is hard because we have no way to distinguish + * process handler termination/debug session ending initiated by the user. + */ + final ProcessHandler masterProcessHandler = new NopProcessHandler(); + addLaunchTaskCompleteListener( + () -> { + masterProcessHandler.notifyTextAvailable( + "Test run completed.\n", ProcessOutputTypes.STDOUT); + masterProcessHandler.detachProcess(); + }); + return DebugSessionStarter.INSTANCE.attachReattachingDebuggerToStartedProcess( + device, + packageName, + masterProcessHandler, + env, + androidDebugger, + androidDebuggerState, + indicator, + consoleView, + Long.MAX_VALUE, + continuation); + case NON_BLAZE: + case MOBILE_INSTALL: + return DebugSessionStarter.INSTANCE.attachDebuggerToStartedProcess( + device, + packageName, + env, + androidDebugger, + androidDebuggerState, + /*destroyRunningProcess*/ d -> { + d.forceStop(packageName); + return Unit.INSTANCE; + }, + indicator, + consoleView, + Long.MAX_VALUE, + continuation); + } + throw new RuntimeException("Unknown lunch mode"); + }); + } catch (InterruptedException e) { + throw new ProcessCanceledException(); + } + } + + void onLaunchTaskComplete() { + for (Runnable runnable : launchTaskCompleteListeners) { + runnable.run(); + } + } + + void addLaunchTaskCompleteListener(Runnable runnable) { + launchTaskCompleteListeners.add(runnable); + } + + @Override + public Executor getExecutor() { + return env.getExecutor(); + } + + @Nullable + @Override + public Integer getUserId(IDevice device) { + return null; + } + + @Override + public String getAmStartOptions() { + return ""; + } +} diff --git a/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/testrecorder/TestRecorderBlazeCommandRunConfigurationProxy.java b/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/testrecorder/TestRecorderBlazeCommandRunConfigurationProxy.java new file mode 100644 index 00000000000..1a7f9a4f621 --- /dev/null +++ b/aswb/sdkcompat/as231/com/google/idea/blaze/android/run/testrecorder/TestRecorderBlazeCommandRunConfigurationProxy.java @@ -0,0 +1,43 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.testrecorder; + +import com.android.annotations.Nullable; +import com.android.ddmlib.IDevice; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.idea.blaze.android.run.runner.BlazeAndroidRunConfigurationRunner; +import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration; +import com.intellij.execution.runners.ExecutionEnvironment; +import java.util.List; + +/** Compat class for implementing TestRecorderBlazeCommandRunConfigurationProxy. */ +public class TestRecorderBlazeCommandRunConfigurationProxy + extends TestRecorderBlazeCommandRunConfigurationProxyBase { + + public TestRecorderBlazeCommandRunConfigurationProxy( + BlazeCommandRunConfiguration baseConfiguration) { + super(baseConfiguration); + } + + @Override + @Nullable + public List> getDeviceFutures(ExecutionEnvironment environment) { + return environment + .getCopyableUserData(BlazeAndroidRunConfigurationRunner.DEVICE_SESSION_KEY) + .deviceFutures + .get(); + } +} diff --git a/aswb/sdkcompat/as231/com/google/idea/blaze/android/sync/model/idea/BlazeAndroidModel.java b/aswb/sdkcompat/as231/com/google/idea/blaze/android/sync/model/idea/BlazeAndroidModel.java new file mode 100644 index 00000000000..3049cdd0eff --- /dev/null +++ b/aswb/sdkcompat/as231/com/google/idea/blaze/android/sync/model/idea/BlazeAndroidModel.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 com.android.tools.idea.model.AndroidModel; +import com.android.tools.idea.model.Namespacing; +import com.android.tools.idea.projectsystem.NamedIdeaSourceProvider; +import com.google.common.util.concurrent.ListenableFuture; +import com.intellij.openapi.project.Project; +import java.io.File; + +/** Blaze model for an android project. #api42. */ +public class BlazeAndroidModel extends BlazeAndroidModelBase { + private final NamedIdeaSourceProvider sourceProvider; + + public BlazeAndroidModel( + Project project, + File rootDirPath, + NamedIdeaSourceProvider sourceProvider, + ListenableFuture applicationId, + int minSdkVersion, + boolean desugarJava8Libs) { + super(project, rootDirPath, applicationId, minSdkVersion, desugarJava8Libs); + this.sourceProvider = sourceProvider; + } + + public NamedIdeaSourceProvider getDefaultSourceProvider() { + return sourceProvider; + } + + @Override + public Namespacing getNamespacing() { + return Namespacing.DISABLED; + } + + @Override + protected String uninitializedApplicationId() { + return AndroidModel.UNINITIALIZED_APPLICATION_ID; + } +} diff --git a/aswb/sdkcompat/as232/com/META-INF/aswb.xml b/aswb/sdkcompat/as232/com/META-INF/aswb.xml new file mode 100644 index 00000000000..fa5c9754f9e --- /dev/null +++ b/aswb/sdkcompat/as232/com/META-INF/aswb.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/aswb/sdkcompat/as232/com/android/tools/configurations/ConfigurationCompat.java b/aswb/sdkcompat/as232/com/android/tools/configurations/ConfigurationCompat.java new file mode 100644 index 00000000000..3d51e831ad7 --- /dev/null +++ b/aswb/sdkcompat/as232/com/android/tools/configurations/ConfigurationCompat.java @@ -0,0 +1,32 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR 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.configurations; + +import com.android.sdklib.devices.Device; + +/** Compat class for Configuration. */ +public class ConfigurationCompat { + + private Configuration configuration; + + public ConfigurationCompat(Configuration configuration) { + this.configuration = configuration; + } + + public void setDevice(Device device, boolean preserveState) { + configuration.setDevice(device, preserveState); + } +} diff --git a/aswb/sdkcompat/as213/com/android/tools/idea/model/AndroidManifestIndexCompat.java b/aswb/sdkcompat/as232/com/android/tools/idea/model/AndroidManifestIndexCompat.java similarity index 100% rename from aswb/sdkcompat/as213/com/android/tools/idea/model/AndroidManifestIndexCompat.java rename to aswb/sdkcompat/as232/com/android/tools/idea/model/AndroidManifestIndexCompat.java diff --git a/aswb/sdkcompat/as213/com/android/tools/idea/progress/StudioLoggerProgressIndicatorCompat.java b/aswb/sdkcompat/as232/com/android/tools/idea/progress/StudioLoggerProgressIndicatorCompat.java similarity index 100% rename from aswb/sdkcompat/as213/com/android/tools/idea/progress/StudioLoggerProgressIndicatorCompat.java rename to aswb/sdkcompat/as232/com/android/tools/idea/progress/StudioLoggerProgressIndicatorCompat.java diff --git a/aswb/sdkcompat/as232/com/android/tools/idea/rendering/RenderErrorContributorCompat.java b/aswb/sdkcompat/as232/com/android/tools/idea/rendering/RenderErrorContributorCompat.java new file mode 100644 index 00000000000..bd5059f8cc3 --- /dev/null +++ b/aswb/sdkcompat/as232/com/android/tools/idea/rendering/RenderErrorContributorCompat.java @@ -0,0 +1,29 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR 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.rendering; + +import com.android.tools.idea.ui.designer.EditorDesignSurface; +import com.android.tools.rendering.RenderResultCompat; +import com.intellij.openapi.actionSystem.DataContext; +import groovyjarjarantlr4.v4.runtime.misc.Nullable; + +/** Contribute blaze specific render errors. */ +public class RenderErrorContributorCompat extends RenderErrorContributorImpl { + public RenderErrorContributorCompat( + EditorDesignSurface surface, RenderResultCompat result, @Nullable DataContext dataContext) { + super(surface, result.get()); + } +} diff --git a/aswb/sdkcompat/as232/com/android/tools/idea/run/tasks/DeployTasksCompat.java b/aswb/sdkcompat/as232/com/android/tools/idea/run/tasks/DeployTasksCompat.java new file mode 100644 index 00000000000..ce440ca9d52 --- /dev/null +++ b/aswb/sdkcompat/as232/com/android/tools/idea/run/tasks/DeployTasksCompat.java @@ -0,0 +1,53 @@ +/* + * Copyright 2020 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR 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.run.tasks; + +import com.android.tools.deployer.DeployerException; +import com.android.tools.idea.execution.common.AndroidExecutionException; +import com.android.tools.idea.execution.common.DeployOptions; +import com.android.tools.idea.run.ApkInfo; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; +import com.google.idea.common.experiments.BoolExperiment; +import com.intellij.openapi.project.Project; +import java.util.Collection; +import java.util.List; + +/** Compat class for {@link DeployTask} */ +public class DeployTasksCompat { + private static final BoolExperiment updateCodeViaJvmti = + new BoolExperiment("android.apply.changes", false); + + private DeployTasksCompat() {} + + public static BlazeLaunchTask createDeployTask( + Project project, Collection packages, DeployOptions deployOptions) { + return launchContext -> { + try { + List unused = + new DeployTask( + project, + packages, + deployOptions.getPmInstallFlags(), + deployOptions.getInstallOnAllUsers(), + deployOptions.getAlwaysInstallWithPm()) + .run(launchContext.getDevice(), launchContext.getProgressIndicator()); + } catch (DeployerException e) { + throw new AndroidExecutionException(e.getId(), e.getMessage()); + } + }; + } +} + diff --git a/aswb/sdkcompat/as232/com/android/tools/rendering/HtmlLinkManagerCompat.java b/aswb/sdkcompat/as232/com/android/tools/rendering/HtmlLinkManagerCompat.java new file mode 100644 index 00000000000..28ca9471258 --- /dev/null +++ b/aswb/sdkcompat/as232/com/android/tools/rendering/HtmlLinkManagerCompat.java @@ -0,0 +1,28 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR 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.rendering; + +import java.io.File; +import org.jetbrains.annotations.NotNull; + +/** Compat class for {@link HtmlLinkManager} */ +public final class HtmlLinkManagerCompat { + public static String createFilePositionUrl(@NotNull File file, int line, int column) { + return HtmlLinkManager.createFilePositionUrl(file, line, column); + } + + private HtmlLinkManagerCompat() {} +} diff --git a/aswb/sdkcompat/as232/com/android/tools/rendering/RenderLoggerCompat.java b/aswb/sdkcompat/as232/com/android/tools/rendering/RenderLoggerCompat.java new file mode 100644 index 00000000000..96af24f48d3 --- /dev/null +++ b/aswb/sdkcompat/as232/com/android/tools/rendering/RenderLoggerCompat.java @@ -0,0 +1,48 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR 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.rendering; + +import java.util.Map; +import java.util.Set; + +/** Compat class for RenderLogger. */ +public class RenderLoggerCompat { + private final RenderLogger renderLogger; + + public RenderLoggerCompat(RenderResultCompat result) { + renderLogger = result.getLogger(); + } + + public RenderLogger get() { + return renderLogger; + } + + public boolean hasErrors() { + return renderLogger.hasErrors(); + } + + public Map getBrokenClasses() { + return renderLogger.getBrokenClasses(); + } + + public Set getMissingClasses() { + return renderLogger.getMissingClasses(); + } + + public static void resetFidelityErrorsFilters() { + RenderLogger.resetFidelityErrorsFilters(); + } +} diff --git a/aswb/sdkcompat/as232/com/android/tools/rendering/RenderResultCompat.java b/aswb/sdkcompat/as232/com/android/tools/rendering/RenderResultCompat.java new file mode 100644 index 00000000000..6cb43bf3def --- /dev/null +++ b/aswb/sdkcompat/as232/com/android/tools/rendering/RenderResultCompat.java @@ -0,0 +1,84 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR 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.rendering; + +import com.android.ide.common.rendering.api.Result; +import com.android.tools.idea.rendering.RenderErrorContributor; +import com.android.tools.idea.rendering.RenderErrorContributorImpl; +import com.android.tools.idea.rendering.RenderErrorModelFactory; +import com.android.tools.idea.rendering.RenderResults; +import com.android.tools.idea.rendering.errors.ui.RenderErrorModel; +import com.android.tools.idea.ui.designer.EditorDesignSurface; +import com.android.tools.rendering.imagepool.ImagePool.Image; +import com.google.idea.blaze.android.rendering.BlazeRenderErrorContributor; +import com.google.idea.blaze.base.settings.Blaze; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiFile; +import org.jetbrains.annotations.Nullable; + +/** Compat class for {@link RenderResult} */ +public final class RenderResultCompat { + private RenderResult result; + + public RenderResultCompat(RenderResult result) { + this.result = result; + } + + public static RenderResultCompat createBlank(PsiFile file) { + return new RenderResultCompat(RenderResults.createBlank(file)); + } + + public RenderErrorModel createErrorModel() { + return RenderErrorModelFactory.createErrorModel(null, result); + } + + public RenderResult get() { + return result; + } + + public RenderLogger getLogger() { + return result.getLogger(); + } + + public Module getModule() { + return result.getModule(); + } + + public Result getRenderResult() { + return result.getRenderResult(); + } + + public Image getRenderedImage() { + return result.getRenderedImage(); + } + + private RenderResultCompat() {} + + /** Extension to provide {@link BlazeRenderErrorContributor}. */ + public static class BlazeProvider extends RenderErrorContributorImpl.Provider { + @Override + public boolean isApplicable(Project project) { + return Blaze.isBlazeProject(project); + } + + @Override + public RenderErrorContributor getContributor( + @Nullable EditorDesignSurface surface, RenderResult result) { + return new BlazeRenderErrorContributor(surface, result); + } + } +} diff --git a/aswb/sdkcompat/as232/com/android/tools/rendering/RenderServiceCompat.java b/aswb/sdkcompat/as232/com/android/tools/rendering/RenderServiceCompat.java new file mode 100644 index 00000000000..de22b6df36d --- /dev/null +++ b/aswb/sdkcompat/as232/com/android/tools/rendering/RenderServiceCompat.java @@ -0,0 +1,27 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR 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.rendering; + +/** Compat class for {@link RenderService} */ +public class RenderServiceCompat { + public static void shutdownRenderExecutor(long l) { + RenderService.shutdownRenderExecutor(l); + } + + public static void initializeRenderExecutor() { + RenderService.initializeRenderExecutor(); + } +} diff --git a/aswb/sdkcompat/as212/com/google/idea/blaze/android/cppimpl/debug/BlazeAutoAndroidDebugger.java b/aswb/sdkcompat/as232/com/google/idea/blaze/android/cppimpl/debug/BlazeAutoAndroidDebugger.java similarity index 63% rename from aswb/sdkcompat/as212/com/google/idea/blaze/android/cppimpl/debug/BlazeAutoAndroidDebugger.java rename to aswb/sdkcompat/as232/com/google/idea/blaze/android/cppimpl/debug/BlazeAutoAndroidDebugger.java index 64931a9101e..08f03d50d8f 100644 --- a/aswb/sdkcompat/as212/com/google/idea/blaze/android/cppimpl/debug/BlazeAutoAndroidDebugger.java +++ b/aswb/sdkcompat/as232/com/google/idea/blaze/android/cppimpl/debug/BlazeAutoAndroidDebugger.java @@ -16,18 +16,26 @@ package com.google.idea.blaze.android.cppimpl.debug; import com.android.ddmlib.Client; -import com.intellij.execution.configurations.RunConfiguration; +import com.android.ddmlib.ClientData; import com.intellij.openapi.project.Project; +import com.intellij.xdebugger.XDebugSession; +import org.jetbrains.annotations.NotNull; /** Shim for #api212 compat. */ public class BlazeAutoAndroidDebugger extends BlazeAutoAndroidDebuggerBase { + @Override - public void attachToClient(Project project, Client client, RunConfiguration config) { + public XDebugSession getExistingDebugSession(@NotNull Project project, @NotNull Client client) { if (isNativeProject(project)) { - log.info("Project has native development enabled. Attaching native debugger."); - nativeDebugger.attachToClient(project, client, config); + log.info("Project has native development enabled"); + return nativeDebugger.getExistingDebugSession(project, client); } else { - super.attachToClient(project, client, config); + return super.getExistingDebugSession(project, client); } } + + @Override + protected boolean isNativeDeployment(Project project, ClientData clientData) { + return isNativeProject(project); + } } diff --git a/aswb/sdkcompat/as232/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeAndroidDebugger.java b/aswb/sdkcompat/as232/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeAndroidDebugger.java new file mode 100644 index 00000000000..e92afddc27c --- /dev/null +++ b/aswb/sdkcompat/as232/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeAndroidDebugger.java @@ -0,0 +1,28 @@ +/* + * Copyright 2018 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.cppimpl.debug; + +/** + * API compat of {@link BlazeNativeAndroidDebuggerBase} with the following additions: + * + *

    + *
  • Creates a run-config setting using {@link BlazeAndroidNativeAttachConfiguration} instead of + * {@link AndroidNativeAttachConfiguration} to override counterproductive validations. + *
+ * + * #api4.0 + */ +public class BlazeNativeAndroidDebugger extends BlazeNativeAndroidDebuggerBase {} diff --git a/aswb/sdkcompat/as232/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeAndroidDebuggerBase.java b/aswb/sdkcompat/as232/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeAndroidDebuggerBase.java new file mode 100644 index 00000000000..e9ba5c444c5 --- /dev/null +++ b/aswb/sdkcompat/as232/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeAndroidDebuggerBase.java @@ -0,0 +1,78 @@ +/* + * Copyright 2020 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.cppimpl.debug; + +import com.android.ddmlib.Client; +import com.android.tools.idea.projectsystem.ApplicationProjectContext; +import com.android.tools.ndk.run.editor.NativeAndroidDebugger; +import com.android.tools.ndk.run.editor.NativeAndroidDebuggerState; +import com.google.idea.blaze.base.model.BlazeProjectData; +import com.google.idea.blaze.base.model.primitives.LanguageClass; +import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; +import com.intellij.openapi.project.Project; +import com.intellij.xdebugger.XDebugProcessStarter; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Extension of {@link NativeAndroidDebugger} with the following key differences compared to {@link + * NativeAndroidDebugger}. + * + *
    + *
  • Overrides {@link #supportsProject} so native debugger is only enabled for native support is + * enabled. + *
+ */ +public class BlazeNativeAndroidDebuggerBase extends NativeAndroidDebugger { + /** + * This ID needs to be lexicographically larger than "Java" so it come after the "Java" debugger + * when sorted lexicographically in the "Attach Debugger to Android Process" dialog. See {@link + * org.jetbrains.android.actions.AndroidProcessChooserDialog#populateDebuggerTypeCombo}. + */ + public static final String ID = "Native" + Blaze.defaultBuildSystemName(); + + @Override + public String getId() { + return ID; + } + + @Override + public String getDisplayName() { + return "Native Only"; + } + + @Override + public boolean supportsProject(Project project) { + BlazeProjectData blazeProjectData = + BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); + return blazeProjectData != null + && blazeProjectData.getWorkspaceLanguageSettings().isLanguageActive(LanguageClass.C); + } + + @Override + public XDebugProcessStarter getDebugProcessStarterForExistingProcess( + @NotNull Project project, + @NotNull Client client, + ApplicationProjectContext applicationContext, + @Nullable NativeAndroidDebuggerState state) { + if (state != null) { + BlazeNativeDebuggerStateSourceMapping.addSourceMapping(project, state); + } + return super.getDebugProcessStarterForExistingProcess( + project, client, applicationContext, state); + } +} diff --git a/aswb/sdkcompat/as232/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeDebuggerStateSourceMapping.java b/aswb/sdkcompat/as232/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeDebuggerStateSourceMapping.java new file mode 100644 index 00000000000..640e5f82075 --- /dev/null +++ b/aswb/sdkcompat/as232/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeDebuggerStateSourceMapping.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.cppimpl.debug; + +import com.android.tools.ndk.run.editor.NativeAndroidDebuggerState; +import com.google.common.collect.ImmutableList; +import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; +import com.intellij.openapi.project.Project; +import org.jetbrains.annotations.NotNull; + +/** Maps source directory when attaching to a process */ +public class BlazeNativeDebuggerStateSourceMapping { + public static void addSourceMapping( + @NotNull Project project, @NotNull NativeAndroidDebuggerState state) { + // Source code is always relative to the workspace root in a blaze project. + String workingDirPath = WorkspaceRoot.fromProject(project).directory().getPath(); + state.setWorkingDir(workingDirPath); + + // Remote built binaries may use /proc/self/cwd to represent the working directory + // so we manually map /proc/self/cwd to the workspace root. We used to use + // `plugin.symbol-file.dwarf.comp-dir-symlink-paths = "/proc/self/cwd"` + // to automatically resolve this but it's no longer supported in newer versions of + // LLDB. + String sourceMapToWorkspaceRootCommand = + "settings append target.source-map /proc/self/cwd " + workingDirPath; + ImmutableList startupCommands = + ImmutableList.builder() + .addAll(state.getUserStartupCommands()) + .add(sourceMapToWorkspaceRootCommand) + .build(); + state.setUserStartupCommands(startupCommands); + } +} diff --git a/aswb/sdkcompat/as232/com/google/idea/blaze/android/projectsystem/BlazeAddDestinationMenuToken.java b/aswb/sdkcompat/as232/com/google/idea/blaze/android/projectsystem/BlazeAddDestinationMenuToken.java new file mode 100644 index 00000000000..e74aa02a7d2 --- /dev/null +++ b/aswb/sdkcompat/as232/com/google/idea/blaze/android/projectsystem/BlazeAddDestinationMenuToken.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.projectsystem; + +import static com.android.tools.idea.util.DependencyManagementUtil.addDependenciesWithUiConfirmation; +import static com.android.tools.idea.util.DependencyManagementUtil.dependsOn; + +import com.android.ide.common.repository.GoogleMavenArtifactId; +import com.android.tools.idea.common.model.NlModel; +import com.android.tools.idea.naveditor.editor.AddDestinationMenuToken; +import com.intellij.openapi.module.Module; +import java.util.List; +import org.jetbrains.annotations.NotNull; + +/** Blaze implementation of project system tokens for the navigation editor: Add Destination Menu */ +public class BlazeAddDestinationMenuToken + implements AddDestinationMenuToken, BlazeToken { + @Override + public void modifyProject( + @NotNull BlazeProjectSystem projectSystem, @NotNull AddDestinationMenuToken.Data data) { + NlModel model = data.getSurface().getModel(); + if (model == null) { + return; + } + Module module = model.getModule(); + if (dependsOn(module, GoogleMavenArtifactId.ANDROIDX_NAVIGATION_DYNAMIC_FEATURES_FRAGMENT)) { + return; + } + final var unused = + addDependenciesWithUiConfirmation( + module, + List.of( + GoogleMavenArtifactId.ANDROIDX_NAVIGATION_DYNAMIC_FEATURES_FRAGMENT.getCoordinate( + "+")), + true, + false); + } +} diff --git a/aswb/sdkcompat/as232/com/google/idea/blaze/android/projectsystem/BlazeModuleDependencies.java b/aswb/sdkcompat/as232/com/google/idea/blaze/android/projectsystem/BlazeModuleDependencies.java new file mode 100644 index 00000000000..72a158468e6 --- /dev/null +++ b/aswb/sdkcompat/as232/com/google/idea/blaze/android/projectsystem/BlazeModuleDependencies.java @@ -0,0 +1,81 @@ +/* + * Copyright 2024 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.projectsystem; + +import static com.google.common.collect.ImmutableList.toImmutableList; + +import com.android.ide.common.repository.GoogleMavenArtifactId; +import com.android.projectmodel.ExternalAndroidLibrary; +import com.android.tools.idea.projectsystem.DependencyScopeType; +import com.android.tools.module.ModuleDependencies; +import com.google.common.collect.ImmutableList; +import com.google.idea.blaze.android.resources.BlazeLightResourceClassService; +import com.intellij.openapi.module.Module; +import com.intellij.psi.JavaPsiFacade; +import com.intellij.psi.PsiClass; +import java.util.List; +import java.util.Objects; +import java.util.function.Predicate; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** ASwB specific implementation of [ModuleDependencies]. */ +public class BlazeModuleDependencies implements ModuleDependencies { + + Module module; + + BlazeModuleDependencies(Module module) { + this.module = module; + } + + @Override + public boolean dependsOn(@NotNull GoogleMavenArtifactId googleMavenArtifactId) { + return false; + } + + @Nullable + @Override + public PsiClass findPsiClassInModuleAndDependencies(@NotNull String fqcn) { + JavaPsiFacade facade = JavaPsiFacade.getInstance(module.getProject()); + return facade.findClass(fqcn, module.getModuleWithDependenciesAndLibrariesScope(false)); + } + + /** + * Fetches the resource packages within the project and from external dependencies. Input + * parameter includeExternalLibraries is ignored as ASwB/Blaze uses a single workspace module to + * map both types of resources. + */ + @NotNull + @Override + public List getResourcePackageNames(boolean includeExternalLibraries) { + // TODO(b/304821496): Add an integration test to test it similar to BuildDependenciesTest + // TODO(b/307604153): Update AndroidExternalLibraryManager to read package name from multiple + // locations + return ImmutableList.builder() + .addAll( + BlazeModuleSystem.getInstance(module) + .getAndroidLibraryDependencies(DependencyScopeType.MAIN) + .stream() + .map(ExternalAndroidLibrary::getPackageName) + .filter(Objects::nonNull) + .filter(Predicate.not(String::isEmpty)) + .collect(toImmutableList())) + .addAll( + BlazeLightResourceClassService.getInstance(module.getProject()) + .getWorkspaceResourcePackages()) + .build(); + } +} diff --git a/aswb/sdkcompat/as232/com/google/idea/blaze/android/projectsystem/BlazeModuleSystem.java b/aswb/sdkcompat/as232/com/google/idea/blaze/android/projectsystem/BlazeModuleSystem.java new file mode 100644 index 00000000000..2c64e2c2b32 --- /dev/null +++ b/aswb/sdkcompat/as232/com/google/idea/blaze/android/projectsystem/BlazeModuleSystem.java @@ -0,0 +1,78 @@ +/* + * Copyright 2020 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.projectsystem; + +import static com.google.common.collect.ImmutableList.toImmutableList; + +import com.android.tools.idea.projectsystem.AndroidModuleSystem; +import com.android.tools.module.ModuleDependencies; +import com.google.common.collect.ImmutableList; +import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.targetmaps.SourceToTargetMap; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.vfs.VirtualFile; +import java.io.File; +import java.nio.file.Path; + +/** Blaze implementation of {@link AndroidModuleSystem}. */ +public class BlazeModuleSystem extends BlazeModuleSystemBase { + + BlazeModuleSystem(Module module) { + super(module); + } + + @Override + public ModuleDependencies getModuleDependencies() { + return new BlazeModuleDependencies(getModule()); + } + + public String blazeTargetNameToKotlinModuleName(String blazeTargetName) { + // Before: //third_party/java_src/android_app/compose_samples/Rally:lib + // After: third_party_java_src_android_app_compose_samples_Rally_lib + assert blazeTargetName.substring(0, 2).equals("//"); + return blazeTargetName.substring(2).replaceAll("['/',':']", "_"); + } + + @Override + public String getModuleNameForCompilation(VirtualFile virtualFile) { + String moduleName = + blazeTargetNameToKotlinModuleName( + SourceToTargetMap.getInstance(project) + .getTargetsToBuildForSourceFile(new File(virtualFile.getPath())) + .get(0) // use the first target + .toString()); + return moduleName; + } + + /** Check every supporting extension point if they contain desugaring library config files */ + @Override + public boolean getDesugarLibraryConfigFilesKnown() { + return DesugaringLibraryConfigFilesLocator.forBuildSystem( + Blaze.getBuildSystemName(module.getProject())) + .stream() + .anyMatch(provider -> provider.getDesugarLibraryConfigFilesKnown()); + } + + /** Collect desugarig library config files from every supporting extension and return the list */ + @Override + public ImmutableList getDesugarLibraryConfigFiles() { + return DesugaringLibraryConfigFilesLocator.forBuildSystem( + Blaze.getBuildSystemName(module.getProject())) + .stream() + .flatMap(provider -> provider.getDesugarLibraryConfigFiles(project).stream()) + .collect(toImmutableList()); + } +} diff --git a/aswb/sdkcompat/as232/com/google/idea/blaze/android/projectsystem/BlazeNavDesignSurfaceToken.java b/aswb/sdkcompat/as232/com/google/idea/blaze/android/projectsystem/BlazeNavDesignSurfaceToken.java new file mode 100644 index 00000000000..80e8b353b01 --- /dev/null +++ b/aswb/sdkcompat/as232/com/google/idea/blaze/android/projectsystem/BlazeNavDesignSurfaceToken.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.projectsystem; + +import static com.android.tools.idea.util.DependencyManagementUtil.addDependenciesWithUiConfirmation; +import static com.google.common.collect.ImmutableList.toImmutableList; + +import com.android.ide.common.repository.GradleCoordinate; +import com.android.tools.idea.common.model.NlModel; +import com.android.tools.idea.naveditor.surface.NavDesignSurface; +import com.android.tools.idea.naveditor.surface.NavDesignSurfaceToken; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.module.Module; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import org.jetbrains.annotations.NotNull; + +/** Blaze implementation of project system tokens for the navigation editor: Nav Design Surface */ +class BlazeNavDesignSurfaceToken implements NavDesignSurfaceToken, BlazeToken { + @Override + public boolean modifyProject(@NotNull BlazeProjectSystem projectSystem, @NotNull NlModel model) { + AtomicBoolean didAdd = new AtomicBoolean(false); + Module module = model.getModule(); + List coordinates = + NavDesignSurface.getDependencies(module).stream() + .map((a) -> a.getCoordinate("+")) + .collect(toImmutableList()); + Runnable runnable = + () -> { + try { + didAdd.set( + addDependenciesWithUiConfirmation(module, coordinates, true, false).isEmpty()); + } catch (Throwable t) { + Logger.getInstance(NavDesignSurface.class).warn("Failed to add dependencies", t); + didAdd.set(false); + } + }; + ApplicationManager.getApplication().invokeAndWait(runnable); + return didAdd.get(); + } +} diff --git a/aswb/sdkcompat/as232/com/google/idea/blaze/android/projectsystem/BlazeProjectSystem.java b/aswb/sdkcompat/as232/com/google/idea/blaze/android/projectsystem/BlazeProjectSystem.java new file mode 100644 index 00000000000..eec76246653 --- /dev/null +++ b/aswb/sdkcompat/as232/com/google/idea/blaze/android/projectsystem/BlazeProjectSystem.java @@ -0,0 +1,291 @@ +/* + * 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.projectsystem; + +import static com.android.tools.idea.projectsystem.SourceProvidersKt.emptySourceProvider; +import static com.google.idea.blaze.base.sync.data.BlazeDataStorage.WORKSPACE_MODULE_NAME; +import static org.jetbrains.android.facet.SourceProviderUtil.createSourceProvidersForLegacyModule; + +import com.android.tools.apk.analyzer.AaptInvoker; +import com.android.tools.idea.log.LogWrapper; +import com.android.tools.idea.model.AndroidModel; +import com.android.tools.idea.model.ClassJarProvider; +import com.android.tools.idea.projectsystem.AndroidProjectSystem; +import com.android.tools.idea.projectsystem.NamedIdeaSourceProvider; +import com.android.tools.idea.projectsystem.ProjectSystemBuildManager; +import com.android.tools.idea.projectsystem.ProjectSystemSyncManager; +import com.android.tools.idea.projectsystem.ScopeType; +import com.android.tools.idea.projectsystem.SourceProviderManager; +import com.android.tools.idea.projectsystem.SourceProviders; +import com.android.tools.idea.projectsystem.SourceProvidersFactory; +import com.android.tools.idea.projectsystem.SourceProvidersImpl; +import com.android.tools.idea.res.AndroidInnerClassFinder; +import com.android.tools.idea.res.AndroidResourceClassPsiElementFinder; +import com.android.tools.idea.sdk.AndroidSdks; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.idea.blaze.android.resources.BlazeLightResourceClassService; +import com.google.idea.blaze.android.sync.model.idea.BlazeAndroidModel; +import com.google.idea.blaze.android.sync.model.idea.BlazeClassJarProvider; +import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.settings.BlazeImportSettings.ProjectType; +import com.intellij.facet.ProjectFacetManager; +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 com.intellij.psi.PsiElementFinder; +import com.intellij.psi.search.GlobalSearchScope; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import org.jetbrains.android.facet.AndroidFacet; +import org.jetbrains.android.sdk.AndroidPlatforms; +import org.jetbrains.annotations.NotNull; + +/** + * Base class to implement common methods in {@link AndroidProjectSystem} for blaze with different + * sdk + */ +public class BlazeProjectSystem implements AndroidProjectSystem { + protected final Project project; + protected final ProjectSystemSyncManager syncManager; + protected final List myFinders; + private final BlazeProjectSystemBuildManager buildManager; + + public BlazeProjectSystem(Project project) { + this.project = project; + syncManager = new BlazeProjectSystemSyncManager(project); + buildManager = new BlazeProjectSystemBuildManager(project); + + myFinders = + Arrays.asList( + AndroidInnerClassFinder.INSTANCE, + new AndroidResourceClassPsiElementFinder(getLightResourceClassService())); + } + + @Override + public Project getProject() { + return project; + } + + @Override + public boolean allowsFileCreation() { + return true; + } + + @Nullable + @Override + public VirtualFile getDefaultApkFile() { + return null; + } + + @Override + public Path getPathToAapt() { + return AaptInvoker.getPathToAapt( + AndroidSdks.getInstance().tryToChooseSdkHandler(), + new LogWrapper(BlazeProjectSystem.class)); + } + + @Override + public ProjectSystemBuildManager getBuildManager() { + return buildManager; + } + + @Override + public BlazeModuleSystem getModuleSystem(Module module) { + return BlazeModuleSystem.getInstance(module); + } + + @Override + public ProjectSystemSyncManager getSyncManager() { + return syncManager; + } + + @Override + public Collection getPsiElementFinders() { + return myFinders; + } + + @Override + public BlazeLightResourceClassService getLightResourceClassService() { + return BlazeLightResourceClassService.getInstance(project); + } + + @Override + public SourceProvidersFactory getSourceProvidersFactory() { + return new SourceProvidersFactory() { + @Override + public SourceProviders createSourceProvidersFor(AndroidFacet facet) { + BlazeAndroidModel model = ((BlazeAndroidModel) AndroidModel.get(facet)); + if (model != null) { + return createForModel(model); + } else { + return createSourceProvidersForLegacyModule(facet); + } + } + + private SourceProviders createForModel(BlazeAndroidModel model) { + NamedIdeaSourceProvider mainSourceProvider = model.getDefaultSourceProvider(); + if (Blaze.getProjectType(project).equals(ProjectType.QUERY_SYNC)) { + return new SourceProvidersImpl( + mainSourceProvider, + ImmutableList.of(mainSourceProvider), + ImmutableList.of(), + ImmutableList.of(), + ImmutableList.of(), + ImmutableList.of(mainSourceProvider), + ImmutableList.of(mainSourceProvider), + emptySourceProvider(ScopeType.MAIN), + emptySourceProvider(ScopeType.UNIT_TEST), + emptySourceProvider(ScopeType.ANDROID_TEST), + emptySourceProvider(ScopeType.TEST_FIXTURES)); + } else { + return new SourceProvidersImpl( + mainSourceProvider, + ImmutableList.of(mainSourceProvider), + ImmutableList.of(mainSourceProvider), + ImmutableList.of(mainSourceProvider), + ImmutableList.of(mainSourceProvider), + ImmutableList.of(mainSourceProvider), + ImmutableList.of(mainSourceProvider), + emptySourceProvider(ScopeType.MAIN), + emptySourceProvider(ScopeType.UNIT_TEST), + emptySourceProvider(ScopeType.ANDROID_TEST), + emptySourceProvider(ScopeType.TEST_FIXTURES)); + } + } + }; + } + + @Override + public ClassJarProvider getClassJarProvider() { + return new BlazeClassJarProvider(project); + } + + @Override + public Collection getAndroidFacetsWithPackageName( + Project project, String packageName) { + return getAndroidFacetsWithPackageName( + project, packageName, GlobalSearchScope.projectScope(project)); + } + + private Collection getAndroidFacetsWithPackageName( + Project project, String packageName, GlobalSearchScope scope) { + List facets = ProjectFacetManager.getInstance(project).getFacets(AndroidFacet.ID); + return facets.stream() + .filter(facet -> hasPackageName(facet, packageName)) + .filter( + facet -> { + VirtualFile file = SourceProviderManager.getInstance(facet).getMainManifestFile(); + if (file == null) { + return false; + } else { + return scope.contains(file); + } + }) + .collect(Collectors.toList()); + } + + @Override + public boolean isNamespaceOrParentPackage(@NotNull String packageName) { + List facets = ProjectFacetManager.getInstance(project).getFacets(AndroidFacet.ID); + GlobalSearchScope scope = GlobalSearchScope.projectScope(project); + for (AndroidFacet facet : facets) { + String moduleNamespace = PackageNameUtils.getPackageName(facet.getModule()); + if (moduleNamespace == null) { + continue; + } + // Check if the moduleNamespace is exactly the package name, or is a subpackage + if (!moduleNamespace.startsWith(packageName)) { + continue; + } + // packageName=com.example should not match moduleNamespace=com.example2 + if (moduleNamespace.length() > packageName.length() + && moduleNamespace.charAt(packageName.length()) != '.') { + continue; + } + VirtualFile file = SourceProviderManager.getInstance(facet).getMainManifestFile(); + if (file == null || !scope.contains(file)) { + continue; + } + return true; + } + return false; + } + + @NotNull + @Override + public Set getKnownApplicationIds() { + List facets = ProjectFacetManager.getInstance(project).getFacets(AndroidFacet.ID); + Set applicationIds = new HashSet<>(facets.size()); + for (AndroidFacet facet : facets) { + AndroidModel model = AndroidModel.get(facet); + if (model == null) { + continue; + } + applicationIds.addAll(model.getAllApplicationIds()); + } + return Collections.unmodifiableSet(applicationIds); + } + + @NotNull + @Override + public Collection findModulesWithApplicationId(@NotNull String applicationId) { + if (Blaze.getProjectType(project).equals(ProjectType.QUERY_SYNC)) { + Module workspaceModule = + ModuleManager.getInstance(project).findModuleByName(WORKSPACE_MODULE_NAME); + if (workspaceModule != null) { + return ImmutableList.of(workspaceModule); + } else { + return ImmutableList.of(); + } + } + List facets = ProjectFacetManager.getInstance(project).getFacets(AndroidFacet.ID); + ImmutableSet.Builder resultBuilder = ImmutableSet.builder(); + for (AndroidFacet facet : facets) { + AndroidModel model = AndroidModel.get(facet); + if (model != null && model.getApplicationId().equals(applicationId)) { + resultBuilder.add(facet.getModule()); + } + } + return resultBuilder.build(); + } + + @Override + public Collection getSubmodules() { + return ImmutableList.of(); + } + + @Override + public Collection getBootClasspath(@NotNull Module module) { + return AndroidPlatforms.getInstance(module).getTarget().getBootClasspath(); + } + + private static boolean hasPackageName(AndroidFacet facet, String packageName) { + String nameFromFacet = PackageNameUtils.getPackageName(facet.getModule()); + if (nameFromFacet == null) { + return false; + } + return nameFromFacet.equals(packageName); + } +} diff --git a/aswb/sdkcompat/as232/com/google/idea/blaze/android/projectsystem/BlazeToken.java b/aswb/sdkcompat/as232/com/google/idea/blaze/android/projectsystem/BlazeToken.java new file mode 100644 index 00000000000..d9a4b21a732 --- /dev/null +++ b/aswb/sdkcompat/as232/com/google/idea/blaze/android/projectsystem/BlazeToken.java @@ -0,0 +1,27 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.projectsystem; + +import com.android.tools.idea.projectsystem.AndroidProjectSystem; +import com.android.tools.idea.projectsystem.Token; + +/** A mix-in implementation of the {@link Token} interface for the Blaze project system. */ +public interface BlazeToken extends Token { + @Override + default boolean isApplicable(AndroidProjectSystem projectSystem) { + return projectSystem instanceof BlazeProjectSystem; + } +} diff --git a/aswb/sdkcompat/as232/com/google/idea/blaze/android/projectsystem/RenderJarClassFileFinder.java b/aswb/sdkcompat/as232/com/google/idea/blaze/android/projectsystem/RenderJarClassFileFinder.java new file mode 100644 index 00000000000..0967d9fc40c --- /dev/null +++ b/aswb/sdkcompat/as232/com/google/idea/blaze/android/projectsystem/RenderJarClassFileFinder.java @@ -0,0 +1,300 @@ +/* + * Copyright 2020 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.projectsystem; + +import static java.util.stream.Collectors.joining; + +import com.android.tools.idea.projectsystem.ClassContent; +import com.android.tools.idea.projectsystem.ClassFileFinder; +import com.android.tools.idea.projectsystem.ClassFileFinderUtil; +import com.android.tools.idea.rendering.classloading.loaders.JarManager; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableSet; +import com.google.idea.blaze.android.libraries.RenderJarCache; +import com.google.idea.blaze.android.sync.model.AndroidResourceModule; +import com.google.idea.blaze.android.sync.model.AndroidResourceModuleRegistry; +import com.google.idea.blaze.android.targetmaps.TargetToBinaryMap; +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.qsync.QuerySync; +import com.google.idea.blaze.base.qsync.QuerySyncManager; +import com.google.idea.blaze.base.qsync.RenderJarArtifactTracker; +import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.settings.BlazeImportSettings.ProjectType; +import com.google.idea.blaze.base.sync.BlazeSyncModificationTracker; +import com.google.idea.blaze.base.sync.data.BlazeDataStorage; +import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; +import com.google.idea.common.experiments.BoolExperiment; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.StringUtil; +import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.regex.Pattern; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * A {@link ClassFileFinder} that uses deploy JAR like artifacts (called render jar henceforth) for + * class files. + * + *

The render JAR contains all runtime dependencies of a binary target. + * + *

The Blaze targets that go into creating a resource module is known. Consequently, it is + * possible to determine which binaries in the projectview depend on the resource declaring blaze + * targets that constitutes the module. This class calculates the binary targets and attempts to + * find classes from the render JARs. + * + *

This only works for resource modules (i.e. not the .workspace module). For .workspace module, + * we try to find the class in all binary targets in projectview + * + *

NOTE: Blaze targets that constitutes the resource module will be called "resource target(s)" + * in comments below. + */ +public class RenderJarClassFileFinder implements ClassFileFinder { + /** Experiment to control whether class file finding from render jars should be enabled. */ + private static final BoolExperiment enabled = + new BoolExperiment("aswb.renderjar.cff.enabled.3", true); + + /** + * Experiment to toggle whether resource resolution is allowed from Render JARs. Render JARs + * should not resolve resources by default. + */ + @VisibleForTesting + static final BoolExperiment resolveResourceClasses = + new BoolExperiment("aswb.resolve.resources.render.jar", false); + + private static final Logger log = Logger.getInstance(RenderJarClassFileFinder.class); + + private static final String INTERNAL_PACKAGE = "_layoutlib_._internal_."; + + // matches foo.bar.R or foo.bar.R$baz + private static final Pattern RESOURCE_CLASS_NAME = Pattern.compile(".+\\.R(\\$[^.]+)?$"); + + private final Module module; + private final Project project; + + // tracks the binary targets that depend resource targets + // will be recalculated after every sync + private ImmutableSet binaryTargets = ImmutableSet.of(); + + // tracks the value of {@link BlazeSyncModificationTracker} when binaryTargets is calculated + // binaryTargets is calculated when the value of {@link BlazeSyncModificationTracker} does not + // equal lastSyncCount + long lastSyncCount = -1; + + // true if the current module is the .workspace Module + private final boolean isWorkspaceModule; + + private final JarManager jarManager; + + public RenderJarClassFileFinder(Module module) { + this.module = module; + this.project = module.getProject(); + this.isWorkspaceModule = BlazeDataStorage.WORKSPACE_MODULE_NAME.equals(module.getName()); + if (ApplicationManager.getApplication().isUnitTestMode()) { + this.jarManager = null; // TODO(b/311649275): Mock in tests when made an interface. + } else { + this.jarManager = JarManager.getInstance(project); + } + } + + @Nullable + @Override + public ClassContent findClassFile(@NotNull String fqcn) { + if (!isEnabled()) { + return null; + } + + // Ever since Compose support was introduced in AS, finding class files is invoked during the + // normal course of opening an editor. The contract for this method requires that it shouldn't + // throw any exceptions, but we've had a few bugs where this method threw an exception, which + // resulted in users not being able to open Kotlin files at all. In order to avoid this + // scenario, we wrap the underlying call and ensure that no exceptions are thrown. + try { + return findClassContent(fqcn); + } catch (Error e) { + log.warn( + String.format( + "Unexpected error while finding the class file for `%1$s`: %2$s", + fqcn, Throwables.getRootCause(e).getMessage())); + return null; + } + } + + @Nullable + public ClassContent findClassContent(String fqcn) { + // Render JAR should not resolve any resources. All resources should be available to the IDE + // through ResourceRepository. Attempting to resolve resources from Render JAR indicates that + // ASwB hasn't properly set up resources for the project. + if (isResourceClass(fqcn) && !resolveResourceClasses.getValue()) { + log.warn(String.format("Attempting to load resource '%s' from RenderJAR.", fqcn)); + return null; + } + + if (Blaze.getProjectType(project).equals(ProjectType.QUERY_SYNC)) { + return findClassQuerySync(fqcn); + } + return findClassLegacySync(fqcn); + } + + private ClassContent findClassQuerySync(String fqcn) { + if (QuerySync.isComposeEnabled(project)) { + RenderJarArtifactTracker renderJarArtifactTracker = + QuerySyncManager.getInstance(project).getRenderJarArtifactTracker(); + // TODO(b/283280194): Setup fqcn -> target and target -> Render jar mappings to avoid + // iterating over all render jars when trying to locate class for fqcn. + // TODO(b/284002836): Collect metrics on time taken to iterate over the jars + for (File renderJar : renderJarArtifactTracker.getRenderJars()) { + return findClassInJar(renderJar, fqcn); + } + return null; + } + return null; + } + + private ClassContent findClassLegacySync(String fqcn) { + BlazeProjectData projectData = + BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); + if (projectData == null) { + log.warn("Could not find BlazeProjectData for project " + project.getName()); + return null; + } + + ImmutableSet binaryTargets = getBinaryTargets(); + if (binaryTargets.isEmpty()) { + log.warn( + String.format( + "No binaries for module %s. Adding a binary target to the projectview and resyncing" + + " might fix the issue.", + module.getName())); + return null; + } + + // Remove internal package prefix if present + fqcn = StringUtil.trimStart(fqcn, INTERNAL_PACKAGE); + + // Look through render resolve JARs of the binaries that depend on the given + // androidResourceModule. One androidResourceModule can comprise of multiple resource targets. + // The binaries can depend on any subset of these resource targets. Generally, we only + // expect one, or a small number of binaries here. + for (TargetKey binaryTarget : binaryTargets) { + ClassContent classContent = getClassFromRenderResolveJar(projectData, fqcn, binaryTarget); + if (classContent != null) { + return classContent; + } + } + log.warn(String.format("Could not find class `%1$s` (module: `%2$s`)", fqcn, module.getName())); + return null; + } + + @VisibleForTesting + static boolean isResourceClass(String fqcn) { + return RESOURCE_CLASS_NAME.matcher(fqcn).matches(); + } + + /** + * Returns the cached list of binary targets that depend on resource targets. The cache is + * recalculated if the project has been synced since last calculation + */ + private ImmutableSet getBinaryTargets() { + long currentSyncCount = + BlazeSyncModificationTracker.getInstance(project).getModificationCount(); + if (currentSyncCount == lastSyncCount) { + // Return the cached set if there hasn't been a sync since last calculation + return binaryTargets; + } + lastSyncCount = currentSyncCount; + + AndroidResourceModule androidResourceModule = + AndroidResourceModuleRegistry.getInstance(project).get(module); + if (androidResourceModule != null) { + binaryTargets = + TargetToBinaryMap.getInstance(project) + .getBinariesDependingOn(androidResourceModule.sourceTargetKeys); + } else if (isWorkspaceModule) { + binaryTargets = TargetToBinaryMap.getInstance(project).getSourceBinaryTargets(); + } else { + binaryTargets = ImmutableSet.of(); + log.warn("Could not find AndroidResourceModule for " + module.getName()); + } + log.info( + String.format( + "Binary targets for module `%1$s`: %2$s", + module.getName(), + binaryTargets.stream() + .limit(5) + .map(t -> t.getLabel().toString()) + .collect(joining(", ")))); + return binaryTargets; + } + + /** + * Returns class file for fqcn if found in the render JAR corresponding to {@code binaryTarget}. + * Returns null if something goes wrong or if render JAR does not contain fqcn + */ + @Nullable + private ClassContent getClassFromRenderResolveJar( + BlazeProjectData projectData, String fqcn, TargetKey binaryTarget) { + TargetIdeInfo ideInfo = projectData.getTargetMap().get(binaryTarget); + if (ideInfo == null) { + return null; + } + + File renderResolveJarFile = + RenderJarCache.getInstance(project) + .getCachedJarForBinaryTarget(projectData.getArtifactLocationDecoder(), ideInfo); + + if (renderResolveJarFile == null) { + return null; + } + + return findClassInJar(renderResolveJarFile, fqcn); + } + + private ClassContent findClassInJar(File renderResolveJarFile, String fqcn) { + String relativePath = ClassFileFinderUtil.getPathFromFqcn(fqcn); + final byte[] bytes; + if (ApplicationManager.getApplication().isUnitTestMode()) { + try { + Path targetPath = renderResolveJarFile.toPath().resolve("!" + relativePath); + bytes = Files.isRegularFile(targetPath) ? Files.readAllBytes(targetPath) : new byte[0]; + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } else { + bytes = + jarManager.loadFileFromJar( + renderResolveJarFile.toPath(), ClassFileFinderUtil.getPathFromFqcn(fqcn)); + } + if (bytes == null) { + return null; + } + + return ClassContent.fromJarEntryContent(renderResolveJarFile, bytes); + } + + public static boolean isEnabled() { + return enabled.getValue(); + } +} diff --git a/aswb/sdkcompat/as232/com/google/idea/blaze/android/rendering/BlazeRenderErrorContributor.java b/aswb/sdkcompat/as232/com/google/idea/blaze/android/rendering/BlazeRenderErrorContributor.java new file mode 100644 index 00000000000..ce8b52ff595 --- /dev/null +++ b/aswb/sdkcompat/as232/com/google/idea/blaze/android/rendering/BlazeRenderErrorContributor.java @@ -0,0 +1,442 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.rendering; + +import static com.android.SdkConstants.ANDROID_MANIFEST_XML; +import static com.google.common.collect.ImmutableMap.toImmutableMap; + +import com.android.tools.idea.rendering.RenderErrorContributor; +import com.android.tools.idea.rendering.RenderUtils; +import com.android.tools.idea.rendering.errors.ui.RenderErrorModel; +import com.android.tools.idea.ui.designer.EditorDesignSurface; +import com.android.tools.rendering.HtmlLinkManager; +import com.android.tools.rendering.HtmlLinkManagerCompat; +import com.android.tools.rendering.RenderLogger; +import com.android.tools.rendering.RenderResult; +import com.android.utils.HtmlBuilder; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.Maps; +import com.google.common.collect.SortedSetMultimap; +import com.google.common.collect.TreeMultimap; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.idea.blaze.android.sync.model.AndroidResourceModule; +import com.google.idea.blaze.android.sync.model.AndroidResourceModuleRegistry; +import com.google.idea.blaze.base.command.buildresult.OutputArtifactResolver; +import com.google.idea.blaze.base.ideinfo.AndroidResFolder; +import com.google.idea.blaze.base.ideinfo.ArtifactLocation; +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.lang.buildfile.references.BuildReferenceManager; +import com.google.idea.blaze.base.model.BlazeProjectData; +import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.settings.BlazeImportSettings.ProjectType; +import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; +import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder; +import com.google.idea.blaze.base.targetmaps.SourceToTargetMap; +import com.google.idea.blaze.base.targetmaps.TransitiveDependencyMap; +import com.intellij.lang.annotation.HighlightSeverity; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.IndexNotReadyException; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VfsUtilCore; +import com.intellij.psi.JavaPsiFacade; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.search.GlobalSearchScope; +import java.io.File; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.function.Function; +import javax.swing.JEditorPane; +import javax.swing.event.HyperlinkEvent; +import javax.swing.event.HyperlinkListener; +import javax.swing.text.html.HTMLDocument; +import javax.swing.text.html.HTMLFrameHyperlinkEvent; + +/** Contribute blaze specific render errors. */ +public class BlazeRenderErrorContributor implements RenderErrorContributor { + private final EditorDesignSurface designSurface; + private final RenderLogger logger; + private final Module module; + private final PsiFile sourceFile; + private final Project project; + private final HtmlLinkManager linkManager; + private final HyperlinkListener linkHandler; + private final Set issues = new LinkedHashSet<>(); + + public BlazeRenderErrorContributor(EditorDesignSurface surface, RenderResult result) { + designSurface = surface; + logger = result.getLogger(); + module = result.getModule(); + sourceFile = result.getSourceFile(); + project = module.getProject(); + linkManager = logger.getLinkManager(); + linkHandler = + e -> { + if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { + JEditorPane pane = (JEditorPane) e.getSource(); + if (e instanceof HTMLFrameHyperlinkEvent) { + HTMLFrameHyperlinkEvent evt = (HTMLFrameHyperlinkEvent) e; + HTMLDocument doc = (HTMLDocument) pane.getDocument(); + doc.processHTMLFrameHyperlinkEvent(evt); + return; + } + + performClick(e.getDescription()); + } + }; + } + + private HtmlLinkManager getLinkManager() { + return linkManager; + } + + private Collection getIssues() { + return Collections.unmodifiableCollection(issues); + } + + private RenderErrorModel.Issue.Builder addIssue() { + return new RenderErrorModel.Issue.Builder() { + @Override + public RenderErrorModel.Issue build() { + RenderErrorModel.Issue built = super.build(); + issues.add(built); + return built; + } + }.setLinkHandler(linkHandler); + } + + private void performClick(String url) { + linkManager.handleUrl( + url, + module, + sourceFile, + true, + new HtmlLinkManager.RefreshableSurface() { + @Override + public void handleRefreshRenderUrl() { + if (designSurface != null) { + // TODO(b/321801969): Remove and replace with direct call when in repo. + // Use reflection to getConfigurations() from designSurface. Can't call directly + // because it returns an incompatible version of ImmutableCollection. + // RenderUtils.clearCache(designSurface.getConfigurations()); would fail at runtime. + try { + Method getConfigurationsMethod = + EditorDesignSurface.class.getMethod("getConfigurations", null); + Object configurations = getConfigurationsMethod.invoke(designSurface); + Method clearCacheMethod = + RenderUtils.class.getMethod( + "clearCache", getConfigurationsMethod.getReturnType()); + clearCacheMethod.invoke(null, configurations); + } catch (NoSuchMethodException ex) { + throw new RuntimeException( + "Error using reflection to get getConfigurations() instance method: " + ex); + } catch (IllegalAccessException ex) { + throw new RuntimeException( + "Error accessing getConfigurations() instance method" + ex); + } catch (InvocationTargetException ex) { + throw new RuntimeException("Error invoking target getConfigurations(): " + ex); + } + var unused = designSurface.forceUserRequestedRefresh(); + } + } + + @Override + public void requestRender() { + if (designSurface != null) { + var unused = designSurface.forceUserRequestedRefresh(); + } + } + }); + } + + @Override + public Collection reportIssues() { + BlazeProjectData blazeProjectData = + BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); + + if (blazeProjectData == null || !logger.hasErrors()) { + return getIssues(); + } + + if (Blaze.getProjectType(project).equals(ProjectType.QUERY_SYNC)) { + // TODO(b/284002829): Setup resource-module specific issue reporting + return getIssues(); + } + + TargetMap targetMap = blazeProjectData.getTargetMap(); + ArtifactLocationDecoder decoder = blazeProjectData.getArtifactLocationDecoder(); + AndroidResourceModule resourceModule = + AndroidResourceModuleRegistry.getInstance(project).get(module); + if (resourceModule == null) { + return getIssues(); + } + + TargetIdeInfo target = targetMap.get(resourceModule.targetKey); + if (target == null) { + return getIssues(); + } + + reportGeneratedResources(resourceModule, targetMap, decoder); + reportNonStandardAndroidManifestName(target, decoder); + reportResourceTargetShouldDependOnClassTarget(target, targetMap, decoder); + return getIssues(); + } + + /** + * We can't find generated resources. If a layout uses them, the layout won't render correctly. + */ + private void reportGeneratedResources( + AndroidResourceModule resourceModule, TargetMap targetMap, ArtifactLocationDecoder decoder) { + Map brokenClasses = logger.getBrokenClasses(); + if (brokenClasses == null || brokenClasses.isEmpty()) { + return; + } + + // Sorted entries for deterministic error message. + SortedMap generatedResources = + Maps.newTreeMap(getGeneratedResources(targetMap.get(resourceModule.targetKey))); + + for (TargetKey dependency : resourceModule.transitiveResourceDependencies) { + generatedResources.putAll(getGeneratedResources(targetMap.get(dependency))); + } + + if (generatedResources.isEmpty()) { + return; + } + + HtmlBuilder builder = new HtmlBuilder(); + builder.add("Generated resources will not be discovered by the IDE:"); + builder.beginList(); + for (Map.Entry entry : generatedResources.entrySet()) { + ArtifactLocation resource = entry.getKey(); + TargetIdeInfo target = entry.getValue(); + builder.listItem().add(resource.getRelativePath()).add(" from "); + addTargetLink(builder, target, decoder); + } + builder + .endList() + .add("Please avoid using generated resources, ") + .addLink("then ", "sync the project", " ", getLinkManager().createSyncProjectUrl()) + .addLink("and ", "refresh the layout", ".", getLinkManager().createRefreshRenderUrl()); + addIssue() + .setSeverity(HighlightSeverity.ERROR, HIGH_PRIORITY + 1) // Reported above broken classes + .setSummary("Generated resources") + .setHtmlContent(builder) + .build(); + } + + private static SortedMap getGeneratedResources( + TargetIdeInfo target) { + if (target == null || target.getAndroidIdeInfo() == null) { + return Collections.emptySortedMap(); + } + SortedMap generatedResources = Maps.newTreeMap(); + generatedResources.putAll( + target.getAndroidIdeInfo().getResources().stream() + .map(AndroidResFolder::getRoot) + .filter(ArtifactLocation::isGenerated) + .collect(toImmutableMap(Function.identity(), resource -> target))); + return generatedResources; + } + + /** + * When the Android manifest isn't AndroidManifest.xml, resolving resource IDs would fail. This + * doesn't seem to be an issue if the manifest belongs to one of the target's dependencies. + */ + private void reportNonStandardAndroidManifestName( + TargetIdeInfo target, ArtifactLocationDecoder decoder) { + if (target.getAndroidIdeInfo() == null || target.getAndroidIdeInfo().getManifest() == null) { + return; + } + + Map brokenClasses = logger.getBrokenClasses(); + if (brokenClasses == null || brokenClasses.isEmpty()) { + return; + } + + ArtifactLocation maniftestArtifactLocation = target.getAndroidIdeInfo().getManifest(); + + File manifest = + Preconditions.checkNotNull( + OutputArtifactResolver.resolve(project, decoder, maniftestArtifactLocation), + "Fail to find file %s", + maniftestArtifactLocation.getRelativePath()); + if (manifest.getName().equals(ANDROID_MANIFEST_XML)) { + return; + } + + HtmlBuilder builder = new HtmlBuilder(); + addTargetLink(builder, target, decoder) + .add(" uses a non-standard name for the Android manifest: "); + String linkToManifest = HtmlLinkManagerCompat.createFilePositionUrl(manifest, -1, 0); + if (linkToManifest != null) { + builder.addLink(manifest.getName(), linkToManifest); + } else { + builder.newline().add(manifest.getPath()); + } + // TODO: add a link to automatically rename the file and refactor all references. + builder + .newline() + .add("Please rename it to ") + .add(ANDROID_MANIFEST_XML) + .addLink(", then ", "sync the project", "", getLinkManager().createSyncProjectUrl()) + .addLink(" and ", "refresh the layout", ".", getLinkManager().createRefreshRenderUrl()); + addIssue() + .setSeverity(HighlightSeverity.ERROR, HIGH_PRIORITY + 1) // Reported above broken classes. + .setSummary("Non-standard manifest name") + .setHtmlContent(builder) + .build(); + } + + /** + * Blaze doesn't resolve class dependencies from resources until building the final + * android_binary, so we could end up with resources that ultimately build correctly, but fail to + * find their class dependencies during rendering in the layout editor. + */ + @SuppressWarnings("rawtypes") + private void reportResourceTargetShouldDependOnClassTarget( + TargetIdeInfo target, TargetMap targetMap, ArtifactLocationDecoder decoder) { + Set missingClasses = logger.getMissingClasses(); + if (missingClasses == null || missingClasses.isEmpty()) { + return; + } + + // Sorted entries for deterministic error message. + SortedSetMultimap missingClassToTargetMap = TreeMultimap.create(); + + SourceToTargetMap sourceToTargetMap = SourceToTargetMap.getInstance(project); + ImmutableCollection transitiveDependencies = + TransitiveDependencyMap.getInstance(project).getTransitiveDependencies(target.getKey()); + + for (String missingClass : missingClasses) { + File sourceFile = getSourceFileForClass(missingClass); + if (sourceFile == null) { + continue; + } + ImmutableCollection sourceTargets = + sourceToTargetMap.getRulesForSourceFile(sourceFile); + if (sourceTargets.stream() + .noneMatch( + sourceTarget -> + sourceTarget.equals(target.getKey()) + || transitiveDependencies.contains(sourceTarget))) { + missingClassToTargetMap.putAll(missingClass, sourceTargets); + } + } + + if (missingClassToTargetMap.isEmpty()) { + return; + } + + HtmlBuilder builder = new HtmlBuilder(); + addTargetLink(builder, target, decoder) + .add(" contains resource files that reference these classes:") + .beginList(); + for (String missingClass : missingClassToTargetMap.keySet()) { + builder + .listItem() + .addLink(missingClass, getLinkManager().createOpenClassUrl(missingClass)) + .add(" from "); + for (TargetKey targetKey : missingClassToTargetMap.get(missingClass)) { + addTargetLink(builder, targetMap.get(targetKey), decoder).add(" "); + } + } + builder.endList().add("Please fix your dependencies so that "); + addTargetLink(builder, target, decoder) + .add(" correctly depends on these classes, ") + .addLink("then ", "sync the project", " ", getLinkManager().createSyncProjectUrl()) + .addLink("and ", "refresh the layout", ".", getLinkManager().createRefreshRenderUrl()) + .newline() + .newline() + .addBold( + "NOTE: blaze can still build with the incorrect dependencies " + + "due to the way it handles resources, " + + "but the layout editor needs them to be correct."); + + addIssue() + .setSeverity(HighlightSeverity.ERROR, HIGH_PRIORITY + 1) // Reported above missing classes. + .setSummary("Missing class dependencies") + .setHtmlContent(builder) + .build(); + } + + private File getSourceFileForClass(String className) { + return ApplicationManager.getApplication() + .runReadAction( + (Computable) + () -> { + try { + PsiClass psiClass = + JavaPsiFacade.getInstance(project) + .findClass(className, GlobalSearchScope.projectScope(project)); + if (psiClass == null) { + return null; + } + return VfsUtilCore.virtualToIoFile( + psiClass.getContainingFile().getVirtualFile()); + } catch (IndexNotReadyException ignored) { + // We're in dumb mode. Abort! Abort! + return null; + } + }); + } + + @CanIgnoreReturnValue + private HtmlBuilder addTargetLink( + HtmlBuilder builder, TargetIdeInfo target, ArtifactLocationDecoder decoder) { + File buildFile = + Preconditions.checkNotNull( + OutputArtifactResolver.resolve(project, decoder, target.getBuildFile()), + "Fail to find file %s", + target.getBuildFile().getRelativePath()); + int line = + ApplicationManager.getApplication() + .runReadAction( + (Computable) + () -> { + PsiElement buildTargetPsi = + BuildReferenceManager.getInstance(project) + .resolveLabel(target.getKey().getLabel()); + if (buildTargetPsi == null) { + return -1; + } + PsiFile psiFile = buildTargetPsi.getContainingFile(); + if (psiFile == null) { + return -1; + } + return StringUtil.offsetToLineNumber( + psiFile.getText(), buildTargetPsi.getTextOffset()); + }); + String url = HtmlLinkManagerCompat.createFilePositionUrl(buildFile, line, 0); + if (url != null) { + return builder.addLink(target.toString(), url); + } + return builder.add(target.toString()); + } +} diff --git a/aswb/sdkcompat/as232/com/google/idea/blaze/android/rendering/BlazeRenderSecurityManagerOverrides.java b/aswb/sdkcompat/as232/com/google/idea/blaze/android/rendering/BlazeRenderSecurityManagerOverrides.java new file mode 100644 index 00000000000..39d3edd195f --- /dev/null +++ b/aswb/sdkcompat/as232/com/google/idea/blaze/android/rendering/BlazeRenderSecurityManagerOverrides.java @@ -0,0 +1,27 @@ +/* + * Copyright 2018 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.rendering; + +import com.android.tools.rendering.security.RenderSecurityManagerOverrides; + +/** Overrides some security restrictions used by the render sandbox. */ +public class BlazeRenderSecurityManagerOverrides implements RenderSecurityManagerOverrides { + @Override + public boolean allowsPropertiesAccess() { + // System properties access is needed for SystemPropertyExperimentLoader + return true; + } +} diff --git a/aswb/sdkcompat/as232/com/google/idea/blaze/android/rendering/RenderErrorLoggingContributorCompat.java b/aswb/sdkcompat/as232/com/google/idea/blaze/android/rendering/RenderErrorLoggingContributorCompat.java new file mode 100644 index 00000000000..268e063c190 --- /dev/null +++ b/aswb/sdkcompat/as232/com/google/idea/blaze/android/rendering/RenderErrorLoggingContributorCompat.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.rendering; + +import com.android.tools.idea.rendering.RenderErrorContributor; +import com.android.tools.idea.rendering.RenderErrorContributorImpl; +import com.android.tools.idea.ui.designer.EditorDesignSurface; +import com.android.tools.rendering.RenderResultCompat; +import com.intellij.openapi.actionSystem.DataContext; +import org.jetbrains.annotations.Nullable; + +/** Collects and uploads render errors by hooking into {@link RenderErrorContributor}. */ +abstract class RenderErrorLoggingContributorCompat extends RenderErrorContributorImpl { + public RenderErrorLoggingContributorCompat( + EditorDesignSurface surface, RenderResultCompat result, @Nullable DataContext dataContext) { + super(surface, result.get()); + } + + public RenderErrorLoggingContributorCompat( + EditorDesignSurface surface, RenderResultCompat result) { + super(surface, result.get()); + } +} diff --git a/aswb/sdkcompat/as232/com/google/idea/blaze/android/resources/BlazeLightResourceClassServiceBase.java b/aswb/sdkcompat/as232/com/google/idea/blaze/android/resources/BlazeLightResourceClassServiceBase.java new file mode 100644 index 00000000000..be9c3b2d882 --- /dev/null +++ b/aswb/sdkcompat/as232/com/google/idea/blaze/android/resources/BlazeLightResourceClassServiceBase.java @@ -0,0 +1,64 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.resources; + +import com.android.tools.idea.projectsystem.LightResourceClassService; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.google.idea.blaze.base.sync.data.BlazeDataStorage; +import com.google.idea.common.experiments.FeatureRolloutExperiment; +import com.intellij.openapi.module.Module; +import com.intellij.psi.PsiClass; +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +/** Implementation of {@link LightResourceClassService} set up at Blaze sync time. */ +public abstract class BlazeLightResourceClassServiceBase implements LightResourceClassService { + + @VisibleForTesting + public static final FeatureRolloutExperiment workspaceResourcesFeature = + new FeatureRolloutExperiment("aswb.workspace.light.class.enabled"); + + Map rClasses = Maps.newHashMap(); + Map rClassesByModule = Maps.newHashMap(); + final Set allRClasses = Sets.newHashSet(); + + @Override + public Collection getLightRClassesAccessibleFromModule(Module module) { + if (workspaceResourcesFeature.isEnabled() + && module.getName().equals(BlazeDataStorage.WORKSPACE_MODULE_NAME)) { + // Returns all the packages in resource modules, and all the workspace packages that + // have previously been asked for. All `res/` directories in our project should belong to a + // resource module. For java sources, IntelliJ will ask for explicit resource package by + // calling `getLightRClasses` at which point we can create the package. This is not completely + // correct and the autocomplete will be slightly off when initial `R` is typed in the editor, + // but this workaround is being used to mitigate issues (b/136685602) while resources + // are re-worked. + return allRClasses; + } else { + return rClasses.values(); + } + } + + @Override + public Collection getLightRClassesDefinedByModule(Module module) { + BlazeRClass rClass = rClassesByModule.get(module); + return rClass == null ? ImmutableSet.of() : ImmutableSet.of(rClass); + } +} diff --git a/aswb/sdkcompat/as232/com/google/idea/blaze/android/resources/BlazeRClass.java b/aswb/sdkcompat/as232/com/google/idea/blaze/android/resources/BlazeRClass.java new file mode 100644 index 00000000000..37895c5a1cc --- /dev/null +++ b/aswb/sdkcompat/as232/com/google/idea/blaze/android/resources/BlazeRClass.java @@ -0,0 +1,82 @@ +/* + * Copyright 2018 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.resources; + +import static org.jetbrains.android.AndroidResolveScopeEnlarger.LIGHT_CLASS_KEY; +import static org.jetbrains.android.AndroidResolveScopeEnlarger.MODULE_POINTER_KEY; + +import com.android.ide.common.rendering.api.ResourceNamespace; +import com.android.tools.idea.res.ResourceRepositoryRClass; +import com.android.tools.idea.res.StudioResourceRepositoryManager; +import com.android.tools.res.CacheableResourceRepository; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModulePointerManager; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiManager; +import org.jetbrains.android.augment.AndroidLightField; +import org.jetbrains.android.facet.AndroidFacet; + +/** Blaze implementation of an R class based on resource repositories. */ +public class BlazeRClass extends ResourceRepositoryRClass { + + private final AndroidFacet androidFacet; + + public BlazeRClass(PsiManager psiManager, AndroidFacet androidFacet, String packageName) { + super( + psiManager, + new ResourcesSource() { + @Override + public String getPackageName() { + return packageName; + } + + // @Override #api4.1 + public Transitivity getTransitivity() { + return Transitivity.TRANSITIVE; + } + + @Override + public StudioResourceRepositoryManager getResourceRepositoryManager() { + return StudioResourceRepositoryManager.getInstance(androidFacet); + } + + @Override + public CacheableResourceRepository getResourceRepository() { + return StudioResourceRepositoryManager.getAppResources(androidFacet); + } + + @Override + public ResourceNamespace getResourceNamespace() { + return ResourceNamespace.RES_AUTO; + } + + @Override + public AndroidLightField.FieldModifier getFieldModifier() { + return AndroidLightField.FieldModifier.NON_FINAL; + } + }); + this.androidFacet = androidFacet; + setModuleInfo(getModule(), false); + VirtualFile virtualFile = myFile.getViewProvider().getVirtualFile(); + virtualFile.putUserData( + MODULE_POINTER_KEY, ModulePointerManager.getInstance(getProject()).create(getModule())); + virtualFile.putUserData(LIGHT_CLASS_KEY, ResourceRepositoryRClass.class); + } + + public Module getModule() { + return androidFacet.getModule(); + } +} diff --git a/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/BazelApplicationProjectContext.java b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/BazelApplicationProjectContext.java new file mode 100644 index 00000000000..cbca2310836 --- /dev/null +++ b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/BazelApplicationProjectContext.java @@ -0,0 +1,61 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.android.tools.idea.projectsystem.ApplicationProjectContext; +import com.android.tools.idea.run.ApkProvisionException; +import com.android.tools.idea.run.ApplicationIdProvider; +import com.intellij.openapi.project.Project; +import java.util.function.Supplier; + +/** + * An implementation of {@link ApplicationProjectContext} used in the Blaze project system. + * + *

Note: The Blaze project system assumes all instances of the {@link + * ApplicationProjectContext} associated with its projects to be backed by this specific class. + */ +public class BazelApplicationProjectContext implements ApplicationProjectContext { + + private final Project project; + private final Supplier applicationId; + + public BazelApplicationProjectContext(Project project, String applicationId) { + this.project = project; + this.applicationId = () -> applicationId; + } + + public BazelApplicationProjectContext( + Project project, ApplicationIdProvider applicationIdProvider) { + this.project = project; + this.applicationId = + () -> { + try { + return applicationIdProvider.getPackageName(); + } catch (ApkProvisionException e) { + throw new RuntimeException(e); + } + }; + } + + public Project getProject() { + return project; + } + + @Override + public String getApplicationId() { + return applicationId.get(); + } +} diff --git a/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/BazelApplicationProjectContextProvider.java b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/BazelApplicationProjectContextProvider.java new file mode 100644 index 00000000000..bffcc3d238d --- /dev/null +++ b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/BazelApplicationProjectContextProvider.java @@ -0,0 +1,49 @@ +/* + * Copyright 2024 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.android.ddmlib.Client; +import com.android.ddmlib.ClientData; +import com.android.tools.idea.projectsystem.ApplicationProjectContext; +import com.android.tools.idea.projectsystem.ApplicationProjectContextProvider; +import com.google.idea.blaze.android.projectsystem.BlazeToken; +import com.intellij.openapi.project.Project; +import javax.annotation.Nullable; + +/** An implementation of {@link ApplicationProjectContextProvider} for the Blaze project system. */ +public class BazelApplicationProjectContextProvider + implements ApplicationProjectContextProvider, BlazeToken { + + private final Project project; + + public BazelApplicationProjectContextProvider(Project project) { + this.project = project; + } + + @Nullable + @Override + public ApplicationProjectContext getApplicationProjectContextProvider(Client client) { + ClientData clientData = client.getClientData(); + if (clientData == null) { + return null; + } + String androidPackageName = clientData.getPackageName(); + if (androidPackageName == null) { + return null; + } + return new BazelApplicationProjectContext(project, androidPackageName); + } +} diff --git a/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/BlazeNativeDebuggerAppContextProvider.java b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/BlazeNativeDebuggerAppContextProvider.java new file mode 100644 index 00000000000..df4cd15d3cf --- /dev/null +++ b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/BlazeNativeDebuggerAppContextProvider.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.android.sdklib.devices.Abi; +import com.android.tools.idea.projectsystem.ApplicationProjectContext; +import com.android.tools.ndk.run.NativeDebuggerAppContext; +import com.android.tools.ndk.run.NativeDebuggerAppContextProvider; +import com.android.tools.ndk.run.SymbolDir; +import com.google.idea.blaze.android.projectsystem.BlazeToken; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import java.io.File; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** An implementation of {@link NativeDebuggerAppContextProvider} for the Blaze project system. */ +public class BlazeNativeDebuggerAppContextProvider + implements NativeDebuggerAppContextProvider, BlazeToken { + + @Override + public NativeDebuggerAppContext getNativeDebuggerAppContext( + ApplicationProjectContext applicationProjectContext) { + final var context = (BazelApplicationProjectContext) applicationProjectContext; + // TODO(solodkyy): Find out whether any configuration that is supposed to be part of this + // context is passed to the debugger in oher ways and make sure this is the + // only way to set up the debugger. + return new NativeDebuggerAppContext() { + @Override + public Project getProject() { + return context.getProject(); + } + + @Override + public String getApplicationId() { + return context.getApplicationId(); + } + + @Override + public Collection getSymDirs(List abis) { + return Collections.emptyList(); + } + + @Override + public Map getSourceMap() { + return Collections.emptyMap(); + } + + @Override + public Map getExplicitModuleSymbolMap(Abi abi) { + return Collections.emptyMap(); + } + + @Override + public Collection getModulesToVerify() { + return Collections.emptyList(); + } + }; + } +} diff --git a/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryApplicationLaunchTaskProvider.java b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryApplicationLaunchTaskProvider.java new file mode 100644 index 00000000000..948dae75f36 --- /dev/null +++ b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryApplicationLaunchTaskProvider.java @@ -0,0 +1,72 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.binary; + +import com.android.tools.idea.run.ApkProvisionException; +import com.android.tools.idea.run.ApplicationIdProvider; +import com.android.tools.idea.run.activity.StartActivityFlagsProvider; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; +import com.google.idea.blaze.android.manifest.ManifestParser; +import com.google.idea.blaze.android.run.binary.tasks.AndroidDeepLinkLaunchTask; +import com.google.idea.blaze.android.run.binary.tasks.BlazeDefaultActivityLaunchTask; +import com.google.idea.blaze.android.run.binary.tasks.SpecificActivityLaunchTask; +import com.intellij.execution.ExecutionException; +import com.intellij.openapi.diagnostic.Logger; + +/** Provides the launch task for android_binary */ +public class BlazeAndroidBinaryApplicationLaunchTaskProvider { + private static final Logger LOG = + Logger.getInstance(BlazeAndroidBinaryApplicationLaunchTaskProvider.class); + + public static BlazeLaunchTask getApplicationLaunchTask( + ApplicationIdProvider applicationIdProvider, + ManifestParser.ParsedManifest mergedManifestParsedManifest, + BlazeAndroidBinaryRunConfigurationState configState, + StartActivityFlagsProvider startActivityFlagsProvider) + throws ExecutionException { + String applicationId; + try { + applicationId = applicationIdProvider.getPackageName(); + } catch (ApkProvisionException e) { + throw new ExecutionException("Unable to identify application id"); + } + + switch (configState.getMode()) { + case BlazeAndroidBinaryRunConfigurationState.LAUNCH_DEFAULT_ACTIVITY: + BlazeDefaultActivityLocator activityLocator = + new BlazeDefaultActivityLocator(mergedManifestParsedManifest); + return new BlazeDefaultActivityLaunchTask( + applicationId, activityLocator, startActivityFlagsProvider); + case BlazeAndroidBinaryRunConfigurationState.LAUNCH_SPECIFIC_ACTIVITY: + return launchContext -> + new SpecificActivityLaunchTask( + applicationId, configState.getActivityClass(), startActivityFlagsProvider) + .run( + launchContext.getDevice(), + launchContext.getProgressIndicator(), + launchContext.getConsoleView()); + case BlazeAndroidBinaryRunConfigurationState.LAUNCH_DEEP_LINK: + return launchContext -> + new AndroidDeepLinkLaunchTask(configState.getDeepLink(), startActivityFlagsProvider) + .run( + launchContext.getDevice(), + launchContext.getProgressIndicator(), + launchContext.getConsoleView()); + default: + return null; + } + } +} diff --git a/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContext.java b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContext.java new file mode 100644 index 00000000000..01af2003452 --- /dev/null +++ b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContext.java @@ -0,0 +1,130 @@ +/* + * Copyright 2019 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.binary; + +import com.android.ddmlib.IDevice; +import com.android.tools.idea.execution.common.debug.AndroidDebugger; +import com.android.tools.idea.execution.common.debug.AndroidDebuggerState; +import com.android.tools.idea.execution.common.debug.DebugSessionStarter; +import com.android.tools.idea.projectsystem.ApplicationProjectContext; +import com.android.tools.idea.run.ApkProvisionException; +import com.android.tools.idea.run.activity.DefaultStartActivityFlagsProvider; +import com.android.tools.idea.run.activity.StartActivityFlagsProvider; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; +import com.google.idea.blaze.android.run.BazelApplicationProjectContext; +import com.google.idea.blaze.android.run.deployinfo.BlazeAndroidDeployInfo; +import com.google.idea.blaze.android.run.runner.ApkBuildStep; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.Executor; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.ui.ConsoleView; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.project.Project; +import com.intellij.xdebugger.XDebugSession; +import javax.annotation.Nullable; +import kotlin.Unit; +import kotlin.coroutines.EmptyCoroutineContext; +import kotlinx.coroutines.BuildersKt; +import org.jetbrains.android.facet.AndroidFacet; + +/** Compat for #api212 */ +public class BlazeAndroidBinaryNormalBuildRunContext + extends BlazeAndroidBinaryNormalBuildRunContextBase { + BlazeAndroidBinaryNormalBuildRunContext( + Project project, + AndroidFacet facet, + RunConfiguration runConfiguration, + ExecutionEnvironment env, + BlazeAndroidBinaryRunConfigurationState configState, + ApkBuildStep buildStep, + String launchId) { + super(project, facet, runConfiguration, env, configState, buildStep, launchId); + } + + @Override + public ApplicationProjectContext getApplicationProjectContext() { + return new BazelApplicationProjectContext(project, getApplicationIdProvider()); + } + + @Override + public BlazeLaunchTask getApplicationLaunchTask( + boolean isDebug, @Nullable Integer userId, String contributorsAmStartOptions) + throws ExecutionException { + String extraFlags = UserIdHelper.getFlagsFromUserId(userId); + if (!contributorsAmStartOptions.isEmpty()) { + extraFlags += (extraFlags.isEmpty() ? "" : " ") + contributorsAmStartOptions; + } + if (isDebug) { + extraFlags += (extraFlags.isEmpty() ? "" : " ") + "-D"; + } + + final StartActivityFlagsProvider startActivityFlagsProvider = + new DefaultStartActivityFlagsProvider(project, isDebug, extraFlags); + + BlazeAndroidDeployInfo deployInfo; + try { + deployInfo = buildStep.getDeployInfo(); + } catch (ApkProvisionException e) { + throw new ExecutionException(e); + } + + return BlazeAndroidBinaryApplicationLaunchTaskProvider.getApplicationLaunchTask( + applicationIdProvider, + deployInfo.getMergedManifest(), + configState, + startActivityFlagsProvider); + } + + @Nullable + @Override + public XDebugSession startDebuggerSession( + AndroidDebugger androidDebugger, + AndroidDebuggerState androidDebuggerState, + ExecutionEnvironment env, + IDevice device, + ConsoleView consoleView, + ProgressIndicator indicator, + String packageName) { + try { + return BuildersKt.runBlocking( + EmptyCoroutineContext.INSTANCE, + (scope, continuation) -> + DebugSessionStarter.INSTANCE.attachDebuggerToStartedProcess( + device, + new BazelApplicationProjectContext(project, packageName), + env, + androidDebugger, + androidDebuggerState, + /*destroyRunningProcess*/ d -> { + d.forceStop(packageName); + return Unit.INSTANCE; + }, + indicator, + consoleView, + 15L, + continuation)); + } catch (InterruptedException e) { + throw new ProcessCanceledException(); + } + } + + @Override + public Executor getExecutor() { + return env.getExecutor(); + } +} diff --git a/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContextBase.java b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContextBase.java new file mode 100644 index 00000000000..96f3f8be324 --- /dev/null +++ b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContextBase.java @@ -0,0 +1,186 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.binary; + +import static com.google.idea.blaze.android.run.runner.BlazeAndroidLaunchTasksProvider.NATIVE_DEBUGGING_ENABLED; + +import com.android.ddmlib.IDevice; +import com.android.tools.idea.execution.common.DeployOptions; +import com.android.tools.idea.gradle.util.DynamicAppUtils; +import com.android.tools.idea.run.ApkFileUnit; +import com.android.tools.idea.run.ApkInfo; +import com.android.tools.idea.run.ApkProvider; +import com.android.tools.idea.run.ApkProvisionException; +import com.android.tools.idea.run.ApplicationIdProvider; +import com.android.tools.idea.run.ConsoleProvider; +import com.android.tools.idea.run.LaunchOptions; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; +import com.android.tools.idea.run.blaze.BlazeLaunchTasksProvider; +import com.android.tools.idea.run.editor.ProfilerState; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.idea.blaze.android.run.deployinfo.BlazeApkProviderService; +import com.google.idea.blaze.android.run.runner.ApkBuildStep; +import com.google.idea.blaze.android.run.runner.BlazeAndroidDeviceSelector; +import com.google.idea.blaze.android.run.runner.BlazeAndroidLaunchTasksProvider; +import com.google.idea.blaze.android.run.runner.BlazeAndroidRunContext; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.Executor; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.openapi.project.Project; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import org.jetbrains.android.facet.AndroidFacet; +import org.jetbrains.annotations.NotNull; + +/** Run context for android_binary. */ +public abstract class BlazeAndroidBinaryNormalBuildRunContextBase + implements BlazeAndroidRunContext { + protected final Project project; + protected final AndroidFacet facet; + protected final RunConfiguration runConfiguration; + protected final ExecutionEnvironment env; + protected final BlazeAndroidBinaryRunConfigurationState configState; + protected final ConsoleProvider consoleProvider; + protected final ApkBuildStep buildStep; + protected final ApkProvider apkProvider; + protected final ApplicationIdProvider applicationIdProvider; + private final String launchId; + + BlazeAndroidBinaryNormalBuildRunContextBase( + Project project, + AndroidFacet facet, + RunConfiguration runConfiguration, + ExecutionEnvironment env, + BlazeAndroidBinaryRunConfigurationState configState, + ApkBuildStep buildStep, + String launchId) { + this.project = project; + this.facet = facet; + this.runConfiguration = runConfiguration; + this.env = env; + this.configState = configState; + this.consoleProvider = new BlazeAndroidBinaryConsoleProvider(project); + this.buildStep = buildStep; + this.apkProvider = BlazeApkProviderService.getInstance().getApkProvider(project, buildStep); + this.applicationIdProvider = new BlazeAndroidBinaryApplicationIdProvider(buildStep); + this.launchId = launchId; + } + + @Override + public BlazeAndroidDeviceSelector getDeviceSelector() { + return new BlazeAndroidDeviceSelector.NormalDeviceSelector(); + } + + @Override + public void augmentLaunchOptions(LaunchOptions.Builder options) { + LaunchOptions.Builder unused = + options + .setDeploy(true) + .setOpenLogcatAutomatically(configState.showLogcatAutomatically()) + .addExtraOptions( + ImmutableMap.of( + NATIVE_DEBUGGING_ENABLED, + configState.getCommonState().isNativeDebuggingEnabled())) + .setClearAppStorage(configState.getClearAppStorage()); + } + + @Override + public ConsoleProvider getConsoleProvider() { + return consoleProvider; + } + + @Override + public ApplicationIdProvider getApplicationIdProvider() { + return applicationIdProvider; + } + + @Override + public ApkBuildStep getBuildStep() { + return buildStep; + } + + @Nullable + @Override + public Integer getUserId(IDevice device) throws ExecutionException { + return UserIdHelper.getUserIdFromConfigurationState(project, device, configState); + } + + @Override + public BlazeLaunchTasksProvider getLaunchTasksProvider(LaunchOptions launchOptions) + throws ExecutionException { + return new BlazeAndroidLaunchTasksProvider(project, this, applicationIdProvider, launchOptions); + } + + @Override + public String getAmStartOptions() { + return configState.getAmStartOptions(); + } + + @Nullable + @Override + public ImmutableList getDeployTasks(IDevice device, DeployOptions deployOptions) + throws ExecutionException { + return ImmutableList.of( + new DeploymentTimingReporterTask( + launchId, + project, + getApkInfoToInstall(device, deployOptions, apkProvider), + deployOptions)); + } + + /** Returns a list of APKs excluding any APKs for features that are disabled. */ + public static List getApkInfoToInstall( + IDevice device, DeployOptions deployOptions, ApkProvider apkProvider) + throws ExecutionException { + Collection apks; + try { + apks = apkProvider.getApks(device); + } catch (ApkProvisionException e) { + throw new ExecutionException(e); + } + List disabledFeatures = deployOptions.getDisabledDynamicFeatures(); + return apks.stream() + .map(apk -> getApkInfoToInstall(apk, disabledFeatures)) + .collect(Collectors.toList()); + } + + @NotNull + private static ApkInfo getApkInfoToInstall(ApkInfo apkInfo, List disabledFeatures) { + if (apkInfo.getFiles().size() > 1) { + List filteredApks = + apkInfo.getFiles().stream() + .filter(feature -> DynamicAppUtils.isFeatureEnabled(disabledFeatures, feature)) + .collect(Collectors.toList()); + return new ApkInfo(filteredApks, apkInfo.getApplicationId()); + } else { + return apkInfo; + } + } + + @Override + public Executor getExecutor() { + return env.getExecutor(); + } + + @Override + public ProfilerState getProfileState() { + return configState.getProfilerState(); + } +} diff --git a/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContextCompat.java b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContextCompat.java new file mode 100644 index 00000000000..d3e7ab1dc65 --- /dev/null +++ b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContextCompat.java @@ -0,0 +1,84 @@ +/* + * Copyright 2022 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.binary; + +import com.android.ddmlib.IDevice; +import com.android.tools.idea.execution.common.debug.AndroidDebugger; +import com.android.tools.idea.execution.common.debug.AndroidDebuggerState; +import com.android.tools.idea.execution.common.debug.DebugSessionStarter; +import com.google.idea.blaze.android.run.BazelApplicationProjectContext; +import com.google.idea.blaze.android.run.runner.ApkBuildStep; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.ui.ConsoleView; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.project.Project; +import com.intellij.xdebugger.XDebugSession; +import javax.annotation.Nullable; +import kotlin.Unit; +import kotlin.coroutines.EmptyCoroutineContext; +import kotlinx.coroutines.BuildersKt; +import org.jetbrains.android.facet.AndroidFacet; + +/** Compat class for {@link BlazeAndroidBinaryNormalBuildRunContext}. */ +public class BlazeAndroidBinaryNormalBuildRunContextCompat + extends BlazeAndroidBinaryNormalBuildRunContext { + + BlazeAndroidBinaryNormalBuildRunContextCompat( + Project project, + AndroidFacet facet, + RunConfiguration runConfiguration, + ExecutionEnvironment env, + BlazeAndroidBinaryRunConfigurationState configState, + ApkBuildStep buildStep, + String launchId) { + super(project, facet, runConfiguration, env, configState, buildStep, launchId); + } + + @Nullable + @Override + public XDebugSession startDebuggerSession( + AndroidDebugger androidDebugger, + AndroidDebuggerState androidDebuggerState, + ExecutionEnvironment env, + IDevice device, + ConsoleView consoleView, + ProgressIndicator indicator, + String packageName) { + try { + return BuildersKt.runBlocking( + EmptyCoroutineContext.INSTANCE, + (scope, continuation) -> + DebugSessionStarter.INSTANCE.attachDebuggerToStartedProcess( + device, + new BazelApplicationProjectContext(project, packageName), + env, + androidDebugger, + androidDebuggerState, + /*destroyRunningProcess*/ d -> { + d.forceStop(packageName); + return Unit.INSTANCE; + }, + indicator, + consoleView, + 15L, + continuation)); + } catch (InterruptedException e) { + throw new ProcessCanceledException(); + } + } +} diff --git a/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryProgramRunner.java b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryProgramRunner.java new file mode 100644 index 00000000000..60bd11debaf --- /dev/null +++ b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryProgramRunner.java @@ -0,0 +1,114 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.binary; + +import static com.intellij.openapi.application.ModalityState.NON_MODAL; + +import com.android.tools.idea.execution.common.AndroidConfigurationExecutor; +import com.android.tools.idea.profilers.ProfileRunExecutor; +import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationHandler; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.ExecutionResult; +import com.intellij.execution.Executor; +import com.intellij.execution.configurations.RunProfile; +import com.intellij.execution.configurations.RunProfileState; +import com.intellij.execution.configurations.RunnerSettings; +import com.intellij.execution.executors.DefaultDebugExecutor; +import com.intellij.execution.executors.DefaultRunExecutor; +import com.intellij.execution.runners.AsyncProgramRunner; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.runners.RunContentBuilder; +import com.intellij.execution.ui.RunContentDescriptor; +import com.intellij.openapi.application.ActionsKt; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; +import org.jetbrains.concurrency.AsyncPromise; +import org.jetbrains.concurrency.Promise; + +/** Program runner for configurations from {@link BlazeAndroidBinaryRunConfigurationHandler}. */ +public class BlazeAndroidBinaryProgramRunner extends AsyncProgramRunner { + @Override + public boolean canRun(String executorId, RunProfile profile) { + BlazeAndroidRunConfigurationHandler handler = + BlazeAndroidRunConfigurationHandler.getHandlerFrom(profile); + if (!(handler instanceof BlazeAndroidBinaryRunConfigurationHandler)) { + return false; + } + return (DefaultDebugExecutor.EXECUTOR_ID.equals(executorId) + || DefaultRunExecutor.EXECUTOR_ID.equals(executorId) + || ProfileRunExecutor.EXECUTOR_ID.equals(executorId)); + } + + @Override + protected Promise execute( + ExecutionEnvironment environment, RunProfileState state) { + FileDocumentManager.getInstance().saveAllDocuments(); + + AsyncPromise promise = new AsyncPromise<>(); + + ProgressManager.getInstance() + .run( + new Task.Backgroundable(environment.getProject(), "Launching ${runProfile.name}") { + @Override + public void run(ProgressIndicator indicator) { + try { + RunContentDescriptor descriptor; + if (state instanceof AndroidConfigurationExecutor) { + AndroidConfigurationExecutor configurationExecutor = + (AndroidConfigurationExecutor) state; + Executor executor = environment.getExecutor(); + if (executor.getId().equals(DefaultDebugExecutor.EXECUTOR_ID)) { + descriptor = configurationExecutor.debug(indicator); + } else if (executor.getId().equals(DefaultRunExecutor.EXECUTOR_ID) + || executor.getId().equals(ProfileRunExecutor.EXECUTOR_ID)) { + descriptor = configurationExecutor.run(indicator); + } else { + throw new ExecutionException("Unsupported executor"); + } + } else { + descriptor = doExecute(state, environment); + } + promise.setResult(descriptor); + } catch (ExecutionException e) { + boolean unused = promise.setError(e); + } + } + + @Override + public void onCancel() { + super.onCancel(); + promise.setResult(null); + } + }); + + return promise; + } + + private RunContentDescriptor doExecute( + final RunProfileState state, final ExecutionEnvironment env) throws ExecutionException { + ExecutionResult result = state.execute(env.getExecutor(), this); + return ActionsKt.invokeAndWaitIfNeeded( + NON_MODAL, + () -> new RunContentBuilder(result, env).showRunContent(env.getContentToReuse())); + } + + @Override + public String getRunnerId() { + return "AndroidBinaryProgramRunner"; + } +} diff --git a/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationHandlerCompat.java b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationHandlerCompat.java new file mode 100644 index 00000000000..188f6e6b844 --- /dev/null +++ b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationHandlerCompat.java @@ -0,0 +1,38 @@ +/* + * Copyright 2022 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.binary; + +import com.android.tools.idea.execution.common.DeployableToDevice; +import com.google.common.annotations.VisibleForTesting; +import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration; +import com.google.idea.blaze.base.settings.Blaze; +import com.intellij.openapi.project.Project; + +/** Compat class for {@link BlazeAndroidBinaryRunConfigurationHandlerCompat}. */ +public class BlazeAndroidBinaryRunConfigurationHandlerCompat { + protected final Project project; + protected final BlazeAndroidBinaryRunConfigurationState configState; + + @VisibleForTesting + protected BlazeAndroidBinaryRunConfigurationHandlerCompat( + BlazeCommandRunConfiguration configuration) { + project = configuration.getProject(); + configState = + new BlazeAndroidBinaryRunConfigurationState( + Blaze.buildSystemName(configuration.getProject())); + configuration.putUserData(DeployableToDevice.getKEY(), true); + } +} diff --git a/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/binary/DeploymentTimingReporterTask.java b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/binary/DeploymentTimingReporterTask.java new file mode 100644 index 00000000000..bc567e3ff30 --- /dev/null +++ b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/binary/DeploymentTimingReporterTask.java @@ -0,0 +1,60 @@ +/* + * Copyright 2021 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.binary; + +import com.android.tools.idea.execution.common.DeployOptions; +import com.android.tools.idea.run.ApkInfo; +import com.android.tools.idea.run.blaze.BlazeLaunchContext; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; +import com.android.tools.idea.run.tasks.DeployTasksCompat; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Stopwatch; +import com.google.common.collect.ImmutableList; +import com.google.idea.blaze.android.run.LaunchMetrics; +import com.intellij.execution.ExecutionException; +import com.intellij.openapi.project.Project; +import java.util.Collection; + +/** A wrapper launch task that wraps the given deployment task and logs the deployment latency. */ +public class DeploymentTimingReporterTask implements BlazeLaunchTask { + private final BlazeLaunchTask deployTask; + private final String launchId; + private final ImmutableList packages; + + public DeploymentTimingReporterTask( + String launchId, Project project, Collection packages, DeployOptions deployOptions) { + this.launchId = launchId; + this.deployTask = DeployTasksCompat.createDeployTask(project, packages, deployOptions); + this.packages = ImmutableList.copyOf(packages); + } + + @VisibleForTesting + public ImmutableList getPackages() { + return packages; + } + + @Override + public void run(BlazeLaunchContext launchContext) throws ExecutionException { + Stopwatch s = Stopwatch.createStarted(); + try { + deployTask.run(launchContext); + LaunchMetrics.logDeploymentTime(launchId, s.elapsed(), true); + } catch (ExecutionException e) { + LaunchMetrics.logDeploymentTime(launchId, s.elapsed(), false); + throw e; + } + } +} diff --git a/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContext.java b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContext.java new file mode 100644 index 00000000000..1d04c7b4e95 --- /dev/null +++ b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContext.java @@ -0,0 +1,134 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.binary.mobileinstall; + +import com.android.ddmlib.IDevice; +import com.android.tools.idea.execution.common.debug.AndroidDebugger; +import com.android.tools.idea.execution.common.debug.AndroidDebuggerState; +import com.android.tools.idea.execution.common.debug.DebugSessionStarter; +import com.android.tools.idea.projectsystem.ApplicationProjectContext; +import com.android.tools.idea.run.ApkProvisionException; +import com.android.tools.idea.run.activity.DefaultStartActivityFlagsProvider; +import com.android.tools.idea.run.activity.StartActivityFlagsProvider; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; +import com.google.idea.blaze.android.run.BazelApplicationProjectContext; +import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryApplicationLaunchTaskProvider; +import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryRunConfigurationState; +import com.google.idea.blaze.android.run.binary.UserIdHelper; +import com.google.idea.blaze.android.run.deployinfo.BlazeAndroidDeployInfo; +import com.google.idea.blaze.android.run.runner.ApkBuildStep; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.Executor; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.ui.ConsoleView; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.project.Project; +import com.intellij.xdebugger.XDebugSession; +import javax.annotation.Nullable; +import kotlin.Unit; +import kotlin.coroutines.EmptyCoroutineContext; +import kotlinx.coroutines.BuildersKt; +import org.jetbrains.android.facet.AndroidFacet; + +/** Run Context for mobile install launches, #api4.0 compat. */ +public class BlazeAndroidBinaryMobileInstallRunContext + extends BlazeAndroidBinaryMobileInstallRunContextBase { + public BlazeAndroidBinaryMobileInstallRunContext( + Project project, + AndroidFacet facet, + RunConfiguration runConfiguration, + ExecutionEnvironment env, + BlazeAndroidBinaryRunConfigurationState configState, + ApkBuildStep buildStep, + String launchId) { + super(project, facet, runConfiguration, env, configState, buildStep, launchId); + } + + @Override + public ApplicationProjectContext getApplicationProjectContext() { + return new BazelApplicationProjectContext(project, getApplicationIdProvider()); + } + + @SuppressWarnings("unchecked") // upstream API + @Override + public BlazeLaunchTask getApplicationLaunchTask( + boolean isDebug, @Nullable Integer userId, String contributorsAmStartOptions) + throws ExecutionException { + + String extraFlags = UserIdHelper.getFlagsFromUserId(userId); + if (!contributorsAmStartOptions.isEmpty()) { + extraFlags += (extraFlags.isEmpty() ? "" : " ") + contributorsAmStartOptions; + } + if (isDebug) { + extraFlags += (extraFlags.isEmpty() ? "" : " ") + "-D"; + } + + final StartActivityFlagsProvider startActivityFlagsProvider = + new DefaultStartActivityFlagsProvider(project, isDebug, extraFlags); + BlazeAndroidDeployInfo deployInfo; + try { + deployInfo = buildStep.getDeployInfo(); + } catch (ApkProvisionException e) { + throw new ExecutionException(e); + } + + return BlazeAndroidBinaryApplicationLaunchTaskProvider.getApplicationLaunchTask( + applicationIdProvider, + deployInfo.getMergedManifest(), + configState, + startActivityFlagsProvider); + } + + @Nullable + @Override + public XDebugSession startDebuggerSession( + AndroidDebugger androidDebugger, + AndroidDebuggerState androidDebuggerState, + ExecutionEnvironment env, + IDevice device, + ConsoleView consoleView, + ProgressIndicator indicator, + String packageName) { + try { + return BuildersKt.runBlocking( + EmptyCoroutineContext.INSTANCE, + (scope, continuation) -> + DebugSessionStarter.INSTANCE.attachDebuggerToStartedProcess( + device, + new BazelApplicationProjectContext(project, packageName), + env, + androidDebugger, + androidDebuggerState, + /*destroyRunningProcess*/ d -> { + d.forceStop(packageName); + return Unit.INSTANCE; + }, + indicator, + consoleView, + 15L, + continuation)); + } catch (InterruptedException e) { + throw new ProcessCanceledException(e); + } + } + + @Override + public Executor getExecutor() { + return env.getExecutor(); + } +} diff --git a/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContextBase.java b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContextBase.java new file mode 100644 index 00000000000..967e5926fb0 --- /dev/null +++ b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContextBase.java @@ -0,0 +1,164 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.binary.mobileinstall; + +import com.android.ddmlib.IDevice; +import com.android.tools.idea.execution.common.DeployOptions; +import com.android.tools.idea.run.ApkFileUnit; +import com.android.tools.idea.run.ApkInfo; +import com.android.tools.idea.run.ApkProvisionException; +import com.android.tools.idea.run.ApplicationIdProvider; +import com.android.tools.idea.run.ConsoleProvider; +import com.android.tools.idea.run.LaunchOptions; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; +import com.android.tools.idea.run.blaze.BlazeLaunchTasksProvider; +import com.android.tools.idea.run.editor.ProfilerState; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryApplicationIdProvider; +import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryConsoleProvider; +import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryRunConfigurationState; +import com.google.idea.blaze.android.run.binary.DeploymentTimingReporterTask; +import com.google.idea.blaze.android.run.binary.UserIdHelper; +import com.google.idea.blaze.android.run.deployinfo.BlazeAndroidDeployInfo; +import com.google.idea.blaze.android.run.runner.ApkBuildStep; +import com.google.idea.blaze.android.run.runner.BlazeAndroidDeviceSelector; +import com.google.idea.blaze.android.run.runner.BlazeAndroidLaunchTasksProvider; +import com.google.idea.blaze.android.run.runner.BlazeAndroidRunContext; +import com.google.idea.blaze.base.sync.data.BlazeDataStorage; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.openapi.project.Project; +import java.util.Collections; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import org.jetbrains.android.facet.AndroidFacet; + +/** Run context for android_binary. */ +abstract class BlazeAndroidBinaryMobileInstallRunContextBase implements BlazeAndroidRunContext { + protected final Project project; + protected final AndroidFacet facet; + protected final RunConfiguration runConfiguration; + protected final ExecutionEnvironment env; + protected final BlazeAndroidBinaryRunConfigurationState configState; + protected final ConsoleProvider consoleProvider; + protected final ApplicationIdProvider applicationIdProvider; + protected final ApkBuildStep buildStep; + private final String launchId; + + public BlazeAndroidBinaryMobileInstallRunContextBase( + Project project, + AndroidFacet facet, + RunConfiguration runConfiguration, + ExecutionEnvironment env, + BlazeAndroidBinaryRunConfigurationState configState, + ApkBuildStep buildStep, + String launchId) { + this.project = project; + this.facet = facet; + this.runConfiguration = runConfiguration; + this.env = env; + this.configState = configState; + this.consoleProvider = new BlazeAndroidBinaryConsoleProvider(project); + this.buildStep = buildStep; + this.applicationIdProvider = new BlazeAndroidBinaryApplicationIdProvider(buildStep); + this.launchId = launchId; + } + + @Override + public BlazeAndroidDeviceSelector getDeviceSelector() { + return new BlazeAndroidDeviceSelector.NormalDeviceSelector(); + } + + @Override + public void augmentLaunchOptions(LaunchOptions.Builder options) { + options + .setDeploy(buildStep.needsIdeDeploy()) + .setOpenLogcatAutomatically(configState.showLogcatAutomatically()); + // This is needed for compatibility with #api211 + options.addExtraOptions( + ImmutableMap.of("android.profilers.state", configState.getProfilerState())); + } + + @Override + public ConsoleProvider getConsoleProvider() { + return consoleProvider; + } + + @Override + public ApplicationIdProvider getApplicationIdProvider() { + return applicationIdProvider; + } + + @Override + public ApkBuildStep getBuildStep() { + return buildStep; + } + + @Override + public ProfilerState getProfileState() { + return configState.getProfilerState(); + } + + @Override + public ImmutableList getDeployTasks(IDevice device, DeployOptions deployOptions) + throws ExecutionException { + if (!buildStep.needsIdeDeploy()) { + return ImmutableList.of(); + } + + BlazeAndroidDeployInfo deployInfo; + try { + deployInfo = buildStep.getDeployInfo(); + } catch (ApkProvisionException e) { + throw new ExecutionException(e); + } + + String packageName = deployInfo.getMergedManifest().packageName; + if (packageName == null) { + throw new ExecutionException("Could not determine package name from deploy info"); + } + + ApkInfo info = + new ApkInfo( + deployInfo.getApksToDeploy().stream() + .map(file -> new ApkFileUnit(BlazeDataStorage.WORKSPACE_MODULE_NAME, file)) + .collect(Collectors.toList()), + packageName); + + return ImmutableList.of( + new DeploymentTimingReporterTask( + launchId, project, Collections.singletonList(info), deployOptions)); + } + + @Nullable + @Override + public Integer getUserId(IDevice device) throws ExecutionException { + return UserIdHelper.getUserIdFromConfigurationState(project, device, configState); + } + + @Override + public BlazeLaunchTasksProvider getLaunchTasksProvider(LaunchOptions launchOptions) + throws ExecutionException { + return new BlazeAndroidLaunchTasksProvider(project, this, applicationIdProvider, launchOptions); + } + + @Override + public String getAmStartOptions() { + return configState.getAmStartOptions(); + } +} diff --git a/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContextCompat.java b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContextCompat.java new file mode 100644 index 00000000000..ca395950614 --- /dev/null +++ b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContextCompat.java @@ -0,0 +1,85 @@ +/* + * Copyright 2022 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.mobileinstall; + +import com.android.ddmlib.IDevice; +import com.android.tools.idea.execution.common.debug.AndroidDebugger; +import com.android.tools.idea.execution.common.debug.AndroidDebuggerState; +import com.android.tools.idea.execution.common.debug.DebugSessionStarter; +import com.google.idea.blaze.android.run.BazelApplicationProjectContext; +import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryRunConfigurationState; +import com.google.idea.blaze.android.run.runner.ApkBuildStep; +import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.ui.ConsoleView; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.project.Project; +import com.intellij.xdebugger.XDebugSession; +import javax.annotation.Nullable; +import kotlin.Unit; +import kotlin.coroutines.EmptyCoroutineContext; +import kotlinx.coroutines.BuildersKt; +import org.jetbrains.android.facet.AndroidFacet; + +/** Compatct class for {@link BlazeAndroidBinaryMobileInstallRunContext}. */ +public class BlazeAndroidBinaryMobileInstallRunContextCompat + extends BlazeAndroidBinaryMobileInstallRunContext { + + public BlazeAndroidBinaryMobileInstallRunContextCompat( + Project project, + AndroidFacet facet, + BlazeCommandRunConfiguration configuration, + ExecutionEnvironment env, + BlazeAndroidBinaryRunConfigurationState configState, + ApkBuildStep buildStep, + String launchId) { + super(project, facet, configuration, env, configState, buildStep, launchId); + } + + @Nullable + @Override + public XDebugSession startDebuggerSession( + AndroidDebugger androidDebugger, + AndroidDebuggerState androidDebuggerState, + ExecutionEnvironment env, + IDevice device, + ConsoleView consoleView, + ProgressIndicator indicator, + String packageName) { + try { + return BuildersKt.runBlocking( + EmptyCoroutineContext.INSTANCE, + (scope, continuation) -> + DebugSessionStarter.INSTANCE.attachDebuggerToStartedProcess( + device, + new BazelApplicationProjectContext(project, packageName), + env, + androidDebugger, + androidDebuggerState, + /*destroyRunningProcess*/ d -> { + d.forceStop(packageName); + return Unit.INSTANCE; + }, + indicator, + consoleView, + 15L, + continuation)); + } catch (InterruptedException e) { + throw new ProcessCanceledException(e); + } + } +} diff --git a/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/runner/BlazeAndroidDebuggerService.java b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/runner/BlazeAndroidDebuggerService.java new file mode 100644 index 00000000000..5715392a171 --- /dev/null +++ b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/runner/BlazeAndroidDebuggerService.java @@ -0,0 +1,137 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.runner; + +import com.android.tools.idea.execution.common.debug.AndroidDebugger; +import com.android.tools.idea.execution.common.debug.AndroidDebuggerState; +import com.android.tools.idea.execution.common.debug.impl.java.AndroidJavaDebugger; +import com.android.tools.ndk.run.editor.AutoAndroidDebuggerState; +import com.android.tools.ndk.run.editor.NativeAndroidDebuggerState; +import com.google.common.collect.ImmutableList; +import com.google.idea.blaze.android.cppimpl.debug.BlazeAutoAndroidDebugger; +import com.google.idea.blaze.android.run.deployinfo.BlazeAndroidDeployInfo; +import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; +import com.intellij.ide.plugins.PluginManagerCore; +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.project.Project; +import javax.annotation.Nullable; + +/** Provides android debuggers and debugger states for blaze projects. */ +public interface BlazeAndroidDebuggerService { + + static BlazeAndroidDebuggerService getInstance(Project project) { + return ServiceManager.getService(project, BlazeAndroidDebuggerService.class); + } + + /** Returns a different debugger depending on whether or not native debugging is required. */ + AndroidDebugger getDebugger(boolean isNativeDebuggingEnabled); + + /** + * Returns fully initialized debugger states. + * + *

Note: Blaze projects should always use this method instead of the debuggers' {@link + * AndroidDebugger#createState()} method. Blaze projects require additional setup such as + * workspace directory flags that cannot be handled by the debuggers themselves. + */ + AndroidDebuggerState getDebuggerState(AndroidDebugger debugger); + + void configureNativeDebugger( + AndroidDebuggerState state, @Nullable BlazeAndroidDeployInfo deployInfo); + + /** Default debugger service. */ + class DefaultDebuggerService implements BlazeAndroidDebuggerService { + private final Project project; + + public DefaultDebuggerService(Project project) { + this.project = project; + } + + @Override + public AndroidDebugger getDebugger(boolean isNativeDebuggingEnabled) { + return isNativeDebuggingEnabled ? new BlazeAutoAndroidDebugger() : new AndroidJavaDebugger(); + } + + @Override + public AndroidDebuggerState getDebuggerState(AndroidDebugger debugger) { + AndroidDebuggerState debuggerState = debugger.createState(); + if (isNdkPluginLoaded() && debuggerState instanceof NativeAndroidDebuggerState) { + NativeAndroidDebuggerState nativeState = (NativeAndroidDebuggerState) debuggerState; + + // Source code is always relative to the workspace root in a blaze project. + String workingDirPath = WorkspaceRoot.fromProject(project).directory().getPath(); + nativeState.setWorkingDir(workingDirPath); + + // Remote built binaries may use /proc/self/cwd to represent the working directory + // so we manually map /proc/self/cwd to the workspace root. We used to use + // `plugin.symbol-file.dwarf.comp-dir-symlink-paths = "/proc/self/cwd"` + // to automatically resolve this but it's no longer supported in newer versions of + // LLDB. + String sourceMapToWorkspaceRootCommand = + "settings append target.source-map /proc/self/cwd/ " + workingDirPath; + ImmutableList startupCommands = + ImmutableList.builder() + .addAll(nativeState.getUserStartupCommands()) + .add(sourceMapToWorkspaceRootCommand) + .build(); + nativeState.setUserStartupCommands(startupCommands); + } + return debuggerState; + } + + @Override + public void configureNativeDebugger( + AndroidDebuggerState rawState, @Nullable BlazeAndroidDeployInfo deployInfo) { + if (!isNdkPluginLoaded() && !(rawState instanceof AutoAndroidDebuggerState)) { + return; + } + AutoAndroidDebuggerState state = (AutoAndroidDebuggerState) rawState; + + // Source code is always relative to the workspace root in a blaze project. + String workingDirPath = WorkspaceRoot.fromProject(project).directory().getPath(); + state.setWorkingDir(workingDirPath); + + // Remote built binaries may use /proc/self/cwd to represent the working directory, + // so we manually map /proc/self/cwd to the workspace root. We used to use + // `plugin.symbol-file.dwarf.comp-dir-symlink-paths = "/proc/self/cwd"` + // to automatically resolve this, but it's no longer supported in newer versions of + // LLDB. + String sourceMapToWorkspaceRootCommand = + "settings append target.source-map /proc/self/cwd/ " + workingDirPath; + + ImmutableList startupCommands = + ImmutableList.builder() + .addAll(state.getUserStartupCommands()) + .add(sourceMapToWorkspaceRootCommand) + .build(); + state.setUserStartupCommands(startupCommands); + + // NDK plugin will pass symbol directories to LLDB as `settings append + // target.exec-search-paths`. + if (deployInfo != null) { + state.setSymbolDirs( + deployInfo.getSymbolFiles().stream() + .map(symbol -> symbol.getParentFile().getAbsolutePath()) + .collect(ImmutableList.toImmutableList())); + } + } + } + + static boolean isNdkPluginLoaded() { + return PluginManagerCore.getLoadedPlugins().stream() + .anyMatch( + d -> d.isEnabled() && d.getPluginId().getIdString().equals("com.android.tools.ndk")); + } +} diff --git a/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/runner/BlazeAndroidLaunchTasksProvider.java b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/runner/BlazeAndroidLaunchTasksProvider.java new file mode 100644 index 00000000000..a1528a9c4d9 --- /dev/null +++ b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/runner/BlazeAndroidLaunchTasksProvider.java @@ -0,0 +1,177 @@ +/* + * Copyright 2022 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.runner; + +import static com.android.tools.idea.profilers.AndroidProfilerLaunchTaskContributor.isProfilerLaunch; + +import com.android.ddmlib.IDevice; +import com.android.tools.deployer.ApkVerifierTracker; +import com.android.tools.idea.execution.common.DeployOptions; +import com.android.tools.idea.execution.common.debug.AndroidDebugger; +import com.android.tools.idea.execution.common.debug.AndroidDebuggerState; +import com.android.tools.idea.profilers.AndroidProfilerLaunchTaskContributor; +import com.android.tools.idea.run.ApkProvisionException; +import com.android.tools.idea.run.ApplicationIdProvider; +import com.android.tools.idea.run.LaunchOptions; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; +import com.android.tools.idea.run.blaze.BlazeLaunchTasksProvider; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.idea.blaze.android.run.binary.UserIdHelper; +import com.google.idea.blaze.android.run.deployinfo.BlazeAndroidDeployInfo; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.ui.ConsoleView; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.project.Project; +import com.intellij.xdebugger.XDebugSession; +import java.util.Collections; +import java.util.List; +import org.jetbrains.annotations.NotNull; + +/** Normal launch tasks provider. #api4.1 */ +public class BlazeAndroidLaunchTasksProvider implements BlazeLaunchTasksProvider { + public static final String NATIVE_DEBUGGING_ENABLED = "NATIVE_DEBUGGING_ENABLED"; + private static final Logger LOG = Logger.getInstance(BlazeAndroidLaunchTasksProvider.class); + + private final Project project; + private final BlazeAndroidRunContext runContext; + private final ApplicationIdProvider applicationIdProvider; + private final LaunchOptions launchOptions; + + public BlazeAndroidLaunchTasksProvider( + Project project, + BlazeAndroidRunContext runContext, + ApplicationIdProvider applicationIdProvider, + LaunchOptions launchOptions) { + this.project = project; + this.runContext = runContext; + this.applicationIdProvider = applicationIdProvider; + this.launchOptions = launchOptions; + } + + @NotNull + @Override + public List getTasks(@NotNull IDevice device, boolean isDebug) + throws ExecutionException { + final List launchTasks = Lists.newArrayList(); + + String packageName; + try { + packageName = applicationIdProvider.getPackageName(); + } catch (ApkProvisionException e) { + throw new ExecutionException("Unable to determine application id: " + e); + } + + Integer userId = runContext.getUserId(device); + + // NOTE: Task for opening the profiler tool-window should come before deployment + // to ensure the tool-window opens correctly. This is required because starting + // the profiler session requires the tool-window to be open. + if (isProfilerLaunch(runContext.getExecutor())) { + launchTasks.add(new BlazeAndroidOpenProfilerWindowTask(project)); + } + + if (launchOptions.isDeploy()) { + String userIdFlags = UserIdHelper.getFlagsFromUserId(userId); + String skipVerification = + ApkVerifierTracker.getSkipVerificationInstallationFlag(device, packageName); + String pmInstallOption; + if (skipVerification != null) { + pmInstallOption = userIdFlags + " " + skipVerification; + } else { + pmInstallOption = userIdFlags; + } + DeployOptions deployOptions = + new DeployOptions(Collections.emptyList(), pmInstallOption, false, false); + ImmutableList deployTasks = runContext.getDeployTasks(device, deployOptions); + launchTasks.addAll(deployTasks); + } + + try { + if (isDebug) { + launchTasks.add( + new CheckApkDebuggableTask(project, runContext.getBuildStep().getDeployInfo())); + } + + ImmutableList.Builder amStartOptions = ImmutableList.builder(); + amStartOptions.add(runContext.getAmStartOptions()); + if (isProfilerLaunch(runContext.getExecutor())) { + amStartOptions.add( + AndroidProfilerLaunchTaskContributor.getAmStartOptions( + project, + packageName, + runContext.getProfileState(), + device, + runContext.getExecutor())); + } + BlazeLaunchTask appLaunchTask = + runContext.getApplicationLaunchTask( + isDebug, userId, String.join(" ", amStartOptions.build())); + if (appLaunchTask != null) { + launchTasks.add(appLaunchTask); + // TODO(arvindanekal): the live edit api changed and we cannot get the apk here to create + // live + // edit; the live edit team or Arvind need to fix this + } + } catch (ApkProvisionException e) { + throw new ExecutionException("Unable to determine application id: " + e); + } + + return ImmutableList.copyOf(launchTasks); + } + + @NotNull + @Override + public XDebugSession startDebugSession( + @NotNull ExecutionEnvironment environment, + @NotNull IDevice device, + @NotNull ConsoleView console, + @NotNull ProgressIndicator indicator, + @NotNull String packageName) + throws ExecutionException { + // Do not get debugger state directly from the debugger itself. + // See BlazeAndroidDebuggerService#getDebuggerState for an explanation. + boolean isNativeDebuggingEnabled = isNativeDebuggingEnabled(launchOptions); + BlazeAndroidDebuggerService debuggerService = BlazeAndroidDebuggerService.getInstance(project); + AndroidDebugger debugger = debuggerService.getDebugger(isNativeDebuggingEnabled); + if (debugger == null) { + throw new ExecutionException("Can't find AndroidDebugger for launch"); + } + AndroidDebuggerState debuggerState = debuggerService.getDebuggerState(debugger); + if (debuggerState == null) { + throw new ExecutionException("Can't find AndroidDebuggerState for launch"); + } + if (isNativeDebuggingEnabled) { + BlazeAndroidDeployInfo deployInfo = null; + try { + deployInfo = runContext.getBuildStep().getDeployInfo(); + } catch (ApkProvisionException e) { + LOG.error(e); + } + debuggerService.configureNativeDebugger(debuggerState, deployInfo); + } + + return runContext.startDebuggerSession( + debugger, debuggerState, environment, device, console, indicator, packageName); + } + + private boolean isNativeDebuggingEnabled(LaunchOptions launchOptions) { + Object flag = launchOptions.getExtraOption(NATIVE_DEBUGGING_ENABLED); + return flag instanceof Boolean && (Boolean) flag; + } +} diff --git a/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/runner/BlazeAndroidRunConfigurationRunner.java b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/runner/BlazeAndroidRunConfigurationRunner.java new file mode 100644 index 00000000000..364a7f9a1ca --- /dev/null +++ b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/runner/BlazeAndroidRunConfigurationRunner.java @@ -0,0 +1,340 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.runner; + +import com.android.ddmlib.IDevice; +import com.android.tools.idea.editors.liveedit.LiveEditService; +import com.android.tools.idea.execution.common.AndroidConfigurationExecutor; +import com.android.tools.idea.execution.common.AndroidConfigurationExecutorRunProfileState; +import com.android.tools.idea.execution.common.AppRunSettings; +import com.android.tools.idea.execution.common.ApplicationDeployer; +import com.android.tools.idea.execution.common.ComponentLaunchOptions; +import com.android.tools.idea.execution.common.DeployOptions; +import com.android.tools.idea.execution.common.stats.RunStats; +import com.android.tools.idea.projectsystem.ApplicationProjectContext; +import com.android.tools.idea.run.ApkProvider; +import com.android.tools.idea.run.ApplicationIdProvider; +import com.android.tools.idea.run.DeviceFutures; +import com.android.tools.idea.run.LaunchOptions; +import com.android.tools.idea.run.blaze.BlazeAndroidConfigurationExecutor; +import com.android.tools.idea.run.configuration.execution.AndroidComplicationConfigurationExecutor; +import com.android.tools.idea.run.configuration.execution.AndroidTileConfigurationExecutor; +import com.android.tools.idea.run.configuration.execution.AndroidWatchFaceConfigurationExecutor; +import com.android.tools.idea.run.configuration.execution.ApplicationDeployerImpl; +import com.android.tools.idea.run.configuration.execution.ComplicationLaunchOptions; +import com.android.tools.idea.run.configuration.execution.TileLaunchOptions; +import com.android.tools.idea.run.configuration.execution.WatchFaceLaunchOptions; +import com.android.tools.idea.run.editor.DeployTarget; +import com.android.tools.idea.run.editor.DeployTargetState; +import com.android.tools.idea.run.util.LaunchUtils; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryRunConfigurationState; +import com.google.idea.blaze.android.run.binary.mobileinstall.MobileInstallBuildStep; +import com.google.idea.blaze.android.run.deployinfo.BlazeApkProviderService; +import com.google.idea.blaze.base.async.executor.ProgressiveTaskWithProgressIndicator; +import com.google.idea.blaze.base.command.BlazeInvocationContext.ContextType; +import com.google.idea.blaze.base.experiments.ExperimentScope; +import com.google.idea.blaze.base.issueparser.BlazeIssueParser; +import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; +import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration; +import com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationRunner; +import com.google.idea.blaze.base.run.state.RunConfigurationState; +import com.google.idea.blaze.base.scope.BlazeContext; +import com.google.idea.blaze.base.scope.Scope; +import com.google.idea.blaze.base.scope.ScopedTask; +import com.google.idea.blaze.base.scope.output.IssueOutput; +import com.google.idea.blaze.base.scope.scopes.IdeaLogScope; +import com.google.idea.blaze.base.scope.scopes.ProblemsViewScope; +import com.google.idea.blaze.base.scope.scopes.ToolWindowScope; +import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.settings.BlazeUserSettings; +import com.google.idea.blaze.base.toolwindow.Task; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.Executor; +import com.intellij.execution.configurations.RunProfileState; +import com.intellij.execution.executors.DefaultDebugExecutor; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Key; +import java.util.Collections; +import java.util.concurrent.CancellationException; +import javax.annotation.Nullable; +import org.jetbrains.android.facet.AndroidFacet; +import org.jetbrains.android.util.AndroidBundle; +import org.jetbrains.annotations.NotNull; + +/** + * Supports the execution. Used by both android_binary and android_test. + * + *

Builds the APK and installs it, launches and debug tasks, etc. + * + *

Any indirection between android_binary/android_test, mobile-install, InstantRun etc. should + * come via the strategy class. + */ +public final class BlazeAndroidRunConfigurationRunner + implements BlazeCommandRunConfigurationRunner { + + private static final Logger LOG = Logger.getInstance(BlazeAndroidRunConfigurationRunner.class); + + private static final Key RUN_CONTEXT_KEY = + Key.create("blaze.run.context"); + public static final Key DEVICE_SESSION_KEY = + Key.create("blaze.device.session"); + + private final Module module; + private final BlazeAndroidRunContext runContext; + private final BlazeCommandRunConfiguration runConfig; + + public BlazeAndroidRunConfigurationRunner( + Module module, BlazeAndroidRunContext runContext, BlazeCommandRunConfiguration runConfig) { + this.module = module; + this.runContext = runContext; + this.runConfig = runConfig; + } + + @Override + @Nullable + public final RunProfileState getRunProfileState(final Executor executor, ExecutionEnvironment env) + throws ExecutionException { + + final AndroidFacet facet = AndroidFacet.getInstance(module); + assert facet != null : "Enforced by fatal validation check in createRunner."; + final Project project = env.getProject(); + + boolean isDebug = executor instanceof DefaultDebugExecutor; + + BlazeAndroidDeviceSelector deviceSelector = runContext.getDeviceSelector(); + BlazeAndroidDeviceSelector.DeviceSession deviceSession = + deviceSelector.getDevice(project, executor, env, isDebug, runConfig.getUniqueID()); + if (deviceSession == null) { + return null; + } + + DeployTarget deployTarget = deviceSession.deployTarget; + if (deployTarget != null && deployTarget.hasCustomRunProfileState(executor)) { + return deployTarget.getRunProfileState(executor, env, DeployTargetState.DEFAULT_STATE); + } + + DeviceFutures deviceFutures = deviceSession.deviceFutures; + if (deviceFutures == null) { + // The user deliberately canceled, or some error was encountered and exposed by the chooser. + // Quietly exit. + return null; + } + + if (deviceFutures.get().isEmpty()) { + throw new ExecutionException(AndroidBundle.message("deployment.target.not.found")); + } + + if (isDebug) { + String error = canDebug(deviceFutures, facet, module.getName()); + if (error != null) { + throw new ExecutionException(error); + } + } + + LaunchOptions.Builder launchOptionsBuilder = getDefaultLaunchOptions(); + runContext.augmentLaunchOptions(launchOptionsBuilder); + + // Store the run context on the execution environment so before-run tasks can access it. + env.putCopyableUserData(RUN_CONTEXT_KEY, runContext); + env.putCopyableUserData(DEVICE_SESSION_KEY, deviceSession); + + RunConfigurationState state = runConfig.getHandler().getState(); + + if (state instanceof BlazeAndroidBinaryRunConfigurationState + && ((BlazeAndroidBinaryRunConfigurationState) state).getCurrentWearLaunchOptions() + != null) { + ComponentLaunchOptions launchOptions = + ((BlazeAndroidBinaryRunConfigurationState) state).getCurrentWearLaunchOptions(); + + return getWearExecutor(launchOptions, env, deployTarget); + } + + ApkProvider apkProvider = + BlazeApkProviderService.getInstance() + .getApkProvider(env.getProject(), runContext.getBuildStep()); + final LaunchOptions launchOptions = launchOptionsBuilder.build(); + BlazeAndroidConfigurationExecutor runner = + new BlazeAndroidConfigurationExecutor( + runContext.getConsoleProvider(), + runContext.getApplicationIdProvider(), + env, + deviceFutures, + runContext.getLaunchTasksProvider(launchOptions), + launchOptions, + apkProvider, + LiveEditService.getInstance(env.getProject())); + return new AndroidConfigurationExecutorRunProfileState(runner); + } + + private RunProfileState getWearExecutor( + ComponentLaunchOptions launchOptions, ExecutionEnvironment env, DeployTarget deployTarget) + throws ExecutionException { + + AppRunSettings settings = + new AppRunSettings() { + @NotNull + @Override + public DeployOptions getDeployOptions() { + return new DeployOptions(Collections.emptyList(), "", true, true); + } + + @NotNull + @Override + public ComponentLaunchOptions getComponentLaunchOptions() { + return launchOptions; + } + + @Override + public Module getModule() { + return runConfig.getModules()[0]; + } + }; + + AndroidConfigurationExecutor configurationExecutor; + ApplicationIdProvider appIdProvider = runContext.getApplicationIdProvider(); + ApplicationProjectContext applicationProjectContext = runContext.getApplicationProjectContext(); + ApkProvider apkProvider = + BlazeApkProviderService.getInstance() + .getApkProvider(env.getProject(), runContext.getBuildStep()); + DeviceFutures deviceFutures = deployTarget.getDevices(env.getProject()); + + ApplicationDeployer deployer = + runContext.getBuildStep() instanceof MobileInstallBuildStep + ? new MobileInstallApplicationDeployer() + : new ApplicationDeployerImpl(env.getProject(), RunStats.from(env)); + + if (launchOptions instanceof TileLaunchOptions) { + configurationExecutor = + new AndroidTileConfigurationExecutor( + env, + deviceFutures, + settings, + appIdProvider, + apkProvider, + applicationProjectContext, + deployer); + } else if (launchOptions instanceof WatchFaceLaunchOptions) { + configurationExecutor = + new AndroidWatchFaceConfigurationExecutor( + env, + deviceFutures, + settings, + appIdProvider, + apkProvider, + applicationProjectContext, + deployer); + } else if (launchOptions instanceof ComplicationLaunchOptions) { + configurationExecutor = + new AndroidComplicationConfigurationExecutor( + env, + deviceFutures, + settings, + appIdProvider, + apkProvider, + applicationProjectContext, + deployer); + } else { + throw new RuntimeException("Unknown launch options " + launchOptions.getClass().getName()); + } + + return new AndroidConfigurationExecutorRunProfileState(configurationExecutor); + } + + private static String canDebug( + DeviceFutures deviceFutures, AndroidFacet facet, String moduleName) { + // If we are debugging on a device, then the app needs to be debuggable + for (ListenableFuture future : deviceFutures.get()) { + if (!future.isDone()) { + // this is an emulator, and we assume that all emulators are debuggable + continue; + } + IDevice device = Futures.getUnchecked(future); + if (!LaunchUtils.canDebugAppOnDevice(facet, device)) { + return AndroidBundle.message( + "android.cannot.debug.noDebugPermissions", moduleName, device.getName()); + } + } + return null; + } + + private static LaunchOptions.Builder getDefaultLaunchOptions() { + return LaunchOptions.builder(); + } + + @Override + public boolean executeBeforeRunTask(ExecutionEnvironment env) { + final Project project = env.getProject(); + BlazeUserSettings settings = BlazeUserSettings.getInstance(); + return Scope.root( + context -> { + context + .push(new ProblemsViewScope(project, settings.getShowProblemsViewOnRun())) + .push(new ExperimentScope()) + .push( + new ToolWindowScope.Builder( + project, new Task(project, "Build apk", Task.Type.BEFORE_LAUNCH)) + .setPopupBehavior(settings.getShowBlazeConsoleOnRun()) + .setIssueParsers( + BlazeIssueParser.defaultIssueParsers( + project, + WorkspaceRoot.fromProject(project), + ContextType.BeforeRunTask)) + .build()) + .push(new IdeaLogScope()); + + BlazeAndroidRunContext runContext = env.getCopyableUserData(RUN_CONTEXT_KEY); + if (runContext == null) { + IssueOutput.error("Could not find run context. Please try again").submit(context); + return false; + } + BlazeAndroidDeviceSelector.DeviceSession deviceSession = + env.getCopyableUserData(DEVICE_SESSION_KEY); + + ApkBuildStep buildStep = runContext.getBuildStep(); + ScopedTask buildTask = + new ScopedTask(context) { + @Override + protected Void execute(BlazeContext context) { + buildStep.build(context, deviceSession); + return null; + } + }; + + try { + ListenableFuture buildFuture = + ProgressiveTaskWithProgressIndicator.builder( + project, + String.format("Executing %s apk build", Blaze.buildSystemName(project))) + .submitTaskWithResult(buildTask); + Futures.getChecked(buildFuture, ExecutionException.class); + } catch (ExecutionException e) { + context.setHasError(); + } catch (CancellationException e) { + context.setCancelled(); + } catch (Exception e) { + LOG.error(e); + return false; + } + return context.shouldContinue(); + }); + } +} diff --git a/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/runner/BlazeAndroidRunContext.java b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/runner/BlazeAndroidRunContext.java new file mode 100644 index 00000000000..468f24a441d --- /dev/null +++ b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/runner/BlazeAndroidRunContext.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.runner; + +import com.android.ddmlib.IDevice; +import com.android.tools.idea.execution.common.DeployOptions; +import com.android.tools.idea.execution.common.debug.AndroidDebugger; +import com.android.tools.idea.execution.common.debug.AndroidDebuggerState; +import com.android.tools.idea.projectsystem.ApplicationProjectContext; +import com.android.tools.idea.run.ApplicationIdProvider; +import com.android.tools.idea.run.ConsoleProvider; +import com.android.tools.idea.run.LaunchOptions; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; +import com.android.tools.idea.run.blaze.BlazeLaunchTasksProvider; +import com.android.tools.idea.run.editor.ProfilerState; +import com.google.common.collect.ImmutableList; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.Executor; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.ui.ConsoleView; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.xdebugger.XDebugSession; +import javax.annotation.Nullable; +import org.jetbrains.annotations.NotNull; + +/** Instantiated when the configuration wants to run. */ +public interface BlazeAndroidRunContext { + + BlazeAndroidDeviceSelector getDeviceSelector(); + + void augmentLaunchOptions(LaunchOptions.Builder options); + + ConsoleProvider getConsoleProvider(); + + ApkBuildStep getBuildStep(); + + ApplicationIdProvider getApplicationIdProvider(); + + ApplicationProjectContext getApplicationProjectContext(); + + BlazeLaunchTasksProvider getLaunchTasksProvider(LaunchOptions launchOptions) + throws ExecutionException; + + /** Returns the tasks to deploy the application. */ + ImmutableList getDeployTasks(IDevice device, DeployOptions deployOptions) + throws ExecutionException; + + /** Returns the task to launch the application. */ + @Nullable + BlazeLaunchTask getApplicationLaunchTask( + boolean isDebug, @Nullable Integer userId, @NotNull String contributorsAmStartOptions) + throws ExecutionException; + + /** Returns the task to connect the debugger. */ + @Nullable + XDebugSession startDebuggerSession( + AndroidDebugger androidDebugger, + AndroidDebuggerState androidDebuggerState, + ExecutionEnvironment env, + IDevice device, + ConsoleView consoleView, + ProgressIndicator indicator, + String packageName); + + @Nullable + Integer getUserId(IDevice device) throws ExecutionException; + + String getAmStartOptions(); + + Executor getExecutor(); + + ProfilerState getProfileState(); +} diff --git a/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/runner/LaunchOptionsCompat.java b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/runner/LaunchOptionsCompat.java new file mode 100644 index 00000000000..f31959514c5 --- /dev/null +++ b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/runner/LaunchOptionsCompat.java @@ -0,0 +1,29 @@ +/* + * Copyright 2022 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.runner; + +import com.android.tools.idea.run.LaunchOptions; + +/** Compat class for LunchOptions.Builder. */ +public class LaunchOptionsCompat { + + private LaunchOptionsCompat() {} + + /** Create default launch options to maintain compatibility with #api211. */ + public static LaunchOptions.Builder getDefaultLaunchOptions() { + return LaunchOptions.builder(); + } +} diff --git a/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/runner/MobileInstallApplicationDeployer.java b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/runner/MobileInstallApplicationDeployer.java new file mode 100644 index 00000000000..541cf750686 --- /dev/null +++ b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/runner/MobileInstallApplicationDeployer.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.runner; + +import static com.android.tools.idea.run.tasks.AbstractDeployTask.getAppToInstall; + +import com.android.ddmlib.IDevice; +import com.android.tools.deployer.Deployer; +import com.android.tools.deployer.DeployerException; +import com.android.tools.deployer.model.App; +import com.android.tools.idea.execution.common.ApplicationDeployer; +import com.android.tools.idea.execution.common.DeployOptions; +import com.android.tools.idea.run.ApkInfo; +import com.intellij.openapi.progress.ProgressIndicator; +import org.jetbrains.annotations.NotNull; + +/** Deploys mobile install application. */ +public class MobileInstallApplicationDeployer implements ApplicationDeployer { + + public MobileInstallApplicationDeployer() {} + + @NotNull + @Override + public Deployer.Result fullDeploy( + @NotNull IDevice device, + @NotNull ApkInfo apkInfo, + @NotNull DeployOptions deployOptions, + ProgressIndicator indicator) { + App app = getAppToInstall(apkInfo); + return new Deployer.Result(false, false, false, app); + } + + @NotNull + @Override + public Deployer.Result applyChangesDeploy( + @NotNull IDevice device, + @NotNull ApkInfo app, + @NotNull DeployOptions deployOptions, + ProgressIndicator indicator) { + throw new RuntimeException("Apply changes is not supported for mobile-install"); + } + + @NotNull + @Override + public Deployer.Result applyCodeChangesDeploy( + @NotNull IDevice device, + @NotNull ApkInfo app, + @NotNull DeployOptions deployOptions, + ProgressIndicator indicator) + throws DeployerException { + throw new RuntimeException("Apply code changes is not supported for mobile-install"); + } +} diff --git a/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/test/BlazeAndroidTestProgramRunner.java b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/test/BlazeAndroidTestProgramRunner.java new file mode 100644 index 00000000000..b294b565455 --- /dev/null +++ b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/test/BlazeAndroidTestProgramRunner.java @@ -0,0 +1,77 @@ +/* + * Copyright 2018 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.test; + +import com.android.tools.idea.execution.common.AndroidConfigurationExecutor; +import com.android.tools.idea.execution.common.AndroidConfigurationProgramRunner; +import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationHandler; +import com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.configurations.RunProfile; +import com.intellij.execution.executors.DefaultDebugExecutor; +import com.intellij.execution.executors.DefaultRunExecutor; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.ui.RunContentDescriptor; +import com.intellij.openapi.progress.ProgressIndicator; +import java.util.Collections; +import java.util.List; +import org.jetbrains.annotations.NotNull; + +/** Program runner for configurations from {@link BlazeAndroidTestRunConfigurationHandler}. */ +public class BlazeAndroidTestProgramRunner extends AndroidConfigurationProgramRunner { + @Override + public boolean canRun(String executorId, RunProfile profile) { + BlazeAndroidRunConfigurationHandler handler = + BlazeAndroidRunConfigurationHandler.getHandlerFrom(profile); + if (!(handler instanceof BlazeAndroidTestRunConfigurationHandler)) { + return false; + } + return super.canRun(executorId, profile) && DefaultRunExecutor.EXECUTOR_ID.equals(executorId) + || DefaultDebugExecutor.EXECUTOR_ID.equals(executorId); + } + + @Override + public String getRunnerId() { + return "AndroidTestProgramRunner"; + } + + @Override + protected boolean canRunWithMultipleDevices(@NotNull String executorId) { + return true; + } + + @NotNull + @Override + protected List getSupportedConfigurationTypeIds() { + return Collections.singletonList(BlazeCommandRunConfigurationType.getInstance().getId()); + } + + @NotNull + @Override + protected RunContentDescriptor run( + @NotNull ExecutionEnvironment environment, + @NotNull AndroidConfigurationExecutor executor, + @NotNull ProgressIndicator indicator) + throws ExecutionException { + if (DefaultDebugExecutor.EXECUTOR_ID.equals(environment.getExecutor().getId())) { + return executor.debug(indicator); + } + if (DefaultRunExecutor.EXECUTOR_ID.equals(environment.getExecutor().getId())) { + return executor.run(indicator); + } + throw new RuntimeException("Unsupported executor"); + } +} diff --git a/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationHandler.java b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationHandler.java new file mode 100644 index 00000000000..67e2ad4dfb7 --- /dev/null +++ b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationHandler.java @@ -0,0 +1,223 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.test; + +import com.android.tools.idea.execution.common.DeployableToDevice; +import com.android.tools.idea.run.ValidationError; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.idea.blaze.android.run.ApkBuildStepProvider; +import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationCommonState; +import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationHandler; +import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationValidationUtil; +import com.google.idea.blaze.android.run.LaunchMetrics; +import com.google.idea.blaze.android.run.runner.ApkBuildStep; +import com.google.idea.blaze.android.run.runner.BlazeAndroidRunConfigurationRunner; +import com.google.idea.blaze.android.run.runner.BlazeAndroidRunContext; +import com.google.idea.blaze.android.run.runner.FullApkBuildStep; +import com.google.idea.blaze.android.run.test.BlazeAndroidTestLaunchMethodsProvider.AndroidTestLaunchMethod; +import com.google.idea.blaze.base.command.BlazeCommandName; +import com.google.idea.blaze.base.command.BlazeInvocationContext; +import com.google.idea.blaze.base.model.primitives.Label; +import com.google.idea.blaze.base.model.primitives.TargetExpression; +import com.google.idea.blaze.base.projectview.ProjectViewManager; +import com.google.idea.blaze.base.projectview.ProjectViewSet; +import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration; +import com.google.idea.blaze.base.run.BlazeConfigurationNameBuilder; +import com.google.idea.blaze.base.run.ExecutorType; +import com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationRunner; +import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.sync.data.BlazeDataStorage; +import com.google.idea.blaze.base.sync.projectstructure.ModuleFinder; +import com.google.idea.blaze.java.AndroidBlazeRules; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.Executor; +import com.intellij.execution.JavaExecutionUtil; +import com.intellij.execution.configurations.RuntimeConfigurationException; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import java.util.List; +import javax.annotation.Nullable; +import org.jetbrains.android.facet.AndroidFacet; + +/** + * {@link com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationHandler} for + * android_test targets. + */ +public class BlazeAndroidTestRunConfigurationHandler + implements BlazeAndroidRunConfigurationHandler { + private final Project project; + private final BlazeAndroidTestRunConfigurationState configState; + + BlazeAndroidTestRunConfigurationHandler(BlazeCommandRunConfiguration configuration) { + this.project = configuration.getProject(); + this.configState = + new BlazeAndroidTestRunConfigurationState( + Blaze.buildSystemName(configuration.getProject())); + configuration.putUserData(DeployableToDevice.getKEY(), true); + } + + @Override + public BlazeAndroidTestRunConfigurationState getState() { + return configState; + } + + @Override + public BlazeAndroidRunConfigurationCommonState getCommonState() { + return configState.getCommonState(); + } + + @Override + public BlazeCommandRunConfigurationRunner createRunner( + Executor executor, ExecutionEnvironment env) throws ExecutionException { + Project project = env.getProject(); + BlazeCommandRunConfiguration configuration = + BlazeAndroidRunConfigurationHandler.getCommandConfig(env); + + BlazeAndroidRunConfigurationValidationUtil.validate(project); + Module module = + ModuleFinder.getInstance(env.getProject()) + .findModuleByName(BlazeDataStorage.WORKSPACE_MODULE_NAME); + AndroidFacet facet = module != null ? AndroidFacet.getInstance(module) : null; + ProjectViewSet projectViewSet = ProjectViewManager.getInstance(project).getProjectViewSet(); + + ImmutableList blazeFlags = + configState + .getCommonState() + .getExpandedBuildFlags( + project, + projectViewSet, + BlazeCommandName.TEST, + BlazeInvocationContext.runConfigContext( + ExecutorType.fromExecutor(env.getExecutor()), configuration.getType(), false)); + ImmutableList exeFlags = + ImmutableList.copyOf( + configState.getCommonState().getExeFlagsState().getFlagsForExternalProcesses()); + + // We collect metrics from a few different locations. In order to tie them all + // together, we create a unique launch id. + String launchId = LaunchMetrics.newLaunchId(); + Label label = Label.create(configuration.getSingleTarget().toString()); + + ApkBuildStep buildStep = + getTestBuildStep( + project, configState, configuration, blazeFlags, exeFlags, launchId, label); + + BlazeAndroidRunContext runContext = + new BlazeAndroidTestRunContext( + project, facet, configuration, env, configState, label, blazeFlags, buildStep); + + LaunchMetrics.logTestLaunch( + launchId, configState.getLaunchMethod().name(), env.getExecutor().getId()); + + return new BlazeAndroidRunConfigurationRunner(module, runContext, configuration); + } + + private static ApkBuildStep getTestBuildStep( + Project project, + BlazeAndroidTestRunConfigurationState configState, + BlazeCommandRunConfiguration configuration, + ImmutableList blazeFlags, + ImmutableList exeFlags, + String launchId, + Label label) + throws ExecutionException { + if (configuration.getTargetKind() + == AndroidBlazeRules.RuleTypes.ANDROID_INSTRUMENTATION_TEST.getKind()) { + boolean useMobileInstall = + AndroidTestLaunchMethod.MOBILE_INSTALL.equals(configState.getLaunchMethod()); + return ApkBuildStepProvider.getInstance(Blaze.getBuildSystemName(project)) + .getAitBuildStep( + project, + useMobileInstall, + /* nativeDebuggingEnabled= */ false, + label, + blazeFlags, + exeFlags, + launchId); + } else { + // TODO(b/248317444): This path is only invoked for the deprecated {@code android_test} + // targets, and should eventually be removed. + return new FullApkBuildStep(project, label, blazeFlags, /* nativeDebuggingEnabled= */ false); + } + } + + @Override + public final void checkConfiguration() throws RuntimeConfigurationException { + BlazeAndroidRunConfigurationValidationUtil.throwTopConfigurationError(validate()); + } + + /** + * We collect errors rather than throwing to avoid missing fatal errors by exiting early for a + * warning. We use a separate method for the collection so the compiler prevents us from + * accidentally throwing. + */ + private List validate() { + List errors = Lists.newArrayList(); + errors.addAll(BlazeAndroidRunConfigurationValidationUtil.validateWorkspaceModule(project)); + errors.addAll(configState.validate(project)); + return errors; + } + + @Override + @Nullable + public String suggestedName(BlazeCommandRunConfiguration configuration) { + TargetExpression target = configuration.getSingleTarget(); + if (target == null) { + return null; + } + BlazeConfigurationNameBuilder nameBuilder = new BlazeConfigurationNameBuilder(configuration); + + boolean isClassTest = + configState.getTestingType() == BlazeAndroidTestRunConfigurationState.TEST_CLASS; + boolean isMethodTest = + configState.getTestingType() == BlazeAndroidTestRunConfigurationState.TEST_METHOD; + if ((isClassTest || isMethodTest) && configState.getClassName() != null) { + // Get the class name without the package. + String className = JavaExecutionUtil.getPresentableClassName(configState.getClassName()); + if (className != null) { + String targetString = className; + if (isMethodTest) { + targetString += "#" + configState.getMethodName(); + } + + if (getState().getLaunchMethod().equals(AndroidTestLaunchMethod.NON_BLAZE)) { + return targetString; + } else { + return nameBuilder.setTargetString(targetString).build(); + } + } + } + return nameBuilder.build(); + } + + @Override + @Nullable + public BlazeCommandName getCommandName() { + if (getState().getLaunchMethod().equals(AndroidTestLaunchMethod.BLAZE_TEST)) { + return BlazeCommandName.TEST; + } else if (getState().getLaunchMethod().equals(AndroidTestLaunchMethod.MOBILE_INSTALL)) { + return BlazeCommandName.MOBILE_INSTALL; + } + return null; + } + + @Override + public String getHandlerName() { + return "Android Test Handler"; + } +} diff --git a/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunContext.java b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunContext.java new file mode 100644 index 00000000000..8f5df7f82fd --- /dev/null +++ b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunContext.java @@ -0,0 +1,292 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.test; + +import static com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryNormalBuildRunContextBase.getApkInfoToInstall; + +import com.android.ddmlib.IDevice; +import com.android.tools.idea.execution.common.DeployOptions; +import com.android.tools.idea.execution.common.debug.AndroidDebugger; +import com.android.tools.idea.execution.common.debug.AndroidDebuggerState; +import com.android.tools.idea.execution.common.debug.DebugSessionStarter; +import com.android.tools.idea.projectsystem.ApplicationProjectContext; +import com.android.tools.idea.run.ApkProvider; +import com.android.tools.idea.run.ApkProvisionException; +import com.android.tools.idea.run.ApplicationIdProvider; +import com.android.tools.idea.run.ConsoleProvider; +import com.android.tools.idea.run.LaunchOptions; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; +import com.android.tools.idea.run.blaze.BlazeLaunchTasksProvider; +import com.android.tools.idea.run.editor.ProfilerState; +import com.android.tools.idea.run.tasks.DeployTasksCompat; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.idea.blaze.android.run.BazelApplicationProjectContext; +import com.google.idea.blaze.android.run.deployinfo.BlazeAndroidDeployInfo; +import com.google.idea.blaze.android.run.deployinfo.BlazeApkProviderService; +import com.google.idea.blaze.android.run.runner.ApkBuildStep; +import com.google.idea.blaze.android.run.runner.BlazeAndroidDeviceSelector; +import com.google.idea.blaze.android.run.runner.BlazeAndroidLaunchTasksProvider; +import com.google.idea.blaze.android.run.runner.BlazeAndroidRunContext; +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.BlazeTestUiSession; +import com.google.idea.blaze.base.run.testlogs.BlazeTestResultHolder; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.Executor; +import com.intellij.execution.process.NopProcessHandler; +import com.intellij.execution.process.ProcessHandler; +import com.intellij.execution.process.ProcessOutputTypes; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.ui.ConsoleView; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.project.Project; +import com.intellij.xdebugger.XDebugSession; +import java.util.List; +import javax.annotation.Nullable; +import kotlin.Unit; +import kotlin.coroutines.EmptyCoroutineContext; +import kotlinx.coroutines.BuildersKt; +import org.jetbrains.android.facet.AndroidFacet; + +/** Run context for android_test. */ +public class BlazeAndroidTestRunContext implements BlazeAndroidRunContext { + protected final Project project; + protected final AndroidFacet facet; + protected final BlazeCommandRunConfiguration runConfiguration; + protected final ExecutionEnvironment env; + protected final BlazeAndroidTestRunConfigurationState configState; + protected final Label label; + protected final ImmutableList blazeFlags; + protected final List launchTaskCompleteListeners = Lists.newArrayList(); + protected final ConsoleProvider consoleProvider; + protected final ApkBuildStep buildStep; + protected final ApplicationIdProvider applicationIdProvider; + protected final ApkProvider apkProvider; + private final BlazeTestResultHolder testResultsHolder = new BlazeTestResultHolder(); + + public BlazeAndroidTestRunContext( + Project project, + AndroidFacet facet, + BlazeCommandRunConfiguration runConfiguration, + ExecutionEnvironment env, + BlazeAndroidTestRunConfigurationState configState, + Label label, + ImmutableList blazeFlags, + ApkBuildStep buildStep) { + this.project = project; + this.facet = facet; + this.runConfiguration = runConfiguration; + this.env = env; + this.label = label; + this.configState = configState; + this.buildStep = buildStep; + this.blazeFlags = blazeFlags; + switch (configState.getLaunchMethod()) { + case MOBILE_INSTALL: + case NON_BLAZE: + consoleProvider = new AitIdeTestConsoleProvider(runConfiguration, configState); + break; + case BLAZE_TEST: + BlazeTestUiSession session = + BlazeTestUiSession.create(ImmutableList.of(), testResultsHolder); + this.consoleProvider = new AitBlazeTestConsoleProvider(project, runConfiguration, session); + break; + default: + throw new IllegalStateException( + "Unsupported launch method " + configState.getLaunchMethod()); + } + applicationIdProvider = new BlazeAndroidTestApplicationIdProvider(buildStep); + apkProvider = BlazeApkProviderService.getInstance().getApkProvider(project, buildStep); + } + + @Override + public BlazeAndroidDeviceSelector getDeviceSelector() { + return new BlazeAndroidDeviceSelector.NormalDeviceSelector(); + } + + @Override + public void augmentLaunchOptions(LaunchOptions.Builder options) { + options.setDeploy(!configState.getLaunchMethod().equals(AndroidTestLaunchMethod.BLAZE_TEST)); + } + + @Override + public ConsoleProvider getConsoleProvider() { + return consoleProvider; + } + + @Override + public ApplicationIdProvider getApplicationIdProvider() { + return applicationIdProvider; + } + + @Override + public ApplicationProjectContext getApplicationProjectContext() { + return new BazelApplicationProjectContext(project, getApplicationIdProvider()); + } + + @Nullable + @Override + public ApkBuildStep getBuildStep() { + return buildStep; + } + + @Override + public ProfilerState getProfileState() { + return null; + } + + @Override + public BlazeLaunchTasksProvider getLaunchTasksProvider(LaunchOptions launchOptions) + throws ExecutionException { + return new BlazeAndroidLaunchTasksProvider(project, this, applicationIdProvider, launchOptions); + } + + @Override + public ImmutableList getDeployTasks(IDevice device, DeployOptions deployOptions) + throws ExecutionException { + if (configState.getLaunchMethod() != AndroidTestLaunchMethod.NON_BLAZE) { + return ImmutableList.of(); + } + return ImmutableList.of( + DeployTasksCompat.createDeployTask( + project, getApkInfoToInstall(device, deployOptions, apkProvider), deployOptions)); + } + + @Override + @Nullable + public BlazeLaunchTask getApplicationLaunchTask( + boolean isDebug, @Nullable Integer userId, String contributorsAmStartOptions) + throws ExecutionException { + switch (configState.getLaunchMethod()) { + case BLAZE_TEST: + BlazeAndroidTestFilter testFilter = + new BlazeAndroidTestFilter( + configState.getTestingType(), + configState.getClassName(), + configState.getMethodName(), + configState.getPackageName()); + return new BlazeAndroidTestLaunchTask( + project, label, blazeFlags, testFilter, this, isDebug, testResultsHolder); + case NON_BLAZE: + case MOBILE_INSTALL: + BlazeAndroidDeployInfo deployInfo; + try { + deployInfo = buildStep.getDeployInfo(); + } catch (ApkProvisionException e) { + throw new ExecutionException(e); + } + return StockAndroidTestLaunchTask.getStockTestLaunchTask( + configState, applicationIdProvider, isDebug, deployInfo, project); + } + throw new AssertionError(); + } + + @Override + @SuppressWarnings({"unchecked", "rawtypes"}) // Raw type from upstream. + public XDebugSession startDebuggerSession( + AndroidDebugger androidDebugger, + AndroidDebuggerState androidDebuggerState, + ExecutionEnvironment env, + IDevice device, + ConsoleView consoleView, + ProgressIndicator indicator, + String packageName) { + try { + return BuildersKt.runBlocking( + EmptyCoroutineContext.INSTANCE, + (scope, continuation) -> { + switch (configState.getLaunchMethod()) { + case BLAZE_TEST: + + /** + * Wires up listeners to automatically reconnect the debugger for each test method. + * When you `blaze test` an android_test in debug mode, it kills the instrumentation + * process between each test method, disconnecting the debugger. We listen for the + * start of a new method waiting for a debugger, and reconnect. TODO: Support + * stopping Blaze from the UI. This is hard because we have no way to distinguish + * process handler termination/debug session ending initiated by the user. + */ + final ProcessHandler masterProcessHandler = new NopProcessHandler(); + addLaunchTaskCompleteListener( + () -> { + masterProcessHandler.notifyTextAvailable( + "Test run completed.\n", ProcessOutputTypes.STDOUT); + masterProcessHandler.detachProcess(); + }); + return DebugSessionStarter.INSTANCE.attachReattachingDebuggerToStartedProcess( + device, + new BazelApplicationProjectContext(project, packageName), + masterProcessHandler, + env, + androidDebugger, + androidDebuggerState, + indicator, + consoleView, + Long.MAX_VALUE, + continuation); + case NON_BLAZE: + case MOBILE_INSTALL: + return DebugSessionStarter.INSTANCE.attachDebuggerToStartedProcess( + device, + new BazelApplicationProjectContext(project, packageName), + env, + androidDebugger, + androidDebuggerState, + /*destroyRunningProcess*/ d -> { + d.forceStop(packageName); + return Unit.INSTANCE; + }, + indicator, + consoleView, + Long.MAX_VALUE, + continuation); + } + throw new RuntimeException("Unknown lunch mode"); + }); + } catch (InterruptedException e) { + throw new ProcessCanceledException(); + } + } + + void onLaunchTaskComplete() { + for (Runnable runnable : launchTaskCompleteListeners) { + runnable.run(); + } + } + + void addLaunchTaskCompleteListener(Runnable runnable) { + launchTaskCompleteListeners.add(runnable); + } + + @Override + public Executor getExecutor() { + return env.getExecutor(); + } + + @Nullable + @Override + public Integer getUserId(IDevice device) { + return null; + } + + @Override + public String getAmStartOptions() { + return ""; + } +} diff --git a/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/testrecorder/TestRecorderBlazeCommandRunConfigurationProxy.java b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/testrecorder/TestRecorderBlazeCommandRunConfigurationProxy.java new file mode 100644 index 00000000000..fa96a5dc6e1 --- /dev/null +++ b/aswb/sdkcompat/as232/com/google/idea/blaze/android/run/testrecorder/TestRecorderBlazeCommandRunConfigurationProxy.java @@ -0,0 +1,34 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.testrecorder; + +import com.google.idea.blaze.android.cppimpl.debug.BlazeAutoAndroidDebuggerBase; +import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration; + +/** Compat class for implementing TestRecorderBlazeCommandRunConfigurationProxy. */ +public class TestRecorderBlazeCommandRunConfigurationProxy + extends TestRecorderBlazeCommandRunConfigurationProxyBase { + + public TestRecorderBlazeCommandRunConfigurationProxy( + BlazeCommandRunConfiguration baseConfiguration) { + super(baseConfiguration); + } + + @Override + public boolean isNativeProject() { + return BlazeAutoAndroidDebuggerBase.isNativeCodeInProject(getModule().getProject()); + } +} diff --git a/aswb/sdkcompat/as212/com/google/idea/blaze/android/sync/model/idea/BlazeAndroidModel.java b/aswb/sdkcompat/as232/com/google/idea/blaze/android/sync/model/idea/BlazeAndroidModel.java similarity index 83% rename from aswb/sdkcompat/as212/com/google/idea/blaze/android/sync/model/idea/BlazeAndroidModel.java rename to aswb/sdkcompat/as232/com/google/idea/blaze/android/sync/model/idea/BlazeAndroidModel.java index 64446b81491..00a9e4a94c9 100644 --- a/aswb/sdkcompat/as212/com/google/idea/blaze/android/sync/model/idea/BlazeAndroidModel.java +++ b/aswb/sdkcompat/as232/com/google/idea/blaze/android/sync/model/idea/BlazeAndroidModel.java @@ -15,14 +15,11 @@ */ package com.google.idea.blaze.android.sync.model.idea; - import com.android.tools.idea.model.AndroidModel; import com.android.tools.idea.model.Namespacing; import com.android.tools.idea.projectsystem.NamedIdeaSourceProvider; import com.google.common.util.concurrent.ListenableFuture; -import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; import java.io.File; /** Blaze model for an android project. #api42. */ @@ -53,14 +50,4 @@ public Namespacing getNamespacing() { protected String uninitializedApplicationId() { return AndroidModel.UNINITIALIZED_APPLICATION_ID; } - - @Override - public boolean isGenerated(VirtualFile file) { - return false; - } - - @Override - public boolean isClassFileOutOfDate(Module module, String fqcn, VirtualFile classFile) { - return BlazeClassJarProvider.testIsClassFileOutOfDate(project, fqcn, classFile); - } } diff --git a/aswb/sdkcompat/as233/com/META-INF/aswb.xml b/aswb/sdkcompat/as233/com/META-INF/aswb.xml new file mode 100644 index 00000000000..fa5c9754f9e --- /dev/null +++ b/aswb/sdkcompat/as233/com/META-INF/aswb.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/aswb/sdkcompat/as233/com/android/tools/configurations/ConfigurationCompat.java b/aswb/sdkcompat/as233/com/android/tools/configurations/ConfigurationCompat.java new file mode 100644 index 00000000000..3d51e831ad7 --- /dev/null +++ b/aswb/sdkcompat/as233/com/android/tools/configurations/ConfigurationCompat.java @@ -0,0 +1,32 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR 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.configurations; + +import com.android.sdklib.devices.Device; + +/** Compat class for Configuration. */ +public class ConfigurationCompat { + + private Configuration configuration; + + public ConfigurationCompat(Configuration configuration) { + this.configuration = configuration; + } + + public void setDevice(Device device, boolean preserveState) { + configuration.setDevice(device, preserveState); + } +} diff --git a/aswb/sdkcompat/as212/com/android/tools/idea/BaseAsCompat.java b/aswb/sdkcompat/as233/com/android/tools/idea/model/AndroidManifestIndexCompat.java similarity index 65% rename from aswb/sdkcompat/as212/com/android/tools/idea/BaseAsCompat.java rename to aswb/sdkcompat/as233/com/android/tools/idea/model/AndroidManifestIndexCompat.java index f904b9827fd..82eb27bee0c 100644 --- a/aswb/sdkcompat/as212/com/android/tools/idea/BaseAsCompat.java +++ b/aswb/sdkcompat/as233/com/android/tools/idea/model/AndroidManifestIndexCompat.java @@ -13,16 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.tools.idea; +package com.android.tools.idea.model; -import com.android.tools.idea.run.tasks.LaunchResult; +/** Compat class for {@link com.android.tools.idea.model.AndroidManifestIndex}. */ +public class AndroidManifestIndexCompat { -/** Simple compat utilities across various versions of AS plugin SDK. */ -public class BaseAsCompat { - // #api212 - public static boolean wasSuccessfulLaunch(LaunchResult launchResult) { - return launchResult.getSuccess(); - } + private AndroidManifestIndexCompat() {} - private BaseAsCompat() {} + /** {@code indexEnabled} was removed in Studio 2021.2. #api211 */ + public static boolean indexEnabled() { + return true; + } } diff --git a/aswb/sdkcompat/as233/com/android/tools/idea/progress/StudioLoggerProgressIndicatorCompat.java b/aswb/sdkcompat/as233/com/android/tools/idea/progress/StudioLoggerProgressIndicatorCompat.java new file mode 100644 index 00000000000..7f9654b2ccf --- /dev/null +++ b/aswb/sdkcompat/as233/com/android/tools/idea/progress/StudioLoggerProgressIndicatorCompat.java @@ -0,0 +1,23 @@ +/* + * Copyright 2022 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR 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.progress; + +/** Compat layer for StudioLoggerProgressIndicator which moved packages in 2021.2. #api211 */ +public class StudioLoggerProgressIndicatorCompat extends StudioLoggerProgressIndicator { + public StudioLoggerProgressIndicatorCompat(Class c) { + super(c); + } +} diff --git a/aswb/sdkcompat/as233/com/android/tools/idea/rendering/RenderErrorContributorCompat.java b/aswb/sdkcompat/as233/com/android/tools/idea/rendering/RenderErrorContributorCompat.java new file mode 100644 index 00000000000..bd5059f8cc3 --- /dev/null +++ b/aswb/sdkcompat/as233/com/android/tools/idea/rendering/RenderErrorContributorCompat.java @@ -0,0 +1,29 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR 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.rendering; + +import com.android.tools.idea.ui.designer.EditorDesignSurface; +import com.android.tools.rendering.RenderResultCompat; +import com.intellij.openapi.actionSystem.DataContext; +import groovyjarjarantlr4.v4.runtime.misc.Nullable; + +/** Contribute blaze specific render errors. */ +public class RenderErrorContributorCompat extends RenderErrorContributorImpl { + public RenderErrorContributorCompat( + EditorDesignSurface surface, RenderResultCompat result, @Nullable DataContext dataContext) { + super(surface, result.get()); + } +} diff --git a/aswb/sdkcompat/as233/com/android/tools/idea/run/tasks/DeployTasksCompat.java b/aswb/sdkcompat/as233/com/android/tools/idea/run/tasks/DeployTasksCompat.java new file mode 100644 index 00000000000..8c5bde9785e --- /dev/null +++ b/aswb/sdkcompat/as233/com/android/tools/idea/run/tasks/DeployTasksCompat.java @@ -0,0 +1,52 @@ +/* + * Copyright 2020 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR 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.run.tasks; + +import com.android.tools.deployer.DeployerException; +import com.android.tools.idea.execution.common.AndroidExecutionException; +import com.android.tools.idea.execution.common.DeployOptions; +import com.android.tools.idea.run.ApkInfo; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; +import com.google.idea.common.experiments.BoolExperiment; +import com.intellij.openapi.project.Project; +import java.util.Collection; +import java.util.List; + +/** Compat class for {@link DeployTask} */ +public class DeployTasksCompat { + private static final BoolExperiment updateCodeViaJvmti = + new BoolExperiment("android.apply.changes", false); + + private DeployTasksCompat() {} + + public static BlazeLaunchTask createDeployTask( + Project project, Collection packages, DeployOptions deployOptions) { + return launchContext -> { + try { + List unused = + new DeployTask( + project, + packages, + deployOptions.getPmInstallFlags(), + deployOptions.getInstallOnAllUsers(), + deployOptions.getAlwaysInstallWithPm()) + .run(launchContext.getDevice(), launchContext.getProgressIndicator()); + } catch (DeployerException e) { + throw new AndroidExecutionException(e.getId(), e.getMessage()); + } + }; + } +} diff --git a/aswb/sdkcompat/as233/com/android/tools/rendering/HtmlLinkManagerCompat.java b/aswb/sdkcompat/as233/com/android/tools/rendering/HtmlLinkManagerCompat.java new file mode 100644 index 00000000000..28ca9471258 --- /dev/null +++ b/aswb/sdkcompat/as233/com/android/tools/rendering/HtmlLinkManagerCompat.java @@ -0,0 +1,28 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR 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.rendering; + +import java.io.File; +import org.jetbrains.annotations.NotNull; + +/** Compat class for {@link HtmlLinkManager} */ +public final class HtmlLinkManagerCompat { + public static String createFilePositionUrl(@NotNull File file, int line, int column) { + return HtmlLinkManager.createFilePositionUrl(file, line, column); + } + + private HtmlLinkManagerCompat() {} +} diff --git a/aswb/sdkcompat/as233/com/android/tools/rendering/RenderLoggerCompat.java b/aswb/sdkcompat/as233/com/android/tools/rendering/RenderLoggerCompat.java new file mode 100644 index 00000000000..96af24f48d3 --- /dev/null +++ b/aswb/sdkcompat/as233/com/android/tools/rendering/RenderLoggerCompat.java @@ -0,0 +1,48 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR 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.rendering; + +import java.util.Map; +import java.util.Set; + +/** Compat class for RenderLogger. */ +public class RenderLoggerCompat { + private final RenderLogger renderLogger; + + public RenderLoggerCompat(RenderResultCompat result) { + renderLogger = result.getLogger(); + } + + public RenderLogger get() { + return renderLogger; + } + + public boolean hasErrors() { + return renderLogger.hasErrors(); + } + + public Map getBrokenClasses() { + return renderLogger.getBrokenClasses(); + } + + public Set getMissingClasses() { + return renderLogger.getMissingClasses(); + } + + public static void resetFidelityErrorsFilters() { + RenderLogger.resetFidelityErrorsFilters(); + } +} diff --git a/aswb/sdkcompat/as233/com/android/tools/rendering/RenderResultCompat.java b/aswb/sdkcompat/as233/com/android/tools/rendering/RenderResultCompat.java new file mode 100644 index 00000000000..6cb43bf3def --- /dev/null +++ b/aswb/sdkcompat/as233/com/android/tools/rendering/RenderResultCompat.java @@ -0,0 +1,84 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR 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.rendering; + +import com.android.ide.common.rendering.api.Result; +import com.android.tools.idea.rendering.RenderErrorContributor; +import com.android.tools.idea.rendering.RenderErrorContributorImpl; +import com.android.tools.idea.rendering.RenderErrorModelFactory; +import com.android.tools.idea.rendering.RenderResults; +import com.android.tools.idea.rendering.errors.ui.RenderErrorModel; +import com.android.tools.idea.ui.designer.EditorDesignSurface; +import com.android.tools.rendering.imagepool.ImagePool.Image; +import com.google.idea.blaze.android.rendering.BlazeRenderErrorContributor; +import com.google.idea.blaze.base.settings.Blaze; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiFile; +import org.jetbrains.annotations.Nullable; + +/** Compat class for {@link RenderResult} */ +public final class RenderResultCompat { + private RenderResult result; + + public RenderResultCompat(RenderResult result) { + this.result = result; + } + + public static RenderResultCompat createBlank(PsiFile file) { + return new RenderResultCompat(RenderResults.createBlank(file)); + } + + public RenderErrorModel createErrorModel() { + return RenderErrorModelFactory.createErrorModel(null, result); + } + + public RenderResult get() { + return result; + } + + public RenderLogger getLogger() { + return result.getLogger(); + } + + public Module getModule() { + return result.getModule(); + } + + public Result getRenderResult() { + return result.getRenderResult(); + } + + public Image getRenderedImage() { + return result.getRenderedImage(); + } + + private RenderResultCompat() {} + + /** Extension to provide {@link BlazeRenderErrorContributor}. */ + public static class BlazeProvider extends RenderErrorContributorImpl.Provider { + @Override + public boolean isApplicable(Project project) { + return Blaze.isBlazeProject(project); + } + + @Override + public RenderErrorContributor getContributor( + @Nullable EditorDesignSurface surface, RenderResult result) { + return new BlazeRenderErrorContributor(surface, result); + } + } +} diff --git a/aswb/sdkcompat/as233/com/android/tools/rendering/RenderServiceCompat.java b/aswb/sdkcompat/as233/com/android/tools/rendering/RenderServiceCompat.java new file mode 100644 index 00000000000..de22b6df36d --- /dev/null +++ b/aswb/sdkcompat/as233/com/android/tools/rendering/RenderServiceCompat.java @@ -0,0 +1,27 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR 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.rendering; + +/** Compat class for {@link RenderService} */ +public class RenderServiceCompat { + public static void shutdownRenderExecutor(long l) { + RenderService.shutdownRenderExecutor(l); + } + + public static void initializeRenderExecutor() { + RenderService.initializeRenderExecutor(); + } +} diff --git a/aswb/sdkcompat/as233/com/google/idea/blaze/android/cppimpl/debug/BlazeAutoAndroidDebugger.java b/aswb/sdkcompat/as233/com/google/idea/blaze/android/cppimpl/debug/BlazeAutoAndroidDebugger.java new file mode 100644 index 00000000000..08f03d50d8f --- /dev/null +++ b/aswb/sdkcompat/as233/com/google/idea/blaze/android/cppimpl/debug/BlazeAutoAndroidDebugger.java @@ -0,0 +1,41 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.cppimpl.debug; + +import com.android.ddmlib.Client; +import com.android.ddmlib.ClientData; +import com.intellij.openapi.project.Project; +import com.intellij.xdebugger.XDebugSession; +import org.jetbrains.annotations.NotNull; + +/** Shim for #api212 compat. */ +public class BlazeAutoAndroidDebugger extends BlazeAutoAndroidDebuggerBase { + + @Override + public XDebugSession getExistingDebugSession(@NotNull Project project, @NotNull Client client) { + if (isNativeProject(project)) { + log.info("Project has native development enabled"); + return nativeDebugger.getExistingDebugSession(project, client); + } else { + return super.getExistingDebugSession(project, client); + } + } + + @Override + protected boolean isNativeDeployment(Project project, ClientData clientData) { + return isNativeProject(project); + } +} diff --git a/aswb/sdkcompat/as233/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeAndroidDebugger.java b/aswb/sdkcompat/as233/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeAndroidDebugger.java new file mode 100644 index 00000000000..e92afddc27c --- /dev/null +++ b/aswb/sdkcompat/as233/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeAndroidDebugger.java @@ -0,0 +1,28 @@ +/* + * Copyright 2018 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.cppimpl.debug; + +/** + * API compat of {@link BlazeNativeAndroidDebuggerBase} with the following additions: + * + *

    + *
  • Creates a run-config setting using {@link BlazeAndroidNativeAttachConfiguration} instead of + * {@link AndroidNativeAttachConfiguration} to override counterproductive validations. + *
+ * + * #api4.0 + */ +public class BlazeNativeAndroidDebugger extends BlazeNativeAndroidDebuggerBase {} diff --git a/aswb/sdkcompat/as233/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeAndroidDebuggerBase.java b/aswb/sdkcompat/as233/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeAndroidDebuggerBase.java new file mode 100644 index 00000000000..e9ba5c444c5 --- /dev/null +++ b/aswb/sdkcompat/as233/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeAndroidDebuggerBase.java @@ -0,0 +1,78 @@ +/* + * Copyright 2020 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.cppimpl.debug; + +import com.android.ddmlib.Client; +import com.android.tools.idea.projectsystem.ApplicationProjectContext; +import com.android.tools.ndk.run.editor.NativeAndroidDebugger; +import com.android.tools.ndk.run.editor.NativeAndroidDebuggerState; +import com.google.idea.blaze.base.model.BlazeProjectData; +import com.google.idea.blaze.base.model.primitives.LanguageClass; +import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; +import com.intellij.openapi.project.Project; +import com.intellij.xdebugger.XDebugProcessStarter; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Extension of {@link NativeAndroidDebugger} with the following key differences compared to {@link + * NativeAndroidDebugger}. + * + *
    + *
  • Overrides {@link #supportsProject} so native debugger is only enabled for native support is + * enabled. + *
+ */ +public class BlazeNativeAndroidDebuggerBase extends NativeAndroidDebugger { + /** + * This ID needs to be lexicographically larger than "Java" so it come after the "Java" debugger + * when sorted lexicographically in the "Attach Debugger to Android Process" dialog. See {@link + * org.jetbrains.android.actions.AndroidProcessChooserDialog#populateDebuggerTypeCombo}. + */ + public static final String ID = "Native" + Blaze.defaultBuildSystemName(); + + @Override + public String getId() { + return ID; + } + + @Override + public String getDisplayName() { + return "Native Only"; + } + + @Override + public boolean supportsProject(Project project) { + BlazeProjectData blazeProjectData = + BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); + return blazeProjectData != null + && blazeProjectData.getWorkspaceLanguageSettings().isLanguageActive(LanguageClass.C); + } + + @Override + public XDebugProcessStarter getDebugProcessStarterForExistingProcess( + @NotNull Project project, + @NotNull Client client, + ApplicationProjectContext applicationContext, + @Nullable NativeAndroidDebuggerState state) { + if (state != null) { + BlazeNativeDebuggerStateSourceMapping.addSourceMapping(project, state); + } + return super.getDebugProcessStarterForExistingProcess( + project, client, applicationContext, state); + } +} diff --git a/aswb/sdkcompat/as233/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeDebuggerStateSourceMapping.java b/aswb/sdkcompat/as233/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeDebuggerStateSourceMapping.java new file mode 100644 index 00000000000..640e5f82075 --- /dev/null +++ b/aswb/sdkcompat/as233/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeDebuggerStateSourceMapping.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.cppimpl.debug; + +import com.android.tools.ndk.run.editor.NativeAndroidDebuggerState; +import com.google.common.collect.ImmutableList; +import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; +import com.intellij.openapi.project.Project; +import org.jetbrains.annotations.NotNull; + +/** Maps source directory when attaching to a process */ +public class BlazeNativeDebuggerStateSourceMapping { + public static void addSourceMapping( + @NotNull Project project, @NotNull NativeAndroidDebuggerState state) { + // Source code is always relative to the workspace root in a blaze project. + String workingDirPath = WorkspaceRoot.fromProject(project).directory().getPath(); + state.setWorkingDir(workingDirPath); + + // Remote built binaries may use /proc/self/cwd to represent the working directory + // so we manually map /proc/self/cwd to the workspace root. We used to use + // `plugin.symbol-file.dwarf.comp-dir-symlink-paths = "/proc/self/cwd"` + // to automatically resolve this but it's no longer supported in newer versions of + // LLDB. + String sourceMapToWorkspaceRootCommand = + "settings append target.source-map /proc/self/cwd " + workingDirPath; + ImmutableList startupCommands = + ImmutableList.builder() + .addAll(state.getUserStartupCommands()) + .add(sourceMapToWorkspaceRootCommand) + .build(); + state.setUserStartupCommands(startupCommands); + } +} diff --git a/aswb/sdkcompat/as233/com/google/idea/blaze/android/projectsystem/BlazeAddDestinationMenuToken.java b/aswb/sdkcompat/as233/com/google/idea/blaze/android/projectsystem/BlazeAddDestinationMenuToken.java new file mode 100644 index 00000000000..e74aa02a7d2 --- /dev/null +++ b/aswb/sdkcompat/as233/com/google/idea/blaze/android/projectsystem/BlazeAddDestinationMenuToken.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.projectsystem; + +import static com.android.tools.idea.util.DependencyManagementUtil.addDependenciesWithUiConfirmation; +import static com.android.tools.idea.util.DependencyManagementUtil.dependsOn; + +import com.android.ide.common.repository.GoogleMavenArtifactId; +import com.android.tools.idea.common.model.NlModel; +import com.android.tools.idea.naveditor.editor.AddDestinationMenuToken; +import com.intellij.openapi.module.Module; +import java.util.List; +import org.jetbrains.annotations.NotNull; + +/** Blaze implementation of project system tokens for the navigation editor: Add Destination Menu */ +public class BlazeAddDestinationMenuToken + implements AddDestinationMenuToken, BlazeToken { + @Override + public void modifyProject( + @NotNull BlazeProjectSystem projectSystem, @NotNull AddDestinationMenuToken.Data data) { + NlModel model = data.getSurface().getModel(); + if (model == null) { + return; + } + Module module = model.getModule(); + if (dependsOn(module, GoogleMavenArtifactId.ANDROIDX_NAVIGATION_DYNAMIC_FEATURES_FRAGMENT)) { + return; + } + final var unused = + addDependenciesWithUiConfirmation( + module, + List.of( + GoogleMavenArtifactId.ANDROIDX_NAVIGATION_DYNAMIC_FEATURES_FRAGMENT.getCoordinate( + "+")), + true, + false); + } +} diff --git a/aswb/sdkcompat/as233/com/google/idea/blaze/android/projectsystem/BlazeModuleDependencies.java b/aswb/sdkcompat/as233/com/google/idea/blaze/android/projectsystem/BlazeModuleDependencies.java new file mode 100644 index 00000000000..512e105c258 --- /dev/null +++ b/aswb/sdkcompat/as233/com/google/idea/blaze/android/projectsystem/BlazeModuleDependencies.java @@ -0,0 +1,83 @@ +/* + * Copyright 2024 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.projectsystem; + +import static com.google.common.collect.ImmutableList.toImmutableList; + +import com.android.ide.common.repository.GoogleMavenArtifactId; +import com.android.projectmodel.ExternalAndroidLibrary; +import com.android.tools.idea.projectsystem.DependencyScopeType; +import com.android.tools.idea.rendering.PsiClassViewClass; +import com.android.tools.module.ModuleDependencies; +import com.android.tools.module.ViewClass; +import com.google.common.collect.ImmutableList; +import com.google.idea.blaze.android.resources.BlazeLightResourceClassService; +import com.intellij.openapi.module.Module; +import com.intellij.psi.JavaPsiFacade; +import java.util.List; +import java.util.Objects; +import java.util.function.Predicate; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** ASwB specific implementation of [ModuleDependencies]. */ +public class BlazeModuleDependencies implements ModuleDependencies { + + Module module; + + BlazeModuleDependencies(Module module) { + this.module = module; + } + + @Override + public boolean dependsOn(@NotNull GoogleMavenArtifactId googleMavenArtifactId) { + return false; + } + + /** + * Fetches the resource packages within the project and from external dependencies. Input + * parameter includeExternalLibraries is ignored as ASwB/Blaze uses a single workspace module to + * map both types of resources. + */ + @NotNull + @Override + public List getResourcePackageNames(boolean includeExternalLibraries) { + // TODO(b/304821496): Add an integration test to test it similar to BuildDependenciesTest + // TODO(b/307604153): Update AndroidExternalLibraryManager to read package name from multiple + // locations + return ImmutableList.builder() + .addAll( + BlazeModuleSystem.getInstance(module) + .getAndroidLibraryDependencies(DependencyScopeType.MAIN) + .stream() + .map(ExternalAndroidLibrary::getPackageName) + .filter(Objects::nonNull) + .filter(Predicate.not(String::isEmpty)) + .collect(toImmutableList())) + .addAll( + BlazeLightResourceClassService.getInstance(module.getProject()) + .getWorkspaceResourcePackages()) + .build(); + } + + @Nullable + @Override + public ViewClass findViewClass(@NotNull String fqcn) { + JavaPsiFacade facade = JavaPsiFacade.getInstance(module.getProject()); + return new PsiClassViewClass( + facade.findClass(fqcn, module.getModuleWithDependenciesAndLibrariesScope(false))); + } +} diff --git a/aswb/sdkcompat/as233/com/google/idea/blaze/android/projectsystem/BlazeModuleSystem.java b/aswb/sdkcompat/as233/com/google/idea/blaze/android/projectsystem/BlazeModuleSystem.java new file mode 100644 index 00000000000..2c64e2c2b32 --- /dev/null +++ b/aswb/sdkcompat/as233/com/google/idea/blaze/android/projectsystem/BlazeModuleSystem.java @@ -0,0 +1,78 @@ +/* + * Copyright 2020 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.projectsystem; + +import static com.google.common.collect.ImmutableList.toImmutableList; + +import com.android.tools.idea.projectsystem.AndroidModuleSystem; +import com.android.tools.module.ModuleDependencies; +import com.google.common.collect.ImmutableList; +import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.targetmaps.SourceToTargetMap; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.vfs.VirtualFile; +import java.io.File; +import java.nio.file.Path; + +/** Blaze implementation of {@link AndroidModuleSystem}. */ +public class BlazeModuleSystem extends BlazeModuleSystemBase { + + BlazeModuleSystem(Module module) { + super(module); + } + + @Override + public ModuleDependencies getModuleDependencies() { + return new BlazeModuleDependencies(getModule()); + } + + public String blazeTargetNameToKotlinModuleName(String blazeTargetName) { + // Before: //third_party/java_src/android_app/compose_samples/Rally:lib + // After: third_party_java_src_android_app_compose_samples_Rally_lib + assert blazeTargetName.substring(0, 2).equals("//"); + return blazeTargetName.substring(2).replaceAll("['/',':']", "_"); + } + + @Override + public String getModuleNameForCompilation(VirtualFile virtualFile) { + String moduleName = + blazeTargetNameToKotlinModuleName( + SourceToTargetMap.getInstance(project) + .getTargetsToBuildForSourceFile(new File(virtualFile.getPath())) + .get(0) // use the first target + .toString()); + return moduleName; + } + + /** Check every supporting extension point if they contain desugaring library config files */ + @Override + public boolean getDesugarLibraryConfigFilesKnown() { + return DesugaringLibraryConfigFilesLocator.forBuildSystem( + Blaze.getBuildSystemName(module.getProject())) + .stream() + .anyMatch(provider -> provider.getDesugarLibraryConfigFilesKnown()); + } + + /** Collect desugarig library config files from every supporting extension and return the list */ + @Override + public ImmutableList getDesugarLibraryConfigFiles() { + return DesugaringLibraryConfigFilesLocator.forBuildSystem( + Blaze.getBuildSystemName(module.getProject())) + .stream() + .flatMap(provider -> provider.getDesugarLibraryConfigFiles(project).stream()) + .collect(toImmutableList()); + } +} diff --git a/aswb/sdkcompat/as233/com/google/idea/blaze/android/projectsystem/BlazeNavDesignSurfaceToken.java b/aswb/sdkcompat/as233/com/google/idea/blaze/android/projectsystem/BlazeNavDesignSurfaceToken.java new file mode 100644 index 00000000000..80e8b353b01 --- /dev/null +++ b/aswb/sdkcompat/as233/com/google/idea/blaze/android/projectsystem/BlazeNavDesignSurfaceToken.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.projectsystem; + +import static com.android.tools.idea.util.DependencyManagementUtil.addDependenciesWithUiConfirmation; +import static com.google.common.collect.ImmutableList.toImmutableList; + +import com.android.ide.common.repository.GradleCoordinate; +import com.android.tools.idea.common.model.NlModel; +import com.android.tools.idea.naveditor.surface.NavDesignSurface; +import com.android.tools.idea.naveditor.surface.NavDesignSurfaceToken; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.module.Module; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import org.jetbrains.annotations.NotNull; + +/** Blaze implementation of project system tokens for the navigation editor: Nav Design Surface */ +class BlazeNavDesignSurfaceToken implements NavDesignSurfaceToken, BlazeToken { + @Override + public boolean modifyProject(@NotNull BlazeProjectSystem projectSystem, @NotNull NlModel model) { + AtomicBoolean didAdd = new AtomicBoolean(false); + Module module = model.getModule(); + List coordinates = + NavDesignSurface.getDependencies(module).stream() + .map((a) -> a.getCoordinate("+")) + .collect(toImmutableList()); + Runnable runnable = + () -> { + try { + didAdd.set( + addDependenciesWithUiConfirmation(module, coordinates, true, false).isEmpty()); + } catch (Throwable t) { + Logger.getInstance(NavDesignSurface.class).warn("Failed to add dependencies", t); + didAdd.set(false); + } + }; + ApplicationManager.getApplication().invokeAndWait(runnable); + return didAdd.get(); + } +} diff --git a/aswb/sdkcompat/as233/com/google/idea/blaze/android/projectsystem/BlazeProjectSystem.java b/aswb/sdkcompat/as233/com/google/idea/blaze/android/projectsystem/BlazeProjectSystem.java new file mode 100644 index 00000000000..2a4c6d95610 --- /dev/null +++ b/aswb/sdkcompat/as233/com/google/idea/blaze/android/projectsystem/BlazeProjectSystem.java @@ -0,0 +1,296 @@ +/* + * 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.projectsystem; + +import static com.android.tools.idea.projectsystem.SourceProvidersKt.emptySourceProvider; +import static com.google.idea.blaze.base.sync.data.BlazeDataStorage.WORKSPACE_MODULE_NAME; +import static org.jetbrains.android.facet.SourceProviderUtil.createSourceProvidersForLegacyModule; + +import com.android.tools.apk.analyzer.AaptInvoker; +import com.android.tools.idea.log.LogWrapper; +import com.android.tools.idea.model.AndroidModel; +import com.android.tools.idea.model.ClassJarProvider; +import com.android.tools.idea.projectsystem.AndroidProjectSystem; +import com.android.tools.idea.projectsystem.NamedIdeaSourceProvider; +import com.android.tools.idea.projectsystem.ProjectSystemBuildManager; +import com.android.tools.idea.projectsystem.ProjectSystemSyncManager; +import com.android.tools.idea.projectsystem.ScopeType; +import com.android.tools.idea.projectsystem.SourceProviderManager; +import com.android.tools.idea.projectsystem.SourceProviders; +import com.android.tools.idea.projectsystem.SourceProvidersFactory; +import com.android.tools.idea.projectsystem.SourceProvidersImpl; +import com.android.tools.idea.res.AndroidInnerClassFinder; +import com.android.tools.idea.res.AndroidResourceClassPsiElementFinder; +import com.android.tools.idea.sdk.AndroidSdks; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.idea.blaze.android.resources.BlazeLightResourceClassService; +import com.google.idea.blaze.android.sync.model.idea.BlazeAndroidModel; +import com.google.idea.blaze.android.sync.model.idea.BlazeClassJarProvider; +import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.settings.BlazeImportSettings.ProjectType; +import com.intellij.facet.ProjectFacetManager; +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 com.intellij.psi.PsiElementFinder; +import com.intellij.psi.search.GlobalSearchScope; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import org.jetbrains.android.facet.AndroidFacet; +import org.jetbrains.android.sdk.AndroidPlatforms; +import org.jetbrains.annotations.NotNull; + +/** + * Base class to implement common methods in {@link AndroidProjectSystem} for blaze with different + * sdk + */ +public class BlazeProjectSystem implements AndroidProjectSystem { + protected final Project project; + protected final ProjectSystemSyncManager syncManager; + protected final List myFinders; + private final BlazeProjectSystemBuildManager buildManager; + + public BlazeProjectSystem(Project project) { + this.project = project; + syncManager = new BlazeProjectSystemSyncManager(project); + buildManager = new BlazeProjectSystemBuildManager(project); + + myFinders = + Arrays.asList( + AndroidInnerClassFinder.INSTANCE, + new AndroidResourceClassPsiElementFinder(getLightResourceClassService())); + } + + @Override + public Project getProject() { + return project; + } + + @Override + public boolean allowsFileCreation() { + return true; + } + + @Nullable + @Override + public VirtualFile getDefaultApkFile() { + return null; + } + + @Override + public Path getPathToAapt() { + return AaptInvoker.getPathToAapt( + AndroidSdks.getInstance().tryToChooseSdkHandler(), + new LogWrapper(BlazeProjectSystem.class)); + } + + @Override + public ProjectSystemBuildManager getBuildManager() { + return buildManager; + } + + @Override + public BlazeModuleSystem getModuleSystem(Module module) { + return BlazeModuleSystem.getInstance(module); + } + + @Override + public ProjectSystemSyncManager getSyncManager() { + return syncManager; + } + + @Override + public Collection getPsiElementFinders() { + return myFinders; + } + + @Override + public BlazeLightResourceClassService getLightResourceClassService() { + return BlazeLightResourceClassService.getInstance(project); + } + + @Override + public SourceProvidersFactory getSourceProvidersFactory() { + return new SourceProvidersFactory() { + @Override + public SourceProviders createSourceProvidersFor(AndroidFacet facet) { + BlazeAndroidModel model = ((BlazeAndroidModel) AndroidModel.get(facet)); + if (model != null) { + return createForModel(model); + } else { + return createSourceProvidersForLegacyModule(facet); + } + } + + private SourceProviders createForModel(BlazeAndroidModel model) { + NamedIdeaSourceProvider mainSourceProvider = model.getDefaultSourceProvider(); + if (Blaze.getProjectType(project).equals(ProjectType.QUERY_SYNC)) { + return new SourceProvidersImpl( + mainSourceProvider, + ImmutableList.of(mainSourceProvider), + ImmutableList.of(), + ImmutableList.of(), + ImmutableList.of(), + ImmutableList.of(mainSourceProvider), + ImmutableList.of(mainSourceProvider), + emptySourceProvider(ScopeType.MAIN), + emptySourceProvider(ScopeType.UNIT_TEST), + emptySourceProvider(ScopeType.ANDROID_TEST), + emptySourceProvider(ScopeType.TEST_FIXTURES)); + } else { + return new SourceProvidersImpl( + mainSourceProvider, + ImmutableList.of(mainSourceProvider), + ImmutableList.of(mainSourceProvider), + ImmutableList.of(mainSourceProvider), + ImmutableList.of(mainSourceProvider), + ImmutableList.of(mainSourceProvider), + ImmutableList.of(mainSourceProvider), + emptySourceProvider(ScopeType.MAIN), + emptySourceProvider(ScopeType.UNIT_TEST), + emptySourceProvider(ScopeType.ANDROID_TEST), + emptySourceProvider(ScopeType.TEST_FIXTURES)); + } + } + }; + } + + @Override + public ClassJarProvider getClassJarProvider() { + return new BlazeClassJarProvider(project); + } + + @Override + public Collection getAndroidFacetsWithPackageName( + Project project, String packageName) { + return getAndroidFacetsWithPackageName( + project, packageName, GlobalSearchScope.projectScope(project)); + } + + private Collection getAndroidFacetsWithPackageName( + Project project, String packageName, GlobalSearchScope scope) { + List facets = ProjectFacetManager.getInstance(project).getFacets(AndroidFacet.ID); + return facets.stream() + .filter(facet -> hasPackageName(facet, packageName)) + .filter( + facet -> { + VirtualFile file = SourceProviderManager.getInstance(facet).getMainManifestFile(); + if (file == null) { + return false; + } else { + return scope.contains(file); + } + }) + .collect(Collectors.toList()); + } + + @Override + public boolean isNamespaceOrParentPackage(@NotNull String packageName) { + List facets = ProjectFacetManager.getInstance(project).getFacets(AndroidFacet.ID); + GlobalSearchScope scope = GlobalSearchScope.projectScope(project); + for (AndroidFacet facet : facets) { + String moduleNamespace = PackageNameUtils.getPackageName(facet.getModule()); + if (moduleNamespace == null) { + continue; + } + // Check if the moduleNamespace is exactly the package name, or is a subpackage + if (!moduleNamespace.startsWith(packageName)) { + continue; + } + // packageName=com.example should not match moduleNamespace=com.example2 + if (moduleNamespace.length() > packageName.length() + && moduleNamespace.charAt(packageName.length()) != '.') { + continue; + } + VirtualFile file = SourceProviderManager.getInstance(facet).getMainManifestFile(); + if (file == null || !scope.contains(file)) { + continue; + } + return true; + } + return false; + } + + @NotNull + @Override + public Set getKnownApplicationIds() { + List facets = ProjectFacetManager.getInstance(project).getFacets(AndroidFacet.ID); + Set applicationIds = new HashSet<>(facets.size()); + for (AndroidFacet facet : facets) { + AndroidModel model = AndroidModel.get(facet); + if (model == null) { + continue; + } + applicationIds.addAll(model.getAllApplicationIds()); + } + return Collections.unmodifiableSet(applicationIds); + } + + @NotNull + @Override + public Collection findModulesWithApplicationId(@NotNull String applicationId) { + if (Blaze.getProjectType(project).equals(ProjectType.QUERY_SYNC)) { + Module workspaceModule = + ModuleManager.getInstance(project).findModuleByName(WORKSPACE_MODULE_NAME); + if (workspaceModule != null) { + return ImmutableList.of(workspaceModule); + } else { + return ImmutableList.of(); + } + } + List facets = ProjectFacetManager.getInstance(project).getFacets(AndroidFacet.ID); + ImmutableSet.Builder resultBuilder = ImmutableSet.builder(); + for (AndroidFacet facet : facets) { + AndroidModel model = AndroidModel.get(facet); + if (model != null && model.getApplicationId().equals(applicationId)) { + resultBuilder.add(facet.getModule()); + } + } + return resultBuilder.build(); + } + + @Override + public Collection getSubmodules() { + return ImmutableList.of(); + } + + @Override + public Collection getBootClasspath(@NotNull Module module) { + return AndroidPlatforms.getInstance(module).getTarget().getBootClasspath(); + } + + @Override + public boolean isAndroidProject() { + return ProjectFacetManager.getInstance(project).hasFacets(AndroidFacet.ID); + } + + private static boolean hasPackageName(AndroidFacet facet, String packageName) { + String nameFromFacet = PackageNameUtils.getPackageName(facet.getModule()); + if (nameFromFacet == null) { + return false; + } + return nameFromFacet.equals(packageName); + } +} diff --git a/aswb/sdkcompat/as233/com/google/idea/blaze/android/projectsystem/BlazeToken.java b/aswb/sdkcompat/as233/com/google/idea/blaze/android/projectsystem/BlazeToken.java new file mode 100644 index 00000000000..d9a4b21a732 --- /dev/null +++ b/aswb/sdkcompat/as233/com/google/idea/blaze/android/projectsystem/BlazeToken.java @@ -0,0 +1,27 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.projectsystem; + +import com.android.tools.idea.projectsystem.AndroidProjectSystem; +import com.android.tools.idea.projectsystem.Token; + +/** A mix-in implementation of the {@link Token} interface for the Blaze project system. */ +public interface BlazeToken extends Token { + @Override + default boolean isApplicable(AndroidProjectSystem projectSystem) { + return projectSystem instanceof BlazeProjectSystem; + } +} diff --git a/aswb/sdkcompat/as233/com/google/idea/blaze/android/projectsystem/RenderJarClassFileFinder.java b/aswb/sdkcompat/as233/com/google/idea/blaze/android/projectsystem/RenderJarClassFileFinder.java new file mode 100644 index 00000000000..0967d9fc40c --- /dev/null +++ b/aswb/sdkcompat/as233/com/google/idea/blaze/android/projectsystem/RenderJarClassFileFinder.java @@ -0,0 +1,300 @@ +/* + * Copyright 2020 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.projectsystem; + +import static java.util.stream.Collectors.joining; + +import com.android.tools.idea.projectsystem.ClassContent; +import com.android.tools.idea.projectsystem.ClassFileFinder; +import com.android.tools.idea.projectsystem.ClassFileFinderUtil; +import com.android.tools.idea.rendering.classloading.loaders.JarManager; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableSet; +import com.google.idea.blaze.android.libraries.RenderJarCache; +import com.google.idea.blaze.android.sync.model.AndroidResourceModule; +import com.google.idea.blaze.android.sync.model.AndroidResourceModuleRegistry; +import com.google.idea.blaze.android.targetmaps.TargetToBinaryMap; +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.qsync.QuerySync; +import com.google.idea.blaze.base.qsync.QuerySyncManager; +import com.google.idea.blaze.base.qsync.RenderJarArtifactTracker; +import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.settings.BlazeImportSettings.ProjectType; +import com.google.idea.blaze.base.sync.BlazeSyncModificationTracker; +import com.google.idea.blaze.base.sync.data.BlazeDataStorage; +import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; +import com.google.idea.common.experiments.BoolExperiment; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.StringUtil; +import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.regex.Pattern; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * A {@link ClassFileFinder} that uses deploy JAR like artifacts (called render jar henceforth) for + * class files. + * + *

The render JAR contains all runtime dependencies of a binary target. + * + *

The Blaze targets that go into creating a resource module is known. Consequently, it is + * possible to determine which binaries in the projectview depend on the resource declaring blaze + * targets that constitutes the module. This class calculates the binary targets and attempts to + * find classes from the render JARs. + * + *

This only works for resource modules (i.e. not the .workspace module). For .workspace module, + * we try to find the class in all binary targets in projectview + * + *

NOTE: Blaze targets that constitutes the resource module will be called "resource target(s)" + * in comments below. + */ +public class RenderJarClassFileFinder implements ClassFileFinder { + /** Experiment to control whether class file finding from render jars should be enabled. */ + private static final BoolExperiment enabled = + new BoolExperiment("aswb.renderjar.cff.enabled.3", true); + + /** + * Experiment to toggle whether resource resolution is allowed from Render JARs. Render JARs + * should not resolve resources by default. + */ + @VisibleForTesting + static final BoolExperiment resolveResourceClasses = + new BoolExperiment("aswb.resolve.resources.render.jar", false); + + private static final Logger log = Logger.getInstance(RenderJarClassFileFinder.class); + + private static final String INTERNAL_PACKAGE = "_layoutlib_._internal_."; + + // matches foo.bar.R or foo.bar.R$baz + private static final Pattern RESOURCE_CLASS_NAME = Pattern.compile(".+\\.R(\\$[^.]+)?$"); + + private final Module module; + private final Project project; + + // tracks the binary targets that depend resource targets + // will be recalculated after every sync + private ImmutableSet binaryTargets = ImmutableSet.of(); + + // tracks the value of {@link BlazeSyncModificationTracker} when binaryTargets is calculated + // binaryTargets is calculated when the value of {@link BlazeSyncModificationTracker} does not + // equal lastSyncCount + long lastSyncCount = -1; + + // true if the current module is the .workspace Module + private final boolean isWorkspaceModule; + + private final JarManager jarManager; + + public RenderJarClassFileFinder(Module module) { + this.module = module; + this.project = module.getProject(); + this.isWorkspaceModule = BlazeDataStorage.WORKSPACE_MODULE_NAME.equals(module.getName()); + if (ApplicationManager.getApplication().isUnitTestMode()) { + this.jarManager = null; // TODO(b/311649275): Mock in tests when made an interface. + } else { + this.jarManager = JarManager.getInstance(project); + } + } + + @Nullable + @Override + public ClassContent findClassFile(@NotNull String fqcn) { + if (!isEnabled()) { + return null; + } + + // Ever since Compose support was introduced in AS, finding class files is invoked during the + // normal course of opening an editor. The contract for this method requires that it shouldn't + // throw any exceptions, but we've had a few bugs where this method threw an exception, which + // resulted in users not being able to open Kotlin files at all. In order to avoid this + // scenario, we wrap the underlying call and ensure that no exceptions are thrown. + try { + return findClassContent(fqcn); + } catch (Error e) { + log.warn( + String.format( + "Unexpected error while finding the class file for `%1$s`: %2$s", + fqcn, Throwables.getRootCause(e).getMessage())); + return null; + } + } + + @Nullable + public ClassContent findClassContent(String fqcn) { + // Render JAR should not resolve any resources. All resources should be available to the IDE + // through ResourceRepository. Attempting to resolve resources from Render JAR indicates that + // ASwB hasn't properly set up resources for the project. + if (isResourceClass(fqcn) && !resolveResourceClasses.getValue()) { + log.warn(String.format("Attempting to load resource '%s' from RenderJAR.", fqcn)); + return null; + } + + if (Blaze.getProjectType(project).equals(ProjectType.QUERY_SYNC)) { + return findClassQuerySync(fqcn); + } + return findClassLegacySync(fqcn); + } + + private ClassContent findClassQuerySync(String fqcn) { + if (QuerySync.isComposeEnabled(project)) { + RenderJarArtifactTracker renderJarArtifactTracker = + QuerySyncManager.getInstance(project).getRenderJarArtifactTracker(); + // TODO(b/283280194): Setup fqcn -> target and target -> Render jar mappings to avoid + // iterating over all render jars when trying to locate class for fqcn. + // TODO(b/284002836): Collect metrics on time taken to iterate over the jars + for (File renderJar : renderJarArtifactTracker.getRenderJars()) { + return findClassInJar(renderJar, fqcn); + } + return null; + } + return null; + } + + private ClassContent findClassLegacySync(String fqcn) { + BlazeProjectData projectData = + BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); + if (projectData == null) { + log.warn("Could not find BlazeProjectData for project " + project.getName()); + return null; + } + + ImmutableSet binaryTargets = getBinaryTargets(); + if (binaryTargets.isEmpty()) { + log.warn( + String.format( + "No binaries for module %s. Adding a binary target to the projectview and resyncing" + + " might fix the issue.", + module.getName())); + return null; + } + + // Remove internal package prefix if present + fqcn = StringUtil.trimStart(fqcn, INTERNAL_PACKAGE); + + // Look through render resolve JARs of the binaries that depend on the given + // androidResourceModule. One androidResourceModule can comprise of multiple resource targets. + // The binaries can depend on any subset of these resource targets. Generally, we only + // expect one, or a small number of binaries here. + for (TargetKey binaryTarget : binaryTargets) { + ClassContent classContent = getClassFromRenderResolveJar(projectData, fqcn, binaryTarget); + if (classContent != null) { + return classContent; + } + } + log.warn(String.format("Could not find class `%1$s` (module: `%2$s`)", fqcn, module.getName())); + return null; + } + + @VisibleForTesting + static boolean isResourceClass(String fqcn) { + return RESOURCE_CLASS_NAME.matcher(fqcn).matches(); + } + + /** + * Returns the cached list of binary targets that depend on resource targets. The cache is + * recalculated if the project has been synced since last calculation + */ + private ImmutableSet getBinaryTargets() { + long currentSyncCount = + BlazeSyncModificationTracker.getInstance(project).getModificationCount(); + if (currentSyncCount == lastSyncCount) { + // Return the cached set if there hasn't been a sync since last calculation + return binaryTargets; + } + lastSyncCount = currentSyncCount; + + AndroidResourceModule androidResourceModule = + AndroidResourceModuleRegistry.getInstance(project).get(module); + if (androidResourceModule != null) { + binaryTargets = + TargetToBinaryMap.getInstance(project) + .getBinariesDependingOn(androidResourceModule.sourceTargetKeys); + } else if (isWorkspaceModule) { + binaryTargets = TargetToBinaryMap.getInstance(project).getSourceBinaryTargets(); + } else { + binaryTargets = ImmutableSet.of(); + log.warn("Could not find AndroidResourceModule for " + module.getName()); + } + log.info( + String.format( + "Binary targets for module `%1$s`: %2$s", + module.getName(), + binaryTargets.stream() + .limit(5) + .map(t -> t.getLabel().toString()) + .collect(joining(", ")))); + return binaryTargets; + } + + /** + * Returns class file for fqcn if found in the render JAR corresponding to {@code binaryTarget}. + * Returns null if something goes wrong or if render JAR does not contain fqcn + */ + @Nullable + private ClassContent getClassFromRenderResolveJar( + BlazeProjectData projectData, String fqcn, TargetKey binaryTarget) { + TargetIdeInfo ideInfo = projectData.getTargetMap().get(binaryTarget); + if (ideInfo == null) { + return null; + } + + File renderResolveJarFile = + RenderJarCache.getInstance(project) + .getCachedJarForBinaryTarget(projectData.getArtifactLocationDecoder(), ideInfo); + + if (renderResolveJarFile == null) { + return null; + } + + return findClassInJar(renderResolveJarFile, fqcn); + } + + private ClassContent findClassInJar(File renderResolveJarFile, String fqcn) { + String relativePath = ClassFileFinderUtil.getPathFromFqcn(fqcn); + final byte[] bytes; + if (ApplicationManager.getApplication().isUnitTestMode()) { + try { + Path targetPath = renderResolveJarFile.toPath().resolve("!" + relativePath); + bytes = Files.isRegularFile(targetPath) ? Files.readAllBytes(targetPath) : new byte[0]; + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } else { + bytes = + jarManager.loadFileFromJar( + renderResolveJarFile.toPath(), ClassFileFinderUtil.getPathFromFqcn(fqcn)); + } + if (bytes == null) { + return null; + } + + return ClassContent.fromJarEntryContent(renderResolveJarFile, bytes); + } + + public static boolean isEnabled() { + return enabled.getValue(); + } +} diff --git a/aswb/sdkcompat/as233/com/google/idea/blaze/android/rendering/BlazeRenderErrorContributor.java b/aswb/sdkcompat/as233/com/google/idea/blaze/android/rendering/BlazeRenderErrorContributor.java new file mode 100644 index 00000000000..ce8b52ff595 --- /dev/null +++ b/aswb/sdkcompat/as233/com/google/idea/blaze/android/rendering/BlazeRenderErrorContributor.java @@ -0,0 +1,442 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.rendering; + +import static com.android.SdkConstants.ANDROID_MANIFEST_XML; +import static com.google.common.collect.ImmutableMap.toImmutableMap; + +import com.android.tools.idea.rendering.RenderErrorContributor; +import com.android.tools.idea.rendering.RenderUtils; +import com.android.tools.idea.rendering.errors.ui.RenderErrorModel; +import com.android.tools.idea.ui.designer.EditorDesignSurface; +import com.android.tools.rendering.HtmlLinkManager; +import com.android.tools.rendering.HtmlLinkManagerCompat; +import com.android.tools.rendering.RenderLogger; +import com.android.tools.rendering.RenderResult; +import com.android.utils.HtmlBuilder; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.Maps; +import com.google.common.collect.SortedSetMultimap; +import com.google.common.collect.TreeMultimap; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.idea.blaze.android.sync.model.AndroidResourceModule; +import com.google.idea.blaze.android.sync.model.AndroidResourceModuleRegistry; +import com.google.idea.blaze.base.command.buildresult.OutputArtifactResolver; +import com.google.idea.blaze.base.ideinfo.AndroidResFolder; +import com.google.idea.blaze.base.ideinfo.ArtifactLocation; +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.lang.buildfile.references.BuildReferenceManager; +import com.google.idea.blaze.base.model.BlazeProjectData; +import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.settings.BlazeImportSettings.ProjectType; +import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; +import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder; +import com.google.idea.blaze.base.targetmaps.SourceToTargetMap; +import com.google.idea.blaze.base.targetmaps.TransitiveDependencyMap; +import com.intellij.lang.annotation.HighlightSeverity; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.IndexNotReadyException; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VfsUtilCore; +import com.intellij.psi.JavaPsiFacade; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.search.GlobalSearchScope; +import java.io.File; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.function.Function; +import javax.swing.JEditorPane; +import javax.swing.event.HyperlinkEvent; +import javax.swing.event.HyperlinkListener; +import javax.swing.text.html.HTMLDocument; +import javax.swing.text.html.HTMLFrameHyperlinkEvent; + +/** Contribute blaze specific render errors. */ +public class BlazeRenderErrorContributor implements RenderErrorContributor { + private final EditorDesignSurface designSurface; + private final RenderLogger logger; + private final Module module; + private final PsiFile sourceFile; + private final Project project; + private final HtmlLinkManager linkManager; + private final HyperlinkListener linkHandler; + private final Set issues = new LinkedHashSet<>(); + + public BlazeRenderErrorContributor(EditorDesignSurface surface, RenderResult result) { + designSurface = surface; + logger = result.getLogger(); + module = result.getModule(); + sourceFile = result.getSourceFile(); + project = module.getProject(); + linkManager = logger.getLinkManager(); + linkHandler = + e -> { + if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { + JEditorPane pane = (JEditorPane) e.getSource(); + if (e instanceof HTMLFrameHyperlinkEvent) { + HTMLFrameHyperlinkEvent evt = (HTMLFrameHyperlinkEvent) e; + HTMLDocument doc = (HTMLDocument) pane.getDocument(); + doc.processHTMLFrameHyperlinkEvent(evt); + return; + } + + performClick(e.getDescription()); + } + }; + } + + private HtmlLinkManager getLinkManager() { + return linkManager; + } + + private Collection getIssues() { + return Collections.unmodifiableCollection(issues); + } + + private RenderErrorModel.Issue.Builder addIssue() { + return new RenderErrorModel.Issue.Builder() { + @Override + public RenderErrorModel.Issue build() { + RenderErrorModel.Issue built = super.build(); + issues.add(built); + return built; + } + }.setLinkHandler(linkHandler); + } + + private void performClick(String url) { + linkManager.handleUrl( + url, + module, + sourceFile, + true, + new HtmlLinkManager.RefreshableSurface() { + @Override + public void handleRefreshRenderUrl() { + if (designSurface != null) { + // TODO(b/321801969): Remove and replace with direct call when in repo. + // Use reflection to getConfigurations() from designSurface. Can't call directly + // because it returns an incompatible version of ImmutableCollection. + // RenderUtils.clearCache(designSurface.getConfigurations()); would fail at runtime. + try { + Method getConfigurationsMethod = + EditorDesignSurface.class.getMethod("getConfigurations", null); + Object configurations = getConfigurationsMethod.invoke(designSurface); + Method clearCacheMethod = + RenderUtils.class.getMethod( + "clearCache", getConfigurationsMethod.getReturnType()); + clearCacheMethod.invoke(null, configurations); + } catch (NoSuchMethodException ex) { + throw new RuntimeException( + "Error using reflection to get getConfigurations() instance method: " + ex); + } catch (IllegalAccessException ex) { + throw new RuntimeException( + "Error accessing getConfigurations() instance method" + ex); + } catch (InvocationTargetException ex) { + throw new RuntimeException("Error invoking target getConfigurations(): " + ex); + } + var unused = designSurface.forceUserRequestedRefresh(); + } + } + + @Override + public void requestRender() { + if (designSurface != null) { + var unused = designSurface.forceUserRequestedRefresh(); + } + } + }); + } + + @Override + public Collection reportIssues() { + BlazeProjectData blazeProjectData = + BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); + + if (blazeProjectData == null || !logger.hasErrors()) { + return getIssues(); + } + + if (Blaze.getProjectType(project).equals(ProjectType.QUERY_SYNC)) { + // TODO(b/284002829): Setup resource-module specific issue reporting + return getIssues(); + } + + TargetMap targetMap = blazeProjectData.getTargetMap(); + ArtifactLocationDecoder decoder = blazeProjectData.getArtifactLocationDecoder(); + AndroidResourceModule resourceModule = + AndroidResourceModuleRegistry.getInstance(project).get(module); + if (resourceModule == null) { + return getIssues(); + } + + TargetIdeInfo target = targetMap.get(resourceModule.targetKey); + if (target == null) { + return getIssues(); + } + + reportGeneratedResources(resourceModule, targetMap, decoder); + reportNonStandardAndroidManifestName(target, decoder); + reportResourceTargetShouldDependOnClassTarget(target, targetMap, decoder); + return getIssues(); + } + + /** + * We can't find generated resources. If a layout uses them, the layout won't render correctly. + */ + private void reportGeneratedResources( + AndroidResourceModule resourceModule, TargetMap targetMap, ArtifactLocationDecoder decoder) { + Map brokenClasses = logger.getBrokenClasses(); + if (brokenClasses == null || brokenClasses.isEmpty()) { + return; + } + + // Sorted entries for deterministic error message. + SortedMap generatedResources = + Maps.newTreeMap(getGeneratedResources(targetMap.get(resourceModule.targetKey))); + + for (TargetKey dependency : resourceModule.transitiveResourceDependencies) { + generatedResources.putAll(getGeneratedResources(targetMap.get(dependency))); + } + + if (generatedResources.isEmpty()) { + return; + } + + HtmlBuilder builder = new HtmlBuilder(); + builder.add("Generated resources will not be discovered by the IDE:"); + builder.beginList(); + for (Map.Entry entry : generatedResources.entrySet()) { + ArtifactLocation resource = entry.getKey(); + TargetIdeInfo target = entry.getValue(); + builder.listItem().add(resource.getRelativePath()).add(" from "); + addTargetLink(builder, target, decoder); + } + builder + .endList() + .add("Please avoid using generated resources, ") + .addLink("then ", "sync the project", " ", getLinkManager().createSyncProjectUrl()) + .addLink("and ", "refresh the layout", ".", getLinkManager().createRefreshRenderUrl()); + addIssue() + .setSeverity(HighlightSeverity.ERROR, HIGH_PRIORITY + 1) // Reported above broken classes + .setSummary("Generated resources") + .setHtmlContent(builder) + .build(); + } + + private static SortedMap getGeneratedResources( + TargetIdeInfo target) { + if (target == null || target.getAndroidIdeInfo() == null) { + return Collections.emptySortedMap(); + } + SortedMap generatedResources = Maps.newTreeMap(); + generatedResources.putAll( + target.getAndroidIdeInfo().getResources().stream() + .map(AndroidResFolder::getRoot) + .filter(ArtifactLocation::isGenerated) + .collect(toImmutableMap(Function.identity(), resource -> target))); + return generatedResources; + } + + /** + * When the Android manifest isn't AndroidManifest.xml, resolving resource IDs would fail. This + * doesn't seem to be an issue if the manifest belongs to one of the target's dependencies. + */ + private void reportNonStandardAndroidManifestName( + TargetIdeInfo target, ArtifactLocationDecoder decoder) { + if (target.getAndroidIdeInfo() == null || target.getAndroidIdeInfo().getManifest() == null) { + return; + } + + Map brokenClasses = logger.getBrokenClasses(); + if (brokenClasses == null || brokenClasses.isEmpty()) { + return; + } + + ArtifactLocation maniftestArtifactLocation = target.getAndroidIdeInfo().getManifest(); + + File manifest = + Preconditions.checkNotNull( + OutputArtifactResolver.resolve(project, decoder, maniftestArtifactLocation), + "Fail to find file %s", + maniftestArtifactLocation.getRelativePath()); + if (manifest.getName().equals(ANDROID_MANIFEST_XML)) { + return; + } + + HtmlBuilder builder = new HtmlBuilder(); + addTargetLink(builder, target, decoder) + .add(" uses a non-standard name for the Android manifest: "); + String linkToManifest = HtmlLinkManagerCompat.createFilePositionUrl(manifest, -1, 0); + if (linkToManifest != null) { + builder.addLink(manifest.getName(), linkToManifest); + } else { + builder.newline().add(manifest.getPath()); + } + // TODO: add a link to automatically rename the file and refactor all references. + builder + .newline() + .add("Please rename it to ") + .add(ANDROID_MANIFEST_XML) + .addLink(", then ", "sync the project", "", getLinkManager().createSyncProjectUrl()) + .addLink(" and ", "refresh the layout", ".", getLinkManager().createRefreshRenderUrl()); + addIssue() + .setSeverity(HighlightSeverity.ERROR, HIGH_PRIORITY + 1) // Reported above broken classes. + .setSummary("Non-standard manifest name") + .setHtmlContent(builder) + .build(); + } + + /** + * Blaze doesn't resolve class dependencies from resources until building the final + * android_binary, so we could end up with resources that ultimately build correctly, but fail to + * find their class dependencies during rendering in the layout editor. + */ + @SuppressWarnings("rawtypes") + private void reportResourceTargetShouldDependOnClassTarget( + TargetIdeInfo target, TargetMap targetMap, ArtifactLocationDecoder decoder) { + Set missingClasses = logger.getMissingClasses(); + if (missingClasses == null || missingClasses.isEmpty()) { + return; + } + + // Sorted entries for deterministic error message. + SortedSetMultimap missingClassToTargetMap = TreeMultimap.create(); + + SourceToTargetMap sourceToTargetMap = SourceToTargetMap.getInstance(project); + ImmutableCollection transitiveDependencies = + TransitiveDependencyMap.getInstance(project).getTransitiveDependencies(target.getKey()); + + for (String missingClass : missingClasses) { + File sourceFile = getSourceFileForClass(missingClass); + if (sourceFile == null) { + continue; + } + ImmutableCollection sourceTargets = + sourceToTargetMap.getRulesForSourceFile(sourceFile); + if (sourceTargets.stream() + .noneMatch( + sourceTarget -> + sourceTarget.equals(target.getKey()) + || transitiveDependencies.contains(sourceTarget))) { + missingClassToTargetMap.putAll(missingClass, sourceTargets); + } + } + + if (missingClassToTargetMap.isEmpty()) { + return; + } + + HtmlBuilder builder = new HtmlBuilder(); + addTargetLink(builder, target, decoder) + .add(" contains resource files that reference these classes:") + .beginList(); + for (String missingClass : missingClassToTargetMap.keySet()) { + builder + .listItem() + .addLink(missingClass, getLinkManager().createOpenClassUrl(missingClass)) + .add(" from "); + for (TargetKey targetKey : missingClassToTargetMap.get(missingClass)) { + addTargetLink(builder, targetMap.get(targetKey), decoder).add(" "); + } + } + builder.endList().add("Please fix your dependencies so that "); + addTargetLink(builder, target, decoder) + .add(" correctly depends on these classes, ") + .addLink("then ", "sync the project", " ", getLinkManager().createSyncProjectUrl()) + .addLink("and ", "refresh the layout", ".", getLinkManager().createRefreshRenderUrl()) + .newline() + .newline() + .addBold( + "NOTE: blaze can still build with the incorrect dependencies " + + "due to the way it handles resources, " + + "but the layout editor needs them to be correct."); + + addIssue() + .setSeverity(HighlightSeverity.ERROR, HIGH_PRIORITY + 1) // Reported above missing classes. + .setSummary("Missing class dependencies") + .setHtmlContent(builder) + .build(); + } + + private File getSourceFileForClass(String className) { + return ApplicationManager.getApplication() + .runReadAction( + (Computable) + () -> { + try { + PsiClass psiClass = + JavaPsiFacade.getInstance(project) + .findClass(className, GlobalSearchScope.projectScope(project)); + if (psiClass == null) { + return null; + } + return VfsUtilCore.virtualToIoFile( + psiClass.getContainingFile().getVirtualFile()); + } catch (IndexNotReadyException ignored) { + // We're in dumb mode. Abort! Abort! + return null; + } + }); + } + + @CanIgnoreReturnValue + private HtmlBuilder addTargetLink( + HtmlBuilder builder, TargetIdeInfo target, ArtifactLocationDecoder decoder) { + File buildFile = + Preconditions.checkNotNull( + OutputArtifactResolver.resolve(project, decoder, target.getBuildFile()), + "Fail to find file %s", + target.getBuildFile().getRelativePath()); + int line = + ApplicationManager.getApplication() + .runReadAction( + (Computable) + () -> { + PsiElement buildTargetPsi = + BuildReferenceManager.getInstance(project) + .resolveLabel(target.getKey().getLabel()); + if (buildTargetPsi == null) { + return -1; + } + PsiFile psiFile = buildTargetPsi.getContainingFile(); + if (psiFile == null) { + return -1; + } + return StringUtil.offsetToLineNumber( + psiFile.getText(), buildTargetPsi.getTextOffset()); + }); + String url = HtmlLinkManagerCompat.createFilePositionUrl(buildFile, line, 0); + if (url != null) { + return builder.addLink(target.toString(), url); + } + return builder.add(target.toString()); + } +} diff --git a/aswb/sdkcompat/as233/com/google/idea/blaze/android/rendering/BlazeRenderSecurityManagerOverrides.java b/aswb/sdkcompat/as233/com/google/idea/blaze/android/rendering/BlazeRenderSecurityManagerOverrides.java new file mode 100644 index 00000000000..39d3edd195f --- /dev/null +++ b/aswb/sdkcompat/as233/com/google/idea/blaze/android/rendering/BlazeRenderSecurityManagerOverrides.java @@ -0,0 +1,27 @@ +/* + * Copyright 2018 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.rendering; + +import com.android.tools.rendering.security.RenderSecurityManagerOverrides; + +/** Overrides some security restrictions used by the render sandbox. */ +public class BlazeRenderSecurityManagerOverrides implements RenderSecurityManagerOverrides { + @Override + public boolean allowsPropertiesAccess() { + // System properties access is needed for SystemPropertyExperimentLoader + return true; + } +} diff --git a/aswb/sdkcompat/as233/com/google/idea/blaze/android/resources/BlazeLightResourceClassServiceBase.java b/aswb/sdkcompat/as233/com/google/idea/blaze/android/resources/BlazeLightResourceClassServiceBase.java new file mode 100644 index 00000000000..be9c3b2d882 --- /dev/null +++ b/aswb/sdkcompat/as233/com/google/idea/blaze/android/resources/BlazeLightResourceClassServiceBase.java @@ -0,0 +1,64 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.resources; + +import com.android.tools.idea.projectsystem.LightResourceClassService; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.google.idea.blaze.base.sync.data.BlazeDataStorage; +import com.google.idea.common.experiments.FeatureRolloutExperiment; +import com.intellij.openapi.module.Module; +import com.intellij.psi.PsiClass; +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +/** Implementation of {@link LightResourceClassService} set up at Blaze sync time. */ +public abstract class BlazeLightResourceClassServiceBase implements LightResourceClassService { + + @VisibleForTesting + public static final FeatureRolloutExperiment workspaceResourcesFeature = + new FeatureRolloutExperiment("aswb.workspace.light.class.enabled"); + + Map rClasses = Maps.newHashMap(); + Map rClassesByModule = Maps.newHashMap(); + final Set allRClasses = Sets.newHashSet(); + + @Override + public Collection getLightRClassesAccessibleFromModule(Module module) { + if (workspaceResourcesFeature.isEnabled() + && module.getName().equals(BlazeDataStorage.WORKSPACE_MODULE_NAME)) { + // Returns all the packages in resource modules, and all the workspace packages that + // have previously been asked for. All `res/` directories in our project should belong to a + // resource module. For java sources, IntelliJ will ask for explicit resource package by + // calling `getLightRClasses` at which point we can create the package. This is not completely + // correct and the autocomplete will be slightly off when initial `R` is typed in the editor, + // but this workaround is being used to mitigate issues (b/136685602) while resources + // are re-worked. + return allRClasses; + } else { + return rClasses.values(); + } + } + + @Override + public Collection getLightRClassesDefinedByModule(Module module) { + BlazeRClass rClass = rClassesByModule.get(module); + return rClass == null ? ImmutableSet.of() : ImmutableSet.of(rClass); + } +} diff --git a/aswb/sdkcompat/as233/com/google/idea/blaze/android/resources/BlazeRClass.java b/aswb/sdkcompat/as233/com/google/idea/blaze/android/resources/BlazeRClass.java new file mode 100644 index 00000000000..3c64683d517 --- /dev/null +++ b/aswb/sdkcompat/as233/com/google/idea/blaze/android/resources/BlazeRClass.java @@ -0,0 +1,82 @@ +/* + * Copyright 2018 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.resources; + +import static org.jetbrains.android.AndroidResolveScopeEnlarger.LIGHT_CLASS_KEY; +import static org.jetbrains.android.AndroidResolveScopeEnlarger.MODULE_POINTER_KEY; + +import com.android.ide.common.rendering.api.ResourceNamespace; +import com.android.tools.idea.res.ResourceRepositoryRClass; +import com.android.tools.idea.res.StudioResourceRepositoryManager; +import com.android.tools.res.LocalResourceRepository; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModulePointerManager; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiManager; +import org.jetbrains.android.augment.AndroidLightField; +import org.jetbrains.android.facet.AndroidFacet; + +/** Blaze implementation of an R class based on resource repositories. */ +public class BlazeRClass extends ResourceRepositoryRClass { + + private final AndroidFacet androidFacet; + + public BlazeRClass(PsiManager psiManager, AndroidFacet androidFacet, String packageName) { + super( + psiManager, + new ResourcesSource() { + @Override + public String getPackageName() { + return packageName; + } + + // @Override #api4.1 + public Transitivity getTransitivity() { + return Transitivity.TRANSITIVE; + } + + @Override + public StudioResourceRepositoryManager getResourceRepositoryManager() { + return StudioResourceRepositoryManager.getInstance(androidFacet); + } + + @Override + public LocalResourceRepository getResourceRepository() { + return StudioResourceRepositoryManager.getAppResources(androidFacet); + } + + @Override + public ResourceNamespace getResourceNamespace() { + return ResourceNamespace.RES_AUTO; + } + + @Override + public AndroidLightField.FieldModifier getFieldModifier() { + return AndroidLightField.FieldModifier.NON_FINAL; + } + }); + this.androidFacet = androidFacet; + setModuleInfo(getModule(), false); + VirtualFile virtualFile = myFile.getViewProvider().getVirtualFile(); + virtualFile.putUserData( + MODULE_POINTER_KEY, ModulePointerManager.getInstance(getProject()).create(getModule())); + virtualFile.putUserData(LIGHT_CLASS_KEY, ResourceRepositoryRClass.class); + } + + public Module getModule() { + return androidFacet.getModule(); + } +} diff --git a/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/BazelApplicationProjectContext.java b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/BazelApplicationProjectContext.java new file mode 100644 index 00000000000..cbca2310836 --- /dev/null +++ b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/BazelApplicationProjectContext.java @@ -0,0 +1,61 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.android.tools.idea.projectsystem.ApplicationProjectContext; +import com.android.tools.idea.run.ApkProvisionException; +import com.android.tools.idea.run.ApplicationIdProvider; +import com.intellij.openapi.project.Project; +import java.util.function.Supplier; + +/** + * An implementation of {@link ApplicationProjectContext} used in the Blaze project system. + * + *

Note: The Blaze project system assumes all instances of the {@link + * ApplicationProjectContext} associated with its projects to be backed by this specific class. + */ +public class BazelApplicationProjectContext implements ApplicationProjectContext { + + private final Project project; + private final Supplier applicationId; + + public BazelApplicationProjectContext(Project project, String applicationId) { + this.project = project; + this.applicationId = () -> applicationId; + } + + public BazelApplicationProjectContext( + Project project, ApplicationIdProvider applicationIdProvider) { + this.project = project; + this.applicationId = + () -> { + try { + return applicationIdProvider.getPackageName(); + } catch (ApkProvisionException e) { + throw new RuntimeException(e); + } + }; + } + + public Project getProject() { + return project; + } + + @Override + public String getApplicationId() { + return applicationId.get(); + } +} diff --git a/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/BazelApplicationProjectContextProvider.java b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/BazelApplicationProjectContextProvider.java new file mode 100644 index 00000000000..6a1758091f3 --- /dev/null +++ b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/BazelApplicationProjectContextProvider.java @@ -0,0 +1,49 @@ +/* + * Copyright 2024 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.android.ddmlib.Client; +import com.android.ddmlib.ClientData; +import com.android.tools.idea.projectsystem.ApplicationProjectContext; +import com.android.tools.idea.projectsystem.ApplicationProjectContextProvider; +import com.google.idea.blaze.android.projectsystem.BlazeToken; +import com.intellij.openapi.project.Project; +import javax.annotation.Nullable; + +/** An implementation of {@link ApplicationProjectContextProvider} for the Blaze project system. */ +public class BazelApplicationProjectContextProvider + implements ApplicationProjectContextProvider, BlazeToken { + + private final Project project; + + public BazelApplicationProjectContextProvider(Project project) { + this.project = project; + } + + @Nullable + @Override + public ApplicationProjectContext getApplicationProjectContext(Client client) { + ClientData clientData = client.getClientData(); + if (clientData == null) { + return null; + } + String androidPackageName = clientData.getPackageName(); + if (androidPackageName == null) { + return null; + } + return new BazelApplicationProjectContext(project, androidPackageName); + } +} diff --git a/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/BlazeNativeDebuggerAppContextProvider.java b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/BlazeNativeDebuggerAppContextProvider.java new file mode 100644 index 00000000000..df4cd15d3cf --- /dev/null +++ b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/BlazeNativeDebuggerAppContextProvider.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.android.sdklib.devices.Abi; +import com.android.tools.idea.projectsystem.ApplicationProjectContext; +import com.android.tools.ndk.run.NativeDebuggerAppContext; +import com.android.tools.ndk.run.NativeDebuggerAppContextProvider; +import com.android.tools.ndk.run.SymbolDir; +import com.google.idea.blaze.android.projectsystem.BlazeToken; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import java.io.File; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** An implementation of {@link NativeDebuggerAppContextProvider} for the Blaze project system. */ +public class BlazeNativeDebuggerAppContextProvider + implements NativeDebuggerAppContextProvider, BlazeToken { + + @Override + public NativeDebuggerAppContext getNativeDebuggerAppContext( + ApplicationProjectContext applicationProjectContext) { + final var context = (BazelApplicationProjectContext) applicationProjectContext; + // TODO(solodkyy): Find out whether any configuration that is supposed to be part of this + // context is passed to the debugger in oher ways and make sure this is the + // only way to set up the debugger. + return new NativeDebuggerAppContext() { + @Override + public Project getProject() { + return context.getProject(); + } + + @Override + public String getApplicationId() { + return context.getApplicationId(); + } + + @Override + public Collection getSymDirs(List abis) { + return Collections.emptyList(); + } + + @Override + public Map getSourceMap() { + return Collections.emptyMap(); + } + + @Override + public Map getExplicitModuleSymbolMap(Abi abi) { + return Collections.emptyMap(); + } + + @Override + public Collection getModulesToVerify() { + return Collections.emptyList(); + } + }; + } +} diff --git a/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryApplicationLaunchTaskProvider.java b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryApplicationLaunchTaskProvider.java new file mode 100644 index 00000000000..948dae75f36 --- /dev/null +++ b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryApplicationLaunchTaskProvider.java @@ -0,0 +1,72 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.binary; + +import com.android.tools.idea.run.ApkProvisionException; +import com.android.tools.idea.run.ApplicationIdProvider; +import com.android.tools.idea.run.activity.StartActivityFlagsProvider; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; +import com.google.idea.blaze.android.manifest.ManifestParser; +import com.google.idea.blaze.android.run.binary.tasks.AndroidDeepLinkLaunchTask; +import com.google.idea.blaze.android.run.binary.tasks.BlazeDefaultActivityLaunchTask; +import com.google.idea.blaze.android.run.binary.tasks.SpecificActivityLaunchTask; +import com.intellij.execution.ExecutionException; +import com.intellij.openapi.diagnostic.Logger; + +/** Provides the launch task for android_binary */ +public class BlazeAndroidBinaryApplicationLaunchTaskProvider { + private static final Logger LOG = + Logger.getInstance(BlazeAndroidBinaryApplicationLaunchTaskProvider.class); + + public static BlazeLaunchTask getApplicationLaunchTask( + ApplicationIdProvider applicationIdProvider, + ManifestParser.ParsedManifest mergedManifestParsedManifest, + BlazeAndroidBinaryRunConfigurationState configState, + StartActivityFlagsProvider startActivityFlagsProvider) + throws ExecutionException { + String applicationId; + try { + applicationId = applicationIdProvider.getPackageName(); + } catch (ApkProvisionException e) { + throw new ExecutionException("Unable to identify application id"); + } + + switch (configState.getMode()) { + case BlazeAndroidBinaryRunConfigurationState.LAUNCH_DEFAULT_ACTIVITY: + BlazeDefaultActivityLocator activityLocator = + new BlazeDefaultActivityLocator(mergedManifestParsedManifest); + return new BlazeDefaultActivityLaunchTask( + applicationId, activityLocator, startActivityFlagsProvider); + case BlazeAndroidBinaryRunConfigurationState.LAUNCH_SPECIFIC_ACTIVITY: + return launchContext -> + new SpecificActivityLaunchTask( + applicationId, configState.getActivityClass(), startActivityFlagsProvider) + .run( + launchContext.getDevice(), + launchContext.getProgressIndicator(), + launchContext.getConsoleView()); + case BlazeAndroidBinaryRunConfigurationState.LAUNCH_DEEP_LINK: + return launchContext -> + new AndroidDeepLinkLaunchTask(configState.getDeepLink(), startActivityFlagsProvider) + .run( + launchContext.getDevice(), + launchContext.getProgressIndicator(), + launchContext.getConsoleView()); + default: + return null; + } + } +} diff --git a/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContext.java b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContext.java new file mode 100644 index 00000000000..d56f73e4fd4 --- /dev/null +++ b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContext.java @@ -0,0 +1,132 @@ +/* + * Copyright 2019 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.binary; + +import com.android.ddmlib.ClientData; +import com.android.ddmlib.IDevice; +import com.android.tools.idea.execution.common.debug.AndroidDebugger; +import com.android.tools.idea.execution.common.debug.AndroidDebuggerState; +import com.android.tools.idea.execution.common.debug.DebugSessionStarter; +import com.android.tools.idea.projectsystem.ApplicationProjectContext; +import com.android.tools.idea.run.ApkProvisionException; +import com.android.tools.idea.run.activity.DefaultStartActivityFlagsProvider; +import com.android.tools.idea.run.activity.StartActivityFlagsProvider; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; +import com.google.idea.blaze.android.run.BazelApplicationProjectContext; +import com.google.idea.blaze.android.run.deployinfo.BlazeAndroidDeployInfo; +import com.google.idea.blaze.android.run.runner.ApkBuildStep; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.Executor; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.ui.ConsoleView; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.project.Project; +import com.intellij.xdebugger.XDebugSession; +import javax.annotation.Nullable; +import kotlin.Unit; +import kotlin.coroutines.EmptyCoroutineContext; +import kotlinx.coroutines.BuildersKt; +import org.jetbrains.android.facet.AndroidFacet; + +/** Compat for #api212 */ +public class BlazeAndroidBinaryNormalBuildRunContext + extends BlazeAndroidBinaryNormalBuildRunContextBase { + BlazeAndroidBinaryNormalBuildRunContext( + Project project, + AndroidFacet facet, + RunConfiguration runConfiguration, + ExecutionEnvironment env, + BlazeAndroidBinaryRunConfigurationState configState, + ApkBuildStep buildStep, + String launchId) { + super(project, facet, runConfiguration, env, configState, buildStep, launchId); + } + + @Override + public ApplicationProjectContext getApplicationProjectContext() { + return new BazelApplicationProjectContext(project, getApplicationIdProvider()); + } + + @Override + public BlazeLaunchTask getApplicationLaunchTask( + boolean isDebug, @Nullable Integer userId, String contributorsAmStartOptions) + throws ExecutionException { + String extraFlags = UserIdHelper.getFlagsFromUserId(userId); + if (!contributorsAmStartOptions.isEmpty()) { + extraFlags += (extraFlags.isEmpty() ? "" : " ") + contributorsAmStartOptions; + } + if (isDebug) { + extraFlags += (extraFlags.isEmpty() ? "" : " ") + "-D"; + } + + final StartActivityFlagsProvider startActivityFlagsProvider = + new DefaultStartActivityFlagsProvider(project, isDebug, extraFlags); + + BlazeAndroidDeployInfo deployInfo; + try { + deployInfo = buildStep.getDeployInfo(); + } catch (ApkProvisionException e) { + throw new ExecutionException(e); + } + + return BlazeAndroidBinaryApplicationLaunchTaskProvider.getApplicationLaunchTask( + applicationIdProvider, + deployInfo.getMergedManifest(), + configState, + startActivityFlagsProvider); + } + + @Nullable + @Override + public XDebugSession startDebuggerSession( + AndroidDebugger androidDebugger, + AndroidDebuggerState androidDebuggerState, + ExecutionEnvironment env, + IDevice device, + ConsoleView consoleView, + ProgressIndicator indicator, + String packageName) { + try { + return BuildersKt.runBlocking( + EmptyCoroutineContext.INSTANCE, + (scope, continuation) -> + DebugSessionStarter.INSTANCE.attachDebuggerToStartedProcess( + device, + new BazelApplicationProjectContext(project, packageName), + env, + androidDebugger, + androidDebuggerState, + /*destroyRunningProcess*/ d -> { + d.forceStop(packageName); + return Unit.INSTANCE; + }, + indicator, + consoleView, + 15L, + ClientData.DebuggerStatus.WAITING, + continuation)); + } catch (InterruptedException e) { + throw new ProcessCanceledException(); + } + } + + @Override + public Executor getExecutor() { + return env.getExecutor(); + } +} diff --git a/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContextBase.java b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContextBase.java new file mode 100644 index 00000000000..96f3f8be324 --- /dev/null +++ b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContextBase.java @@ -0,0 +1,186 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.binary; + +import static com.google.idea.blaze.android.run.runner.BlazeAndroidLaunchTasksProvider.NATIVE_DEBUGGING_ENABLED; + +import com.android.ddmlib.IDevice; +import com.android.tools.idea.execution.common.DeployOptions; +import com.android.tools.idea.gradle.util.DynamicAppUtils; +import com.android.tools.idea.run.ApkFileUnit; +import com.android.tools.idea.run.ApkInfo; +import com.android.tools.idea.run.ApkProvider; +import com.android.tools.idea.run.ApkProvisionException; +import com.android.tools.idea.run.ApplicationIdProvider; +import com.android.tools.idea.run.ConsoleProvider; +import com.android.tools.idea.run.LaunchOptions; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; +import com.android.tools.idea.run.blaze.BlazeLaunchTasksProvider; +import com.android.tools.idea.run.editor.ProfilerState; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.idea.blaze.android.run.deployinfo.BlazeApkProviderService; +import com.google.idea.blaze.android.run.runner.ApkBuildStep; +import com.google.idea.blaze.android.run.runner.BlazeAndroidDeviceSelector; +import com.google.idea.blaze.android.run.runner.BlazeAndroidLaunchTasksProvider; +import com.google.idea.blaze.android.run.runner.BlazeAndroidRunContext; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.Executor; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.openapi.project.Project; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import org.jetbrains.android.facet.AndroidFacet; +import org.jetbrains.annotations.NotNull; + +/** Run context for android_binary. */ +public abstract class BlazeAndroidBinaryNormalBuildRunContextBase + implements BlazeAndroidRunContext { + protected final Project project; + protected final AndroidFacet facet; + protected final RunConfiguration runConfiguration; + protected final ExecutionEnvironment env; + protected final BlazeAndroidBinaryRunConfigurationState configState; + protected final ConsoleProvider consoleProvider; + protected final ApkBuildStep buildStep; + protected final ApkProvider apkProvider; + protected final ApplicationIdProvider applicationIdProvider; + private final String launchId; + + BlazeAndroidBinaryNormalBuildRunContextBase( + Project project, + AndroidFacet facet, + RunConfiguration runConfiguration, + ExecutionEnvironment env, + BlazeAndroidBinaryRunConfigurationState configState, + ApkBuildStep buildStep, + String launchId) { + this.project = project; + this.facet = facet; + this.runConfiguration = runConfiguration; + this.env = env; + this.configState = configState; + this.consoleProvider = new BlazeAndroidBinaryConsoleProvider(project); + this.buildStep = buildStep; + this.apkProvider = BlazeApkProviderService.getInstance().getApkProvider(project, buildStep); + this.applicationIdProvider = new BlazeAndroidBinaryApplicationIdProvider(buildStep); + this.launchId = launchId; + } + + @Override + public BlazeAndroidDeviceSelector getDeviceSelector() { + return new BlazeAndroidDeviceSelector.NormalDeviceSelector(); + } + + @Override + public void augmentLaunchOptions(LaunchOptions.Builder options) { + LaunchOptions.Builder unused = + options + .setDeploy(true) + .setOpenLogcatAutomatically(configState.showLogcatAutomatically()) + .addExtraOptions( + ImmutableMap.of( + NATIVE_DEBUGGING_ENABLED, + configState.getCommonState().isNativeDebuggingEnabled())) + .setClearAppStorage(configState.getClearAppStorage()); + } + + @Override + public ConsoleProvider getConsoleProvider() { + return consoleProvider; + } + + @Override + public ApplicationIdProvider getApplicationIdProvider() { + return applicationIdProvider; + } + + @Override + public ApkBuildStep getBuildStep() { + return buildStep; + } + + @Nullable + @Override + public Integer getUserId(IDevice device) throws ExecutionException { + return UserIdHelper.getUserIdFromConfigurationState(project, device, configState); + } + + @Override + public BlazeLaunchTasksProvider getLaunchTasksProvider(LaunchOptions launchOptions) + throws ExecutionException { + return new BlazeAndroidLaunchTasksProvider(project, this, applicationIdProvider, launchOptions); + } + + @Override + public String getAmStartOptions() { + return configState.getAmStartOptions(); + } + + @Nullable + @Override + public ImmutableList getDeployTasks(IDevice device, DeployOptions deployOptions) + throws ExecutionException { + return ImmutableList.of( + new DeploymentTimingReporterTask( + launchId, + project, + getApkInfoToInstall(device, deployOptions, apkProvider), + deployOptions)); + } + + /** Returns a list of APKs excluding any APKs for features that are disabled. */ + public static List getApkInfoToInstall( + IDevice device, DeployOptions deployOptions, ApkProvider apkProvider) + throws ExecutionException { + Collection apks; + try { + apks = apkProvider.getApks(device); + } catch (ApkProvisionException e) { + throw new ExecutionException(e); + } + List disabledFeatures = deployOptions.getDisabledDynamicFeatures(); + return apks.stream() + .map(apk -> getApkInfoToInstall(apk, disabledFeatures)) + .collect(Collectors.toList()); + } + + @NotNull + private static ApkInfo getApkInfoToInstall(ApkInfo apkInfo, List disabledFeatures) { + if (apkInfo.getFiles().size() > 1) { + List filteredApks = + apkInfo.getFiles().stream() + .filter(feature -> DynamicAppUtils.isFeatureEnabled(disabledFeatures, feature)) + .collect(Collectors.toList()); + return new ApkInfo(filteredApks, apkInfo.getApplicationId()); + } else { + return apkInfo; + } + } + + @Override + public Executor getExecutor() { + return env.getExecutor(); + } + + @Override + public ProfilerState getProfileState() { + return configState.getProfilerState(); + } +} diff --git a/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContextCompat.java b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContextCompat.java new file mode 100644 index 00000000000..27f330a86e8 --- /dev/null +++ b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContextCompat.java @@ -0,0 +1,86 @@ +/* + * Copyright 2022 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.binary; + +import com.android.ddmlib.ClientData; +import com.android.ddmlib.IDevice; +import com.android.tools.idea.execution.common.debug.AndroidDebugger; +import com.android.tools.idea.execution.common.debug.AndroidDebuggerState; +import com.android.tools.idea.execution.common.debug.DebugSessionStarter; +import com.google.idea.blaze.android.run.BazelApplicationProjectContext; +import com.google.idea.blaze.android.run.runner.ApkBuildStep; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.ui.ConsoleView; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.project.Project; +import com.intellij.xdebugger.XDebugSession; +import javax.annotation.Nullable; +import kotlin.Unit; +import kotlin.coroutines.EmptyCoroutineContext; +import kotlinx.coroutines.BuildersKt; +import org.jetbrains.android.facet.AndroidFacet; + +/** Compat class for {@link BlazeAndroidBinaryNormalBuildRunContext}. */ +public class BlazeAndroidBinaryNormalBuildRunContextCompat + extends BlazeAndroidBinaryNormalBuildRunContext { + + BlazeAndroidBinaryNormalBuildRunContextCompat( + Project project, + AndroidFacet facet, + RunConfiguration runConfiguration, + ExecutionEnvironment env, + BlazeAndroidBinaryRunConfigurationState configState, + ApkBuildStep buildStep, + String launchId) { + super(project, facet, runConfiguration, env, configState, buildStep, launchId); + } + + @Nullable + @Override + public XDebugSession startDebuggerSession( + AndroidDebugger androidDebugger, + AndroidDebuggerState androidDebuggerState, + ExecutionEnvironment env, + IDevice device, + ConsoleView consoleView, + ProgressIndicator indicator, + String packageName) { + try { + return BuildersKt.runBlocking( + EmptyCoroutineContext.INSTANCE, + (scope, continuation) -> + DebugSessionStarter.INSTANCE.attachDebuggerToStartedProcess( + device, + new BazelApplicationProjectContext(project, packageName), + env, + androidDebugger, + androidDebuggerState, + /*destroyRunningProcess*/ d -> { + d.forceStop(packageName); + return Unit.INSTANCE; + }, + indicator, + consoleView, + 15L, + ClientData.DebuggerStatus.WAITING, + continuation)); + } catch (InterruptedException e) { + throw new ProcessCanceledException(); + } + } +} diff --git a/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryProgramRunner.java b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryProgramRunner.java new file mode 100644 index 00000000000..60bd11debaf --- /dev/null +++ b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryProgramRunner.java @@ -0,0 +1,114 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.binary; + +import static com.intellij.openapi.application.ModalityState.NON_MODAL; + +import com.android.tools.idea.execution.common.AndroidConfigurationExecutor; +import com.android.tools.idea.profilers.ProfileRunExecutor; +import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationHandler; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.ExecutionResult; +import com.intellij.execution.Executor; +import com.intellij.execution.configurations.RunProfile; +import com.intellij.execution.configurations.RunProfileState; +import com.intellij.execution.configurations.RunnerSettings; +import com.intellij.execution.executors.DefaultDebugExecutor; +import com.intellij.execution.executors.DefaultRunExecutor; +import com.intellij.execution.runners.AsyncProgramRunner; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.runners.RunContentBuilder; +import com.intellij.execution.ui.RunContentDescriptor; +import com.intellij.openapi.application.ActionsKt; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; +import org.jetbrains.concurrency.AsyncPromise; +import org.jetbrains.concurrency.Promise; + +/** Program runner for configurations from {@link BlazeAndroidBinaryRunConfigurationHandler}. */ +public class BlazeAndroidBinaryProgramRunner extends AsyncProgramRunner { + @Override + public boolean canRun(String executorId, RunProfile profile) { + BlazeAndroidRunConfigurationHandler handler = + BlazeAndroidRunConfigurationHandler.getHandlerFrom(profile); + if (!(handler instanceof BlazeAndroidBinaryRunConfigurationHandler)) { + return false; + } + return (DefaultDebugExecutor.EXECUTOR_ID.equals(executorId) + || DefaultRunExecutor.EXECUTOR_ID.equals(executorId) + || ProfileRunExecutor.EXECUTOR_ID.equals(executorId)); + } + + @Override + protected Promise execute( + ExecutionEnvironment environment, RunProfileState state) { + FileDocumentManager.getInstance().saveAllDocuments(); + + AsyncPromise promise = new AsyncPromise<>(); + + ProgressManager.getInstance() + .run( + new Task.Backgroundable(environment.getProject(), "Launching ${runProfile.name}") { + @Override + public void run(ProgressIndicator indicator) { + try { + RunContentDescriptor descriptor; + if (state instanceof AndroidConfigurationExecutor) { + AndroidConfigurationExecutor configurationExecutor = + (AndroidConfigurationExecutor) state; + Executor executor = environment.getExecutor(); + if (executor.getId().equals(DefaultDebugExecutor.EXECUTOR_ID)) { + descriptor = configurationExecutor.debug(indicator); + } else if (executor.getId().equals(DefaultRunExecutor.EXECUTOR_ID) + || executor.getId().equals(ProfileRunExecutor.EXECUTOR_ID)) { + descriptor = configurationExecutor.run(indicator); + } else { + throw new ExecutionException("Unsupported executor"); + } + } else { + descriptor = doExecute(state, environment); + } + promise.setResult(descriptor); + } catch (ExecutionException e) { + boolean unused = promise.setError(e); + } + } + + @Override + public void onCancel() { + super.onCancel(); + promise.setResult(null); + } + }); + + return promise; + } + + private RunContentDescriptor doExecute( + final RunProfileState state, final ExecutionEnvironment env) throws ExecutionException { + ExecutionResult result = state.execute(env.getExecutor(), this); + return ActionsKt.invokeAndWaitIfNeeded( + NON_MODAL, + () -> new RunContentBuilder(result, env).showRunContent(env.getContentToReuse())); + } + + @Override + public String getRunnerId() { + return "AndroidBinaryProgramRunner"; + } +} diff --git a/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationHandlerCompat.java b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationHandlerCompat.java new file mode 100644 index 00000000000..188f6e6b844 --- /dev/null +++ b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationHandlerCompat.java @@ -0,0 +1,38 @@ +/* + * Copyright 2022 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.binary; + +import com.android.tools.idea.execution.common.DeployableToDevice; +import com.google.common.annotations.VisibleForTesting; +import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration; +import com.google.idea.blaze.base.settings.Blaze; +import com.intellij.openapi.project.Project; + +/** Compat class for {@link BlazeAndroidBinaryRunConfigurationHandlerCompat}. */ +public class BlazeAndroidBinaryRunConfigurationHandlerCompat { + protected final Project project; + protected final BlazeAndroidBinaryRunConfigurationState configState; + + @VisibleForTesting + protected BlazeAndroidBinaryRunConfigurationHandlerCompat( + BlazeCommandRunConfiguration configuration) { + project = configuration.getProject(); + configState = + new BlazeAndroidBinaryRunConfigurationState( + Blaze.buildSystemName(configuration.getProject())); + configuration.putUserData(DeployableToDevice.getKEY(), true); + } +} diff --git a/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/binary/DeploymentTimingReporterTask.java b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/binary/DeploymentTimingReporterTask.java new file mode 100644 index 00000000000..bc567e3ff30 --- /dev/null +++ b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/binary/DeploymentTimingReporterTask.java @@ -0,0 +1,60 @@ +/* + * Copyright 2021 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.binary; + +import com.android.tools.idea.execution.common.DeployOptions; +import com.android.tools.idea.run.ApkInfo; +import com.android.tools.idea.run.blaze.BlazeLaunchContext; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; +import com.android.tools.idea.run.tasks.DeployTasksCompat; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Stopwatch; +import com.google.common.collect.ImmutableList; +import com.google.idea.blaze.android.run.LaunchMetrics; +import com.intellij.execution.ExecutionException; +import com.intellij.openapi.project.Project; +import java.util.Collection; + +/** A wrapper launch task that wraps the given deployment task and logs the deployment latency. */ +public class DeploymentTimingReporterTask implements BlazeLaunchTask { + private final BlazeLaunchTask deployTask; + private final String launchId; + private final ImmutableList packages; + + public DeploymentTimingReporterTask( + String launchId, Project project, Collection packages, DeployOptions deployOptions) { + this.launchId = launchId; + this.deployTask = DeployTasksCompat.createDeployTask(project, packages, deployOptions); + this.packages = ImmutableList.copyOf(packages); + } + + @VisibleForTesting + public ImmutableList getPackages() { + return packages; + } + + @Override + public void run(BlazeLaunchContext launchContext) throws ExecutionException { + Stopwatch s = Stopwatch.createStarted(); + try { + deployTask.run(launchContext); + LaunchMetrics.logDeploymentTime(launchId, s.elapsed(), true); + } catch (ExecutionException e) { + LaunchMetrics.logDeploymentTime(launchId, s.elapsed(), false); + throw e; + } + } +} diff --git a/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContext.java b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContext.java new file mode 100644 index 00000000000..771363e7250 --- /dev/null +++ b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContext.java @@ -0,0 +1,136 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.binary.mobileinstall; + +import com.android.ddmlib.ClientData; +import com.android.ddmlib.IDevice; +import com.android.tools.idea.execution.common.debug.AndroidDebugger; +import com.android.tools.idea.execution.common.debug.AndroidDebuggerState; +import com.android.tools.idea.execution.common.debug.DebugSessionStarter; +import com.android.tools.idea.projectsystem.ApplicationProjectContext; +import com.android.tools.idea.run.ApkProvisionException; +import com.android.tools.idea.run.activity.DefaultStartActivityFlagsProvider; +import com.android.tools.idea.run.activity.StartActivityFlagsProvider; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; +import com.google.idea.blaze.android.run.BazelApplicationProjectContext; +import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryApplicationLaunchTaskProvider; +import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryRunConfigurationState; +import com.google.idea.blaze.android.run.binary.UserIdHelper; +import com.google.idea.blaze.android.run.deployinfo.BlazeAndroidDeployInfo; +import com.google.idea.blaze.android.run.runner.ApkBuildStep; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.Executor; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.ui.ConsoleView; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.project.Project; +import com.intellij.xdebugger.XDebugSession; +import javax.annotation.Nullable; +import kotlin.Unit; +import kotlin.coroutines.EmptyCoroutineContext; +import kotlinx.coroutines.BuildersKt; +import org.jetbrains.android.facet.AndroidFacet; + +/** Run Context for mobile install launches, #api4.0 compat. */ +public class BlazeAndroidBinaryMobileInstallRunContext + extends BlazeAndroidBinaryMobileInstallRunContextBase { + public BlazeAndroidBinaryMobileInstallRunContext( + Project project, + AndroidFacet facet, + RunConfiguration runConfiguration, + ExecutionEnvironment env, + BlazeAndroidBinaryRunConfigurationState configState, + ApkBuildStep buildStep, + String launchId) { + super(project, facet, runConfiguration, env, configState, buildStep, launchId); + } + + @Override + public ApplicationProjectContext getApplicationProjectContext() { + return new BazelApplicationProjectContext(project, getApplicationIdProvider()); + } + + @SuppressWarnings("unchecked") // upstream API + @Override + public BlazeLaunchTask getApplicationLaunchTask( + boolean isDebug, @Nullable Integer userId, String contributorsAmStartOptions) + throws ExecutionException { + + String extraFlags = UserIdHelper.getFlagsFromUserId(userId); + if (!contributorsAmStartOptions.isEmpty()) { + extraFlags += (extraFlags.isEmpty() ? "" : " ") + contributorsAmStartOptions; + } + if (isDebug) { + extraFlags += (extraFlags.isEmpty() ? "" : " ") + "-D"; + } + + final StartActivityFlagsProvider startActivityFlagsProvider = + new DefaultStartActivityFlagsProvider(project, isDebug, extraFlags); + BlazeAndroidDeployInfo deployInfo; + try { + deployInfo = buildStep.getDeployInfo(); + } catch (ApkProvisionException e) { + throw new ExecutionException(e); + } + + return BlazeAndroidBinaryApplicationLaunchTaskProvider.getApplicationLaunchTask( + applicationIdProvider, + deployInfo.getMergedManifest(), + configState, + startActivityFlagsProvider); + } + + @Nullable + @Override + public XDebugSession startDebuggerSession( + AndroidDebugger androidDebugger, + AndroidDebuggerState androidDebuggerState, + ExecutionEnvironment env, + IDevice device, + ConsoleView consoleView, + ProgressIndicator indicator, + String packageName) { + try { + return BuildersKt.runBlocking( + EmptyCoroutineContext.INSTANCE, + (scope, continuation) -> + DebugSessionStarter.INSTANCE.attachDebuggerToStartedProcess( + device, + new BazelApplicationProjectContext(project, packageName), + env, + androidDebugger, + androidDebuggerState, + /*destroyRunningProcess*/ d -> { + d.forceStop(packageName); + return Unit.INSTANCE; + }, + indicator, + consoleView, + 15L, + ClientData.DebuggerStatus.WAITING, + continuation)); + } catch (InterruptedException e) { + throw new ProcessCanceledException(e); + } + } + + @Override + public Executor getExecutor() { + return env.getExecutor(); + } +} diff --git a/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContextBase.java b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContextBase.java new file mode 100644 index 00000000000..967e5926fb0 --- /dev/null +++ b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContextBase.java @@ -0,0 +1,164 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.binary.mobileinstall; + +import com.android.ddmlib.IDevice; +import com.android.tools.idea.execution.common.DeployOptions; +import com.android.tools.idea.run.ApkFileUnit; +import com.android.tools.idea.run.ApkInfo; +import com.android.tools.idea.run.ApkProvisionException; +import com.android.tools.idea.run.ApplicationIdProvider; +import com.android.tools.idea.run.ConsoleProvider; +import com.android.tools.idea.run.LaunchOptions; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; +import com.android.tools.idea.run.blaze.BlazeLaunchTasksProvider; +import com.android.tools.idea.run.editor.ProfilerState; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryApplicationIdProvider; +import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryConsoleProvider; +import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryRunConfigurationState; +import com.google.idea.blaze.android.run.binary.DeploymentTimingReporterTask; +import com.google.idea.blaze.android.run.binary.UserIdHelper; +import com.google.idea.blaze.android.run.deployinfo.BlazeAndroidDeployInfo; +import com.google.idea.blaze.android.run.runner.ApkBuildStep; +import com.google.idea.blaze.android.run.runner.BlazeAndroidDeviceSelector; +import com.google.idea.blaze.android.run.runner.BlazeAndroidLaunchTasksProvider; +import com.google.idea.blaze.android.run.runner.BlazeAndroidRunContext; +import com.google.idea.blaze.base.sync.data.BlazeDataStorage; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.openapi.project.Project; +import java.util.Collections; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import org.jetbrains.android.facet.AndroidFacet; + +/** Run context for android_binary. */ +abstract class BlazeAndroidBinaryMobileInstallRunContextBase implements BlazeAndroidRunContext { + protected final Project project; + protected final AndroidFacet facet; + protected final RunConfiguration runConfiguration; + protected final ExecutionEnvironment env; + protected final BlazeAndroidBinaryRunConfigurationState configState; + protected final ConsoleProvider consoleProvider; + protected final ApplicationIdProvider applicationIdProvider; + protected final ApkBuildStep buildStep; + private final String launchId; + + public BlazeAndroidBinaryMobileInstallRunContextBase( + Project project, + AndroidFacet facet, + RunConfiguration runConfiguration, + ExecutionEnvironment env, + BlazeAndroidBinaryRunConfigurationState configState, + ApkBuildStep buildStep, + String launchId) { + this.project = project; + this.facet = facet; + this.runConfiguration = runConfiguration; + this.env = env; + this.configState = configState; + this.consoleProvider = new BlazeAndroidBinaryConsoleProvider(project); + this.buildStep = buildStep; + this.applicationIdProvider = new BlazeAndroidBinaryApplicationIdProvider(buildStep); + this.launchId = launchId; + } + + @Override + public BlazeAndroidDeviceSelector getDeviceSelector() { + return new BlazeAndroidDeviceSelector.NormalDeviceSelector(); + } + + @Override + public void augmentLaunchOptions(LaunchOptions.Builder options) { + options + .setDeploy(buildStep.needsIdeDeploy()) + .setOpenLogcatAutomatically(configState.showLogcatAutomatically()); + // This is needed for compatibility with #api211 + options.addExtraOptions( + ImmutableMap.of("android.profilers.state", configState.getProfilerState())); + } + + @Override + public ConsoleProvider getConsoleProvider() { + return consoleProvider; + } + + @Override + public ApplicationIdProvider getApplicationIdProvider() { + return applicationIdProvider; + } + + @Override + public ApkBuildStep getBuildStep() { + return buildStep; + } + + @Override + public ProfilerState getProfileState() { + return configState.getProfilerState(); + } + + @Override + public ImmutableList getDeployTasks(IDevice device, DeployOptions deployOptions) + throws ExecutionException { + if (!buildStep.needsIdeDeploy()) { + return ImmutableList.of(); + } + + BlazeAndroidDeployInfo deployInfo; + try { + deployInfo = buildStep.getDeployInfo(); + } catch (ApkProvisionException e) { + throw new ExecutionException(e); + } + + String packageName = deployInfo.getMergedManifest().packageName; + if (packageName == null) { + throw new ExecutionException("Could not determine package name from deploy info"); + } + + ApkInfo info = + new ApkInfo( + deployInfo.getApksToDeploy().stream() + .map(file -> new ApkFileUnit(BlazeDataStorage.WORKSPACE_MODULE_NAME, file)) + .collect(Collectors.toList()), + packageName); + + return ImmutableList.of( + new DeploymentTimingReporterTask( + launchId, project, Collections.singletonList(info), deployOptions)); + } + + @Nullable + @Override + public Integer getUserId(IDevice device) throws ExecutionException { + return UserIdHelper.getUserIdFromConfigurationState(project, device, configState); + } + + @Override + public BlazeLaunchTasksProvider getLaunchTasksProvider(LaunchOptions launchOptions) + throws ExecutionException { + return new BlazeAndroidLaunchTasksProvider(project, this, applicationIdProvider, launchOptions); + } + + @Override + public String getAmStartOptions() { + return configState.getAmStartOptions(); + } +} diff --git a/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContextCompat.java b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContextCompat.java new file mode 100644 index 00000000000..f11e21dba03 --- /dev/null +++ b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContextCompat.java @@ -0,0 +1,87 @@ +/* + * Copyright 2022 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.mobileinstall; + +import com.android.ddmlib.ClientData; +import com.android.ddmlib.IDevice; +import com.android.tools.idea.execution.common.debug.AndroidDebugger; +import com.android.tools.idea.execution.common.debug.AndroidDebuggerState; +import com.android.tools.idea.execution.common.debug.DebugSessionStarter; +import com.google.idea.blaze.android.run.BazelApplicationProjectContext; +import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryRunConfigurationState; +import com.google.idea.blaze.android.run.runner.ApkBuildStep; +import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.ui.ConsoleView; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.project.Project; +import com.intellij.xdebugger.XDebugSession; +import javax.annotation.Nullable; +import kotlin.Unit; +import kotlin.coroutines.EmptyCoroutineContext; +import kotlinx.coroutines.BuildersKt; +import org.jetbrains.android.facet.AndroidFacet; + +/** Compatct class for {@link BlazeAndroidBinaryMobileInstallRunContext}. */ +public class BlazeAndroidBinaryMobileInstallRunContextCompat + extends BlazeAndroidBinaryMobileInstallRunContext { + + public BlazeAndroidBinaryMobileInstallRunContextCompat( + Project project, + AndroidFacet facet, + BlazeCommandRunConfiguration configuration, + ExecutionEnvironment env, + BlazeAndroidBinaryRunConfigurationState configState, + ApkBuildStep buildStep, + String launchId) { + super(project, facet, configuration, env, configState, buildStep, launchId); + } + + @Nullable + @Override + public XDebugSession startDebuggerSession( + AndroidDebugger androidDebugger, + AndroidDebuggerState androidDebuggerState, + ExecutionEnvironment env, + IDevice device, + ConsoleView consoleView, + ProgressIndicator indicator, + String packageName) { + try { + return BuildersKt.runBlocking( + EmptyCoroutineContext.INSTANCE, + (scope, continuation) -> + DebugSessionStarter.INSTANCE.attachDebuggerToStartedProcess( + device, + new BazelApplicationProjectContext(project, packageName), + env, + androidDebugger, + androidDebuggerState, + /*destroyRunningProcess*/ d -> { + d.forceStop(packageName); + return Unit.INSTANCE; + }, + indicator, + consoleView, + 15L, + ClientData.DebuggerStatus.WAITING, + continuation)); + } catch (InterruptedException e) { + throw new ProcessCanceledException(e); + } + } +} diff --git a/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/runner/BlazeAndroidDebuggerService.java b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/runner/BlazeAndroidDebuggerService.java new file mode 100644 index 00000000000..5715392a171 --- /dev/null +++ b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/runner/BlazeAndroidDebuggerService.java @@ -0,0 +1,137 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.runner; + +import com.android.tools.idea.execution.common.debug.AndroidDebugger; +import com.android.tools.idea.execution.common.debug.AndroidDebuggerState; +import com.android.tools.idea.execution.common.debug.impl.java.AndroidJavaDebugger; +import com.android.tools.ndk.run.editor.AutoAndroidDebuggerState; +import com.android.tools.ndk.run.editor.NativeAndroidDebuggerState; +import com.google.common.collect.ImmutableList; +import com.google.idea.blaze.android.cppimpl.debug.BlazeAutoAndroidDebugger; +import com.google.idea.blaze.android.run.deployinfo.BlazeAndroidDeployInfo; +import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; +import com.intellij.ide.plugins.PluginManagerCore; +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.project.Project; +import javax.annotation.Nullable; + +/** Provides android debuggers and debugger states for blaze projects. */ +public interface BlazeAndroidDebuggerService { + + static BlazeAndroidDebuggerService getInstance(Project project) { + return ServiceManager.getService(project, BlazeAndroidDebuggerService.class); + } + + /** Returns a different debugger depending on whether or not native debugging is required. */ + AndroidDebugger getDebugger(boolean isNativeDebuggingEnabled); + + /** + * Returns fully initialized debugger states. + * + *

Note: Blaze projects should always use this method instead of the debuggers' {@link + * AndroidDebugger#createState()} method. Blaze projects require additional setup such as + * workspace directory flags that cannot be handled by the debuggers themselves. + */ + AndroidDebuggerState getDebuggerState(AndroidDebugger debugger); + + void configureNativeDebugger( + AndroidDebuggerState state, @Nullable BlazeAndroidDeployInfo deployInfo); + + /** Default debugger service. */ + class DefaultDebuggerService implements BlazeAndroidDebuggerService { + private final Project project; + + public DefaultDebuggerService(Project project) { + this.project = project; + } + + @Override + public AndroidDebugger getDebugger(boolean isNativeDebuggingEnabled) { + return isNativeDebuggingEnabled ? new BlazeAutoAndroidDebugger() : new AndroidJavaDebugger(); + } + + @Override + public AndroidDebuggerState getDebuggerState(AndroidDebugger debugger) { + AndroidDebuggerState debuggerState = debugger.createState(); + if (isNdkPluginLoaded() && debuggerState instanceof NativeAndroidDebuggerState) { + NativeAndroidDebuggerState nativeState = (NativeAndroidDebuggerState) debuggerState; + + // Source code is always relative to the workspace root in a blaze project. + String workingDirPath = WorkspaceRoot.fromProject(project).directory().getPath(); + nativeState.setWorkingDir(workingDirPath); + + // Remote built binaries may use /proc/self/cwd to represent the working directory + // so we manually map /proc/self/cwd to the workspace root. We used to use + // `plugin.symbol-file.dwarf.comp-dir-symlink-paths = "/proc/self/cwd"` + // to automatically resolve this but it's no longer supported in newer versions of + // LLDB. + String sourceMapToWorkspaceRootCommand = + "settings append target.source-map /proc/self/cwd/ " + workingDirPath; + ImmutableList startupCommands = + ImmutableList.builder() + .addAll(nativeState.getUserStartupCommands()) + .add(sourceMapToWorkspaceRootCommand) + .build(); + nativeState.setUserStartupCommands(startupCommands); + } + return debuggerState; + } + + @Override + public void configureNativeDebugger( + AndroidDebuggerState rawState, @Nullable BlazeAndroidDeployInfo deployInfo) { + if (!isNdkPluginLoaded() && !(rawState instanceof AutoAndroidDebuggerState)) { + return; + } + AutoAndroidDebuggerState state = (AutoAndroidDebuggerState) rawState; + + // Source code is always relative to the workspace root in a blaze project. + String workingDirPath = WorkspaceRoot.fromProject(project).directory().getPath(); + state.setWorkingDir(workingDirPath); + + // Remote built binaries may use /proc/self/cwd to represent the working directory, + // so we manually map /proc/self/cwd to the workspace root. We used to use + // `plugin.symbol-file.dwarf.comp-dir-symlink-paths = "/proc/self/cwd"` + // to automatically resolve this, but it's no longer supported in newer versions of + // LLDB. + String sourceMapToWorkspaceRootCommand = + "settings append target.source-map /proc/self/cwd/ " + workingDirPath; + + ImmutableList startupCommands = + ImmutableList.builder() + .addAll(state.getUserStartupCommands()) + .add(sourceMapToWorkspaceRootCommand) + .build(); + state.setUserStartupCommands(startupCommands); + + // NDK plugin will pass symbol directories to LLDB as `settings append + // target.exec-search-paths`. + if (deployInfo != null) { + state.setSymbolDirs( + deployInfo.getSymbolFiles().stream() + .map(symbol -> symbol.getParentFile().getAbsolutePath()) + .collect(ImmutableList.toImmutableList())); + } + } + } + + static boolean isNdkPluginLoaded() { + return PluginManagerCore.getLoadedPlugins().stream() + .anyMatch( + d -> d.isEnabled() && d.getPluginId().getIdString().equals("com.android.tools.ndk")); + } +} diff --git a/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/runner/BlazeAndroidLaunchTasksProvider.java b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/runner/BlazeAndroidLaunchTasksProvider.java new file mode 100644 index 00000000000..a1528a9c4d9 --- /dev/null +++ b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/runner/BlazeAndroidLaunchTasksProvider.java @@ -0,0 +1,177 @@ +/* + * Copyright 2022 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.runner; + +import static com.android.tools.idea.profilers.AndroidProfilerLaunchTaskContributor.isProfilerLaunch; + +import com.android.ddmlib.IDevice; +import com.android.tools.deployer.ApkVerifierTracker; +import com.android.tools.idea.execution.common.DeployOptions; +import com.android.tools.idea.execution.common.debug.AndroidDebugger; +import com.android.tools.idea.execution.common.debug.AndroidDebuggerState; +import com.android.tools.idea.profilers.AndroidProfilerLaunchTaskContributor; +import com.android.tools.idea.run.ApkProvisionException; +import com.android.tools.idea.run.ApplicationIdProvider; +import com.android.tools.idea.run.LaunchOptions; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; +import com.android.tools.idea.run.blaze.BlazeLaunchTasksProvider; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.idea.blaze.android.run.binary.UserIdHelper; +import com.google.idea.blaze.android.run.deployinfo.BlazeAndroidDeployInfo; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.ui.ConsoleView; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.project.Project; +import com.intellij.xdebugger.XDebugSession; +import java.util.Collections; +import java.util.List; +import org.jetbrains.annotations.NotNull; + +/** Normal launch tasks provider. #api4.1 */ +public class BlazeAndroidLaunchTasksProvider implements BlazeLaunchTasksProvider { + public static final String NATIVE_DEBUGGING_ENABLED = "NATIVE_DEBUGGING_ENABLED"; + private static final Logger LOG = Logger.getInstance(BlazeAndroidLaunchTasksProvider.class); + + private final Project project; + private final BlazeAndroidRunContext runContext; + private final ApplicationIdProvider applicationIdProvider; + private final LaunchOptions launchOptions; + + public BlazeAndroidLaunchTasksProvider( + Project project, + BlazeAndroidRunContext runContext, + ApplicationIdProvider applicationIdProvider, + LaunchOptions launchOptions) { + this.project = project; + this.runContext = runContext; + this.applicationIdProvider = applicationIdProvider; + this.launchOptions = launchOptions; + } + + @NotNull + @Override + public List getTasks(@NotNull IDevice device, boolean isDebug) + throws ExecutionException { + final List launchTasks = Lists.newArrayList(); + + String packageName; + try { + packageName = applicationIdProvider.getPackageName(); + } catch (ApkProvisionException e) { + throw new ExecutionException("Unable to determine application id: " + e); + } + + Integer userId = runContext.getUserId(device); + + // NOTE: Task for opening the profiler tool-window should come before deployment + // to ensure the tool-window opens correctly. This is required because starting + // the profiler session requires the tool-window to be open. + if (isProfilerLaunch(runContext.getExecutor())) { + launchTasks.add(new BlazeAndroidOpenProfilerWindowTask(project)); + } + + if (launchOptions.isDeploy()) { + String userIdFlags = UserIdHelper.getFlagsFromUserId(userId); + String skipVerification = + ApkVerifierTracker.getSkipVerificationInstallationFlag(device, packageName); + String pmInstallOption; + if (skipVerification != null) { + pmInstallOption = userIdFlags + " " + skipVerification; + } else { + pmInstallOption = userIdFlags; + } + DeployOptions deployOptions = + new DeployOptions(Collections.emptyList(), pmInstallOption, false, false); + ImmutableList deployTasks = runContext.getDeployTasks(device, deployOptions); + launchTasks.addAll(deployTasks); + } + + try { + if (isDebug) { + launchTasks.add( + new CheckApkDebuggableTask(project, runContext.getBuildStep().getDeployInfo())); + } + + ImmutableList.Builder amStartOptions = ImmutableList.builder(); + amStartOptions.add(runContext.getAmStartOptions()); + if (isProfilerLaunch(runContext.getExecutor())) { + amStartOptions.add( + AndroidProfilerLaunchTaskContributor.getAmStartOptions( + project, + packageName, + runContext.getProfileState(), + device, + runContext.getExecutor())); + } + BlazeLaunchTask appLaunchTask = + runContext.getApplicationLaunchTask( + isDebug, userId, String.join(" ", amStartOptions.build())); + if (appLaunchTask != null) { + launchTasks.add(appLaunchTask); + // TODO(arvindanekal): the live edit api changed and we cannot get the apk here to create + // live + // edit; the live edit team or Arvind need to fix this + } + } catch (ApkProvisionException e) { + throw new ExecutionException("Unable to determine application id: " + e); + } + + return ImmutableList.copyOf(launchTasks); + } + + @NotNull + @Override + public XDebugSession startDebugSession( + @NotNull ExecutionEnvironment environment, + @NotNull IDevice device, + @NotNull ConsoleView console, + @NotNull ProgressIndicator indicator, + @NotNull String packageName) + throws ExecutionException { + // Do not get debugger state directly from the debugger itself. + // See BlazeAndroidDebuggerService#getDebuggerState for an explanation. + boolean isNativeDebuggingEnabled = isNativeDebuggingEnabled(launchOptions); + BlazeAndroidDebuggerService debuggerService = BlazeAndroidDebuggerService.getInstance(project); + AndroidDebugger debugger = debuggerService.getDebugger(isNativeDebuggingEnabled); + if (debugger == null) { + throw new ExecutionException("Can't find AndroidDebugger for launch"); + } + AndroidDebuggerState debuggerState = debuggerService.getDebuggerState(debugger); + if (debuggerState == null) { + throw new ExecutionException("Can't find AndroidDebuggerState for launch"); + } + if (isNativeDebuggingEnabled) { + BlazeAndroidDeployInfo deployInfo = null; + try { + deployInfo = runContext.getBuildStep().getDeployInfo(); + } catch (ApkProvisionException e) { + LOG.error(e); + } + debuggerService.configureNativeDebugger(debuggerState, deployInfo); + } + + return runContext.startDebuggerSession( + debugger, debuggerState, environment, device, console, indicator, packageName); + } + + private boolean isNativeDebuggingEnabled(LaunchOptions launchOptions) { + Object flag = launchOptions.getExtraOption(NATIVE_DEBUGGING_ENABLED); + return flag instanceof Boolean && (Boolean) flag; + } +} diff --git a/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/runner/BlazeAndroidRunConfigurationRunner.java b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/runner/BlazeAndroidRunConfigurationRunner.java new file mode 100644 index 00000000000..364a7f9a1ca --- /dev/null +++ b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/runner/BlazeAndroidRunConfigurationRunner.java @@ -0,0 +1,340 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.runner; + +import com.android.ddmlib.IDevice; +import com.android.tools.idea.editors.liveedit.LiveEditService; +import com.android.tools.idea.execution.common.AndroidConfigurationExecutor; +import com.android.tools.idea.execution.common.AndroidConfigurationExecutorRunProfileState; +import com.android.tools.idea.execution.common.AppRunSettings; +import com.android.tools.idea.execution.common.ApplicationDeployer; +import com.android.tools.idea.execution.common.ComponentLaunchOptions; +import com.android.tools.idea.execution.common.DeployOptions; +import com.android.tools.idea.execution.common.stats.RunStats; +import com.android.tools.idea.projectsystem.ApplicationProjectContext; +import com.android.tools.idea.run.ApkProvider; +import com.android.tools.idea.run.ApplicationIdProvider; +import com.android.tools.idea.run.DeviceFutures; +import com.android.tools.idea.run.LaunchOptions; +import com.android.tools.idea.run.blaze.BlazeAndroidConfigurationExecutor; +import com.android.tools.idea.run.configuration.execution.AndroidComplicationConfigurationExecutor; +import com.android.tools.idea.run.configuration.execution.AndroidTileConfigurationExecutor; +import com.android.tools.idea.run.configuration.execution.AndroidWatchFaceConfigurationExecutor; +import com.android.tools.idea.run.configuration.execution.ApplicationDeployerImpl; +import com.android.tools.idea.run.configuration.execution.ComplicationLaunchOptions; +import com.android.tools.idea.run.configuration.execution.TileLaunchOptions; +import com.android.tools.idea.run.configuration.execution.WatchFaceLaunchOptions; +import com.android.tools.idea.run.editor.DeployTarget; +import com.android.tools.idea.run.editor.DeployTargetState; +import com.android.tools.idea.run.util.LaunchUtils; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryRunConfigurationState; +import com.google.idea.blaze.android.run.binary.mobileinstall.MobileInstallBuildStep; +import com.google.idea.blaze.android.run.deployinfo.BlazeApkProviderService; +import com.google.idea.blaze.base.async.executor.ProgressiveTaskWithProgressIndicator; +import com.google.idea.blaze.base.command.BlazeInvocationContext.ContextType; +import com.google.idea.blaze.base.experiments.ExperimentScope; +import com.google.idea.blaze.base.issueparser.BlazeIssueParser; +import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; +import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration; +import com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationRunner; +import com.google.idea.blaze.base.run.state.RunConfigurationState; +import com.google.idea.blaze.base.scope.BlazeContext; +import com.google.idea.blaze.base.scope.Scope; +import com.google.idea.blaze.base.scope.ScopedTask; +import com.google.idea.blaze.base.scope.output.IssueOutput; +import com.google.idea.blaze.base.scope.scopes.IdeaLogScope; +import com.google.idea.blaze.base.scope.scopes.ProblemsViewScope; +import com.google.idea.blaze.base.scope.scopes.ToolWindowScope; +import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.settings.BlazeUserSettings; +import com.google.idea.blaze.base.toolwindow.Task; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.Executor; +import com.intellij.execution.configurations.RunProfileState; +import com.intellij.execution.executors.DefaultDebugExecutor; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Key; +import java.util.Collections; +import java.util.concurrent.CancellationException; +import javax.annotation.Nullable; +import org.jetbrains.android.facet.AndroidFacet; +import org.jetbrains.android.util.AndroidBundle; +import org.jetbrains.annotations.NotNull; + +/** + * Supports the execution. Used by both android_binary and android_test. + * + *

Builds the APK and installs it, launches and debug tasks, etc. + * + *

Any indirection between android_binary/android_test, mobile-install, InstantRun etc. should + * come via the strategy class. + */ +public final class BlazeAndroidRunConfigurationRunner + implements BlazeCommandRunConfigurationRunner { + + private static final Logger LOG = Logger.getInstance(BlazeAndroidRunConfigurationRunner.class); + + private static final Key RUN_CONTEXT_KEY = + Key.create("blaze.run.context"); + public static final Key DEVICE_SESSION_KEY = + Key.create("blaze.device.session"); + + private final Module module; + private final BlazeAndroidRunContext runContext; + private final BlazeCommandRunConfiguration runConfig; + + public BlazeAndroidRunConfigurationRunner( + Module module, BlazeAndroidRunContext runContext, BlazeCommandRunConfiguration runConfig) { + this.module = module; + this.runContext = runContext; + this.runConfig = runConfig; + } + + @Override + @Nullable + public final RunProfileState getRunProfileState(final Executor executor, ExecutionEnvironment env) + throws ExecutionException { + + final AndroidFacet facet = AndroidFacet.getInstance(module); + assert facet != null : "Enforced by fatal validation check in createRunner."; + final Project project = env.getProject(); + + boolean isDebug = executor instanceof DefaultDebugExecutor; + + BlazeAndroidDeviceSelector deviceSelector = runContext.getDeviceSelector(); + BlazeAndroidDeviceSelector.DeviceSession deviceSession = + deviceSelector.getDevice(project, executor, env, isDebug, runConfig.getUniqueID()); + if (deviceSession == null) { + return null; + } + + DeployTarget deployTarget = deviceSession.deployTarget; + if (deployTarget != null && deployTarget.hasCustomRunProfileState(executor)) { + return deployTarget.getRunProfileState(executor, env, DeployTargetState.DEFAULT_STATE); + } + + DeviceFutures deviceFutures = deviceSession.deviceFutures; + if (deviceFutures == null) { + // The user deliberately canceled, or some error was encountered and exposed by the chooser. + // Quietly exit. + return null; + } + + if (deviceFutures.get().isEmpty()) { + throw new ExecutionException(AndroidBundle.message("deployment.target.not.found")); + } + + if (isDebug) { + String error = canDebug(deviceFutures, facet, module.getName()); + if (error != null) { + throw new ExecutionException(error); + } + } + + LaunchOptions.Builder launchOptionsBuilder = getDefaultLaunchOptions(); + runContext.augmentLaunchOptions(launchOptionsBuilder); + + // Store the run context on the execution environment so before-run tasks can access it. + env.putCopyableUserData(RUN_CONTEXT_KEY, runContext); + env.putCopyableUserData(DEVICE_SESSION_KEY, deviceSession); + + RunConfigurationState state = runConfig.getHandler().getState(); + + if (state instanceof BlazeAndroidBinaryRunConfigurationState + && ((BlazeAndroidBinaryRunConfigurationState) state).getCurrentWearLaunchOptions() + != null) { + ComponentLaunchOptions launchOptions = + ((BlazeAndroidBinaryRunConfigurationState) state).getCurrentWearLaunchOptions(); + + return getWearExecutor(launchOptions, env, deployTarget); + } + + ApkProvider apkProvider = + BlazeApkProviderService.getInstance() + .getApkProvider(env.getProject(), runContext.getBuildStep()); + final LaunchOptions launchOptions = launchOptionsBuilder.build(); + BlazeAndroidConfigurationExecutor runner = + new BlazeAndroidConfigurationExecutor( + runContext.getConsoleProvider(), + runContext.getApplicationIdProvider(), + env, + deviceFutures, + runContext.getLaunchTasksProvider(launchOptions), + launchOptions, + apkProvider, + LiveEditService.getInstance(env.getProject())); + return new AndroidConfigurationExecutorRunProfileState(runner); + } + + private RunProfileState getWearExecutor( + ComponentLaunchOptions launchOptions, ExecutionEnvironment env, DeployTarget deployTarget) + throws ExecutionException { + + AppRunSettings settings = + new AppRunSettings() { + @NotNull + @Override + public DeployOptions getDeployOptions() { + return new DeployOptions(Collections.emptyList(), "", true, true); + } + + @NotNull + @Override + public ComponentLaunchOptions getComponentLaunchOptions() { + return launchOptions; + } + + @Override + public Module getModule() { + return runConfig.getModules()[0]; + } + }; + + AndroidConfigurationExecutor configurationExecutor; + ApplicationIdProvider appIdProvider = runContext.getApplicationIdProvider(); + ApplicationProjectContext applicationProjectContext = runContext.getApplicationProjectContext(); + ApkProvider apkProvider = + BlazeApkProviderService.getInstance() + .getApkProvider(env.getProject(), runContext.getBuildStep()); + DeviceFutures deviceFutures = deployTarget.getDevices(env.getProject()); + + ApplicationDeployer deployer = + runContext.getBuildStep() instanceof MobileInstallBuildStep + ? new MobileInstallApplicationDeployer() + : new ApplicationDeployerImpl(env.getProject(), RunStats.from(env)); + + if (launchOptions instanceof TileLaunchOptions) { + configurationExecutor = + new AndroidTileConfigurationExecutor( + env, + deviceFutures, + settings, + appIdProvider, + apkProvider, + applicationProjectContext, + deployer); + } else if (launchOptions instanceof WatchFaceLaunchOptions) { + configurationExecutor = + new AndroidWatchFaceConfigurationExecutor( + env, + deviceFutures, + settings, + appIdProvider, + apkProvider, + applicationProjectContext, + deployer); + } else if (launchOptions instanceof ComplicationLaunchOptions) { + configurationExecutor = + new AndroidComplicationConfigurationExecutor( + env, + deviceFutures, + settings, + appIdProvider, + apkProvider, + applicationProjectContext, + deployer); + } else { + throw new RuntimeException("Unknown launch options " + launchOptions.getClass().getName()); + } + + return new AndroidConfigurationExecutorRunProfileState(configurationExecutor); + } + + private static String canDebug( + DeviceFutures deviceFutures, AndroidFacet facet, String moduleName) { + // If we are debugging on a device, then the app needs to be debuggable + for (ListenableFuture future : deviceFutures.get()) { + if (!future.isDone()) { + // this is an emulator, and we assume that all emulators are debuggable + continue; + } + IDevice device = Futures.getUnchecked(future); + if (!LaunchUtils.canDebugAppOnDevice(facet, device)) { + return AndroidBundle.message( + "android.cannot.debug.noDebugPermissions", moduleName, device.getName()); + } + } + return null; + } + + private static LaunchOptions.Builder getDefaultLaunchOptions() { + return LaunchOptions.builder(); + } + + @Override + public boolean executeBeforeRunTask(ExecutionEnvironment env) { + final Project project = env.getProject(); + BlazeUserSettings settings = BlazeUserSettings.getInstance(); + return Scope.root( + context -> { + context + .push(new ProblemsViewScope(project, settings.getShowProblemsViewOnRun())) + .push(new ExperimentScope()) + .push( + new ToolWindowScope.Builder( + project, new Task(project, "Build apk", Task.Type.BEFORE_LAUNCH)) + .setPopupBehavior(settings.getShowBlazeConsoleOnRun()) + .setIssueParsers( + BlazeIssueParser.defaultIssueParsers( + project, + WorkspaceRoot.fromProject(project), + ContextType.BeforeRunTask)) + .build()) + .push(new IdeaLogScope()); + + BlazeAndroidRunContext runContext = env.getCopyableUserData(RUN_CONTEXT_KEY); + if (runContext == null) { + IssueOutput.error("Could not find run context. Please try again").submit(context); + return false; + } + BlazeAndroidDeviceSelector.DeviceSession deviceSession = + env.getCopyableUserData(DEVICE_SESSION_KEY); + + ApkBuildStep buildStep = runContext.getBuildStep(); + ScopedTask buildTask = + new ScopedTask(context) { + @Override + protected Void execute(BlazeContext context) { + buildStep.build(context, deviceSession); + return null; + } + }; + + try { + ListenableFuture buildFuture = + ProgressiveTaskWithProgressIndicator.builder( + project, + String.format("Executing %s apk build", Blaze.buildSystemName(project))) + .submitTaskWithResult(buildTask); + Futures.getChecked(buildFuture, ExecutionException.class); + } catch (ExecutionException e) { + context.setHasError(); + } catch (CancellationException e) { + context.setCancelled(); + } catch (Exception e) { + LOG.error(e); + return false; + } + return context.shouldContinue(); + }); + } +} diff --git a/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/runner/BlazeAndroidRunContext.java b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/runner/BlazeAndroidRunContext.java new file mode 100644 index 00000000000..468f24a441d --- /dev/null +++ b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/runner/BlazeAndroidRunContext.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.runner; + +import com.android.ddmlib.IDevice; +import com.android.tools.idea.execution.common.DeployOptions; +import com.android.tools.idea.execution.common.debug.AndroidDebugger; +import com.android.tools.idea.execution.common.debug.AndroidDebuggerState; +import com.android.tools.idea.projectsystem.ApplicationProjectContext; +import com.android.tools.idea.run.ApplicationIdProvider; +import com.android.tools.idea.run.ConsoleProvider; +import com.android.tools.idea.run.LaunchOptions; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; +import com.android.tools.idea.run.blaze.BlazeLaunchTasksProvider; +import com.android.tools.idea.run.editor.ProfilerState; +import com.google.common.collect.ImmutableList; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.Executor; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.ui.ConsoleView; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.xdebugger.XDebugSession; +import javax.annotation.Nullable; +import org.jetbrains.annotations.NotNull; + +/** Instantiated when the configuration wants to run. */ +public interface BlazeAndroidRunContext { + + BlazeAndroidDeviceSelector getDeviceSelector(); + + void augmentLaunchOptions(LaunchOptions.Builder options); + + ConsoleProvider getConsoleProvider(); + + ApkBuildStep getBuildStep(); + + ApplicationIdProvider getApplicationIdProvider(); + + ApplicationProjectContext getApplicationProjectContext(); + + BlazeLaunchTasksProvider getLaunchTasksProvider(LaunchOptions launchOptions) + throws ExecutionException; + + /** Returns the tasks to deploy the application. */ + ImmutableList getDeployTasks(IDevice device, DeployOptions deployOptions) + throws ExecutionException; + + /** Returns the task to launch the application. */ + @Nullable + BlazeLaunchTask getApplicationLaunchTask( + boolean isDebug, @Nullable Integer userId, @NotNull String contributorsAmStartOptions) + throws ExecutionException; + + /** Returns the task to connect the debugger. */ + @Nullable + XDebugSession startDebuggerSession( + AndroidDebugger androidDebugger, + AndroidDebuggerState androidDebuggerState, + ExecutionEnvironment env, + IDevice device, + ConsoleView consoleView, + ProgressIndicator indicator, + String packageName); + + @Nullable + Integer getUserId(IDevice device) throws ExecutionException; + + String getAmStartOptions(); + + Executor getExecutor(); + + ProfilerState getProfileState(); +} diff --git a/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/runner/LaunchOptionsCompat.java b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/runner/LaunchOptionsCompat.java new file mode 100644 index 00000000000..f31959514c5 --- /dev/null +++ b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/runner/LaunchOptionsCompat.java @@ -0,0 +1,29 @@ +/* + * Copyright 2022 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.runner; + +import com.android.tools.idea.run.LaunchOptions; + +/** Compat class for LunchOptions.Builder. */ +public class LaunchOptionsCompat { + + private LaunchOptionsCompat() {} + + /** Create default launch options to maintain compatibility with #api211. */ + public static LaunchOptions.Builder getDefaultLaunchOptions() { + return LaunchOptions.builder(); + } +} diff --git a/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/runner/MobileInstallApplicationDeployer.java b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/runner/MobileInstallApplicationDeployer.java new file mode 100644 index 00000000000..541cf750686 --- /dev/null +++ b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/runner/MobileInstallApplicationDeployer.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.runner; + +import static com.android.tools.idea.run.tasks.AbstractDeployTask.getAppToInstall; + +import com.android.ddmlib.IDevice; +import com.android.tools.deployer.Deployer; +import com.android.tools.deployer.DeployerException; +import com.android.tools.deployer.model.App; +import com.android.tools.idea.execution.common.ApplicationDeployer; +import com.android.tools.idea.execution.common.DeployOptions; +import com.android.tools.idea.run.ApkInfo; +import com.intellij.openapi.progress.ProgressIndicator; +import org.jetbrains.annotations.NotNull; + +/** Deploys mobile install application. */ +public class MobileInstallApplicationDeployer implements ApplicationDeployer { + + public MobileInstallApplicationDeployer() {} + + @NotNull + @Override + public Deployer.Result fullDeploy( + @NotNull IDevice device, + @NotNull ApkInfo apkInfo, + @NotNull DeployOptions deployOptions, + ProgressIndicator indicator) { + App app = getAppToInstall(apkInfo); + return new Deployer.Result(false, false, false, app); + } + + @NotNull + @Override + public Deployer.Result applyChangesDeploy( + @NotNull IDevice device, + @NotNull ApkInfo app, + @NotNull DeployOptions deployOptions, + ProgressIndicator indicator) { + throw new RuntimeException("Apply changes is not supported for mobile-install"); + } + + @NotNull + @Override + public Deployer.Result applyCodeChangesDeploy( + @NotNull IDevice device, + @NotNull ApkInfo app, + @NotNull DeployOptions deployOptions, + ProgressIndicator indicator) + throws DeployerException { + throw new RuntimeException("Apply code changes is not supported for mobile-install"); + } +} diff --git a/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/test/BlazeAndroidTestProgramRunner.java b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/test/BlazeAndroidTestProgramRunner.java new file mode 100644 index 00000000000..b294b565455 --- /dev/null +++ b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/test/BlazeAndroidTestProgramRunner.java @@ -0,0 +1,77 @@ +/* + * Copyright 2018 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.test; + +import com.android.tools.idea.execution.common.AndroidConfigurationExecutor; +import com.android.tools.idea.execution.common.AndroidConfigurationProgramRunner; +import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationHandler; +import com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.configurations.RunProfile; +import com.intellij.execution.executors.DefaultDebugExecutor; +import com.intellij.execution.executors.DefaultRunExecutor; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.ui.RunContentDescriptor; +import com.intellij.openapi.progress.ProgressIndicator; +import java.util.Collections; +import java.util.List; +import org.jetbrains.annotations.NotNull; + +/** Program runner for configurations from {@link BlazeAndroidTestRunConfigurationHandler}. */ +public class BlazeAndroidTestProgramRunner extends AndroidConfigurationProgramRunner { + @Override + public boolean canRun(String executorId, RunProfile profile) { + BlazeAndroidRunConfigurationHandler handler = + BlazeAndroidRunConfigurationHandler.getHandlerFrom(profile); + if (!(handler instanceof BlazeAndroidTestRunConfigurationHandler)) { + return false; + } + return super.canRun(executorId, profile) && DefaultRunExecutor.EXECUTOR_ID.equals(executorId) + || DefaultDebugExecutor.EXECUTOR_ID.equals(executorId); + } + + @Override + public String getRunnerId() { + return "AndroidTestProgramRunner"; + } + + @Override + protected boolean canRunWithMultipleDevices(@NotNull String executorId) { + return true; + } + + @NotNull + @Override + protected List getSupportedConfigurationTypeIds() { + return Collections.singletonList(BlazeCommandRunConfigurationType.getInstance().getId()); + } + + @NotNull + @Override + protected RunContentDescriptor run( + @NotNull ExecutionEnvironment environment, + @NotNull AndroidConfigurationExecutor executor, + @NotNull ProgressIndicator indicator) + throws ExecutionException { + if (DefaultDebugExecutor.EXECUTOR_ID.equals(environment.getExecutor().getId())) { + return executor.debug(indicator); + } + if (DefaultRunExecutor.EXECUTOR_ID.equals(environment.getExecutor().getId())) { + return executor.run(indicator); + } + throw new RuntimeException("Unsupported executor"); + } +} diff --git a/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationHandler.java b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationHandler.java new file mode 100644 index 00000000000..67e2ad4dfb7 --- /dev/null +++ b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationHandler.java @@ -0,0 +1,223 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.test; + +import com.android.tools.idea.execution.common.DeployableToDevice; +import com.android.tools.idea.run.ValidationError; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.idea.blaze.android.run.ApkBuildStepProvider; +import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationCommonState; +import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationHandler; +import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationValidationUtil; +import com.google.idea.blaze.android.run.LaunchMetrics; +import com.google.idea.blaze.android.run.runner.ApkBuildStep; +import com.google.idea.blaze.android.run.runner.BlazeAndroidRunConfigurationRunner; +import com.google.idea.blaze.android.run.runner.BlazeAndroidRunContext; +import com.google.idea.blaze.android.run.runner.FullApkBuildStep; +import com.google.idea.blaze.android.run.test.BlazeAndroidTestLaunchMethodsProvider.AndroidTestLaunchMethod; +import com.google.idea.blaze.base.command.BlazeCommandName; +import com.google.idea.blaze.base.command.BlazeInvocationContext; +import com.google.idea.blaze.base.model.primitives.Label; +import com.google.idea.blaze.base.model.primitives.TargetExpression; +import com.google.idea.blaze.base.projectview.ProjectViewManager; +import com.google.idea.blaze.base.projectview.ProjectViewSet; +import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration; +import com.google.idea.blaze.base.run.BlazeConfigurationNameBuilder; +import com.google.idea.blaze.base.run.ExecutorType; +import com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationRunner; +import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.sync.data.BlazeDataStorage; +import com.google.idea.blaze.base.sync.projectstructure.ModuleFinder; +import com.google.idea.blaze.java.AndroidBlazeRules; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.Executor; +import com.intellij.execution.JavaExecutionUtil; +import com.intellij.execution.configurations.RuntimeConfigurationException; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import java.util.List; +import javax.annotation.Nullable; +import org.jetbrains.android.facet.AndroidFacet; + +/** + * {@link com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationHandler} for + * android_test targets. + */ +public class BlazeAndroidTestRunConfigurationHandler + implements BlazeAndroidRunConfigurationHandler { + private final Project project; + private final BlazeAndroidTestRunConfigurationState configState; + + BlazeAndroidTestRunConfigurationHandler(BlazeCommandRunConfiguration configuration) { + this.project = configuration.getProject(); + this.configState = + new BlazeAndroidTestRunConfigurationState( + Blaze.buildSystemName(configuration.getProject())); + configuration.putUserData(DeployableToDevice.getKEY(), true); + } + + @Override + public BlazeAndroidTestRunConfigurationState getState() { + return configState; + } + + @Override + public BlazeAndroidRunConfigurationCommonState getCommonState() { + return configState.getCommonState(); + } + + @Override + public BlazeCommandRunConfigurationRunner createRunner( + Executor executor, ExecutionEnvironment env) throws ExecutionException { + Project project = env.getProject(); + BlazeCommandRunConfiguration configuration = + BlazeAndroidRunConfigurationHandler.getCommandConfig(env); + + BlazeAndroidRunConfigurationValidationUtil.validate(project); + Module module = + ModuleFinder.getInstance(env.getProject()) + .findModuleByName(BlazeDataStorage.WORKSPACE_MODULE_NAME); + AndroidFacet facet = module != null ? AndroidFacet.getInstance(module) : null; + ProjectViewSet projectViewSet = ProjectViewManager.getInstance(project).getProjectViewSet(); + + ImmutableList blazeFlags = + configState + .getCommonState() + .getExpandedBuildFlags( + project, + projectViewSet, + BlazeCommandName.TEST, + BlazeInvocationContext.runConfigContext( + ExecutorType.fromExecutor(env.getExecutor()), configuration.getType(), false)); + ImmutableList exeFlags = + ImmutableList.copyOf( + configState.getCommonState().getExeFlagsState().getFlagsForExternalProcesses()); + + // We collect metrics from a few different locations. In order to tie them all + // together, we create a unique launch id. + String launchId = LaunchMetrics.newLaunchId(); + Label label = Label.create(configuration.getSingleTarget().toString()); + + ApkBuildStep buildStep = + getTestBuildStep( + project, configState, configuration, blazeFlags, exeFlags, launchId, label); + + BlazeAndroidRunContext runContext = + new BlazeAndroidTestRunContext( + project, facet, configuration, env, configState, label, blazeFlags, buildStep); + + LaunchMetrics.logTestLaunch( + launchId, configState.getLaunchMethod().name(), env.getExecutor().getId()); + + return new BlazeAndroidRunConfigurationRunner(module, runContext, configuration); + } + + private static ApkBuildStep getTestBuildStep( + Project project, + BlazeAndroidTestRunConfigurationState configState, + BlazeCommandRunConfiguration configuration, + ImmutableList blazeFlags, + ImmutableList exeFlags, + String launchId, + Label label) + throws ExecutionException { + if (configuration.getTargetKind() + == AndroidBlazeRules.RuleTypes.ANDROID_INSTRUMENTATION_TEST.getKind()) { + boolean useMobileInstall = + AndroidTestLaunchMethod.MOBILE_INSTALL.equals(configState.getLaunchMethod()); + return ApkBuildStepProvider.getInstance(Blaze.getBuildSystemName(project)) + .getAitBuildStep( + project, + useMobileInstall, + /* nativeDebuggingEnabled= */ false, + label, + blazeFlags, + exeFlags, + launchId); + } else { + // TODO(b/248317444): This path is only invoked for the deprecated {@code android_test} + // targets, and should eventually be removed. + return new FullApkBuildStep(project, label, blazeFlags, /* nativeDebuggingEnabled= */ false); + } + } + + @Override + public final void checkConfiguration() throws RuntimeConfigurationException { + BlazeAndroidRunConfigurationValidationUtil.throwTopConfigurationError(validate()); + } + + /** + * We collect errors rather than throwing to avoid missing fatal errors by exiting early for a + * warning. We use a separate method for the collection so the compiler prevents us from + * accidentally throwing. + */ + private List validate() { + List errors = Lists.newArrayList(); + errors.addAll(BlazeAndroidRunConfigurationValidationUtil.validateWorkspaceModule(project)); + errors.addAll(configState.validate(project)); + return errors; + } + + @Override + @Nullable + public String suggestedName(BlazeCommandRunConfiguration configuration) { + TargetExpression target = configuration.getSingleTarget(); + if (target == null) { + return null; + } + BlazeConfigurationNameBuilder nameBuilder = new BlazeConfigurationNameBuilder(configuration); + + boolean isClassTest = + configState.getTestingType() == BlazeAndroidTestRunConfigurationState.TEST_CLASS; + boolean isMethodTest = + configState.getTestingType() == BlazeAndroidTestRunConfigurationState.TEST_METHOD; + if ((isClassTest || isMethodTest) && configState.getClassName() != null) { + // Get the class name without the package. + String className = JavaExecutionUtil.getPresentableClassName(configState.getClassName()); + if (className != null) { + String targetString = className; + if (isMethodTest) { + targetString += "#" + configState.getMethodName(); + } + + if (getState().getLaunchMethod().equals(AndroidTestLaunchMethod.NON_BLAZE)) { + return targetString; + } else { + return nameBuilder.setTargetString(targetString).build(); + } + } + } + return nameBuilder.build(); + } + + @Override + @Nullable + public BlazeCommandName getCommandName() { + if (getState().getLaunchMethod().equals(AndroidTestLaunchMethod.BLAZE_TEST)) { + return BlazeCommandName.TEST; + } else if (getState().getLaunchMethod().equals(AndroidTestLaunchMethod.MOBILE_INSTALL)) { + return BlazeCommandName.MOBILE_INSTALL; + } + return null; + } + + @Override + public String getHandlerName() { + return "Android Test Handler"; + } +} diff --git a/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunContext.java b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunContext.java new file mode 100644 index 00000000000..d2e7c57efb1 --- /dev/null +++ b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunContext.java @@ -0,0 +1,294 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.test; + +import static com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryNormalBuildRunContextBase.getApkInfoToInstall; + +import com.android.ddmlib.ClientData; +import com.android.ddmlib.IDevice; +import com.android.tools.idea.execution.common.DeployOptions; +import com.android.tools.idea.execution.common.debug.AndroidDebugger; +import com.android.tools.idea.execution.common.debug.AndroidDebuggerState; +import com.android.tools.idea.execution.common.debug.DebugSessionStarter; +import com.android.tools.idea.projectsystem.ApplicationProjectContext; +import com.android.tools.idea.run.ApkProvider; +import com.android.tools.idea.run.ApkProvisionException; +import com.android.tools.idea.run.ApplicationIdProvider; +import com.android.tools.idea.run.ConsoleProvider; +import com.android.tools.idea.run.LaunchOptions; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; +import com.android.tools.idea.run.blaze.BlazeLaunchTasksProvider; +import com.android.tools.idea.run.editor.ProfilerState; +import com.android.tools.idea.run.tasks.DeployTasksCompat; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.idea.blaze.android.run.BazelApplicationProjectContext; +import com.google.idea.blaze.android.run.deployinfo.BlazeAndroidDeployInfo; +import com.google.idea.blaze.android.run.deployinfo.BlazeApkProviderService; +import com.google.idea.blaze.android.run.runner.ApkBuildStep; +import com.google.idea.blaze.android.run.runner.BlazeAndroidDeviceSelector; +import com.google.idea.blaze.android.run.runner.BlazeAndroidLaunchTasksProvider; +import com.google.idea.blaze.android.run.runner.BlazeAndroidRunContext; +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.BlazeTestUiSession; +import com.google.idea.blaze.base.run.testlogs.BlazeTestResultHolder; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.Executor; +import com.intellij.execution.process.NopProcessHandler; +import com.intellij.execution.process.ProcessHandler; +import com.intellij.execution.process.ProcessOutputTypes; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.ui.ConsoleView; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.project.Project; +import com.intellij.xdebugger.XDebugSession; +import java.util.List; +import javax.annotation.Nullable; +import kotlin.Unit; +import kotlin.coroutines.EmptyCoroutineContext; +import kotlinx.coroutines.BuildersKt; +import org.jetbrains.android.facet.AndroidFacet; + +/** Run context for android_test. */ +public class BlazeAndroidTestRunContext implements BlazeAndroidRunContext { + protected final Project project; + protected final AndroidFacet facet; + protected final BlazeCommandRunConfiguration runConfiguration; + protected final ExecutionEnvironment env; + protected final BlazeAndroidTestRunConfigurationState configState; + protected final Label label; + protected final ImmutableList blazeFlags; + protected final List launchTaskCompleteListeners = Lists.newArrayList(); + protected final ConsoleProvider consoleProvider; + protected final ApkBuildStep buildStep; + protected final ApplicationIdProvider applicationIdProvider; + protected final ApkProvider apkProvider; + private final BlazeTestResultHolder testResultsHolder = new BlazeTestResultHolder(); + + public BlazeAndroidTestRunContext( + Project project, + AndroidFacet facet, + BlazeCommandRunConfiguration runConfiguration, + ExecutionEnvironment env, + BlazeAndroidTestRunConfigurationState configState, + Label label, + ImmutableList blazeFlags, + ApkBuildStep buildStep) { + this.project = project; + this.facet = facet; + this.runConfiguration = runConfiguration; + this.env = env; + this.label = label; + this.configState = configState; + this.buildStep = buildStep; + this.blazeFlags = blazeFlags; + switch (configState.getLaunchMethod()) { + case MOBILE_INSTALL: + case NON_BLAZE: + consoleProvider = new AitIdeTestConsoleProvider(runConfiguration, configState); + break; + case BLAZE_TEST: + BlazeTestUiSession session = + BlazeTestUiSession.create(ImmutableList.of(), testResultsHolder); + this.consoleProvider = new AitBlazeTestConsoleProvider(project, runConfiguration, session); + break; + default: + throw new IllegalStateException( + "Unsupported launch method " + configState.getLaunchMethod()); + } + applicationIdProvider = new BlazeAndroidTestApplicationIdProvider(buildStep); + apkProvider = BlazeApkProviderService.getInstance().getApkProvider(project, buildStep); + } + + @Override + public BlazeAndroidDeviceSelector getDeviceSelector() { + return new BlazeAndroidDeviceSelector.NormalDeviceSelector(); + } + + @Override + public void augmentLaunchOptions(LaunchOptions.Builder options) { + options.setDeploy(!configState.getLaunchMethod().equals(AndroidTestLaunchMethod.BLAZE_TEST)); + } + + @Override + public ConsoleProvider getConsoleProvider() { + return consoleProvider; + } + + @Override + public ApplicationIdProvider getApplicationIdProvider() { + return applicationIdProvider; + } + + @Override + public ApplicationProjectContext getApplicationProjectContext() { + return new BazelApplicationProjectContext(project, getApplicationIdProvider()); + } + + @Nullable + @Override + public ApkBuildStep getBuildStep() { + return buildStep; + } + + @Override + public ProfilerState getProfileState() { + return null; + } + + @Override + public BlazeLaunchTasksProvider getLaunchTasksProvider(LaunchOptions launchOptions) + throws ExecutionException { + return new BlazeAndroidLaunchTasksProvider(project, this, applicationIdProvider, launchOptions); + } + + @Override + public ImmutableList getDeployTasks(IDevice device, DeployOptions deployOptions) + throws ExecutionException { + if (configState.getLaunchMethod() != AndroidTestLaunchMethod.NON_BLAZE) { + return ImmutableList.of(); + } + return ImmutableList.of( + DeployTasksCompat.createDeployTask( + project, getApkInfoToInstall(device, deployOptions, apkProvider), deployOptions)); + } + + @Override + @Nullable + public BlazeLaunchTask getApplicationLaunchTask( + boolean isDebug, @Nullable Integer userId, String contributorsAmStartOptions) + throws ExecutionException { + switch (configState.getLaunchMethod()) { + case BLAZE_TEST: + BlazeAndroidTestFilter testFilter = + new BlazeAndroidTestFilter( + configState.getTestingType(), + configState.getClassName(), + configState.getMethodName(), + configState.getPackageName()); + return new BlazeAndroidTestLaunchTask( + project, label, blazeFlags, testFilter, this, isDebug, testResultsHolder); + case NON_BLAZE: + case MOBILE_INSTALL: + BlazeAndroidDeployInfo deployInfo; + try { + deployInfo = buildStep.getDeployInfo(); + } catch (ApkProvisionException e) { + throw new ExecutionException(e); + } + return StockAndroidTestLaunchTask.getStockTestLaunchTask( + configState, applicationIdProvider, isDebug, deployInfo, project); + } + throw new AssertionError(); + } + + @Override + @SuppressWarnings({"unchecked", "rawtypes"}) // Raw type from upstream. + public XDebugSession startDebuggerSession( + AndroidDebugger androidDebugger, + AndroidDebuggerState androidDebuggerState, + ExecutionEnvironment env, + IDevice device, + ConsoleView consoleView, + ProgressIndicator indicator, + String packageName) { + try { + return BuildersKt.runBlocking( + EmptyCoroutineContext.INSTANCE, + (scope, continuation) -> { + switch (configState.getLaunchMethod()) { + case BLAZE_TEST: + + /** + * Wires up listeners to automatically reconnect the debugger for each test method. + * When you `blaze test` an android_test in debug mode, it kills the instrumentation + * process between each test method, disconnecting the debugger. We listen for the + * start of a new method waiting for a debugger, and reconnect. TODO: Support + * stopping Blaze from the UI. This is hard because we have no way to distinguish + * process handler termination/debug session ending initiated by the user. + */ + final ProcessHandler masterProcessHandler = new NopProcessHandler(); + addLaunchTaskCompleteListener( + () -> { + masterProcessHandler.notifyTextAvailable( + "Test run completed.\n", ProcessOutputTypes.STDOUT); + masterProcessHandler.detachProcess(); + }); + return DebugSessionStarter.INSTANCE.attachReattachingDebuggerToStartedProcess( + device, + new BazelApplicationProjectContext(project, packageName), + masterProcessHandler, + env, + androidDebugger, + androidDebuggerState, + indicator, + consoleView, + Long.MAX_VALUE, + continuation); + case NON_BLAZE: + case MOBILE_INSTALL: + return DebugSessionStarter.INSTANCE.attachDebuggerToStartedProcess( + device, + new BazelApplicationProjectContext(project, packageName), + env, + androidDebugger, + androidDebuggerState, + /*destroyRunningProcess*/ d -> { + d.forceStop(packageName); + return Unit.INSTANCE; + }, + indicator, + consoleView, + Long.MAX_VALUE, + ClientData.DebuggerStatus.WAITING, + continuation); + } + throw new RuntimeException("Unknown lunch mode"); + }); + } catch (InterruptedException e) { + throw new ProcessCanceledException(); + } + } + + void onLaunchTaskComplete() { + for (Runnable runnable : launchTaskCompleteListeners) { + runnable.run(); + } + } + + void addLaunchTaskCompleteListener(Runnable runnable) { + launchTaskCompleteListeners.add(runnable); + } + + @Override + public Executor getExecutor() { + return env.getExecutor(); + } + + @Nullable + @Override + public Integer getUserId(IDevice device) { + return null; + } + + @Override + public String getAmStartOptions() { + return ""; + } +} diff --git a/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/testrecorder/TestRecorderBlazeCommandRunConfigurationProxy.java b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/testrecorder/TestRecorderBlazeCommandRunConfigurationProxy.java new file mode 100644 index 00000000000..fa96a5dc6e1 --- /dev/null +++ b/aswb/sdkcompat/as233/com/google/idea/blaze/android/run/testrecorder/TestRecorderBlazeCommandRunConfigurationProxy.java @@ -0,0 +1,34 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.testrecorder; + +import com.google.idea.blaze.android.cppimpl.debug.BlazeAutoAndroidDebuggerBase; +import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration; + +/** Compat class for implementing TestRecorderBlazeCommandRunConfigurationProxy. */ +public class TestRecorderBlazeCommandRunConfigurationProxy + extends TestRecorderBlazeCommandRunConfigurationProxyBase { + + public TestRecorderBlazeCommandRunConfigurationProxy( + BlazeCommandRunConfiguration baseConfiguration) { + super(baseConfiguration); + } + + @Override + public boolean isNativeProject() { + return BlazeAutoAndroidDebuggerBase.isNativeCodeInProject(getModule().getProject()); + } +} diff --git a/aswb/sdkcompat/as233/com/google/idea/blaze/android/sync/model/idea/BlazeAndroidModel.java b/aswb/sdkcompat/as233/com/google/idea/blaze/android/sync/model/idea/BlazeAndroidModel.java new file mode 100644 index 00000000000..00a9e4a94c9 --- /dev/null +++ b/aswb/sdkcompat/as233/com/google/idea/blaze/android/sync/model/idea/BlazeAndroidModel.java @@ -0,0 +1,53 @@ +/* + * Copyright 2020 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 com.android.tools.idea.model.AndroidModel; +import com.android.tools.idea.model.Namespacing; +import com.android.tools.idea.projectsystem.NamedIdeaSourceProvider; +import com.google.common.util.concurrent.ListenableFuture; +import com.intellij.openapi.project.Project; +import java.io.File; + +/** Blaze model for an android project. #api42. */ +public class BlazeAndroidModel extends BlazeAndroidModelBase { + private final NamedIdeaSourceProvider sourceProvider; + + public BlazeAndroidModel( + Project project, + File rootDirPath, + NamedIdeaSourceProvider sourceProvider, + ListenableFuture applicationId, + int minSdkVersion, + boolean desugarJava8Libs) { + super(project, rootDirPath, applicationId, minSdkVersion, desugarJava8Libs); + this.sourceProvider = sourceProvider; + } + + public NamedIdeaSourceProvider getDefaultSourceProvider() { + return sourceProvider; + } + + @Override + public Namespacing getNamespacing() { + return Namespacing.DISABLED; + } + + @Override + protected String uninitializedApplicationId() { + return AndroidModel.UNINITIALIZED_APPLICATION_ID; + } +} diff --git a/aswb/src/META-INF/aswb.xml b/aswb/src/META-INF/aswb.xml index 890cafc1145..63b5e6b5796 100644 --- a/aswb/src/META-INF/aswb.xml +++ b/aswb/src/META-INF/aswb.xml @@ -19,6 +19,7 @@ com.intellij.modules.androidstudio org.jetbrains.android com.google.gct.test.recorder + com.intellij.modules.java @@ -31,8 +32,6 @@ - + @@ -66,11 +66,13 @@ + + @@ -94,7 +96,10 @@ - + + + + @@ -106,7 +111,7 @@ - + @@ -118,4 +123,8 @@ + + + + diff --git a/aswb/src/META-INF/ndk_contents.xml b/aswb/src/META-INF/ndk_contents.xml index 578be7294fe..6afc76d1025 100644 --- a/aswb/src/META-INF/ndk_contents.xml +++ b/aswb/src/META-INF/ndk_contents.xml @@ -20,6 +20,10 @@ + + + + diff --git a/aswb/src/com/android/tools/idea/run/ValidationErrorCompat.java b/aswb/src/com/android/tools/idea/run/ValidationErrorCompat.java new file mode 100644 index 00000000000..fb233c7eeb8 --- /dev/null +++ b/aswb/src/com/android/tools/idea/run/ValidationErrorCompat.java @@ -0,0 +1,25 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR 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.run; + +/** Compat class for {@link ValidationError} */ +public class ValidationErrorCompat { + private ValidationErrorCompat() {} + + public static ValidationError fatal(String message, Runnable quickFick) { + return ValidationError.fatal(message, (dataContext) -> quickFick.run()); + } +} 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 dbd8f2384c2..29cd9527b38 100644 --- a/aswb/src/com/google/idea/blaze/android/cppimpl/BlazeNdkSupportEnabler.java +++ b/aswb/src/com/google/idea/blaze/android/cppimpl/BlazeNdkSupportEnabler.java @@ -19,22 +19,30 @@ import com.android.tools.ndk.NdkHelper; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; 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.qsync.BlazeProjectListenerProvider; +import com.google.idea.blaze.base.qsync.QuerySyncProject; import com.google.idea.blaze.base.scope.BlazeContext; import com.google.idea.blaze.base.settings.BlazeImportSettings; import com.google.idea.blaze.base.sync.SyncListener; import com.google.idea.blaze.base.sync.SyncMode; import com.google.idea.blaze.base.sync.SyncResult; +import com.google.idea.blaze.common.Context; +import com.google.idea.blaze.qsync.BlazeProjectListener; +import com.google.idea.blaze.qsync.BlazeProjectSnapshot; +import com.google.idea.blaze.qsync.project.QuerySyncLanguage; 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.OCWorkspaceEventImpl; import com.jetbrains.cidr.lang.workspace.OCWorkspaceModificationTrackersImpl; +import java.util.Set; -final class BlazeNdkSupportEnabler implements SyncListener { +final class BlazeNdkSupportEnabler implements SyncListener, BlazeProjectListenerProvider { @Override public void onSyncComplete( @@ -51,6 +59,22 @@ public void onSyncComplete( enableCSupportInIde(project, enabled); } + @Override + public BlazeProjectListener createListener(QuerySyncProject project) { + return new BlazeProjectListener() { + @Override + public void onNewProjectSnapshot(Context context, BlazeProjectSnapshot instance) { + + Set allLanguages = + Sets.union( + instance.queryData().projectDefinition().languageClasses(), + QuerySyncLanguage.fromProtoList(instance.project().getActiveLanguagesList())); + + enableCSupportInIde(project.getIdeProject(), allLanguages.contains(QuerySyncLanguage.CC)); + } + }; + } + /** * If {@code enabled} is true, this method will enable C support in the IDE if it is not already * enabled. if {@code enabled} is false this method will clear out any currently stored diff --git a/aswb/src/com/google/idea/blaze/android/cppimpl/NdkCppSupportChecker.java b/aswb/src/com/google/idea/blaze/android/cppimpl/NdkCppSupportChecker.java new file mode 100644 index 00000000000..bfddb725345 --- /dev/null +++ b/aswb/src/com/google/idea/blaze/android/cppimpl/NdkCppSupportChecker.java @@ -0,0 +1,41 @@ +/* + * Copyright 2024 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.cppimpl; + +import com.android.tools.ndk.AndroidSysroot; +import com.android.tools.ndk.configuration.NdkConfigurationValidatorKt; +import com.google.idea.blaze.cpp.CppSupportChecker; +import com.jetbrains.cidr.lang.toolchains.CidrCompilerSwitches; +import com.jetbrains.cidr.lang.toolchains.CidrCompilerSwitches.Format; +import java.nio.file.Path; + +/** + * Uses the Android Studio API to check if a CPP configuration is supported, i.e. is for NDK + * development rather than non-Android CPP. + */ +public class NdkCppSupportChecker implements CppSupportChecker { + + @Override + public boolean supportsCppConfiguration( + CidrCompilerSwitches compilerSwitches, Path workspaceRoot) { + return NdkConfigurationValidatorKt.isValidNdkConfiguration( + compilerSwitches.getList(Format.RAW), + workspaceRoot, + AndroidSysroot::isValidAndroidSysroot, + AndroidSysroot::isValidAndroidSysrootUsrInclude, + AndroidSysroot::isPotentialNonAndroidSysrootUsrInclude); + } +} diff --git a/aswb/src/com/google/idea/blaze/android/cppimpl/debug/BlazeAutoAndroidDebuggerBase.java b/aswb/src/com/google/idea/blaze/android/cppimpl/debug/BlazeAutoAndroidDebuggerBase.java index f920f1414d6..855aee1589c 100644 --- a/aswb/src/com/google/idea/blaze/android/cppimpl/debug/BlazeAutoAndroidDebuggerBase.java +++ b/aswb/src/com/google/idea/blaze/android/cppimpl/debug/BlazeAutoAndroidDebuggerBase.java @@ -21,7 +21,6 @@ import com.google.idea.blaze.base.settings.Blaze; import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; /** Attached either java or native debugger depending on if native debugging is enabled. */ @@ -32,17 +31,16 @@ public abstract class BlazeAutoAndroidDebuggerBase extends AutoAndroidDebugger { @Override protected boolean isNativeProject(Project project) { + return isNativeCodeInProject(project); + } + + public static boolean isNativeCodeInProject(Project project) { BlazeProjectData blazeProjectData = BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); return blazeProjectData != null && blazeProjectData.getWorkspaceLanguageSettings().isLanguageActive(LanguageClass.C); } - @Override - protected boolean isNativeDeployment(Project project, Module debuggeeModule) { - return isNativeProject(project); - } - @Override public String getId() { return ID; diff --git a/aswb/src/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeAndroidDebuggerBase.java b/aswb/src/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeAndroidDebuggerBase.java deleted file mode 100644 index dd6e972e66d..00000000000 --- a/aswb/src/com/google/idea/blaze/android/cppimpl/debug/BlazeNativeAndroidDebuggerBase.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright 2020 The Bazel Authors. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.cppimpl.debug; - -import com.android.tools.idea.run.ValidationError; -import com.android.tools.ndk.run.attach.AndroidNativeAttachConfiguration; -import com.android.tools.ndk.run.editor.NativeAndroidDebugger; -import com.google.idea.blaze.base.model.BlazeProjectData; -import com.google.idea.blaze.base.model.primitives.LanguageClass; -import com.google.idea.blaze.base.settings.Blaze; -import com.google.idea.blaze.base.sync.data.BlazeDataStorage; -import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; -import com.intellij.execution.BeforeRunTask; -import com.intellij.execution.Executor; -import com.intellij.execution.configurations.ConfigurationFactory; -import com.intellij.execution.configurations.ConfigurationType; -import com.intellij.execution.configurations.RunConfiguration; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.Messages; -import icons.BlazeIcons; -import java.util.Collections; -import java.util.List; -import javax.swing.Icon; -import org.jetbrains.annotations.Nls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * Extension of {@link NativeAndroidDebugger} with the following key differences compared to {@link - * NativeAndroidDebugger}. - * - *

    - *
  • Overrides {@link #supportsProject} so native debugger is only enabled for native support is - * enabled. - *
- */ -public class BlazeNativeAndroidDebuggerBase extends NativeAndroidDebugger { - /** - * This ID needs to be lexicographically larger than "Java" so it come after the "Java" debugger - * when sorted lexicographically in the "Attach Debugger to Android Process" dialog. See {@link - * org.jetbrains.android.actions.AndroidProcessChooserDialog#populateDebuggerTypeCombo}. - */ - public static final String ID = "Native" + Blaze.defaultBuildSystemName(); - - @Override - public String getId() { - return ID; - } - - @Override - public String getDisplayName() { - return "Native Only"; - } - - @Override - public boolean supportsProject(Project project) { - BlazeProjectData blazeProjectData = - BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); - return blazeProjectData != null - && blazeProjectData.getWorkspaceLanguageSettings().isLanguageActive(LanguageClass.C); - } - - @Override - @Nullable - public Module findModuleForProcess(Project project, String packageName) { - // Blaze plugin uses the workspace module for all run/debug related tasks. - return ModuleManager.getInstance(project) - .findModuleByName(BlazeDataStorage.WORKSPACE_MODULE_NAME); - } - - @Override - protected void handleModuleNotFound(String packageName) { - // Can only happen if the workspace module is missing. The workspace module should always - // be present as long as there's been a successful sync. - ApplicationManager.getApplication() - .invokeLater( - () -> - Messages.showErrorDialog( - "Module information missing.\nPlease try again after a successful sync.", - "Module Information Missing")); - } - - /** - * Extension of {@link AndroidNativeAttachConfiguration} with the following specializations: - * - *
    - *
  • Performs NO-OP during run configuration validation. The inherited {@link #validate} - * method contains validations specific to gradle based projects, such as special checks for - * library projects and outdated manifest checks that don't work for blaze projects. See - * implementation of {@link com.android.tools.idea.run.AndroidRunConfigurationBase#validate} - * for more details. - *
  • Returns empty list for before run tasks. The inherited {@link #getBeforeRunTasks()} - * returns gradle specific before-run tasks that aren't applicable to blaze projects. - *
- * - * Currently {@link #validate} doesn't perform any validations, but as common errors and use-cases - * are surfaced we should add them to the method. - */ - protected static class BlazeAndroidNativeAttachConfiguration - extends AndroidNativeAttachConfiguration { - BlazeAndroidNativeAttachConfiguration(Project project, ConfigurationFactory factory) { - super(project, factory); - } - - // Only required for #api203 - @Nullable - public String getPackageNameOverride() { - // "package name override" is not required in blaze projects. - return null; - } - - /** Don't validate anything. */ - @Override - public List validate(@Nullable Executor executor) { - return Collections.emptyList(); - } - - /** Overridden to stop providing Grade tasks from superclasses */ - @Override - public List> getBeforeRunTasks() { - return Collections.emptyList(); - } - } - - /** Configuration type for {@link BlazeAndroidNativeAttachConfiguration}. */ - protected static class BlazeAndroidNativeAttachConfigurationType implements ConfigurationType { - @NotNull - @Override - public String getDisplayName() { - return "Android Native Attach"; - } - - @Nls - @Override - public String getConfigurationTypeDescription() { - return "Android Native Attach Configuration"; - } - - @Override - public Icon getIcon() { - return BlazeIcons.Logo; - } - - @NotNull - @Override - public String getId() { - return "BlazeAndroidNativeAttachConfigurationType"; - } - - @Override - public ConfigurationFactory[] getConfigurationFactories() { - return new ConfigurationFactory[] {new Factory()}; - } - - /** Makes new {@link BlazeAndroidNativeAttachConfiguration}. */ - static class Factory extends ConfigurationFactory { - protected Factory() { - super(new BlazeAndroidNativeAttachConfigurationType()); - } - - @Override - public RunConfiguration createTemplateConfiguration(Project project) { - return new BlazeAndroidNativeAttachConfiguration(project, this); - } - } - } -} diff --git a/aswb/src/com/google/idea/blaze/android/editor/ProjectUnresolvedResourceStatsCollector.java b/aswb/src/com/google/idea/blaze/android/editor/ProjectUnresolvedResourceStatsCollector.java index a4a8e072598..f7059b88f3f 100644 --- a/aswb/src/com/google/idea/blaze/android/editor/ProjectUnresolvedResourceStatsCollector.java +++ b/aswb/src/com/google/idea/blaze/android/editor/ProjectUnresolvedResourceStatsCollector.java @@ -39,7 +39,6 @@ import com.google.idea.blaze.base.syncstatus.SyncStatusContributor; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.fileTypes.FileTypeRegistry; import com.intellij.openapi.project.Project; @@ -60,7 +59,7 @@ /** Class to log unresolved resource symbols per project. */ class ProjectUnresolvedResourceStatsCollector implements Disposable { static ProjectUnresolvedResourceStatsCollector getInstance(Project project) { - return ServiceManager.getService(project, ProjectUnresolvedResourceStatsCollector.class); + return project.getService(ProjectUnresolvedResourceStatsCollector.class); } private final WorkspacePathResolverProvider workspacePathResolverProvider; diff --git a/aswb/src/com/google/idea/blaze/android/filecache/ArtifactCache.java b/aswb/src/com/google/idea/blaze/android/filecache/ArtifactCache.java index ee1f4da02fe..aa4c0be7ba0 100644 --- a/aswb/src/com/google/idea/blaze/android/filecache/ArtifactCache.java +++ b/aswb/src/com/google/idea/blaze/android/filecache/ArtifactCache.java @@ -15,8 +15,8 @@ */ package com.google.idea.blaze.android.filecache; -import com.google.idea.blaze.base.command.buildresult.OutputArtifact; import com.google.idea.blaze.base.scope.BlazeContext; +import com.google.idea.blaze.common.artifact.OutputArtifactWithoutDigest; import java.nio.file.Path; import java.util.Collection; import javax.annotation.Nullable; @@ -35,19 +35,21 @@ public interface ArtifactCache { void clearCache(); /** - * Fetches and caches the given collection of {@link OutputArtifact} to disk. + * Fetches and caches the given collection of {@link OutputArtifactWithoutDigest} to disk. * * @param artifacts Collection of artifacts to add to cache * @param removeMissingArtifacts if true, will remove any cached artifact that is not present in * {@code artifacts}. */ void putAll( - Collection artifacts, BlazeContext context, boolean removeMissingArtifacts); + Collection artifacts, + BlazeContext context, + boolean removeMissingArtifacts); /** - * Returns the {@link Path} corresponding to the given {@link OutputArtifact}, or {@code null} if - * the artifact is not tracked in cache. + * Returns the {@link Path} corresponding to the given {@link OutputArtifactWithoutDigest}, or + * {@code null} if the artifact is not tracked in cache. */ @Nullable - Path get(OutputArtifact artifact); + Path get(OutputArtifactWithoutDigest artifact); } diff --git a/aswb/src/com/google/idea/blaze/android/filecache/ArtifactMetadata.java b/aswb/src/com/google/idea/blaze/android/filecache/ArtifactMetadata.java index 72bd362792e..c23af23af27 100644 --- a/aswb/src/com/google/idea/blaze/android/filecache/ArtifactMetadata.java +++ b/aswb/src/com/google/idea/blaze/android/filecache/ArtifactMetadata.java @@ -18,8 +18,9 @@ import com.google.devtools.intellij.model.ProjectData; import com.google.devtools.intellij.model.ProjectData.LocalFile; import com.google.devtools.intellij.model.ProjectData.LocalFileOrOutputArtifact; -import com.google.idea.blaze.base.command.buildresult.OutputArtifact; -import com.google.idea.blaze.base.filecache.ArtifactState; +import com.google.idea.blaze.base.filecache.ArtifactStateProtoConverter; +import com.google.idea.blaze.common.artifact.ArtifactState; +import com.google.idea.blaze.common.artifact.OutputArtifactWithoutDigest; import java.util.Objects; /** Data class to (de)serialize metadata of an artifact in cache */ @@ -66,14 +67,15 @@ public int hashCode() { * * @throws ArtifactNotFoundException if the artifact is not present. */ - public static ArtifactMetadata forArtifact(OutputArtifact artifact) + public static ArtifactMetadata forArtifact(OutputArtifactWithoutDigest artifact) throws ArtifactNotFoundException { ArtifactState artifactState = artifact.toArtifactState(); if (artifactState == null) { throw new ArtifactNotFoundException(artifact); } // Serialize to proto to make grabbing the fields easier - LocalFileOrOutputArtifact serializedArtifact = artifactState.serializeToProto(); + LocalFileOrOutputArtifact serializedArtifact = + ArtifactStateProtoConverter.toProto(artifactState); if (serializedArtifact.hasArtifact()) { ProjectData.OutputArtifact o = serializedArtifact.getArtifact(); return new ArtifactMetadata(o.getRelativePath(), o.getId()); diff --git a/aswb/src/com/google/idea/blaze/android/filecache/ArtifactNotFoundException.java b/aswb/src/com/google/idea/blaze/android/filecache/ArtifactNotFoundException.java index f6893f8100b..9161f015b62 100644 --- a/aswb/src/com/google/idea/blaze/android/filecache/ArtifactNotFoundException.java +++ b/aswb/src/com/google/idea/blaze/android/filecache/ArtifactNotFoundException.java @@ -15,12 +15,12 @@ */ package com.google.idea.blaze.android.filecache; -import com.google.idea.blaze.base.command.buildresult.OutputArtifact; +import com.google.idea.blaze.common.artifact.OutputArtifactWithoutDigest; import java.io.IOException; /** Signals that a particular artifact cannot be located. */ public class ArtifactNotFoundException extends IOException { - public ArtifactNotFoundException(OutputArtifact artifact) { + public ArtifactNotFoundException(OutputArtifactWithoutDigest artifact) { super(artifact.getRelativePath() + " not found."); } } diff --git a/aswb/src/com/google/idea/blaze/android/filecache/CacheEntry.java b/aswb/src/com/google/idea/blaze/android/filecache/CacheEntry.java index 545ab86ad64..23f99cc4605 100644 --- a/aswb/src/com/google/idea/blaze/android/filecache/CacheEntry.java +++ b/aswb/src/com/google/idea/blaze/android/filecache/CacheEntry.java @@ -17,8 +17,8 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableSet; -import com.google.idea.blaze.base.command.buildresult.BlazeArtifact; -import com.google.idea.blaze.base.command.buildresult.OutputArtifact; +import com.google.idea.blaze.common.artifact.BlazeArtifact; +import com.google.idea.blaze.common.artifact.OutputArtifactWithoutDigest; import com.intellij.openapi.util.io.FileUtil; import com.intellij.util.PathUtil; import java.util.Objects; @@ -76,7 +76,7 @@ public int hashCode() { /** Returns a {@link CacheEntry} corresponding to the given {@code OutputArtifact}. */ @VisibleForTesting - public static CacheEntry forArtifact(OutputArtifact blazeArtifact) + public static CacheEntry forArtifact(OutputArtifactWithoutDigest blazeArtifact) throws ArtifactNotFoundException { ArtifactMetadata artifactMetadata = ArtifactMetadata.forArtifact(blazeArtifact); diff --git a/aswb/src/com/google/idea/blaze/android/filecache/CacheFetcher.java b/aswb/src/com/google/idea/blaze/android/filecache/CacheFetcher.java new file mode 100644 index 00000000000..b1a1ef03d19 --- /dev/null +++ b/aswb/src/com/google/idea/blaze/android/filecache/CacheFetcher.java @@ -0,0 +1,36 @@ +/* + * Copyright 2022 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.filecache; + +import com.google.idea.blaze.base.model.BlazeProjectData; +import com.google.idea.blaze.base.projectview.ProjectViewSet; +import java.io.File; + +/** Fetches or loads files in cache directory. */ +public interface CacheFetcher { + /** Fetches files to dir. */ + void fetch(ProjectViewSet projectViewSet, BlazeProjectData projectData, File dir); + + /** Returns a cache key for file. */ + default String getCacheKey(File file) { + return file.getName(); + } + + /** Post-processes a cache file. */ + default File processCacheFile(File file) { + return file; + } +} diff --git a/aswb/src/com/google/idea/blaze/android/filecache/LocalArtifactCache.java b/aswb/src/com/google/idea/blaze/android/filecache/LocalArtifactCache.java index 41380081e14..e2ac2d64d18 100644 --- a/aswb/src/com/google/idea/blaze/android/filecache/LocalArtifactCache.java +++ b/aswb/src/com/google/idea/blaze/android/filecache/LocalArtifactCache.java @@ -24,16 +24,16 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.idea.blaze.base.async.FutureUtil; -import com.google.idea.blaze.base.command.buildresult.BlazeArtifact; -import com.google.idea.blaze.base.command.buildresult.OutputArtifact; +import com.google.idea.blaze.base.command.buildresult.RemoteOutputArtifact; import com.google.idea.blaze.base.io.FileOperationProvider; import com.google.idea.blaze.base.logging.EventLoggingService; import com.google.idea.blaze.base.prefetch.FetchExecutor; import com.google.idea.blaze.base.prefetch.RemoteArtifactPrefetcher; import com.google.idea.blaze.base.scope.BlazeContext; import com.google.idea.blaze.base.scope.output.IssueOutput; -import com.google.idea.blaze.base.scope.output.PrintOutput; import com.google.idea.blaze.base.scope.scopes.TimingScope.EventType; +import com.google.idea.blaze.common.PrintOutput; +import com.google.idea.blaze.common.artifact.OutputArtifactWithoutDigest; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Pair; @@ -58,7 +58,7 @@ import javax.annotation.Nullable; /** - * LocalArtifactCache is an on disk cache for {@link OutputArtifact}s. + * LocalArtifactCache is an on disk cache for {@link OutputArtifactWithoutDigest}s. * *

The {@link #get} returns the path to a local copy of the cached artifact (if present). * @@ -75,9 +75,9 @@ * is set to true. * * - * Internally, every {@link OutputArtifact} is mapped to a {@link CacheEntry} object, which captures - * metadata about the artifact including data like timestamp or objfs blobId that allows it to - * determine when an artifact has been updated. + * Internally, every {@link OutputArtifactWithoutDigest} is mapped to a {@link CacheEntry} object, + * which captures metadata about the artifact including data like timestamp or objfs blobId that + * allows it to determine when an artifact has been updated. * *

An in memory map of these CacheEntries allows for quick retrieval of the cached objects. But * this map is also persisted to the disk after calls to {@link #clearCache} and {@link #putAll} and @@ -156,9 +156,11 @@ public synchronized void clearCache() { */ @Override public synchronized void putAll( - Collection artifacts, BlazeContext context, boolean removeMissingArtifacts) { + Collection artifacts, + BlazeContext context, + boolean removeMissingArtifacts) { // Utility maps for the passed artifacts - Map keyToArtifact = new HashMap<>(); + Map keyToArtifact = new HashMap<>(); Map keyToCacheEntry = new HashMap<>(); artifacts.forEach( a -> { @@ -184,7 +186,7 @@ public synchronized void putAll( || !cacheState.get(kv.getKey()).equals(kv.getValue())) .map(Entry::getKey) .collect(ImmutableList.toImmutableList()); - ImmutableMap updatedKeyToArtifact = + ImmutableMap updatedKeyToArtifact = updatedKeys.stream().collect(ImmutableMap.toImmutableMap(k -> k, keyToArtifact::get)); ImmutableMap updatedKeyToCacheEntry = updatedKeys.stream().collect(ImmutableMap.toImmutableMap(k -> k, keyToCacheEntry::get)); @@ -204,7 +206,7 @@ public synchronized void putAll( RemoteArtifactPrefetcher.getInstance() .downloadArtifacts( project.getName(), - BlazeArtifact.getRemoteArtifacts(updatedKeyToArtifact.values())); + RemoteOutputArtifact.getRemoteArtifacts(updatedKeyToArtifact.values())); FutureUtil.waitForFuture(context, downloadArtifactsFuture) .timed("FetchCacheArtifacts", EventType.Prefetching) .withProgressMessage(String.format("Fetching Artifacts for %s...", cacheName)) @@ -251,7 +253,7 @@ public synchronized void putAll( @Override @Nullable - public synchronized Path get(OutputArtifact artifact) { + public synchronized Path get(OutputArtifactWithoutDigest artifact) { CacheEntry queriedEntry; try { queriedEntry = CacheEntry.forArtifact(artifact); @@ -318,11 +320,11 @@ private void loadCacheData() { } /** - * Returns a list of futures copying the given {@link OutputArtifact}s to disk. The returned - * futures return the cache key on successful copy, or an empty string on copy failure. + * Returns a list of futures copying the given {@link OutputArtifactWithoutDigest}s to disk. The + * returned futures return the cache key on successful copy, or an empty string on copy failure. */ private ImmutableList> copyLocally( - Map updatedKeyToArtifact, + Map updatedKeyToArtifact, Map updatedKeyToCacheEntry) { return updatedKeyToArtifact.entrySet().stream() .map( @@ -348,7 +350,7 @@ private ImmutableList> copyLocally( .collect(ImmutableList.toImmutableList()); } - private static void copyLocally(OutputArtifact blazeArtifact, Path destinationPath) + private static void copyLocally(OutputArtifactWithoutDigest blazeArtifact, Path destinationPath) throws IOException { try (InputStream stream = blazeArtifact.getInputStream()) { Files.copy(stream, destinationPath, StandardCopyOption.REPLACE_EXISTING); diff --git a/aswb/src/com/google/idea/blaze/android/filecache/LocalDirectoryCache.java b/aswb/src/com/google/idea/blaze/android/filecache/LocalDirectoryCache.java new file mode 100644 index 00000000000..0bc13c88831 --- /dev/null +++ b/aswb/src/com/google/idea/blaze/android/filecache/LocalDirectoryCache.java @@ -0,0 +1,112 @@ +/* + * Copyright 2022 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.filecache; + +import static com.google.common.collect.ImmutableMap.toImmutableMap; +import static java.util.Arrays.stream; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.ForwardingCache.SimpleForwardingCache; +import com.google.common.collect.ImmutableMap; +import com.google.idea.blaze.base.io.FileOperationProvider; +import com.google.idea.blaze.base.model.BlazeProjectData; +import com.google.idea.blaze.base.projectview.ProjectViewSet; +import com.intellij.openapi.diagnostic.Logger; +import java.io.File; +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ExecutionException; + +/** + * A cache that handles caching files locally and maintains an in-memory cache of the same files. + */ +public class LocalDirectoryCache extends SimpleForwardingCache { + private static final Logger logger = Logger.getInstance(LocalDirectoryCache.class); + + // TODO(xinruiy): Reuse this class for other FileCache + private File cacheDir; + private CacheFetcher cacheFetcher; + + protected LocalDirectoryCache(Cache delegate) { + super(delegate); + } + + public LocalDirectoryCache(File cacheDir, CacheFetcher cacheFetcher) { + this(CacheBuilder.newBuilder().build()); + this.cacheDir = cacheDir; + this.cacheFetcher = cacheFetcher; + } + + /** Fetches files to cache directory and updates in-memory cache. */ + public void fetchFilesToCacheDir(ProjectViewSet projectViewSet, BlazeProjectData projectData) { + try { + cacheFetcher.fetch(projectViewSet, projectData, cacheDir); + } finally { + refresh(getCacheMap()); + } + } + + /** Refreshes in-memory cache to match what's stored in cacheDir. */ + public void refresh() { + FileOperationProvider.getInstance().mkdirs(cacheDir); + if (!FileOperationProvider.getInstance().isDirectory(cacheDir)) { + throw new IllegalArgumentException( + "Cache Directory '" + cacheDir + "' is not a valid directory"); + } + refresh(getCacheMap()); + } + + private void refresh(Map cacheMap) { + invalidateAll(); + putAll(cacheMap); + } + + public File get(String key) throws ExecutionException { + return get( + key, + () -> { + ImmutableMap latestValue = getCacheMap(); + if (latestValue.containsKey(key)) { + // Refresh the whole cache map as we have retrieved all files. + refresh(latestValue); + } else { + throw new IOException("Failed to find file from cache directory for key " + key); + } + return latestValue.get(key); + }); + } + + /** Removes files from cache directory and invalidates the in-memory cache. */ + public void removeAll() { + FileOperationProvider fileOperationProvider = FileOperationProvider.getInstance(); + if (fileOperationProvider.exists(cacheDir)) { + try { + fileOperationProvider.deleteDirectoryContents(cacheDir, true); + } catch (IOException e) { + logger.warn("Failed to clear unpacked AAR directory: " + cacheDir, e); + } finally { + invalidateAll(); + } + } + } + + /** Visits the cache directory and generates a map from cache key to cached file. */ + private ImmutableMap getCacheMap() { + return stream(cacheDir.listFiles()) + .collect(toImmutableMap(cacheFetcher::getCacheKey, cacheFetcher::processCacheFile)); + } +} diff --git a/aswb/src/com/google/idea/blaze/android/libraries/AarLibraryContents.java b/aswb/src/com/google/idea/blaze/android/libraries/AarLibraryContents.java index 5dd8196fc01..0fe195896bc 100644 --- a/aswb/src/com/google/idea/blaze/android/libraries/AarLibraryContents.java +++ b/aswb/src/com/google/idea/blaze/android/libraries/AarLibraryContents.java @@ -17,14 +17,16 @@ package com.google.idea.blaze.android.libraries; import com.google.auto.value.AutoValue; -import com.google.idea.blaze.base.command.buildresult.BlazeArtifact; +import com.google.common.collect.ImmutableList; +import com.google.idea.blaze.common.artifact.BlazeArtifact; import javax.annotation.Nullable; /* Provides BlazeArtifacts to unpack an Aar. */ @AutoValue abstract class AarLibraryContents { - static AarLibraryContents create(BlazeArtifact aar, @Nullable BlazeArtifact jar) { - return new AutoValue_AarLibraryContents(aar, jar); + static AarLibraryContents create( + BlazeArtifact aar, @Nullable BlazeArtifact jar, ImmutableList srcJars) { + return new AutoValue_AarLibraryContents(aar, jar, srcJars); } /* Provides BlazeArtifact of the .aar file that we need to fetch and copy locally */ @@ -40,4 +42,7 @@ static AarLibraryContents create(BlazeArtifact aar, @Nullable BlazeArtifact jar) */ @Nullable abstract BlazeArtifact jar(); + + /** Returns the source jar specified in the srcjar attribute of aar_import. */ + abstract ImmutableList srcJars(); } diff --git a/aswb/src/com/google/idea/blaze/android/libraries/RenderJarCache.java b/aswb/src/com/google/idea/blaze/android/libraries/RenderJarCache.java index 14e16b5b816..1958e0fa040 100644 --- a/aswb/src/com/google/idea/blaze/android/libraries/RenderJarCache.java +++ b/aswb/src/com/google/idea/blaze/android/libraries/RenderJarCache.java @@ -22,8 +22,6 @@ import com.google.idea.blaze.android.projectsystem.RenderJarClassFileFinder; import com.google.idea.blaze.android.sync.aspects.strategy.RenderResolveOutputGroupProvider; import com.google.idea.blaze.android.sync.importer.BlazeImportUtil; -import com.google.idea.blaze.base.command.buildresult.BlazeArtifact; -import com.google.idea.blaze.base.command.buildresult.OutputArtifact; import com.google.idea.blaze.base.filecache.FileCache; import com.google.idea.blaze.base.ideinfo.AndroidIdeInfo; import com.google.idea.blaze.base.ideinfo.ArtifactLocation; @@ -39,6 +37,8 @@ import com.google.idea.blaze.base.sync.data.BlazeDataStorage; import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder; +import com.google.idea.blaze.common.artifact.BlazeArtifact; +import com.google.idea.blaze.common.artifact.OutputArtifactWithoutDigest; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import java.io.File; @@ -124,7 +124,7 @@ private void onSync( boolean removeMissingFiles = syncMode == SyncMode.INCREMENTAL; - ImmutableList artifactsToCache = + ImmutableList artifactsToCache = getArtifactsToCache(projectViewSet, projectData); artifactCache.putAll(artifactsToCache, context, removeMissingFiles); @@ -139,7 +139,7 @@ private void refresh(BlazeContext context, BlazeBuildOutputs buildOutput) { return; } - ImmutableList renderJars = + ImmutableList renderJars = buildOutput .getOutputGroupArtifacts( RenderResolveOutputGroupProvider.RESOLVE_OUTPUT_GROUP::contains) @@ -152,7 +152,7 @@ private void refresh(BlazeContext context, BlazeBuildOutputs buildOutput) { artifactCache.putAll(renderJars, context, false); } - private ImmutableList getArtifactsToCache( + private ImmutableList getArtifactsToCache( ProjectViewSet projectViewSet, BlazeProjectData projectData) { List renderJars = BlazeImportUtil.getSourceTargetsStream(project, projectData, projectViewSet) @@ -163,10 +163,10 @@ private ImmutableList getArtifactsToCache( .collect(Collectors.toList()); ArtifactLocationDecoder decoder = projectData.getArtifactLocationDecoder(); - List blazeArtifacts = + List blazeArtifacts = decoder.resolveOutputs(renderJars).stream() - .filter(artifact -> artifact instanceof OutputArtifact) - .map(artifact -> (OutputArtifact) artifact) + .filter(artifact -> artifact instanceof OutputArtifactWithoutDigest) + .map(artifact -> (OutputArtifactWithoutDigest) artifact) .collect(Collectors.toList()); return ImmutableList.copyOf(blazeArtifacts); } @@ -191,12 +191,12 @@ public File getCachedJarForBinaryTarget( } BlazeArtifact jarArtifact = artifactLocationDecoder.resolveOutput(jarArtifactLocation); - if (!(jarArtifact instanceof OutputArtifact)) { + if (!(jarArtifact instanceof OutputArtifactWithoutDigest)) { Logger.getInstance(RenderJarCache.class) .warn("Unexpected render jar that is not an OutputArtifact: " + jarArtifactLocation); return null; } - Path jarPath = artifactCache.get((OutputArtifact) jarArtifact); + Path jarPath = artifactCache.get((OutputArtifactWithoutDigest) jarArtifact); return jarPath == null ? null : jarPath.toFile(); } diff --git a/aswb/src/com/google/idea/blaze/android/libraries/UnpackedAarUtils.java b/aswb/src/com/google/idea/blaze/android/libraries/UnpackedAarUtils.java index bcfa68fc0bb..60bb636aecd 100644 --- a/aswb/src/com/google/idea/blaze/android/libraries/UnpackedAarUtils.java +++ b/aswb/src/com/google/idea/blaze/android/libraries/UnpackedAarUtils.java @@ -19,20 +19,25 @@ import static com.android.SdkConstants.FN_LINT_JAR; import com.android.SdkConstants; -import com.google.idea.blaze.base.command.buildresult.BlazeArtifact; -import com.google.idea.blaze.base.command.buildresult.OutputArtifact; import com.google.idea.blaze.base.command.buildresult.SourceArtifact; +import com.google.idea.blaze.common.artifact.BlazeArtifact; +import com.google.idea.blaze.common.artifact.OutputArtifactWithoutDigest; import com.intellij.openapi.util.io.FileUtil; import com.intellij.util.PathUtil; import java.io.File; /** Utility methods to convert aar to other type that needed by {@link UnpackedAars} */ public final class UnpackedAarUtils { + /** + * The name of imported_aar' ijar. It will always be same for any aars due to aar's format (more + * details http://tools.android.com/tech-docs/new-build-system/aar-format). + */ + public static final String CLASS_JAR_FILE_NAME = "classes_and_libs_merged.jar"; /* Converts {@link BlazeArtifact} to the key which is used to create aar directory name */ public static String getArtifactKey(BlazeArtifact artifact) { - if (artifact instanceof OutputArtifact) { - return ((OutputArtifact) artifact).getKey(); + if (artifact instanceof OutputArtifactWithoutDigest) { + return ((OutputArtifactWithoutDigest) artifact).getRelativePath(); } if (artifact instanceof SourceArtifact) { return ((SourceArtifact) artifact).getFile().getPath(); @@ -48,12 +53,21 @@ public static String getAarDirName(BlazeArtifact aar) { + SdkConstants.DOT_AAR; } + /** + * Returns the source jar name according to {@link BlazeArtifact}. Differen with ijar that share + * same name accoss different aars, source jars can have different names. + */ + public static String getSrcJarName(BlazeArtifact srcJar) { + String key = getArtifactKey(srcJar); + String name = FileUtil.getNameWithoutExtension(PathUtil.getFileName(key)); + return name + SdkConstants.DOT_SRCJAR; + } + /** * Returns aar directory name in the format of _. This provides flexibility to * caller to generate aar directory name to deal with different cases. But this method cannot * guarantee the uniqueness. - -* + * * @param name the file name of aar file without extension. * @param hashCode the file name of aar is not guaranteed to be unique, so a hash code is used to * make sure the directory name is unique. Caller needs to make sure the hashcode provided is @@ -72,7 +86,7 @@ public static File getJarFile(File aarDir) { // At this point, we don't know the name of the original jar, but we must give the cache // file a name. Just use a name similar to what bazel currently uses, and that conveys // the origin of the jar (merged from classes.jar and libs/*.jar). - return new File(jarsDirectory, "classes_and_libs_merged.jar"); + return new File(jarsDirectory, CLASS_JAR_FILE_NAME); } /** diff --git a/aswb/src/com/google/idea/blaze/android/libraries/UnpackedAars.java b/aswb/src/com/google/idea/blaze/android/libraries/UnpackedAars.java index 5b348937989..0a5a0c491a8 100644 --- a/aswb/src/com/google/idea/blaze/android/libraries/UnpackedAars.java +++ b/aswb/src/com/google/idea/blaze/android/libraries/UnpackedAars.java @@ -15,19 +15,22 @@ */ package com.google.idea.blaze.android.libraries; +import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableMap.toImmutableMap; import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.idea.blaze.android.sync.model.AarLibrary; import com.google.idea.blaze.base.async.FutureUtil; -import com.google.idea.blaze.base.command.buildresult.BlazeArtifact; -import com.google.idea.blaze.base.command.buildresult.BlazeArtifact.LocalFileArtifact; +import com.google.idea.blaze.base.command.buildresult.LocalFileArtifact; import com.google.idea.blaze.base.command.buildresult.RemoteOutputArtifact; import com.google.idea.blaze.base.filecache.FileCache; import com.google.idea.blaze.base.filecache.FileCacheDiffer; +import com.google.idea.blaze.base.ideinfo.ArtifactLocation; +import com.google.idea.blaze.base.ideinfo.LibraryArtifact; import com.google.idea.blaze.base.model.BlazeLibrary; import com.google.idea.blaze.base.model.BlazeProjectData; import com.google.idea.blaze.base.model.RemoteOutputArtifacts; @@ -35,7 +38,6 @@ import com.google.idea.blaze.base.projectview.ProjectViewManager; import com.google.idea.blaze.base.projectview.ProjectViewSet; import com.google.idea.blaze.base.scope.BlazeContext; -import com.google.idea.blaze.base.scope.output.PrintOutput; import com.google.idea.blaze.base.scope.scopes.TimingScope.EventType; import com.google.idea.blaze.base.settings.BlazeImportSettings; import com.google.idea.blaze.base.settings.BlazeImportSettingsManager; @@ -45,7 +47,8 @@ import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; import com.google.idea.blaze.base.sync.libraries.BlazeLibraryCollector; import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder; -import com.intellij.openapi.components.ServiceManager; +import com.google.idea.blaze.common.PrintOutput; +import com.google.idea.blaze.common.artifact.BlazeArtifact; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import java.io.File; @@ -86,7 +89,7 @@ public class UnpackedAars { private final AarCache aarCache; public static UnpackedAars getInstance(Project project) { - return ServiceManager.getService(project, UnpackedAars.class); + return project.getService(UnpackedAars.class); } public UnpackedAars(Project project) { @@ -173,7 +176,8 @@ private void refresh( RemoteArtifactPrefetcher.getInstance() .downloadArtifacts( /* projectName= */ project.getName(), - /* outputArtifacts= */ BlazeArtifact.getRemoteArtifacts(artifactsToDownload)); + /* outputArtifacts= */ RemoteOutputArtifact.getRemoteArtifacts( + artifactsToDownload)); FutureUtil.waitForFuture(context, downloadArtifactsFuture) .timed("FetchAars", EventType.Prefetching) @@ -209,37 +213,59 @@ private void refresh( } } + private File logAndGetFallbackJar( + ArtifactLocationDecoder decoder, AarLibrary library, BlazeArtifact jar) { + if (aarCache.isEmpty()) { + logger.warn("Cache state is empty"); + } + + // if artifact is RemoteOutputArtifact, we can only find it in aar cache. So it's expected + // that the aar directory has been cached. It's unexpected when it runs into this case and + // cannot find any fallback file. + if (jar instanceof RemoteOutputArtifact) { + BlazeArtifact aar = decoder.resolveOutput(library.aarArtifact); + logger.warn( + String.format( + "Fail to look up from cache state for library [aarArtifact = %s, jar = %s]", + aar, jar)); + logger.debug("Cache state contains the following keys: " + aarCache.getCachedKeys()); + } + return getFallbackFile(jar); + } + /** Returns the merged jar derived from an AAR, in the unpacked AAR directory. */ @Nullable public File getClassJar(ArtifactLocationDecoder decoder, AarLibrary library) { if (library.libraryArtifact == null) { return null; } - BlazeArtifact jar = decoder.resolveOutput(library.libraryArtifact.jarForIntellijLibrary()); - if (aarCache.isEmpty()) { - logger.warn("Cache state is empty"); - return getFallbackFile(jar); - } - - BlazeArtifact aar = decoder.resolveOutput(library.aarArtifact); File aarDir = getAarDir(decoder, library); - // check if it was actually cached if (aarDir == null) { - // if artifact is RemoteOutputArtifact, we can only find it in aar cache. So it's expected - // that the aar directory has been cached. It's unexpected when it runs into this case and - // cannot find any fallback file. - if (jar instanceof RemoteOutputArtifact) { - logger.warn( - String.format( - "Fail to look up %s from cache state for library [aarArtifact = %s, jar = %s]", - aarDir, aar, jar)); - logger.debug("Cache state contains the following keys: " + aarCache.getCachedKeys()); - } - return getFallbackFile(jar); + BlazeArtifact jar = decoder.resolveOutput(library.libraryArtifact.jarForIntellijLibrary()); + return logAndGetFallbackJar(decoder, library, jar); } return UnpackedAarUtils.getJarFile(aarDir); } + /** Returns the src jars derived from an AAR, in the unpacked AAR directory. */ + public ImmutableList getCachedSrcJars(ArtifactLocationDecoder decoder, AarLibrary library) { + if (library.libraryArtifact == null) { + return ImmutableList.of(); + } + File aarDir = getAarDir(decoder, library); + return library.libraryArtifact.getSourceJars().stream() + .map( + artifactLocation -> { + BlazeArtifact srcJar = decoder.resolveOutput(artifactLocation); + if (aarDir == null) { + return logAndGetFallbackJar(decoder, library, srcJar); + } + String srcJarName = UnpackedAarUtils.getSrcJarName(srcJar); + return new File(aarDir, srcJarName); + }) + .collect(toImmutableList()); + } + /** Returns the res/ directory corresponding to an unpacked AAR file. */ @Nullable public File getResourceDirectory(ArtifactLocationDecoder decoder, AarLibrary library) { @@ -256,7 +282,12 @@ public File getLintRuleJar(ArtifactLocationDecoder decoder, AarLibrary library) @Nullable public File getAarDir(ArtifactLocationDecoder decoder, AarLibrary library) { - BlazeArtifact artifact = decoder.resolveOutput(library.aarArtifact); + return getAarDir(decoder, library.aarArtifact); + } + + @Nullable + public File getAarDir(ArtifactLocationDecoder decoder, ArtifactLocation aar) { + BlazeArtifact artifact = decoder.resolveOutput(aar); String aarDirName = UnpackedAarUtils.getAarDirName(artifact); return aarCache.getCachedAarDir(aarDirName); } @@ -270,7 +301,8 @@ private static File getFallbackFile(BlazeArtifact output) { return ((LocalFileArtifact) output).getFile(); } - static class FileCacheAdapter implements FileCache { + /** An implementation of {@link FileCache} delegating to {@link UnpackedAars}. */ + public static class FileCacheAdapter implements FileCache { @Override public String getName() { return "Unpacked AAR libraries"; @@ -330,11 +362,18 @@ private static ImmutableMap getArtifactsToCache( Map outputs = new HashMap<>(); for (AarLibrary library : aarLibraries) { BlazeArtifact aar = decoder.resolveOutput(library.aarArtifact); - BlazeArtifact jar = - library.libraryArtifact != null - ? decoder.resolveOutput(library.libraryArtifact.jarForIntellijLibrary()) - : null; - outputs.put(UnpackedAarUtils.getAarDirName(aar), AarLibraryContents.create(aar, jar)); + LibraryArtifact libraryArtifact = library.libraryArtifact; + BlazeArtifact jar = null; + ImmutableList srcJars = ImmutableList.of(); + if (libraryArtifact != null) { + jar = decoder.resolveOutput(libraryArtifact.jarForIntellijLibrary()); + srcJars = + libraryArtifact.getSourceJars().stream() + .map(decoder::resolveOutput) + .collect(toImmutableList()); + } + outputs.put( + UnpackedAarUtils.getAarDirName(aar), AarLibraryContents.create(aar, jar, srcJars)); } return ImmutableMap.copyOf(outputs); } diff --git a/aswb/src/com/google/idea/blaze/android/libraries/Unpacker.java b/aswb/src/com/google/idea/blaze/android/libraries/Unpacker.java index 2566bdc65ec..e29f972299e 100644 --- a/aswb/src/com/google/idea/blaze/android/libraries/Unpacker.java +++ b/aswb/src/com/google/idea/blaze/android/libraries/Unpacker.java @@ -19,12 +19,13 @@ import static com.android.SdkConstants.FN_LINT_JAR; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; -import com.google.idea.blaze.base.command.buildresult.BlazeArtifact; -import com.google.idea.blaze.base.command.buildresult.BlazeArtifact.LocalFileArtifact; +import com.google.idea.blaze.base.command.buildresult.LocalFileArtifact; import com.google.idea.blaze.base.io.FileOperationProvider; import com.google.idea.blaze.base.prefetch.FetchExecutor; +import com.google.idea.blaze.common.artifact.BlazeArtifact; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.io.FileUtil; import com.intellij.util.io.ZipUtil; @@ -61,6 +62,9 @@ */ public final class Unpacker { private static final Logger logger = Logger.getInstance(Unpacker.class); + // Jars that are expected to be extracted from .aar to local + private static final ImmutableSet EXPECTED_JARS = + ImmutableSet.of(FN_LINT_JAR, "inspector.jar"); /** Updated prefetched aars to aar directory. */ public static void unpack( @@ -88,21 +92,23 @@ private static void unpackAarsToDir( * with same name is found next time. */ private static void unpackAarToDir( - FileOperationProvider ops, AarLibraryContents aarAndJar, AarCache aarCache) { - String cacheKey = UnpackedAarUtils.getAarDirName(aarAndJar.aar()); + FileOperationProvider ops, AarLibraryContents aarLibraryContents, AarCache aarCache) { + String cacheKey = UnpackedAarUtils.getAarDirName(aarLibraryContents.aar()); try { File aarDir = aarCache.recreateAarDir(ops, cacheKey); // TODO(brendandouglas): decompress via ZipInputStream so we don't require a local file - File toCopy = getOrCreateLocalFile(aarAndJar.aar()); + File toCopy = getOrCreateLocalFile(aarLibraryContents.aar()); ZipUtil.extract( toCopy, aarDir, - // Skip jars except lint.jar. We will copy jar in AarLibraryContents instead. + // Skip jars except EXPECTED_JARS. We will copy jar in AarLibraryContents instead. // That could give us freedom in the future to use an ijar or header jar instead, - // which is more lightweight. But it's not applied to lint.jar - (dir, name) -> name.equals(FN_LINT_JAR) || !name.endsWith(".jar")); + // which is more lightweight. For EXPECTED_JARS, they are not collected JarLibrary, + // so that we are not able to copy them from AarLibraryContents. But we need them for + // some functions e.g. lint check, lay out inspection etc. So copy them directly. + (dir, name) -> EXPECTED_JARS.contains(name) || !name.endsWith(".jar")); - BlazeArtifact aar = aarAndJar.aar(); + BlazeArtifact aar = aarLibraryContents.aar(); try { aarCache.createTimeStampFile( @@ -113,18 +119,31 @@ private static void unpackAarToDir( } // copy merged jar - if (aarAndJar.jar() != null) { - try (InputStream stream = aarAndJar.jar().getInputStream()) { + if (aarLibraryContents.jar() != null) { + try (InputStream stream = aarLibraryContents.jar().getInputStream()) { Path destination = Paths.get(UnpackedAarUtils.getJarFile(aarDir).getPath()); ops.mkdirs(destination.getParent().toFile()); Files.copy(stream, destination, StandardCopyOption.REPLACE_EXISTING); + } catch (Exception e) { + logger.warn("Failed to copy class jar for " + aar, e); } } + // copy src jars + for (BlazeArtifact srcjar : aarLibraryContents.srcJars()) { + try (InputStream stream = srcjar.getInputStream()) { + Path destination = aarDir.toPath().resolve(UnpackedAarUtils.getSrcJarName(srcjar)); + ops.mkdirs(destination.getParent().toFile()); + Files.copy(stream, destination, StandardCopyOption.REPLACE_EXISTING); + } catch (Exception e) { + logger.warn("Failed to copy source jar for " + aar, e); + } + } } catch (IOException e) { logger.warn( String.format( - "Failed to extract AAR %s to %s", aarAndJar.aar(), aarCache.aarDirForKey(cacheKey)), + "Failed to extract AAR %s to %s", + aarLibraryContents.aar(), aarCache.aarDirForKey(cacheKey)), e); } } diff --git a/aswb/src/com/google/idea/blaze/android/manifest/ManifestParser.java b/aswb/src/com/google/idea/blaze/android/manifest/ManifestParser.java index fc2e79927ef..349b16fd2cd 100644 --- a/aswb/src/com/google/idea/blaze/android/manifest/ManifestParser.java +++ b/aswb/src/com/google/idea/blaze/android/manifest/ManifestParser.java @@ -26,7 +26,6 @@ import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.idea.blaze.android.run.DefaultActivityLocatorCompat; -import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import java.io.IOException; @@ -46,7 +45,7 @@ public class ManifestParser { private static final Logger log = Logger.getInstance(ManifestParser.class); public static ManifestParser getInstance(Project project) { - return ServiceManager.getService(project, ManifestParser.class); + return project.getService(ManifestParser.class); } /** Container class for common manifest attributes required by the blaze plugin. */ diff --git a/aswb/src/com/google/idea/blaze/android/manifest/ParsedManifestService.java b/aswb/src/com/google/idea/blaze/android/manifest/ParsedManifestService.java index 11bd9ee5a98..8c18f839895 100644 --- a/aswb/src/com/google/idea/blaze/android/manifest/ParsedManifestService.java +++ b/aswb/src/com/google/idea/blaze/android/manifest/ParsedManifestService.java @@ -26,7 +26,6 @@ import com.google.idea.blaze.base.sync.SyncListener; import com.google.idea.blaze.base.sync.SyncMode; import com.google.idea.blaze.base.sync.SyncResult; -import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.project.Project; import java.io.File; import java.io.FileInputStream; @@ -41,20 +40,22 @@ public class ParsedManifestService { Maps.newHashMap(); public static ParsedManifestService getInstance(Project project) { - return ServiceManager.getService(project, ParsedManifestService.class); + return project.getService(ParsedManifestService.class); } /** * Returns parsed manifest from the given manifest file. Returns null if the manifest is invalid. * *

An invalid manifest is anything that could not be parsed by the parser, such as a malformed - * manifest file. + * manifest file. This method must be thread safe as it's invoked from concurrent background + * threads. * * @throws IOException only when an IO error occurs. Errors related to malformed manifests are * indicated by returning null. */ @Nullable - public ManifestParser.ParsedManifest getParsedManifest(File file) throws IOException { + public synchronized ManifestParser.ParsedManifest getParsedManifest(File file) + throws IOException { if (!manifestFileToParsedManifests.containsKey(file)) { try (InputStream inputStream = new FileInputStream(file)) { ManifestParser.ParsedManifest parsedManifest = parseManifestFromInputStream(inputStream); diff --git a/aswb/src/com/google/idea/blaze/android/npw/project/BlazeAndroidModuleTemplate.java b/aswb/src/com/google/idea/blaze/android/npw/project/BlazeAndroidModuleTemplate.java index 3dced7860b2..8d3b76b90e3 100644 --- a/aswb/src/com/google/idea/blaze/android/npw/project/BlazeAndroidModuleTemplate.java +++ b/aswb/src/com/google/idea/blaze/android/npw/project/BlazeAndroidModuleTemplate.java @@ -18,6 +18,7 @@ import com.android.tools.idea.projectsystem.AndroidModulePaths; import com.android.tools.idea.projectsystem.IdeaSourceProvider; import com.android.tools.idea.projectsystem.NamedModuleTemplate; +import com.android.tools.idea.projectsystem.SourceProviderManager; import com.google.common.collect.Streams; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; @@ -34,7 +35,6 @@ import java.util.stream.Collectors; import javax.annotation.Nullable; import org.jetbrains.android.facet.AndroidFacet; -import org.jetbrains.android.facet.SourceProviderManager; /** * Project paths for a Blaze Android project. @@ -119,7 +119,6 @@ public File getAidlDirectory(@Nullable String packageName) { public File getManifestDirectory() { return srcDirectory; } - /** * The new component wizard uses {@link NamedModuleTemplate#getName()} for the default package * name of the new component. If we can figure it out from the target directory here, then we can @@ -148,17 +147,14 @@ public static List getTemplates( public static List getTemplates( AndroidFacet androidFacet, @Nullable VirtualFile targetDirectory) { - Module module = androidFacet.getModule(); BlazeAndroidModuleTemplate paths = new BlazeAndroidModuleTemplate(); VirtualFile[] roots = ModuleRootManager.getInstance(module).getContentRoots(); if (roots.length > 0) { paths.moduleRoot = VfsUtilCore.virtualToIoFile(roots[0]); } - IdeaSourceProvider sourceProvider = SourceProviderManager.getInstance(androidFacet).getSources(); - // 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). diff --git a/aswb/src/com/google/idea/blaze/android/projectsystem/BlazeModuleSystemBase.java b/aswb/src/com/google/idea/blaze/android/projectsystem/BlazeModuleSystemBase.java index 7d3a8f81371..78839a5a26a 100644 --- a/aswb/src/com/google/idea/blaze/android/projectsystem/BlazeModuleSystemBase.java +++ b/aswb/src/com/google/idea/blaze/android/projectsystem/BlazeModuleSystemBase.java @@ -15,9 +15,18 @@ */ package com.google.idea.blaze.android.projectsystem; +import static com.android.SdkConstants.DOT_AAR; +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.ImmutableSet.toImmutableSet; +import static com.google.idea.blaze.base.qsync.DependencyTracker.DependencyBuildRequest.singleTarget; +import static java.util.Arrays.stream; + import com.android.ide.common.repository.GradleCoordinate; import com.android.ide.common.util.PathString; import com.android.manifmerger.ManifestSystemProperty; +import com.android.projectmodel.ExternalAndroidLibrary; +import com.android.projectmodel.ExternalLibraryImpl; +import com.android.projectmodel.SelectiveResourceFolder; import com.android.tools.idea.projectsystem.AndroidModuleSystem; import com.android.tools.idea.projectsystem.CapabilityNotSupported; import com.android.tools.idea.projectsystem.CapabilityStatus; @@ -34,24 +43,49 @@ 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.idea.blaze.android.compose.ComposeStatusProvider; +import com.google.idea.blaze.android.libraries.UnpackedAars; import com.google.idea.blaze.android.npw.project.BlazeAndroidModuleTemplate; +import com.google.idea.blaze.android.sync.model.AarLibrary; import com.google.idea.blaze.android.sync.model.AndroidResourceModule; import com.google.idea.blaze.android.sync.model.AndroidResourceModuleRegistry; +import com.google.idea.blaze.android.sync.model.BlazeAndroidSyncData; +import com.google.idea.blaze.android.sync.qsync.AndroidExternalLibraryManager; import com.google.idea.blaze.base.command.buildresult.OutputArtifactResolver; +import com.google.idea.blaze.base.ideinfo.AndroidAarIdeInfo; import com.google.idea.blaze.base.ideinfo.ArtifactLocation; import com.google.idea.blaze.base.ideinfo.Dependency; import com.google.idea.blaze.base.ideinfo.TargetIdeInfo; import com.google.idea.blaze.base.ideinfo.TargetKey; import com.google.idea.blaze.base.io.VfsUtils; import com.google.idea.blaze.base.lang.buildfile.references.BuildReferenceManager; +import com.google.idea.blaze.base.model.BlazeLibrary; 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.qsync.DependencyTracker; +import com.google.idea.blaze.base.qsync.QuerySync; +import com.google.idea.blaze.base.qsync.QuerySyncManager; +import com.google.idea.blaze.base.qsync.QuerySyncProject; +import com.google.idea.blaze.base.scope.BlazeContext; import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.settings.BlazeImportSettings.ProjectType; +import com.google.idea.blaze.base.settings.BlazeImportSettingsManager; +import com.google.idea.blaze.base.sync.SyncCache; import com.google.idea.blaze.base.sync.data.BlazeDataStorage; import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; +import com.google.idea.blaze.base.sync.libraries.BlazeLibraryCollector; +import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder; import com.google.idea.blaze.base.targetmaps.ReverseDependencyMap; import com.google.idea.blaze.base.targetmaps.TransitiveDependencyMap; +import com.google.idea.blaze.common.Label; +import com.google.idea.blaze.qsync.BlazeProject; +import com.google.idea.blaze.qsync.BlazeProjectSnapshot; +import com.google.idea.blaze.qsync.deps.ArtifactTracker; +import com.google.idea.blaze.qsync.project.ProjectPath; +import com.google.idea.blaze.qsync.project.ProjectProto; import com.google.idea.common.experiments.BoolExperiment; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; @@ -59,7 +93,6 @@ import com.intellij.openapi.fileEditor.OpenFileDescriptor; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; -import com.intellij.openapi.module.ModuleServiceManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; @@ -67,13 +100,15 @@ import com.intellij.psi.search.ProjectScope; import java.io.File; import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; import java.nio.file.Path; -import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; +import java.util.Optional; import java.util.stream.Stream; import kotlin.Triple; import org.jetbrains.annotations.Nullable; @@ -94,16 +129,50 @@ abstract class BlazeModuleSystemBase implements AndroidModuleSystem { protected static final Logger logger = Logger.getInstance(BlazeModuleSystem.class); protected Module module; protected final Project project; + private final ProjectPath.Resolver pathResolver; SampleDataDirectoryProvider sampleDataDirectoryProvider; RenderJarClassFileFinder classFileFinder; final boolean isWorkspaceModule; + private AndroidExternalLibraryManager androidExternalLibraryManager = null; BlazeModuleSystemBase(Module module) { this.module = module; this.project = module.getProject(); + this.pathResolver = + ProjectPath.Resolver.create( + WorkspaceRoot.fromProject(project).path(), + Path.of( + BlazeImportSettingsManager.getInstance(project) + .getImportSettings() + .getProjectDataDirectory())); classFileFinder = new RenderJarClassFileFinder(module); sampleDataDirectoryProvider = new BlazeSampleDataDirectoryProvider(module); - isWorkspaceModule = BlazeDataStorage.WORKSPACE_MODULE_NAME.equals(module.getName()); + isWorkspaceModule = module.getName().equals(BlazeDataStorage.WORKSPACE_MODULE_NAME); + if (Blaze.getProjectType(project) == ProjectType.QUERY_SYNC + && !QuerySync.USE_NEW_BUILD_ARTIFACT_MANAGEMENT) { + androidExternalLibraryManager = + new AndroidExternalLibraryManager( + () -> { + if (!QuerySyncManager.getInstance(project).isProjectLoaded()) { + return ImmutableList.of(); + } + ArtifactTracker artifactTracker = + QuerySyncManager.getInstance(project).getArtifactTracker(); + Path aarDirectory = artifactTracker.getExternalAarDirectory(); + // This can be called by the IDE as the user navigates the project and so might be + // called before a sync has been completed and the project structure has been set + // up. + if (!aarDirectory.toFile().exists()) { + logger.warn("Aar library directory not created yet"); + return ImmutableList.of(); + } + try (Stream stream = Files.list(aarDirectory)) { + return stream.collect(toImmutableList()); + } catch (IOException ioe) { + throw new UncheckedIOException("Could not list aars", ioe); + } + }); + } } @Override @@ -111,12 +180,12 @@ public Module getModule() { return module; } - // @Override #as42: Method added in AS 203 + @Override public ClassFileFinder getModuleClassFileFinder() { return classFileFinder; } - // @Override #as42: Method added in AS 203 + @Override public ClassFileFinder getClassFileFinderForSourceFile(VirtualFile sourceFile) { return classFileFinder; } @@ -223,16 +292,32 @@ public GradleCoordinate getRegisteredDependency(GradleCoordinate coordinate) { return null; } - Set firstLevelDeps = + ImmutableSet firstLevelDeps = resourceModuleTarget.getDependencies().stream() .map(Dependency::getTargetKey) - .collect(Collectors.toSet()); + .collect(toImmutableSet()); return locateArtifactsFor(coordinate).anyMatch(firstLevelDeps::contains) ? coordinate : null; } + @Nullable + private Label getResolvedLabel(GradleCoordinate coordinate) { + return MavenArtifactLocator.forBuildSystem(Blaze.getBuildSystemName(module.getProject())) + .stream() + .map(locator -> locator.labelFor(coordinate)) + .map(l -> Label.of(l.toString())) + .findFirst() + .orElse(null); + } + @Nullable private TargetKey getResolvedTarget(GradleCoordinate coordinate) { + if (Blaze.getProjectType(project) == ProjectType.QUERY_SYNC) { + // TODO (b/262289199): While there is a way of mapping a gradle coordinate to a target, + // that is a very tricky practice that while it could be supported with Query Sync, we + // should try to avoid it. + return null; + } BlazeProjectData projectData = BlazeProjectDataManager.getInstance(module.getProject()).getBlazeProjectData(); @@ -270,7 +355,7 @@ public GradleCoordinate getResolvedDependency(GradleCoordinate coordinate) { return getResolvedDependency(coordinate, DependencyScopeType.MAIN); } - // @Override #api42 : Method added in api203 + @Override @Nullable public GradleCoordinate getResolvedDependency( GradleCoordinate gradleCoordinate, DependencyScopeType dependencyScopeType) @@ -279,22 +364,82 @@ public GradleCoordinate getResolvedDependency( return target != null ? gradleCoordinate : null; } + /** + * Returns first cached aar that build by the label provided. This is used by qsync. Returns null + * if failed to find such aar locally. + * + * @param label the label that aar build from + * @param buildDeps whether run blaze build to build aar before get it. + */ + private Optional getCachedAarForLabel(Label label, boolean buildDeps) { + DependencyTracker dependencyTracker = + QuerySyncManager.getInstance(project).getDependencyTracker(); + if (dependencyTracker == null) { + return Optional.empty(); + } + + if (buildDeps) { + BlazeContext tmpContext = BlazeContext.create(); + try { + boolean unused = + dependencyTracker.buildDependenciesForTargets(tmpContext, singleTarget(label)); + } catch (Exception e) { + tmpContext.handleException("Failed to build dependencies", e); + } + } + + Optional> paths = dependencyTracker.getCachedArtifacts(label); + if (paths.isEmpty()) { + return Optional.empty(); + } + return paths.get().stream().filter(path -> path.toString().endsWith(DOT_AAR)).findFirst(); + } + /** * Returns the absolute path of the dependency, if it exists. * * @param coordinate external coordinates for the dependency. * @return the absolute path of the dependency including workspace root and path. */ + // @Override removed in 232 @Nullable - // @Override #api42 public Path getDependencyPath(GradleCoordinate coordinate) { + if (Blaze.getProjectType(project) == ProjectType.QUERY_SYNC) { + Label label = getResolvedLabel(coordinate); + if (label == null) { + return null; + } + Optional result = getCachedAarForLabel(label, false); + if (result.isPresent()) { + return result.get(); + } + // Failed to get cache aar without building the target. It means the target may not be built + // correctly (either not build or its artifact not cached correctly), we need to + // re-build it again as the target cannot not be build except here. This is required for + // compose layout inspector. + return getCachedAarForLabel(label, true).orElse(null); + } TargetKey target = getResolvedTarget(coordinate); - if (target != null) { - return WorkspaceRoot.fromProject(project) - .fileForPath(target.getLabel().blazePackage()) - .toPath(); + if (target == null) { + return null; + } + BlazeProjectData projectData = + BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); + Path defaultPath = + WorkspaceRoot.fromProject(project).fileForPath(target.getLabel().blazePackage()).toPath(); + if (projectData == null) { + return defaultPath; + } + + AndroidAarIdeInfo aarIdeInfo = projectData.getTargetMap().get(target).getAndroidAarIdeInfo(); + // Returns its local aar directory path (if exists) instead of google3 one for imported aars. + if (aarIdeInfo == null) { + return defaultPath; } - return null; + File aarDir = + UnpackedAars.getInstance(module.getProject()) + .getAarDir(projectData.getArtifactLocationDecoder(), aarIdeInfo.getAar()); + return aarDir == null || !aarDir.exists() ? defaultPath : aarDir.toPath(); } private Stream locateArtifactsFor(GradleCoordinate coordinate) { @@ -317,14 +462,17 @@ private Stream locateArtifactsFor(GradleCoordinate coordinate) { */ @Override public List getResourceModuleDependencies() { + if (Blaze.getProjectType(project) == ProjectType.QUERY_SYNC) { + return ImmutableList.of(); + } AndroidResourceModuleRegistry resourceModuleRegistry = AndroidResourceModuleRegistry.getInstance(project); if (isWorkspaceModule) { // The workspace module depends on every resource module. - return Arrays.stream(ModuleManager.getInstance(project).getModules()) + return stream(ModuleManager.getInstance(project).getModules()) .filter(module -> resourceModuleRegistry.get(module) != null) - .collect(Collectors.toList()); + .collect(toImmutableList()); } AndroidResourceModule resourceModule = resourceModuleRegistry.get(module); if (resourceModule == null) { @@ -334,7 +482,7 @@ public List getResourceModuleDependencies() { return resourceModule.transitiveResourceDependencies.stream() .map(resourceModuleRegistry::getModuleContainingResourcesOf) .filter(Objects::nonNull) - .collect(Collectors.toList()); + .collect(toImmutableList()); } @Override @@ -374,7 +522,7 @@ public List getDirectResourceModuleDependents() { .map(TargetIdeInfo::getKey) .map(resourceModuleRegistry::getModuleContainingResourcesOf) .filter(Objects::nonNull) - .collect(Collectors.toList()); + .collect(toImmutableList()); } @Override @@ -411,7 +559,7 @@ public ManifestOverrides getManifestOverrides() { manifestValues.forEach( (key, value) -> ManifestValueProcessor.processManifestValue(key, value, directOverrides, placeholders)); - return new ManifestOverrides(directOverrides.build(), placeholders.build()); + return new ManifestOverrides(directOverrides.buildOrThrow(), placeholders.buildOrThrow()); } @Override @@ -431,6 +579,151 @@ public boolean getUsesCompose() { return ComposeStatusProvider.isComposeEnabled(project); } + public Collection getDependentLibraries() { + if (Blaze.getProjectType(project) == ProjectType.QUERY_SYNC) { + if (QuerySync.USE_NEW_BUILD_ARTIFACT_MANAGEMENT) { + ProjectProto.Project projectProto = + QuerySyncManager.getInstance(project) + .getLoadedProject() + .map(QuerySyncProject::getSnapshotHolder) + .flatMap(BlazeProject::getCurrent) + .map(BlazeProjectSnapshot::project) + .orElse(null); + if (projectProto == null) { + return ImmutableList.of(); + } + ImmutableList matchingModules = + projectProto.getModulesList().stream() + .filter(m -> m.getName().equals(module.getName())) + .collect(ImmutableList.toImmutableList()); + if (matchingModules.isEmpty()) { + return ImmutableList.of(); + } + return Iterables.getOnlyElement(matchingModules).getAndroidExternalLibrariesList().stream() + .map(this::fromProto) + .collect(toImmutableList()); + } else { + return androidExternalLibraryManager.getExternalLibraries(); + } + } + BlazeProjectData blazeProjectData = + BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); + + if (blazeProjectData == null) { + return ImmutableList.of(); + } + + if (isWorkspaceModule) { + return SyncCache.getInstance(project) + .get(BlazeModuleSystem.class, BlazeModuleSystemBase::getLibrariesForWorkspaceModule); + } + + AndroidResourceModuleRegistry registry = AndroidResourceModuleRegistry.getInstance(project); + TargetIdeInfo target = blazeProjectData.getTargetMap().get(registry.getTargetKey(module)); + if (target == null) { + // this can happen if the module points to the , + // does not contain any resource + // contains all external resources as module's local resources, so there's + // no dependent libraries + return ImmutableList.of(); + } + + BlazeAndroidSyncData androidSyncData = + blazeProjectData.getSyncState().get(BlazeAndroidSyncData.class); + if (androidSyncData == null) { + return ImmutableList.of(); + } + + ImmutableList.Builder libraries = ImmutableList.builder(); + ArtifactLocationDecoder decoder = blazeProjectData.getArtifactLocationDecoder(); + ExternalLibraryInterner externalLibraryInterner = ExternalLibraryInterner.getInstance(project); + for (String libraryKey : registry.get(module).resourceLibraryKeys) { + ImmutableMap aarLibraries = androidSyncData.importResult.aarLibraries; + ExternalAndroidLibrary externalLibrary = + toExternalLibrary(project, aarLibraries.get(libraryKey), decoder); + if (externalLibrary != null) { + libraries.add(externalLibraryInterner.intern(externalLibrary)); + } + } + return libraries.build(); + } + + private ExternalAndroidLibrary fromProto(ProjectProto.ExternalAndroidLibrary proto) { + return new ExternalLibraryImpl(proto.getName()) + .withLocation(toPathString(proto.getLocation())) + .withManifestFile(toPathString(proto.getManifestFile())) + .withResFolder(new SelectiveResourceFolder(toPathString(proto.getResFolder()), null)) + .withSymbolFile(toPathString(proto.getSymbolFile())) + .withPackageName(proto.getPackageName()); + } + + private PathString toPathString(ProjectProto.ProjectPath projectPath) { + return new PathString(pathResolver.resolve(ProjectPath.create(projectPath))); + } + + private static ImmutableList getLibrariesForWorkspaceModule( + Project project, BlazeProjectData blazeProjectData) { + ArtifactLocationDecoder decoder = blazeProjectData.getArtifactLocationDecoder(); + ExternalLibraryInterner externalLibraryInterner = ExternalLibraryInterner.getInstance(project); + ImmutableList.Builder libraries = ImmutableList.builder(); + for (BlazeLibrary library : + BlazeLibraryCollector.getLibraries( + ProjectViewManager.getInstance(project).getProjectViewSet(), blazeProjectData)) { + if (library instanceof AarLibrary) { + ExternalAndroidLibrary externalLibrary = + toExternalLibrary(project, (AarLibrary) library, decoder); + if (externalLibrary != null) { + libraries.add(externalLibraryInterner.intern(externalLibrary)); + } + } + } + return libraries.build(); + } + + @Nullable + static ExternalAndroidLibrary toExternalLibrary( + Project project, @Nullable AarLibrary library, ArtifactLocationDecoder decoder) { + if (library == null) { + return null; + } + UnpackedAars unpackedAars = UnpackedAars.getInstance(project); + File aarFile = unpackedAars.getAarDir(decoder, library); + if (aarFile == null) { + logger.warn( + String.format( + "Fail to locate AAR file %s. Re-sync the project may solve the problem", + library.aarArtifact)); + return null; + } + File resFolder = unpackedAars.getResourceDirectory(decoder, library); + PathString resFolderPathString = resFolder == null ? null : new PathString(resFolder); + return new ExternalLibraryImpl(library.key.toString()) + .withLocation(new PathString(aarFile)) + .withManifestFile( + resFolderPathString == null + ? null + : resFolderPathString.getParentOrRoot().resolve("AndroidManifest.xml")) + .withResFolder( + resFolderPathString == null + ? null + : new SelectiveResourceFolder(resFolderPathString, null)) + .withSymbolFile( + resFolderPathString == null + ? null + : resFolderPathString.getParentOrRoot().resolve("R.txt")) + .withPackageName(library.resourcePackage); + } + + @Override + public Collection getAndroidLibraryDependencies( + DependencyScopeType dependencyScopeType) { + if (dependencyScopeType == DependencyScopeType.MAIN) { + return getDependentLibraries(); + } else { + return Collections.emptyList(); + } + } + @TestOnly public static BlazeModuleSystem create(Module module) { Preconditions.checkState(ApplicationManager.getApplication().isUnitTestMode()); @@ -438,6 +731,6 @@ public static BlazeModuleSystem create(Module module) { } public static BlazeModuleSystem getInstance(Module module) { - return ModuleServiceManager.getService(module, BlazeModuleSystem.class); + return module.getService(BlazeModuleSystem.class); } } diff --git a/aswb/src/com/google/idea/blaze/android/projectsystem/BlazeProjectSystemBuildManager.java b/aswb/src/com/google/idea/blaze/android/projectsystem/BlazeProjectSystemBuildManager.java index b2b3924c202..97711f1f884 100644 --- a/aswb/src/com/google/idea/blaze/android/projectsystem/BlazeProjectSystemBuildManager.java +++ b/aswb/src/com/google/idea/blaze/android/projectsystem/BlazeProjectSystemBuildManager.java @@ -15,16 +15,20 @@ */ package com.google.idea.blaze.android.projectsystem; +import static java.lang.Math.max; + +import com.android.annotations.concurrency.UiThread; import com.android.tools.idea.projectsystem.ProjectSystemBuildManager; import com.google.idea.blaze.base.build.BlazeBuildListener; import com.google.idea.blaze.base.build.BlazeBuildService; import com.google.idea.blaze.base.sync.aspects.BlazeBuildOutputs; import com.intellij.openapi.Disposable; -import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.components.Service; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.messages.Topic; import java.util.Collection; +import java.util.concurrent.atomic.AtomicInteger; import org.jetbrains.annotations.NotNull; /** Blaze implementation of {@link ProjectSystemBuildManager} */ @@ -43,7 +47,7 @@ public void compileProject() { BlazeBuildService.getInstance(project).buildProject(); } - // @Override #api203 + @Override public void compileFilesAndDependencies(Collection files) { // TODO(b/191937319): Implement incremental builds for individual files // Just compile the entire project for now. @@ -65,6 +69,35 @@ public BuildResult getLastBuildResult() { return LastBuildResultCache.getInstance(project).getLastBuildResult(); } + @UiThread + // @Override #api221 + public boolean isBuilding() { + return BlazeBuildCounter.getInstance(project).isBuilding(); + } + + @Service + static final class BlazeBuildCounter { + private final AtomicInteger buildCount = new AtomicInteger(0); + + public BlazeBuildCounter(Project project) {} + + public void onBuildStarted() { + buildCount.incrementAndGet(); + } + + public void onBuildCompleted() { + buildCount.updateAndGet(i -> max(i - 1, 0)); + } + + public boolean isBuilding() { + return buildCount.get() > 0; + } + + public static BlazeBuildCounter getInstance(Project project) { + return project.getService(BlazeBuildCounter.class); + } + } + /** * Class to publish BlazeBuildListener callbacks to {@link * BlazeProjectSystemBuildManager#PROJECT_SYSTEM_BUILD_TOPIC} @@ -72,6 +105,7 @@ public BuildResult getLastBuildResult() { static final class BuildCallbackPublisher implements BlazeBuildListener { @Override public void buildStarting(Project project) { + BlazeBuildCounter.getInstance(project).onBuildStarted(); project .getMessageBus() .syncPublisher(PROJECT_SYSTEM_BUILD_TOPIC) @@ -90,7 +124,7 @@ public void buildCompleted(Project project, BlazeBuildOutputs buildOutputs) { .getMessageBus() .syncPublisher(PROJECT_SYSTEM_BUILD_TOPIC) .beforeBuildCompleted(projectSystemBuildResult); - + BlazeBuildCounter.getInstance(project).onBuildCompleted(); project .getMessageBus() .syncPublisher(PROJECT_SYSTEM_BUILD_TOPIC) @@ -101,7 +135,7 @@ public void buildCompleted(Project project, BlazeBuildOutputs buildOutputs) { /** Caches the Build result from the most recent build */ static final class LastBuildResultCache { private static LastBuildResultCache getInstance(Project project) { - return ServiceManager.getService(project, LastBuildResultCache.class); + return project.getService(LastBuildResultCache.class); } private BuildResult lastBuildResult = BuildResult.createUnknownBuildResult(); diff --git a/aswb/src/com/google/idea/blaze/android/projectsystem/BlazeProjectSystemSyncManager.java b/aswb/src/com/google/idea/blaze/android/projectsystem/BlazeProjectSystemSyncManager.java index aa736ba7a9d..6d48a423a36 100644 --- a/aswb/src/com/google/idea/blaze/android/projectsystem/BlazeProjectSystemSyncManager.java +++ b/aswb/src/com/google/idea/blaze/android/projectsystem/BlazeProjectSystemSyncManager.java @@ -29,7 +29,6 @@ import com.google.idea.blaze.base.sync.SyncListener; import com.google.idea.blaze.base.sync.SyncMode; import com.google.idea.blaze.base.sync.status.BlazeSyncStatus; -import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Disposer; import com.intellij.util.messages.MessageBusConnection; @@ -147,6 +146,25 @@ public void afterSync( .syncPublisher(PROJECT_SYSTEM_SYNC_TOPIC) .syncEnded(lastSyncResultCache.lastSyncResult); } + + /** Called after sync. Only used in new query-sync * */ + @Override + public void afterQuerySync(Project project, BlazeContext context) { + LastSyncResultCache lastSyncResultCache = LastSyncResultCache.getInstance(project); + + if (context.isCancelled()) { + lastSyncResultCache.lastSyncResult = SyncResult.CANCELLED; + } else if (context.hasErrors()) { + lastSyncResultCache.lastSyncResult = SyncResult.FAILURE; + } else { + lastSyncResultCache.lastSyncResult = SyncResult.SUCCESS; + } + + project + .getMessageBus() + .syncPublisher(PROJECT_SYSTEM_SYNC_TOPIC) + .syncEnded(lastSyncResultCache.lastSyncResult); + } } @VisibleForTesting @@ -154,7 +172,7 @@ static class LastSyncResultCache { SyncResult lastSyncResult = SyncResult.UNKNOWN; public static LastSyncResultCache getInstance(Project project) { - return ServiceManager.getService(project, LastSyncResultCache.class); + return project.getService(LastSyncResultCache.class); } /** diff --git a/aswb/src/com/google/idea/blaze/android/projectsystem/DesugaringLibraryConfigFilesLocator.java b/aswb/src/com/google/idea/blaze/android/projectsystem/DesugaringLibraryConfigFilesLocator.java new file mode 100644 index 00000000000..457ad795ea3 --- /dev/null +++ b/aswb/src/com/google/idea/blaze/android/projectsystem/DesugaringLibraryConfigFilesLocator.java @@ -0,0 +1,58 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.projectsystem; + +import static com.google.common.collect.ImmutableList.toImmutableList; + +import com.google.common.collect.ImmutableList; +import com.google.idea.blaze.base.settings.BuildSystemName; +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.openapi.project.Project; +import java.nio.file.Path; +import java.util.Arrays; + +/** + * Build systems can implement their own {@link DesugaringLibraryConfigFilesLocator} to help the IDE + * to locate desugaring config files. Note that there can be multiple locators enabled at the same + * time; see {@link DesugaringLibraryConfigFilesLocator#forBuildSystem(BuildSystemName)} on how to + * obtain them for a given build system. + */ +public interface DesugaringLibraryConfigFilesLocator { + ExtensionPointName EP_NAME = + ExtensionPointName.create("com.google.idea.blaze.DesugaringLibraryConfigFilesLocator"); + + /** Returns true if desugaring library config files exist. */ + public boolean getDesugarLibraryConfigFilesKnown(); + + /** Returns the list of paths to the desugaring library config files */ + ImmutableList getDesugarLibraryConfigFiles(Project project); + + /** + * Returns the {@link BuildSystemName} this {@link DesugaringLibraryConfigFilesLocator} supports. + */ + BuildSystemName buildSystem(); + + /** + * Returns an {@link ImmutableList} of {@link DesugaringLibraryConfigFilesLocator} that supports + * the given build system. + */ + static ImmutableList forBuildSystem( + BuildSystemName buildSystemName) { + return Arrays.stream(EP_NAME.getExtensions()) + .filter(provider -> provider.buildSystem().equals(buildSystemName)) + .collect(toImmutableList()); + } +} diff --git a/aswb/sdkcompat/as212/com/google/idea/blaze/android/projectsystem/ExternalLibraryInterner.java b/aswb/src/com/google/idea/blaze/android/projectsystem/ExternalLibraryInterner.java similarity index 90% rename from aswb/sdkcompat/as212/com/google/idea/blaze/android/projectsystem/ExternalLibraryInterner.java rename to aswb/src/com/google/idea/blaze/android/projectsystem/ExternalLibraryInterner.java index 428579821e8..1d965ea833b 100644 --- a/aswb/sdkcompat/as212/com/google/idea/blaze/android/projectsystem/ExternalLibraryInterner.java +++ b/aswb/src/com/google/idea/blaze/android/projectsystem/ExternalLibraryInterner.java @@ -18,7 +18,6 @@ import com.android.projectmodel.ExternalAndroidLibrary; import com.google.common.collect.Interner; import com.google.common.collect.Interners; -import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.project.Project; /** Project Level service to dedup instances of {@link ExternalAndroidLibrary}. */ @@ -26,7 +25,7 @@ public class ExternalLibraryInterner { private Interner externalLibraryInterner = Interners.newWeakInterner(); public static ExternalLibraryInterner getInstance(Project project) { - return ServiceManager.getService(project, ExternalLibraryInterner.class); + return project.getService(ExternalLibraryInterner.class); } public ExternalAndroidLibrary intern(ExternalAndroidLibrary externalLibrary) { diff --git a/aswb/sdkcompat/as213/com/google/idea/blaze/android/projectsystem/ManifestValueProcessor.java b/aswb/src/com/google/idea/blaze/android/projectsystem/ManifestValueProcessor.java similarity index 100% rename from aswb/sdkcompat/as213/com/google/idea/blaze/android/projectsystem/ManifestValueProcessor.java rename to aswb/src/com/google/idea/blaze/android/projectsystem/ManifestValueProcessor.java diff --git a/aswb/src/com/google/idea/blaze/android/projectsystem/PackageNameUtils.java b/aswb/src/com/google/idea/blaze/android/projectsystem/PackageNameUtils.java index 27a60cbf114..83a7fcd9b20 100644 --- a/aswb/src/com/google/idea/blaze/android/projectsystem/PackageNameUtils.java +++ b/aswb/src/com/google/idea/blaze/android/projectsystem/PackageNameUtils.java @@ -21,6 +21,7 @@ import com.android.tools.idea.model.AndroidManifestRawText; import com.android.tools.idea.model.MergedManifestModificationTracker; import com.android.tools.idea.projectsystem.ManifestOverrides; +import com.android.tools.idea.projectsystem.SourceProviderManager; import com.google.common.annotations.VisibleForTesting; import com.google.idea.common.experiments.BoolExperiment; import com.intellij.openapi.module.Module; @@ -34,7 +35,6 @@ import org.jetbrains.android.dom.manifest.AndroidManifestUtils; import org.jetbrains.android.dom.manifest.AndroidManifestXmlFile; import org.jetbrains.android.facet.AndroidFacet; -import org.jetbrains.android.facet.SourceProviderManager; import org.jetbrains.annotations.Nullable; /** Utilities to obtain the package name for a given module. */ diff --git a/aswb/src/com/google/idea/blaze/android/qsync/AndroidProjectProtoTransform.java b/aswb/src/com/google/idea/blaze/android/qsync/AndroidProjectProtoTransform.java new file mode 100644 index 00000000000..8521b8bdb3c --- /dev/null +++ b/aswb/src/com/google/idea/blaze/android/qsync/AndroidProjectProtoTransform.java @@ -0,0 +1,69 @@ +/* + * Copyright 2024 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.qsync; + +import com.google.common.collect.ImmutableList; +import com.google.idea.blaze.android.manifest.ManifestParser; +import com.google.idea.blaze.base.qsync.ProjectProtoTransformProvider; +import com.google.idea.blaze.base.qsync.QuerySyncProject; +import com.google.idea.blaze.common.Context; +import com.google.idea.blaze.exception.BuildException; +import com.google.idea.blaze.qsync.deps.ProjectProtoUpdate; +import com.google.idea.blaze.qsync.deps.ProjectProtoUpdateOperation; +import com.google.idea.blaze.qsync.java.AddAndroidResPackages; +import com.google.idea.blaze.qsync.java.AddDependencyAars; +import com.google.idea.blaze.qsync.project.BuildGraphData; +import com.google.idea.blaze.qsync.project.ProjectProto.Project; +import com.google.idea.blaze.qsync.project.ProjectProtoTransform; +import java.util.List; + +/** A {@link ProjectProtoTransform} that adds android specific information to the project proto. */ +public class AndroidProjectProtoTransform implements ProjectProtoTransform { + + /** + * Provides a {@link ProjectProtoTransform} that adds android specific information to the project + * proto. + */ + public static class Provider implements ProjectProtoTransformProvider { + @Override + public List createTransforms(QuerySyncProject project) { + return ImmutableList.of(new AndroidProjectProtoTransform(project)); + } + } + + private final ImmutableList updateOperations; + + private AndroidProjectProtoTransform(QuerySyncProject project) { + updateOperations = + ImmutableList.of( + new AddDependencyAars( + project.getArtifactTracker()::getStateSnapshot, + project.getBuildArtifactCache(), + project.getProjectDefinition(), + in -> ManifestParser.parseManifestFromInputStream(in).packageName), + new AddAndroidResPackages(project.getArtifactTracker()::getStateSnapshot)); + } + + @Override + public Project apply(Project proto, BuildGraphData graph, Context context) + throws BuildException { + ProjectProtoUpdate update = new ProjectProtoUpdate(proto, graph, context); + for (var op : updateOperations) { + op.update(update); + } + return update.build(); + } +} diff --git a/aswb/src/com/google/idea/blaze/android/qsync/BlazeAndroidQuerySyncPlugin.java b/aswb/src/com/google/idea/blaze/android/qsync/BlazeAndroidQuerySyncPlugin.java new file mode 100644 index 00000000000..4c7c47675f3 --- /dev/null +++ b/aswb/src/com/google/idea/blaze/android/qsync/BlazeAndroidQuerySyncPlugin.java @@ -0,0 +1,156 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.qsync; + +import static com.google.common.collect.ImmutableSet.toImmutableSet; +import static java.util.stream.Collectors.joining; + +import com.android.tools.idea.model.AndroidModel; +import com.android.tools.idea.projectsystem.NamedIdeaSourceProvider; +import com.android.tools.idea.projectsystem.NamedIdeaSourceProviderBuilder; +import com.android.tools.idea.projectsystem.ScopeType; +import com.google.common.collect.ImmutableSet; +import com.google.common.util.concurrent.Futures; +import com.google.idea.blaze.android.qsync.projectstructure.AndroidFacetModuleCustomizer; +import com.google.idea.blaze.android.resources.BlazeLightResourceClassService; +import com.google.idea.blaze.android.sdk.BlazeSdkProvider; +import com.google.idea.blaze.android.sync.model.AndroidSdkPlatform; +import com.google.idea.blaze.android.sync.model.idea.BlazeAndroidModel; +import com.google.idea.blaze.android.sync.sdk.AndroidSdkFromProjectView; +import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; +import com.google.idea.blaze.base.model.primitives.WorkspaceType; +import com.google.idea.blaze.base.projectview.ProjectViewManager; +import com.google.idea.blaze.base.projectview.ProjectViewSet; +import com.google.idea.blaze.base.projectview.section.sections.BuildFlagsSection; +import com.google.idea.blaze.base.qsync.BlazeQuerySyncPlugin; +import com.google.idea.blaze.base.sync.projectview.LanguageSupport; +import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings; +import com.google.idea.blaze.common.Context; +import com.google.idea.blaze.common.PrintOutput; +import com.google.idea.blaze.java.projectview.JavaLanguageLevelSection; +import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsProvider; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.roots.LanguageLevelProjectExtension; +import com.intellij.openapi.roots.ex.ProjectRootManagerEx; +import com.intellij.openapi.vfs.VfsUtilCore; +import com.intellij.pom.java.LanguageLevel; +import com.intellij.util.containers.ContainerUtil; +import java.io.File; +import java.util.Set; +import org.jetbrains.android.facet.AndroidFacet; +import org.jetbrains.android.facet.AndroidFacetProperties; + +/** ASwB sync plugin. */ +public class BlazeAndroidQuerySyncPlugin implements BlazeQuerySyncPlugin { + + @Override + public void updateProjectSettingsForQuerySync( + Project project, Context context, ProjectViewSet projectViewSet) { + if (!isAndroidWorkspace(LanguageSupport.createWorkspaceLanguageSettings(projectViewSet))) { + return; + } + AndroidSdkPlatform androidSdkPlatform = + AndroidSdkFromProjectView.getAndroidSdkPlatform(context, projectViewSet); + Sdk sdk = BlazeSdkProvider.getInstance().findSdk(androidSdkPlatform.androidSdk); + LanguageLevel javaLanguageLevel = + JavaLanguageLevelSection.getLanguageLevel(projectViewSet, LanguageLevel.JDK_11); + ProjectRootManagerEx rootManager = ProjectRootManagerEx.getInstanceEx(project); + rootManager.setProjectSdk(sdk); + LanguageLevelProjectExtension ext = LanguageLevelProjectExtension.getInstance(project); + ext.setLanguageLevel(javaLanguageLevel); + } + + @Override + public void updateProjectStructureForQuerySync( + Project project, + Context context, + IdeModifiableModelsProvider models, + WorkspaceRoot workspaceRoot, + Module workspaceModule, + Set androidResourceDirectories, + Set androidSourcePackages, + WorkspaceLanguageSettings workspaceLanguageSettings) { + + if (!isAndroidWorkspace(workspaceLanguageSettings)) { + return; + } + + AndroidFacetModuleCustomizer androidFacetModuleCustomizer = + new AndroidFacetModuleCustomizer(models); + + // Attach AndroidFacet to workspace modules + androidFacetModuleCustomizer.createAndroidFacet(workspaceModule, false); + + // Add all source resource directories to this AndroidFacet + AndroidFacet workspaceFacet = AndroidFacet.getInstance(workspaceModule); + if (workspaceFacet == null) { + context.output( + PrintOutput.error( + "workspace_type is android, but no android facet present; not configuring android" + + " resources")); + context.output( + PrintOutput.output( + "Consider adding \"workspace_type: java\" or similar to your .blazeproject file, or" + + " add the android facet in project settings if this is an android project.")); + return; + } + ImmutableSet androidResourceDirectoryFiles = + androidResourceDirectories.stream() + .map(dir -> new File(workspaceRoot.directory(), dir).getAbsoluteFile()) + .collect(toImmutableSet()); + NamedIdeaSourceProvider sourceProvider = + NamedIdeaSourceProviderBuilder.create( + workspaceModule.getName(), VfsUtilCore.fileToUrl(new File("MissingManifest.xml"))) + .withScopeType(ScopeType.MAIN) + .withResDirectoryUrls( + ContainerUtil.map(androidResourceDirectoryFiles, VfsUtilCore::fileToUrl)) + .build(); + + // Set AndroidModel for this AndroidFacet + ProjectViewSet projectViewSet = ProjectViewManager.getInstance(project).getProjectViewSet(); + boolean desugarJava8Libs = + projectViewSet.listItems(BuildFlagsSection.KEY).contains("--config=android_java8_libs"); + AndroidSdkPlatform androidSdkPlatform = + AndroidSdkFromProjectView.getAndroidSdkPlatform(context, projectViewSet); + + BlazeAndroidModel androidModel = + new BlazeAndroidModel( + project, + workspaceRoot.directory(), + sourceProvider, + Futures.immediateFuture(":workspace"), + androidSdkPlatform != null ? androidSdkPlatform.androidMinSdkLevel : 1, + desugarJava8Libs); + AndroidModel.set(workspaceFacet, androidModel); + workspaceFacet.getProperties().RES_FOLDERS_RELATIVE_PATH = + androidResourceDirectoryFiles.stream() + .map(VfsUtilCore::fileToUrl) + .collect(joining(AndroidFacetProperties.PATH_LIST_SEPARATOR_IN_FACET_CONFIGURATION)); + + // Register all source java packages as workspace packages + BlazeLightResourceClassService.Builder rClassBuilder = + new BlazeLightResourceClassService.Builder(project); + rClassBuilder.addWorkspacePackages(androidSourcePackages); + // TODO(b/283282438): Make Preview work with resources in project's res folder in Query Sync + BlazeLightResourceClassService.getInstance(project).installRClasses(rClassBuilder); + } + + private static boolean isAndroidWorkspace(WorkspaceLanguageSettings workspaceLanguageSettings) { + return workspaceLanguageSettings.isWorkspaceType(WorkspaceType.ANDROID); + } +} diff --git a/aswb/src/com/google/idea/blaze/android/qsync/projectstructure/AndroidFacetModuleCustomizer.java b/aswb/src/com/google/idea/blaze/android/qsync/projectstructure/AndroidFacetModuleCustomizer.java new file mode 100755 index 00000000000..a8db85e8119 --- /dev/null +++ b/aswb/src/com/google/idea/blaze/android/qsync/projectstructure/AndroidFacetModuleCustomizer.java @@ -0,0 +1,61 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.qsync.projectstructure; + +import com.android.AndroidProjectTypes; +import com.android.builder.model.AndroidProject; +import com.intellij.facet.FacetManager; +import com.intellij.facet.ModifiableFacetModel; +import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsProvider; +import com.intellij.openapi.module.Module; +import org.jetbrains.android.facet.AndroidFacet; +import org.jetbrains.android.facet.AndroidFacetProperties; +import org.jetbrains.android.facet.AndroidFacetPropertiesCompat; + +/** Adds the Android facet to modules imported from {@link AndroidProject}s. */ +public class AndroidFacetModuleCustomizer { + + private final IdeModifiableModelsProvider models; + + public AndroidFacetModuleCustomizer(IdeModifiableModelsProvider models) { + this.models = models; + } + + public void createAndroidFacet(Module module, boolean isApp) { + ModifiableFacetModel modifiableFacetModel = models.getModifiableFacetModel(module); + AndroidFacet facet = modifiableFacetModel.getFacetByType(AndroidFacet.ID); + FacetManager facetManager = FacetManager.getInstance(module); + if (facet != null) { + configureFacet(facet, isApp); + } else { + // Module does not have Android facet. Create one and add it. + facet = facetManager.createFacet(AndroidFacet.getFacetType(), AndroidFacet.NAME, null); + modifiableFacetModel.addFacet(facet); + configureFacet(facet, isApp); + } + } + + private static void configureFacet(AndroidFacet facet, boolean isApp) { + AndroidFacetProperties facetState = facet.getProperties(); + facetState.ALLOW_USER_CONFIGURATION = false; + facetState.PROJECT_TYPE = + isApp ? AndroidProjectTypes.PROJECT_TYPE_APP : AndroidProjectTypes.PROJECT_TYPE_LIBRARY; + facetState.MANIFEST_FILE_RELATIVE_PATH = ""; + facetState.RES_FOLDER_RELATIVE_PATH = ""; + facetState.ASSETS_FOLDER_RELATIVE_PATH = ""; + AndroidFacetPropertiesCompat.enableSourcesAutogeneration(facetState, false); + } +} diff --git a/aswb/src/com/google/idea/blaze/android/resources/BlazeLightResourceClassService.java b/aswb/src/com/google/idea/blaze/android/resources/BlazeLightResourceClassService.java index 90697501184..27db3fe5daa 100644 --- a/aswb/src/com/google/idea/blaze/android/resources/BlazeLightResourceClassService.java +++ b/aswb/src/com/google/idea/blaze/android/resources/BlazeLightResourceClassService.java @@ -23,11 +23,9 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; -import com.google.common.collect.Sets; import com.google.idea.blaze.base.sync.data.BlazeDataStorage; import com.google.idea.common.experiments.BoolExperiment; import com.google.idea.common.experiments.FeatureRolloutExperiment; -import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.project.Project; @@ -43,7 +41,8 @@ import org.jetbrains.annotations.Nullable; /** Implementation of {@link LightResourceClassService} set up at Blaze sync time. */ -public class BlazeLightResourceClassService implements LightResourceClassService { +public class BlazeLightResourceClassService extends BlazeLightResourceClassServiceBase + implements LightResourceClassService { @VisibleForTesting public static final FeatureRolloutExperiment workspaceResourcesFeature = @@ -58,17 +57,15 @@ public class BlazeLightResourceClassService implements LightResourceClassService private final Project project; - private Map rClasses = Maps.newHashMap(); - private Map rClassesByModule = Maps.newHashMap(); private Map rClassPackages = Maps.newHashMap(); private Map workspaceRClasses = Maps.newHashMap(); private Set workspaceRClassNames = ImmutableSet.of(); + private Set workspaceResourcePackages = ImmutableSet.of(); - private final Set allRClasses = Sets.newHashSet(); private PsiManager psiManager; public static BlazeLightResourceClassService getInstance(Project project) { - return ServiceManager.getService(project, BlazeLightResourceClassService.class); + return project.getService(BlazeLightResourceClassService.class); } private BlazeLightResourceClassService(Project project) { @@ -81,6 +78,7 @@ public static class Builder { Map rClassByModuleMap = Maps.newHashMap(); Map rClassPackages = Maps.newHashMap(); Set workspaceRClassNames = ImmutableSet.of(); + Set workspaceResourcePackages = ImmutableSet.of(); PsiManager psiManager; @@ -105,6 +103,7 @@ public void addWorkspacePackages(Set resourceJavaPackages) { if (!workspaceResourcesFeature.isEnabled()) { return; } + this.workspaceResourcePackages = resourceJavaPackages; this.workspaceRClassNames = resourceJavaPackages.stream() .map(Builder::getQualifiedRClassName) @@ -133,11 +132,16 @@ private void addStubPackages(String resourceJavaPackage) { } } + public Set getWorkspaceResourcePackages() { + return workspaceResourcePackages; + } + public void installRClasses(Builder builder) { this.rClasses = builder.rClassMap; this.rClassesByModule = builder.rClassByModuleMap; this.rClassPackages = builder.rClassPackages; + this.workspaceResourcePackages = builder.workspaceResourcePackages; this.workspaceRClasses = new HashMap<>(); this.workspaceRClassNames = ImmutableSet.copyOf(builder.workspaceRClassNames); this.psiManager = builder.psiManager; @@ -197,30 +201,6 @@ private BlazeRClass getRClassForWorkspace(String qualifiedName, GlobalSearchScop } @Override - public Collection getLightRClassesAccessibleFromModule( - Module module, boolean includeTest) { - if (workspaceResourcesFeature.isEnabled() - && module.getName().equals(BlazeDataStorage.WORKSPACE_MODULE_NAME)) { - // Returns all the packages in resource modules, and all the workspace packages that - // have previously been asked for. All `res/` directories in our project should belong to a - // resource module. For java sources, IntelliJ will ask for explicit resource package by - // calling `getLightRClasses` at which point we can create the package. This is not completely - // correct and the autocomplete will be slightly off when initial `R` is typed in the editor, - // but this workaround is being used to mitigate issues (b/136685602) while resources - // are re-worked. - return allRClasses; - } else { - return rClasses.values(); - } - } - - // @Override #api4.0: override added in as4.1 - public Collection getLightRClassesDefinedByModule( - Module module, boolean includeTestClasses) { - BlazeRClass rClass = rClassesByModule.get(module); - return rClass == null ? ImmutableSet.of() : ImmutableSet.of(rClass); - } - public Collection getLightRClassesContainingModuleResources(Module module) { return rClasses.values(); } diff --git a/aswb/src/com/google/idea/blaze/android/resources/actions/BlazeCreateResourceUtils.java b/aswb/src/com/google/idea/blaze/android/resources/actions/BlazeCreateResourceUtils.java index 6def189f7d0..1ca0f34b281 100644 --- a/aswb/src/com/google/idea/blaze/android/resources/actions/BlazeCreateResourceUtils.java +++ b/aswb/src/com/google/idea/blaze/android/resources/actions/BlazeCreateResourceUtils.java @@ -24,6 +24,8 @@ import com.google.idea.blaze.base.command.buildresult.OutputArtifactResolver; import com.google.idea.blaze.base.ideinfo.TargetKey; import com.google.idea.blaze.base.model.BlazeProjectData; +import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.settings.BlazeImportSettings.ProjectType; import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder; import com.google.idea.blaze.base.targetmaps.SourceToTargetMap; @@ -50,6 +52,8 @@ class BlazeCreateResourceUtils { private static final String PLACEHOLDER_TEXT = "choose a res/ directory with dropdown or browse button"; + // TODO(b/295880481): Revisit how resources are created. This file conflates logic and UI, but it + // also reveals that the integration with Studio is done at the wrong level. static void setupResDirectoryChoices( Project project, @Nullable VirtualFile contextFile, @@ -57,6 +61,31 @@ static void setupResDirectoryChoices( ComboboxWithBrowseButton resDirComboAndBrowser) { // Reset the item list before filling it back up. resDirComboAndBrowser.getComboBox().removeAllItems(); + + if (Blaze.getProjectType(project).equals(ProjectType.QUERY_SYNC)) { + if (contextFile == null) { + return; + } + // For query sync, populates the combo box with either the directory itself, if a directory + // was right-clicked, or the containing directory, if a file was right-clicked. This + // could be augmented with additional options (i.e. res folders in higher directories and + // perhaps other res folders) + File fileFromContext = VfsUtilCore.virtualToIoFile(contextFile); + if (!fileFromContext.isDirectory()) { + fileFromContext = fileFromContext.getParentFile(); + } + File closestDirToContext = new File(fileFromContext.getPath(), "res"); + + @SuppressWarnings({"unchecked", "rawtypes"}) // Class comes with raw type from JetBrains + JComboBox resDirCombo = resDirComboAndBrowser.getComboBox(); + resDirCombo.setEditable(true); + resDirCombo.addItem(closestDirToContext); + resDirCombo.setSelectedItem(closestDirToContext); + resDirComboAndBrowser.setVisible(true); + resDirLabel.setVisible(true); + return; + } + BlazeProjectData blazeProjectData = BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); if (blazeProjectData != null) { 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 bd51c7e43d2..c84530523f9 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 @@ -284,6 +284,9 @@ public JComponent getPanel() { return myPanel; } + // @Override #api223 + public void setAllowValueEditing(boolean enabled) {} + private void setChangeFileNameVisible(boolean isVisible) { myFileNameLabel.setVisible(isVisible); myFileNameCombo.setVisible(isVisible); diff --git a/aswb/src/com/google/idea/blaze/android/run/ApkBuildStepProvider.java b/aswb/src/com/google/idea/blaze/android/run/ApkBuildStepProvider.java index 074fa5f9c15..2547864094b 100644 --- a/aswb/src/com/google/idea/blaze/android/run/ApkBuildStepProvider.java +++ b/aswb/src/com/google/idea/blaze/android/run/ApkBuildStepProvider.java @@ -20,6 +20,7 @@ import com.google.idea.blaze.base.model.primitives.Label; import com.google.idea.blaze.base.settings.BuildSystemName; import com.google.idea.blaze.base.util.BuildSystemExtensionPoint; +import com.intellij.execution.ExecutionException; import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.openapi.project.Project; @@ -32,12 +33,27 @@ static ApkBuildStepProvider getInstance(BuildSystemName buildSystemName) { return BuildSystemExtensionPoint.getInstance(EP_NAME, buildSystemName); } - /** Returns a build step for the given build configurations. */ - ApkBuildStep getBuildStep( + /** Returns a build step that can build artifacts required for an {@code android_binary}. */ + ApkBuildStep getBinaryBuildStep( Project project, boolean useMobileInstall, + boolean nativeDebuggingEnabled, Label label, ImmutableList blazeFlags, ImmutableList exeFlags, String launchId); + + /** + * Returns a build step that can build artifacts required for an {@code + * android_instrumentation_test}. + */ + ApkBuildStep getAitBuildStep( + Project project, + boolean useMobileInstall, + boolean nativeDebuggingEnabled, + Label label, + ImmutableList blazeFlags, + ImmutableList exeFlags, + String launchId) + throws ExecutionException; } diff --git a/aswb/src/com/google/idea/blaze/android/run/BazelApkBuildStepProvider.java b/aswb/src/com/google/idea/blaze/android/run/BazelApkBuildStepProvider.java index 692d31ca0ad..cb532d58704 100644 --- a/aswb/src/com/google/idea/blaze/android/run/BazelApkBuildStepProvider.java +++ b/aswb/src/com/google/idea/blaze/android/run/BazelApkBuildStepProvider.java @@ -15,21 +15,32 @@ */ package com.google.idea.blaze.android.run; +import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.idea.blaze.android.run.binary.mobileinstall.MobileInstallBuildStep; import com.google.idea.blaze.android.run.runner.ApkBuildStep; +import com.google.idea.blaze.android.run.runner.BlazeInstrumentationTestApkBuildStep; import com.google.idea.blaze.android.run.runner.FullApkBuildStep; +import com.google.idea.blaze.android.run.runner.InstrumentationInfo; +import com.google.idea.blaze.android.run.runner.InstrumentationInfo.InstrumentationParserException; +import com.google.idea.blaze.base.model.BlazeProjectData; import com.google.idea.blaze.base.model.primitives.Label; import com.google.idea.blaze.base.settings.BuildSystemName; +import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; +import com.intellij.execution.ExecutionException; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; /** Provides APK build step for Bazel projects. */ public class BazelApkBuildStepProvider implements ApkBuildStepProvider { + private static final Logger logger = Logger.getInstance(BazelApkBuildStepProvider.class); + @Override - public ApkBuildStep getBuildStep( + public ApkBuildStep getBinaryBuildStep( Project project, boolean useMobileInstall, + boolean nativeDebuggingEnabled, Label label, ImmutableList blazeFlags, ImmutableList exeFlags, @@ -37,8 +48,35 @@ public ApkBuildStep getBuildStep( if (useMobileInstall) { return new MobileInstallBuildStep(project, label, blazeFlags, exeFlags, launchId); } else { - return new FullApkBuildStep(project, label, blazeFlags); + return new FullApkBuildStep(project, label, blazeFlags, nativeDebuggingEnabled); + } + } + + @Override + public ApkBuildStep getAitBuildStep( + Project project, + boolean useMobileInstall, + boolean nativeDebuggingEnabled, + Label label, + ImmutableList blazeFlags, + ImmutableList exeFlags, + String launchId) + throws ExecutionException { + if (useMobileInstall) { + return new MobileInstallBuildStep(project, label, blazeFlags, exeFlags, launchId); + } + + BlazeProjectData data = + Preconditions.checkNotNull( + BlazeProjectDataManager.getInstance(project).getBlazeProjectData()); + InstrumentationInfo info; + try { + info = InstrumentationInfo.getInstrumentationInfo(label, data); + } catch (InstrumentationParserException e) { + logger.warn("Could not get instrumentation info: " + e.getMessage()); + throw new ExecutionException(e.getMessage(), e); } + return new BlazeInstrumentationTestApkBuildStep(project, info, blazeFlags); } @Override 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 a5147d7dd2d..756ea45a61c 100644 --- a/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationCommonState.java +++ b/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationCommonState.java @@ -16,6 +16,7 @@ package com.google.idea.blaze.android.run; import com.android.tools.idea.run.ValidationError; +import com.android.tools.idea.run.ValidationErrorCompat; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.idea.blaze.android.run.state.DebuggerSettingsState; @@ -46,11 +47,10 @@ public class BlazeAndroidRunConfigurationCommonState implements RunConfiguration private static final String USER_BLAZE_FLAG_TAG = "blaze-user-flag"; private static final String USER_EXE_FLAG_TAG = "blaze-user-exe-flag"; - // We need to split "-c dbg" into two flags because we pass flags - // as a list of strings to the command line executor and we need blaze - // to see -c and dbg as two separate entities, not one. + // "-c dbg" defines both the copt and strip flags below; however, we want to allow users to + // override -c (to fastbuild or opt) without entirely compromising the generation of debug info. private static final ImmutableList NATIVE_DEBUG_FLAGS = - ImmutableList.of("--fission=no", "-c", "dbg"); + ImmutableList.of("--copt=-g", "--fission=no", "--strip=never", "-c", "dbg"); private final RunConfigurationFlagsState blazeFlags; private final RunConfigurationFlagsState exeFlags; @@ -87,10 +87,12 @@ public ImmutableList getExpandedBuildFlags( BlazeCommandName command, BlazeInvocationContext context) { return ImmutableList.builder() + // Add Native Debugging flags first to allow for overriding -c dbg in .blazeproject or run + // configurations. + .addAll(getNativeDebuggerFlags()) .addAll( BlazeFlags.blazeFlags(project, projectViewSet, command, BlazeContext.create(), context)) .addAll(getBlazeFlagsState().getFlagsForExternalProcesses()) - .addAll(getNativeDebuggerFlags()) .build(); } @@ -114,7 +116,7 @@ public List validate(Project project) { if (isNativeDebuggingEnabled() && !blazeProjectData.getWorkspaceLanguageSettings().isLanguageActive(LanguageClass.C)) { errors.add( - ValidationError.fatal( + ValidationErrorCompat.fatal( "Native debugging requires C language support.", () -> AdditionalLanguagesHelper.enableLanguageSupport( 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 f536c3e8303..c717407a075 100644 --- a/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationValidationUtil.java +++ b/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationValidationUtil.java @@ -17,6 +17,7 @@ import com.android.tools.idea.project.AndroidProjectInfo; import com.android.tools.idea.run.ValidationError; +import com.android.tools.idea.run.ValidationErrorCompat; import com.google.common.collect.Lists; import com.google.common.collect.Ordering; import com.google.idea.blaze.base.projectview.ProjectViewManager; @@ -32,7 +33,7 @@ import com.intellij.openapi.project.Project; import java.util.List; import org.jetbrains.android.facet.AndroidFacet; -import org.jetbrains.android.sdk.AndroidPlatform; +import org.jetbrains.android.sdk.AndroidPlatformsCompat; import org.jetbrains.android.util.AndroidBundle; /** @@ -69,17 +70,17 @@ public static List validateWorkspaceModule(Project project) { ModuleFinder.getInstance(project).findModuleByName(BlazeDataStorage.WORKSPACE_MODULE_NAME); if (workspaceModule == null) { errors.add( - ValidationError.fatal( + ValidationErrorCompat.fatal( "No workspace module found. Please resync project.", () -> resync(project))); return errors; } if (AndroidFacet.getInstance(workspaceModule) == null) { errors.add( - ValidationError.fatal( + ValidationErrorCompat.fatal( "Android model missing from workspace module. Please resync project.", () -> resync(project))); } - if (AndroidPlatform.getInstance(workspaceModule) == null) { + if (AndroidPlatformsCompat.getInstance(workspaceModule) == null) { errors.add(ValidationError.fatal(AndroidBundle.message("select.platform.error"))); } return errors; @@ -90,7 +91,7 @@ public static void validate(Project project) throws ExecutionException { errors.addAll(validateWorkspaceModule(project)); if (AndroidProjectInfo.getInstance(project).requiredAndroidModelMissing()) { - errors.add(ValidationError.fatal(SYNC_FAILED_ERR_MSG, () -> resync(project))); + errors.add(ValidationErrorCompat.fatal(SYNC_FAILED_ERR_MSG, () -> resync(project))); } ProjectViewSet projectViewSet = ProjectViewManager.getInstance(project).getProjectViewSet(); diff --git a/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunState.java b/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunState.java deleted file mode 100644 index 5d291517cd3..00000000000 --- a/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunState.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright 2019 The Bazel Authors. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.android.tools.idea.run.AndroidProcessHandler; -import com.android.tools.idea.run.ApkProvisionException; -import com.android.tools.idea.run.ApplicationIdProvider; -import com.android.tools.idea.run.DeviceFutures; -import com.android.tools.idea.run.LaunchInfo; -import com.android.tools.idea.run.LaunchOptions; -import com.android.tools.idea.run.LaunchOptionsProvider; -import com.android.tools.idea.run.LaunchTaskRunner; -import com.android.tools.idea.run.tasks.LaunchTasksProvider; -import com.android.tools.idea.run.util.SwapInfo; -import com.android.tools.idea.stats.RunStats; -import com.google.common.annotations.VisibleForTesting; -import com.google.idea.blaze.android.run.runner.BlazeAndroidDeviceSelector.DeviceSession; -import com.google.idea.blaze.android.run.runner.BlazeAndroidRunContext; -import com.google.idea.blaze.base.run.smrunner.SmRunnerUtils; -import com.intellij.execution.DefaultExecutionResult; -import com.intellij.execution.ExecutionException; -import com.intellij.execution.ExecutionResult; -import com.intellij.execution.Executor; -import com.intellij.execution.configurations.RunProfileState; -import com.intellij.execution.process.ProcessHandler; -import com.intellij.execution.runners.ExecutionEnvironment; -import com.intellij.execution.runners.ProgramRunner; -import com.intellij.execution.ui.ConsoleView; -import com.intellij.execution.ui.RunContentDescriptor; -import com.intellij.execution.ui.RunContentManager; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.util.Key; -import javax.annotation.Nullable; - -/** State for android_binary and android_test runs. */ -public final class BlazeAndroidRunState implements RunProfileState { - private static final Key CONSOLE_VIEW_KEY = - new Key<>("android.run.state.consoleview"); - - private final ExecutionEnvironment env; - private final String launchConfigName; - private final DeviceSession deviceSession; - private final BlazeAndroidRunContext runContext; - private final LaunchOptions.Builder launchOptionsBuilder; - - public BlazeAndroidRunState( - ExecutionEnvironment env, - LaunchOptions.Builder launchOptionsBuilder, - DeviceSession deviceSession, - BlazeAndroidRunContext runContext) { - this.env = env; - this.launchConfigName = env.getRunProfile().getName(); - this.deviceSession = deviceSession; - this.runContext = runContext; - this.launchOptionsBuilder = launchOptionsBuilder; - } - - @Nullable - @Override - public ExecutionResult execute(Executor executor, ProgramRunner runner) - throws ExecutionException { - DefaultExecutionResult result = executeInner(executor, runner); - if (result == null) { - return null; - } - return SmRunnerUtils.attachRerunFailedTestsAction(result); - } - - @Nullable - private DefaultExecutionResult executeInner(Executor executor, ProgramRunner runner) - throws ExecutionException { - ProcessHandler processHandler; - ConsoleView console; - - ApplicationIdProvider applicationIdProvider = runContext.getApplicationIdProvider(); - - String applicationId; - try { - applicationId = applicationIdProvider.getPackageName(); - } catch (ApkProvisionException e) { - throw new ExecutionException("Unable to obtain application id", e); - } - - if (executor instanceof LaunchOptionsProvider) { - launchOptionsBuilder.addExtraOptions(((LaunchOptionsProvider) executor).getLaunchOptions()); - } - - LaunchTasksProvider launchTasksProvider = - runContext.getLaunchTasksProvider(launchOptionsBuilder); - - DeviceFutures deviceFutures = deviceSession.deviceFutures; - assert deviceFutures != null; - ProcessHandler previousSessionProcessHandler = - deviceSession.sessionInfo != null ? deviceSession.sessionInfo.getProcessHandler() : null; - - boolean isSwap = env.getUserData(SwapInfo.SWAP_INFO_KEY) != null; - if (!isSwap) { - // In the case of cold swap, there is an existing process that is connected, - // but we are going to launch a new one. - // Detach the previous process handler so that we don't end up with - // 2 run tabs for the same launch (the existing one and the new one). - if (previousSessionProcessHandler != null) { - RunContentManager manager = RunContentManager.getInstance(env.getProject()); - RunContentDescriptor descriptor = - manager.findContentDescriptor(executor, previousSessionProcessHandler); - if (descriptor != null) { - manager.removeRunContent(executor, descriptor); - } - previousSessionProcessHandler.detachProcess(); - } - - processHandler = new AndroidProcessHandler(env.getProject(), applicationId); - console = - runContext - .getConsoleProvider() - .createAndAttach(env.getProject(), processHandler, executor); - // Stash the console. When we swap, we need the console, as that has the method to print a - // hyperlink. - // (If we only need normal text output, we can call ProcessHandler#notifyTextAvailable - // instead.) - processHandler.putCopyableUserData(CONSOLE_VIEW_KEY, console); - } else { - assert previousSessionProcessHandler != null - : "No process handler from previous session, yet current tasks don't create one"; - processHandler = previousSessionProcessHandler; - console = processHandler.getCopyableUserData(CONSOLE_VIEW_KEY); - assert console != null; - } - - LaunchInfo launchInfo = new LaunchInfo(executor, runner, env, runContext.getConsoleProvider()); - - LaunchTaskRunner task = - new LaunchTaskRunner( - env.getProject(), - launchConfigName, - applicationId, - env.getExecutionTarget().getDisplayName(), - launchInfo, - processHandler, - deviceFutures, - launchTasksProvider, - RunStats.from(env), - console::printHyperlink); - - ProgressManager.getInstance().run(task); - - return console == null ? null : new DefaultExecutionResult(console, processHandler); - } - - @VisibleForTesting - public BlazeAndroidRunContext getRunContext() { - return runContext; - } -} diff --git a/aswb/src/com/google/idea/blaze/android/run/BlazeCommandAndroidDebuggerInfoProvider.java b/aswb/src/com/google/idea/blaze/android/run/BlazeCommandAndroidDebuggerInfoProvider.java index 2972ba0949e..03dd9f80da7 100644 --- a/aswb/src/com/google/idea/blaze/android/run/BlazeCommandAndroidDebuggerInfoProvider.java +++ b/aswb/src/com/google/idea/blaze/android/run/BlazeCommandAndroidDebuggerInfoProvider.java @@ -15,13 +15,12 @@ */ package com.google.idea.blaze.android.run; -import com.android.tools.idea.run.editor.AndroidDebugger; +import com.android.tools.idea.execution.common.debug.AndroidDebugger; +import com.android.tools.idea.execution.common.debug.AndroidDebuggerState; +import com.android.tools.idea.execution.common.debug.impl.java.AndroidJavaDebugger; import com.android.tools.idea.run.editor.AndroidDebuggerInfoProvider; -import com.android.tools.idea.run.editor.AndroidDebuggerState; -import com.android.tools.idea.run.editor.AndroidJavaDebugger; import com.google.idea.blaze.android.cppimpl.debug.BlazeNativeAndroidDebugger; import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryRunConfigurationState; -import com.google.idea.blaze.android.run.runner.BlazeAndroidDebuggerService; import com.google.idea.blaze.android.run.test.BlazeAndroidTestRunConfigurationState; import com.google.idea.blaze.base.model.BlazeProjectData; import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration; @@ -43,6 +42,7 @@ public boolean supportsProject(Project project) { } @Override + @SuppressWarnings("rawtypes") // List includes multiple AndroidDebuggerState types. public List getAndroidDebuggers(RunConfiguration configuration) { if (getCommonState(configuration) != null) { return Arrays.asList(new BlazeNativeAndroidDebugger(), new AndroidJavaDebugger()); @@ -52,7 +52,8 @@ public List getAndroidDebuggers(RunConfiguration configuration) @Nullable @Override - public AndroidDebugger getSelectedAndroidDebugger(RunConfiguration configuration) { + public AndroidDebugger getSelectedAndroidDebugger( + RunConfiguration configuration) { // b/170159822 Always return java debugger because BlazeAutoAndroidDebugger doesn't work and // users likely want the java debugger not the native debugger. return new AndroidJavaDebugger(); @@ -61,14 +62,14 @@ public AndroidDebugger getSelectedAndroidDebugger(RunConfiguration configuration @Nullable @Override public AndroidDebuggerState getSelectedAndroidDebuggerState(RunConfiguration configuration) { - AndroidDebugger debugger = getSelectedAndroidDebugger(configuration); + AndroidDebugger debugger = getSelectedAndroidDebugger(configuration); if (debugger == null) { return null; } - return BlazeAndroidDebuggerService.getInstance(configuration.getProject()) - .getDebuggerState(debugger); + return debugger.createState(); } + @Nullable private BlazeAndroidRunConfigurationCommonState getCommonState(RunConfiguration configuration) { if (!(configuration instanceof BlazeCommandRunConfiguration)) { return null; diff --git a/aswb/src/com/google/idea/blaze/android/run/LaunchMetrics.java b/aswb/src/com/google/idea/blaze/android/run/LaunchMetrics.java index 4775a26e685..8ed659862b6 100644 --- a/aswb/src/com/google/idea/blaze/android/run/LaunchMetrics.java +++ b/aswb/src/com/google/idea/blaze/android/run/LaunchMetrics.java @@ -15,8 +15,6 @@ */ package com.google.idea.blaze.android.run; -import com.android.tools.idea.BaseAsCompat; -import com.android.tools.idea.run.tasks.LaunchResult; import com.google.common.base.StandardSystemProperty; import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; @@ -58,26 +56,26 @@ public static String newLaunchId() { } public static void logBuildTime( - String launchId, boolean usesStudioDeployer, Duration buildDuration, int blazeExitCode) { + String launchId, + boolean usesStudioDeployer, + Duration buildDuration, + int blazeExitCode, + ImmutableMap additionalMetrics) { if (!launchMetricsEnabled.getValue()) { return; } - ImmutableMap metrics = - ImmutableMap.of( - KEY_LAUNCH_ID, - launchId, - KEY_USES_STUDIO_DEPLOYER, - Boolean.toString(usesStudioDeployer), - KEY_BUILD_DURATION_MILLIS, - Long.toString(buildDuration.toMillis()), - KEY_BLAZE_EXIT_CODE, - Integer.toString(blazeExitCode)); + ImmutableMap.builder() + .put(KEY_LAUNCH_ID, launchId) + .put(KEY_USES_STUDIO_DEPLOYER, Boolean.toString(usesStudioDeployer)) + .put(KEY_BUILD_DURATION_MILLIS, Long.toString(buildDuration.toMillis())) + .put(KEY_BLAZE_EXIT_CODE, Integer.toString(blazeExitCode)) + .putAll(additionalMetrics) + .buildOrThrow(); EventLoggingService.getInstance().logEvent(LaunchMetrics.class, "BuildTiming", metrics); } - public static void logDeploymentTime( - String launchId, Duration duration, LaunchResult launchResult) { + public static void logDeploymentTime(String launchId, Duration duration, boolean wasSuccessful) { if (!launchMetricsEnabled.getValue()) { return; } @@ -89,7 +87,7 @@ public static void logDeploymentTime( KEY_DEPLOY_DURATION_MILLIS, Long.toString(duration.toMillis()), KEY_DEPLOY_STATUS, - Boolean.toString(BaseAsCompat.wasSuccessfulLaunch(launchResult))); + Boolean.toString(wasSuccessful)); EventLoggingService.getInstance().logEvent(LaunchMetrics.class, "DeployTiming", metrics); } diff --git a/aswb/src/com/google/idea/blaze/android/run/NativeSymbolFinder.java b/aswb/src/com/google/idea/blaze/android/run/NativeSymbolFinder.java new file mode 100644 index 00000000000..88f241568db --- /dev/null +++ b/aswb/src/com/google/idea/blaze/android/run/NativeSymbolFinder.java @@ -0,0 +1,36 @@ +/* + * Copyright 2022 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run; + +import com.google.common.collect.ImmutableList; +import com.google.idea.blaze.base.command.buildresult.BuildResultHelper; +import com.google.idea.blaze.base.model.primitives.Label; +import com.google.idea.blaze.base.scope.BlazeContext; +import com.intellij.openapi.extensions.ExtensionPointName; +import java.io.File; + +/** Configures Blaze build to output native symbols and obtains symbol file paths. */ +public interface NativeSymbolFinder { + ExtensionPointName EP_NAME = + ExtensionPointName.create("com.google.idea.blaze.NativeSymbolFinder"); + + /** Returns additional build flags required to output native symbols. */ + public String getAdditionalBuildFlags(); + + /** Returns native symbol files present in build output. */ + public ImmutableList getNativeSymbolsForBuild( + BlazeContext context, Label label, BuildResultHelper buildResultHelper); +} 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 deleted file mode 100644 index f7fd606066e..00000000000 --- a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryProgramRunner.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2016 The Bazel Authors. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.idea.blaze.android.run.binary; - - -import com.android.tools.idea.profilers.ProfileRunExecutor; -import com.android.tools.idea.run.AndroidProgramRunner; -import com.android.tools.idea.run.AndroidSessionInfo; -import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationHandler; -import com.intellij.execution.ExecutionException; -import com.intellij.execution.ExecutionResult; -import com.intellij.execution.configurations.RunConfiguration; -import com.intellij.execution.configurations.RunProfile; -import com.intellij.execution.configurations.RunProfileState; -import com.intellij.execution.executors.DefaultDebugExecutor; -import com.intellij.execution.executors.DefaultRunExecutor; -import com.intellij.execution.process.ProcessHandler; -import com.intellij.execution.runners.ExecutionEnvironment; -import com.intellij.execution.runners.RunContentBuilder; -import com.intellij.execution.ui.RunContentDescriptor; -import com.intellij.openapi.fileEditor.FileDocumentManager; - -/** Program runner for configurations from {@link BlazeAndroidBinaryRunConfigurationHandler}. */ -public class BlazeAndroidBinaryProgramRunner extends AndroidProgramRunner { - @Override - public boolean canRun(String executorId, RunProfile profile) { - BlazeAndroidRunConfigurationHandler handler = - BlazeAndroidRunConfigurationHandler.getHandlerFrom(profile); - if (!(handler instanceof BlazeAndroidBinaryRunConfigurationHandler)) { - return false; - } - return (DefaultDebugExecutor.EXECUTOR_ID.equals(executorId) - || DefaultRunExecutor.EXECUTOR_ID.equals(executorId) - || ProfileRunExecutor.EXECUTOR_ID.equals(executorId)); - } - - @Override - protected boolean canRunWithMultipleDevices(String executorId) { - return false; - } - - @Override - protected RunContentDescriptor doExecute( - final RunProfileState state, final ExecutionEnvironment env) throws ExecutionException { - FileDocumentManager.getInstance().saveAllDocuments(); - ExecutionResult result = state.execute(env.getExecutor(), this); - RunContentDescriptor descriptor = - new RunContentBuilder(result, env).showRunContent(env.getContentToReuse()); - - if (descriptor != null) { - ProcessHandler processHandler = descriptor.getProcessHandler(); - assert processHandler != null; - - RunProfile runProfile = env.getRunProfile(); - RunConfiguration runConfiguration = - (runProfile instanceof RunConfiguration) ? (RunConfiguration) runProfile : null; - - // The created AndroidSessionInfo is already added to userdata by #create. - AndroidSessionInfo.create( - processHandler, - descriptor, - runConfiguration, - env.getExecutor().getId(), - env.getExecutor().getActionName(), - env.getExecutionTarget()); - } - - return descriptor; - } - - @Override - public String getRunnerId() { - return "AndroidBinaryProgramRunner"; - } -} 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 deb749fdca6..1e62fed2087 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 @@ -15,20 +15,20 @@ */ package com.google.idea.blaze.android.run.binary; -import static com.android.tools.idea.run.deployment.DeviceAndSnapshotComboBoxAction.DEPLOYS_TO_LOCAL_DEVICE; import static com.google.idea.blaze.android.run.LaunchMetrics.logBinaryLaunch; import com.android.tools.idea.run.ValidationError; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.idea.blaze.android.run.ApkBuildStepProvider; import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationCommonState; import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationHandler; import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationValidationUtil; import com.google.idea.blaze.android.run.LaunchMetrics; import com.google.idea.blaze.android.run.binary.AndroidBinaryLaunchMethodsUtils.AndroidBinaryLaunchMethod; -import com.google.idea.blaze.android.run.binary.mobileinstall.BlazeAndroidBinaryMobileInstallRunContext; +import com.google.idea.blaze.android.run.binary.mobileinstall.BlazeAndroidBinaryMobileInstallRunContextCompat; import com.google.idea.blaze.android.run.runner.ApkBuildStep; import com.google.idea.blaze.android.run.runner.BlazeAndroidRunConfigurationRunner; import com.google.idea.blaze.android.run.runner.BlazeAndroidRunContext; @@ -68,27 +68,22 @@ * android_binary targets. */ public class BlazeAndroidBinaryRunConfigurationHandler + extends BlazeAndroidBinaryRunConfigurationHandlerCompat implements BlazeAndroidRunConfigurationHandler { + + @VisibleForTesting + protected BlazeAndroidBinaryRunConfigurationHandler(BlazeCommandRunConfiguration configuration) { + super(configuration); + } + private static final Logger LOG = Logger.getInstance(BlazeAndroidBinaryRunConfigurationHandler.class); - private final Project project; - private final BlazeAndroidBinaryRunConfigurationState configState; - // Keys to store state for the MI migration prompt private static final String MI_LAST_PROMPT = "MI_MIGRATE_LAST_PROMPT"; static final String MI_NEVER_ASK_AGAIN = "MI_MIGRATE_NEVER_AGAIN"; private static final Long MI_TIMEOUT_MS = TimeUnit.HOURS.toMillis(20); // 20 hours - @VisibleForTesting - protected BlazeAndroidBinaryRunConfigurationHandler(BlazeCommandRunConfiguration configuration) { - project = configuration.getProject(); - configState = - new BlazeAndroidBinaryRunConfigurationState( - Blaze.buildSystemName(configuration.getProject())); - configuration.putUserData(DEPLOYS_TO_LOCAL_DEVICE, true); - } - @Override public BlazeAndroidBinaryRunConfigurationState getState() { return configState; @@ -138,9 +133,10 @@ public BlazeCommandRunConfigurationRunner createRunner( configState.getCommonState().getExeFlagsState().getFlagsForExternalProcesses()); ApkBuildStep buildStep = ApkBuildStepProvider.getInstance(Blaze.getBuildSystemName(project)) - .getBuildStep( + .getBinaryBuildStep( project, AndroidBinaryLaunchMethodsUtils.useMobileInstall(configState.getLaunchMethod()), + configState.getCommonState().isNativeDebuggingEnabled(), Label.create(configuration.getSingleTarget().toString()), blazeFlags, exeFlags, @@ -151,7 +147,7 @@ public BlazeCommandRunConfigurationRunner createRunner( switch (configState.getLaunchMethod()) { case NON_BLAZE: runContext = - new BlazeAndroidBinaryNormalBuildRunContext( + new BlazeAndroidBinaryNormalBuildRunContextCompat( project, facet, configuration, env, configState, buildStep, launchId); break; case MOBILE_INSTALL_V2: @@ -160,7 +156,7 @@ public BlazeCommandRunConfigurationRunner createRunner( // fall through case MOBILE_INSTALL: runContext = - new BlazeAndroidBinaryMobileInstallRunContext( + new BlazeAndroidBinaryMobileInstallRunContextCompat( project, facet, configuration, env, configState, buildStep, launchId); break; default: @@ -227,6 +223,7 @@ public String getHandlerName() { * * @return true if dialog was shown and user migrated, otherwise false */ + @CanIgnoreReturnValue private boolean maybeShowMobileInstallOptIn( Project project, BlazeCommandRunConfiguration configuration) { long lastPrompt = PropertiesComponent.getInstance(project).getOrInitLong(MI_LAST_PROMPT, 0L); 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 1ee42aa44c5..94ca73bdca2 100644 --- a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationState.java +++ b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationState.java @@ -15,14 +15,22 @@ */ package com.google.idea.blaze.android.run.binary; +import static com.google.common.base.Verify.verify; + +import com.android.annotations.Nullable; +import com.android.tools.idea.execution.common.ComponentLaunchOptions; import com.android.tools.idea.run.ValidationError; +import com.android.tools.idea.run.configuration.ComplicationWatchFaceInfo; +import com.android.tools.idea.run.configuration.DefaultComplicationWatchFaceInfo; +import com.android.tools.idea.run.configuration.execution.ComplicationLaunchOptions; +import com.android.tools.idea.run.configuration.execution.TileLaunchOptions; +import com.android.tools.idea.run.configuration.execution.WatchFaceLaunchOptions; import com.android.tools.idea.run.editor.AndroidProfilersPanel; import com.android.tools.idea.run.editor.ProfilerState; -import com.android.tools.idea.run.util.LaunchUtils; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; +import com.google.common.base.VerifyException; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Maps; import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationCommonState; import com.google.idea.blaze.android.run.binary.AndroidBinaryLaunchMethodsUtils.AndroidBinaryLaunchMethod; import com.google.idea.blaze.base.run.state.RunConfigurationState; @@ -30,6 +38,8 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.InvalidDataException; import com.intellij.openapi.util.WriteExternalException; +import com.intellij.util.xmlb.XmlSerializer; +import java.util.HashMap; import java.util.Map; import org.jdom.Element; @@ -38,10 +48,15 @@ public final class BlazeAndroidBinaryRunConfigurationState implements RunConfigu /** Element name used to group the {@link ProfilerState} settings */ private static final String PROFILERS_ELEMENT_NAME = "Profilers"; + public static final String LAUNCH_TILE = "launch_tile"; + public static final String LAUNCH_DEFAULT_ACTIVITY = "default_activity"; public static final String LAUNCH_SPECIFIC_ACTIVITY = "specific_activity"; public static final String DO_NOTHING = "do_nothing"; public static final String LAUNCH_DEEP_LINK = "launch_deep_link"; + public static final String LAUNCH_COMPLICATION = "launch_complication"; + public static final String LAUNCH_WATCHFACE = "launch_watchface"; + private static final String LAUNCH_OPTIONS_NAME = "LaunchOptions"; private static final String LAUNCH_METHOD_ATTR = "launch-method"; // Remove once v2 becomes default. @@ -62,19 +77,32 @@ public final class BlazeAndroidBinaryRunConfigurationState implements RunConfigu private static final String DEEP_LINK = "DEEP_LINK"; private static final String ACTIVITY_CLASS = "ACTIVITY_CLASS"; private static final String MODE = "MODE"; - private static final String ACTIVITY_EXTRA_FLAGS = "ACTIVITY_EXTRA_FLAGS"; private String deepLink = ""; private String activityClass = ""; private String mode = LAUNCH_DEFAULT_ACTIVITY; private static final String AM_START_OPTIONS = "AM_START_OPTIONS"; + private static final String CLEAR_APP_STORAGE = "CLEAR_APP_STORAGE"; private String amStartOptions = ""; + private boolean clearAppStorage = false; + + private String buildSystem; + + public final ComplicationWatchFaceInfo watchfaceInfo = DefaultComplicationWatchFaceInfo.INSTANCE; + private final BlazeAndroidRunConfigurationCommonState commonState; + // TODO: move activity launch here from top level properties. + private final Map wearLaunchOptions = new HashMap<>(); BlazeAndroidBinaryRunConfigurationState(String buildSystemName) { commonState = new BlazeAndroidRunConfigurationCommonState(buildSystemName); profilerState = new ProfilerState(); + buildSystem = buildSystemName; + + wearLaunchOptions.put(LAUNCH_TILE, new TileLaunchOptions()); + wearLaunchOptions.put(LAUNCH_COMPLICATION, new ComplicationLaunchOptions()); + wearLaunchOptions.put(LAUNCH_WATCHFACE, new WatchFaceLaunchOptions()); } public BlazeAndroidRunConfigurationCommonState getCommonState() { @@ -85,6 +113,21 @@ public AndroidBinaryLaunchMethod getLaunchMethod() { return launchMethod; } + /** Returns ComponentLaunchOptions if wear surface as chosen for launch otherwise returns null. */ + @Nullable + public ComponentLaunchOptions getCurrentWearLaunchOptions() { + return wearLaunchOptions.get(mode); + } + + /** Set Wear launch options corresponding to the current mode. */ + public void setWearLaunchOptions(ComponentLaunchOptions options) { + verify( + mode.equals(LAUNCH_COMPLICATION) + || mode.equals(LAUNCH_TILE) + || mode.equals(LAUNCH_WATCHFACE)); + wearLaunchOptions.put(mode, options); + } + @VisibleForTesting public void setLaunchMethod(AndroidBinaryLaunchMethod launchMethod) { this.launchMethod = launchMethod; @@ -162,6 +205,14 @@ public String getAmStartOptions() { return amStartOptions; } + public boolean getClearAppStorage() { + return clearAppStorage; + } + + public void setClearAppStorage(boolean clearAppStorage) { + this.clearAppStorage = clearAppStorage; + } + /** * We collect errors rather than throwing to avoid missing fatal errors by exiting early for a * warning. @@ -215,25 +266,18 @@ public void readExternal(Element element) throws InvalidDataException { setAmStartOptions(amStartOptionsString); } - for (Map.Entry entry : getLegacyValues(element).entrySet()) { - String value = entry.getValue(); - switch (entry.getKey()) { - case DEEP_LINK: - deepLink = Strings.nullToEmpty(value); - break; - case ACTIVITY_CLASS: - activityClass = Strings.nullToEmpty(value); - break; - case MODE: - mode = Strings.isNullOrEmpty(value) ? LAUNCH_DEFAULT_ACTIVITY : value; - break; - case ACTIVITY_EXTRA_FLAGS: - if (userId == null) { - userId = LaunchUtils.getUserIdFromFlags(value); - } - break; - default: - break; + setClearAppStorage(Boolean.parseBoolean(element.getAttributeValue(CLEAR_APP_STORAGE))); + + // Read wear launch options + Element launchOptionsElement = element.getChild(LAUNCH_OPTIONS_NAME); + if (launchOptionsElement != null) { + for (Map.Entry option : wearLaunchOptions.entrySet()) { + Element optionElement = launchOptionsElement.getChild(option.getKey()); + if (optionElement != null) { + XmlSerializer.deserializeInto(option.getValue(), optionElement); + } else { + throw new VerifyException("Missing launch option declaration " + option.getKey()); + } } } } @@ -244,7 +288,7 @@ public void writeExternal(Element element) throws WriteExternalException { // Group profiler settings under its own section. Previously written profiler info // are replaced manually because ProfilerState#writeExternal does not handle the removal - // process; unlike i.e, implementers of RunConfigurationState. + // process; unlike i.e., implementers of RunConfigurationState. Element profilersElement = new Element(PROFILERS_ELEMENT_NAME); element.removeChildren(PROFILERS_ELEMENT_NAME); element.addContent(profilersElement); @@ -258,23 +302,24 @@ public void writeExternal(Element element) throws WriteExternalException { element.setAttribute(WORK_PROFILE_ATTR, Boolean.toString(useWorkProfileIfPresent)); element.setAttribute(SHOW_LOGCAT_AUTOMATICALLY, Boolean.toString(showLogcatAutomatically)); element.setAttribute(AM_START_OPTIONS, amStartOptions); + element.setAttribute(CLEAR_APP_STORAGE, Boolean.toString(clearAppStorage)); if (userId != null) { element.setAttribute(USER_ID_ATTR, Integer.toString(userId)); } else { element.removeAttribute(USER_ID_ATTR); } - } - /** Imports legacy values in the old reflective JDOM externalizer manner. Can be removed ~2.0+. */ - private static Map getLegacyValues(Element element) { - Map result = Maps.newHashMap(); - for (Element option : element.getChildren("option")) { - String name = option.getAttributeValue("name"); - String value = option.getAttributeValue("value"); - result.put(name, value); + // Add wear launch options + Element launchOptionsElement = new Element(LAUNCH_OPTIONS_NAME); + element.removeChildren(LAUNCH_OPTIONS_NAME); + element.addContent(launchOptionsElement); + + for (Map.Entry option : wearLaunchOptions.entrySet()) { + Element optionElement = new Element(option.getKey()); + launchOptionsElement.addContent(optionElement); + XmlSerializer.serializeInto(option.getValue(), optionElement); } - return result; } @Override @@ -282,4 +327,32 @@ public RunConfigurationStateEditor getEditor(Project project) { return new BlazeAndroidBinaryRunConfigurationStateEditor( commonState.getEditor(project), new AndroidProfilersPanel(project, profilerState), project); } + + // Create a deep copy of BlazeAndroidBinaryRunConfigurationState. + @Override + public BlazeAndroidBinaryRunConfigurationState clone() { + BlazeAndroidBinaryRunConfigurationState clone = + new BlazeAndroidBinaryRunConfigurationState(buildSystem); + + clone.launchMethod = launchMethod; + clone.profilerState = profilerState; + clone.deepLink = deepLink; + clone.activityClass = activityClass; + clone.mode = mode; + clone.useSplitApksIfPossible = useSplitApksIfPossible; + clone.useWorkProfileIfPresent = useWorkProfileIfPresent; + clone.userId = userId; + clone.showLogcatAutomatically = showLogcatAutomatically; + clone.amStartOptions = amStartOptions; + clone.clearAppStorage = clearAppStorage; + clone.wearLaunchOptions.put( + LAUNCH_TILE, ((TileLaunchOptions) wearLaunchOptions.get(LAUNCH_TILE)).clone()); + clone.wearLaunchOptions.put( + LAUNCH_COMPLICATION, + ((ComplicationLaunchOptions) wearLaunchOptions.get(LAUNCH_COMPLICATION)).clone()); + clone.wearLaunchOptions.put( + LAUNCH_WATCHFACE, + ((WatchFaceLaunchOptions) wearLaunchOptions.get(LAUNCH_WATCHFACE)).clone()); + return clone; + } } diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/DeploymentTimingReporterTask.java b/aswb/src/com/google/idea/blaze/android/run/binary/DeploymentTimingReporterTask.java deleted file mode 100644 index 74f2cb14835..00000000000 --- a/aswb/src/com/google/idea/blaze/android/run/binary/DeploymentTimingReporterTask.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2021 The Bazel Authors. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.idea.blaze.android.run.binary; - -import com.android.tools.idea.run.ApkInfo; -import com.android.tools.idea.run.tasks.LaunchContext; -import com.android.tools.idea.run.tasks.LaunchResult; -import com.android.tools.idea.run.tasks.LaunchTask; -import com.google.common.base.Stopwatch; -import com.google.idea.blaze.android.run.LaunchMetrics; -import com.google.wireless.android.sdk.stats.LaunchTaskDetail; -import java.util.Collection; -import org.jetbrains.annotations.TestOnly; - -/** A wrapper launch task that wraps the given deployment task and logs the deployment latency. */ -public class DeploymentTimingReporterTask implements LaunchTask { - private final LaunchTask deployTask; - private final String launchId; - - public DeploymentTimingReporterTask(String launchId, LaunchTask deployTask) { - this.launchId = launchId; - this.deployTask = deployTask; - } - - @Override - public String getDescription() { - return deployTask.getDescription(); - } - - @Override - public int getDuration() { - return deployTask.getDuration(); - } - - @Override - public boolean shouldRun(LaunchContext launchContext) { - return deployTask.shouldRun(launchContext); - } - - @Override - public LaunchResult run(LaunchContext launchContext) { - Stopwatch s = Stopwatch.createStarted(); - LaunchResult launchResult = deployTask.run(launchContext); - LaunchMetrics.logDeploymentTime(launchId, s.elapsed(), launchResult); - return launchResult; - } - - @Override - public String getId() { - return deployTask.getId(); - } - - @Override - public Collection getApkInfos() { - return deployTask.getApkInfos(); - } - - @Override - public Collection getSubTaskDetails() { - return deployTask.getSubTaskDetails(); - } - - @TestOnly - public LaunchTask getWrappedTask() { - return deployTask; - } -} diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/UserIdHelper.java b/aswb/src/com/google/idea/blaze/android/run/binary/UserIdHelper.java index 4ed8326b0c3..ab71d4747c2 100644 --- a/aswb/src/com/google/idea/blaze/android/run/binary/UserIdHelper.java +++ b/aswb/src/com/google/idea/blaze/android/run/binary/UserIdHelper.java @@ -20,8 +20,9 @@ import com.android.ddmlib.IDevice; import com.android.ddmlib.ShellCommandUnresponsiveException; import com.android.ddmlib.TimeoutException; -import com.android.tools.idea.run.ConsolePrinter; +import com.android.tools.idea.execution.common.RunConfigurationNotifier; import com.intellij.execution.ExecutionException; +import com.intellij.openapi.project.Project; import java.io.IOException; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -29,19 +30,23 @@ /** Helpers for user id */ public final class UserIdHelper { + private UserIdHelper() {} + private static final Pattern USER_ID_REGEX = Pattern.compile("UserInfo\\{([0-9]+):Work profile:[0-9]+\\}"); @Nullable public static Integer getUserIdFromConfigurationState( - IDevice device, ConsolePrinter consolePrinter, BlazeAndroidBinaryRunConfigurationState state) + Project project, IDevice device, BlazeAndroidBinaryRunConfigurationState state) throws ExecutionException { if (state.useWorkProfileIfPresent()) { try { Integer userId = getWorkProfileId(device); if (userId == null) { - consolePrinter.stderr( - "Could not locate work profile on selected device. Launching default user.\n"); + RunConfigurationNotifier.INSTANCE.notifyWarning( + project, + "", + "Could not locate work profile on selected device. Launching default user."); } return userId; } catch (TimeoutException diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/mobileinstall/MobileInstallBuildStep.java b/aswb/src/com/google/idea/blaze/android/run/binary/mobileinstall/MobileInstallBuildStep.java index c21f4357b06..4c8c464eba0 100644 --- a/aswb/src/com/google/idea/blaze/android/run/binary/mobileinstall/MobileInstallBuildStep.java +++ b/aswb/src/com/google/idea/blaze/android/run/binary/mobileinstall/MobileInstallBuildStep.java @@ -23,6 +23,7 @@ import com.android.tools.idea.run.DeviceFutures; import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -151,7 +152,9 @@ public void build(BlazeContext context, BlazeAndroidDeviceSelector.DeviceSession } BuildInvoker invoker = - Blaze.getBuildSystemProvider(project).getBuildSystem().getBuildInvoker(project, context); + Blaze.getBuildSystemProvider(project) + .getBuildSystem() + .getBuildInvoker(project, context, BlazeCommandName.MOBILE_INSTALL); BlazeCommand.Builder command = BlazeCommand.builder(invoker, BlazeCommandName.MOBILE_INSTALL); if (passAdbArgWithSerialToMi.getValue()) { @@ -167,8 +170,8 @@ public void build(BlazeContext context, BlazeAndroidDeviceSelector.DeviceSession } WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProject(project); - final String deployInfoSuffix = getDeployInfoSuffix(Blaze.getBuildSystemName(project)); - + BuildSystemName buildSystemName = Blaze.getBuildSystemName(project); + String deployInfoSuffix = getDeployInfoSuffix(buildSystemName); try (BuildResultHelper buildResultHelper = invoker.createBuildResultHelper(); AdbTunnelConfigurator tunnelConfig = getTunnelConfigurator(context)) { tunnelConfig.setupConnection(context); @@ -185,7 +188,8 @@ public void build(BlazeContext context, BlazeAndroidDeviceSelector.DeviceSession + " to the default adb server.") .submit(context); } else { - deviceFlag += ":tcp:" + adbAddr.getPort(); + command.addBlazeFlags( + BlazeFlags.ADB_ARG + "-P ", BlazeFlags.ADB_ARG + adbAddr.getPort()); } } command.addBlazeFlags(BlazeFlags.DEVICE, deviceFlag); @@ -195,9 +199,12 @@ public void build(BlazeContext context, BlazeAndroidDeviceSelector.DeviceSession .addTargets(label) .addBlazeFlags(blazeFlags) .addBlazeFlags(buildResultHelper.getBuildFlags()) - .addExeFlags(exeFlags) - // MI launches apps by default. Defer app launch to BlazeAndroidLaunchTasksProvider. - .addExeFlags("--nolaunch_app"); + .addExeFlags(exeFlags); + + if (buildSystemName == BuildSystemName.Blaze) { + // MI launches apps by default. Defer app launch to BlazeAndroidLaunchTasksProvider. + command.addExeFlags("--nolaunch_app"); + } if (StudioDeployerExperiment.isEnabled()) { command.addExeFlags("--nodeploy"); @@ -217,7 +224,8 @@ public void build(BlazeContext context, BlazeAndroidDeviceSelector.DeviceSession Stopwatch s = Stopwatch.createStarted(); int exitCode = task.run(); - logBuildTime(launchId, StudioDeployerExperiment.isEnabled(), s.elapsed(), exitCode); + logBuildTime( + launchId, StudioDeployerExperiment.isEnabled(), s.elapsed(), exitCode, ImmutableMap.of()); if (exitCode != 0) { IssueOutput.error("Blaze build failed. See Blaze Console for details.").submit(context); @@ -229,8 +237,7 @@ public void build(BlazeContext context, BlazeAndroidDeviceSelector.DeviceSession project, context, BlazeBuildOutputs.noOutputs(BuildResult.fromExitCode(exitCode))); context.output(new StatusOutput("Reading deployment information...")); - String executionRoot = - ExecRootUtil.getExecutionRoot(buildResultHelper, project, blazeFlags, context); + String executionRoot = ExecRootUtil.getExecutionRoot(invoker, context); if (executionRoot == null) { IssueOutput.error("Could not locate execroot!").submit(context); return; @@ -258,6 +265,11 @@ public void build(BlazeContext context, BlazeAndroidDeviceSelector.DeviceSession } } + @Override + public boolean needsIdeDeploy() { + return StudioDeployerExperiment.isEnabled(); + } + @Override public BlazeAndroidDeployInfo getDeployInfo() throws ApkProvisionException { if (deployInfo != null) { diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/mobileinstall/StudioDeployerExperiment.java b/aswb/src/com/google/idea/blaze/android/run/binary/mobileinstall/StudioDeployerExperiment.java index d2fbde40b00..2bb6d36d1cd 100644 --- a/aswb/src/com/google/idea/blaze/android/run/binary/mobileinstall/StudioDeployerExperiment.java +++ b/aswb/src/com/google/idea/blaze/android/run/binary/mobileinstall/StudioDeployerExperiment.java @@ -16,6 +16,7 @@ package com.google.idea.blaze.android.run.binary.mobileinstall; import com.google.idea.common.experiments.FeatureRolloutExperiment; +import com.intellij.openapi.util.SystemInfo; /** * A utility class that manages the experiment to use studio's built in deployer for deploying apks @@ -24,10 +25,16 @@ public class StudioDeployerExperiment { /** Indicates if we should deploy via Studio or via MI. */ private static final FeatureRolloutExperiment useStudioDeployer = - new FeatureRolloutExperiment("aswb.use.studio.deployer"); + new FeatureRolloutExperiment("aswb.use.studio.deployer.2"); /** Returns whether mobile install deployments should happen via the studio deployer. */ public static boolean isEnabled() { + // The Studio deployer experiment is specific to local builds on Linux. For other platforms, + // we'll rely entirely on the new Blaze specific deployment flow. + if (!SystemInfo.isLinux) { + return false; + } + return useStudioDeployer.isEnabled(); } diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/tasks/ActivityLaunchTask.java b/aswb/src/com/google/idea/blaze/android/run/binary/tasks/ActivityLaunchTask.java new file mode 100644 index 00000000000..1e7cdad4947 --- /dev/null +++ b/aswb/src/com/google/idea/blaze/android/run/binary/tasks/ActivityLaunchTask.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.tasks; + +import com.android.ddmlib.CollectingOutputReceiver; +import com.android.ddmlib.IDevice; +import com.android.tools.idea.execution.common.AndroidExecutionException; +import com.android.tools.idea.run.activity.AndroidActivityLauncher; +import com.android.tools.idea.run.activity.StartActivityFlagsProvider; +import com.android.tools.idea.run.configuration.execution.ExecutionUtils; +import com.google.common.annotations.VisibleForTesting; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.ui.ConsoleView; +import com.intellij.openapi.progress.ProgressIndicator; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** Provides the launch task for activity */ +public abstract class ActivityLaunchTask { + @VisibleForTesting static final String ACTIVITY_DOES_NOT_EXIST = "ACTIVITY_DOES_NOT_EXIST"; + + @VisibleForTesting + static final String UNABLE_TO_DETERMINE_LAUNCH_ACTIVITY = "UNABLE_TO_DETERMINE_LAUNCH_ACTIVITY"; + + private static final String ACTIVITY_DOES_NOT_EXIST_REGEX = + "Activity class \\{[^}]*} does not exist"; + private static final Pattern activityDoesNotExistPattern = + Pattern.compile(ACTIVITY_DOES_NOT_EXIST_REGEX); + @NotNull private final String myApplicationId; + @NotNull private final StartActivityFlagsProvider myStartActivityFlagsProvider; + + public ActivityLaunchTask( + @NotNull String applicationId, + @NotNull StartActivityFlagsProvider startActivityFlagsProvider) { + myApplicationId = applicationId; + myStartActivityFlagsProvider = startActivityFlagsProvider; + } + + @VisibleForTesting + public String getStartActivityCommand(@NotNull IDevice device) throws ExecutionException { + String activityName = getQualifiedActivityName(device); + if (activityName == null) { + throw new AndroidExecutionException( + UNABLE_TO_DETERMINE_LAUNCH_ACTIVITY, "Unable to determine activity name"); + } + String activityPath = + AndroidActivityLauncher.getLauncherActivityPath(myApplicationId, activityName); + return AndroidActivityLauncher.getStartActivityCommand( + activityPath, myStartActivityFlagsProvider.getFlags(device)); + } + + @Nullable + protected abstract String getQualifiedActivityName(@NotNull IDevice device); + + public void run(IDevice device, ProgressIndicator progressIndicator, ConsoleView consoleView) + throws ExecutionException { + String command = getStartActivityCommand(device); + CollectingOutputReceiver collectingOutputReceiver = new CollectingOutputReceiver(); + + executeShellCommand(consoleView, device, command, collectingOutputReceiver, progressIndicator); + final Matcher matcher = + activityDoesNotExistPattern.matcher(collectingOutputReceiver.getOutput()); + if (matcher.find()) { + throw new AndroidExecutionException(ACTIVITY_DOES_NOT_EXIST, matcher.group()); + } + } + + @VisibleForTesting + protected void executeShellCommand( + ConsoleView console, + IDevice device, + String command, + CollectingOutputReceiver collectingOutputReceiver, + ProgressIndicator progressIndicator) + throws ExecutionException { + // The timeout is quite large to accommodate ARM emulators. + ExecutionUtils.executeShellCommand( + device, + command, + console, + collectingOutputReceiver, + 15, + TimeUnit.SECONDS, + progressIndicator); + } +} diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/tasks/AndroidDeepLinkLaunchTask.java b/aswb/src/com/google/idea/blaze/android/run/binary/tasks/AndroidDeepLinkLaunchTask.java new file mode 100644 index 00000000000..da6f81e83de --- /dev/null +++ b/aswb/src/com/google/idea/blaze/android/run/binary/tasks/AndroidDeepLinkLaunchTask.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.tasks; + +import com.android.ddmlib.IDevice; +import com.android.tools.analytics.UsageTracker; +import com.android.tools.idea.run.activity.StartActivityFlagsProvider; +import com.android.tools.idea.run.configuration.execution.ExecutionUtils; +import com.google.wireless.android.sdk.stats.AndroidStudioEvent; +import com.google.wireless.android.sdk.stats.AndroidStudioEvent.EventCategory; +import com.google.wireless.android.sdk.stats.AndroidStudioEvent.EventKind; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.ui.ConsoleView; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.ProgressIndicator; +import org.jetbrains.annotations.NotNull; + +/** Provides the launch task for android_binary */ +public class AndroidDeepLinkLaunchTask { + + private static final String ID = "LAUNCH_DEEP_LINK"; + + @NotNull private final String myDeepLink; + @NotNull StartActivityFlagsProvider myStartActivityFlagsProvider; + + public AndroidDeepLinkLaunchTask( + @NotNull String deepLink, @NotNull StartActivityFlagsProvider startActivityFlagsProvider) { + myDeepLink = deepLink; + myStartActivityFlagsProvider = startActivityFlagsProvider; + } + + public void run(IDevice device, ProgressIndicator progressIndicator, ConsoleView consoleView) + throws ExecutionException { + + final String text = "Launching deeplink: " + myDeepLink + ".\n"; + ExecutionUtils.println(consoleView, "Launching deeplink: " + myDeepLink + ".\n"); + Logger.getInstance(this.getClass()).info(text); + + UsageTracker.log( + AndroidStudioEvent.newBuilder() + .setCategory(EventCategory.APP_INDEXING) + .setKind(EventKind.APP_INDEXING_DEEP_LINK_LAUNCHED)); + // Enable AppIndexing API log + ExecutionUtils.executeShellCommand( + device, "setprop log.tag.AppIndexApi VERBOSE", consoleView, progressIndicator); + + // Launch deeplink + String command = + getLaunchDeepLinkCommand(myDeepLink, myStartActivityFlagsProvider.getFlags(device)); + ExecutionUtils.executeShellCommand(device, command, consoleView, progressIndicator); + } + + @NotNull + public static String getLaunchDeepLinkCommand( + @NotNull String deepLink, @NotNull String extraFlags) { + return "am start" + + " -a android.intent.action.VIEW" + + " -c android.intent.category.BROWSABLE" + + " -d " + + singleQuoteShell(deepLink) + + (extraFlags.isEmpty() ? "" : " " + extraFlags); + } + + @NotNull + private static String singleQuoteShell(@NotNull String literal) { + return "'" + literal.replace("'", "'\\''") + "'"; + } +} diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/tasks/BlazeDefaultActivityLaunchTask.java b/aswb/src/com/google/idea/blaze/android/run/binary/tasks/BlazeDefaultActivityLaunchTask.java new file mode 100644 index 00000000000..3b6bc7d811f --- /dev/null +++ b/aswb/src/com/google/idea/blaze/android/run/binary/tasks/BlazeDefaultActivityLaunchTask.java @@ -0,0 +1,47 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.tasks; + +import com.android.tools.idea.run.activity.ActivityLocator; +import com.android.tools.idea.run.activity.StartActivityFlagsProvider; +import com.android.tools.idea.run.blaze.BlazeLaunchContext; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; +import com.intellij.execution.ExecutionException; + +/** The {@code BlazeLaunchTask} that run a {@code DefaultActivityLaunchTask}. */ +public class BlazeDefaultActivityLaunchTask implements BlazeLaunchTask { + private final DefaultActivityLaunchTask defaultActivityLaunchTask; + + public BlazeDefaultActivityLaunchTask( + String applicationId, + ActivityLocator activityLocator, + StartActivityFlagsProvider startActivityFlagsProvider) { + defaultActivityLaunchTask = + new DefaultActivityLaunchTask(applicationId, activityLocator, startActivityFlagsProvider); + } + + public DefaultActivityLaunchTask getDefaultActivityLaunchTask() { + return defaultActivityLaunchTask; + } + + @Override + public void run(BlazeLaunchContext blazeLaunchContext) throws ExecutionException { + defaultActivityLaunchTask.run( + blazeLaunchContext.getDevice(), + blazeLaunchContext.getProgressIndicator(), + blazeLaunchContext.getConsoleView()); + } +} diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/tasks/DefaultActivityLaunchTask.java b/aswb/src/com/google/idea/blaze/android/run/binary/tasks/DefaultActivityLaunchTask.java new file mode 100644 index 00000000000..306327473d1 --- /dev/null +++ b/aswb/src/com/google/idea/blaze/android/run/binary/tasks/DefaultActivityLaunchTask.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.tasks; + +import com.android.ddmlib.IDevice; +import com.android.tools.idea.run.activity.ActivityLocator; +import com.android.tools.idea.run.activity.StartActivityFlagsProvider; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** Provides the launch task for activity */ +public class DefaultActivityLaunchTask extends ActivityLaunchTask { + private static final String ID = "DEFAULT_ACTIVITY"; + + @NotNull private final ActivityLocator myActivityLocator; + + public DefaultActivityLaunchTask( + @NotNull String applicationId, + @NotNull ActivityLocator activityLocator, + @NotNull StartActivityFlagsProvider startActivityFlagsProvider) { + super(applicationId, startActivityFlagsProvider); + myActivityLocator = activityLocator; + } + + @Nullable + @Override + protected String getQualifiedActivityName(@NotNull IDevice device) { + try { + return myActivityLocator.getQualifiedActivityName(device); + } catch (ActivityLocator.ActivityLocatorException e) { + return null; + } + } +} diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/tasks/SpecificActivityLaunchTask.java b/aswb/src/com/google/idea/blaze/android/run/binary/tasks/SpecificActivityLaunchTask.java new file mode 100644 index 00000000000..264ec5ea44d --- /dev/null +++ b/aswb/src/com/google/idea/blaze/android/run/binary/tasks/SpecificActivityLaunchTask.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.tasks; + +import com.android.ddmlib.IDevice; +import com.android.tools.idea.run.activity.StartActivityFlagsProvider; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** Provides the launch task for activity */ +public class SpecificActivityLaunchTask extends ActivityLaunchTask { + @NotNull private final String myActivity; + + public SpecificActivityLaunchTask( + @NotNull String applicationId, + @NotNull String activity, + @NotNull StartActivityFlagsProvider startActivityFlagsProvider) { + super(applicationId, startActivityFlagsProvider); + myActivity = activity; + } + + @Nullable + @Override + protected String getQualifiedActivityName(@NotNull IDevice device) { + return myActivity; + } +} diff --git a/aswb/src/com/google/idea/blaze/android/run/deployinfo/BlazeAndroidDeployInfo.java b/aswb/src/com/google/idea/blaze/android/run/deployinfo/BlazeAndroidDeployInfo.java index e84150454d6..83d6bb85721 100644 --- a/aswb/src/com/google/idea/blaze/android/run/deployinfo/BlazeAndroidDeployInfo.java +++ b/aswb/src/com/google/idea/blaze/android/run/deployinfo/BlazeAndroidDeployInfo.java @@ -26,6 +26,7 @@ public class BlazeAndroidDeployInfo { private final ParsedManifest mergedManifest; @Nullable private final ParsedManifest testTargetMergedManifest; private final ImmutableList apksToDeploy; + private final ImmutableList symbolFiles; /** * Note: Not every deployment has a test target, so {@param testTargetMergedManifest} can be null. @@ -34,9 +35,18 @@ public BlazeAndroidDeployInfo( ParsedManifest mergedManifest, @Nullable ParsedManifest testTargetMergedManifest, ImmutableList apksToDeploy) { + this(mergedManifest, testTargetMergedManifest, apksToDeploy, ImmutableList.of()); + } + + public BlazeAndroidDeployInfo( + ParsedManifest mergedManifest, + @Nullable ParsedManifest testTargetMergedManifest, + ImmutableList apksToDeploy, + ImmutableList symbolFiles) { this.mergedManifest = mergedManifest; this.testTargetMergedManifest = testTargetMergedManifest; this.apksToDeploy = apksToDeploy; + this.symbolFiles = symbolFiles; } /** @@ -63,4 +73,9 @@ public ManifestParser.ParsedManifest getTestTargetMergedManifest() { public ImmutableList getApksToDeploy() { return apksToDeploy; } + + /** Returns the full list of C++ symbol files to provide to LLDB to symbolize debugging. */ + public ImmutableList getSymbolFiles() { + return symbolFiles; + } } 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 a6525e395a2..88c62b3ee64 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 @@ -21,16 +21,15 @@ import com.google.devtools.build.lib.rules.android.deployinfo.AndroidDeployInfoOuterClass.Artifact; import com.google.idea.blaze.android.manifest.ManifestParser.ParsedManifest; import com.google.idea.blaze.android.manifest.ParsedManifestService; -import com.google.idea.blaze.base.command.buildresult.BlazeArtifact; import com.google.idea.blaze.base.command.buildresult.BuildResultHelper; import com.google.idea.blaze.base.command.buildresult.BuildResultHelper.GetArtifactsException; -import com.google.idea.blaze.base.command.buildresult.OutputArtifact; +import com.google.idea.blaze.base.command.buildresult.LocalFileArtifact; import com.google.idea.blaze.base.command.buildresult.ParsedBepOutput; import com.google.idea.blaze.base.model.primitives.Label; +import com.google.idea.blaze.common.artifact.OutputArtifact; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.List; @@ -46,28 +45,26 @@ public class BlazeApkDeployInfoProtoHelper { public AndroidDeployInfo readDeployInfoProtoForTarget( Label target, BuildResultHelper buildResultHelper, Predicate pathFilter) throws GetDeployInfoException { - ImmutableList deployInfoFiles; + ImmutableList outputArtifacts; try { - deployInfoFiles = - BlazeArtifact.getLocalFiles( - buildResultHelper.getBuildArtifactsForTarget(target, pathFilter)); + outputArtifacts = buildResultHelper.getBuildArtifactsForTarget(target, pathFilter); } catch (GetArtifactsException e) { throw new GetDeployInfoException(e.getMessage()); } - if (deployInfoFiles.isEmpty()) { + if (outputArtifacts.isEmpty()) { Logger log = Logger.getInstance(BlazeApkDeployInfoProtoHelper.class.getName()); try { ParsedBepOutput bepOutput = buildResultHelper.getBuildOutput(); log.warn("Local execroot: " + bepOutput.getLocalExecRoot()); log.warn("All output artifacts:"); for (OutputArtifact outputArtifact : bepOutput.getAllOutputArtifacts(path -> true)) { - log.warn(outputArtifact.getKey() + " -> " + outputArtifact.getRelativePath()); + log.warn(outputArtifact.getRelativePath() + " -> " + outputArtifact.getRelativePath()); } log.warn("All local artifacts for " + target + ":"); List allBuildArtifacts = buildResultHelper.getBuildArtifactsForTarget(target, path -> true); - List allLocalFiles = BlazeArtifact.getLocalFiles(allBuildArtifacts); + List allLocalFiles = LocalFileArtifact.getLocalFiles(allBuildArtifacts); for (File file : allLocalFiles) { String path = file.getPath(); log.warn(path); @@ -82,15 +79,15 @@ public AndroidDeployInfo readDeployInfoProtoForTarget( "No deploy info proto artifact found. Was android_deploy_info in the output groups?"); } - if (deployInfoFiles.size() > 1) { + if (outputArtifacts.size() > 1) { throw new GetDeployInfoException( "More than one deploy info proto artifact found: " - + deployInfoFiles.stream() - .map(File::getPath) + + outputArtifacts.stream() + .map(OutputArtifact::getRelativePath) .collect(Collectors.joining(", ", "[", "]"))); } - try (InputStream inputStream = new FileInputStream(deployInfoFiles.get(0))) { + try (InputStream inputStream = outputArtifacts.get(0).getInputStream()) { return AndroidDeployInfo.parseFrom(inputStream); } catch (IOException e) { throw new GetDeployInfoException(e.getMessage()); @@ -100,6 +97,16 @@ public AndroidDeployInfo readDeployInfoProtoForTarget( public BlazeAndroidDeployInfo extractDeployInfoAndInvalidateManifests( Project project, File executionRoot, AndroidDeployInfo deployInfoProto) throws GetDeployInfoException { + return extractDeployInfoAndInvalidateManifests( + project, executionRoot, deployInfoProto, ImmutableList.of()); + } + + public BlazeAndroidDeployInfo extractDeployInfoAndInvalidateManifests( + Project project, + File executionRoot, + AndroidDeployInfo deployInfoProto, + ImmutableList symbolFiles) + throws GetDeployInfoException { File mergedManifestFile = new File(executionRoot, deployInfoProto.getMergedManifest().getExecRootPath()); ParsedManifest mergedManifest = getParsedManifestSafe(project, mergedManifestFile); @@ -122,7 +129,8 @@ public BlazeAndroidDeployInfo extractDeployInfoAndInvalidateManifests( .map(artifact -> new File(executionRoot, artifact.getExecRootPath())) .collect(ImmutableList.toImmutableList()); - return new BlazeAndroidDeployInfo(mergedManifest, testTargetMergedManifest, apksToDeploy); + return new BlazeAndroidDeployInfo( + mergedManifest, testTargetMergedManifest, apksToDeploy, symbolFiles); } public BlazeAndroidDeployInfo extractInstrumentationTestDeployInfoAndInvalidateManifests( diff --git a/aswb/src/com/google/idea/blaze/android/run/deployinfo/BlazeApkProviderService.java b/aswb/src/com/google/idea/blaze/android/run/deployinfo/BlazeApkProviderService.java index 1683c989eef..e89caf6c251 100644 --- a/aswb/src/com/google/idea/blaze/android/run/deployinfo/BlazeApkProviderService.java +++ b/aswb/src/com/google/idea/blaze/android/run/deployinfo/BlazeApkProviderService.java @@ -17,13 +17,13 @@ import com.android.tools.idea.run.ApkProvider; import com.google.idea.blaze.android.run.runner.ApkBuildStep; -import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; /** Service to provide ApkProviders that source APKs from {@link ApkBuildStep}. */ public interface BlazeApkProviderService { static BlazeApkProviderService getInstance() { - return ServiceManager.getService(BlazeApkProviderService.class); + return ApplicationManager.getApplication().getService(BlazeApkProviderService.class); } /** Returns an APK provider that sources APKs from the given {@link ApkBuildStep}. */ diff --git a/aswb/src/com/google/idea/blaze/android/run/runner/AaptUtil.java b/aswb/src/com/google/idea/blaze/android/run/runner/AaptUtil.java index ad869080d84..6a9f2f3f96f 100644 --- a/aswb/src/com/google/idea/blaze/android/run/runner/AaptUtil.java +++ b/aswb/src/com/google/idea/blaze/android/run/runner/AaptUtil.java @@ -30,7 +30,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.Nullable; -import org.jetbrains.android.sdk.AndroidPlatform; +import org.jetbrains.android.sdk.AndroidPlatformCompat; /** A collection of utilities for extracting information from APKs using aapt. */ public final class AaptUtil { @@ -69,12 +69,12 @@ private static MatchResult getAaptBadging(Project project, File apk, Pattern pat if (!apk.exists()) { throw new AaptUtilException("apk file does not exist: " + apk); } - AndroidPlatform androidPlatform = SdkUtil.getAndroidPlatform(project); + AndroidPlatformCompat androidPlatform = SdkUtil.getAndroidPlatform(project); if (androidPlatform == null) { throw new AaptUtilException( "Could not find Android platform sdk for project " + project.getName()); } - BuildToolInfo toolInfo = androidPlatform.getSdkData().getLatestBuildTool(true); + BuildToolInfo toolInfo = androidPlatform.getLatestBuildTool(true); if (toolInfo == null) { throw new AaptUtilException( "Could not find Android sdk build-tools for project " + project.getName()); diff --git a/aswb/src/com/google/idea/blaze/android/run/runner/ApkBuildStep.java b/aswb/src/com/google/idea/blaze/android/run/runner/ApkBuildStep.java index dd515fa70f4..ce7a8cb8a04 100644 --- a/aswb/src/com/google/idea/blaze/android/run/runner/ApkBuildStep.java +++ b/aswb/src/com/google/idea/blaze/android/run/runner/ApkBuildStep.java @@ -27,5 +27,13 @@ public interface ApkBuildStep { */ void build(BlazeContext context, BlazeAndroidDeviceSelector.DeviceSession deviceSession); + /** + * Returns whether the IDE should deploy the artifacts of the build. This is true in most cases + * except for mobile-install where the build itself does the deploy. + */ + default boolean needsIdeDeploy() { + return true; + } + BlazeAndroidDeployInfo getDeployInfo() throws ApkProvisionException; } diff --git a/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidDeviceSelector.java b/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidDeviceSelector.java index c8ed4d98717..b4fdcf0fd33 100644 --- a/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidDeviceSelector.java +++ b/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidDeviceSelector.java @@ -13,158 +13,63 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.google.idea.blaze.android.run.runner; -import com.android.tools.idea.run.AndroidSessionInfo; +import com.android.tools.idea.execution.common.AndroidSessionInfo; import com.android.tools.idea.run.DeviceFutures; import com.android.tools.idea.run.editor.DeployTarget; +import com.google.common.annotations.VisibleForTesting; import com.intellij.execution.ExecutionException; import com.intellij.execution.Executor; import com.intellij.execution.runners.ExecutionEnvironment; -import com.intellij.icons.AllIcons; -import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.ui.Messages; import javax.annotation.Nullable; -import org.jetbrains.android.facet.AndroidFacet; /** Selects a device. */ public interface BlazeAndroidDeviceSelector { - /** A device session */ class DeviceSession { @Nullable public final DeployTarget deployTarget; @Nullable public final DeviceFutures deviceFutures; - @Nullable public final AndroidSessionInfo sessionInfo; + public DeviceSession( + @Nullable DeployTarget deployTarget, @Nullable DeviceFutures deviceFutures) { + this.deployTarget = deployTarget; + this.deviceFutures = deviceFutures; + } + + // Only for back compat + @VisibleForTesting public DeviceSession( @Nullable DeployTarget deployTarget, @Nullable DeviceFutures deviceFutures, @Nullable AndroidSessionInfo sessionInfo) { - this.deployTarget = deployTarget; - this.deviceFutures = deviceFutures; - this.sessionInfo = sessionInfo; + this(deployTarget, deviceFutures); } } DeviceSession getDevice( - Project project, - AndroidFacet facet, - Executor executor, - ExecutionEnvironment env, - AndroidSessionInfo info, - boolean debug, - int runConfigId) + Project project, Executor executor, ExecutionEnvironment env, boolean debug, int runConfigId) throws ExecutionException; - /** Standard device selector */ class NormalDeviceSelector implements BlazeAndroidDeviceSelector { - - private static final DialogWrapper.DoNotAskOption ourKillLaunchOption = - new KillLaunchDialogOption(); - private static final Logger LOG = Logger.getInstance(NormalDeviceSelector.class); - - static class KillLaunchDialogOption implements DialogWrapper.DoNotAskOption { - private boolean show; - - @Override - public boolean isToBeShown() { - return !show; - } - - @Override - public void setToBeShown(boolean toBeShown, int exitCode) { - show = !toBeShown; - } - - @Override - public boolean canBeHidden() { - return true; - } - - @Override - public boolean shouldSaveOptionsOnCancel() { - return true; - } - - @Override - public String getDoNotShowMessage() { - return "Do not ask again"; - } - } - @Override public DeviceSession getDevice( Project project, - AndroidFacet facet, Executor executor, ExecutionEnvironment env, - AndroidSessionInfo info, boolean debug, - int runConfigId) - throws ExecutionException { - // If there is an existing session, then terminate those sessions - if (info != null) { - boolean continueLaunch = promptAndKillSession(executor, project, info); - if (!continueLaunch) { - return null; - } - } - + int runConfigId) { DeployTarget deployTarget = BlazeDeployTargetService.getInstance(project).getDeployTarget(); if (deployTarget == null) { return null; } - DeviceFutures deviceFutures = null; if (!deployTarget.hasCustomRunProfileState(executor)) { - deviceFutures = deployTarget.getDevices(facet); - } - return new DeviceSession(deployTarget, deviceFutures, info); - } - - private boolean promptAndKillSession( - Executor executor, Project project, AndroidSessionInfo info) { - String previousExecutor = info.getExecutorId(); - String currentExecutor = executor.getId(); - - if (ourKillLaunchOption.isToBeShown()) { - String msg; - String noText; - if (previousExecutor.equals(currentExecutor)) { - msg = - String.format( - "Restart App?\nThe app is already running. " - + "Would you like to kill it and restart the session?"); - noText = "Cancel"; - } else { - msg = - String.format( - "To switch from %1$s to %2$s, the app has to restart. Continue?", - previousExecutor, currentExecutor); - noText = "Cancel " + currentExecutor; - } - - String targetName = info.getExecutionTarget().getDisplayName(); - String title = "Launching " + targetName; - String yesText = "Restart " + targetName; - if (Messages.NO - == Messages.showYesNoDialog( - project, - msg, - title, - yesText, - noText, - AllIcons.General.QuestionDialog, - ourKillLaunchOption)) { - return false; - } + deviceFutures = deployTarget.getDevices(project); } - - LOG.info("Disconnecting existing session of the same launch configuration"); - info.getProcessHandler().detachProcess(); - return true; + return new DeviceSession(deployTarget, deviceFutures); } } } diff --git a/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidOpenProfilerWindowTask.java b/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidOpenProfilerWindowTask.java index 47a02f53f2c..b3941316668 100644 --- a/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidOpenProfilerWindowTask.java +++ b/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidOpenProfilerWindowTask.java @@ -16,15 +16,13 @@ package com.google.idea.blaze.android.run.runner; import com.android.tools.idea.profilers.ProfilerProgramRunner; -import com.android.tools.idea.run.tasks.LaunchContext; -import com.android.tools.idea.run.tasks.LaunchResult; -import com.android.tools.idea.run.tasks.LaunchTask; -import com.android.tools.idea.run.tasks.LaunchTaskDurations; +import com.android.tools.idea.run.blaze.BlazeLaunchContext; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; /** Opens the profiler tool window. */ -public class BlazeAndroidOpenProfilerWindowTask implements LaunchTask { +public class BlazeAndroidOpenProfilerWindowTask implements BlazeLaunchTask { private static final String ID = "OPEN_PROFILER_TOOLWINDOW"; private final Project project; @@ -33,24 +31,8 @@ public BlazeAndroidOpenProfilerWindowTask(Project project) { } @Override - public String getDescription() { - return "Open the Profiler Tool Window"; - } - - @Override - public int getDuration() { - return LaunchTaskDurations.LAUNCH_ACTIVITY; - } - - @Override - public String getId() { - return ID; - } - - @Override - public LaunchResult run(LaunchContext launchContext) { + public void run(BlazeLaunchContext launchContext) { ApplicationManager.getApplication() .invokeLater(() -> ProfilerProgramRunner.createProfilerToolWindow(project, null)); - return LaunchResult.success(); } } diff --git a/aswb/src/com/google/idea/blaze/android/run/runner/BlazeDeployTargetService.java b/aswb/src/com/google/idea/blaze/android/run/runner/BlazeDeployTargetService.java index bcacf42ffcb..3b1133bb1d2 100644 --- a/aswb/src/com/google/idea/blaze/android/run/runner/BlazeDeployTargetService.java +++ b/aswb/src/com/google/idea/blaze/android/run/runner/BlazeDeployTargetService.java @@ -20,13 +20,12 @@ import com.android.tools.idea.run.TargetSelectionMode; import com.android.tools.idea.run.editor.DeployTarget; import com.android.tools.idea.run.editor.DeployTargetProvider; -import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.project.Project; /** A service that provides the {@link DeployTarget} selected in the current project. */ public interface BlazeDeployTargetService { static BlazeDeployTargetService getInstance(Project project) { - return ServiceManager.getService(project, BlazeDeployTargetService.class); + return project.getService(BlazeDeployTargetService.class); } DeployTarget getDeployTarget(); diff --git a/aswb/src/com/google/idea/blaze/android/run/runner/BlazeInstrumentationTestApkBuildStep.java b/aswb/src/com/google/idea/blaze/android/run/runner/BlazeInstrumentationTestApkBuildStep.java index 5d092e40800..1537feac26f 100644 --- a/aswb/src/com/google/idea/blaze/android/run/runner/BlazeInstrumentationTestApkBuildStep.java +++ b/aswb/src/com/google/idea/blaze/android/run/runner/BlazeInstrumentationTestApkBuildStep.java @@ -32,13 +32,7 @@ import com.google.idea.blaze.base.command.buildresult.BuildResultHelper.GetArtifactsException; import com.google.idea.blaze.base.console.BlazeConsoleLineProcessorProvider; import com.google.idea.blaze.base.filecache.FileCaches; -import com.google.idea.blaze.base.ideinfo.AndroidIdeInfo; -import com.google.idea.blaze.base.ideinfo.AndroidInstrumentationInfo; -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.model.primitives.WorkspaceRoot; import com.google.idea.blaze.base.scope.BlazeContext; import com.google.idea.blaze.base.scope.output.IssueOutput; @@ -48,10 +42,8 @@ import com.google.idea.blaze.base.sync.aspects.BuildResult; import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; import com.google.idea.blaze.base.util.SaveUtil; -import com.google.idea.blaze.java.AndroidBlazeRules.RuleTypes; import com.intellij.openapi.project.Project; import java.io.File; -import javax.annotation.Nullable; /** Builds the APKs required for an android instrumentation test. */ public class BlazeInstrumentationTestApkBuildStep implements ApkBuildStep { @@ -60,7 +52,7 @@ public class BlazeInstrumentationTestApkBuildStep implements ApkBuildStep { private static final String DEPLOY_INFO_FILE_SUFFIX = ".deployinfo.pb"; private final Project project; - private final Label instrumentationTestLabel; + private final InstrumentationInfo instrumentationInfo; private final ImmutableList buildFlags; private final BlazeApkDeployInfoProtoHelper deployInfoHelper; private BlazeAndroidDeployInfo deployInfo = null; @@ -69,18 +61,18 @@ public class BlazeInstrumentationTestApkBuildStep implements ApkBuildStep { * Note: Target kind of {@param instrumentationTestlabel} must be "android_instrumentation_test". */ public BlazeInstrumentationTestApkBuildStep( - Project project, Label instrumentationTestLabel, ImmutableList buildFlags) { - this(project, instrumentationTestLabel, buildFlags, new BlazeApkDeployInfoProtoHelper()); + Project project, InstrumentationInfo instrumentationInfo, ImmutableList buildFlags) { + this(project, instrumentationInfo, buildFlags, new BlazeApkDeployInfoProtoHelper()); } @VisibleForTesting public BlazeInstrumentationTestApkBuildStep( Project project, - Label instrumentationTestLabel, + InstrumentationInfo instrumentationInfo, ImmutableList buildFlags, BlazeApkDeployInfoProtoHelper deployInfoHelper) { this.project = project; - this.instrumentationTestLabel = instrumentationTestLabel; + this.instrumentationInfo = instrumentationInfo; this.buildFlags = buildFlags; this.deployInfoHelper = deployInfoHelper; } @@ -94,11 +86,6 @@ public void build(BlazeContext context, BlazeAndroidDeviceSelector.DeviceSession return; } - InstrumentorToTarget testComponents = getInstrumentorToTargetPair(context, projectData); - if (testComponents == null) { - return; - } - BuildInvoker invoker = Blaze.getBuildSystemProvider(project).getBuildSystem().getBuildInvoker(project, context); BlazeCommand.Builder command = BlazeCommand.builder(invoker, BlazeCommandName.BUILD); @@ -108,10 +95,10 @@ public void build(BlazeContext context, BlazeAndroidDeviceSelector.DeviceSession // will always return a local invoker (deployInfoHelper below required that the artifacts // are on the local filesystem). try (BuildResultHelper buildResultHelper = invoker.createBuildResultHelper()) { - if (testComponents.isSelfInstrumentingTest()) { - command.addTargets(testComponents.instrumentor); + if (instrumentationInfo.isSelfInstrumentingTest()) { + command.addTargets(instrumentationInfo.testApp); } else { - command.addTargets(testComponents.target, testComponents.instrumentor); + command.addTargets(instrumentationInfo.targetApp, instrumentationInfo.testApp); } command .addBlazeFlags("--output_groups=+android_deploy_info") @@ -138,8 +125,7 @@ public void build(BlazeContext context, BlazeAndroidDeviceSelector.DeviceSession } try { context.output(new StatusOutput("Reading deployment information...")); - String executionRoot = - ExecRootUtil.getExecutionRoot(buildResultHelper, project, buildFlags, context); + String executionRoot = ExecRootUtil.getExecutionRoot(invoker, context); if (executionRoot == null) { IssueOutput.error("Could not locate execroot!").submit(context); return; @@ -147,17 +133,17 @@ public void build(BlazeContext context, BlazeAndroidDeviceSelector.DeviceSession AndroidDeployInfo instrumentorDeployInfoProto = deployInfoHelper.readDeployInfoProtoForTarget( - testComponents.instrumentor, + instrumentationInfo.testApp, buildResultHelper, fileName -> fileName.endsWith(DEPLOY_INFO_FILE_SUFFIX)); - if (testComponents.isSelfInstrumentingTest()) { + if (instrumentationInfo.isSelfInstrumentingTest()) { deployInfo = deployInfoHelper.extractDeployInfoAndInvalidateManifests( project, new File(executionRoot), instrumentorDeployInfoProto); } else { AndroidDeployInfo targetDeployInfoProto = deployInfoHelper.readDeployInfoProtoForTarget( - testComponents.target, + instrumentationInfo.targetApp, buildResultHelper, fileName -> fileName.endsWith(DEPLOY_INFO_FILE_SUFFIX)); deployInfo = @@ -185,97 +171,4 @@ public BlazeAndroidDeployInfo getDeployInfo() throws ApkProvisionException { "Failed to read APK deploy info. Either build step hasn't been executed or there was an" + " error obtaining deploy info after build."); } - - /** - * Extracts test instrumentor and instrumentation target labels from the target map. - * - * @return The labels contained in an {@link InstrumentorToTarget} object. - */ - @Nullable - @VisibleForTesting - public InstrumentorToTarget getInstrumentorToTargetPair( - BlazeContext context, BlazeProjectData projectData) { - // The following extracts the dependency info required during an instrumentation test. - // To disambiguate, the following terms are used: - // - test: The android_instrumentation_test target. - // - instrumentor: The target of kind android_binary that's used as the binary that - // orchestrates the instrumentation test. - // - app: The android_binary app that's being tested in this instrumentation test through - // the instrumentor. - TargetMap targetMap = projectData.getTargetMap(); - TargetIdeInfo testTarget = targetMap.get(TargetKey.forPlainTarget(instrumentationTestLabel)); - if (testTarget == null - || testTarget.getKind() != RuleTypes.ANDROID_INSTRUMENTATION_TEST.getKind()) { - IssueOutput.error( - "Unable to identify target \"" - + instrumentationTestLabel - + "\". Please sync the project and try again.") - .submit(context); - return null; - } - AndroidInstrumentationInfo testInstrumentationInfo = testTarget.getAndroidInstrumentationInfo(); - if (testInstrumentationInfo == null) { - IssueOutput.error( - "Required target data missing for \"" - + instrumentationTestLabel - + "\". Has the target definition changed recently? Please sync the project and" - + " try again.") - .submit(context); - return null; - } - - Label instrumentorLabel = testInstrumentationInfo.getTestApp(); - if (instrumentorLabel == null) { - IssueOutput.error( - "No \"test_app\" in target definition for " - + testTarget.getKey().getLabel() - + ". Please ensure \"test_app\" attribute is set. See" - + " https://docs.bazel.build/versions/master/be/android.html#android_instrumentation_test.test_app" - + " for more information.") - .submit(context); - return null; - } - - TargetIdeInfo instrumentorTarget = targetMap.get(TargetKey.forPlainTarget(instrumentorLabel)); - if (instrumentorTarget == null) { - IssueOutput.error( - "Unable to identify target \"" - + instrumentorLabel - + "\". Please sync the project and try again.") - .submit(context); - return null; - } - AndroidIdeInfo instrumentorAndroidInfo = instrumentorTarget.getAndroidIdeInfo(); - if (instrumentorAndroidInfo == null) { - IssueOutput.error( - "Required target data missing for \"" - + instrumentorLabel - + "\". Has the target definition changed recently? Please sync the project and" - + " try again.") - .submit(context); - return null; - } - Label appLabel = instrumentorAndroidInfo.getInstruments(); - return new InstrumentorToTarget(appLabel, instrumentorLabel); - } - - /** - * A container for a instrumentation test instrumentor and the target app it instruments. If the - * target label is null, the test is considered to be self-instrumenting. - */ - @VisibleForTesting - public static class InstrumentorToTarget { - @Nullable public final Label target; - public final Label instrumentor; - - InstrumentorToTarget(@Nullable Label target, Label instrumentor) { - this.target = target; - this.instrumentor = instrumentor; - } - - /** Returns whether the instrumentor contains the target itself (self-instrumenting). */ - public boolean isSelfInstrumentingTest() { - return target == null; - } - } } diff --git a/aswb/src/com/google/idea/blaze/android/run/runner/CheckApkDebuggableTask.java b/aswb/src/com/google/idea/blaze/android/run/runner/CheckApkDebuggableTask.java index e912e81538c..748734ce06b 100644 --- a/aswb/src/com/google/idea/blaze/android/run/runner/CheckApkDebuggableTask.java +++ b/aswb/src/com/google/idea/blaze/android/run/runner/CheckApkDebuggableTask.java @@ -18,10 +18,9 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import com.android.ddmlib.IDevice; -import com.android.tools.idea.run.ConsolePrinter; -import com.android.tools.idea.run.tasks.LaunchContext; -import com.android.tools.idea.run.tasks.LaunchResult; -import com.android.tools.idea.run.tasks.LaunchTask; +import com.android.tools.idea.execution.common.RunConfigurationNotifier; +import com.android.tools.idea.run.blaze.BlazeLaunchContext; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.devrel.gmscore.tools.apk.arsc.BinaryResourceFile; @@ -30,6 +29,8 @@ import com.google.devrel.gmscore.tools.apk.arsc.XmlChunk; import com.google.devrel.gmscore.tools.apk.arsc.XmlStartElementChunk; import com.google.idea.blaze.android.run.deployinfo.BlazeAndroidDeployInfo; +import com.intellij.execution.ExecutionException; +import com.intellij.openapi.project.Project; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -38,34 +39,18 @@ import org.jetbrains.annotations.NotNull; /** Checks APKs to see if they are debuggable and warn the user if they aren't. */ -public class CheckApkDebuggableTask implements LaunchTask { +public class CheckApkDebuggableTask implements BlazeLaunchTask { private static final String ID = "APK_DEBUGGABILITY_CHECKER"; private final BlazeAndroidDeployInfo deployInfo; - public CheckApkDebuggableTask(BlazeAndroidDeployInfo deployInfo) { + public CheckApkDebuggableTask(Project project, BlazeAndroidDeployInfo deployInfo) { this.deployInfo = deployInfo; } @Override - public String getDescription() { - return "Checking debug attribute in APKs"; - } - - @Override - public int getDuration() { - return 2; // See com.android.tools.idea.run.tasks.LaunchTaskDurations for related magic numbers. - } - - @Override - public String getId() { - return ID; - } - - @Override - public LaunchResult run(@NotNull LaunchContext launchContext) { + public void run(@NotNull BlazeLaunchContext launchContext) throws ExecutionException { checkApkDebuggableTaskDelegate( - deployInfo, launchContext.getDevice(), launchContext.getConsolePrinter()); - return LaunchResult.success(); // Don't block deployment. + launchContext.getEnv().getProject(), deployInfo, launchContext.getDevice()); } /** @@ -75,7 +60,8 @@ public LaunchResult run(@NotNull LaunchContext launchContext) { */ @VisibleForTesting public static void checkApkDebuggableTaskDelegate( - BlazeAndroidDeployInfo deployInfo, IDevice device, ConsolePrinter consolePrinter) { + Project project, BlazeAndroidDeployInfo deployInfo, IDevice device) + throws ExecutionException { if (isDebugDevice(device)) { return; } @@ -94,9 +80,9 @@ public static void checkApkDebuggableTaskDelegate( + ". Debugger may not attach properly or attach at all." + " Please ensure \"android:debuggable\" attribute is set to true or" + " overridden to true via manifest overrides."; - consolePrinter.stderr(message); + RunConfigurationNotifier.INSTANCE.notifyWarning(project, "", message); } catch (IOException e) { - consolePrinter.stderr("Could not read deploy apks: " + e.getMessage()); + throw new ExecutionException("Could not read deploy apks: " + e.getMessage()); } } diff --git a/aswb/src/com/google/idea/blaze/android/run/runner/ExecRootUtil.java b/aswb/src/com/google/idea/blaze/android/run/runner/ExecRootUtil.java index 5e62341bfe8..32a61e168a0 100644 --- a/aswb/src/com/google/idea/blaze/android/run/runner/ExecRootUtil.java +++ b/aswb/src/com/google/idea/blaze/android/run/runner/ExecRootUtil.java @@ -15,72 +15,31 @@ */ package com.google.idea.blaze.android.run.runner; -import com.google.common.collect.ImmutableList; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.idea.blaze.base.command.buildresult.BuildResultHelper; +import com.google.idea.blaze.base.bazel.BuildSystem.BuildInvoker; import com.google.idea.blaze.base.command.buildresult.BuildResultHelper.GetArtifactsException; -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.primitives.WorkspaceRoot; import com.google.idea.blaze.base.scope.BlazeContext; import com.google.idea.blaze.base.scope.output.IssueOutput; -import com.google.idea.blaze.base.scope.output.StatusOutput; -import com.google.idea.blaze.base.settings.Blaze; -import com.google.idea.common.experiments.BoolExperiment; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import java.util.concurrent.ExecutionException; +import com.google.idea.blaze.base.sync.SyncScope.SyncFailedException; import javax.annotation.Nullable; /** Utility for fetching execroot. */ public final class ExecRootUtil { - /** Enables using blaze info as fallback for fetching execroot. */ - private static final BoolExperiment useBlazeInfoAsExecrootFallback = - new BoolExperiment("enable.execroot.fallback", false); - - private static final Logger log = Logger.getInstance(ExecRootUtil.class); - /** * Returns the execroot of the given project. * - *

This method tries to get the execroot from BEP output. In the event where BEP isn't reliable - * for obtaining the execroot, enabling {@link #useBlazeInfoAsExecrootFallback} will cause this - * method to also obtain the execroot using blaze info. + *

This method tries to obtain the execroot using blaze info. */ @Nullable - public static String getExecutionRoot( - BuildResultHelper buildResultHelper, - Project project, - ImmutableList buildFlags, - BlazeContext context) + public static String getExecutionRoot(BuildInvoker invoker, BlazeContext context) throws GetArtifactsException { - String executionRoot = buildResultHelper.getBuildOutput().getLocalExecRoot(); - if (executionRoot != null) { - return executionRoot; - } else if (!useBlazeInfoAsExecrootFallback.getValue()) { - return null; - } - - log.warn("Could not get execroot from BEP. Falling back to using blaze info."); - context.output(new StatusOutput("Fetching project output directory...")); - ListenableFuture execRootFuture = - BlazeInfoRunner.getInstance() - .runBlazeInfo( - context, - Blaze.getBuildSystemProvider(project).getBinaryPath(project), - WorkspaceRoot.fromProject(project), - buildFlags, - BlazeInfo.EXECUTION_ROOT_KEY); try { - return execRootFuture.get(); - } catch (InterruptedException e) { - IssueOutput.warn("Build cancelled.").submit(context); - context.setCancelled(); - } catch (ExecutionException e) { - IssueOutput.error(e.getMessage()).submit(context); + return invoker.getBlazeInfo().getExecutionRoot().getAbsolutePath(); + } catch (SyncFailedException e) { + IssueOutput.error("Could not obtain exec root from blaze info: " + e.getMessage()) + .submit(context); context.setHasError(); + return null; } - return null; } private ExecRootUtil() {} diff --git a/aswb/src/com/google/idea/blaze/android/run/runner/FullApkBuildStep.java b/aswb/src/com/google/idea/blaze/android/run/runner/FullApkBuildStep.java index 7272320d110..ddf257d9f95 100644 --- a/aswb/src/com/google/idea/blaze/android/run/runner/FullApkBuildStep.java +++ b/aswb/src/com/google/idea/blaze/android/run/runner/FullApkBuildStep.java @@ -15,11 +15,14 @@ */ package com.google.idea.blaze.android.run.runner; +import static java.util.stream.Collectors.joining; + import com.android.tools.idea.run.ApkProvisionException; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.ListenableFuture; import com.google.devtools.build.lib.rules.android.deployinfo.AndroidDeployInfoOuterClass.AndroidDeployInfo; +import com.google.idea.blaze.android.run.NativeSymbolFinder; import com.google.idea.blaze.android.run.RemoteApkDownloader; import com.google.idea.blaze.android.run.deployinfo.BlazeAndroidDeployInfo; import com.google.idea.blaze.android.run.deployinfo.BlazeApkDeployInfoProtoHelper; @@ -50,6 +53,7 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.util.List; /** Builds the APK using normal blaze build. */ public class FullApkBuildStep implements ApkBuildStep { @@ -66,6 +70,7 @@ public class FullApkBuildStep implements ApkBuildStep { private final Label label; private final ImmutableList buildFlags; private final BlazeApkDeployInfoProtoHelper deployInfoHelper; + private final boolean nativeDebuggingEnabled; private BlazeAndroidDeployInfo deployInfo = null; @VisibleForTesting @@ -73,15 +78,72 @@ public FullApkBuildStep( Project project, Label label, ImmutableList buildFlags, + boolean nativeDebuggingEnabled, BlazeApkDeployInfoProtoHelper deployInfoHelper) { this.project = project; this.label = label; this.buildFlags = buildFlags; this.deployInfoHelper = deployInfoHelper; + this.nativeDebuggingEnabled = nativeDebuggingEnabled; + } + + public FullApkBuildStep( + Project project, + Label label, + ImmutableList buildFlags, + boolean nativeDebuggingEnabled) { + this(project, label, buildFlags, nativeDebuggingEnabled, new BlazeApkDeployInfoProtoHelper()); } - public FullApkBuildStep(Project project, Label label, ImmutableList buildFlags) { - this(project, label, buildFlags, new BlazeApkDeployInfoProtoHelper()); + private static boolean apksRequireDownload(BlazeAndroidDeployInfo deployInfo) { + for (File apk : deployInfo.getApksToDeploy()) { + for (RemoteApkDownloader downloader : RemoteApkDownloader.EP_NAME.getExtensionList()) { + if (downloader.canDownload(apk)) { + return true; + } + } + } + return false; + } + + private static File downloadApkIfRemote(File apk, BlazeContext context) { + for (RemoteApkDownloader downloader : RemoteApkDownloader.EP_NAME.getExtensionList()) { + if (downloader.canDownload(apk)) { + try { + context.output(new StatusOutput("Downloading " + apk.getPath())); + File tempFile = Files.createTempFile("localcopy", apk.getName()).toFile(); + tempFile.deleteOnExit(); + downloader.download(apk, tempFile); + return tempFile; + } catch (IOException ex) { + // fallback to using original, don't want to block the whole app deployment process. + log.warn("Couldn't create local copy of file " + apk.getPath(), ex); + } + } + } + return apk; + } + + private static File downloadLibIfRemote(File lib, BlazeContext context) { + for (RemoteApkDownloader downloader : RemoteApkDownloader.EP_NAME.getExtensionList()) { + if (downloader.canDownload(lib)) { + try { + // File name must be preserved for LLDB lookup, but multiple files will have the same name + // if multiple android_platforms are built, so place each file in its own temp dir. + context.output(new StatusOutput("Downloading " + lib.getPath())); + File tempDir = Files.createTempDirectory("localcopy").toFile(); + tempDir.deleteOnExit(); + File tempFile = new File(tempDir, lib.getName()); + tempFile.deleteOnExit(); + downloader.download(lib, tempFile); + return tempFile; + } catch (IOException ex) { + // fallback to using original, don't want to block the whole app deployment process. + log.warn("Couldn't create local copy of file " + lib.getPath(), ex); + } + } + } + return lib; } @Override @@ -100,11 +162,18 @@ public void build(BlazeContext context, BlazeAndroidDeviceSelector.DeviceSession WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProject(project); try (BuildResultHelper buildResultHelper = invoker.createBuildResultHelper()) { - command - .addTargets(label) - .addBlazeFlags("--output_groups=+android_deploy_info") - .addBlazeFlags(buildFlags) - .addBlazeFlags(buildResultHelper.getBuildFlags()); + List nativeSymbolFinderList = + NativeSymbolFinder.EP_NAME.getExtensionList(); + command.addTargets(label).addBlazeFlags("--output_groups=+android_deploy_info"); + + if (!nativeSymbolFinderList.isEmpty()) { + command.addBlazeFlags( + nativeSymbolFinderList.stream() + .map(NativeSymbolFinder::getAdditionalBuildFlags) + .collect(joining(" "))); + } + + command.addBlazeFlags(buildFlags).addBlazeFlags(buildResultHelper.getBuildFlags()); SaveUtil.saveAllFiles(); int retVal = @@ -126,8 +195,7 @@ public void build(BlazeContext context, BlazeAndroidDeviceSelector.DeviceSession } context.output(new StatusOutput("Reading deployment information...")); - String executionRoot = - ExecRootUtil.getExecutionRoot(buildResultHelper, project, buildFlags, context); + String executionRoot = ExecRootUtil.getExecutionRoot(invoker, context); if (executionRoot == null) { IssueOutput.error("Could not locate execroot!").submit(context); return; @@ -136,9 +204,15 @@ public void build(BlazeContext context, BlazeAndroidDeviceSelector.DeviceSession AndroidDeployInfo deployInfoProto = deployInfoHelper.readDeployInfoProtoForTarget( label, buildResultHelper, fileName -> fileName.endsWith(DEPLOY_INFO_SUFFIX)); + ImmutableList libs = + nativeSymbolFinderList.stream() + .flatMap( + finder -> + finder.getNativeSymbolsForBuild(context, label, buildResultHelper).stream()) + .collect(ImmutableList.toImmutableList()); deployInfo = deployInfoHelper.extractDeployInfoAndInvalidateManifests( - project, new File(executionRoot), deployInfoProto); + project, new File(executionRoot), deployInfoProto, libs); } catch (GetArtifactsException e) { IssueOutput.error("Could not read BEP output: " + e.getMessage()).submit(context); } catch (GetDeployInfoException e) { @@ -152,42 +226,22 @@ public void build(BlazeContext context, BlazeAndroidDeviceSelector.DeviceSession deployInfo.getApksToDeploy().stream() .map(apk -> FullApkBuildStep.downloadApkIfRemote(apk, context)) .collect(ImmutableList.toImmutableList()); + ImmutableList localLibs = + this.nativeDebuggingEnabled + ? deployInfo.getSymbolFiles().stream() + .map(lib -> FullApkBuildStep.downloadLibIfRemote(lib, context)) + .collect(ImmutableList.toImmutableList()) + : ImmutableList.of(); deployInfo = new BlazeAndroidDeployInfo( - deployInfo.getMergedManifest(), deployInfo.getTestTargetMergedManifest(), localApks); + deployInfo.getMergedManifest(), + deployInfo.getTestTargetMergedManifest(), + localApks, + localLibs); context.output(new StatusOutput("Done fetching APKs.")); } } - private static boolean apksRequireDownload(BlazeAndroidDeployInfo deployInfo) { - for (File apk : deployInfo.getApksToDeploy()) { - for (RemoteApkDownloader downloader : RemoteApkDownloader.EP_NAME.getExtensionList()) { - if (downloader.canDownload(apk)) { - return true; - } - } - } - return false; - } - - private static File downloadApkIfRemote(File apk, BlazeContext context) { - for (RemoteApkDownloader downloader : RemoteApkDownloader.EP_NAME.getExtensionList()) { - if (downloader.canDownload(apk)) { - try { - context.output(new StatusOutput("Downloading " + apk.getPath())); - File tempFile = Files.createTempFile("localcopy", apk.getName()).toFile(); - tempFile.deleteOnExit(); - downloader.download(apk, tempFile); - return tempFile; - } catch (IOException ex) { - // fallback to using original, don't want to block the whole app deployment process. - log.warn("Couldn't create local copy of file " + apk.getPath(), ex); - } - } - } - return apk; - } - @Override public BlazeAndroidDeployInfo getDeployInfo() throws ApkProvisionException { if (deployInfo != null) { diff --git a/aswb/src/com/google/idea/blaze/android/run/runner/InstrumentationInfo.java b/aswb/src/com/google/idea/blaze/android/run/runner/InstrumentationInfo.java new file mode 100644 index 00000000000..53f0e8337c2 --- /dev/null +++ b/aswb/src/com/google/idea/blaze/android/run/runner/InstrumentationInfo.java @@ -0,0 +1,154 @@ +/* + * Copyright 2022 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.runner; + +import com.google.common.annotations.VisibleForTesting; +import com.google.idea.blaze.base.ideinfo.AndroidIdeInfo; +import com.google.idea.blaze.base.ideinfo.AndroidInstrumentationInfo; +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.java.AndroidBlazeRules.RuleTypes; +import com.google.idea.blaze.qsync.project.ProjectTarget; +import javax.annotation.Nullable; + +/** + * Container for information about {@code android_instrumentation_test}: it holds links to the test + * target and the instrumented target. + */ +@VisibleForTesting +public class InstrumentationInfo { + /** + * {@code android_binary} target corresponding to the application under test. + * + *

This is obtained from the {@code instruments} attribute of the binary that contains the + * tests. + */ + @Nullable public final Label targetApp; + + /** + * {@code android_binary} target that contains the instrumentation tests. + * + *

This is obtained from the {@code test_app} attribute of an {@code + * android_instrumentation_test} target. + */ + public final Label testApp; + + @VisibleForTesting + public InstrumentationInfo(@Nullable Label targetApp, Label testApp) { + this.targetApp = targetApp; + this.testApp = testApp; + } + + /** Returns whether the test app contains the target itself (self-instrumenting). */ + public boolean isSelfInstrumentingTest() { + return targetApp == null; + } + + /** Exception thrown on errors while retrieving {@link InstrumentationInfo} from project data. */ + public static final class InstrumentationParserException extends RuntimeException { + public InstrumentationParserException(String msg) { + super(msg); + } + } + + /** + * Extracts information about the test and target apps from the instrumentation test rule. + * + * @return The labels contained in an {@link InstrumentationInfo} object. + */ + @VisibleForTesting + public static InstrumentationInfo getInstrumentationInfo( + Label instrumentationTestLabel, BlazeProjectData projectData) { + if (projectData.isQuerySync()) { + ProjectTarget testTarget = + (ProjectTarget) projectData.getBuildTarget(instrumentationTestLabel); + if (testTarget == null) { + String msg = "Unable to identify target \"" + instrumentationTestLabel + "\"."; + throw new InstrumentationParserException(msg); + } + if (testTarget.testApp().isEmpty()) { + String msg = "Unable to identify test_app for target \"" + instrumentationTestLabel + "\"."; + throw new InstrumentationParserException(msg); + } + Label testApp = Label.create(testTarget.testApp().get().toString()); + ProjectTarget targetApp = (ProjectTarget) projectData.getBuildTarget(testApp); + Label instruments = null; + if (targetApp != null && targetApp.instruments().isPresent()) { + instruments = Label.create(targetApp.instruments().get().toString()); + } + return new InstrumentationInfo(instruments, testApp); + } + + // The following extracts the dependency info required during an instrumentation test. + // To disambiguate, we try to follow the same terminology as used by the + // android_instrumentation_test rule docs: + // - test: The android_instrumentation_test target. + // - test_app: The target of kind android_binary that's used as the binary that + // orchestrates the instrumentation test. + // - target_app: The android_binary app that's being tested by the test_app. + TargetMap targetMap = projectData.getTargetMap(); + TargetIdeInfo testTarget = targetMap.get(TargetKey.forPlainTarget(instrumentationTestLabel)); + if (testTarget == null + || testTarget.getKind() != RuleTypes.ANDROID_INSTRUMENTATION_TEST.getKind()) { + String msg = + "Unable to identify target \"" + + instrumentationTestLabel + + "\". Please sync the project and try again."; + throw new InstrumentationParserException(msg); + } + AndroidInstrumentationInfo testInstrumentationInfo = testTarget.getAndroidInstrumentationInfo(); + if (testInstrumentationInfo == null) { + String msg = + "Required target data missing for \"" + + instrumentationTestLabel + + "\". Has the target definition changed recently? Please sync the project and" + + " try again."; + throw new InstrumentationParserException(msg); + } + + Label testApp = testInstrumentationInfo.getTestApp(); + if (testApp == null) { + String msg = + "No \"test_app\" in target definition for " + + testTarget.getKey().getLabel() + + ". Please ensure \"test_app\" attribute is set. See" + + " https://docs.bazel.build/versions/master/be/android.html#android_instrumentation_test.test_app" + + " for more information."; + throw new InstrumentationParserException(msg); + } + + TargetIdeInfo testAppIdeInfo = targetMap.get(TargetKey.forPlainTarget(testApp)); + if (testAppIdeInfo == null) { + String msg = + "Unable to identify target \"" + testApp + "\". Please sync the project and try again."; + throw new InstrumentationParserException(msg); + } + AndroidIdeInfo testAppAndroidInfo = testAppIdeInfo.getAndroidIdeInfo(); + if (testAppAndroidInfo == null) { + String msg = + "Required target data missing for \"" + + testApp + + "\". Has the target definition changed recently? Please sync the project and" + + " try again."; + throw new InstrumentationParserException(msg); + } + Label targetApp = testAppAndroidInfo.getInstruments(); + return new InstrumentationInfo(targetApp, testApp); + } +} diff --git a/aswb/src/com/google/idea/blaze/android/run/test/AndroidTestConsoleProvider.java b/aswb/src/com/google/idea/blaze/android/run/test/AitBlazeTestConsoleProvider.java similarity index 66% rename from aswb/src/com/google/idea/blaze/android/run/test/AndroidTestConsoleProvider.java rename to aswb/src/com/google/idea/blaze/android/run/test/AitBlazeTestConsoleProvider.java index 587bbeed94e..97e5c1514d0 100644 --- a/aswb/src/com/google/idea/blaze/android/run/test/AndroidTestConsoleProvider.java +++ b/aswb/src/com/google/idea/blaze/android/run/test/AitBlazeTestConsoleProvider.java @@ -16,7 +16,7 @@ package com.google.idea.blaze.android.run.test; import com.android.tools.idea.run.ConsoleProvider; -import com.android.tools.idea.testartifacts.instrumented.AndroidTestConsoleProperties; +import com.google.idea.blaze.android.run.test.BlazeAndroidTestLaunchMethodsProvider.AndroidTestLaunchMethod; 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; @@ -25,44 +25,35 @@ import com.intellij.execution.executors.DefaultDebugExecutor; import com.intellij.execution.filters.TextConsoleBuilderFactory; import com.intellij.execution.process.ProcessHandler; -import com.intellij.execution.testframework.sm.SMTestRunnerConnectionUtil; import com.intellij.execution.ui.ConsoleView; import com.intellij.openapi.Disposable; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Disposer; import javax.annotation.Nullable; -/** Console provider for android_test */ -class AndroidTestConsoleProvider implements ConsoleProvider { +/** + * Console provider for {@code android_instrumentation_test}, when the tests are run using Blaze + * {@link AndroidTestLaunchMethod#BLAZE_TEST}. + */ +class AitBlazeTestConsoleProvider implements ConsoleProvider { private final Project project; private final BlazeCommandRunConfiguration runConfiguration; - private final BlazeAndroidTestRunConfigurationState configState; @Nullable private final BlazeTestUiSession testUiSession; - AndroidTestConsoleProvider( + AitBlazeTestConsoleProvider( Project project, BlazeCommandRunConfiguration runConfiguration, - BlazeAndroidTestRunConfigurationState configState, @Nullable BlazeTestUiSession testUiSession) { this.project = project; this.runConfiguration = runConfiguration; - this.configState = configState; this.testUiSession = testUiSession; } @Override public ConsoleView createAndAttach(Disposable parent, ProcessHandler handler, Executor executor) throws ExecutionException { - 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); - } - throw new AssertionError(); + ConsoleView console = createBlazeTestConsole(executor); + console.attachToProcess(handler); + return console; } private ConsoleView createBlazeTestConsole(Executor executor) { @@ -77,15 +68,4 @@ private ConsoleView createBlazeTestConsole(Executor executor) { private static boolean isDebugging(Executor executor) { return executor instanceof DefaultDebugExecutor; } - - private ConsoleProvider getStockConsoleProvider() { - return (parent, handler, executor) -> { - AndroidTestConsoleProperties properties = - new AndroidTestConsoleProperties(runConfiguration, executor); - ConsoleView consoleView = - SMTestRunnerConnectionUtil.createAndAttachConsole("Android", handler, properties); - Disposer.register(parent, consoleView); - return consoleView; - }; - } } diff --git a/aswb/src/com/google/idea/blaze/android/run/test/AitIdeTestConsoleProvider.java b/aswb/src/com/google/idea/blaze/android/run/test/AitIdeTestConsoleProvider.java new file mode 100644 index 00000000000..02072bf6c4e --- /dev/null +++ b/aswb/src/com/google/idea/blaze/android/run/test/AitIdeTestConsoleProvider.java @@ -0,0 +1,55 @@ +/* + * Copyright 2022 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.test; + +import com.android.tools.idea.run.ConsoleProvider; +import com.android.tools.idea.testartifacts.instrumented.AndroidTestConsoleProperties; +import com.google.common.base.Preconditions; +import com.google.idea.blaze.android.run.test.BlazeAndroidTestLaunchMethodsProvider.AndroidTestLaunchMethod; +import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.Executor; +import com.intellij.execution.process.ProcessHandler; +import com.intellij.execution.testframework.sm.SMTestRunnerConnectionUtil; +import com.intellij.execution.ui.ConsoleView; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.util.Disposer; + +/** + * Console provider for {@code android_instrumentation_test} aka AIT, when the tests are run + * directly by the IDE. + */ +class AitIdeTestConsoleProvider implements ConsoleProvider { + private final BlazeCommandRunConfiguration runConfiguration; + + AitIdeTestConsoleProvider( + BlazeCommandRunConfiguration runConfiguration, + BlazeAndroidTestRunConfigurationState configState) { + Preconditions.checkArgument( + configState.getLaunchMethod() != AndroidTestLaunchMethod.BLAZE_TEST); + this.runConfiguration = runConfiguration; + } + + @Override + public ConsoleView createAndAttach(Disposable parent, ProcessHandler handler, Executor executor) + throws ExecutionException { + AndroidTestConsoleProperties properties = + new AndroidTestConsoleProperties(runConfiguration, executor); + ConsoleView consoleView = SMTestRunnerConnectionUtil.createConsole("Android", properties); + Disposer.register(parent, consoleView); + return consoleView; + } +} 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 index 5560389927b..52c0b5d18a8 100644 --- a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestLaunchMethodsProvider.java +++ b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestLaunchMethodsProvider.java @@ -35,8 +35,24 @@ static AndroidTestLaunchMethodComboEntry[] getAllLaunchMethods(Project project) /** All possible test launch methods. */ enum AndroidTestLaunchMethod { + /** + * Build using Blaze; have the IDE run the test on a locally attached device. This approach is + * the default. It however does not support all attributes in {@code android_local_test}, esp. + * those around custom target devices, or installing special APKs before running the tests. + */ NON_BLAZE, + + /** + * Equivalent to calling {@code blaze test} on a target. This only works on forge (i.e. cannot + * be used with {@code --test_strategy=local}. It doesn't seem like you can debug a test in this + * mode. + */ BLAZE_TEST, + + /** + * Runs a test using mobile-install. (i.e. {@code blaze mobile-install + * //some/android_local_test}. + */ MOBILE_INSTALL, } diff --git a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestLaunchTask.java b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestLaunchTask.java index e662783297d..dc7dd974c06 100644 --- a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestLaunchTask.java +++ b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestLaunchTask.java @@ -15,13 +15,9 @@ */ package com.google.idea.blaze.android.run.test; -import com.android.tools.idea.run.ConsolePrinter; -import com.android.tools.idea.run.tasks.LaunchContext; -import com.android.tools.idea.run.tasks.LaunchResult; -import com.android.tools.idea.run.tasks.LaunchTask; -import com.android.tools.idea.run.tasks.LaunchTaskDurations; -import com.android.tools.idea.run.util.LaunchStatus; -import com.android.tools.idea.run.util.ProcessHandlerLaunchStatus; +import com.android.tools.idea.run.blaze.BlazeLaunchContext; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; +import com.android.tools.idea.run.configuration.execution.ExecutionUtils; 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; @@ -29,6 +25,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.buildresult.BuildResultHelper; +import com.google.idea.blaze.base.command.buildresult.BuildResultHelper.GetArtifactsException; +import com.google.idea.blaze.base.command.buildresult.BuildResultHelperBep; import com.google.idea.blaze.base.filecache.FileCaches; import com.google.idea.blaze.base.ideinfo.AndroidInstrumentationInfo; import com.google.idea.blaze.base.ideinfo.TargetIdeInfo; @@ -38,9 +37,8 @@ import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; import com.google.idea.blaze.base.projectview.ProjectViewManager; import com.google.idea.blaze.base.projectview.ProjectViewSet; -import com.google.idea.blaze.base.scope.BlazeContext; +import com.google.idea.blaze.base.run.testlogs.BlazeTestResultHolder; import com.google.idea.blaze.base.scope.Scope; -import com.google.idea.blaze.base.scope.ScopedFunction; import com.google.idea.blaze.base.scope.output.IssueOutput; import com.google.idea.blaze.base.settings.Blaze; import com.google.idea.blaze.base.sync.aspects.BlazeBuildOutputs; @@ -51,11 +49,11 @@ import com.intellij.execution.process.ProcessAdapter; import com.intellij.execution.process.ProcessEvent; import com.intellij.execution.process.ProcessHandler; +import com.intellij.execution.ui.ConsoleView; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.CancellationException; +import java.util.Optional; import java.util.concurrent.ExecutionException; import org.jetbrains.annotations.NotNull; import org.jetbrains.ide.PooledThreadExecutor; @@ -64,7 +62,7 @@ * An Android application launcher that invokes `blaze test` on an android_test target, and sets up * process handling and debugging for the test run. */ -public class BlazeAndroidTestLaunchTask implements LaunchTask { +public class BlazeAndroidTestLaunchTask implements BlazeLaunchTask { private static final String ID = "BLAZE_ANDROID_TEST"; // Uses a local device/emulator attached to adb to run an android_test. @@ -80,10 +78,11 @@ public class BlazeAndroidTestLaunchTask implements LaunchTask { private final Label target; private final List buildFlags; private final BlazeAndroidTestFilter testFilter; + private final BlazeTestResultHolder testResultsHolder; private ListenableFuture blazeResult; - private final BlazeAndroidTestRunContextBase runContext; + private final BlazeAndroidTestRunContext runContext; private final boolean debug; @@ -92,118 +91,115 @@ public BlazeAndroidTestLaunchTask( Label target, List buildFlags, BlazeAndroidTestFilter testFilter, - BlazeAndroidTestRunContextBase runContext, - boolean debug) { + BlazeAndroidTestRunContext runContext, + boolean debug, + BlazeTestResultHolder testResultsHolder) { this.project = project; this.target = target; this.buildFlags = buildFlags; this.testFilter = testFilter; this.runContext = runContext; this.debug = debug; + this.testResultsHolder = testResultsHolder; } - @NotNull @Override - public String getDescription() { - return String.format("Running %s tests", Blaze.buildSystemName(project)); - } - - @Override - public int getDuration() { - return LaunchTaskDurations.LAUNCH_ACTIVITY; - } - - @Override - public LaunchResult run(@NotNull LaunchContext launchContext) { + public void run(@NotNull BlazeLaunchContext launchContext) + throws com.intellij.execution.ExecutionException { BlazeExecutor blazeExecutor = BlazeExecutor.getInstance(); - ProcessHandlerLaunchStatus processHandlerLaunchStatus = - (ProcessHandlerLaunchStatus) launchContext.getLaunchStatus(); - final ProcessHandler processHandler = processHandlerLaunchStatus.getProcessHandler(); + final ProcessHandler processHandler = launchContext.getProcessHandler(); blazeResult = blazeExecutor.submit( - new Callable() { - @Override - public Boolean call() throws Exception { - return Scope.root( - new ScopedFunction() { - @Override - public Boolean execute(@NotNull BlazeContext context) { - ProjectViewSet projectViewSet = - ProjectViewManager.getInstance(project).getProjectViewSet(); - if (projectViewSet == null) { - IssueOutput.error("Could not load project view. Please resync project.") - .submit(context); - return false; - } + () -> + Scope.root( + context -> { + SaveUtil.saveAllFiles(); - BlazeProjectData projectData = - BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); - TargetIdeInfo targetInfo = - projectData.getTargetMap().get(TargetKey.forPlainTarget(target)); - if (targetInfo == null - || targetInfo.getKind() - != RuleTypes.ANDROID_INSTRUMENTATION_TEST.getKind()) { - IssueOutput.error( - "Unable to identify target \"" - + target - + "\". If this is a newly added target, please sync the" - + " project and try again.") - .submit(context); - return null; - } - AndroidInstrumentationInfo testInstrumentationInfo = - targetInfo.getAndroidInstrumentationInfo(); - if (testInstrumentationInfo == null) { - IssueOutput.error( - "Required target data missing for \"" - + target - + "\". Has the target definition changed recently? Please" - + " sync the project and try again.") - .submit(context); - } - Label targetDevice = testInstrumentationInfo.getTargetDevice(); + ProjectViewSet projectViewSet = + ProjectViewManager.getInstance(project).getProjectViewSet(); + if (projectViewSet == null) { + IssueOutput.error("Could not load project view. Please resync project.") + .submit(context); + return false; + } - BlazeCommand.Builder commandBuilder = - BlazeCommand.builder( - Blaze.getBuildSystemProvider(project).getBinaryPath(project), - BlazeCommandName.TEST) - .addTargets(target); - // Build flags must match BlazeBeforeRunTask. - commandBuilder.addBlazeFlags(buildFlags); - // Run the test on the selected local device/emulator if no target device is - // specified. - if (targetDevice == null) { - commandBuilder - .addBlazeFlags(TEST_LOCAL_DEVICE, BlazeFlags.TEST_OUTPUT_STREAMED) - .addBlazeFlags( - testDeviceSerialFlags( - launchContext.getDevice().getSerialNumber())) - .addBlazeFlags(testFilter.getBlazeFlags()); - } - if (debug) { - commandBuilder.addBlazeFlags( - TEST_DEBUG, BlazeFlags.NO_CACHE_TEST_RESULTS); - } + BlazeProjectData projectData = + BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); + TargetIdeInfo targetInfo = + projectData.getTargetMap().get(TargetKey.forPlainTarget(target)); + if (targetInfo == null + || targetInfo.getKind() + != RuleTypes.ANDROID_INSTRUMENTATION_TEST.getKind()) { + IssueOutput.error( + "Unable to identify target \"" + + target + + "\". If this is a newly added target, please sync the" + + " project and try again.") + .submit(context); + return null; + } + AndroidInstrumentationInfo testInstrumentationInfo = + targetInfo.getAndroidInstrumentationInfo(); + if (testInstrumentationInfo == null) { + IssueOutput.error( + "Required target data missing for \"" + + target + + "\". Has the target definition changed recently? Please" + + " sync the project and try again.") + .submit(context); + return null; + } + + BlazeCommand.Builder commandBuilder = + BlazeCommand.builder( + Blaze.getBuildSystemProvider(project) + .getBuildSystem() + .getBuildInvoker(project, context), + BlazeCommandName.TEST) + .addTargets(target); + // Build flags must match BlazeBeforeRunTask. + commandBuilder.addBlazeFlags(buildFlags); + + // Run the test on the selected local device/emulator if no target device is + // specified. + Label targetDevice = testInstrumentationInfo.getTargetDevice(); + if (targetDevice == null) { + commandBuilder + .addBlazeFlags(TEST_LOCAL_DEVICE, BlazeFlags.TEST_OUTPUT_STREAMED) + .addBlazeFlags( + testDeviceSerialFlags(launchContext.getDevice().getSerialNumber())) + .addBlazeFlags(testFilter.getBlazeFlags()); + } + + if (debug) { + commandBuilder.addBlazeFlags(TEST_DEBUG, BlazeFlags.NO_CACHE_TEST_RESULTS); + } + + ConsoleView console = launchContext.getConsoleView(); + LineProcessingOutputStream.LineProcessor stdoutLineProcessor = + line -> { + ExecutionUtils.println(console, line); + return true; + }; + LineProcessingOutputStream.LineProcessor stderrLineProcessor = + line -> { + ExecutionUtils.println(console, line); + return true; + }; + + ExecutionUtils.println( + console, + String.format("Starting %s test...\n", Blaze.buildSystemName(project))); + + int retVal; + try (BuildResultHelper buildResultHelper = new BuildResultHelperBep()) { + commandBuilder.addBlazeFlags(buildResultHelper.getBuildFlags()); BlazeCommand command = commandBuilder.build(); + ExecutionUtils.println(console, command + "\n"); - ConsolePrinter printer = launchContext.getConsolePrinter(); - printer.stdout( - String.format("Starting %s test...\n", Blaze.buildSystemName(project))); - printer.stdout(command + "\n"); - LineProcessingOutputStream.LineProcessor stdoutLineProcessor = - line -> { - printer.stdout(line); - return true; - }; - LineProcessingOutputStream.LineProcessor stderrLineProcessor = - line -> { - printer.stderr(line); - return true; - }; - SaveUtil.saveAllFiles(); - int retVal = + retVal = ExternalTask.builder(WorkspaceRoot.fromProject(project)) .addBlazeCommand(command) .context(context) @@ -211,35 +207,30 @@ public Boolean execute(@NotNull BlazeContext context) { .stderr(LineProcessingOutputStream.of(stderrLineProcessor)) .build() .run(); + + if (retVal != 0) { + context.setHasError(); + } else { + testResultsHolder.setTestResults( + buildResultHelper.getTestResults(Optional.empty())); + } ListenableFuture unusedFuture = FileCaches.refresh( project, context, BlazeBuildOutputs.noOutputs(BuildResult.fromExitCode(retVal))); - - if (retVal != 0) { - context.setHasError(); - } - - return !context.hasErrors(); + } catch (GetArtifactsException e) { + LOG.error(e.getMessage()); } - }); - } - }); + return !context.hasErrors(); + })); blazeResult.addListener(runContext::onLaunchTaskComplete, PooledThreadExecutor.INSTANCE); // The debug case is set up in ConnectBlazeTestDebuggerTask if (!debug) { - waitAndSetUpForKillingBlazeOnStop(processHandler, launchContext.getLaunchStatus()); + waitAndSetUpForKillingBlazeOnStop(processHandler); } - return LaunchResult.success(); - } - - @NotNull - @Override - public String getId() { - return ID; } /** @@ -247,31 +238,23 @@ public String getId() { * Blaze process to stop. In non-debug mode, we wait for test execution to finish before returning * from launch() (this matches the behavior of the stock ddmlib runner). */ - private void waitAndSetUpForKillingBlazeOnStop( - @NotNull final ProcessHandler processHandler, @NotNull final LaunchStatus launchStatus) { + @SuppressWarnings("Interruption") + private void waitAndSetUpForKillingBlazeOnStop(@NotNull final ProcessHandler processHandler) { processHandler.addProcessListener( new ProcessAdapter() { @Override public void processWillTerminate(ProcessEvent event, boolean willBeDestroyed) { blazeResult.cancel(true /* mayInterruptIfRunning */); - launchStatus.terminateLaunch("Test run stopped.\n", true); } }); try { blazeResult.get(); - launchStatus.terminateLaunch("Tests ran to completion.\n", true); - } catch (CancellationException e) { - // The user has canceled the test. - launchStatus.terminateLaunch("Test run stopped.\n", true); } catch (InterruptedException e) { // We've been interrupted - cancel the underlying Blaze process. blazeResult.cancel(true /* mayInterruptIfRunning */); - launchStatus.terminateLaunch("Test run stopped.\n", true); } catch (ExecutionException e) { LOG.error(e); - launchStatus.terminateLaunch( - "Test run stopped due to internal exception. Please file a bug report.\n", true); } } diff --git a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestListener.java b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestListener.java new file mode 100644 index 00000000000..e3f51e7558d --- /dev/null +++ b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestListener.java @@ -0,0 +1,170 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.android.run.test; + +import com.android.ddmlib.testrunner.ITestRunListener; +import com.android.ddmlib.testrunner.TestIdentifier; +import com.android.tools.idea.flags.StudioFlags; +import com.android.tools.idea.run.configuration.execution.ExecutionUtils; +import com.android.tools.idea.testartifacts.instrumented.AndroidTestLocationProvider; +import com.intellij.execution.process.ProcessHandler; +import com.intellij.execution.process.ProcessOutputType; +import com.intellij.execution.testframework.sm.ServiceMessageBuilder; +import com.intellij.execution.ui.ConsoleView; +import java.util.Map; +import java.util.Objects; + +/** + * Copy of {@link com.android.tools.idea.testartifacts.instrumented.AndroidTestListener} + * encapsulating a {@link ProcessHandler}, which is notified on any {@link ServiceMessageBuilder} + * the listener creates. + */ +public class BlazeAndroidTestListener implements ITestRunListener { + + private static final String DISPLAY_PREFIX = "android.studio.display."; + + private final ConsoleView consoleView; + private final ProcessHandler processHandler; + + private long testStartingTime; + private long testSuiteStartingTime; + private String testClassName = null; + + public BlazeAndroidTestListener(ConsoleView consoleView, ProcessHandler processHandler) { + this.consoleView = consoleView; + this.processHandler = processHandler; + } + + @Override + public void testRunStopped(long elapsedTime) { + ExecutionUtils.printlnError(consoleView, "Test run stopped.\n"); + } + + @Override + public void testRunEnded(long elapsedTime, Map runMetrics) { + if (testClassName != null) { + testSuiteFinished(); + } + ExecutionUtils.println(consoleView, "Tests ran to completion."); + } + + @Override + public void testRunFailed(String errorMessage) { + ExecutionUtils.printlnError(consoleView, "Test running failed: " + errorMessage); + } + + @Override + public void testRunStarted(String runName, int testCount) { + ExecutionUtils.println(consoleView, "Started running tests"); + + final ServiceMessageBuilder builder = new ServiceMessageBuilder("enteredTheMatrix"); + notifyProcessHandler(builder); + } + + @Override + public void testStarted(TestIdentifier test) { + if (!Objects.equals(test.getClassName(), testClassName)) { + if (testClassName != null) { + testSuiteFinished(); + } + testClassName = test.getClassName(); + testSuiteStarted(); + } + ServiceMessageBuilder builder = + new ServiceMessageBuilder("testStarted") + .addAttribute("name", test.getTestName()) + .addAttribute( + "locationHint", + AndroidTestLocationProvider.PROTOCOL_ID + + "://" + + test.getClassName() + + '.' + + test.getTestName() + + "()"); + notifyProcessHandler(builder); + testStartingTime = System.currentTimeMillis(); + } + + private void testSuiteStarted() { + testSuiteStartingTime = System.currentTimeMillis(); + ServiceMessageBuilder builder = + new ServiceMessageBuilder("testSuiteStarted") + .addAttribute("name", testClassName) + .addAttribute( + "locationHint", AndroidTestLocationProvider.PROTOCOL_ID + "://" + testClassName); + notifyProcessHandler(builder); + } + + private void testSuiteFinished() { + ServiceMessageBuilder builder = + new ServiceMessageBuilder("testSuiteFinished") + .addAttribute("name", testClassName) + .addAttribute( + "duration", Long.toString(System.currentTimeMillis() - testSuiteStartingTime)); + notifyProcessHandler(builder); + testClassName = null; + } + + @Override + public void testFailed(TestIdentifier test, String stackTrace) { + ServiceMessageBuilder builder = + new ServiceMessageBuilder("testFailed") + .addAttribute("name", test.getTestName()) + .addAttribute("message", "") + .addAttribute("details", stackTrace) + .addAttribute("error", "true"); + notifyProcessHandler(builder); + } + + @Override + public void testAssumptionFailure(TestIdentifier test, String trace) { + ServiceMessageBuilder builder = + ServiceMessageBuilder.testIgnored(test.getTestName()) + .addAttribute("message", "Test ignored. Assumption Failed:") + .addAttribute("details", trace); + notifyProcessHandler(builder); + } + + @Override + public void testIgnored(TestIdentifier test) { + ServiceMessageBuilder builder = ServiceMessageBuilder.testIgnored(test.getTestName()); + notifyProcessHandler(builder); + } + + @Override + public void testEnded(TestIdentifier test, Map testMetrics) { + if (StudioFlags.PRINT_INSTRUMENTATION_STATUS.get()) { + + for (Map.Entry entry : testMetrics.entrySet()) { + String key = entry.getKey(); + if (key.startsWith(DISPLAY_PREFIX)) { + ExecutionUtils.println( + consoleView, key.substring(DISPLAY_PREFIX.length()) + ": " + entry.getValue()); + } + } + } + + ServiceMessageBuilder builder = + new ServiceMessageBuilder("testFinished") + .addAttribute("name", test.getTestName()) + .addAttribute("duration", Long.toString(System.currentTimeMillis() - testStartingTime)); + notifyProcessHandler(builder); + } + + private void notifyProcessHandler(ServiceMessageBuilder serviceMessageBuilder) { + processHandler.notifyTextAvailable(serviceMessageBuilder.toString(), ProcessOutputType.STDOUT); + } +} diff --git a/aswb/src/com/google/idea/blaze/android/run/test/ConnectBlazeTestDebuggerTaskHelper.java b/aswb/src/com/google/idea/blaze/android/run/test/ConnectBlazeTestDebuggerTaskHelper.java deleted file mode 100644 index b4f575fed71..00000000000 --- a/aswb/src/com/google/idea/blaze/android/run/test/ConnectBlazeTestDebuggerTaskHelper.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright 2016 The Bazel Authors. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.idea.blaze.android.run.test; - -import com.android.ddmlib.Client; -import com.android.tools.idea.run.AndroidDebugState; -import com.android.tools.idea.run.AndroidProcessText; -import com.android.tools.idea.run.AndroidRunConfiguration; -import com.android.tools.idea.run.AndroidSessionInfo; -import com.android.tools.idea.run.LaunchInfo; -import com.android.tools.idea.run.ProcessHandlerConsolePrinter; -import com.android.tools.idea.run.tasks.ConnectJavaDebuggerTask; -import com.android.tools.idea.run.util.ProcessHandlerLaunchStatus; -import com.intellij.debugger.engine.RemoteDebugProcessHandler; -import com.intellij.debugger.ui.DebuggerPanelsManager; -import com.intellij.execution.ExecutionException; -import com.intellij.execution.configurations.RemoteConnection; -import com.intellij.execution.configurations.RunConfiguration; -import com.intellij.execution.configurations.RunProfile; -import com.intellij.execution.process.ProcessHandler; -import com.intellij.execution.runners.ExecutionEnvironment; -import com.intellij.execution.runners.ExecutionEnvironmentBuilder; -import com.intellij.execution.ui.RunContentDescriptor; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import java.util.Locale; - -/** Connects the blaze debugger during execution. */ -class ConnectBlazeTestDebuggerTaskHelper { - private ConnectBlazeTestDebuggerTaskHelper() {} - - /** - * Nearly a clone of {@link ConnectJavaDebuggerTask#launchDebugger}. There are a few changes to - * account for null variables that could occur in our implementation. - */ - public static ProcessHandler launchDebugger( - Project project, - LaunchInfo currentLaunchInfo, - Client client, - ProcessHandlerLaunchStatus launchStatus, - ProcessHandlerConsolePrinter printer) { - String debugPort = Integer.toString(client.getDebuggerListenPort()); - int pid = client.getClientData().getPid(); - Logger.getInstance(ConnectJavaDebuggerTask.class) - .info( - String.format( - Locale.US, - "Attempting to connect debugger to port %1$s [client %2$d]", - debugPort, - pid)); - - // create a new process handler - RemoteConnection connection = new RemoteConnection(true, "localhost", debugPort, false); - RemoteDebugProcessHandler debugProcessHandler = new RemoteDebugProcessHandler(project); - - // switch the launch status and console printers to point to the new process handler - // this is required, esp. for AndroidTestListener which holds a - // reference to the launch status and printers, and those should - // be updated to point to the new process handlers, - // otherwise test results will not be forwarded appropriately - ProcessHandler oldProcessHandler = launchStatus.getProcessHandler(); - launchStatus.setProcessHandler(debugProcessHandler); - printer.setProcessHandler(debugProcessHandler); - - // Detach old process handler after the launch status - // has been updated to point to the new process handler. - oldProcessHandler.detachProcess(); - - AndroidDebugState debugState = - new AndroidDebugState( - project, debugProcessHandler, connection, currentLaunchInfo.consoleProvider); - - RunContentDescriptor oldDescriptor; - AndroidSessionInfo oldSession = oldProcessHandler.getUserData(AndroidSessionInfo.KEY); - if (oldSession != null) { - oldDescriptor = oldSession.getDescriptor(); - } else { - // This is the first time we are attaching the debugger; get it from the environment instead. - oldDescriptor = currentLaunchInfo.env.getContentToReuse(); - } - - RunContentDescriptor debugDescriptor; - try { - // @formatter:off - ExecutionEnvironment debugEnv = - new ExecutionEnvironmentBuilder(currentLaunchInfo.env) - .executor(currentLaunchInfo.executor) - .runner(currentLaunchInfo.runner) - .contentToReuse(oldDescriptor) - .build(); - debugDescriptor = - DebuggerPanelsManager.getInstance(project) - .attachVirtualMachine(debugEnv, debugState, connection, false); - // @formatter:on - } catch (ExecutionException e) { - printer.stderr("ExecutionException: " + e.getMessage() + '.'); - return null; - } - - // Based on the above try block we shouldn't get here unless we have assigned to debugDescriptor - assert debugDescriptor != null; - - // re-run the collected text from the old process handler to the new - // TODO: is there a race between messages received once the debugger has been connected, - // and these messages that are printed out? - final AndroidProcessText oldText = AndroidProcessText.get(oldProcessHandler); - if (oldText != null) { - oldText.printTo(debugProcessHandler); - } - - RunProfile runProfile = currentLaunchInfo.env.getRunProfile(); - RunConfiguration runConfiguration = - runProfile instanceof AndroidRunConfiguration ? (AndroidRunConfiguration) runProfile : null; - AndroidSessionInfo sessionInfo = - AndroidSessionInfo.create( - debugProcessHandler, - debugDescriptor, - runConfiguration, - currentLaunchInfo.env.getExecutor().getId(), - currentLaunchInfo.env.getExecutor().getActionName(), - currentLaunchInfo.env.getExecutionTarget()); - - debugProcessHandler.putUserData(AndroidSessionInfo.KEY, sessionInfo); - debugProcessHandler.putUserData(AndroidSessionInfo.ANDROID_DEBUG_CLIENT, client); - debugProcessHandler.putUserData( - AndroidSessionInfo.ANDROID_DEVICE_API_LEVEL, client.getDevice().getVersion()); - - return debugProcessHandler; - } -} 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 0bbe5c95e38..6ec9004fb57 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 @@ -15,30 +15,33 @@ */ package com.google.idea.blaze.android.run.test; +import com.android.ddmlib.IDevice; import com.android.ddmlib.testrunner.RemoteAndroidTestRunner; +import com.android.tools.idea.execution.common.RunConfigurationNotifier; +import com.android.tools.idea.execution.common.processhandler.AndroidProcessHandler; 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.tasks.LaunchContext; -import com.android.tools.idea.run.tasks.LaunchResult; -import com.android.tools.idea.run.tasks.LaunchTask; -import com.android.tools.idea.run.util.LaunchStatus; -import com.android.tools.idea.testartifacts.instrumented.AndroidTestListener; +import com.android.tools.idea.run.blaze.BlazeLaunchContext; +import com.android.tools.idea.run.blaze.BlazeLaunchTask; +import com.android.tools.idea.run.configuration.execution.ExecutionUtils; import com.google.common.collect.ImmutableList; import com.google.idea.blaze.android.manifest.ManifestParser; import com.google.idea.blaze.android.run.deployinfo.BlazeAndroidDeployInfo; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.process.ProcessHandler; +import com.intellij.execution.ui.ConsoleView; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.text.StringUtil; import java.util.List; +import javax.annotation.Nullable; import org.jetbrains.annotations.NotNull; -class StockAndroidTestLaunchTask implements LaunchTask { +class StockAndroidTestLaunchTask implements BlazeLaunchTask { private static final String ID = "STOCK_ANDROID_TEST"; - private static final Logger LOG = Logger.getInstance(StockAndroidTestLaunchTask.class); - private final BlazeAndroidTestRunConfigurationState configState; private final String instrumentationTestRunner; private final String testApplicationId; @@ -55,27 +58,28 @@ class StockAndroidTestLaunchTask implements LaunchTask { this.testApplicationId = testPackage; } - public static LaunchTask getStockTestLaunchTask( + @Nullable + public static BlazeLaunchTask getStockTestLaunchTask( BlazeAndroidTestRunConfigurationState configState, ApplicationIdProvider applicationIdProvider, boolean waitForDebugger, BlazeAndroidDeployInfo deployInfo, - LaunchStatus launchStatus) { + Project project) + throws ExecutionException { String testPackage; try { testPackage = applicationIdProvider.getTestPackageName(); - if (testPackage == null) { - launchStatus.terminateLaunch("Unable to determine test package name", true); - return null; - } } catch (ApkProvisionException e) { - launchStatus.terminateLaunch("Unable to determine test package name", true); - return null; + throw new ExecutionException("Unable to determine test package name. " + e.getMessage()); + } + if (testPackage == null) { + throw new ExecutionException("Unable to determine test package name."); } - List availableRunners = getRunnersFromManifest(deployInfo); if (availableRunners.isEmpty()) { - launchStatus.terminateLaunch( + RunConfigurationNotifier.INSTANCE.notifyError( + project, + "", String.format( "No instrumentation test runner is defined in the manifest.\n" + "At least one instrumentation tag must be defined for the\n" @@ -91,41 +95,44 @@ public static LaunchTask getStockTestLaunchTask( + " \n" + "\n" + "", - testPackage), - true); + testPackage)); // Note: Gradle users will never see the above message, so don't mention Gradle here. // Even if no runners are defined in build.gradle, Gradle will add a default to the manifest. - return null; + throw new ExecutionException("No instrumentation test runner is defined in the manifest."); } String runner = configState.getInstrumentationRunnerClass(); - if (!StringUtil.isEmpty(runner)) { - if (!availableRunners.contains(runner)) { - launchStatus.terminateLaunch( - String.format( - "Instrumentation test runner \"%2$s\"\n" - + "is not defined for the \"%1$s\" package in the manifest.\n" - + "Clear the 'Specific instrumentation runner' field in your configuration\n" - + "to default to \"%3$s\",\n" - + "or add the runner to your AndroidManifest.xml:\n" - + "\n" - + "\n" - + "\n" - + " \n" - + " \n" - + "\n" - + "", - testPackage, runner, availableRunners.get(0)), - true); - return null; - } - } else { + if (StringUtil.isEmpty(runner)) { // Default to the first available runner. runner = availableRunners.get(0); } + if (!availableRunners.contains(runner)) { + RunConfigurationNotifier.INSTANCE.notifyError( + project, + "", + String.format( + "Instrumentation test runner \"%2$s\"\n" + + "is not defined for the \"%1$s\" package in the manifest.\n" + + "Clear the 'Specific instrumentation runner' field in your configuration\n" + + "to default to \"%3$s\",\n" + + "or add the runner to your AndroidManifest.xml:\n" + + "\n" + + "\n" + + "\n" + + " \n" + + " \n" + + "\n" + + "", + testPackage, runner, availableRunners.get(0))); + throw new ExecutionException( + String.format( + "Instrumentation test runner \"%2$s\" is not defined for the \"%1$s\" package in the" + + " manifest.", + testPackage, runner)); + } return new StockAndroidTestLaunchTask(configState, runner, testPackage, waitForDebugger); } @@ -137,7 +144,6 @@ private static ImmutableList getRunnersFromManifest( .runReadAction( (Computable>) () -> getRunnersFromManifest(deployInfo)); } - ManifestParser.ParsedManifest parsedManifest = deployInfo.getMergedManifest(); if (parsedManifest != null) { return ImmutableList.copyOf(parsedManifest.instrumentationClassNames); @@ -145,24 +151,13 @@ private static ImmutableList getRunnersFromManifest( return ImmutableList.of(); } - @Override - public String getDescription() { - return "Launching instrumentation runner"; - } - - @Override - public int getDuration() { - return 2; - } - - @Override - public LaunchResult run(@NotNull LaunchContext launchContext) { - ConsolePrinter printer = launchContext.getConsolePrinter(); - printer.stdout("Running tests\n"); - + @SuppressWarnings("FutureReturnValueIgnored") + public void run(@NotNull BlazeLaunchContext launchContext) { + ConsoleView console = launchContext.getConsoleView(); + IDevice device = launchContext.getDevice(); + ExecutionUtils.println(console, "Running tests\n"); final RemoteAndroidTestRunner runner = - new RemoteAndroidTestRunner( - testApplicationId, instrumentationTestRunner, launchContext.getDevice()); + new RemoteAndroidTestRunner(testApplicationId, instrumentationTestRunner, device); switch (configState.getTestingType()) { case BlazeAndroidTestRunConfigurationState.TEST_ALL_IN_MODULE: break; @@ -176,32 +171,37 @@ public LaunchResult run(@NotNull LaunchContext launchContext) { runner.setMethodName(configState.getClassName(), configState.getMethodName()); break; default: - LOG.error(String.format("Unrecognized testing type: %d", configState.getTestingType())); - return LaunchResult.error("", getDescription()); + throw new RuntimeException( + String.format("Unrecognized testing type: %d", configState.getTestingType())); } runner.setDebug(waitForDebugger); runner.setRunOptions(configState.getExtraOptions()); - - printer.stdout("$ adb shell " + runner.getAmInstrumentCommand()); - + ExecutionUtils.printShellCommand(console, runner.getAmInstrumentCommand()); // run in a separate thread as this will block until the tests complete ApplicationManager.getApplication() .executeOnPooledThread( () -> { try { - runner.run(new AndroidTestListener(printer)); + // This issues "am instrument" command and blocks execution. + runner.run( + new BlazeAndroidTestListener(console, launchContext.getProcessHandler())); + // Detach the device from the android process handler manually as soon as "am + // instrument" command finishes. This is required because the android process + // handler may overlook target process especially when the test + // runs really fast (~10ms). Because the android process handler discovers new + // processes by polling, this race condition happens easily. By detaching the device + // manually, we can avoid the android process handler waiting for (already finished) + // process to show up until it times out (10 secs). + // Note: this is a copy of ag/9593981, but it is worth figuring out a better + // strategy here if the behavior of AndroidTestListener is not guaranteed. + ProcessHandler processHandler = launchContext.getProcessHandler(); + if (processHandler instanceof AndroidProcessHandler) { + ((AndroidProcessHandler) processHandler).detachDevice(launchContext.getDevice()); + } } catch (Exception e) { - LOG.info(e); - printer.stderr("Error: Unexpected exception while running tests: " + e); + ExecutionUtils.printlnError( + console, "Error: Unexpected exception while running tests: " + e); } }); - - return LaunchResult.success(); - } - - @NotNull - @Override - public String getId() { - return ID; } } 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 092c40c614e..e3b2e808654 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 @@ -102,7 +102,7 @@ public String getTestFilter(Project project, List> testLocations) { String filter = BlazeJUnitTestFilterFlags.testFilterForClassesAndMethods( failedClassesAndMethods, JUnitVersion.JUNIT_4); - return filter != null ? BlazeFlags.TEST_FILTER + "=" + filter : null; + return filter != null ? String.format("%s='%s'", BlazeFlags.TEST_FILTER, filter) : null; } private static void appendTest(Map>> map, Location location) { diff --git a/aswb/src/com/google/idea/blaze/android/run/testrecorder/TestRecorderBlazeCommandRunConfigurationProxy.java b/aswb/src/com/google/idea/blaze/android/run/testrecorder/TestRecorderBlazeCommandRunConfigurationProxyBase.java similarity index 79% rename from aswb/src/com/google/idea/blaze/android/run/testrecorder/TestRecorderBlazeCommandRunConfigurationProxy.java rename to aswb/src/com/google/idea/blaze/android/run/testrecorder/TestRecorderBlazeCommandRunConfigurationProxyBase.java index aa6e7713145..5387ae97d31 100644 --- a/aswb/src/com/google/idea/blaze/android/run/testrecorder/TestRecorderBlazeCommandRunConfigurationProxy.java +++ b/aswb/src/com/google/idea/blaze/android/run/testrecorder/TestRecorderBlazeCommandRunConfigurationProxyBase.java @@ -15,36 +15,30 @@ */ package com.google.idea.blaze.android.run.testrecorder; -import com.android.annotations.Nullable; -import com.android.ddmlib.IDevice; -import com.google.common.util.concurrent.ListenableFuture; import com.google.gct.testrecorder.run.TestRecorderRunConfigurationProxy; import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryRunConfigurationHandler; import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryRunConfigurationState; -import com.google.idea.blaze.android.run.runner.BlazeAndroidRunConfigurationRunner; import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration; import com.google.idea.blaze.base.sync.data.BlazeDataStorage; import com.google.idea.blaze.base.sync.projectstructure.ModuleFinder; import com.intellij.execution.configurations.LocatableConfigurationBase; -import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.openapi.module.Module; -import java.util.List; /** {@link TestRecorderRunConfigurationProxy} for blaze. */ -public class TestRecorderBlazeCommandRunConfigurationProxy +public abstract class TestRecorderBlazeCommandRunConfigurationProxyBase implements TestRecorderRunConfigurationProxy { private final BlazeCommandRunConfiguration myBaseConfiguration; private final BlazeAndroidBinaryRunConfigurationHandler myBaseConfigurationHandler; - public TestRecorderBlazeCommandRunConfigurationProxy( + public TestRecorderBlazeCommandRunConfigurationProxyBase( BlazeCommandRunConfiguration baseConfiguration) { myBaseConfiguration = baseConfiguration; myBaseConfigurationHandler = (BlazeAndroidBinaryRunConfigurationHandler) baseConfiguration.getHandler(); } - @Override + // @Override Removed #api232 public LocatableConfigurationBase getTestRecorderRunConfiguration() { return new TestRecorderBlazeCommandRunConfiguration(myBaseConfiguration); } @@ -64,7 +58,7 @@ public boolean isLaunchActivitySupported() { || BlazeAndroidBinaryRunConfigurationState.LAUNCH_SPECIFIC_ACTIVITY.equals(mode); } - @Override + // @Override Removed #api232 public String getLaunchActivityClass() { BlazeAndroidBinaryRunConfigurationState state = myBaseConfigurationHandler.getState(); @@ -74,13 +68,4 @@ public String getLaunchActivityClass() { return ""; } - - @Nullable - @Override - public List> getDeviceFutures(ExecutionEnvironment environment) { - return environment - .getCopyableUserData(BlazeAndroidRunConfigurationRunner.DEVICE_SESSION_KEY) - .deviceFutures - .get(); - } } diff --git a/aswb/src/com/google/idea/blaze/android/sdk/BlazeSdkProvider.java b/aswb/src/com/google/idea/blaze/android/sdk/BlazeSdkProvider.java index 866351d88ee..2a5e43fe22e 100644 --- a/aswb/src/com/google/idea/blaze/android/sdk/BlazeSdkProvider.java +++ b/aswb/src/com/google/idea/blaze/android/sdk/BlazeSdkProvider.java @@ -15,7 +15,7 @@ */ package com.google.idea.blaze.android.sdk; -import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.projectRoots.Sdk; import java.util.List; import javax.annotation.Nullable; @@ -23,7 +23,7 @@ /** Indirection to Sdks for testing purposes. */ public interface BlazeSdkProvider { static BlazeSdkProvider getInstance() { - return ServiceManager.getService(BlazeSdkProvider.class); + return ApplicationManager.getApplication().getService(BlazeSdkProvider.class); } List getAllAndroidSdks(); diff --git a/aswb/src/com/google/idea/blaze/android/sdk/BlazeSdkProviderImpl.java b/aswb/src/com/google/idea/blaze/android/sdk/BlazeSdkProviderImpl.java index 99b2cba6f58..b4481f9ab32 100644 --- a/aswb/src/com/google/idea/blaze/android/sdk/BlazeSdkProviderImpl.java +++ b/aswb/src/com/google/idea/blaze/android/sdk/BlazeSdkProviderImpl.java @@ -25,6 +25,7 @@ import java.util.List; import javax.annotation.Nullable; import org.jetbrains.android.sdk.AndroidSdkAdditionalData; +import org.jetbrains.android.sdk.AndroidSdkAdditionalDataCompat; /** Indirection to Sdks for testing purposes. */ public class BlazeSdkProviderImpl implements BlazeSdkProvider { @@ -54,8 +55,7 @@ public Sdk findSdk(String targetHash) { @Override @Nullable public String getSdkTargetHash(Sdk sdk) { - AndroidSdkAdditionalData additionalData = - AndroidSdks.getInstance().getAndroidSdkAdditionalData(sdk); + AndroidSdkAdditionalData additionalData = AndroidSdkAdditionalDataCompat.from(sdk); if (additionalData == null) { return null; } diff --git a/aswb/src/com/google/idea/blaze/android/settings/BlazeAndroidUserSettings.java b/aswb/src/com/google/idea/blaze/android/settings/BlazeAndroidUserSettings.java index 81176577075..919c169c9c9 100644 --- a/aswb/src/com/google/idea/blaze/android/settings/BlazeAndroidUserSettings.java +++ b/aswb/src/com/google/idea/blaze/android/settings/BlazeAndroidUserSettings.java @@ -15,8 +15,8 @@ */ package com.google.idea.blaze.android.settings; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.PersistentStateComponent; -import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.components.State; import com.intellij.openapi.components.Storage; import com.intellij.util.xmlb.XmlSerializerUtil; @@ -30,7 +30,7 @@ public class BlazeAndroidUserSettings private boolean useLayoutEditor = false; public static BlazeAndroidUserSettings getInstance() { - return ServiceManager.getService(BlazeAndroidUserSettings.class); + return ApplicationManager.getApplication().getService(BlazeAndroidUserSettings.class); } @Override 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 d94df1ca5d6..f69b26479c0 100644 --- a/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidLibrarySource.java +++ b/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidLibrarySource.java @@ -95,7 +95,7 @@ public AarJarFilter(Collection aarLibraries) { Set aarJarsPaths = new HashSet<>(); for (AarLibrary aarLibrary : aarLibraries) { if (aarLibrary.libraryArtifact != null) { - ArtifactLocation location = aarLibrary.libraryArtifact.jarForIntellijLibrary(); + ArtifactLocation jarLocation = aarLibrary.libraryArtifact.jarForIntellijLibrary(); // Keep track of the relative paths of the "configuration". It might be that we have a // host // config (x86-64) and various target configurations (armv7a, aarch64). @@ -103,10 +103,19 @@ public AarJarFilter(Collection aarLibraries) { // to figure out aar libraries. However, what we picked might not match up with jdeps, and // we'd end up creating a jar library from jdeps. Then when we compare the aar // against the jar library, it won't match unless we ignore the configuration segment. - String configurationLessPath = location.getRelativePath(); + String configurationLessPath = jarLocation.getRelativePath(); if (!configurationLessPath.isEmpty()) { aarJarsPaths.add(configurationLessPath); } + + // filter out srcjar of imported aars to make sure all contents belong to aar will be + // stored in same cache directory + ImmutableList srcJarLocations = + aarLibrary.libraryArtifact.getSourceJars(); + srcJarLocations.stream() + .map(ArtifactLocation::getRelativePath) + .filter(path -> !path.isEmpty()) + .forEach(aarJarsPaths::add); } } this.aarJarsPaths = aarJarsPaths; diff --git a/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidSyncBuildFlagsProvider.java b/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidSyncBuildFlagsProvider.java deleted file mode 100644 index 63de6c49357..00000000000 --- a/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidSyncBuildFlagsProvider.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2022 The Bazel Authors. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.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.BuildFlagsProvider; -import com.google.idea.blaze.base.projectview.ProjectViewSet; -import com.google.idea.blaze.base.projectview.section.sections.SyncFlagsSection; -import com.google.idea.blaze.base.scope.BlazeContext; -import com.google.idea.blaze.base.scope.output.PrintOutput; -import com.google.idea.blaze.base.scope.output.SummaryOutput; -import com.google.idea.blaze.base.scope.output.SummaryOutput.Prefix; -import com.google.idea.common.experiments.BoolExperiment; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import java.util.List; - -/** Provides additional flags for bazel/blaze build and sync commands. */ -public class BlazeAndroidSyncBuildFlagsProvider implements BuildFlagsProvider { - - // Enables auto-setting fat_apk_cpu=x86_64 flag for sync - private static final BoolExperiment forceFatApkCpuExperiment = - new BoolExperiment("blaze.sync.flags.enableForceFatApkCpuExperiment", true); - private static final Logger logger = Logger.getInstance(BlazeAndroidSyncBuildFlagsProvider.class); - - @Override - public void addBuildFlags( - Project project, - ProjectViewSet projectViewSet, - BlazeCommandName command, - BlazeInvocationContext context, - List flags) {} - - @Override - public void addSyncFlags( - Project project, - ProjectViewSet projectViewSet, - BlazeCommandName command, - BlazeContext context, - BlazeInvocationContext invocationContext, - List flags) { - List syncOnlyFlags = - BlazeFlags.expandBuildFlags(projectViewSet.listItems(SyncFlagsSection.KEY)); - if (forceFatApkCpuExperiment.getValue() - && syncOnlyFlags.stream().noneMatch(flag -> flag.contains("fat_apk_cpu"))) { - String message = - "Forcing fat_apk_cpu flag to a single cpu architecture (x86_64) for sync. Refer" - + " to go/auto-set-fat-apk-cpu-to-single-cpu for more details."; - // Print to console only for "build" command. This method is invoked in a number of other - // contexts such as collecting the flags for "blaze info", and we don't want to spam the - // console on all of those invocations. - if (BlazeCommandName.BUILD.equals(command)) { - // Print to both summary and print outputs (i.e. main and subtask window of blaze console) - context.output(SummaryOutput.output(Prefix.INFO, message)); - context.output(PrintOutput.log(message)); - logger.info("Forcing fat_apk_cpu to x86_64 for sync"); - } - flags.add("--fat_apk_cpu=x86_64"); - } - } -} diff --git a/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidSyncPlugin.java b/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidSyncPlugin.java index 3e168bebc40..000b39557a8 100644 --- a/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidSyncPlugin.java +++ b/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidSyncPlugin.java @@ -15,6 +15,7 @@ */ package com.google.idea.blaze.android.sync; + import com.android.tools.idea.model.AndroidModel; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; 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 216d71b2c0d..62a41bf8ba5 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 @@ -40,10 +40,10 @@ import com.google.idea.blaze.base.model.LibraryKey; import com.google.idea.blaze.base.model.primitives.Label; import com.google.idea.blaze.base.scope.BlazeContext; -import com.google.idea.blaze.base.scope.Output; import com.google.idea.blaze.base.scope.output.IssueOutput; import com.google.idea.blaze.base.scope.output.IssueOutput.Category; import com.google.idea.blaze.base.scope.output.PerformanceWarning; +import com.google.idea.blaze.common.Output; import com.google.idea.common.experiments.BoolExperiment; import com.intellij.openapi.project.Project; import java.util.Collection; diff --git a/aswb/src/com/google/idea/blaze/android/sync/importer/BlazeImportUtil.java b/aswb/src/com/google/idea/blaze/android/sync/importer/BlazeImportUtil.java index 680291325ce..1b2ab3540f9 100644 --- a/aswb/src/com/google/idea/blaze/android/sync/importer/BlazeImportUtil.java +++ b/aswb/src/com/google/idea/blaze/android/sync/importer/BlazeImportUtil.java @@ -32,12 +32,12 @@ import com.google.idea.blaze.base.projectview.ProjectViewManager; import com.google.idea.blaze.base.projectview.ProjectViewSet; import com.google.idea.blaze.base.scope.BlazeContext; -import com.google.idea.blaze.base.scope.Output; import com.google.idea.blaze.base.scope.output.IssueOutput; import com.google.idea.blaze.base.settings.Blaze; import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; import com.google.idea.blaze.base.sync.projectview.ImportRoots; import com.google.idea.blaze.base.sync.projectview.ProjectViewTargetImportFilter; +import com.google.idea.blaze.common.Output; import com.google.idea.blaze.java.sync.model.BlazeJarLibrary; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.text.StringUtil; diff --git a/aswb/src/com/google/idea/blaze/android/sync/model/AarLibrary.java b/aswb/src/com/google/idea/blaze/android/sync/model/AarLibrary.java index 4d78505654c..4e8509bdb53 100644 --- a/aswb/src/com/google/idea/blaze/android/sync/model/AarLibrary.java +++ b/aswb/src/com/google/idea/blaze/android/sync/model/AarLibrary.java @@ -15,20 +15,21 @@ */ package com.google.idea.blaze.android.sync.model; +import com.android.SdkConstants; import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; import com.google.devtools.intellij.model.ProjectData; import com.google.idea.blaze.android.libraries.UnpackedAars; import com.google.idea.blaze.base.ideinfo.ArtifactLocation; import com.google.idea.blaze.base.ideinfo.LibraryArtifact; import com.google.idea.blaze.base.model.BlazeLibrary; +import com.google.idea.blaze.base.model.BlazeProjectData; +import com.google.idea.blaze.base.model.LibraryFilesProvider; import com.google.idea.blaze.base.model.LibraryKey; import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder; -import com.google.idea.blaze.java.libraries.JarCache; import com.google.idea.common.experiments.BoolExperiment; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; -import com.intellij.openapi.roots.OrderRootType; -import com.intellij.openapi.roots.libraries.Library.ModifiableModel; import com.intellij.openapi.util.text.StringUtil; import java.io.File; import java.util.Objects; @@ -106,50 +107,6 @@ public ProjectData.BlazeLibrary toProto() { return super.toProto().toBuilder().setAarLibrary(aarLibraryBuilder.build()).build(); } - /** - * Create an IntelliJ library that matches Android Studio's expectation for an AAR. See {@link - * org.jetbrains.android.facet.ResourceFolderManager#addAarsFromModuleLibraries}. - */ - @Override - public void modifyLibraryModel( - Project project, - ArtifactLocationDecoder artifactLocationDecoder, - ModifiableModel libraryModel) { - UnpackedAars unpackedAars = UnpackedAars.getInstance(project); - - File resourceDirectory = unpackedAars.getResourceDirectory(artifactLocationDecoder, this); - if (resourceDirectory == null) { - logger.warn("No resource directory found for aar: " + aarArtifact); - return; - } - libraryModel.addRoot(pathToUrl(resourceDirectory), OrderRootType.CLASSES); - - // aars that were generated by the aspect to expose resources external to the project don't - // have class jars or sources - if (libraryArtifact == null) { - return; - } - - File jar = unpackedAars.getClassJar(artifactLocationDecoder, this); - if (jar != null) { - libraryModel.addRoot(pathToUrl(jar), OrderRootType.CLASSES); - } - - // Unconditionally add any linked to source jars. BlazeJarLibrary doesn't do this - it only - // attaches sources for libraries that the user explicitly asks for. We don't do that for two - // reasons: 1) all the logic for attaching sources to a library (AttachSourceJarAction, - // BlazeSourceJarNavigationPolicy, LibraryActionHelper etc) are all tied to Java specific - // libraries, and 2) So far, aar_imports are primarily used for very few 3rd party dependencies. - // Longer term, we may want to make this behave just like the Java libraries. - for (ArtifactLocation srcJar : libraryArtifact.getSourceJars()) { - File sourceJar = - JarCache.getInstance(project).getCachedSourceJar(artifactLocationDecoder, srcJar); - if (sourceJar != null) { - libraryModel.addRoot(pathToUrl(sourceJar), OrderRootType.SOURCES); - } - } - } - @Nullable public File getLintRuleJar(Project project, ArtifactLocationDecoder decoder) { UnpackedAars unpackedAars = UnpackedAars.getInstance(project); @@ -162,6 +119,11 @@ public int hashCode() { return Objects.hash(super.hashCode(), libraryArtifact, aarArtifact, resourcePackage); } + @Override + public LibraryFilesProvider getDefaultLibraryFilesProvider(Project project) { + return new DefaultAarLibraryFilesProvider(project); + } + @Override public boolean equals(Object other) { if (this == other) { @@ -177,4 +139,75 @@ public boolean equals(Object other) { && this.aarArtifact.equals(that.aarArtifact) && Objects.equals(this.resourcePackage, that.resourcePackage); } + + @Override + public String getExtension() { + return SdkConstants.DOT_AAR; + } + + /** An implementation of {@link LibraryFilesProvider} for {@link AarLibrary}. */ + private final class DefaultAarLibraryFilesProvider implements LibraryFilesProvider { + private final Project project; + + DefaultAarLibraryFilesProvider(Project project) { + this.project = project; + } + + @Override + public String getName() { + return AarLibrary.this.key.getIntelliJLibraryName(); + } + + @Override + public ImmutableList getClassFiles(BlazeProjectData blazeProjectData) { + UnpackedAars unpackedAars = UnpackedAars.getInstance(project); + File resourceDirectory = + UnpackedAars.getInstance(project) + .getResourceDirectory(blazeProjectData.getArtifactLocationDecoder(), AarLibrary.this); + if (resourceDirectory == null) { + logger.warn("No resource directory found for aar: " + aarArtifact); + return ImmutableList.of(); + } + + File jar = + unpackedAars.getClassJar(blazeProjectData.getArtifactLocationDecoder(), AarLibrary.this); + // not every aar has class jar attached, do not return non-existent jar to avoid false alarm + // log. + if (jar != null && jar.exists()) { + return ImmutableList.of(resourceDirectory, jar); + } + return ImmutableList.of(resourceDirectory); + } + + @Override + public ImmutableList getSourceFiles(BlazeProjectData blazeProjectData) { + // Unconditionally add any linked to source jars. BlazeJarLibrary doesn't do this - it only + // attaches sources for libraries that the user explicitly asks for. We don't do that for two + // reasons: 1) all the logic for attaching sources to a library (AttachSourceJarAction, + // BlazeSourceJarNavigationPolicy, LibraryActionHelper etc) are all tied to Java specific + // libraries, and 2) So far, aar_imports are primarily used for very few 3rd party + // dependencies. + // Longer term, we may want to make this behave just like the Java libraries. + return UnpackedAars.getInstance(project) + .getCachedSrcJars(blazeProjectData.getArtifactLocationDecoder(), AarLibrary.this); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof DefaultAarLibraryFilesProvider)) { + return false; + } + + DefaultAarLibraryFilesProvider that = (DefaultAarLibraryFilesProvider) other; + return Objects.equals(project, that.project) && getName().equals(that.getName()); + } + + @Override + public int hashCode() { + return Objects.hash(project, getName()); + } + } } diff --git a/aswb/src/com/google/idea/blaze/android/sync/model/AndroidResourceModule.java b/aswb/src/com/google/idea/blaze/android/sync/model/AndroidResourceModule.java index e2cd98e00b9..c5acb1e9f47 100644 --- a/aswb/src/com/google/idea/blaze/android/sync/model/AndroidResourceModule.java +++ b/aswb/src/com/google/idea/blaze/android/sync/model/AndroidResourceModule.java @@ -19,6 +19,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; import com.google.devtools.intellij.model.ProjectData; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.idea.blaze.base.ideinfo.ArtifactLocation; import com.google.idea.blaze.base.ideinfo.ProtoWrapper; import com.google.idea.blaze.base.ideinfo.TargetKey; @@ -155,11 +156,13 @@ public Builder(TargetKey targetKey) { this.sourceTargetKeys.add(targetKey); } + @CanIgnoreReturnValue public Builder addResource(ArtifactLocation resource) { this.resources.add(resource); return this; } + @CanIgnoreReturnValue public Builder addResources(Collection resources) { this.resources.addAll(resources); return this; @@ -169,11 +172,13 @@ public boolean hasResources() { return !this.resources.isEmpty(); } + @CanIgnoreReturnValue public Builder addResourceLibraryKey(String aarLibraryKey) { this.resourceLibraryKeys.add(aarLibraryKey); return this; } + @CanIgnoreReturnValue public Builder addResourceLibraryKeys(Collection aarLibraryKeys) { this.resourceLibraryKeys.addAll(aarLibraryKeys); return this; @@ -183,17 +188,20 @@ public Set getResourceLibraryKeys() { return this.resourceLibraryKeys; } + @CanIgnoreReturnValue public Builder addResourceAndTransitiveResource(ArtifactLocation resource) { this.resources.add(resource); this.transitiveResources.add(resource); return this; } + @CanIgnoreReturnValue public Builder addTransitiveResource(ArtifactLocation resource) { this.transitiveResources.add(resource); return this; } + @CanIgnoreReturnValue public Builder addTransitiveResources(Collection resources) { this.transitiveResources.addAll(resources); return this; @@ -203,30 +211,36 @@ public Set getTransitiveResources() { return this.transitiveResources; } + @CanIgnoreReturnValue public Builder addTransitiveResourceDependency(TargetKey dependency) { this.transitiveResourceDependencies.add(dependency); return this; } + @CanIgnoreReturnValue public Builder addTransitiveResourceDependencies(Collection dependencies) { this.transitiveResourceDependencies.addAll(dependencies); return this; } + @CanIgnoreReturnValue public Builder addTransitiveResourceDependency(Label dependency) { this.transitiveResourceDependencies.add(TargetKey.forPlainTarget(dependency)); return this; } + @CanIgnoreReturnValue public Builder addTransitiveResourceDependency(String dependency) { return addTransitiveResourceDependency(Label.create(dependency)); } + @CanIgnoreReturnValue public Builder addSourceTarget(TargetKey target) { this.sourceTargetKeys.add(target); return this; } + @CanIgnoreReturnValue public Builder addSourceTargets(Collection targetKeys) { this.sourceTargetKeys.addAll(targetKeys); return this; diff --git a/aswb/src/com/google/idea/blaze/android/sync/model/AndroidResourceModuleRegistry.java b/aswb/src/com/google/idea/blaze/android/sync/model/AndroidResourceModuleRegistry.java index f5d0952d25a..0e897ce5a5a 100644 --- a/aswb/src/com/google/idea/blaze/android/sync/model/AndroidResourceModuleRegistry.java +++ b/aswb/src/com/google/idea/blaze/android/sync/model/AndroidResourceModuleRegistry.java @@ -19,7 +19,6 @@ import com.google.common.collect.HashBiMap; import com.google.idea.blaze.android.sync.importer.BlazeAndroidWorkspaceImporter; import com.google.idea.blaze.base.ideinfo.TargetKey; -import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import java.util.HashMap; @@ -58,7 +57,7 @@ public class AndroidResourceModuleRegistry { private final Map targetToResourceModule = new HashMap<>(); public static AndroidResourceModuleRegistry getInstance(Project project) { - return ServiceManager.getService(project, AndroidResourceModuleRegistry.class); + return project.getService(AndroidResourceModuleRegistry.class); } /** diff --git a/aswb/src/com/google/idea/blaze/android/sync/model/idea/BlazeAndroidModelBase.java b/aswb/src/com/google/idea/blaze/android/sync/model/idea/BlazeAndroidModelBase.java index 3d8e9313f53..3e996c8c12f 100644 --- a/aswb/src/com/google/idea/blaze/android/sync/model/idea/BlazeAndroidModelBase.java +++ b/aswb/src/com/google/idea/blaze/android/sync/model/idea/BlazeAndroidModelBase.java @@ -21,9 +21,12 @@ import com.android.tools.idea.model.AndroidModel; import com.android.tools.idea.model.ClassJarProvider; import com.android.tools.lint.detector.api.Desugaring; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.ListenableFuture; import com.google.idea.blaze.base.model.BlazeProjectData; +import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.settings.BlazeImportSettings.ProjectType; import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; import com.google.idea.blaze.base.sync.libraries.LintCollector; import com.intellij.openapi.diagnostic.Logger; @@ -113,9 +116,12 @@ public Set getDesugaring() { return desugarJava8Libs ? Desugaring.FULL : Desugaring.DEFAULT; } - // #api211 @Override + @Override @Nullable public Iterable getLintRuleJarsOverride() { + if (Blaze.getProjectType(project) != ProjectType.ASPECT_SYNC) { + return ImmutableList.of(); + } BlazeProjectData blazeProjectData = BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); return LintCollector.getLintJars(project, blazeProjectData); 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 9c90e0b56b9..8fe8e613809 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,10 @@ 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.qsync.QuerySyncManager; +import com.google.idea.blaze.base.qsync.RenderJarArtifactTracker; +import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.settings.BlazeImportSettings.ProjectType; 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; @@ -65,6 +69,16 @@ public BlazeClassJarProvider(final Project project) { @Override public List getModuleExternalLibraries(Module module) { + + if (Blaze.getProjectType(project) == ProjectType.QUERY_SYNC) { + // As Query Sync has a single workspace module but multiple resource modules + // (TODO(b/283282438): for setting up the resources). All render jars are mapped to the same + // workspace module + RenderJarArtifactTracker renderJarArtifactTracker = + QuerySyncManager.getInstance(project).getRenderJarArtifactTracker(); + return renderJarArtifactTracker.getRenderJars(); + } + BlazeProjectData blazeProjectData = BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); diff --git a/aswb/src/com/google/idea/blaze/android/sync/projectstructure/AndroidFacetModuleCustomizer.java b/aswb/src/com/google/idea/blaze/android/sync/projectstructure/AndroidFacetModuleCustomizer.java index febe5ec730f..7da6e1939a8 100755 --- a/aswb/src/com/google/idea/blaze/android/sync/projectstructure/AndroidFacetModuleCustomizer.java +++ b/aswb/src/com/google/idea/blaze/android/sync/projectstructure/AndroidFacetModuleCustomizer.java @@ -22,6 +22,7 @@ import com.intellij.openapi.module.Module; import org.jetbrains.android.facet.AndroidFacet; import org.jetbrains.android.facet.AndroidFacetProperties; +import org.jetbrains.android.facet.AndroidFacetPropertiesCompat; /** Adds the Android facet to modules imported from {@link AndroidProject}s. */ public class AndroidFacetModuleCustomizer { @@ -61,6 +62,6 @@ private static void configureFacet(AndroidFacet facet, boolean isApp) { facetState.MANIFEST_FILE_RELATIVE_PATH = ""; facetState.RES_FOLDER_RELATIVE_PATH = ""; facetState.ASSETS_FOLDER_RELATIVE_PATH = ""; - facetState.ENABLE_SOURCES_AUTOGENERATION = false; + AndroidFacetPropertiesCompat.enableSourcesAutogeneration(facetState, false); } } diff --git a/aswb/src/com/google/idea/blaze/android/sync/projectstructure/BlazeAndroidProjectStructureSyncer.java b/aswb/src/com/google/idea/blaze/android/sync/projectstructure/BlazeAndroidProjectStructureSyncer.java index 0527bfb3db1..2382d6b168e 100644 --- a/aswb/src/com/google/idea/blaze/android/sync/projectstructure/BlazeAndroidProjectStructureSyncer.java +++ b/aswb/src/com/google/idea/blaze/android/sync/projectstructure/BlazeAndroidProjectStructureSyncer.java @@ -53,10 +53,10 @@ import com.google.idea.blaze.base.projectview.ProjectViewSet; import com.google.idea.blaze.base.projectview.section.sections.BuildFlagsSection; import com.google.idea.blaze.base.scope.BlazeContext; -import com.google.idea.blaze.base.scope.output.PrintOutput; import com.google.idea.blaze.base.sync.BlazeSyncPlugin; import com.google.idea.blaze.base.sync.projectstructure.ModuleFinder; import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder; +import com.google.idea.blaze.common.PrintOutput; import com.google.idea.blaze.java.AndroidBlazeRules.RuleTypes; import com.google.idea.common.experiments.BoolExperiment; import com.intellij.openapi.diagnostic.Logger; @@ -388,7 +388,8 @@ private static File manifestFileForAndroidTarget( /** * Parses the provided manifest to calculate applicationId. Returns the provided default if the - * manifest file does not exist, or is invalid + * manifest file does not exist, or is invalid. This method is potentially called concurrently + * from background threads so it must be thread safe. */ static String getApplicationIdFromManifestOrDefault( Project project, diff --git a/aswb/src/com/google/idea/blaze/android/sync/qsync/AndroidExternalLibraryManager.java b/aswb/src/com/google/idea/blaze/android/sync/qsync/AndroidExternalLibraryManager.java new file mode 100644 index 00000000000..2a5c0e551e9 --- /dev/null +++ b/aswb/src/com/google/idea/blaze/android/sync/qsync/AndroidExternalLibraryManager.java @@ -0,0 +1,94 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.qsync; + +import com.android.ide.common.util.PathString; +import com.android.projectmodel.ExternalAndroidLibrary; +import com.android.projectmodel.ExternalLibraryImpl; +import com.android.projectmodel.SelectiveResourceFolder; +import com.google.idea.blaze.android.manifest.ManifestParser; +import com.google.idea.blaze.android.manifest.ManifestParser.ParsedManifest; +import com.intellij.openapi.diagnostic.Logger; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; +import javax.annotation.Nullable; + +/** Retrieves {@link ExternalAndroidLibrary} for external aars */ +public final class AndroidExternalLibraryManager { + private static final Logger logger = Logger.getInstance(AndroidExternalLibraryManager.class); + + private final Supplier> pathSupplier; + private final Map librariesByPath; + + public AndroidExternalLibraryManager(Supplier> pathSupplier) { + this.pathSupplier = pathSupplier; + this.librariesByPath = new HashMap<>(); + } + + public synchronized Collection getExternalLibraries() { + Collection currentPaths = pathSupplier.get(); + librariesByPath.keySet().retainAll(currentPaths); + + for (Path path : currentPaths) { + // TODO: This assumes that a given dependency will never change over the lifetime of a + // project, which may not always be true. + if (librariesByPath.containsKey(path)) { + continue; + } else { + ExternalAndroidLibrary externalAndroidLibrary = toExternalLibrary(path); + if (externalAndroidLibrary != null) { + librariesByPath.put(path, externalAndroidLibrary); + } + } + } + return librariesByPath.values(); + } + + @Nullable + private static ExternalAndroidLibrary toExternalLibrary(Path libraryPath) { + File aarFile = libraryPath.toFile(); + File manifest = new File(aarFile, "AndroidManifest.xml"); + if (!manifest.exists()) { + logger.warn(String.format("No manifest for library %s", aarFile.getName())); + return null; + } + + String javaPackage; + try (InputStream is = new FileInputStream(manifest)) { + ParsedManifest parsedManifest = ManifestParser.parseManifestFromInputStream(is); + javaPackage = parsedManifest.packageName; + } catch (IOException ioe) { + logger.warn( + String.format("Could not parse package from manifest in library %s", aarFile.getName())); + return null; + } + + PathString resFolderPathString = new PathString(new File(aarFile, "res")); + return new ExternalLibraryImpl(aarFile.getName()) + .withLocation(new PathString(aarFile)) + .withManifestFile(new PathString(manifest)) + .withResFolder(new SelectiveResourceFolder(resFolderPathString, null)) + .withSymbolFile(resFolderPathString.getParentOrRoot().resolve("R.txt")) + .withPackageName(javaPackage); + } +} 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 220fe4a2bce..7301a0080ab 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 @@ -24,19 +24,19 @@ import com.google.idea.blaze.android.sync.model.AndroidSdkPlatform; import com.google.idea.blaze.base.projectview.ProjectViewSet; import com.google.idea.blaze.base.projectview.ProjectViewSet.ProjectViewFile; -import com.google.idea.blaze.base.scope.BlazeContext; import com.google.idea.blaze.base.scope.output.IssueOutput; +import com.google.idea.blaze.base.sync.BlazeSyncManager; +import com.google.idea.blaze.common.Context; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.pom.Navigatable; import java.util.Collection; import java.util.List; import java.util.Set; import javax.annotation.Nullable; -import org.jetbrains.android.sdk.AndroidPlatform; -import org.jetbrains.android.sdk.AndroidSdkAdditionalData; +import org.jetbrains.android.sdk.AndroidPlatformCompat; /** Calculates AndroidSdkPlatform. */ -public class AndroidSdkFromProjectView { +public final class AndroidSdkFromProjectView { private static final Joiner COMMA_JOINER = Joiner.on(", "); public static final String NO_SDK_ERROR_TEMPLATE = "No such android_sdk_platform: '%s'. " @@ -46,10 +46,11 @@ public class AndroidSdkFromProjectView { @Nullable public static AndroidSdkPlatform getAndroidSdkPlatform( - BlazeContext context, ProjectViewSet projectViewSet) { + Context context, ProjectViewSet projectViewSet) { List sdks = BlazeSdkProvider.getInstance().getAllAndroidSdks(); if (sdks.isEmpty()) { - IssueOutput.error("No Android SDK configured. Please use the SDK manager to configure.") + String msg = "No Android SDK configured. Please use the SDK manager to configure."; + IssueOutput.error(msg) .navigatable( new Navigatable() { @Override @@ -68,6 +69,7 @@ public boolean canNavigateToSource() { } }) .submit(context); + BlazeSyncManager.printAndLogError(msg, context); return null; } if (projectViewSet == null) { @@ -79,23 +81,26 @@ public boolean canNavigateToSource() { if (androidSdk == null) { ProjectViewFile projectViewFile = projectViewSet.getTopLevelProjectViewFile(); - IssueOutput.error( - ("No android platform configured, please select one using the `android_sdk_platform`" - + " flag in the project view file. Available android_sdk_platforms are: " - + getAvailableTargetHashesAsList(sdks) - + ". To install more android SDKs, use the SDK manager.")) + String msg = + "No android platform configured, please select one using the `android_sdk_platform`" + + " flag in the project view file. Available android_sdk_platforms are: " + + getAvailableTargetHashesAsList(sdks) + + ". To install more android SDKs, use the SDK manager."; + IssueOutput.error(msg) .inFile(projectViewFile != null ? projectViewFile.projectViewFile : null) .submit(context); + BlazeSyncManager.printAndLogError(msg, context); return null; } Sdk sdk = BlazeSdkProvider.getInstance().findSdk(androidSdk); if (sdk == null) { ProjectViewFile projectViewFile = projectViewSet.getTopLevelProjectViewFile(); - IssueOutput.error( - String.format(NO_SDK_ERROR_TEMPLATE, androidSdk, getAllAvailableTargetHashes())) + String msg = String.format(NO_SDK_ERROR_TEMPLATE, androidSdk, getAllAvailableTargetHashes()); + IssueOutput.error(msg) .inFile(projectViewFile != null ? projectViewFile.projectViewFile : null) .submit(context); + BlazeSyncManager.printAndLogError(msg, context); return null; } @@ -128,14 +133,8 @@ private static String getAllAvailableTargetHashes() { } private static int getAndroidSdkApiLevel(Sdk sdk) { - int androidSdkApiLevel = 1; - AndroidSdkAdditionalData additionalData = (AndroidSdkAdditionalData) sdk.getSdkAdditionalData(); - if (additionalData != null) { - AndroidPlatform androidPlatform = additionalData.getAndroidPlatform(); - if (androidPlatform != null) { - androidSdkApiLevel = androidPlatform.getApiLevel(); - } - } - return androidSdkApiLevel; + return AndroidPlatformCompat.getApiLevel(sdk); } + + private AndroidSdkFromProjectView() {} } diff --git a/aswb/src/com/google/idea/blaze/android/sync/sdk/SdkUtil.java b/aswb/src/com/google/idea/blaze/android/sync/sdk/SdkUtil.java index c4097b9d66b..b58f80f78c0 100644 --- a/aswb/src/com/google/idea/blaze/android/sync/sdk/SdkUtil.java +++ b/aswb/src/com/google/idea/blaze/android/sync/sdk/SdkUtil.java @@ -19,11 +19,15 @@ import static com.android.SdkConstants.RES_FOLDER; import com.android.tools.idea.updater.configure.SdkUpdaterConfigurableProvider; +import com.google.idea.blaze.android.projectview.AndroidSdkPlatformSection; import com.google.idea.blaze.android.sdk.BlazeSdkProvider; -import com.google.idea.blaze.android.sync.model.AndroidSdkPlatform; import com.google.idea.blaze.android.sync.model.BlazeAndroidSyncData; import com.google.idea.blaze.base.logging.EventLoggingService; import com.google.idea.blaze.base.model.BlazeProjectData; +import com.google.idea.blaze.base.projectview.ProjectViewManager; +import com.google.idea.blaze.base.projectview.ProjectViewSet; +import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.settings.BlazeImportSettings.ProjectType; import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; @@ -38,7 +42,9 @@ import com.intellij.openapi.roots.OrderRootType; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.ui.UIUtil; -import org.jetbrains.android.sdk.AndroidPlatform; +import java.util.Optional; +import org.jetbrains.android.sdk.AndroidPlatformCompat; +import org.jetbrains.android.sdk.AndroidPlatformsCompat; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -47,27 +53,39 @@ public class SdkUtil { private static final Logger logger = Logger.getInstance(SdkUtil.class); @Nullable - public static AndroidSdkPlatform getAndroidSdkPlatform(BlazeProjectData blazeProjectData) { + private static String getAndroidSdkPlatform(Project project, BlazeProjectData blazeProjectData) { + // TODO(b/271874279): Retrieve sdk from project data + if (Blaze.getProjectType(project) == ProjectType.QUERY_SYNC) { + ProjectViewSet projectViewSet = ProjectViewManager.getInstance(project).getProjectViewSet(); + if (projectViewSet == null) { + return null; + } + return projectViewSet.getScalarValue(AndroidSdkPlatformSection.KEY).orElse(null); + } + BlazeAndroidSyncData syncData = blazeProjectData.getSyncState().get(BlazeAndroidSyncData.class); - return syncData != null ? syncData.androidSdkPlatform : null; + return Optional.ofNullable(syncData) + .map(data -> data.androidSdkPlatform) + .map(sdk -> sdk.androidSdk) + .orElse(null); } @Nullable - public static AndroidPlatform getAndroidPlatform(@NotNull Project project) { + public static AndroidPlatformCompat getAndroidPlatform(@NotNull Project project) { BlazeProjectData blazeProjectData = BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); if (blazeProjectData == null) { return null; } - AndroidSdkPlatform androidSdkPlatform = getAndroidSdkPlatform(blazeProjectData); + String androidSdkPlatform = getAndroidSdkPlatform(project, blazeProjectData); if (androidSdkPlatform == null) { return null; } - Sdk sdk = BlazeSdkProvider.getInstance().findSdk(androidSdkPlatform.androidSdk); + Sdk sdk = BlazeSdkProvider.getInstance().findSdk(androidSdkPlatform); if (sdk == null) { return null; } - return AndroidPlatform.getInstance(sdk); + return AndroidPlatformsCompat.getInstance(sdk); } /** Opens the SDK manager settings page */ diff --git a/aswb/src/com/google/idea/blaze/android/targetmaps/TargetToBinaryMap.java b/aswb/src/com/google/idea/blaze/android/targetmaps/TargetToBinaryMap.java index 7c89be568f5..256d47b51c2 100644 --- a/aswb/src/com/google/idea/blaze/android/targetmaps/TargetToBinaryMap.java +++ b/aswb/src/com/google/idea/blaze/android/targetmaps/TargetToBinaryMap.java @@ -17,14 +17,13 @@ import com.google.common.collect.ImmutableSet; import com.google.idea.blaze.base.ideinfo.TargetKey; -import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.project.Project; import java.util.Collection; /** Interface to find android binary targets that depend on given target(s) */ public interface TargetToBinaryMap { static TargetToBinaryMap getInstance(Project project) { - return ServiceManager.getService(project, TargetToBinaryMap.class); + return project.getService(TargetToBinaryMap.class); } /** Returns a set of android binary targets that depend on any of the given {@code targetKeys} */ diff --git a/aswb/src/org/jetbrains/android/facet/AndroidFacetPropertiesCompat.java b/aswb/src/org/jetbrains/android/facet/AndroidFacetPropertiesCompat.java new file mode 100644 index 00000000000..fff25619e8a --- /dev/null +++ b/aswb/src/org/jetbrains/android/facet/AndroidFacetPropertiesCompat.java @@ -0,0 +1,24 @@ +/* + * Copyright 2022 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.android.facet; + +/** Compat class for AndroidFacetProperties. */ +public class AndroidFacetPropertiesCompat { + + private AndroidFacetPropertiesCompat() {} + + public static void enableSourcesAutogeneration(AndroidFacetProperties props, boolean enable) {} +} diff --git a/aswb/src/org/jetbrains/android/sdk/AndroidPlatformCompat.java b/aswb/src/org/jetbrains/android/sdk/AndroidPlatformCompat.java new file mode 100644 index 00000000000..f745ba55516 --- /dev/null +++ b/aswb/src/org/jetbrains/android/sdk/AndroidPlatformCompat.java @@ -0,0 +1,49 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.android.sdk; + +import com.android.sdklib.BuildToolInfo; +import com.android.tools.sdk.AndroidPlatform; +import com.intellij.openapi.projectRoots.Sdk; + +/** Compat class for {@link com.android.tools.sdk.AndroidPlatform} */ +public class AndroidPlatformCompat { + AndroidPlatform androidPlatform; + + public AndroidPlatformCompat(AndroidPlatform androidPlatform) { + this.androidPlatform = androidPlatform; + } + + public BuildToolInfo getLatestBuildTool(boolean allowBuildTool) { + return androidPlatform.getSdkData().getLatestBuildTool(allowBuildTool); + } + + public int getApiLevel() { + return androidPlatform.getApiLevel(); + } + + public static int getApiLevel(Sdk sdk) { + int androidSdkApiLevel = 1; + AndroidSdkAdditionalData additionalData = (AndroidSdkAdditionalData) sdk.getSdkAdditionalData(); + if (additionalData != null) { + AndroidPlatform androidPlatform = additionalData.getAndroidPlatform(); + if (androidPlatform != null) { + androidSdkApiLevel = androidPlatform.getApiLevel(); + } + } + return androidSdkApiLevel; + } +} diff --git a/aswb/src/org/jetbrains/android/sdk/AndroidPlatformsCompat.java b/aswb/src/org/jetbrains/android/sdk/AndroidPlatformsCompat.java new file mode 100644 index 00000000000..d016d7ce236 --- /dev/null +++ b/aswb/src/org/jetbrains/android/sdk/AndroidPlatformsCompat.java @@ -0,0 +1,32 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.android.sdk; + +import com.intellij.openapi.module.Module; +import com.intellij.openapi.projectRoots.Sdk; + +/** Compat shim class for {@link AndroidPlatforms} */ +public class AndroidPlatformsCompat { + private AndroidPlatformsCompat() {} + + public static AndroidPlatformCompat getInstance(Sdk sdk) { + return new AndroidPlatformCompat(AndroidPlatforms.getInstance(sdk)); + } + + public static AndroidPlatformCompat getInstance(Module module) { + return new AndroidPlatformCompat(AndroidPlatforms.getInstance(module)); + } +} diff --git a/aswb/src/org/jetbrains/android/sdk/AndroidSdkAdditionalDataCompat.java b/aswb/src/org/jetbrains/android/sdk/AndroidSdkAdditionalDataCompat.java new file mode 100644 index 00000000000..6364b6fa8cf --- /dev/null +++ b/aswb/src/org/jetbrains/android/sdk/AndroidSdkAdditionalDataCompat.java @@ -0,0 +1,27 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.android.sdk; + +import com.intellij.openapi.projectRoots.Sdk; + +/** Compat shim class for {@link AndroidSdkAdditionalData} */ +public class AndroidSdkAdditionalDataCompat { + private AndroidSdkAdditionalDataCompat() {} + + public static AndroidSdkAdditionalData from(Sdk sdk) { + return AndroidSdkAdditionalData.from(sdk); + } +} diff --git a/aswb/src/org/jetbrains/android/sdk/AndroidSdkDataCompat.java b/aswb/src/org/jetbrains/android/sdk/AndroidSdkDataCompat.java new file mode 100644 index 00000000000..4aea6d6e832 --- /dev/null +++ b/aswb/src/org/jetbrains/android/sdk/AndroidSdkDataCompat.java @@ -0,0 +1,43 @@ +/* + * Copyright 2023 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.android.sdk; + +import com.android.sdklib.IAndroidTarget; +import com.android.tools.sdk.AndroidSdkData; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.io.File; + +/** Compat class for {@link com.android.tools.sdk.AndroidSdkData} */ +public class AndroidSdkDataCompat { + AndroidSdkData androidSdkData; + + private AndroidSdkDataCompat(File sdkLocation, boolean forceReparse) { + androidSdkData = AndroidSdkData.getSdkData(sdkLocation, forceReparse); + } + + public static AndroidSdkDataCompat getSdkData(String sdkHomepath) { + return new AndroidSdkDataCompat(new File(sdkHomepath), false); + } + + @CanIgnoreReturnValue + public static AndroidSdkDataCompat getSdkData(File sdkLocation, boolean forceReparse) { + return new AndroidSdkDataCompat(sdkLocation, forceReparse); + } + + public IAndroidTarget[] getTargets() { + return androidSdkData.getTargets(); + } +} diff --git a/aswb/tests/integrationtests/as203/com/google/idea/blaze/android/functional/BlazeModuleSystemDependentLibrariesIntegrationTest.java b/aswb/tests/integrationtests/as203/com/google/idea/blaze/android/functional/BlazeModuleSystemDependentLibrariesIntegrationTest.java deleted file mode 100644 index ff77b2bb61f..00000000000 --- a/aswb/tests/integrationtests/as203/com/google/idea/blaze/android/functional/BlazeModuleSystemDependentLibrariesIntegrationTest.java +++ /dev/null @@ -1,276 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.functional; - -import static com.google.common.truth.Truth.assertThat; -import static com.google.idea.blaze.android.targetmapbuilder.NbAarTarget.aar_import; -import static com.google.idea.blaze.android.targetmapbuilder.NbAndroidTarget.android_binary; -import static com.google.idea.blaze.android.targetmapbuilder.NbAndroidTarget.android_library; -import static com.google.idea.blaze.android.targetmapbuilder.NbJavaTarget.java_library; - -import com.android.SdkConstants; -import com.android.ide.common.repository.GradleCoordinate; -import com.android.ide.common.util.PathString; -import com.android.projectmodel.ExternalAndroidLibrary; -import com.android.projectmodel.ExternalLibraryImpl; -import com.android.projectmodel.SelectiveResourceFolder; -import com.android.tools.idea.projectsystem.GoogleMavenArtifactId; -import com.google.common.collect.ImmutableList; -import com.google.idea.blaze.android.BlazeAndroidIntegrationTestCase; -import com.google.idea.blaze.android.MockSdkUtil; -import com.google.idea.blaze.android.libraries.AarLibraryFileBuilder; -import com.google.idea.blaze.android.libraries.UnpackedAarUtils; -import com.google.idea.blaze.android.libraries.UnpackedAars; -import com.google.idea.blaze.android.projectsystem.BlazeModuleSystem; -import com.google.idea.blaze.android.projectsystem.MavenArtifactLocator; -import com.google.idea.blaze.android.targetmapbuilder.NbAarTarget; -import com.google.idea.blaze.android.targetmapbuilder.NbAndroidTarget; -import com.google.idea.blaze.base.ideinfo.ArtifactLocation; -import com.google.idea.blaze.base.io.FileOperationProvider; -import com.google.idea.blaze.base.model.LibraryKey; -import com.google.idea.blaze.base.model.primitives.Label; -import com.google.idea.blaze.base.model.primitives.WorkspacePath; -import com.google.idea.blaze.base.settings.BuildSystemName; -import com.google.idea.blaze.base.sync.data.BlazeDataStorage; -import com.google.idea.common.experiments.ExperimentService; -import com.google.idea.common.experiments.MockExperimentService; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleManager; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.util.PathUtil; -import java.io.File; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Comparator; -import java.util.List; -import java.util.stream.Collectors; -import javax.annotation.Nullable; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -/** Integration test for {@link BlazeModuleSystem#getDependentLibraries()}. */ -@RunWith(JUnit4.class) -public class BlazeModuleSystemDependentLibrariesIntegrationTest - extends BlazeAndroidIntegrationTestCase { - private BlazeModuleSystem appModuleSystem; - private BlazeModuleSystem workspaceModuleSystem; - - @Before - public void setup() { - final String recyclerView = "//third_party/recyclerview:recyclerview"; - final String constraintLayout = "//third_party/constraint_layout:constraint_layout"; - final String quantum = "//third_party/quantum:values"; - final String aarFile = "//third_party/aar:an_aar"; - final String individualLibrary = "//third_party/individualLibrary:values"; - final String guava = "//third_party/guava:java"; - final String main = "//java/com/google:app"; - final String intermediateDependency = "//java/com/google/intermediate:intermediate"; - - // BlazeAndroidRunConfigurationCommonState.isNativeDebuggingEnabled() always - // returns false if this experiment is false. Enable it by setting it to true. - MockExperimentService experimentService = new MockExperimentService(); - registerApplicationComponent(ExperimentService.class, experimentService); - - registerExtension( - MavenArtifactLocator.EP_NAME, - new MavenArtifactLocator() { - @Override - public Label labelFor(GradleCoordinate coordinate) { - switch (GoogleMavenArtifactId.forCoordinate(coordinate)) { - case RECYCLERVIEW_V7: - return Label.create("//third_party/recyclerview:recyclerview"); - case CONSTRAINT_LAYOUT: - return Label.create("//third_party/constraint_layout:constraint_layout"); - default: - return null; - } - } - - @Override - public BuildSystemName buildSystem() { - return BuildSystemName.Blaze; - } - }); - - // This MainActivity.java file is needed because blaze sync will fail if the source - // directory is empty, so we put something there. The fact that it's MainActivity.java - // doesn't mean anything. - workspace.createFile( - new WorkspacePath("java/com/google/app/MainActivity.java"), - "package com.google.app", - "import android.app.Activity;", - "public class MainActivity extends Activity {", - "}"); - - // Make JARs appear nonempty so that they aren't filtered out - registerApplicationService( - FileOperationProvider.class, - new FileOperationProvider() { - @Override - public long getFileSize(File file) { - return file.getName().endsWith("jar") ? 500L : super.getFileSize(file); - } - }); - - setProjectView( - "directories:", - " java/com/google", - "targets:", - " //java/com/google:app", - "android_sdk_platform: android-27"); - MockSdkUtil.registerSdk(workspace, "27"); - - NbAarTarget aarTarget = - aar_import(aarFile) - .aar("lib_aar.aar") - .generated_jar("_aar/an_aar/classes_and_libs_merged.jar"); - AarLibraryFileBuilder.aar(workspaceRoot, aarTarget.getAar().getRelativePath()) - .src( - "res/values/colors.xml", - ImmutableList.of( - "", - "", - " #ffffff", - "")) - .build(); - - NbAndroidTarget binaryTarget = - android_binary(main) - .source_jar("app.jar") - .res("res") - .res_folder("//third_party/shared/res", "app-third_party-shared-res.aar") - .src("app/MainActivity.java") - .dep(guava, quantum, aarFile, intermediateDependency); - binaryTarget - .getAarList() - .forEach(aar -> AarLibraryFileBuilder.aar(workspaceRoot, aar.getRelativePath()).build()); - - NbAndroidTarget quantumTarget = - android_library(quantum) - .res_folder("//third_party/quantum/res", "values-third_party-quantum-res.aar"); - quantumTarget - .getAarList() - .forEach(aar -> AarLibraryFileBuilder.aar(workspaceRoot, aar.getRelativePath()).build()); - - NbAndroidTarget constraintLayoutTarget = - android_library(constraintLayout) - .res_folder( - "//third_party/constraint_layout/res", - "constraint_layout-third_party-constraint_layout-res.aar"); - constraintLayoutTarget - .getAarList() - .forEach(aar -> AarLibraryFileBuilder.aar(workspaceRoot, aar.getRelativePath()).build()); - - setTargetMap( - binaryTarget, - android_library(individualLibrary).res("res"), - java_library(guava).source_jar("//third_party/guava-21.jar"), - quantumTarget, - aarTarget, - android_library(recyclerView).res("res"), - android_library(intermediateDependency).res("res").dep(constraintLayout), - constraintLayoutTarget); - runFullBlazeSyncWithNoIssues(); - - Module appModule = - ModuleManager.getInstance(getProject()).findModuleByName("java.com.google.app"); - appModuleSystem = BlazeModuleSystem.getInstance(appModule); - - Module workspaceModule = - ModuleManager.getInstance(getProject()) - .findModuleByName(BlazeDataStorage.WORKSPACE_MODULE_NAME); - workspaceModuleSystem = BlazeModuleSystem.getInstance(workspaceModule); - } - - private static ArtifactLocation source(String relativePath) { - return ArtifactLocation.builder().setRelativePath(relativePath).setIsSource(true).build(); - } - - private ExternalAndroidLibrary getAarLibrary( - PathString rootPath, String aarPath, @Nullable String resourcePackage) { - String path = rootPath.resolve(aarPath).getNativePath(); - String name = FileUtil.getNameWithoutExtension(PathUtil.getFileName(path)); - String aarDirName = - UnpackedAarUtils.generateAarDirectoryName(name, path.hashCode()) + SdkConstants.DOT_AAR; - UnpackedAars unpackedAars = UnpackedAars.getInstance(getProject()); - File aarDir = new File(unpackedAars.getCacheDir(), aarDirName); - PathString aarFile = new PathString(aarDir); - PathString resFolder = new PathString(UnpackedAarUtils.getResDir(aarDir)); - return new ExternalLibraryImpl(LibraryKey.libraryNameFromArtifactLocation(source(aarPath))) - .withLocation(aarFile) - .withManifestFile( - resFolder == null ? null : resFolder.getParentOrRoot().resolve("AndroidManifest.xml")) - .withResFolder(resFolder == null ? null : new SelectiveResourceFolder(resFolder, null)) - .withSymbolFile(resFolder == null ? null : resFolder.getParentOrRoot().resolve("R.txt")) - .withPackageName(resourcePackage); - } - - @Test - public void getDependencies_multipleModulesGetSameLibraryInstances() { - List workspaceModuleLibraries = - workspaceModuleSystem.getDependentLibraries().stream() - .sorted(Comparator.comparing(ExternalAndroidLibrary::getAddress)) - .collect(Collectors.toList()); - List appModuleLibraries = - appModuleSystem.getDependentLibraries().stream() - .sorted(Comparator.comparing(ExternalAndroidLibrary::getAddress)) - .collect(Collectors.toList()); - assertThat(workspaceModuleLibraries.size()).isEqualTo(appModuleLibraries.size()); - // Two modules depend on same resource libraries, so the reference of libraries should be the - // same i.e. there should not be duplicate library instances - for (int i = 0; i < workspaceModuleLibraries.size(); i++) { - assertThat(workspaceModuleLibraries.get(i)).isSameAs(appModuleLibraries.get(i)); - } - } - - @Test - public void getDependencies_appModule() { - PathString rootPath = new PathString(workspaceRoot.directory()); - Collection libraries = appModuleSystem.getDependentLibraries(); - assertThat(new ArrayList<>(libraries)) - .containsExactly( - getAarLibrary(rootPath, "third_party/aar/lib_aar.aar", "third_party.aar"), - getAarLibrary(rootPath, "java/com/google/app-third_party-shared-res.aar", "com.google"), - getAarLibrary( - rootPath, - "third_party/constraint_layout/constraint_layout-third_party-constraint_layout-res.aar", - "third_party.constraint_layout"), - getAarLibrary( - rootPath, - "third_party/quantum/values-third_party-quantum-res.aar", - "third_party.quantum")); - } - - @Test - public void getDependencies_workspaceModule() { - PathString rootPath = new PathString(workspaceRoot.directory()); - Collection libraries = workspaceModuleSystem.getDependentLibraries(); - assertThat(new ArrayList<>(libraries)) - .containsExactly( - getAarLibrary( - rootPath, - "third_party/quantum/values-third_party-quantum-res.aar", - "third_party.quantum"), - getAarLibrary(rootPath, "java/com/google/app-third_party-shared-res.aar", "com.google"), - getAarLibrary( - rootPath, - "third_party/constraint_layout/constraint_layout-third_party-constraint_layout-res.aar", - "third_party.constraint_layout"), - getAarLibrary(rootPath, "third_party/aar/lib_aar.aar", "third_party.aar")); - } -} diff --git a/aswb/tests/integrationtests/com/google/idea/blaze/android/AswbGotoDeclarationTest.java b/aswb/tests/integrationtests/com/google/idea/blaze/android/AswbGotoDeclarationTest.java index 6861805245f..33e4e0c1655 100644 --- a/aswb/tests/integrationtests/com/google/idea/blaze/android/AswbGotoDeclarationTest.java +++ b/aswb/tests/integrationtests/com/google/idea/blaze/android/AswbGotoDeclarationTest.java @@ -22,7 +22,7 @@ import com.android.SdkConstants; import com.android.tools.idea.res.AarResourceRepositoryCache; import com.google.common.collect.ImmutableList; -import com.google.idea.blaze.android.libraries.AarLibraryFileBuilder; +import com.google.idea.blaze.android.libraries.LibraryFileBuilder; import com.google.idea.blaze.android.libraries.UnpackedAarUtils; import com.google.idea.blaze.android.libraries.UnpackedAars; import com.google.idea.blaze.android.targetmapbuilder.NbAarTarget; @@ -80,8 +80,8 @@ public void gotoDeclaration_withExternalResources() { // is invoked on elements declared in the AAR, the IDE should open the resource file inside the // unpacked AAR. File aarContainingStylesXml = - AarLibraryFileBuilder.aar(workspaceRoot, "java/com/foo/libs/libs_aar.aar") - .src( + LibraryFileBuilder.aar(workspaceRoot, "java/com/foo/libs/libs_aar.aar") + .addContent( "res/values/styles.xml", ImmutableList.of( "", @@ -167,8 +167,8 @@ public void gotoDeclaration_fromSrcToAarResources() { .generated_jar("classes_and_libs_merged.jar"); File aarLibraryFile = - AarLibraryFileBuilder.aar(workspaceRoot, aarTarget.getAar().getRelativePath()) - .src( + LibraryFileBuilder.aar(workspaceRoot, aarTarget.getAar().getRelativePath()) + .addContent( "res/layout/activity_aar.xml", ImmutableList.of( "", @@ -180,7 +180,7 @@ public void gotoDeclaration_fromSrcToAarResources() { " android:paddingRight=\"16dp\"", " tools:context=\".MainActivity\" >", "")) - .src( + .addContent( "res/values/colors.xml", ImmutableList.of( "", @@ -218,8 +218,8 @@ public void gotoDeclaration_fromResourceToAarResources() { .aar("lib_aar.aar") .generated_jar("classes_and_libs_merged.jar"); File aarLibraryFile = - AarLibraryFileBuilder.aar(workspaceRoot, aarTarget.getAar().getRelativePath()) - .src( + LibraryFileBuilder.aar(workspaceRoot, aarTarget.getAar().getRelativePath()) + .addContent( "res/values/colors.xml", ImmutableList.of( "", @@ -249,8 +249,8 @@ public void gotoDeclaration_fromAarResourceToAarResources() { .aar("lib_aar.aar") .generated_jar("classes_and_libs_merged.jar"); File aarLibraryFile = - AarLibraryFileBuilder.aar(workspaceRoot, aarTarget.getAar().getRelativePath()) - .src( + LibraryFileBuilder.aar(workspaceRoot, aarTarget.getAar().getRelativePath()) + .addContent( "res/values/colors.xml", ImmutableList.of( "", diff --git a/aswb/tests/integrationtests/com/google/idea/blaze/android/functional/AswbRenderTaskTest.java b/aswb/tests/integrationtests/com/google/idea/blaze/android/functional/AswbRenderTaskTest.java deleted file mode 100644 index 5d6c44154c9..00000000000 --- a/aswb/tests/integrationtests/com/google/idea/blaze/android/functional/AswbRenderTaskTest.java +++ /dev/null @@ -1,297 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.functional; - -import static com.google.common.truth.Truth.assertThat; -import static com.google.idea.blaze.android.targetmapbuilder.NbAndroidTarget.android_library; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.mock; - -import com.android.ide.common.rendering.api.Result; -import com.android.ide.common.rendering.api.ViewInfo; -import com.android.tools.idea.configurations.Configuration; -import com.android.tools.idea.rendering.AswbRenderTestUtils; -import com.android.tools.idea.rendering.RenderLogger; -import com.android.tools.idea.rendering.RenderResult; -import com.android.tools.idea.rendering.RenderService; -import com.android.tools.idea.rendering.RenderTask; -import com.google.common.util.concurrent.Futures; -import com.google.idea.blaze.android.AswbImageDiffUtil; -import com.google.idea.blaze.android.BlazeAndroidIntegrationTestCase; -import com.google.idea.blaze.android.MockSdkUtil; -import com.google.idea.blaze.android.fixtures.ManifestFixture; -import com.google.idea.blaze.android.libraries.AarLibraryFileBuilder; -import com.google.idea.blaze.android.targetmapbuilder.NbAndroidTarget; -import com.google.idea.blaze.base.model.primitives.WorkspacePath; -import com.google.idea.blaze.base.sync.projectstructure.ModuleFinder; -import com.intellij.openapi.application.WriteAction; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.projectRoots.ProjectJdkTable; -import com.intellij.openapi.projectRoots.Sdk; -import com.intellij.openapi.roots.ModuleRootModificationUtil; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiManager; -import com.intellij.util.containers.ImmutableList; -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.concurrent.TimeUnit; -import javax.imageio.ImageIO; -import org.intellij.lang.annotations.Language; -import org.jetbrains.android.facet.AndroidFacet; -import org.junit.After; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -/** Tests {@link RenderTask} for blaze projects. */ -@RunWith(JUnit4.class) -public class AswbRenderTaskTest extends BlazeAndroidIntegrationTestCase { - @Language("XML") - private static final String LAYOUT_XML = - "\n" - + "\n" - + " \n" - + " \n" - + " \n" - + ""; - - @Language("XML") - private static final String DIMENS_XML = - "\n" - + "\n" - + " 100px\n" - + " 200px\n" - + ""; - - @Before - public void setUpRenderTaskTest() throws Exception { - AswbRenderTestUtils.beforeRenderTestCase(); - } - - @After - public void tearDownRenderTaskTest() throws Exception { - AswbRenderTestUtils.afterRenderTestCase(); - } - - @Test - public void testLayoutReferenceResourceFromSameModule() throws Exception { - Sdk sdk = MockSdkUtil.registerSdk(workspace, "29"); - VirtualFile layoutXml = - workspace.createFile( - new WorkspacePath("java/com/foo/res/layout/activity_main.xml"), LAYOUT_XML); - workspace.createFile(new WorkspacePath("java/com/foo/res/values/dimens.xml"), DIMENS_XML); - VirtualFile icBannerPng = - workspace.createFile(new WorkspacePath("java/com/foo/res/drawable/ic_banner.png")); - WriteAction.run( - () -> - icBannerPng.setBinaryContent( - Files.readAllBytes(getTestResource("testdata/ic_banner.png")))); - - setProjectView( - "directories:", - " java/com/foo", - "targets:", - " //java/com/foo:foo", - "android_sdk_platform: android-29"); - - setTargetMap( - android_library("//java/com/foo:foo") - .res("res") - .manifest(new ManifestFixture.Factory(getProject(), workspace).fromPackage("com.foo"))); - - runFullBlazeSync(); - // RenderTask makes use of a real SDK instance. - Module resourceModule = getResourceModuleWithSdk("java.com.foo.foo", sdk); - RenderResult renderResult = withRenderTask(resourceModule, layoutXml); - checkRenderResult(renderResult); - } - - @Test - public void testLayoutReferenceResourceFromModuleDependency() throws Exception { - Sdk sdk = MockSdkUtil.registerSdk(workspace, "29"); - VirtualFile layoutXml = - workspace.createFile( - new WorkspacePath("java/com/foo/res/layout/activity_main.xml"), LAYOUT_XML); - workspace.createFile(new WorkspacePath("java/com/bar/res/values/dimens.xml"), DIMENS_XML); - VirtualFile icBannerPng = - workspace.createFile(new WorkspacePath("java/com/bar/res/drawable/ic_banner.png")); - WriteAction.run( - () -> - icBannerPng.setBinaryContent( - Files.readAllBytes(getTestResource("testdata/ic_banner.png")))); - - // Module foo is dependent on module bar; bar is a module because it's included under - // "directories" in project view. - setProjectView( - "directories:", - " java/com/foo", - " java/com/bar", - "targets:", - " //java/com/foo:foo", - "android_sdk_platform: android-29"); - - setTargetMap( - android_library("//java/com/foo:foo") - .dep("//java/com/bar:bar") - .res("res") - .manifest(new ManifestFixture.Factory(getProject(), workspace).fromPackage("com.foo")), - android_library("//java/com/bar:bar") - .res("res") - .manifest(new ManifestFixture.Factory(getProject(), workspace).fromPackage("com.bar"))); - - runFullBlazeSync(); - // RenderTask makes use of a real SDK instance. - Module resourceModule = getResourceModuleWithSdk("java.com.foo.foo", sdk); - RenderResult renderResult = withRenderTask(resourceModule, layoutXml); - checkRenderResult(renderResult); - } - - @Ignore("b/174590445") - @Test - public void testLayoutReferenceResourceFromLibraryDependency() throws Exception { - Sdk sdk = MockSdkUtil.registerSdk(workspace, "29"); - VirtualFile layoutXml = - workspace.createFile( - new WorkspacePath("java/com/foo/res/layout/activity_main.xml"), LAYOUT_XML); - workspace.createFile(new WorkspacePath("java/com/bar/res/values/dimens.xml"), DIMENS_XML); - - // Module foo is dependent on library bar; bar is a library because it's not included anywhere - // in project view. - setProjectView( - "directories:", - " java/com/foo", - "targets:", - " //java/com/foo:foo", - "android_sdk_platform: android-29"); - - NbAndroidTarget bar = - android_library("//java/com/bar:bar") - .res_folder("//java/com/bar/res", "bar-java-com-bar-res.aar"); - AarLibraryFileBuilder.aar(workspaceRoot, bar.getAarList().get(0).getRelativePath()) - .src("res/values/dimens.xml", ImmutableList.singleton(DIMENS_XML)) - .src( - "res/drawable/ic_banner.png", - Files.readAllBytes(getTestResource("testdata/ic_banner.png"))) - .build(); - - setTargetMap( - android_library("//java/com/foo:foo") - .dep("//java/com/bar:bar") - .res("res") - .manifest(new ManifestFixture.Factory(getProject(), workspace).fromPackage("com.foo")), - bar); - - runFullBlazeSync(); - // RenderTask makes use of a real SDK instance. - Module resourceModule = getResourceModuleWithSdk("java.com.foo.foo", sdk); - RenderResult renderResult = withRenderTask(resourceModule, layoutXml); - checkRenderResult(renderResult); - } - - private Module getResourceModuleWithSdk(String moduleName, Sdk sdk) { - Module resModule = ModuleFinder.getInstance(getProject()).findModuleByName(moduleName); - ModuleRootModificationUtil.setModuleSdk(resModule, sdk); - Disposer.register( - getTestRootDisposable(), - () -> WriteAction.run(() -> ProjectJdkTable.getInstance().removeJdk(sdk))); - return resModule; - } - - private static RenderResult withRenderTask(Module module, VirtualFile file) throws Exception { - Configuration configuration = AswbRenderTestUtils.getConfiguration(module, file); - RenderLogger logger = mock(RenderLogger.class); - AndroidFacet facet = AndroidFacet.getInstance(module); - PsiFile psiFile = PsiManager.getInstance(module.getProject()).findFile(file); - assertNotNull(psiFile); - RenderService renderService = RenderService.getInstance(module.getProject()); - final RenderTask task = - renderService - .taskBuilder(facet, configuration) - .withLogger(logger) - .withPsiFile(psiFile) - .disableSecurityManager() - .disableDecorations() - .build() - .get(); - - assertNotNull(task); - RenderResult result = Futures.getUnchecked(task.render()); - if (!task.isDisposed()) { - task.dispose().get(5, TimeUnit.SECONDS); - } - return result; - } - - /** - * Asserts that the given result matches what is expected from {@link #LAYOUT_XML}. Here we expect - * the inner linear layout's dimensions to match the dimensions declared in {@link #DIMENS_XML} - * and the rendered image is identical to the golden image. - */ - private static void checkRenderResult(RenderResult result) { - assertThat(result.getRenderResult().getStatus()).isEqualTo(Result.Status.SUCCESS); - ViewInfo view = result.getRootViews().get(0).getChildren().get(0); - // The inner linear layout should be 200px wide and 100px high as defined in #DIMENS_XML. - // We get the width and height by looking at the difference between layout bounds. - assertThat(view.getRight() - view.getLeft()).isEqualTo(200); - assertThat(view.getBottom() - view.getTop()).isEqualTo(100); - - try { - BufferedImage golden = ImageIO.read(getTestResource("testdata/golden.png").toFile()); - BufferedImage rendered = result.getRenderedImage().getCopy(); - AswbImageDiffUtil.assertImageSimilar("renderedLayout", golden, rendered, 0); - } catch (IOException e) { - fail(e.getMessage()); - } - } - - private static Path getTestResource(String relPath) { - // When running from the IDE the working directory is the module containing the test sources. - Path ideLocalPath = Paths.get(relPath); - if (ideLocalPath.toFile().exists()) { - return ideLocalPath; - } - - // When running from bazel the working directory is the workspace root. Test data files - // are located in the same directory as the test target itself. Directory to the test - // target is exposed as "TEST_BINARY" environment variable. - Path bazelLocalPath = Paths.get(System.getenv("TEST_BINARY")).getParent().resolve(relPath); - if (bazelLocalPath.toFile().exists()) { - return bazelLocalPath; - } - - fail("Test resource file " + relPath + " doesn't exist."); - return null; - } -} diff --git a/aswb/tests/integrationtests/com/google/idea/blaze/android/functional/BlazeInstrumentationTestApkBuildStepIntegrationTest.java b/aswb/tests/integrationtests/com/google/idea/blaze/android/functional/BlazeInstrumentationTestApkBuildStepIntegrationTest.java index a97b95da790..a200e67cfa8 100644 --- a/aswb/tests/integrationtests/com/google/idea/blaze/android/functional/BlazeInstrumentationTestApkBuildStepIntegrationTest.java +++ b/aswb/tests/integrationtests/com/google/idea/blaze/android/functional/BlazeInstrumentationTestApkBuildStepIntegrationTest.java @@ -34,20 +34,17 @@ import com.google.idea.blaze.android.run.deployinfo.BlazeApkDeployInfoProtoHelper.GetDeployInfoException; import com.google.idea.blaze.android.run.runner.BlazeAndroidDeviceSelector.DeviceSession; import com.google.idea.blaze.android.run.runner.BlazeInstrumentationTestApkBuildStep; -import com.google.idea.blaze.android.run.runner.BlazeInstrumentationTestApkBuildStep.InstrumentorToTarget; +import com.google.idea.blaze.android.run.runner.InstrumentationInfo; import com.google.idea.blaze.base.async.process.ExternalTask; import com.google.idea.blaze.base.async.process.ExternalTaskProvider; import com.google.idea.blaze.base.bazel.BuildSystemProvider; import com.google.idea.blaze.base.bazel.BuildSystemProviderWrapper; import com.google.idea.blaze.base.command.buildresult.BuildResultHelper; import com.google.idea.blaze.base.command.buildresult.BuildResultHelper.GetArtifactsException; -import com.google.idea.blaze.base.command.buildresult.ParsedBepOutput; 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.google.idea.blaze.base.scope.output.IssueOutput; -import com.google.idea.blaze.base.sync.aspects.BuildResult; -import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; import java.io.File; import org.junit.Before; import org.junit.Test; @@ -58,9 +55,6 @@ @RunWith(JUnit4.class) public class BlazeInstrumentationTestApkBuildStepIntegrationTest extends BlazeAndroidIntegrationTestCase { - /** Exposed to test methods to toggle presence of execroot */ - private BuildResultHelper mockBuildResultHelper; - private void setupProject() { setProjectView( "directories:", @@ -97,69 +91,20 @@ private void setupProject() { runFullBlazeSyncWithNoIssues(); } - /** Setup build result helper to return BEP output with test execroot by default. */ + /** Setup build system provider with {@link BuildSystemProviderWrapper} */ @Before - public void setupBuildResultHelperProvider() throws GetArtifactsException { - mockBuildResultHelper = mock(BuildResultHelper.class); - when(mockBuildResultHelper.getBuildOutput()) - .thenReturn( - new ParsedBepOutput(null, getExecRoot(), null, null, 0, BuildResult.SUCCESS, 0)); + public void setupBuildSystemProvider() { BuildSystemProviderWrapper buildSystem = new BuildSystemProviderWrapper(() -> getProject()); - buildSystem.setBuildResultHelperSupplier(() -> mockBuildResultHelper); registerExtension(BuildSystemProvider.EP_NAME, buildSystem); } - @Test - public void getInstrumentorToTargetPair_separateInstrumentorAndTestTargets() { - setupProject(); - MessageCollector messageCollector = new MessageCollector(); - BlazeContext context = BlazeContext.create(); - context.addOutputSink(IssueOutput.class, messageCollector); - - BlazeInstrumentationTestApkBuildStep buildStep = - new BlazeInstrumentationTestApkBuildStep( - getProject(), - Label.create("//java/com/foo/app:instrumentation_test"), - ImmutableList.of()); - InstrumentorToTarget pair = - buildStep.getInstrumentorToTargetPair( - context, BlazeProjectDataManager.getInstance(getProject()).getBlazeProjectData()); - - assertThat(pair.instrumentor).isEqualTo(Label.create("//java/com/foo/app:test_app")); - assertThat(pair.target).isEqualTo(Label.create("//java/com/foo/app:app")); - assertThat(pair.isSelfInstrumentingTest()).isFalse(); - assertThat(messageCollector.getMessages()).isEmpty(); - } - - @Test - public void getInstrumentorToTargetPair_selfInstrumentingTest() { - setupProject(); - MessageCollector messageCollector = new MessageCollector(); - BlazeContext context = BlazeContext.create(); - context.addOutputSink(IssueOutput.class, messageCollector); - - BlazeInstrumentationTestApkBuildStep buildStep = - new BlazeInstrumentationTestApkBuildStep( - getProject(), - Label.create("//java/com/foo/app:self_instrumenting_test"), - ImmutableList.of()); - InstrumentorToTarget pair = - buildStep.getInstrumentorToTargetPair( - context, BlazeProjectDataManager.getInstance(getProject()).getBlazeProjectData()); - - assertThat(pair.instrumentor) - .isEqualTo(Label.create("//java/com/foo/app:test_app_self_instrumenting")); - assertThat(pair.target).isNull(); - assertThat(pair.isSelfInstrumentingTest()).isTrue(); - assertThat(messageCollector.getMessages()).isEmpty(); - } - @Test public void deployInfoBuiltCorrectly() throws GetDeployInfoException, ApkProvisionException { setupProject(); - Label testTarget = Label.create("//java/com/foo/app:instrumentation_test"); Label instrumentorTarget = Label.create("//java/com/foo/app:test_app"); Label appTarget = Label.create("//java/com/foo/app:app"); + InstrumentationInfo info = new InstrumentationInfo(appTarget, instrumentorTarget); + BlazeContext context = BlazeContext.create(); ImmutableList blazeFlags = ImmutableList.of("some_blaze_flag", "some_other_flag"); @@ -187,7 +132,7 @@ public void deployInfoBuiltCorrectly() throws GetDeployInfoException, ApkProvisi // Perform BlazeInstrumentationTestApkBuildStep buildStep = - new BlazeInstrumentationTestApkBuildStep(getProject(), testTarget, blazeFlags, helper); + new BlazeInstrumentationTestApkBuildStep(getProject(), info, blazeFlags, helper); buildStep.build(context, new DeviceSession(null, null, null)); // Verify @@ -204,8 +149,9 @@ public void deployInfoBuiltCorrectly() throws GetDeployInfoException, ApkProvisi public void deployInfoBuiltCorrectly_selfInstrumentingTest() throws GetDeployInfoException, ApkProvisionException { setupProject(); - Label testTarget = Label.create("//java/com/foo/app:self_instrumenting_test"); Label instrumentorTarget = Label.create("//java/com/foo/app:test_app_self_instrumenting"); + InstrumentationInfo info = new InstrumentationInfo(null, instrumentorTarget); + BlazeContext context = BlazeContext.create(); ImmutableList blazeFlags = ImmutableList.of("some_blaze_flag", "some_other_flag"); @@ -227,7 +173,7 @@ public void deployInfoBuiltCorrectly_selfInstrumentingTest() // Perform BlazeInstrumentationTestApkBuildStep buildStep = - new BlazeInstrumentationTestApkBuildStep(getProject(), testTarget, blazeFlags, helper); + new BlazeInstrumentationTestApkBuildStep(getProject(), info, blazeFlags, helper); buildStep.build(context, new DeviceSession(null, null, null)); // Verify @@ -242,9 +188,9 @@ public void deployInfoBuiltCorrectly_selfInstrumentingTest() @Test public void exceptionDuringDeployInfoExtraction() throws GetDeployInfoException { setupProject(); - Label testTarget = Label.create("//java/com/foo/app:instrumentation_test"); Label instrumentorTarget = Label.create("//java/com/foo/app:test_app"); Label appTarget = Label.create("//java/com/foo/app:app"); + InstrumentationInfo info = new InstrumentationInfo(instrumentorTarget, appTarget); MessageCollector messageCollector = new MessageCollector(); BlazeContext context = BlazeContext.create(); @@ -269,8 +215,7 @@ public void exceptionDuringDeployInfoExtraction() throws GetDeployInfoException // Perform BlazeInstrumentationTestApkBuildStep buildStep = - new BlazeInstrumentationTestApkBuildStep( - getProject(), testTarget, ImmutableList.of(), helper); + new BlazeInstrumentationTestApkBuildStep(getProject(), info, ImmutableList.of(), helper); buildStep.build(context, new DeviceSession(null, null, null)); // Verify @@ -282,9 +227,9 @@ public void exceptionDuringDeployInfoExtraction() throws GetDeployInfoException @Test public void blazeCommandFailed() throws GetDeployInfoException { setupProject(); - Label testTarget = Label.create("//java/com/foo/app:instrumentation_test"); Label instrumentorTarget = Label.create("//java/com/foo/app:test_app"); Label appTarget = Label.create("//java/com/foo/app:app"); + InstrumentationInfo info = new InstrumentationInfo(appTarget, instrumentorTarget); MessageCollector messageCollector = new MessageCollector(); BlazeContext context = BlazeContext.create(); @@ -313,8 +258,7 @@ public void blazeCommandFailed() throws GetDeployInfoException { // Perform BlazeInstrumentationTestApkBuildStep buildStep = - new BlazeInstrumentationTestApkBuildStep( - getProject(), testTarget, ImmutableList.of(), helper); + new BlazeInstrumentationTestApkBuildStep(getProject(), info, ImmutableList.of(), helper); buildStep.build(context, new DeviceSession(null, null, null)); // Verify @@ -327,9 +271,9 @@ public void blazeCommandFailed() throws GetDeployInfoException { public void nullExecRoot() throws GetDeployInfoException, ApkProvisionException, GetArtifactsException { setupProject(); - Label testTarget = Label.create("//java/com/foo/app:instrumentation_test"); Label instrumentorTarget = Label.create("//java/com/foo/app:test_app"); Label appTarget = Label.create("//java/com/foo/app:app"); + InstrumentationInfo info = new InstrumentationInfo(appTarget, instrumentorTarget); ImmutableList blazeFlags = ImmutableList.of("some_blaze_flag", "some_other_flag"); MessageCollector messageCollector = new MessageCollector(); @@ -337,8 +281,8 @@ public void nullExecRoot() context.addOutputSink(IssueOutput.class, messageCollector); // Return null execroot - when(mockBuildResultHelper.getBuildOutput()) - .thenReturn(new ParsedBepOutput(null, null, null, null, 0, BuildResult.SUCCESS, 0)); + // the only way for execroot to be null is for getBlazeInfo() to throw an exception + BuildSystemProviderWrapper.getInstance(getProject()).setThrowExceptionOnGetBlazeInfo(true); // Setup interceptor for fake running of blaze commands and capture details. ExternalTaskInterceptor externalTaskInterceptor = new ExternalTaskInterceptor(); @@ -364,7 +308,7 @@ public void nullExecRoot() // Perform BlazeInstrumentationTestApkBuildStep buildStep = - new BlazeInstrumentationTestApkBuildStep(getProject(), testTarget, blazeFlags, helper); + new BlazeInstrumentationTestApkBuildStep(getProject(), info, blazeFlags, helper); buildStep.build(context, new DeviceSession(null, null, null)); // Verify @@ -372,56 +316,6 @@ public void nullExecRoot() assertThat(messageCollector.getMessages()).contains("Could not locate execroot!"); } - @Test - public void noTestAppSpecified() { - setProjectView( - "directories:", - " java/com/foo/app", - "targets:", - " //java/com/foo/app:instrumentation_test", - "android_sdk_platform: android-27"); - MockSdkUtil.registerSdk(workspace, "27"); - - workspace.createFile( - new WorkspacePath("java/com/foo/app/MainActivity.java"), - "package com.foo.app", - "import android.app.Activity;", - "public class MainActivity extends Activity {}"); - - workspace.createFile( - new WorkspacePath("java/com/foo/app/Test.java"), - "package com.foo.app", - "public class Test {}"); - - setTargetMap( - android_binary("//java/com/foo/app:app").src("MainActivity.java"), - android_binary("//java/com/foo/app:test_app") - .setResourceJavaPackage("com.foo.app.androidtest") - .src("Test.java") - .instruments("//java/com/foo/app:app"), - android_instrumentation_test("//java/com/foo/app:instrumentation_test")); - runFullBlazeSyncWithNoIssues(); - - MessageCollector messageCollector = new MessageCollector(); - BlazeContext context = BlazeContext.create(); - context.addOutputSink(IssueOutput.class, messageCollector); - - BlazeInstrumentationTestApkBuildStep buildStep = - new BlazeInstrumentationTestApkBuildStep( - getProject(), - Label.create("//java/com/foo/app:instrumentation_test"), - ImmutableList.of()); - InstrumentorToTarget pair = - buildStep.getInstrumentorToTargetPair( - context, BlazeProjectDataManager.getInstance(getProject()).getBlazeProjectData()); - - assertThat(pair).isNull(); - assertThat(messageCollector.getMessages()).hasSize(1); - assertThat(messageCollector.getMessages().get(0)) - .contains( - "No \"test_app\" in target definition for //java/com/foo/app:instrumentation_test."); - } - /** Saves the latest blaze command and context for later verification. */ private static class ExternalTaskInterceptor implements ExternalTaskProvider { ImmutableList command; @@ -434,21 +328,4 @@ public ExternalTask build(ExternalTask.Builder builder) { return scopes -> 0; } } - - @Test - public void findInstrumentorAndTestTargets() { - setupProject(); - BlazeInstrumentationTestApkBuildStep buildStep = - new BlazeInstrumentationTestApkBuildStep( - getProject(), - Label.create("//java/com/foo/app:instrumentation_test"), - ImmutableList.of()); - - InstrumentorToTarget pair = - buildStep.getInstrumentorToTargetPair( - BlazeContext.create(), - BlazeProjectDataManager.getInstance(getProject()).getBlazeProjectData()); - assertThat(pair.instrumentor).isEqualTo(Label.create("//java/com/foo/app:test_app")); - assertThat(pair.target).isEqualTo(Label.create("//java/com/foo/app:app")); - } } diff --git a/aswb/tests/integrationtests/com/google/idea/blaze/android/functional/BlazeModuleSystemExternalDependencyIntegrationTest.java b/aswb/tests/integrationtests/com/google/idea/blaze/android/functional/BlazeModuleSystemExternalDependencyIntegrationTest.java index e0240a4137d..a7e43af430e 100644 --- a/aswb/tests/integrationtests/com/google/idea/blaze/android/functional/BlazeModuleSystemExternalDependencyIntegrationTest.java +++ b/aswb/tests/integrationtests/com/google/idea/blaze/android/functional/BlazeModuleSystemExternalDependencyIntegrationTest.java @@ -15,22 +15,35 @@ */ package com.google.idea.blaze.android.functional; +import static com.android.ide.common.repository.GoogleMavenArtifactIdCompat.CONSTRAINT_LAYOUT_COORDINATE; import static com.google.common.truth.Truth.assertThat; +import static com.google.idea.blaze.android.targetmapbuilder.NbAarTarget.aar_import; import static com.google.idea.blaze.android.targetmapbuilder.NbAndroidTarget.android_library; +import com.android.SdkConstants; import com.android.ide.common.repository.GradleCoordinate; -import com.android.tools.idea.projectsystem.GoogleMavenArtifactId; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.idea.blaze.android.BlazeAndroidIntegrationTestCase; import com.google.idea.blaze.android.MockSdkUtil; +import com.google.idea.blaze.android.libraries.LibraryFileBuilder; +import com.google.idea.blaze.android.libraries.UnpackedAarUtils; +import com.google.idea.blaze.android.libraries.UnpackedAars; import com.google.idea.blaze.android.projectsystem.BlazeModuleSystem; +import com.google.idea.blaze.android.projectsystem.DesugaringLibraryConfigFilesLocator; import com.google.idea.blaze.android.projectsystem.MavenArtifactLocator; +import com.google.idea.blaze.android.targetmapbuilder.NbAarTarget; import com.google.idea.blaze.base.model.primitives.Label; import com.google.idea.blaze.base.model.primitives.WorkspacePath; import com.google.idea.blaze.base.settings.BuildSystemName; import com.google.idea.blaze.base.sync.projectstructure.ModuleFinder; import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.util.PathUtil; +import java.io.File; import java.nio.file.Path; +import java.nio.file.Paths; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -43,13 +56,29 @@ @RunWith(JUnit4.class) public class BlazeModuleSystemExternalDependencyIntegrationTest extends BlazeAndroidIntegrationTestCase { - private static final GradleCoordinate CONSTRAINT_LAYOUT_COORDINATE = - GoogleMavenArtifactId.CONSTRAINT_LAYOUT.getCoordinate("+"); private static final String CONSTRAINT_LAYOUT_LABEL = "//third_party/java/android/android_sdk_linux/extras/android/compatibility/constraint_layout:constraint_layout"; @Before public void setupSourcesAndProjectView() { + registerExtension( + DesugaringLibraryConfigFilesLocator.EP_NAME, + new DesugaringLibraryConfigFilesLocator() { + @Override + public boolean getDesugarLibraryConfigFilesKnown() { + return true; + } + + @Override + public ImmutableList getDesugarLibraryConfigFiles(Project project) { + return ImmutableList.of(Paths.get("a/a.json"), Paths.get("b/b.json")); + } + + @Override + public BuildSystemName buildSystem() { + return BuildSystemName.Blaze; + } + }); registerExtension( MavenArtifactLocator.EP_NAME, new MavenArtifactLocator() { @@ -201,4 +230,37 @@ public void getDependencyArtifactLocation() { assertThat(artifactPath.toString()) .endsWith(Label.create(CONSTRAINT_LAYOUT_LABEL).blazePackage().relativePath()); } + + @Test + public void getDependencyArtifactLocation_aarImport_aarDirReturn() { + NbAarTarget aarTarget = + aar_import(CONSTRAINT_LAYOUT_LABEL) + .aar("lib_aar.aar") + .generated_jar("classes_and_libs_merged.jar"); + File aarLibraryFile = + LibraryFileBuilder.aar(workspaceRoot, aarTarget.getAar().getRelativePath()).build(); + setTargetMap( + android_library("//java/com/foo/gallery/activities:activities") + .src("MainActivity.java") + .dep(CONSTRAINT_LAYOUT_LABEL) + .res("res"), + aarTarget); + runFullBlazeSyncWithNoIssues(); + + Module workspaceModule = + ModuleFinder.getInstance(getProject()) + .findModuleByName("java.com.foo.gallery.activities.activities"); + BlazeModuleSystem workspaceModuleSystem = BlazeModuleSystem.getInstance(workspaceModule); + Path path = workspaceModuleSystem.getDependencyPath(CONSTRAINT_LAYOUT_COORDINATE); + assertThat(path.toFile()).isEqualTo(getAarDir(aarLibraryFile)); + } + + private File getAarDir(File aarLibraryFile) { + String path = aarLibraryFile.getAbsolutePath(); + String name = FileUtil.getNameWithoutExtension(PathUtil.getFileName(path)); + String aarDirName = + UnpackedAarUtils.generateAarDirectoryName(name, path.hashCode()) + SdkConstants.DOT_AAR; + UnpackedAars unpackedAars = UnpackedAars.getInstance(getProject()); + return new File(unpackedAars.getCacheDir(), aarDirName); + } } diff --git a/aswb/tests/integrationtests/com/google/idea/blaze/android/functional/FullApkBuildStepIntegrationTest.java b/aswb/tests/integrationtests/com/google/idea/blaze/android/functional/FullApkBuildStepIntegrationTest.java index fc76a549845..cd395de5d16 100644 --- a/aswb/tests/integrationtests/com/google/idea/blaze/android/functional/FullApkBuildStepIntegrationTest.java +++ b/aswb/tests/integrationtests/com/google/idea/blaze/android/functional/FullApkBuildStepIntegrationTest.java @@ -30,6 +30,7 @@ import com.google.idea.blaze.android.BlazeAndroidIntegrationTestCase; import com.google.idea.blaze.android.MessageCollector; import com.google.idea.blaze.android.MockSdkUtil; +import com.google.idea.blaze.android.run.NativeSymbolFinder; import com.google.idea.blaze.android.run.RemoteApkDownloader; import com.google.idea.blaze.android.run.deployinfo.BlazeAndroidDeployInfo; import com.google.idea.blaze.android.run.deployinfo.BlazeApkDeployInfoProtoHelper; @@ -41,17 +42,16 @@ import com.google.idea.blaze.base.bazel.BuildSystemProvider; import com.google.idea.blaze.base.bazel.BuildSystemProviderWrapper; import com.google.idea.blaze.base.command.buildresult.BuildResultHelper; -import com.google.idea.blaze.base.command.buildresult.BuildResultHelper.GetArtifactsException; -import com.google.idea.blaze.base.command.buildresult.ParsedBepOutput; 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.google.idea.blaze.base.scope.output.IssueOutput; -import com.google.idea.blaze.base.sync.aspects.BuildResult; import com.google.idea.common.experiments.ExperimentService; import com.google.idea.common.experiments.MockExperimentService; import com.google.idea.testing.ServiceHelper; +import com.intellij.testFramework.ExtensionTestUtil; import java.io.File; +import java.util.ArrayList; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -60,9 +60,6 @@ /** Integration tests for {@link FullApkBuildStep} */ @RunWith(JUnit4.class) public class FullApkBuildStepIntegrationTest extends BlazeAndroidIntegrationTestCase { - /** Exposed to test methods to toggle presence of execroot */ - private BuildResultHelper mockBuildResultHelper; - private Label buildTarget; private BlazeContext context; private ImmutableList blazeFlags; @@ -104,15 +101,10 @@ public void setupTestInfoGatherers() { registerApplicationService(ExternalTaskProvider.class, externalTaskInterceptor); } - /** Setup build result helper to return BEP output with test execroot by default. */ + /** Setup build system provider with {@link BuildSystemProviderWrapper} */ @Before - public void setupBuildResultHelperProvider() throws GetArtifactsException { - mockBuildResultHelper = mock(BuildResultHelper.class); - when(mockBuildResultHelper.getBuildOutput()) - .thenReturn( - new ParsedBepOutput(null, getExecRoot(), null, null, 0, BuildResult.SUCCESS, 0)); + public void setupBuildSystemProvider() { BuildSystemProviderWrapper buildSystem = new BuildSystemProviderWrapper(() -> getProject()); - buildSystem.setBuildResultHelperSupplier(() -> mockBuildResultHelper); registerExtension(BuildSystemProvider.EP_NAME, buildSystem); } @@ -128,12 +120,13 @@ public void build_completesSuccessfully() throws Exception { when(helper.readDeployInfoProtoForTarget(eq(buildTarget), any(BuildResultHelper.class), any())) .thenReturn(fakeProto); when(helper.extractDeployInfoAndInvalidateManifests( - eq(getProject()), eq(new File(getExecRoot())), eq(fakeProto))) + eq(getProject()), eq(new File(getExecRoot())), eq(fakeProto), any())) .thenReturn(mockDeployInfo); // Perform FullApkBuildStep buildStep = - new FullApkBuildStep(getProject(), buildTarget, blazeFlags, helper); + new FullApkBuildStep( + getProject(), buildTarget, blazeFlags, /* nativeDebuggingEnabled= */ false, helper); buildStep.build(context, new DeviceSession(null, null, null)); // Verify @@ -154,19 +147,22 @@ public void build_withRemoteApk() throws Exception { // Return fake deploy info proto and mocked deploy info data object. BlazeAndroidDeployInfo mockDeployInfo = mock(BlazeAndroidDeployInfo.class); File apkFile = new File("/path/to/apk"); + File libFile = new File("/path/to/lib"); when(mockDeployInfo.getApksToDeploy()).thenReturn(ImmutableList.of(apkFile)); + when(mockDeployInfo.getSymbolFiles()).thenReturn(ImmutableList.of(libFile)); BlazeApkDeployInfoProtoHelper helper = mock(BlazeApkDeployInfoProtoHelper.class); AndroidDeployInfo fakeProto = AndroidDeployInfo.newBuilder().build(); when(helper.readDeployInfoProtoForTarget(eq(buildTarget), any(BuildResultHelper.class), any())) .thenReturn(fakeProto); when(helper.extractDeployInfoAndInvalidateManifests( - eq(getProject()), eq(new File(getExecRoot())), eq(fakeProto))) + eq(getProject()), eq(new File(getExecRoot())), eq(fakeProto), any())) .thenReturn(mockDeployInfo); - // Perform + // Perform (with native debugging disabled). FullApkBuildStep buildStep = - new FullApkBuildStep(getProject(), buildTarget, blazeFlags, helper); + new FullApkBuildStep( + getProject(), buildTarget, blazeFlags, /* nativeDebuggingEnabled= */ false, helper); buildStep.build(context, new DeviceSession(null, null, null)); // Verify @@ -174,6 +170,7 @@ public void build_withRemoteApk() throws Exception { assertThat(buildStep.getDeployInfo().getApksToDeploy()).doesNotContain(apkFile); assertThat(getOnlyElement(buildStep.getDeployInfo().getApksToDeploy()).getPath()) .contains("localcopy"); + assertThat(buildStep.getDeployInfo().getSymbolFiles()).isEmpty(); assertThat(externalTaskInterceptor.command).contains(buildTarget.toString()); assertThat(externalTaskInterceptor.command).contains("--output_groups=+android_deploy_info"); assertThat(externalTaskInterceptor.command).containsAllIn(blazeFlags); @@ -203,12 +200,13 @@ public void build_withRemoteApkButDisabledRemoteApkFetching() throws Exception { when(helper.readDeployInfoProtoForTarget(eq(buildTarget), any(BuildResultHelper.class), any())) .thenReturn(fakeProto); when(helper.extractDeployInfoAndInvalidateManifests( - eq(getProject()), eq(new File(getExecRoot())), eq(fakeProto))) + eq(getProject()), eq(new File(getExecRoot())), eq(fakeProto), any())) .thenReturn(mockDeployInfo); // Perform FullApkBuildStep buildStep = - new FullApkBuildStep(getProject(), buildTarget, blazeFlags, helper); + new FullApkBuildStep( + getProject(), buildTarget, blazeFlags, /* nativeDebuggingEnabled= */ false, helper); buildStep.build(context, new DeviceSession(null, null, null)); // Verify @@ -220,6 +218,135 @@ public void build_withRemoteApkButDisabledRemoteApkFetching() throws Exception { verify(mockDownloader, times(0)).download(any(), any()); } + @Test + public void build_withNativeSymbols() throws Exception { + NativeSymbolFinder mockSymbolFinder = mock(NativeSymbolFinder.class); + when(mockSymbolFinder.getAdditionalBuildFlags()) + .thenReturn("--output_groups=+android_deploy_info,+ndk_symbolization"); + File lib = new File("/path/to/symbol"); + File altLib = new File("/path/to/alt/symbol"); + ImmutableList symbolFiles = ImmutableList.of(lib, altLib); + when(mockSymbolFinder.getNativeSymbolsForBuild(any(), any(), any())).thenReturn(symbolFiles); + registerExtension(NativeSymbolFinder.EP_NAME, mockSymbolFinder); + + BlazeAndroidDeployInfo mockDeployInfo = mock(BlazeAndroidDeployInfo.class); + File apkFile = new File("/path/to/apk"); + when(mockDeployInfo.getApksToDeploy()).thenReturn(ImmutableList.of(apkFile)); + when(mockDeployInfo.getSymbolFiles()).thenReturn(symbolFiles); + + BlazeApkDeployInfoProtoHelper helper = mock(BlazeApkDeployInfoProtoHelper.class); + AndroidDeployInfo fakeProto = AndroidDeployInfo.newBuilder().build(); + when(helper.readDeployInfoProtoForTarget(eq(buildTarget), any(BuildResultHelper.class), any())) + .thenReturn(fakeProto); + // Expect symbol files to be passed to Helper when building DeployInfo. + when(helper.extractDeployInfoAndInvalidateManifests( + eq(getProject()), eq(new File(getExecRoot())), eq(fakeProto), eq(symbolFiles))) + .thenReturn(mockDeployInfo); + + // Perform + FullApkBuildStep buildStep = + new FullApkBuildStep(getProject(), buildTarget, blazeFlags, true, helper); + buildStep.build(context, new DeviceSession(null, null, null)); + + // Verify + assertThat(buildStep.getDeployInfo()).isNotNull(); + assertThat(buildStep.getDeployInfo().getApksToDeploy()).containsExactly(apkFile); + assertThat(buildStep.getDeployInfo().getSymbolFiles()).isEqualTo(symbolFiles); + assertThat(externalTaskInterceptor.command).contains(buildTarget.toString()); + assertThat(externalTaskInterceptor.command) + .contains("--output_groups=+android_deploy_info,+ndk_symbolization"); + assertThat(externalTaskInterceptor.command).containsAllIn(blazeFlags); + } + + @Test + public void build_withoutNativeSymbols() throws Exception { + // Unregister all EPs of NativeSymbolFinder + ExtensionTestUtil.maskExtensions( + NativeSymbolFinder.EP_NAME, + /* newExtensions= */ new ArrayList<>(), + getTestRootDisposable()); + + BlazeAndroidDeployInfo mockDeployInfo = mock(BlazeAndroidDeployInfo.class); + File apkFile = new File("/path/to/apk"); + when(mockDeployInfo.getApksToDeploy()).thenReturn(ImmutableList.of(apkFile)); + File lib = new File("/path/to/symbol"); + File altLib = new File("/path/to/alt/symbol"); + ImmutableList symbolFiles = ImmutableList.of(lib, altLib); + when(mockDeployInfo.getSymbolFiles()).thenReturn(symbolFiles); + + BlazeApkDeployInfoProtoHelper helper = mock(BlazeApkDeployInfoProtoHelper.class); + AndroidDeployInfo fakeProto = AndroidDeployInfo.getDefaultInstance(); + when(helper.readDeployInfoProtoForTarget(eq(buildTarget), any(BuildResultHelper.class), any())) + .thenReturn(fakeProto); + // Expect symbol files to be passed to Helper when building DeployInfo. + when(helper.extractDeployInfoAndInvalidateManifests( + eq(getProject()), eq(new File(getExecRoot())), eq(fakeProto), eq(ImmutableList.of()))) + .thenReturn(mockDeployInfo); + + // Perform + FullApkBuildStep buildStep = + new FullApkBuildStep(getProject(), buildTarget, blazeFlags, true, helper); + buildStep.build(context, new DeviceSession(null, null, null)); + + // Verify + assertThat(buildStep.getDeployInfo()).isNotNull(); + assertThat(buildStep.getDeployInfo().getApksToDeploy()).containsExactly(apkFile); + assertThat(buildStep.getDeployInfo().getSymbolFiles()).isEqualTo(symbolFiles); + assertThat(externalTaskInterceptor.command).contains(buildTarget.toString()); + assertThat(externalTaskInterceptor.command).contains("--output_groups=+android_deploy_info"); + assertThat(externalTaskInterceptor.command).containsAllIn(blazeFlags); + } + + @Test + public void build_withRemoteApk_withNativeDebugging() throws Exception { + // Setup remote APK downloader for ensuring the download method is called + RemoteApkDownloader mockDownloader = mock(RemoteApkDownloader.class); + when(mockDownloader.canDownload(any())).thenReturn(true); + registerExtension(RemoteApkDownloader.EP_NAME, mockDownloader); + + NativeSymbolFinder mockSymbolFinder = mock(NativeSymbolFinder.class); + when(mockSymbolFinder.getAdditionalBuildFlags()) + .thenReturn("--output_groups=+android_deploy_info,+ndk_symbolization"); + File lib = new File("/path/to/symbol"); + File altLib = new File("/path/to/alt/symbol"); + ImmutableList symbolFiles = ImmutableList.of(lib, altLib); + when(mockSymbolFinder.getNativeSymbolsForBuild(any(), any(), any())).thenReturn(symbolFiles); + registerExtension(NativeSymbolFinder.EP_NAME, mockSymbolFinder); + + // Return fake deploy info proto and mocked deploy info data object. + BlazeAndroidDeployInfo mockDeployInfo = mock(BlazeAndroidDeployInfo.class); + File apkFile = new File("/path/to/apk"); + when(mockDeployInfo.getApksToDeploy()).thenReturn(ImmutableList.of(apkFile)); + when(mockDeployInfo.getSymbolFiles()).thenReturn(symbolFiles); + + BlazeApkDeployInfoProtoHelper helper = mock(BlazeApkDeployInfoProtoHelper.class); + AndroidDeployInfo fakeProto = AndroidDeployInfo.getDefaultInstance(); + when(helper.readDeployInfoProtoForTarget(eq(buildTarget), any(BuildResultHelper.class), any())) + .thenReturn(fakeProto); + when(helper.extractDeployInfoAndInvalidateManifests( + eq(getProject()), eq(new File(getExecRoot())), eq(fakeProto), eq(symbolFiles))) + .thenReturn(mockDeployInfo); + + // Perform + FullApkBuildStep buildStep = + new FullApkBuildStep( + getProject(), buildTarget, blazeFlags, /* nativeDebuggingEnabled= */ true, helper); + buildStep.build(context, new DeviceSession(null, null, null)); + + // Verify + assertThat(buildStep.getDeployInfo()).isNotNull(); + assertThat(buildStep.getDeployInfo().getApksToDeploy()).doesNotContain(apkFile); + assertThat(getOnlyElement(buildStep.getDeployInfo().getApksToDeploy()).getPath()) + .contains("localcopy"); + assertThat(buildStep.getDeployInfo().getSymbolFiles()).containsNoneOf(lib, altLib); + assertThat(buildStep.getDeployInfo().getSymbolFiles().get(0).getPath()).contains("localcopy"); + assertThat(buildStep.getDeployInfo().getSymbolFiles().get(1).getPath()).contains("localcopy"); + assertThat(externalTaskInterceptor.command).contains(buildTarget.toString()); + assertThat(externalTaskInterceptor.command).contains("--output_groups=+android_deploy_info"); + assertThat(externalTaskInterceptor.command).containsAllIn(blazeFlags); + verify(mockDownloader, times(3)).download(any(), any()); + } + @Test public void build_exceptionDuringDeployInfoExtraction_shouldFail() throws Exception { // Return fake deploy info proto and mocked deploy info data object. @@ -227,12 +354,17 @@ public void build_exceptionDuringDeployInfoExtraction_shouldFail() throws Except BlazeApkDeployInfoProtoHelper helper = mock(BlazeApkDeployInfoProtoHelper.class); when(helper.readDeployInfoProtoForTarget(eq(buildTarget), any(BuildResultHelper.class), any())) .thenReturn(fakeProto); - when(helper.extractDeployInfoAndInvalidateManifests(any(), any(), any())) + when(helper.extractDeployInfoAndInvalidateManifests(any(), any(), any(), any())) .thenThrow(new GetDeployInfoException("Fake Exception")); // Perform FullApkBuildStep buildStep = - new FullApkBuildStep(getProject(), buildTarget, ImmutableList.of(), helper); + new FullApkBuildStep( + getProject(), + buildTarget, + ImmutableList.of(), + /* nativeDebuggingEnabled= */ false, + helper); buildStep.build(context, new DeviceSession(null, null, null)); // Verify @@ -254,12 +386,17 @@ public void build_withBlazeCommandFailure_shouldFail() throws Exception { when(helper.readDeployInfoProtoForTarget(eq(buildTarget), any(BuildResultHelper.class), any())) .thenReturn(fakeProto); when(helper.extractDeployInfoAndInvalidateManifests( - eq(getProject()), eq(new File(getExecRoot())), eq(fakeProto))) + eq(getProject()), eq(new File(getExecRoot())), eq(fakeProto), any())) .thenReturn(mockDeployInfo); // Perform FullApkBuildStep buildStep = - new FullApkBuildStep(getProject(), buildTarget, ImmutableList.of(), helper); + new FullApkBuildStep( + getProject(), + buildTarget, + ImmutableList.of(), + /* nativeDebuggingEnabled= */ false, + helper); buildStep.build(context, new DeviceSession(null, null, null)); // Verify @@ -271,8 +408,8 @@ public void build_withBlazeCommandFailure_shouldFail() throws Exception { @Test public void build_withNullExecRoot_shouldFail() throws Exception { // Return null execroot - when(mockBuildResultHelper.getBuildOutput()) - .thenReturn(new ParsedBepOutput(null, null, null, null, 0, BuildResult.SUCCESS, 0)); + // the only way for execroot to be null is for getBlazeInfo() to throw an exception + BuildSystemProviderWrapper.getInstance(getProject()).setThrowExceptionOnGetBlazeInfo(true); // Return fake deploy info proto and mocked deploy info data object. AndroidDeployInfo fakeProto = AndroidDeployInfo.newBuilder().build(); @@ -282,12 +419,13 @@ public void build_withNullExecRoot_shouldFail() throws Exception { when(helper.readDeployInfoProtoForTarget(eq(buildTarget), any(BuildResultHelper.class), any())) .thenReturn(fakeProto); when(helper.extractDeployInfoAndInvalidateManifests( - eq(getProject()), eq(new File(getExecRoot())), eq(fakeProto))) + eq(getProject()), eq(new File(getExecRoot())), eq(fakeProto), any())) .thenReturn(mockDeployInfo); // Perform FullApkBuildStep buildStep = - new FullApkBuildStep(getProject(), buildTarget, blazeFlags, helper); + new FullApkBuildStep( + getProject(), buildTarget, blazeFlags, /* nativeDebuggingEnabled= */ false, helper); buildStep.build(context, new DeviceSession(null, null, null)); // Verify diff --git a/aswb/tests/integrationtests/com/google/idea/blaze/android/functional/InstrumentationInfoTest.java b/aswb/tests/integrationtests/com/google/idea/blaze/android/functional/InstrumentationInfoTest.java new file mode 100644 index 00000000000..9950c9a2b72 --- /dev/null +++ b/aswb/tests/integrationtests/com/google/idea/blaze/android/functional/InstrumentationInfoTest.java @@ -0,0 +1,158 @@ +/* + * Copyright 2019 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.functional; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.idea.blaze.android.targetmapbuilder.NbAndroidInstrumentationTestTarget.android_instrumentation_test; +import static com.google.idea.blaze.android.targetmapbuilder.NbAndroidTarget.android_binary; +import static org.junit.Assert.fail; + +import com.google.idea.blaze.android.BlazeAndroidIntegrationTestCase; +import com.google.idea.blaze.android.MockSdkUtil; +import com.google.idea.blaze.android.run.runner.InstrumentationInfo; +import com.google.idea.blaze.android.run.runner.InstrumentationInfo.InstrumentationParserException; +import com.google.idea.blaze.base.model.primitives.Label; +import com.google.idea.blaze.base.model.primitives.WorkspacePath; +import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Integration tests for {@link InstrumentationInfo} */ +@RunWith(JUnit4.class) +public class InstrumentationInfoTest extends BlazeAndroidIntegrationTestCase { + private void setupProject() { + setProjectView( + "directories:", + " java/com/foo/app", + "targets:", + " //java/com/foo/app:instrumentation_test", + "android_sdk_platform: android-27"); + MockSdkUtil.registerSdk(workspace, "27"); + + workspace.createFile( + new WorkspacePath("java/com/foo/app/MainActivity.java"), + "package com.foo.app", + "import android.app.Activity;", + "public class MainActivity extends Activity {}"); + + workspace.createFile( + new WorkspacePath("java/com/foo/app/Test.java"), + "package com.foo.app", + "public class Test {}"); + + setTargetMap( + android_binary("//java/com/foo/app:app").src("MainActivity.java"), + android_binary("//java/com/foo/app:test_app") + .setResourceJavaPackage("com.foo.app.androidtest") + .src("Test.java") + .instruments("//java/com/foo/app:app"), + android_binary("//java/com/foo/app:test_app_self_instrumenting") + .setResourceJavaPackage("com.foo.app.androidtest.selfinstrumenting") + .src("Test.java"), + android_instrumentation_test("//java/com/foo/app:instrumentation_test") + .test_app("//java/com/foo/app:test_app"), + android_instrumentation_test("//java/com/foo/app:self_instrumenting_test") + .test_app("//java/com/foo/app:test_app_self_instrumenting")); + runFullBlazeSyncWithNoIssues(); + } + + @Test + public void separateInstrumentationAndTargetApp() { + setupProject(); + + Label instrumentationTestLabel = Label.create("//java/com/foo/app:instrumentation_test"); + InstrumentationInfo info = + InstrumentationInfo.getInstrumentationInfo( + instrumentationTestLabel, + BlazeProjectDataManager.getInstance(getProject()).getBlazeProjectData()); + + assertThat(info.testApp).isEqualTo(Label.create("//java/com/foo/app:test_app")); + assertThat(info.targetApp).isEqualTo(Label.create("//java/com/foo/app:app")); + assertThat(info.isSelfInstrumentingTest()).isFalse(); + } + + @Test + public void selfInstrumentingTest() { + setupProject(); + + Label instrumentationTestLabel = Label.create("//java/com/foo/app:self_instrumenting_test"); + InstrumentationInfo info = + InstrumentationInfo.getInstrumentationInfo( + instrumentationTestLabel, + BlazeProjectDataManager.getInstance(getProject()).getBlazeProjectData()); + + assertThat(info.testApp) + .isEqualTo(Label.create("//java/com/foo/app:test_app_self_instrumenting")); + assertThat(info.targetApp).isNull(); + assertThat(info.isSelfInstrumentingTest()).isTrue(); + } + + @Test + public void noTestAppSpecified() { + setProjectView( + "directories:", + " java/com/foo/app", + "targets:", + " //java/com/foo/app:instrumentation_test", + "android_sdk_platform: android-27"); + MockSdkUtil.registerSdk(workspace, "27"); + + workspace.createFile( + new WorkspacePath("java/com/foo/app/MainActivity.java"), + "package com.foo.app", + "import android.app.Activity;", + "public class MainActivity extends Activity {}"); + + workspace.createFile( + new WorkspacePath("java/com/foo/app/Test.java"), + "package com.foo.app", + "public class Test {}"); + + setTargetMap( + android_binary("//java/com/foo/app:app").src("MainActivity.java"), + android_binary("//java/com/foo/app:test_app") + .setResourceJavaPackage("com.foo.app.androidtest") + .src("Test.java") + .instruments("//java/com/foo/app:app"), + android_instrumentation_test("//java/com/foo/app:instrumentation_test")); + runFullBlazeSyncWithNoIssues(); + + Label instrumentationTestLabel = Label.create("//java/com/foo/app:instrumentation_test"); + try { + InstrumentationInfo.getInstrumentationInfo( + instrumentationTestLabel, + BlazeProjectDataManager.getInstance(getProject()).getBlazeProjectData()); + fail("parsing should've thrown an exception"); + } catch (InstrumentationParserException e) { + assertThat(e.getMessage()) + .startsWith( + "No \"test_app\" in target definition for //java/com/foo/app:instrumentation_test."); + } + } + + @Test + public void findTestAndAppTargets() { + setupProject(); + Label instrumentationTestLabel = Label.create("//java/com/foo/app:instrumentation_test"); + InstrumentationInfo info = + InstrumentationInfo.getInstrumentationInfo( + instrumentationTestLabel, + BlazeProjectDataManager.getInstance(getProject()).getBlazeProjectData()); + assertThat(info.testApp).isEqualTo(Label.create("//java/com/foo/app:test_app")); + assertThat(info.targetApp).isEqualTo(Label.create("//java/com/foo/app:app")); + } +} diff --git a/aswb/tests/integrationtests/com/google/idea/blaze/android/functional/InstrumentationTestTargetIntegrationTest.java b/aswb/tests/integrationtests/com/google/idea/blaze/android/functional/InstrumentationTestTargetIntegrationTest.java index 6f7fbb792e2..8207ba86a01 100644 --- a/aswb/tests/integrationtests/com/google/idea/blaze/android/functional/InstrumentationTestTargetIntegrationTest.java +++ b/aswb/tests/integrationtests/com/google/idea/blaze/android/functional/InstrumentationTestTargetIntegrationTest.java @@ -74,7 +74,7 @@ public void setup() { .instruments("//java/com/foo/app:app"), android_instrumentation_test("//java/com/foo/app:instrumentation_test") .test_app("//java/com/foo/app:test_app") - .target_device("//tools/android/emulated_devices/generic_phone:android_17_x86")); + .target_device("//tools/mobile/devices/android/generic_phone:android_17_x86")); runFullBlazeSyncWithNoIssues(); } @@ -93,7 +93,7 @@ public void findInstrumentorAndTestTargets() { Label instrumentorLabel = Label.create("//java/com/foo/app:test_app"); Label appLabel = Label.create("//java/com/foo/app:app"); Label targetDeviceLabel = - Label.create("//tools/android/emulated_devices/generic_phone:android_17_x86"); + Label.create("//tools/mobile/devices/android/generic_phone:android_17_x86"); TargetMap targetMap = projectData.getTargetMap(); TargetIdeInfo testTarget = targetMap.get(TargetKey.forPlainTarget(testLabel)); diff --git a/aswb/tests/integrationtests/com/google/idea/blaze/android/functional/MobileInstallBuildStepIntegrationTest.java b/aswb/tests/integrationtests/com/google/idea/blaze/android/functional/MobileInstallBuildStepIntegrationTest.java index 5ff2a0db35e..00102d66ec9 100644 --- a/aswb/tests/integrationtests/com/google/idea/blaze/android/functional/MobileInstallBuildStepIntegrationTest.java +++ b/aswb/tests/integrationtests/com/google/idea/blaze/android/functional/MobileInstallBuildStepIntegrationTest.java @@ -16,27 +16,15 @@ package com.google.idea.blaze.android.functional; import static com.google.common.truth.Truth.assertThat; -import static com.google.idea.blaze.android.targetmapbuilder.NbAndroidTarget.android_binary; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import com.android.ddmlib.IDevice; -import com.android.ddmlib.IDevice.HardwareFeature; -import com.android.sdklib.IAndroidTarget; -import com.android.sdklib.devices.Abi; -import com.android.tools.idea.run.AndroidDevice; import com.android.tools.idea.run.DeviceFutures; -import com.android.tools.idea.run.LaunchCompatibility; import com.google.common.collect.ImmutableList; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; import com.google.devtools.build.lib.rules.android.deployinfo.AndroidDeployInfoOuterClass.AndroidDeployInfo; -import com.google.errorprone.annotations.Keep; -import com.google.idea.blaze.android.BlazeAndroidIntegrationTestCase; -import com.google.idea.blaze.android.MessageCollector; -import com.google.idea.blaze.android.MockSdkUtil; +import com.google.idea.blaze.android.MobileInstallBuildStepTestCase; import com.google.idea.blaze.android.run.binary.mobileinstall.AdbTunnelConfigurator; import com.google.idea.blaze.android.run.binary.mobileinstall.AdbTunnelConfigurator.AdbTunnelConfiguratorProvider; import com.google.idea.blaze.android.run.binary.mobileinstall.MobileInstallBuildStep; @@ -44,92 +32,17 @@ import com.google.idea.blaze.android.run.deployinfo.BlazeApkDeployInfoProtoHelper; import com.google.idea.blaze.android.run.deployinfo.BlazeApkDeployInfoProtoHelper.GetDeployInfoException; import com.google.idea.blaze.android.run.runner.BlazeAndroidDeviceSelector.DeviceSession; -import com.google.idea.blaze.base.async.process.ExternalTask; import com.google.idea.blaze.base.async.process.ExternalTaskProvider; -import com.google.idea.blaze.base.bazel.BuildSystemProvider; import com.google.idea.blaze.base.bazel.BuildSystemProviderWrapper; import com.google.idea.blaze.base.command.buildresult.BuildResultHelper; -import com.google.idea.blaze.base.command.buildresult.BuildResultHelper.GetArtifactsException; -import com.google.idea.blaze.base.command.buildresult.ParsedBepOutput; -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.google.idea.blaze.base.scope.output.IssueOutput; -import com.google.idea.blaze.base.sync.aspects.BuildResult; -import com.intellij.openapi.project.Project; -import com.intellij.ui.SimpleColoredComponent; import java.io.File; -import java.util.EnumSet; -import java.util.List; -import java.util.Set; -import org.jetbrains.annotations.Nullable; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** Integration tests for {@link MobileInstallBuildStep} */ @RunWith(JUnit4.class) -public class MobileInstallBuildStepIntegrationTest extends BlazeAndroidIntegrationTestCase { - /** Exposed to test methods to toggle presence of execroot */ - private BuildResultHelper mockBuildResultHelper; - - private Label buildTarget; - private ImmutableList blazeFlags; - private ImmutableList execFlags; - private ExternalTaskInterceptor externalTaskInterceptor; - private BlazeContext context; - private MessageCollector messageCollector; - - @Before - public void setupProject() { - setProjectView( - "directories:", - " java/com/foo/app", - "targets:", - " //java/com/foo/app:app", - "android_sdk_platform: android-27"); - MockSdkUtil.registerSdk(workspace, "27"); - - workspace.createFile( - new WorkspacePath("java/com/foo/app/MainActivity.java"), - "package com.foo.app", - "import android.app.Activity;", - "public class MainActivity extends Activity {}"); - - setTargetMap(android_binary("//java/com/foo/app:app").src("MainActivity.java")); - runFullBlazeSyncWithNoIssues(); - - // MI invocation flags - buildTarget = Label.create("//java/com/foo/app:app"); - blazeFlags = ImmutableList.of("some_blaze_flag", "other_blaze_flag"); - execFlags = ImmutableList.of("some_exec_flag", "other_exec_flag"); - } - - /** Setup build result helper to return BEP output with test execroot by default. */ - @Before - public void setupBuildResultHelperProvider() throws GetArtifactsException { - mockBuildResultHelper = mock(BuildResultHelper.class); - when(mockBuildResultHelper.getBuildOutput()) - .thenReturn( - new ParsedBepOutput(null, getExecRoot(), null, null, 0, BuildResult.SUCCESS, 0)); - BuildSystemProviderWrapper buildSystem = new BuildSystemProviderWrapper(() -> getProject()); - buildSystem.setBuildResultHelperSupplier(() -> mockBuildResultHelper); - registerExtension(BuildSystemProvider.EP_NAME, buildSystem); - } - - @Before - public void setupTestDetailCollectors() { - // Setup interceptor for fake running of blaze commands and capture details. - externalTaskInterceptor = new ExternalTaskInterceptor(); - registerApplicationService(ExternalTaskProvider.class, externalTaskInterceptor); - - // Collect messages sent to IssueOutput. - messageCollector = new MessageCollector(); - context = BlazeContext.create(); - context.addOutputSink(IssueOutput.class, messageCollector); - } - +public final class MobileInstallBuildStepIntegrationTest extends MobileInstallBuildStepTestCase { @Test public void deployInfoBuiltCorrectly() throws Exception { // Mobile-install build step requires only one device be active. DeviceFutures class is final, @@ -154,13 +67,12 @@ public void deployInfoBuiltCorrectly() throws Exception { // Verify assertThat(buildStep.getDeployInfo()).isNotNull(); assertThat(buildStep.getDeployInfo()).isEqualTo(mockDeployInfo); - assertThat(externalTaskInterceptor.context).isEqualTo(context); - assertThat(externalTaskInterceptor.command).containsAllIn(blazeFlags); - assertThat(externalTaskInterceptor.command).containsAllIn(execFlags); - assertThat(externalTaskInterceptor.command).contains("--nolaunch_app"); - assertThat(externalTaskInterceptor.command) + assertThat(externalTaskInterceptor.getContext()).isEqualTo(context); + assertThat(externalTaskInterceptor.getCommand()).containsAllIn(blazeFlags); + assertThat(externalTaskInterceptor.getCommand()).containsAllIn(execFlags); + assertThat(externalTaskInterceptor.getCommand()) .containsAnyOf("serial-number", "serial-number:tcp:0"); - assertThat(externalTaskInterceptor.command).contains(buildTarget.toString()); + assertThat(externalTaskInterceptor.getCommand()).contains(buildTarget.toString()); } @Test @@ -193,15 +105,14 @@ public void deployInfoBuiltCorrectly_withInactiveAdbTunnelSetup() throws Excepti // Verify assertThat(buildStep.getDeployInfo()).isNotNull(); assertThat(buildStep.getDeployInfo()).isEqualTo(mockDeployInfo); - assertThat(externalTaskInterceptor.context).isEqualTo(context); - assertThat(externalTaskInterceptor.command).containsAllIn(blazeFlags); - assertThat(externalTaskInterceptor.command).containsAllIn(execFlags); - assertThat(externalTaskInterceptor.command).contains("--nolaunch_app"); - assertThat(externalTaskInterceptor.command).contains("--device"); + assertThat(externalTaskInterceptor.getContext()).isEqualTo(context); + assertThat(externalTaskInterceptor.getCommand()).containsAllIn(blazeFlags); + assertThat(externalTaskInterceptor.getCommand()).containsAllIn(execFlags); + assertThat(externalTaskInterceptor.getCommand()).contains("--device"); // workaround for inconsistent stateful AndroidDebugBridge class. - assertThat(externalTaskInterceptor.command) + assertThat(externalTaskInterceptor.getCommand()) .containsAnyOf("serial-number", "serial-number:tcp:0"); - assertThat(externalTaskInterceptor.command).contains(buildTarget.toString()); + assertThat(externalTaskInterceptor.getCommand()).contains(buildTarget.toString()); } @Test @@ -234,13 +145,12 @@ public void deployInfoBuiltCorrectly_withAdbTunnelSetup() throws Exception { // Verify assertThat(buildStep.getDeployInfo()).isNotNull(); assertThat(buildStep.getDeployInfo()).isEqualTo(mockDeployInfo); - assertThat(externalTaskInterceptor.context).isEqualTo(context); - assertThat(externalTaskInterceptor.command).containsAllIn(blazeFlags); - assertThat(externalTaskInterceptor.command).containsAllIn(execFlags); - assertThat(externalTaskInterceptor.command).contains("--nolaunch_app"); - assertThat(externalTaskInterceptor.command).contains("--device"); - assertThat(externalTaskInterceptor.command).contains("serial-number:tcp:12345"); - assertThat(externalTaskInterceptor.command).contains(buildTarget.toString()); + assertThat(externalTaskInterceptor.getContext()).isEqualTo(context); + assertThat(externalTaskInterceptor.getCommand()).containsAllIn(blazeFlags); + assertThat(externalTaskInterceptor.getCommand()).containsAllIn(execFlags); + assertThat(externalTaskInterceptor.getCommand()).contains("--device"); + assertThat(externalTaskInterceptor.getCommand()).contains("serial-number:tcp:12345"); + assertThat(externalTaskInterceptor.getCommand()).contains(buildTarget.toString()); } @Test @@ -259,10 +169,7 @@ public void deployInfoBuiltCorrectly_withNullAdbTunnelSetup() throws Exception { getProject(), new File(getExecRoot()), fakeProto)) .thenReturn(mockDeployInfo); - // Setup mock AdbTunnelConfigurator for testing device port flags. - AdbTunnelConfigurator tunnelConfigurator = mock(AdbTunnelConfigurator.class); - when(tunnelConfigurator.isActive()).thenReturn(true); - when(tunnelConfigurator.getAdbServerPort()).thenReturn(12345); + // Do not pass AdbTunnelConfigurator. registerExtension(AdbTunnelConfiguratorProvider.EP_NAME, providerCxt -> null); // Perform @@ -273,15 +180,14 @@ public void deployInfoBuiltCorrectly_withNullAdbTunnelSetup() throws Exception { // Verify assertThat(buildStep.getDeployInfo()).isNotNull(); assertThat(buildStep.getDeployInfo()).isEqualTo(mockDeployInfo); - assertThat(externalTaskInterceptor.context).isEqualTo(context); - assertThat(externalTaskInterceptor.command).containsAllIn(blazeFlags); - assertThat(externalTaskInterceptor.command).containsAllIn(execFlags); - assertThat(externalTaskInterceptor.command).contains("--nolaunch_app"); - assertThat(externalTaskInterceptor.command).contains("--device"); + assertThat(externalTaskInterceptor.getContext()).isEqualTo(context); + assertThat(externalTaskInterceptor.getCommand()).containsAllIn(blazeFlags); + assertThat(externalTaskInterceptor.getCommand()).containsAllIn(execFlags); + assertThat(externalTaskInterceptor.getCommand()).contains("--device"); // workaround for inconsistent stateful AndroidDebugBridge class. - assertThat(externalTaskInterceptor.command) + assertThat(externalTaskInterceptor.getCommand()) .containsAnyOf("serial-number", "serial-number:tcp:0"); - assertThat(externalTaskInterceptor.command).contains(buildTarget.toString()); + assertThat(externalTaskInterceptor.getCommand()).contains(buildTarget.toString()); } @Test @@ -379,8 +285,8 @@ public void blazeCommandFailed() throws Exception { @Test public void nullExecRoot() throws Exception { // Return null execroot - when(mockBuildResultHelper.getBuildOutput()) - .thenReturn(new ParsedBepOutput(null, null, null, null, 0, BuildResult.SUCCESS, 0)); + // the only way for execroot to be null is for getBlazeInfo() to throw an exception + BuildSystemProviderWrapper.getInstance(getProject()).setThrowExceptionOnGetBlazeInfo(true); // Mobile-install build step requires only one device be active. DeviceFutures class is final, // so we have to make one with a stub AndroidDevice. @@ -405,118 +311,4 @@ public void nullExecRoot() throws Exception { assertThat(context.hasErrors()).isTrue(); assertThat(messageCollector.getMessages()).contains("Could not locate execroot!"); } - - /** Saves the latest blaze command and context for later verification. */ - private static class ExternalTaskInterceptor implements ExternalTaskProvider { - ImmutableList command; - BlazeContext context; - - @Override - public ExternalTask build(ExternalTask.Builder builder) { - command = builder.command.build(); - context = builder.context; - return scopes -> 0; - } - } - - /** - * A fake android device that returns a mocked launched-device. This class is required because - * {@link DeviceFutures} and all other implementations of {@link AndroidDevice} are final, - * therefore we need this to stub out a fake {@link DeviceSession}. - */ - private static class FakeDevice extends AndroidDeviceCompat { - @Override - public ListenableFuture getLaunchedDevice() { - IDevice device = mock(IDevice.class); - when(device.getSerialNumber()).thenReturn("serial-number"); - return Futures.immediateFuture(device); - } - - // - // All methods below this point has no purpose. Please ignore. - // - @Override - public boolean isRunning() { - return false; - } - - @Override - public boolean isVirtual() { - return false; - } - - @Override - public com.android.sdklib.AndroidVersion getVersion() { - return null; - } - - @Override - public int getDensity() { - return 0; - } - - @Override - public List getAbis() { - return null; - } - - @Override - public String getSerial() { - return null; - } - - @Override - public boolean supportsFeature(HardwareFeature hardwareFeature) { - return false; - } - - @Override - public String getName() { - return null; - } - - // @Override #api42 - @Keep - public boolean renderLabel( - SimpleColoredComponent simpleColoredComponent, boolean b, @Nullable String s) { - return false; - } - - // @Override #api42 - @Keep - public void prepareToRenderLabel() {} - - // api40: see new API for canRun below - @Keep - public LaunchCompatibility canRun( - com.android.sdklib.AndroidVersion androidVersion, - IAndroidTarget iAndroidTarget, - EnumSet enumSet, - @Nullable Set set) { - return null; - } - - @Override - public ListenableFuture launch(Project project) { - return null; - } - - // @Override #api 3.6 - @Keep - public ListenableFuture launch(Project project, String s) { - return null; - } - - // @Override #api 4.0 - @Keep - public ListenableFuture launch(Project project, List list) { - return null; - } - - // @Override #api 3.6 - @Keep - public boolean isDebuggable() { - return false; - } - } } diff --git a/aswb/tests/integrationtests/com/google/idea/blaze/android/run/AvailableDebuggersTest.java b/aswb/tests/integrationtests/com/google/idea/blaze/android/run/AvailableDebuggersTest.java index a74de09869d..33bd27c8968 100644 --- a/aswb/tests/integrationtests/com/google/idea/blaze/android/run/AvailableDebuggersTest.java +++ b/aswb/tests/integrationtests/com/google/idea/blaze/android/run/AvailableDebuggersTest.java @@ -15,16 +15,15 @@ */ package com.google.idea.blaze.android.run; +import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth8.assertThat; -import com.android.tools.idea.run.editor.AndroidDebugger; -import com.android.tools.idea.run.editor.AndroidJavaDebugger; import com.google.idea.blaze.android.BlazeAndroidIntegrationTestCase; import com.google.idea.blaze.android.MockSdkUtil; import com.google.idea.blaze.android.cppimpl.debug.BlazeAutoAndroidDebugger; import com.google.idea.blaze.android.cppimpl.debug.BlazeNativeAndroidDebugger; -import java.util.List; -import java.util.stream.Collectors; +import com.google.idea.blaze.android.tools.idea.run.editor.AndroidDebuggerCompat; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -38,14 +37,13 @@ public void getDebuggers_noCLanguageSupport_returnsJavaAndAutoDebuggers() { MockSdkUtil.registerSdk(workspace, "27"); runFullBlazeSyncWithNoIssues(); - List> availableDebuggers = - AndroidDebugger.EP_NAME.getExtensionList().stream() - .filter((debugger) -> debugger.supportsProject(getProject())) - .map(AndroidDebugger::getClass) - .collect(Collectors.toList()); - assertThat(availableDebuggers).hasSize(2); - assertThat(availableDebuggers).contains(AndroidJavaDebugger.class); - assertThat(availableDebuggers).contains(BlazeAutoAndroidDebugger.class); + assertThat( + AndroidDebuggerCompat.getAvailableDebuggerExtensionList().stream() + .filter((debugger) -> debugger.supportsProject(getProject())) + .map(Object::getClass)) + .containsExactly( + AndroidDebuggerCompat.getAndroidJavaDebugger().getClass(), + BlazeAutoAndroidDebugger.class); } @Test @@ -59,14 +57,14 @@ public void getDebuggers_withCLanguageSupport_returnsJavaAndAutoAndNativeDebugge MockSdkUtil.registerSdk(workspace, "27"); runFullBlazeSyncWithNoIssues(); - List> availableDebuggers = - AndroidDebugger.EP_NAME.getExtensionList().stream() - .filter((debugger) -> debugger.supportsProject(getProject())) - .map(AndroidDebugger::getClass) - .collect(Collectors.toList()); - assertThat(availableDebuggers).hasSize(3); - assertThat(availableDebuggers).contains(AndroidJavaDebugger.class); - assertThat(availableDebuggers).contains(BlazeAutoAndroidDebugger.class); - assertThat(availableDebuggers).contains(BlazeNativeAndroidDebugger.class); + assertThat( + AndroidDebuggerCompat.getAvailableDebuggerExtensionList().stream() + .filter((debugger) -> debugger.supportsProject(getProject())) + .map(Object::getClass) + .collect(toImmutableList())) + .containsExactly( + AndroidDebuggerCompat.getAndroidJavaDebugger().getClass(), + BlazeAutoAndroidDebugger.class, + BlazeNativeAndroidDebugger.class); } } diff --git a/aswb/tests/integrationtests/com/google/idea/blaze/android/run/DebuggerInfoProviderTest.java b/aswb/tests/integrationtests/com/google/idea/blaze/android/run/DebuggerInfoProviderTest.java index 9d8b1b537d9..b0d386e2447 100644 --- a/aswb/tests/integrationtests/com/google/idea/blaze/android/run/DebuggerInfoProviderTest.java +++ b/aswb/tests/integrationtests/com/google/idea/blaze/android/run/DebuggerInfoProviderTest.java @@ -15,13 +15,13 @@ */ package com.google.idea.blaze.android.run; +import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.truth.Truth.assertThat; import static com.google.idea.blaze.java.AndroidBlazeRules.RuleTypes.ANDROID_BINARY; import static com.google.idea.blaze.java.AndroidBlazeRules.RuleTypes.ANDROID_INSTRUMENTATION_TEST; -import com.android.tools.idea.run.editor.AndroidDebugger; import com.android.tools.idea.run.editor.AndroidDebuggerInfoProvider; -import com.android.tools.idea.run.editor.AndroidJavaDebugger; +import com.google.common.collect.ImmutableList; import com.google.idea.blaze.android.BlazeAndroidIntegrationTestCase; import com.google.idea.blaze.android.MockSdkUtil; import com.google.idea.blaze.android.cppimpl.debug.BlazeNativeAndroidDebugger; @@ -29,6 +29,7 @@ import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryRunConfigurationState; import com.google.idea.blaze.android.run.test.BlazeAndroidTestLaunchMethodsProvider.AndroidTestLaunchMethod; import com.google.idea.blaze.android.run.test.BlazeAndroidTestRunConfigurationState; +import com.google.idea.blaze.android.tools.idea.run.editor.AndroidDebuggerCompat; import com.google.idea.blaze.base.dependencies.TargetInfo; import com.google.idea.blaze.base.model.primitives.Label; import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration; @@ -36,8 +37,6 @@ import com.intellij.execution.RunManager; import com.intellij.execution.RunnerAndConfigurationSettings; import com.intellij.execution.impl.RunManagerImpl; -import java.util.List; -import java.util.stream.Collectors; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -68,37 +67,48 @@ public void setupProject() { @Test public void getDebuggerFromProvider_nonNativeDebugging_returnsAndroidJavaDebugger() { - AndroidDebugger debugger = - debuggerInfoProvider.getSelectedAndroidDebugger(createAndroidBinaryRunConfiguration(false)); - assertThat(debugger).isInstanceOf(AndroidJavaDebugger.class); + assertThat( + AndroidDebuggerCompat.getSelectedAndroidDebugger( + debuggerInfoProvider, createAndroidBinaryRunConfiguration(false))) + .isInstanceOf(AndroidDebuggerCompat.getAndroidJavaDebugger().getClass()); } @Test public void getDebuggerFromProvider_withNativeDebugging_returnsBlazeAndroidNativeDebugger() { - AndroidDebugger debugger = - debuggerInfoProvider.getSelectedAndroidDebugger(createAndroidBinaryRunConfiguration(true)); - // Always prefer "Java" debugger for the "Connect Debugger to Android Process" dialog. - assertThat(debugger).isInstanceOf(AndroidJavaDebugger.class); + assertThat( + AndroidDebuggerCompat.getSelectedAndroidDebugger( + debuggerInfoProvider, createAndroidBinaryRunConfiguration(true))) + .isInstanceOf(AndroidDebuggerCompat.getAndroidJavaDebugger().getClass()); } @Test + @SuppressWarnings({"rawtypes"}) // Raw type from upstream. public void getAndroidDebuggers_withAndroidBinaryRunConfiguration_returnsJavaAndNative() { - List debuggers = - debuggerInfoProvider.getAndroidDebuggers(createAndroidBinaryRunConfiguration(true)); - - List classList = debuggers.stream().map(Object::getClass).collect(Collectors.toList()); + ImmutableList classList = + AndroidDebuggerCompat.getAndroidDebuggers( + debuggerInfoProvider, createAndroidBinaryRunConfiguration(true)) + .stream() + .map(Object::getClass) + .collect(toImmutableList()); assertThat(classList) - .containsExactly(AndroidJavaDebugger.class, BlazeNativeAndroidDebugger.class); + .containsExactly( + AndroidDebuggerCompat.getAndroidJavaDebugger().getClass(), + BlazeNativeAndroidDebugger.class); } @Test + @SuppressWarnings({"rawtypes"}) // Raw type from upstream. public void getAndroidDebuggers_withAndroidTestRunConfiguration_returnsJavaAndNative() { - List debuggers = - debuggerInfoProvider.getAndroidDebuggers(createAndroidTestRunConfiguration(true)); - - List classList = debuggers.stream().map(Object::getClass).collect(Collectors.toList()); + ImmutableList classList = + AndroidDebuggerCompat.getAndroidDebuggers( + debuggerInfoProvider, createAndroidTestRunConfiguration(true)) + .stream() + .map(Object::getClass) + .collect(toImmutableList()); assertThat(classList) - .containsExactly(AndroidJavaDebugger.class, BlazeNativeAndroidDebugger.class); + .containsExactly( + AndroidDebuggerCompat.getAndroidJavaDebugger().getClass(), + BlazeNativeAndroidDebugger.class); } private BlazeCommandRunConfiguration createAndroidBinaryRunConfiguration( diff --git a/aswb/tests/integrationtests/com/google/idea/blaze/android/run/runner/DefaultDebuggerServiceImplTest.java b/aswb/tests/integrationtests/com/google/idea/blaze/android/run/runner/DefaultDebuggerServiceImplTest.java index 8f0cd8b51eb..f2e343956d2 100644 --- a/aswb/tests/integrationtests/com/google/idea/blaze/android/run/runner/DefaultDebuggerServiceImplTest.java +++ b/aswb/tests/integrationtests/com/google/idea/blaze/android/run/runner/DefaultDebuggerServiceImplTest.java @@ -17,7 +17,7 @@ import static com.google.common.truth.Truth.assertThat; -import com.android.tools.ndk.run.editor.NativeAndroidDebuggerState; +import com.android.tools.ndk.run.editor.AutoAndroidDebuggerState; import com.google.idea.blaze.android.cppimpl.debug.BlazeAutoAndroidDebugger; import com.google.idea.blaze.android.run.runner.BlazeAndroidDebuggerService.DefaultDebuggerService; import com.google.idea.blaze.base.BlazeIntegrationTestCase; @@ -35,8 +35,8 @@ public void getDebuggerState_nativeDebugger_setsWorkspaceRootAndSourceRemap() { DefaultDebuggerService debuggerService = new DefaultDebuggerService(getProject()); BlazeAutoAndroidDebugger nativeDebugger = new BlazeAutoAndroidDebugger(); - NativeAndroidDebuggerState state = - (NativeAndroidDebuggerState) debuggerService.getDebuggerState(nativeDebugger); + AutoAndroidDebuggerState state = nativeDebugger.createState(); + debuggerService.configureNativeDebugger(state, null); assertThat(state.getWorkingDir()).isEqualTo(workspaceRoot); assertThat(state.getUserStartupCommands()) diff --git a/aswb/tests/integrationtests/com/google/idea/blaze/android/run/testrecorder/TestRecorderBlazeCommandRunConfigurationTest.java b/aswb/tests/integrationtests/com/google/idea/blaze/android/run/testrecorder/TestRecorderBlazeCommandRunConfigurationTest.java index 61779e050b9..4ac3094b009 100644 --- a/aswb/tests/integrationtests/com/google/idea/blaze/android/run/testrecorder/TestRecorderBlazeCommandRunConfigurationTest.java +++ b/aswb/tests/integrationtests/com/google/idea/blaze/android/run/testrecorder/TestRecorderBlazeCommandRunConfigurationTest.java @@ -115,7 +115,6 @@ public void testLaunchActivityClass() { TestRecorderRunConfigurationProxy proxy = TestRecorderRunConfigurationProxy.getInstance(blazeConfiguration); assertThat(proxy).isNotNull(); - assertThat(proxy.getLaunchActivityClass()).isEqualTo("MyAppMainActivity"); } @Test 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 7d81fed81d5..7ed71793541 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 @@ -28,7 +28,7 @@ import com.google.common.collect.ImmutableList; import com.google.idea.blaze.android.BlazeAndroidIntegrationTestCase; import com.google.idea.blaze.android.MockSdkUtil; -import com.google.idea.blaze.android.libraries.AarLibraryFileBuilder; +import com.google.idea.blaze.android.libraries.LibraryFileBuilder; import com.google.idea.blaze.android.sdk.BlazeSdkProvider; import com.google.idea.blaze.android.sync.sdk.AndroidSdkFromProjectView; import com.google.idea.blaze.android.sync.sdk.SdkUtil; @@ -154,7 +154,8 @@ public void testSimpleSync_invalidSdkAndFailToReInstall() { .setAddProjectViewTargets(true) .build()); List allSdks = BlazeSdkProvider.getInstance().getAllAndroidSdks(); - assertThat(allSdks).containsExactly(testEnvArgument.sdk); + assertThat(allSdks).hasSize(1); + assertThat(allSdks.get(0).getName()).isEqualTo(testEnvArgument.sdk.getName()); errorCollector.assertIssues( String.format( AndroidSdkFromProjectView.NO_SDK_ERROR_TEMPLATE, @@ -349,7 +350,7 @@ public void testAarImportWithSources() { "android_sdk_platform: android-25"); workspace.createFile(new WorkspacePath("java/com/google/foo.aar")); - workspace.createFile(new WorkspacePath("java/com/google/foo.srcjar")); + workspace.createFile(new WorkspacePath("foo.srcjar")); workspace.createDirectory(new WorkspacePath("java/com/google")); workspace.createFile( new WorkspacePath("java/com/google/Source.java"), @@ -361,8 +362,8 @@ public void testAarImportWithSources() { "public class Other {}"); // construct an aar file with a res file. - AarLibraryFileBuilder.aar(workspaceRoot, "java/com/google/foo.aar") - .src( + LibraryFileBuilder.aar(workspaceRoot, "java/com/google/foo.aar") + .addContent( "res/values/colors.xml", ImmutableList.of( "", diff --git a/aswb/tests/integrationtests/com/google/idea/blaze/android/sync/importer/BlazeAndroidWorkspaceImporterTest.java b/aswb/tests/integrationtests/com/google/idea/blaze/android/sync/importer/BlazeAndroidWorkspaceImporterTest.java index 70f082baac8..bf6a7497c22 100644 --- a/aswb/tests/integrationtests/com/google/idea/blaze/android/sync/importer/BlazeAndroidWorkspaceImporterTest.java +++ b/aswb/tests/integrationtests/com/google/idea/blaze/android/sync/importer/BlazeAndroidWorkspaceImporterTest.java @@ -27,19 +27,14 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; +import com.google.idea.blaze.android.BlazeAndroidIntegrationTestCase; import com.google.idea.blaze.android.projectview.GeneratedAndroidResourcesSection; import com.google.idea.blaze.android.projectview.GenfilesPath; import com.google.idea.blaze.android.sync.BlazeAndroidJavaSyncAugmenter; import com.google.idea.blaze.android.sync.BlazeAndroidLibrarySource; -import com.google.idea.blaze.android.sync.importer.problems.GeneratedResourceRetentionFilter; import com.google.idea.blaze.android.sync.model.AarLibrary; import com.google.idea.blaze.android.sync.model.AndroidResourceModule; import com.google.idea.blaze.android.sync.model.BlazeAndroidImportResult; -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.LibraryArtifact; import com.google.idea.blaze.base.ideinfo.TargetIdeInfo; @@ -47,68 +42,50 @@ import com.google.idea.blaze.base.ideinfo.TargetMap; import com.google.idea.blaze.base.io.FileOperationProvider; import com.google.idea.blaze.base.model.LibraryKey; -import com.google.idea.blaze.base.model.primitives.GenericBlazeRules; -import com.google.idea.blaze.base.model.primitives.Kind; -import com.google.idea.blaze.base.model.primitives.Kind.Provider; import com.google.idea.blaze.base.model.primitives.Label; import com.google.idea.blaze.base.model.primitives.LanguageClass; import com.google.idea.blaze.base.model.primitives.WorkspacePath; import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; import com.google.idea.blaze.base.model.primitives.WorkspaceType; -import com.google.idea.blaze.base.prefetch.MockPrefetchService; -import com.google.idea.blaze.base.prefetch.PrefetchService; -import com.google.idea.blaze.base.prefetch.RemoteArtifactPrefetcher; 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.scope.BlazeContext; -import com.google.idea.blaze.base.scope.ErrorCollector; import com.google.idea.blaze.base.scope.output.IssueOutput; import com.google.idea.blaze.base.settings.Blaze; import com.google.idea.blaze.base.settings.BlazeImportSettings; +import com.google.idea.blaze.base.settings.BlazeImportSettings.ProjectType; import com.google.idea.blaze.base.settings.BlazeImportSettingsManager; import com.google.idea.blaze.base.settings.BuildSystemName; -import com.google.idea.blaze.base.sync.MockRemoteArtifactPrefetcher; import com.google.idea.blaze.base.sync.projectview.ImportRoots; import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings; import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder; import com.google.idea.blaze.base.sync.workspace.MockArtifactLocationDecoder; import com.google.idea.blaze.base.sync.workspace.WorkingSet; -import com.google.idea.blaze.java.AndroidBlazeRules; -import com.google.idea.blaze.java.JavaBlazeRules; -import com.google.idea.blaze.java.sync.BlazeJavaSyncAugmenter; import com.google.idea.blaze.java.sync.importer.BlazeJavaWorkspaceImporter; import com.google.idea.blaze.java.sync.importer.JavaSourceFilter; -import com.google.idea.blaze.java.sync.importer.emptylibrary.EmptyLibraryFilterSettings; import com.google.idea.blaze.java.sync.jdeps.MockJdepsMap; import com.google.idea.blaze.java.sync.model.BlazeJarLibrary; import com.google.idea.blaze.java.sync.model.BlazeJavaImportResult; -import com.google.idea.blaze.java.sync.source.JavaLikeLanguage; -import com.google.idea.blaze.java.sync.source.JavaSourcePackageReader; -import com.google.idea.blaze.java.sync.source.PackageManifestReader; -import com.google.idea.blaze.java.sync.source.SourceArtifact; import com.google.idea.blaze.java.sync.workingset.JavaWorkingSet; import com.google.idea.common.experiments.ExperimentService; import com.google.idea.common.experiments.MockExperimentService; -import com.intellij.openapi.extensions.impl.ExtensionPointImpl; import com.intellij.openapi.project.Project; import java.io.File; import java.util.List; import java.util.Map; import java.util.function.Predicate; import java.util.stream.Collectors; -import javax.annotation.Nullable; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** Tests for BlazeAndroidWorkspaceImporter */ @RunWith(JUnit4.class) -public class BlazeAndroidWorkspaceImporterTest extends BlazeTestCase { - - private final WorkspaceRoot workspaceRoot = new WorkspaceRoot(new File("/root")); +public class BlazeAndroidWorkspaceImporterTest extends BlazeAndroidIntegrationTestCase { private static final String FAKE_GEN_ROOT_EXECUTION_PATH_FRAGMENT = "bazel-out/gcc-4.X.Y-crosstool-v17-hybrid-grtev3-k8-fastbuild/bin"; @@ -122,10 +99,9 @@ public File decode(ArtifactLocation artifactLocation) { }; private static final BlazeImportSettings DUMMY_IMPORT_SETTINGS = - new BlazeImportSettings("", "", "", "", BuildSystemName.Bazel); + new BlazeImportSettings("", "", "", "", BuildSystemName.Bazel, ProjectType.ASPECT_SYNC); private BlazeContext context; - private ErrorCollector errorCollector = new ErrorCollector(); private final MockJdepsMap jdepsMap = new MockJdepsMap(); private final JavaWorkingSet workingSet = new JavaWorkingSet( @@ -135,68 +111,20 @@ public File decode(ArtifactLocation artifactLocation) { private final WorkspaceLanguageSettings workspaceLanguageSettings = new WorkspaceLanguageSettings( WorkspaceType.ANDROID, ImmutableSet.of(LanguageClass.ANDROID, LanguageClass.JAVA)); - private ExtensionPointImpl retentionFilterEp; private MockExperimentService experimentService; - @Override - protected void initTest(Container applicationServices, Container projectServices) { + @Before + public void importerSetUp() { experimentService = new MockExperimentService(); - applicationServices.register(ExperimentService.class, experimentService); - - BlazeExecutor blazeExecutor = new MockBlazeExecutor(); - applicationServices.register(BlazeExecutor.class, blazeExecutor); + registerApplicationComponent(ExperimentService.class, experimentService); - projectServices.register( - BlazeImportSettingsManager.class, new BlazeImportSettingsManager(project)); BlazeImportSettingsManager.getInstance(getProject()).setImportSettings(DUMMY_IMPORT_SETTINGS); MockFileOperationProvider mockFileOperationProvider = new MockFileOperationProvider(); - applicationServices.register(FileOperationProvider.class, mockFileOperationProvider); - - ExtensionPointImpl targetKindEp = - registerExtensionPoint(Provider.EP_NAME, Provider.class); - targetKindEp.registerExtension(new AndroidBlazeRules()); - targetKindEp.registerExtension(new JavaBlazeRules()); - targetKindEp.registerExtension(new GenericBlazeRules()); - applicationServices.register(Kind.ApplicationState.class, new Kind.ApplicationState()); - - retentionFilterEp = - registerExtensionPoint( - GeneratedResourceRetentionFilter.EP_NAME, GeneratedResourceRetentionFilter.class); + registerApplicationService(FileOperationProvider.class, mockFileOperationProvider); context = BlazeContext.create(); context.addOutputSink(IssueOutput.class, errorCollector); - - registerExtensionPoint(BlazeJavaSyncAugmenter.EP_NAME, BlazeJavaSyncAugmenter.class); - registerExtensionPoint(EmptyLibraryFilterSettings.EP_NAME, EmptyLibraryFilterSettings.class); - - // For importJavaWorkspace. - applicationServices.register( - JavaSourcePackageReader.class, - new JavaSourcePackageReader() { - @Nullable - @Override - public String getDeclaredPackageOfJavaFile( - BlazeContext context, - ArtifactLocationDecoder artifactLocationDecoder, - SourceArtifact sourceArtifact) { - return null; - } - }); - - applicationServices.register(PackageManifestReader.class, new PackageManifestReader()); - applicationServices.register(PrefetchService.class, new MockPrefetchService()); - - registerExtensionPoint(JavaLikeLanguage.EP_NAME, JavaLikeLanguage.class) - .registerExtension(new JavaLikeLanguage.Java()); - - applicationServices.register( - RemoteArtifactPrefetcher.class, new MockRemoteArtifactPrefetcher()); - } - - @Override - protected BuildSystemProvider createBuildSystemProvider() { - return new BazelBuildSystemProvider(); } private BlazeAndroidImportResult importWorkspace( @@ -204,10 +132,10 @@ private BlazeAndroidImportResult importWorkspace( ProjectViewSet projectViewSet = ProjectViewSet.builder().add(projectView).build(); BlazeAndroidWorkspaceImporter workspaceImporter = new BlazeAndroidWorkspaceImporter( - project, + getProject(), context, BlazeImportInput.forProject( - project, workspaceRoot, projectViewSet, targetMap, FAKE_ARTIFACT_DECODER)); + getProject(), workspaceRoot, projectViewSet, targetMap, FAKE_ARTIFACT_DECODER)); return workspaceImporter.importWorkspace(); } @@ -215,13 +143,13 @@ private BlazeAndroidImportResult importWorkspace( private BlazeJavaImportResult importJavaWorkspace( WorkspaceRoot workspaceRoot, TargetMap targetMap, ProjectView projectView) { - BuildSystemName buildSystemName = Blaze.getBuildSystemName(project); + BuildSystemName buildSystemName = Blaze.getBuildSystemName(getProject()); ProjectViewSet projectViewSet = ProjectViewSet.builder().add(projectView).build(); JavaSourceFilter sourceFilter = new JavaSourceFilter(buildSystemName, workspaceRoot, projectViewSet, targetMap); BlazeJavaWorkspaceImporter blazeWorkspaceImporter = new BlazeJavaWorkspaceImporter( - project, + getProject(), workspaceRoot, projectViewSet, workspaceLanguageSettings, @@ -628,9 +556,6 @@ public void conflictingResourceRClasses_resourceMergingEnabled_mergesClassesInto */ @Test public void generatedResourceRetentionFilter_retainsPassingResourceDependency() { - retentionFilterEp.registerExtension( - artifactLocation -> artifactLocation.getRelativePath().startsWith("common_deps")); - ProjectView projectView = ProjectView.builder() .add( @@ -1264,10 +1189,10 @@ public void androidResourceModuleGeneration_longResourceDepChains_constructedCor ProjectViewSet projectViewSet = ProjectViewSet.builder().add(projectView).build(); MockBlazeAndroidWorkspaceImporter mockBlazeAndroidWorkspaceImporter = new MockBlazeAndroidWorkspaceImporter( - project, + getProject(), context, BlazeImportInput.forProject( - project, workspaceRoot, projectViewSet, targetMap, FAKE_ARTIFACT_DECODER)); + getProject(), workspaceRoot, projectViewSet, targetMap, FAKE_ARTIFACT_DECODER)); AndroidResourceModule expectedAndroidResourceModule1 = AndroidResourceModule.builder(TargetKey.forPlainTarget(Label.create("//foo:lib"))) .addResourceAndTransitiveResource(source("foo/res")) diff --git a/aswb/tests/integrationtests/com/google/idea/blaze/android/sync/model/idea/BlazeImportFixture.java b/aswb/tests/integrationtests/com/google/idea/blaze/android/sync/model/idea/BlazeImportFixture.java index 87b07cdc48c..00c53067cee 100644 --- a/aswb/tests/integrationtests/com/google/idea/blaze/android/sync/model/idea/BlazeImportFixture.java +++ b/aswb/tests/integrationtests/com/google/idea/blaze/android/sync/model/idea/BlazeImportFixture.java @@ -15,8 +15,8 @@ */ package com.google.idea.blaze.android.sync.model.idea; +import com.android.ide.common.repository.GoogleMavenArtifactIdCompat; import com.android.ide.common.repository.GradleCoordinate; -import com.android.tools.idea.projectsystem.GoogleMavenArtifactId; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.idea.blaze.android.projectsystem.MavenArtifactLocator; @@ -202,14 +202,7 @@ public MavenArtifactLocator getMavenArtifactLocator() { new MavenArtifactLocator() { @Override public Label labelFor(GradleCoordinate coordinate) { - switch (GoogleMavenArtifactId.forCoordinate(coordinate)) { - case RECYCLERVIEW_V7: - return Label.create("//third_party/recyclerview:recyclerview"); - case CONSTRAINT_LAYOUT: - return Label.create("//third_party/constraint_layout:constraint_layout"); - default: - return null; - } + return GoogleMavenArtifactIdCompat.getLabelForGoogleMaventArtifact(coordinate); } @Override diff --git a/aswb/tests/integrationtests/com/google/idea/blaze/android/projectsystem/RenderJarClassFileFinderTest.java b/aswb/tests/integrationtests/sdkcompat/as223/com/google/idea/blaze/android/projectsystem/RenderJarClassFileFinderTest.java similarity index 96% rename from aswb/tests/integrationtests/com/google/idea/blaze/android/projectsystem/RenderJarClassFileFinderTest.java rename to aswb/tests/integrationtests/sdkcompat/as223/com/google/idea/blaze/android/projectsystem/RenderJarClassFileFinderTest.java index 93af1f0fc33..e88f2dd9a35 100644 --- a/aswb/tests/integrationtests/com/google/idea/blaze/android/projectsystem/RenderJarClassFileFinderTest.java +++ b/aswb/tests/integrationtests/sdkcompat/as223/com/google/idea/blaze/android/projectsystem/RenderJarClassFileFinderTest.java @@ -27,8 +27,7 @@ import com.google.idea.blaze.android.sync.aspects.strategy.RenderResolveOutputGroupProvider; import com.google.idea.blaze.android.sync.model.AndroidResourceModule; import com.google.idea.blaze.android.sync.model.AndroidResourceModuleRegistry; -import com.google.idea.blaze.base.command.buildresult.LocalFileOutputArtifact; -import com.google.idea.blaze.base.command.buildresult.OutputArtifact; +import com.google.idea.blaze.base.command.buildresult.LocalFileOutputArtifactWithoutDigest; import com.google.idea.blaze.base.filecache.FileCache; import com.google.idea.blaze.base.ideinfo.AndroidIdeInfo; import com.google.idea.blaze.base.ideinfo.ArtifactLocation; @@ -47,6 +46,7 @@ import com.google.idea.blaze.base.sync.projectstructure.ModuleEditorProvider; import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder; import com.google.idea.blaze.base.sync.workspace.MockArtifactLocationDecoder; +import com.google.idea.blaze.common.artifact.OutputArtifactWithoutDigest; import com.google.idea.common.experiments.ExperimentService; import com.google.idea.common.experiments.MockExperimentService; import com.intellij.openapi.application.ApplicationManager; @@ -144,21 +144,21 @@ public void workspaceModule_canFindAllClassesInAllBinaries() throws ArtifactNotF CacheEntry binACacheEntry = CacheEntry.forArtifact( - (LocalFileOutputArtifact) + (LocalFileOutputArtifactWithoutDigest) artifactLocationDecoder.resolveOutput( getArtifactLocation("com/google/example/simple/bin_a.jar"))); String binAJar = cacheDir.getAbsoluteFile() + "/" + binACacheEntry.getFileName(); CacheEntry binBCacheEntry = CacheEntry.forArtifact( - (LocalFileOutputArtifact) + (LocalFileOutputArtifactWithoutDigest) artifactLocationDecoder.resolveOutput( getArtifactLocation("com/google/example/simple/bin_b.jar"))); String binBJar = cacheDir.getAbsoluteFile() + "/" + binBCacheEntry.getFileName(); CacheEntry binCCacheEntry = CacheEntry.forArtifact( - (LocalFileOutputArtifact) + (LocalFileOutputArtifactWithoutDigest) artifactLocationDecoder.resolveOutput( getArtifactLocation("com/google/example/simple/bin_c.jar"))); String binCJar = cacheDir.getAbsoluteFile() + "/" + binCCacheEntry.getFileName(); @@ -228,21 +228,21 @@ public void resourceModule_canFindSourceClasses() throws ArtifactNotFoundExcepti CacheEntry binACacheEntry = CacheEntry.forArtifact( - (LocalFileOutputArtifact) + (LocalFileOutputArtifactWithoutDigest) artifactLocationDecoder.resolveOutput( getArtifactLocation("com/google/example/simple/bin_a.jar"))); String binAJar = cacheDir.getAbsoluteFile() + "/" + binACacheEntry.getFileName(); CacheEntry binBCacheEntry = CacheEntry.forArtifact( - (LocalFileOutputArtifact) + (LocalFileOutputArtifactWithoutDigest) artifactLocationDecoder.resolveOutput( getArtifactLocation("com/google/example/simple/bin_b.jar"))); String binBJar = cacheDir.getAbsoluteFile() + "/" + binBCacheEntry.getFileName(); CacheEntry binCCacheEntry = CacheEntry.forArtifact( - (LocalFileOutputArtifact) + (LocalFileOutputArtifactWithoutDigest) artifactLocationDecoder.resolveOutput( getArtifactLocation("com/google/example/simple/bin_c.jar"))); String binCJar = cacheDir.getAbsoluteFile() + "/" + binCCacheEntry.getFileName(); @@ -285,21 +285,21 @@ public void resourceModule_canFindDependencyClasses() throws ArtifactNotFoundExc CacheEntry binACacheEntry = CacheEntry.forArtifact( - (LocalFileOutputArtifact) + (LocalFileOutputArtifactWithoutDigest) artifactLocationDecoder.resolveOutput( getArtifactLocation("com/google/example/simple/bin_a.jar"))); String binAJar = cacheDir.getAbsoluteFile() + "/" + binACacheEntry.getFileName(); CacheEntry binBCacheEntry = CacheEntry.forArtifact( - (LocalFileOutputArtifact) + (LocalFileOutputArtifactWithoutDigest) artifactLocationDecoder.resolveOutput( getArtifactLocation("com/google/example/simple/bin_b.jar"))); String binBJar = cacheDir.getAbsoluteFile() + "/" + binBCacheEntry.getFileName(); CacheEntry binCCacheEntry = CacheEntry.forArtifact( - (LocalFileOutputArtifact) + (LocalFileOutputArtifactWithoutDigest) artifactLocationDecoder.resolveOutput( getArtifactLocation("com/google/example/simple/bin_c.jar"))); String binCJar = cacheDir.getAbsoluteFile() + "/" + binCCacheEntry.getFileName(); @@ -447,8 +447,8 @@ private void createBinaryJars() throws ArtifactNotFoundException { fileSystem.createDirectory(cacheDirFile.getAbsolutePath()); String cacheDir = cacheDirFile.getPath(); - OutputArtifact binAArtifact = - (OutputArtifact) + OutputArtifactWithoutDigest binAArtifact = + (OutputArtifactWithoutDigest) artifactLocationDecoder.resolveOutput( getArtifactLocation("com/google/example/simple/bin_a.jar")); CacheEntry binACacheEntry = CacheEntry.forArtifact(binAArtifact); @@ -465,8 +465,8 @@ private void createBinaryJars() throws ArtifactNotFoundException { fileSystem.createFile(binAJar + "!/com/google/example/simple/trans_dep_a/R$dimen.class"); artifactCache.addTrackedFile(binAArtifact, binAJar); - OutputArtifact binBArtifact = - (OutputArtifact) + OutputArtifactWithoutDigest binBArtifact = + (OutputArtifactWithoutDigest) artifactLocationDecoder.resolveOutput( getArtifactLocation("com/google/example/simple/bin_b.jar")); CacheEntry binBCacheEntry = CacheEntry.forArtifact(binBArtifact); @@ -483,8 +483,8 @@ private void createBinaryJars() throws ArtifactNotFoundException { fileSystem.createFile(binBJar + "!/com/google/example/simple/trans_dep_b/R$dimen.class"); artifactCache.addTrackedFile(binBArtifact, binBJar); - OutputArtifact binCArtifact = - (OutputArtifact) + OutputArtifactWithoutDigest binCArtifact = + (OutputArtifactWithoutDigest) artifactLocationDecoder.resolveOutput( getArtifactLocation("com/google/example/simple/bin_c.jar")); CacheEntry binCCacheEntry = CacheEntry.forArtifact(binCArtifact); diff --git a/aswb/tests/integrationtests/sdkcompat/as231/com/google/idea/blaze/android/projectsystem/RenderJarClassFileFinderTest.java b/aswb/tests/integrationtests/sdkcompat/as231/com/google/idea/blaze/android/projectsystem/RenderJarClassFileFinderTest.java new file mode 100644 index 00000000000..e88f2dd9a35 --- /dev/null +++ b/aswb/tests/integrationtests/sdkcompat/as231/com/google/idea/blaze/android/projectsystem/RenderJarClassFileFinderTest.java @@ -0,0 +1,552 @@ +/* + * Copyright 2020 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.projectsystem; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.idea.blaze.android.sync.projectstructure.BlazeAndroidProjectStructureSyncer.moduleNameForAndroidModule; +import static com.google.idea.blaze.base.sync.data.BlazeDataStorage.WORKSPACE_MODULE_NAME; + +import com.google.idea.blaze.android.BlazeAndroidIntegrationTestCase; +import com.google.idea.blaze.android.filecache.ArtifactNotFoundException; +import com.google.idea.blaze.android.filecache.CacheEntry; +import com.google.idea.blaze.android.filecache.MockArtifactCache; +import com.google.idea.blaze.android.libraries.RenderJarCache; +import com.google.idea.blaze.android.sync.aspects.strategy.RenderResolveOutputGroupProvider; +import com.google.idea.blaze.android.sync.model.AndroidResourceModule; +import com.google.idea.blaze.android.sync.model.AndroidResourceModuleRegistry; +import com.google.idea.blaze.base.command.buildresult.LocalFileOutputArtifactWithoutDigest; +import com.google.idea.blaze.base.filecache.FileCache; +import com.google.idea.blaze.base.ideinfo.AndroidIdeInfo; +import com.google.idea.blaze.base.ideinfo.ArtifactLocation; +import com.google.idea.blaze.base.ideinfo.TargetIdeInfo; +import com.google.idea.blaze.base.ideinfo.TargetKey; +import com.google.idea.blaze.base.ideinfo.TargetMap; +import com.google.idea.blaze.base.ideinfo.TargetMapBuilder; +import com.google.idea.blaze.base.io.FileOperationProvider; +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.settings.BlazeImportSettings; +import com.google.idea.blaze.base.settings.BlazeImportSettingsManager; +import com.google.idea.blaze.base.sync.BlazeSyncPlugin.ModuleEditor; +import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; +import com.google.idea.blaze.base.sync.projectstructure.ModuleEditorProvider; +import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder; +import com.google.idea.blaze.base.sync.workspace.MockArtifactLocationDecoder; +import com.google.idea.blaze.common.artifact.OutputArtifactWithoutDigest; +import com.google.idea.common.experiments.ExperimentService; +import com.google.idea.common.experiments.MockExperimentService; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleManager; +import com.intellij.openapi.module.StdModuleTypes; +import java.io.File; +import java.util.HashMap; +import java.util.Objects; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link RenderJarClassFileFinder} */ +@RunWith(JUnit4.class) +public class RenderJarClassFileFinderTest extends BlazeAndroidIntegrationTestCase { + + private static final String BLAZE_BIN = "blaze-out/crosstool/bin"; + + // Utility map to quickly access Labels in target map. Populated by `buildTargetMap`, and used to + // ensure the tests don't accidentally access targets not set up in the target map + private final HashMap targetNameToLabel = new HashMap<>(); + + private ArtifactLocationDecoder artifactLocationDecoder; + private MockArtifactCache artifactCache; + + @Before + public void initTest() throws ArtifactNotFoundException { + TargetMap targetMap = buildTargetMap(); + setTargetMap(targetMap); + + // Since this is not a light test, the ArtifactLocationDecoder points to the actual file in the + // File System + artifactLocationDecoder = + new MockArtifactLocationDecoder() { + @Override + public File decode(ArtifactLocation artifactLocation) { + File f = + new File(fileSystem.getRootDir(), artifactLocation.getExecutionRootRelativePath()); + + // Create the artifact if it does not exist + // This allows us to set fileModifiedTime for the file which is used by RenderJarCache + FileOperationProvider.getInstance().mkdirs(f); + + // Set last modified time for RenderJarCache to use + FileOperationProvider.getInstance().setFileModifiedTime(f, 1000L); + return f; + } + }; + + registerProjectService( + BlazeProjectDataManager.class, + new MockBlazeProjectDataManager( + MockBlazeProjectDataBuilder.builder(workspaceRoot) + .setTargetMap(targetMap) + .setArtifactLocationDecoder(artifactLocationDecoder) + .build())); + + setProjectView( + "targets:", + " //com/google/example/simple/bin_a:bin_a", + " //com/google/example/simple/bin_b:bin_b", + " //com/google/example/simple/bin_c:bin_c"); + + MockExperimentService experimentService = new MockExperimentService(); + registerApplicationComponent(ExperimentService.class, experimentService); + experimentService.setExperiment(RenderResolveOutputGroupProvider.buildOnSync, true); + // Disable resource resolution from Render Jars + experimentService.setExperiment(RenderJarClassFileFinder.resolveResourceClasses, false); + + ApplicationManager.getApplication().runWriteAction(this::createAndRegisterModules); + + artifactCache = new MockArtifactCache(); + registerExtension(FileCache.EP_NAME, new RenderJarCache.FileCacheAdapter()); + registerProjectService( + RenderJarCache.class, + new RenderJarCache( + getProject(), RenderJarCache.getCacheDirForProject(getProject()), artifactCache)); + + createBinaryJars(); + FileCache.EP_NAME.extensions().forEach(ep -> ep.initialize(getProject())); + } + + /** Tests that .workspace module can find classes from all binaries in the projectview. */ + @Test + public void workspaceModule_canFindAllClassesInAllBinaries() throws ArtifactNotFoundException { + Module workspaceModule = + ModuleManager.getInstance(getProject()).findModuleByName(WORKSPACE_MODULE_NAME); + assertThat(workspaceModule).isNotNull(); + + RenderJarClassFileFinder classFileFinder = new RenderJarClassFileFinder(workspaceModule); + + File cacheDir = RenderJarCache.getInstance(getProject()).getCacheDir(); + + CacheEntry binACacheEntry = + CacheEntry.forArtifact( + (LocalFileOutputArtifactWithoutDigest) + artifactLocationDecoder.resolveOutput( + getArtifactLocation("com/google/example/simple/bin_a.jar"))); + String binAJar = cacheDir.getAbsoluteFile() + "/" + binACacheEntry.getFileName(); + + CacheEntry binBCacheEntry = + CacheEntry.forArtifact( + (LocalFileOutputArtifactWithoutDigest) + artifactLocationDecoder.resolveOutput( + getArtifactLocation("com/google/example/simple/bin_b.jar"))); + String binBJar = cacheDir.getAbsoluteFile() + "/" + binBCacheEntry.getFileName(); + + CacheEntry binCCacheEntry = + CacheEntry.forArtifact( + (LocalFileOutputArtifactWithoutDigest) + artifactLocationDecoder.resolveOutput( + getArtifactLocation("com/google/example/simple/bin_c.jar"))); + String binCJar = cacheDir.getAbsoluteFile() + "/" + binCCacheEntry.getFileName(); + + assertThat(classFileFinder.findClassFile("com.google.example.simple.src_a.SrcA")) + .isEqualTo(fileSystem.findFile(binAJar + "!/com/google/example/simple/src_a/SrcA.class")); + assertThat(classFileFinder.findClassFile("com.google.example.simple.src_a.SrcA$Inner")) + .isEqualTo( + fileSystem.findFile(binAJar + "!/com/google/example/simple/src_a/SrcA$Inner.class")); + + assertThat(classFileFinder.findClassFile("com.google.example.simple.src_b.SrcB")) + .isEqualTo(fileSystem.findFile(binBJar + "!/com/google/example/simple/src_b/SrcB.class")); + assertThat(classFileFinder.findClassFile("com.google.example.simple.src_b.SrcB$Inner")) + .isEqualTo( + fileSystem.findFile(binBJar + "!/com/google/example/simple/src_b/SrcB$Inner.class")); + + assertThat(classFileFinder.findClassFile("com.google.example.simple.src_c.SrcC")) + .isEqualTo(fileSystem.findFile(binCJar + "!/com/google/example/simple/src_c/SrcC.class")); + assertThat(classFileFinder.findClassFile("com.google.example.simple.src_c.SrcC$Inner")) + .isEqualTo( + fileSystem.findFile(binCJar + "!/com/google/example/simple/src_c/SrcC$Inner.class")); + + assertThat(classFileFinder.findClassFile("com.google.example.simple.trans_dep_a.TransDepA")) + .isEqualTo( + fileSystem.findFile( + binAJar + "!/com/google/example/simple/trans_dep_a/TransDepA.class")); + assertThat( + classFileFinder.findClassFile("com.google.example.simple.trans_dep_a.TransDepA$Inner")) + .isEqualTo( + fileSystem.findFile( + binAJar + "!/com/google/example/simple/trans_dep_a/TransDepA$Inner.class")); + + assertThat(classFileFinder.findClassFile("com.google.example.simple.trans_dep_b.TransDepB")) + .isEqualTo( + fileSystem.findFile( + binBJar + "!/com/google/example/simple/trans_dep_b/TransDepB.class")); + assertThat( + classFileFinder.findClassFile("com.google.example.simple.trans_dep_b.TransDepB$Inner")) + .isEqualTo( + fileSystem.findFile( + binBJar + "!/com/google/example/simple/trans_dep_b/TransDepB$Inner.class")); + + assertThat(classFileFinder.findClassFile("com.google.example.simple.trans_dep_c.TransDepC")) + .isEqualTo( + fileSystem.findFile( + binCJar + "!/com/google/example/simple/trans_dep_c/TransDepC.class")); + assertThat( + classFileFinder.findClassFile("com.google.example.simple.trans_dep_c.TransDepC$Inner")) + .isEqualTo( + fileSystem.findFile( + binCJar + "!/com/google/example/simple/trans_dep_c/TransDepC$Inner.class")); + } + + /** + * Tests that resource modules can correctly find classes corresponding to sources of the targets + * that comprise the resource module. + */ + @Test + public void resourceModule_canFindSourceClasses() throws ArtifactNotFoundException { + AndroidResourceModuleRegistry moduleRegistry = + AndroidResourceModuleRegistry.getInstance(getProject()); + Module aResourceModule = + moduleRegistry.getModuleContainingResourcesOf(getTargetKey("/src_a:src_a")); + RenderJarClassFileFinder aClassFileFinder = new RenderJarClassFileFinder(aResourceModule); + + File cacheDir = RenderJarCache.getInstance(getProject()).getCacheDir(); + + CacheEntry binACacheEntry = + CacheEntry.forArtifact( + (LocalFileOutputArtifactWithoutDigest) + artifactLocationDecoder.resolveOutput( + getArtifactLocation("com/google/example/simple/bin_a.jar"))); + String binAJar = cacheDir.getAbsoluteFile() + "/" + binACacheEntry.getFileName(); + + CacheEntry binBCacheEntry = + CacheEntry.forArtifact( + (LocalFileOutputArtifactWithoutDigest) + artifactLocationDecoder.resolveOutput( + getArtifactLocation("com/google/example/simple/bin_b.jar"))); + String binBJar = cacheDir.getAbsoluteFile() + "/" + binBCacheEntry.getFileName(); + + CacheEntry binCCacheEntry = + CacheEntry.forArtifact( + (LocalFileOutputArtifactWithoutDigest) + artifactLocationDecoder.resolveOutput( + getArtifactLocation("com/google/example/simple/bin_c.jar"))); + String binCJar = cacheDir.getAbsoluteFile() + "/" + binCCacheEntry.getFileName(); + + assertThat(aClassFileFinder.findClassFile("com.google.example.simple.src_a.SrcA")) + .isEqualTo(fileSystem.findFile(binAJar + "!/com/google/example/simple/src_a/SrcA.class")); + assertThat(aClassFileFinder.findClassFile("com.google.example.simple.src_a.SrcA$Inner")) + .isEqualTo( + fileSystem.findFile(binAJar + "!/com/google/example/simple/src_a/SrcA$Inner.class")); + + assertThat(aClassFileFinder.findClassFile("com.google.example.simple.src_b.SrcB")) + .isEqualTo(fileSystem.findFile(binBJar + "!/com/google/example/simple/src_b/SrcB.class")); + assertThat(aClassFileFinder.findClassFile("com.google.example.simple.src_b.SrcB$Inner")) + .isEqualTo( + fileSystem.findFile(binBJar + "!/com/google/example/simple/src_b/SrcB$Inner.class")); + + Module cResourceModule = + moduleRegistry.getModuleContainingResourcesOf(getTargetKey("/src_c:src_c")); + RenderJarClassFileFinder cClassFileFinder = new RenderJarClassFileFinder(cResourceModule); + assertThat(cClassFileFinder.findClassFile("com.google.example.simple.src_c.SrcC")) + .isEqualTo(fileSystem.findFile(binCJar + "!/com/google/example/simple/src_c/SrcC.class")); + assertThat(cClassFileFinder.findClassFile("com.google.example.simple.src_c.SrcC$Inner")) + .isEqualTo( + fileSystem.findFile(binCJar + "!/com/google/example/simple/src_c/SrcC$Inner.class")); + } + + /** + * Tests that resource modules can find classes from dependencies of source targets that comprise + * the resource module. + */ + @Test + public void resourceModule_canFindDependencyClasses() throws ArtifactNotFoundException { + AndroidResourceModuleRegistry moduleRegistry = + AndroidResourceModuleRegistry.getInstance(getProject()); + Module aResourceModule = + moduleRegistry.getModuleContainingResourcesOf(getTargetKey("/src_a:src_a")); + RenderJarClassFileFinder aClassFileFinder = new RenderJarClassFileFinder(aResourceModule); + + File cacheDir = RenderJarCache.getInstance(getProject()).getCacheDir(); + + CacheEntry binACacheEntry = + CacheEntry.forArtifact( + (LocalFileOutputArtifactWithoutDigest) + artifactLocationDecoder.resolveOutput( + getArtifactLocation("com/google/example/simple/bin_a.jar"))); + String binAJar = cacheDir.getAbsoluteFile() + "/" + binACacheEntry.getFileName(); + + CacheEntry binBCacheEntry = + CacheEntry.forArtifact( + (LocalFileOutputArtifactWithoutDigest) + artifactLocationDecoder.resolveOutput( + getArtifactLocation("com/google/example/simple/bin_b.jar"))); + String binBJar = cacheDir.getAbsoluteFile() + "/" + binBCacheEntry.getFileName(); + + CacheEntry binCCacheEntry = + CacheEntry.forArtifact( + (LocalFileOutputArtifactWithoutDigest) + artifactLocationDecoder.resolveOutput( + getArtifactLocation("com/google/example/simple/bin_c.jar"))); + String binCJar = cacheDir.getAbsoluteFile() + "/" + binCCacheEntry.getFileName(); + + assertThat(aClassFileFinder.findClassFile("com.google.example.simple.trans_dep_a.TransDepA")) + .isEqualTo( + fileSystem.findFile( + binAJar + "!/com/google/example/simple/trans_dep_a/TransDepA.class")); + assertThat( + aClassFileFinder.findClassFile("com.google.example.simple.trans_dep_a.TransDepA$Inner")) + .isEqualTo( + fileSystem.findFile( + binAJar + "!/com/google/example/simple/trans_dep_a/TransDepA$Inner.class")); + + assertThat(aClassFileFinder.findClassFile("com.google.example.simple.trans_dep_b.TransDepB")) + .isEqualTo( + fileSystem.findFile( + binBJar + "!/com/google/example/simple/trans_dep_b/TransDepB.class")); + assertThat( + aClassFileFinder.findClassFile("com.google.example.simple.trans_dep_b.TransDepB$Inner")) + .isEqualTo( + fileSystem.findFile( + binBJar + "!/com/google/example/simple/trans_dep_b/TransDepB$Inner.class")); + + Module cResourceModule = + moduleRegistry.getModuleContainingResourcesOf(getTargetKey("/src_c:src_c")); + RenderJarClassFileFinder cClassFileFinder = new RenderJarClassFileFinder(cResourceModule); + assertThat(cClassFileFinder.findClassFile("com.google.example.simple.trans_dep_c.TransDepC")) + .isEqualTo( + fileSystem.findFile( + binCJar + "!/com/google/example/simple/trans_dep_c/TransDepC.class")); + assertThat( + cClassFileFinder.findClassFile("com.google.example.simple.trans_dep_c.TransDepC$Inner")) + .isEqualTo( + fileSystem.findFile( + binCJar + "!/com/google/example/simple/trans_dep_c/TransDepC$Inner.class")); + } + + @Test + public void failsWhenResolvingResourcesPresentInRenderJars() { + Module workspaceModule = + ModuleManager.getInstance(getProject()).findModuleByName(WORKSPACE_MODULE_NAME); + assertThat(workspaceModule).isNotNull(); + + RenderJarClassFileFinder classFileFinder = new RenderJarClassFileFinder(workspaceModule); + + assertThat(classFileFinder.findClassFile("com.google.example.simple.bin_a.R$color")).isNull(); + assertThat(classFileFinder.findClassFile("com.google.example.simple.src_b.R$attr")).isNull(); + assertThat(classFileFinder.findClassFile("com.google.example.simple.trans_dep_c.R$dimen")) + .isNull(); + } + + /** + * Creates the .workspace module and resource modules, and registers the resource modules in + * {@link AndroidResourceModuleRegistry}. + */ + private void createAndRegisterModules() { + ApplicationManager.getApplication().assertWriteAccessAllowed(); + + AndroidResourceModuleRegistry moduleRegistry = + AndroidResourceModuleRegistry.getInstance(getProject()); + + BlazeImportSettings importSettings = + BlazeImportSettingsManager.getInstance(getProject()).getImportSettings(); + ModuleEditor moduleEditor = + ModuleEditorProvider.getInstance().getModuleEditor(getProject(), importSettings); + + moduleEditor.createModule(WORKSPACE_MODULE_NAME, StdModuleTypes.JAVA); + + AndroidResourceModule resourceModule1 = + AndroidResourceModule.builder(getTargetKey("/src_a:src_a")) + .addSourceTarget(getTargetKey("/src_b:src_b")) + .build(); + + AndroidResourceModule resourceModule2 = + AndroidResourceModule.builder(getTargetKey("/src_c:src_c")).build(); + + Module module1 = + moduleEditor.createModule( + moduleNameForAndroidModule(resourceModule1.targetKey), StdModuleTypes.JAVA); + Module module2 = + moduleEditor.createModule( + moduleNameForAndroidModule(resourceModule2.targetKey), StdModuleTypes.JAVA); + moduleRegistry.put(module1, resourceModule1); + moduleRegistry.put(module2, resourceModule2); + moduleEditor.commit(); + } + + /** + * Creates a target map with the following dependency structure: + * + *

{@code
+   * bin_a -> src_a
+   * bin_b -> src_b
+   * bin_c -> src_c
+   *
+   * src_a -> trans_dep_a
+   * src_b -> trans_dep_b
+   * src_c -> trans_dep_c
+   *
+   * NOTE: x -> y means x is directly dependent on y
+   * }
+ */ + private TargetMap buildTargetMap() { + Label binA = createAndTrackLabel("/bin_a:bin_a"); + Label binB = createAndTrackLabel("/bin_b:bin_b"); + Label binC = createAndTrackLabel("/bin_c:bin_c"); + + Label directDepA = createAndTrackLabel("/src_a:src_a"); + Label directDepB = createAndTrackLabel("/src_b:src_b"); + Label directDepC = createAndTrackLabel("/src_c:src_c"); + + Label transDepA = createAndTrackLabel("/trans_dep_a:trans_dep_a"); + Label transDepB = createAndTrackLabel("/trans_dep_b:trans_dep_b"); + Label transDepC = createAndTrackLabel("/trans_dep_c:trans_dep_c"); + + return TargetMapBuilder.builder() + .addTarget( + mockBinaryTargetIdeInfoBuilder("com/google/example/simple/bin_a.jar") + .setLabel(binA) + .addDependency(directDepA)) + .addTarget( + mockBinaryTargetIdeInfoBuilder("com/google/example/simple/bin_b.jar") + .setLabel(binB) + .addDependency(directDepB)) + .addTarget( + mockBinaryTargetIdeInfoBuilder("com/google/example/simple/bin_c.jar") + .setLabel(binC) + .addDependency(directDepC)) + .addTarget(mockLibraryTargetIdeInfoBuilder().setLabel(directDepA).addDependency(transDepA)) + .addTarget(mockLibraryTargetIdeInfoBuilder().setLabel(directDepB).addDependency(transDepB)) + .addTarget(mockLibraryTargetIdeInfoBuilder().setLabel(directDepC).addDependency(transDepC)) + .addTarget(mockLibraryTargetIdeInfoBuilder().setLabel(transDepA)) + .addTarget(mockLibraryTargetIdeInfoBuilder().setLabel(transDepB)) + .addTarget(mockLibraryTargetIdeInfoBuilder().setLabel(transDepC)) + .build(); + } + + /** + * Creates empty files corresponding to content entries in a JAR. Doesn't create an actual + * archive, only mimics the archive roots in file system. + */ + private void createBinaryJars() throws ArtifactNotFoundException { + File cacheDirFile = RenderJarCache.getInstance(getProject()).getCacheDir(); + fileSystem.createDirectory(cacheDirFile.getAbsolutePath()); + String cacheDir = cacheDirFile.getPath(); + + OutputArtifactWithoutDigest binAArtifact = + (OutputArtifactWithoutDigest) + artifactLocationDecoder.resolveOutput( + getArtifactLocation("com/google/example/simple/bin_a.jar")); + CacheEntry binACacheEntry = CacheEntry.forArtifact(binAArtifact); + String binAJar = cacheDir + "/" + binACacheEntry.getFileName(); + fileSystem.createFile(binAJar); + fileSystem.createFile(binAJar + "!/com/google/example/simple/bin_a/MainActivity.class"); + fileSystem.createFile(binAJar + "!/com/google/example/simple/bin_a/R$color.class"); + fileSystem.createFile(binAJar + "!/com/google/example/simple/src_a/SrcA.class"); + fileSystem.createFile(binAJar + "!/com/google/example/simple/src_a/SrcA$Inner.class"); + fileSystem.createFile(binAJar + "!/com/google/example/simple/src_a/R$attr.class"); + fileSystem.createFile(binAJar + "!/com/google/example/simple/trans_dep_a/TransDepA.class"); + fileSystem.createFile( + binAJar + "!/com/google/example/simple/trans_dep_a/TransDepA$Inner.class"); + fileSystem.createFile(binAJar + "!/com/google/example/simple/trans_dep_a/R$dimen.class"); + artifactCache.addTrackedFile(binAArtifact, binAJar); + + OutputArtifactWithoutDigest binBArtifact = + (OutputArtifactWithoutDigest) + artifactLocationDecoder.resolveOutput( + getArtifactLocation("com/google/example/simple/bin_b.jar")); + CacheEntry binBCacheEntry = CacheEntry.forArtifact(binBArtifact); + String binBJar = cacheDir + "/" + binBCacheEntry.getFileName(); + fileSystem.createFile(binBJar); + fileSystem.createFile(binBJar + "!/com/google/example/simple/bin_b/MainActivity.class"); + fileSystem.createFile(binBJar + "!/com/google/example/simple/bin_b/R$color.class"); + fileSystem.createFile(binBJar + "!/com/google/example/simple/src_b/SrcB.class"); + fileSystem.createFile(binBJar + "!/com/google/example/simple/src_b/SrcB$Inner.class"); + fileSystem.createFile(binBJar + "!/com/google/example/simple/src_b/R$attr.class"); + fileSystem.createFile(binBJar + "!/com/google/example/simple/trans_dep_b/TransDepB.class"); + fileSystem.createFile( + binBJar + "!/com/google/example/simple/trans_dep_b/TransDepB$Inner.class"); + fileSystem.createFile(binBJar + "!/com/google/example/simple/trans_dep_b/R$dimen.class"); + artifactCache.addTrackedFile(binBArtifact, binBJar); + + OutputArtifactWithoutDigest binCArtifact = + (OutputArtifactWithoutDigest) + artifactLocationDecoder.resolveOutput( + getArtifactLocation("com/google/example/simple/bin_c.jar")); + CacheEntry binCCacheEntry = CacheEntry.forArtifact(binCArtifact); + String binCJar = cacheDir + "/" + binCCacheEntry.getFileName(); + fileSystem.createFile(binCJar); + fileSystem.createFile(binCJar + "!/com/google/example/simple/bin_c/MainActivity.class"); + fileSystem.createFile(binCJar + "!/com/google/example/simple/bin_c/R$color.class"); + fileSystem.createFile(binCJar + "!/com/google/example/simple/src_c/SrcC.class"); + fileSystem.createFile(binCJar + "!/com/google/example/simple/src_c/SrcC$Inner.class"); + fileSystem.createFile(binCJar + "!/com/google/example/simple/src_c/R$attr.class"); + fileSystem.createFile(binCJar + "!/com/google/example/simple/trans_dep_c/TransDepC.class"); + fileSystem.createFile( + binCJar + "!/com/google/example/simple/trans_dep_c/TransDepC$Inner.class"); + fileSystem.createFile(binCJar + "!/com/google/example/simple/trans_dep_c/R$dimen.class"); + artifactCache.addTrackedFile(binCArtifact, binCJar); + } + + /** + * Creates a {@link Label} from the given {@code targetName} and caches it in {@link + * #targetNameToLabel}. + */ + private Label createAndTrackLabel(String targetName) { + Label label = Label.create("//com/google/example/simple" + targetName); + targetNameToLabel.put(targetName, label); + return label; + } + + /** + * Returns the {@link TargetKey} of the Label corresponding to {@code targetName}. This method is + * used to ensure the tests don't accidentally create and test a label not in target map. + */ + private TargetKey getTargetKey(String targetName) { + Label label = + Objects.requireNonNull( + targetNameToLabel.get(targetName), + String.format("%s not registered in target map.", targetName)); + return TargetKey.forPlainTarget(label); + } + + private static TargetIdeInfo.Builder mockLibraryTargetIdeInfoBuilder() { + return TargetIdeInfo.builder() + .setKind("android_library") + .setAndroidInfo(AndroidIdeInfo.builder()); + } + + /** + * Returns {@link TargetIdeInfo.Builder} which has its "kind" set to "android_binary", and sets + * AndroidIdeInfo with render JAR field as provided in {@code renderResolveJarRelativePath}. + */ + private static TargetIdeInfo.Builder mockBinaryTargetIdeInfoBuilder( + String renderResolveJarRelativePath) { + return TargetIdeInfo.builder() + .setKind("android_binary") + .setAndroidInfo( + AndroidIdeInfo.builder() + .setRenderResolveJar(getArtifactLocation(renderResolveJarRelativePath))); + } + + private static ArtifactLocation getArtifactLocation(String relativePath) { + return ArtifactLocation.builder() + .setRootExecutionPathFragment(BLAZE_BIN) + .setRelativePath(relativePath) + .build(); + } +} diff --git a/aswb/tests/integrationtests/sdkcompat/as232/com/google/idea/blaze/android/projectsystem/RenderJarClassFileFinderTest.java b/aswb/tests/integrationtests/sdkcompat/as232/com/google/idea/blaze/android/projectsystem/RenderJarClassFileFinderTest.java new file mode 100644 index 00000000000..194c7bc2551 --- /dev/null +++ b/aswb/tests/integrationtests/sdkcompat/as232/com/google/idea/blaze/android/projectsystem/RenderJarClassFileFinderTest.java @@ -0,0 +1,690 @@ +/* + * Copyright 2020 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.projectsystem; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.idea.blaze.android.sync.projectstructure.BlazeAndroidProjectStructureSyncer.moduleNameForAndroidModule; +import static com.google.idea.blaze.base.sync.data.BlazeDataStorage.WORKSPACE_MODULE_NAME; + +import com.google.idea.blaze.android.BlazeAndroidIntegrationTestCase; +import com.google.idea.blaze.android.filecache.ArtifactNotFoundException; +import com.google.idea.blaze.android.filecache.CacheEntry; +import com.google.idea.blaze.android.filecache.MockArtifactCache; +import com.google.idea.blaze.android.libraries.RenderJarCache; +import com.google.idea.blaze.android.sync.aspects.strategy.RenderResolveOutputGroupProvider; +import com.google.idea.blaze.android.sync.model.AndroidResourceModule; +import com.google.idea.blaze.android.sync.model.AndroidResourceModuleRegistry; +import com.google.idea.blaze.base.command.buildresult.LocalFileOutputArtifactWithoutDigest; +import com.google.idea.blaze.base.filecache.FileCache; +import com.google.idea.blaze.base.ideinfo.AndroidIdeInfo; +import com.google.idea.blaze.base.ideinfo.ArtifactLocation; +import com.google.idea.blaze.base.ideinfo.TargetIdeInfo; +import com.google.idea.blaze.base.ideinfo.TargetKey; +import com.google.idea.blaze.base.ideinfo.TargetMap; +import com.google.idea.blaze.base.ideinfo.TargetMapBuilder; +import com.google.idea.blaze.base.io.FileOperationProvider; +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.settings.BlazeImportSettings; +import com.google.idea.blaze.base.settings.BlazeImportSettingsManager; +import com.google.idea.blaze.base.sync.BlazeSyncPlugin.ModuleEditor; +import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; +import com.google.idea.blaze.base.sync.projectstructure.ModuleEditorProvider; +import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder; +import com.google.idea.blaze.base.sync.workspace.MockArtifactLocationDecoder; +import com.google.idea.blaze.common.artifact.OutputArtifactWithoutDigest; +import com.google.idea.common.experiments.ExperimentService; +import com.google.idea.common.experiments.MockExperimentService; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleManager; +import com.intellij.openapi.module.StdModuleTypes; +import java.io.File; +import java.util.HashMap; +import java.util.Objects; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link RenderJarClassFileFinder} */ +@RunWith(JUnit4.class) +public class RenderJarClassFileFinderTest extends BlazeAndroidIntegrationTestCase { + + private static final String BLAZE_BIN = "blaze-out/crosstool/bin"; + + // Utility map to quickly access Labels in target map. Populated by `buildTargetMap`, and used to + // ensure the tests don't accidentally access targets not set up in the target map + private final HashMap targetNameToLabel = new HashMap<>(); + + private ArtifactLocationDecoder artifactLocationDecoder; + private MockArtifactCache artifactCache; + + @Before + public void initTest() throws ArtifactNotFoundException { + TargetMap targetMap = buildTargetMap(); + setTargetMap(targetMap); + + // Since this is not a light test, the ArtifactLocationDecoder points to the actual file in the + // File System + artifactLocationDecoder = + new MockArtifactLocationDecoder() { + @Override + public File decode(ArtifactLocation artifactLocation) { + File f = + new File(fileSystem.getRootDir(), artifactLocation.getExecutionRootRelativePath()); + + // Create the artifact if it does not exist + // This allows us to set fileModifiedTime for the file which is used by RenderJarCache + FileOperationProvider.getInstance().mkdirs(f); + + // Set last modified time for RenderJarCache to use + FileOperationProvider.getInstance().setFileModifiedTime(f, 1000L); + return f; + } + }; + + registerProjectService( + BlazeProjectDataManager.class, + new MockBlazeProjectDataManager( + MockBlazeProjectDataBuilder.builder(workspaceRoot) + .setTargetMap(targetMap) + .setArtifactLocationDecoder(artifactLocationDecoder) + .build())); + + setProjectView( + "targets:", + " //com/google/example/simple/bin_a:bin_a", + " //com/google/example/simple/bin_b:bin_b", + " //com/google/example/simple/bin_c:bin_c"); + + MockExperimentService experimentService = new MockExperimentService(); + registerApplicationComponent(ExperimentService.class, experimentService); + experimentService.setExperiment(RenderResolveOutputGroupProvider.buildOnSync, true); + // Disable resource resolution from Render Jars + experimentService.setExperiment(RenderJarClassFileFinder.resolveResourceClasses, false); + + ApplicationManager.getApplication().runWriteAction(this::createAndRegisterModules); + + artifactCache = new MockArtifactCache(); + registerExtension(FileCache.EP_NAME, new RenderJarCache.FileCacheAdapter()); + registerProjectService( + RenderJarCache.class, + new RenderJarCache( + getProject(), RenderJarCache.getCacheDirForProject(getProject()), artifactCache)); + + createBinaryJars(); + FileCache.EP_NAME.extensions().forEach(ep -> ep.initialize(getProject())); + } + + /** Tests that .workspace module can find classes from all binaries in the projectview. */ + @Test + public void workspaceModule_canFindAllClassesInAllBinaries() throws Exception { + Module workspaceModule = + ModuleManager.getInstance(getProject()).findModuleByName(WORKSPACE_MODULE_NAME); + assertThat(workspaceModule).isNotNull(); + + RenderJarClassFileFinder classFileFinder = new RenderJarClassFileFinder(workspaceModule); + + File cacheDir = RenderJarCache.getInstance(getProject()).getCacheDir(); + + CacheEntry binACacheEntry = + CacheEntry.forArtifact( + (LocalFileOutputArtifactWithoutDigest) + artifactLocationDecoder.resolveOutput( + getArtifactLocation("com/google/example/simple/bin_a.jar"))); + String binAJar = cacheDir.getAbsoluteFile() + "/" + binACacheEntry.getFileName(); + + CacheEntry binBCacheEntry = + CacheEntry.forArtifact( + (LocalFileOutputArtifactWithoutDigest) + artifactLocationDecoder.resolveOutput( + getArtifactLocation("com/google/example/simple/bin_b.jar"))); + String binBJar = cacheDir.getAbsoluteFile() + "/" + binBCacheEntry.getFileName(); + + CacheEntry binCCacheEntry = + CacheEntry.forArtifact( + (LocalFileOutputArtifactWithoutDigest) + artifactLocationDecoder.resolveOutput( + getArtifactLocation("com/google/example/simple/bin_c.jar"))); + String binCJar = cacheDir.getAbsoluteFile() + "/" + binCCacheEntry.getFileName(); + + assertThat( + Objects.requireNonNull( + classFileFinder.findClassFile("com.google.example.simple.src_a.SrcA")) + .getContent()) + .isEqualTo( + Objects.requireNonNull( + fileSystem.findFile(binAJar + "!/com/google/example/simple/src_a/SrcA.class")) + .contentsToByteArray()); + assertThat( + Objects.requireNonNull( + classFileFinder.findClassFile("com.google.example.simple.src_a.SrcA$Inner")) + .getContent()) + .isEqualTo( + Objects.requireNonNull( + fileSystem.findFile( + binAJar + "!/com/google/example/simple/src_a/SrcA$Inner.class")) + .contentsToByteArray()); + + assertThat( + Objects.requireNonNull( + classFileFinder.findClassFile("com.google.example.simple.src_b.SrcB")) + .getContent()) + .isEqualTo( + Objects.requireNonNull( + fileSystem.findFile(binBJar + "!/com/google/example/simple/src_b/SrcB.class")) + .contentsToByteArray()); + assertThat( + Objects.requireNonNull( + classFileFinder.findClassFile("com.google.example.simple.src_b.SrcB$Inner")) + .getContent()) + .isEqualTo( + Objects.requireNonNull( + fileSystem.findFile( + binBJar + "!/com/google/example/simple/src_b/SrcB$Inner.class")) + .contentsToByteArray()); + + assertThat( + Objects.requireNonNull( + classFileFinder.findClassFile("com.google.example.simple.src_c.SrcC")) + .getContent()) + .isEqualTo( + Objects.requireNonNull( + fileSystem.findFile(binCJar + "!/com/google/example/simple/src_c/SrcC.class")) + .contentsToByteArray()); + assertThat( + Objects.requireNonNull( + classFileFinder.findClassFile("com.google.example.simple.src_c.SrcC$Inner")) + .getContent()) + .isEqualTo( + Objects.requireNonNull( + fileSystem.findFile( + binCJar + "!/com/google/example/simple/src_c/SrcC$Inner.class")) + .contentsToByteArray()); + + assertThat( + Objects.requireNonNull( + classFileFinder.findClassFile( + "com.google.example.simple.trans_dep_a.TransDepA")) + .getContent()) + .isEqualTo( + Objects.requireNonNull( + fileSystem.findFile( + binAJar + "!/com/google/example/simple/trans_dep_a/TransDepA.class")) + .contentsToByteArray()); + assertThat( + Objects.requireNonNull( + classFileFinder.findClassFile( + "com.google.example.simple.trans_dep_a.TransDepA$Inner")) + .getContent()) + .isEqualTo( + Objects.requireNonNull( + fileSystem.findFile( + binAJar + "!/com/google/example/simple/trans_dep_a/TransDepA$Inner.class")) + .contentsToByteArray()); + + assertThat( + Objects.requireNonNull( + classFileFinder.findClassFile( + "com.google.example.simple.trans_dep_b.TransDepB")) + .getContent()) + .isEqualTo( + Objects.requireNonNull( + fileSystem.findFile( + binBJar + "!/com/google/example/simple/trans_dep_b/TransDepB.class")) + .contentsToByteArray()); + assertThat( + Objects.requireNonNull( + classFileFinder.findClassFile( + "com.google.example.simple.trans_dep_b.TransDepB$Inner")) + .getContent()) + .isEqualTo( + Objects.requireNonNull( + fileSystem.findFile( + binBJar + "!/com/google/example/simple/trans_dep_b/TransDepB$Inner.class")) + .contentsToByteArray()); + + assertThat( + Objects.requireNonNull( + classFileFinder.findClassFile( + "com.google.example.simple.trans_dep_c.TransDepC")) + .getContent()) + .isEqualTo( + Objects.requireNonNull( + fileSystem.findFile( + binCJar + "!/com/google/example/simple/trans_dep_c/TransDepC.class")) + .contentsToByteArray()); + assertThat( + Objects.requireNonNull( + classFileFinder.findClassFile( + "com.google.example.simple.trans_dep_c.TransDepC$Inner")) + .getContent()) + .isEqualTo( + Objects.requireNonNull( + fileSystem.findFile( + binCJar + "!/com/google/example/simple/trans_dep_c/TransDepC$Inner.class")) + .contentsToByteArray()); + } + + /** + * Tests that resource modules can correctly find classes corresponding to sources of the targets + * that comprise the resource module. + */ + @Test + public void resourceModule_canFindSourceClasses() throws Exception { + AndroidResourceModuleRegistry moduleRegistry = + AndroidResourceModuleRegistry.getInstance(getProject()); + Module aResourceModule = + moduleRegistry.getModuleContainingResourcesOf(getTargetKey("/src_a:src_a")); + RenderJarClassFileFinder aClassFileFinder = new RenderJarClassFileFinder(aResourceModule); + + File cacheDir = RenderJarCache.getInstance(getProject()).getCacheDir(); + + CacheEntry binACacheEntry = + CacheEntry.forArtifact( + (LocalFileOutputArtifactWithoutDigest) + artifactLocationDecoder.resolveOutput( + getArtifactLocation("com/google/example/simple/bin_a.jar"))); + String binAJar = cacheDir.getAbsoluteFile() + "/" + binACacheEntry.getFileName(); + + CacheEntry binBCacheEntry = + CacheEntry.forArtifact( + (LocalFileOutputArtifactWithoutDigest) + artifactLocationDecoder.resolveOutput( + getArtifactLocation("com/google/example/simple/bin_b.jar"))); + String binBJar = cacheDir.getAbsoluteFile() + "/" + binBCacheEntry.getFileName(); + + CacheEntry binCCacheEntry = + CacheEntry.forArtifact( + (LocalFileOutputArtifactWithoutDigest) + artifactLocationDecoder.resolveOutput( + getArtifactLocation("com/google/example/simple/bin_c.jar"))); + String binCJar = cacheDir.getAbsoluteFile() + "/" + binCCacheEntry.getFileName(); + + assertThat( + Objects.requireNonNull( + aClassFileFinder.findClassFile("com.google.example.simple.src_a.SrcA")) + .getContent()) + .isEqualTo( + Objects.requireNonNull( + fileSystem.findFile(binAJar + "!/com/google/example/simple/src_a/SrcA.class")) + .contentsToByteArray()); + assertThat( + Objects.requireNonNull( + aClassFileFinder.findClassFile("com.google.example.simple.src_a.SrcA$Inner")) + .getContent()) + .isEqualTo( + Objects.requireNonNull( + fileSystem.findFile( + binAJar + "!/com/google/example/simple/src_a/SrcA$Inner.class")) + .contentsToByteArray()); + + assertThat( + Objects.requireNonNull( + aClassFileFinder.findClassFile("com.google.example.simple.src_b.SrcB")) + .getContent()) + .isEqualTo( + Objects.requireNonNull( + fileSystem.findFile(binBJar + "!/com/google/example/simple/src_b/SrcB.class")) + .contentsToByteArray()); + assertThat( + Objects.requireNonNull( + aClassFileFinder.findClassFile("com.google.example.simple.src_b.SrcB$Inner")) + .getContent()) + .isEqualTo( + Objects.requireNonNull( + fileSystem.findFile( + binBJar + "!/com/google/example/simple/src_b/SrcB$Inner.class")) + .contentsToByteArray()); + + Module cResourceModule = + moduleRegistry.getModuleContainingResourcesOf(getTargetKey("/src_c:src_c")); + RenderJarClassFileFinder cClassFileFinder = new RenderJarClassFileFinder(cResourceModule); + assertThat( + Objects.requireNonNull( + cClassFileFinder.findClassFile("com.google.example.simple.src_c.SrcC")) + .getContent()) + .isEqualTo( + Objects.requireNonNull( + fileSystem.findFile(binCJar + "!/com/google/example/simple/src_c/SrcC.class")) + .contentsToByteArray()); + assertThat( + Objects.requireNonNull( + cClassFileFinder.findClassFile("com.google.example.simple.src_c.SrcC$Inner")) + .getContent()) + .isEqualTo( + Objects.requireNonNull( + fileSystem.findFile( + binCJar + "!/com/google/example/simple/src_c/SrcC$Inner.class")) + .contentsToByteArray()); + } + + /** + * Tests that resource modules can find classes from dependencies of source targets that comprise + * the resource module. + */ + @Test + public void resourceModule_canFindDependencyClasses() throws Exception { + AndroidResourceModuleRegistry moduleRegistry = + AndroidResourceModuleRegistry.getInstance(getProject()); + Module aResourceModule = + moduleRegistry.getModuleContainingResourcesOf(getTargetKey("/src_a:src_a")); + RenderJarClassFileFinder aClassFileFinder = new RenderJarClassFileFinder(aResourceModule); + + File cacheDir = RenderJarCache.getInstance(getProject()).getCacheDir(); + + CacheEntry binACacheEntry = + CacheEntry.forArtifact( + (LocalFileOutputArtifactWithoutDigest) + artifactLocationDecoder.resolveOutput( + getArtifactLocation("com/google/example/simple/bin_a.jar"))); + String binAJar = cacheDir.getAbsoluteFile() + "/" + binACacheEntry.getFileName(); + + CacheEntry binBCacheEntry = + CacheEntry.forArtifact( + (LocalFileOutputArtifactWithoutDigest) + artifactLocationDecoder.resolveOutput( + getArtifactLocation("com/google/example/simple/bin_b.jar"))); + String binBJar = cacheDir.getAbsoluteFile() + "/" + binBCacheEntry.getFileName(); + + CacheEntry binCCacheEntry = + CacheEntry.forArtifact( + (LocalFileOutputArtifactWithoutDigest) + artifactLocationDecoder.resolveOutput( + getArtifactLocation("com/google/example/simple/bin_c.jar"))); + String binCJar = cacheDir.getAbsoluteFile() + "/" + binCCacheEntry.getFileName(); + + assertThat( + Objects.requireNonNull( + aClassFileFinder.findClassFile( + "com.google.example.simple.trans_dep_a.TransDepA")) + .getContent()) + .isEqualTo( + Objects.requireNonNull( + fileSystem.findFile( + binAJar + "!/com/google/example/simple/trans_dep_a/TransDepA.class")) + .contentsToByteArray()); + assertThat( + Objects.requireNonNull( + aClassFileFinder.findClassFile( + "com.google.example.simple.trans_dep_a.TransDepA$Inner")) + .getContent()) + .isEqualTo( + Objects.requireNonNull( + fileSystem.findFile( + binAJar + "!/com/google/example/simple/trans_dep_a/TransDepA$Inner.class")) + .contentsToByteArray()); + + assertThat( + Objects.requireNonNull( + aClassFileFinder.findClassFile( + "com.google.example.simple.trans_dep_b.TransDepB")) + .getContent()) + .isEqualTo( + Objects.requireNonNull( + fileSystem.findFile( + binBJar + "!/com/google/example/simple/trans_dep_b/TransDepB.class")) + .contentsToByteArray()); + assertThat( + Objects.requireNonNull( + aClassFileFinder.findClassFile( + "com.google.example.simple.trans_dep_b.TransDepB$Inner")) + .getContent()) + .isEqualTo( + Objects.requireNonNull( + fileSystem.findFile( + binBJar + "!/com/google/example/simple/trans_dep_b/TransDepB$Inner.class")) + .contentsToByteArray()); + + Module cResourceModule = + moduleRegistry.getModuleContainingResourcesOf(getTargetKey("/src_c:src_c")); + RenderJarClassFileFinder cClassFileFinder = new RenderJarClassFileFinder(cResourceModule); + assertThat( + Objects.requireNonNull( + cClassFileFinder.findClassFile( + "com.google.example.simple.trans_dep_c.TransDepC")) + .getContent()) + .isEqualTo( + Objects.requireNonNull( + fileSystem.findFile( + binCJar + "!/com/google/example/simple/trans_dep_c/TransDepC.class")) + .contentsToByteArray()); + assertThat( + Objects.requireNonNull( + cClassFileFinder.findClassFile( + "com.google.example.simple.trans_dep_c.TransDepC$Inner")) + .getContent()) + .isEqualTo( + Objects.requireNonNull( + fileSystem.findFile( + binCJar + "!/com/google/example/simple/trans_dep_c/TransDepC$Inner.class")) + .contentsToByteArray()); + } + + @Test + public void failsWhenResolvingResourcesPresentInRenderJars() { + Module workspaceModule = + ModuleManager.getInstance(getProject()).findModuleByName(WORKSPACE_MODULE_NAME); + assertThat(workspaceModule).isNotNull(); + + RenderJarClassFileFinder classFileFinder = new RenderJarClassFileFinder(workspaceModule); + + assertThat(classFileFinder.findClassFile("com.google.example.simple.bin_a.R$color")).isNull(); + assertThat(classFileFinder.findClassFile("com.google.example.simple.src_b.R$attr")).isNull(); + assertThat(classFileFinder.findClassFile("com.google.example.simple.trans_dep_c.R$dimen")) + .isNull(); + } + + /** + * Creates the .workspace module and resource modules, and registers the resource modules in + * {@link AndroidResourceModuleRegistry}. + */ + private void createAndRegisterModules() { + ApplicationManager.getApplication().assertWriteAccessAllowed(); + + AndroidResourceModuleRegistry moduleRegistry = + AndroidResourceModuleRegistry.getInstance(getProject()); + + BlazeImportSettings importSettings = + BlazeImportSettingsManager.getInstance(getProject()).getImportSettings(); + ModuleEditor moduleEditor = + ModuleEditorProvider.getInstance().getModuleEditor(getProject(), importSettings); + + moduleEditor.createModule(WORKSPACE_MODULE_NAME, StdModuleTypes.JAVA); + + AndroidResourceModule resourceModule1 = + AndroidResourceModule.builder(getTargetKey("/src_a:src_a")) + .addSourceTarget(getTargetKey("/src_b:src_b")) + .build(); + + AndroidResourceModule resourceModule2 = + AndroidResourceModule.builder(getTargetKey("/src_c:src_c")).build(); + + Module module1 = + moduleEditor.createModule( + moduleNameForAndroidModule(resourceModule1.targetKey), StdModuleTypes.JAVA); + Module module2 = + moduleEditor.createModule( + moduleNameForAndroidModule(resourceModule2.targetKey), StdModuleTypes.JAVA); + moduleRegistry.put(module1, resourceModule1); + moduleRegistry.put(module2, resourceModule2); + moduleEditor.commit(); + } + + /** + * Creates a target map with the following dependency structure: + * + *
{@code
+   * bin_a -> src_a
+   * bin_b -> src_b
+   * bin_c -> src_c
+   *
+   * src_a -> trans_dep_a
+   * src_b -> trans_dep_b
+   * src_c -> trans_dep_c
+   *
+   * NOTE: x -> y means x is directly dependent on y
+   * }
+ */ + private TargetMap buildTargetMap() { + Label binA = createAndTrackLabel("/bin_a:bin_a"); + Label binB = createAndTrackLabel("/bin_b:bin_b"); + Label binC = createAndTrackLabel("/bin_c:bin_c"); + + Label directDepA = createAndTrackLabel("/src_a:src_a"); + Label directDepB = createAndTrackLabel("/src_b:src_b"); + Label directDepC = createAndTrackLabel("/src_c:src_c"); + + Label transDepA = createAndTrackLabel("/trans_dep_a:trans_dep_a"); + Label transDepB = createAndTrackLabel("/trans_dep_b:trans_dep_b"); + Label transDepC = createAndTrackLabel("/trans_dep_c:trans_dep_c"); + + return TargetMapBuilder.builder() + .addTarget( + mockBinaryTargetIdeInfoBuilder("com/google/example/simple/bin_a.jar") + .setLabel(binA) + .addDependency(directDepA)) + .addTarget( + mockBinaryTargetIdeInfoBuilder("com/google/example/simple/bin_b.jar") + .setLabel(binB) + .addDependency(directDepB)) + .addTarget( + mockBinaryTargetIdeInfoBuilder("com/google/example/simple/bin_c.jar") + .setLabel(binC) + .addDependency(directDepC)) + .addTarget(mockLibraryTargetIdeInfoBuilder().setLabel(directDepA).addDependency(transDepA)) + .addTarget(mockLibraryTargetIdeInfoBuilder().setLabel(directDepB).addDependency(transDepB)) + .addTarget(mockLibraryTargetIdeInfoBuilder().setLabel(directDepC).addDependency(transDepC)) + .addTarget(mockLibraryTargetIdeInfoBuilder().setLabel(transDepA)) + .addTarget(mockLibraryTargetIdeInfoBuilder().setLabel(transDepB)) + .addTarget(mockLibraryTargetIdeInfoBuilder().setLabel(transDepC)) + .build(); + } + + /** + * Creates empty files corresponding to content entries in a JAR. Doesn't create an actual + * archive, only mimics the archive roots in file system. + */ + private void createBinaryJars() throws ArtifactNotFoundException { + File cacheDirFile = RenderJarCache.getInstance(getProject()).getCacheDir(); + fileSystem.createDirectory(cacheDirFile.getAbsolutePath()); + String cacheDir = cacheDirFile.getPath(); + + OutputArtifactWithoutDigest binAArtifact = + (OutputArtifactWithoutDigest) + artifactLocationDecoder.resolveOutput( + getArtifactLocation("com/google/example/simple/bin_a.jar")); + CacheEntry binACacheEntry = CacheEntry.forArtifact(binAArtifact); + String binAJar = cacheDir + "/" + binACacheEntry.getFileName(); + fileSystem.createFile(binAJar); + fileSystem.createFile(binAJar + "!/com/google/example/simple/bin_a/MainActivity.class"); + fileSystem.createFile(binAJar + "!/com/google/example/simple/bin_a/R$color.class"); + fileSystem.createFile(binAJar + "!/com/google/example/simple/src_a/SrcA.class"); + fileSystem.createFile(binAJar + "!/com/google/example/simple/src_a/SrcA$Inner.class"); + fileSystem.createFile(binAJar + "!/com/google/example/simple/src_a/R$attr.class"); + fileSystem.createFile(binAJar + "!/com/google/example/simple/trans_dep_a/TransDepA.class"); + fileSystem.createFile( + binAJar + "!/com/google/example/simple/trans_dep_a/TransDepA$Inner.class"); + fileSystem.createFile(binAJar + "!/com/google/example/simple/trans_dep_a/R$dimen.class"); + artifactCache.addTrackedFile(binAArtifact, binAJar); + + OutputArtifactWithoutDigest binBArtifact = + (OutputArtifactWithoutDigest) + artifactLocationDecoder.resolveOutput( + getArtifactLocation("com/google/example/simple/bin_b.jar")); + CacheEntry binBCacheEntry = CacheEntry.forArtifact(binBArtifact); + String binBJar = cacheDir + "/" + binBCacheEntry.getFileName(); + fileSystem.createFile(binBJar); + fileSystem.createFile(binBJar + "!/com/google/example/simple/bin_b/MainActivity.class"); + fileSystem.createFile(binBJar + "!/com/google/example/simple/bin_b/R$color.class"); + fileSystem.createFile(binBJar + "!/com/google/example/simple/src_b/SrcB.class"); + fileSystem.createFile(binBJar + "!/com/google/example/simple/src_b/SrcB$Inner.class"); + fileSystem.createFile(binBJar + "!/com/google/example/simple/src_b/R$attr.class"); + fileSystem.createFile(binBJar + "!/com/google/example/simple/trans_dep_b/TransDepB.class"); + fileSystem.createFile( + binBJar + "!/com/google/example/simple/trans_dep_b/TransDepB$Inner.class"); + fileSystem.createFile(binBJar + "!/com/google/example/simple/trans_dep_b/R$dimen.class"); + artifactCache.addTrackedFile(binBArtifact, binBJar); + + OutputArtifactWithoutDigest binCArtifact = + (OutputArtifactWithoutDigest) + artifactLocationDecoder.resolveOutput( + getArtifactLocation("com/google/example/simple/bin_c.jar")); + CacheEntry binCCacheEntry = CacheEntry.forArtifact(binCArtifact); + String binCJar = cacheDir + "/" + binCCacheEntry.getFileName(); + fileSystem.createFile(binCJar); + fileSystem.createFile(binCJar + "!/com/google/example/simple/bin_c/MainActivity.class"); + fileSystem.createFile(binCJar + "!/com/google/example/simple/bin_c/R$color.class"); + fileSystem.createFile(binCJar + "!/com/google/example/simple/src_c/SrcC.class"); + fileSystem.createFile(binCJar + "!/com/google/example/simple/src_c/SrcC$Inner.class"); + fileSystem.createFile(binCJar + "!/com/google/example/simple/src_c/R$attr.class"); + fileSystem.createFile(binCJar + "!/com/google/example/simple/trans_dep_c/TransDepC.class"); + fileSystem.createFile( + binCJar + "!/com/google/example/simple/trans_dep_c/TransDepC$Inner.class"); + fileSystem.createFile(binCJar + "!/com/google/example/simple/trans_dep_c/R$dimen.class"); + artifactCache.addTrackedFile(binCArtifact, binCJar); + } + + /** + * Creates a {@link Label} from the given {@code targetName} and caches it in {@link + * #targetNameToLabel}. + */ + private Label createAndTrackLabel(String targetName) { + Label label = Label.create("//com/google/example/simple" + targetName); + targetNameToLabel.put(targetName, label); + return label; + } + + /** + * Returns the {@link TargetKey} of the Label corresponding to {@code targetName}. This method is + * used to ensure the tests don't accidentally create and test a label not in target map. + */ + private TargetKey getTargetKey(String targetName) { + Label label = + Objects.requireNonNull( + targetNameToLabel.get(targetName), + String.format("%s not registered in target map.", targetName)); + return TargetKey.forPlainTarget(label); + } + + private static TargetIdeInfo.Builder mockLibraryTargetIdeInfoBuilder() { + return TargetIdeInfo.builder() + .setKind("android_library") + .setAndroidInfo(AndroidIdeInfo.builder()); + } + + /** + * Returns {@link TargetIdeInfo.Builder} which has its "kind" set to "android_binary", and sets + * AndroidIdeInfo with render JAR field as provided in {@code renderResolveJarRelativePath}. + */ + private static TargetIdeInfo.Builder mockBinaryTargetIdeInfoBuilder( + String renderResolveJarRelativePath) { + return TargetIdeInfo.builder() + .setKind("android_binary") + .setAndroidInfo( + AndroidIdeInfo.builder() + .setRenderResolveJar(getArtifactLocation(renderResolveJarRelativePath))); + } + + private static ArtifactLocation getArtifactLocation(String relativePath) { + return ArtifactLocation.builder() + .setRootExecutionPathFragment(BLAZE_BIN) + .setRelativePath(relativePath) + .build(); + } +} diff --git a/aswb/tests/unittests/com/google/idea/blaze/android/filecache/LocalArtifactCacheTest.java b/aswb/tests/unittests/com/google/idea/blaze/android/filecache/LocalArtifactCacheTest.java index f2b6e91b652..f79ddd35f54 100644 --- a/aswb/tests/unittests/com/google/idea/blaze/android/filecache/LocalArtifactCacheTest.java +++ b/aswb/tests/unittests/com/google/idea/blaze/android/filecache/LocalArtifactCacheTest.java @@ -19,8 +19,7 @@ import static com.google.idea.blaze.android.filecache.LocalArtifactCache.CACHE_DATA_FILENAME; import com.google.common.collect.ImmutableList; -import com.google.idea.blaze.base.command.buildresult.LocalFileOutputArtifact; -import com.google.idea.blaze.base.command.buildresult.OutputArtifact; +import com.google.idea.blaze.base.command.buildresult.LocalFileOutputArtifactWithoutDigest; import com.google.idea.blaze.base.ideinfo.ArtifactLocation; import com.google.idea.blaze.base.io.FileOperationProvider; import com.google.idea.blaze.base.model.MockBlazeProjectDataBuilder; @@ -32,6 +31,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.sync.workspace.MockArtifactLocationDecoder; +import com.google.idea.blaze.common.artifact.OutputArtifactWithoutDigest; import com.google.idea.testing.IntellijRule; import java.io.File; import java.io.IOException; @@ -110,24 +110,24 @@ public void noStateFile_existingArtifacts_initializesWithAConsistentStateFile() .forEach(f -> assertThat(f.exists()).isTrue()); } - private LocalFileOutputArtifact newLocalOutputArtifact(String path) { + private LocalFileOutputArtifactWithoutDigest newLocalOutputArtifact(String path) { String execRoot = workspaceRoot.directory().getAbsolutePath(); String mnemonic = "k8-opt"; - return new LocalFileOutputArtifact( + return new LocalFileOutputArtifactWithoutDigest( new File(execRoot + "/blaze-out" + mnemonic + "/" + path), mnemonic + "/" + path, mnemonic); } @Test public void put_addsArtifactInDirectory() throws IOException { // Create blaze artifacts in FS - ImmutableList outputArtifacts = + ImmutableList outputArtifacts = ImmutableList.of( newLocalOutputArtifact("relative/path_1/artifact_1.jar"), newLocalOutputArtifact("relative/path_2/artifact_2.jar"), newLocalOutputArtifact("relative/path_3/artifact_3.jar")); - for (OutputArtifact a : outputArtifacts) { - File file = ((LocalFileOutputArtifact) a).getFile(); + for (OutputArtifactWithoutDigest a : outputArtifacts) { + File file = ((LocalFileOutputArtifactWithoutDigest) a).getFile(); assertThat(Paths.get(file.getParent()).toFile().mkdirs()).isTrue(); assertThat(file.createNewFile()).isTrue(); } @@ -161,13 +161,13 @@ public void put_addsArtifactInDirectory() throws IOException { @Test public void get_fetchesCorrectFileForArtifact() throws IOException { // Create blaze artifacts in FS - ImmutableList outputArtifacts = + ImmutableList outputArtifacts = ImmutableList.of( newLocalOutputArtifact("relative/path_1/artifact_1.jar"), newLocalOutputArtifact("relative/path_2/artifact_2.jar"), newLocalOutputArtifact("relative/path_3/artifact_3.jar")); - for (OutputArtifact a : outputArtifacts) { - File file = ((LocalFileOutputArtifact) a).getFile(); + for (OutputArtifactWithoutDigest a : outputArtifacts) { + File file = ((LocalFileOutputArtifactWithoutDigest) a).getFile(); assertThat(Paths.get(file.getParent()).toFile().mkdirs()).isTrue(); assertThat(file.createNewFile()).isTrue(); } @@ -177,7 +177,7 @@ public void get_fetchesCorrectFileForArtifact() throws IOException { artifactCache.putAll(outputArtifacts, blazeContext, false); // Attempt to get an arbitraty artifact - OutputArtifact artifactToFetch = outputArtifacts.get(1); + OutputArtifactWithoutDigest artifactToFetch = outputArtifacts.get(1); // Check that the returned file matches the expected file File expectedFile = diff --git a/aswb/tests/unittests/com/google/idea/blaze/android/libraries/RenderJarCacheTest.java b/aswb/tests/unittests/com/google/idea/blaze/android/libraries/RenderJarCacheTest.java index 9566b0946ae..77a676c5e38 100644 --- a/aswb/tests/unittests/com/google/idea/blaze/android/libraries/RenderJarCacheTest.java +++ b/aswb/tests/unittests/com/google/idea/blaze/android/libraries/RenderJarCacheTest.java @@ -26,9 +26,7 @@ import com.google.idea.blaze.base.MockProjectViewManager; import com.google.idea.blaze.base.async.executor.BlazeExecutor; import com.google.idea.blaze.base.async.executor.MockBlazeExecutor; -import com.google.idea.blaze.base.command.buildresult.BlazeArtifact; -import com.google.idea.blaze.base.command.buildresult.LocalFileOutputArtifact; -import com.google.idea.blaze.base.command.buildresult.OutputArtifact; +import com.google.idea.blaze.base.command.buildresult.LocalFileOutputArtifactWithoutDigest; import com.google.idea.blaze.base.filecache.FileCache; import com.google.idea.blaze.base.ideinfo.AndroidIdeInfo; import com.google.idea.blaze.base.ideinfo.ArtifactLocation; @@ -46,12 +44,13 @@ import com.google.idea.blaze.base.projectview.ProjectViewManager; import com.google.idea.blaze.base.projectview.ProjectViewSet; import com.google.idea.blaze.base.projectview.parser.ProjectViewParser; +import com.google.idea.blaze.base.qsync.settings.QuerySyncSettings; import com.google.idea.blaze.base.scope.BlazeContext; import com.google.idea.blaze.base.scope.ErrorCollector; import com.google.idea.blaze.base.scope.OutputSink; import com.google.idea.blaze.base.scope.output.IssueOutput; -import com.google.idea.blaze.base.scope.output.PrintOutput; import com.google.idea.blaze.base.settings.BlazeImportSettings; +import com.google.idea.blaze.base.settings.BlazeImportSettings.ProjectType; import com.google.idea.blaze.base.settings.BlazeImportSettingsManager; import com.google.idea.blaze.base.settings.BuildSystemName; import com.google.idea.blaze.base.sync.BlazeSyncPlugin; @@ -60,6 +59,9 @@ import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder; import com.google.idea.blaze.base.sync.workspace.MockArtifactLocationDecoder; import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolverImpl; +import com.google.idea.blaze.common.PrintOutput; +import com.google.idea.blaze.common.artifact.BlazeArtifact; +import com.google.idea.blaze.common.artifact.OutputArtifactWithoutDigest; import com.google.idea.blaze.java.AndroidBlazeRules; import com.google.idea.blaze.java.AndroidBlazeRules.RuleTypes; import com.google.idea.blaze.java.sync.BlazeJavaSyncPlugin; @@ -143,6 +145,7 @@ public File decode(ArtifactLocation artifactLocation) { intellijRule.registerExtensionPoint(Kind.Provider.EP_NAME, Kind.Provider.class); intellijRule.registerExtension(Kind.Provider.EP_NAME, new AndroidBlazeRules()); intellijRule.registerApplicationService(ApplicationState.class, new ApplicationState()); + intellijRule.registerApplicationService(QuerySyncSettings.class, new QuerySyncSettings()); // registered because `RenderJarCache` uses it to filter source targets projectViewManager = new MockProjectViewManager(); @@ -177,7 +180,7 @@ public void onSync_passesCorrectJarsToArtifactCache() { SyncMode.INCREMENTAL)); @SuppressWarnings("unchecked") // irrelevant unchecked conversion warning for artifactsCaptor - ArgumentCaptor> artifactsCaptor = + ArgumentCaptor> artifactsCaptor = ArgumentCaptor.forClass(Collection.class); ArgumentCaptor contextCaptor = ArgumentCaptor.forClass(BlazeContext.class); @@ -186,8 +189,8 @@ public void onSync_passesCorrectJarsToArtifactCache() { verify(mockedArtifactCache, Mockito.times(1)) .putAll(artifactsCaptor.capture(), contextCaptor.capture(), removeCaptor.capture()); - Collection passedArtifact = artifactsCaptor.getValue(); - assertThat(passedArtifact.stream().map(Object::toString)) + Collection passedArtifact = artifactsCaptor.getValue(); + assertThat(passedArtifact.stream().map(OutputArtifactWithoutDigest::getRelativePath)) .containsExactly( "com/foo/bar/baz/baz_render_jar.jar", "com/foo/bar/qux/qux_render_jar.jar"); } @@ -229,7 +232,7 @@ private void setupProjectData() throws IOException { BlazeArtifact bazRenderJar = artifactLocationDecoder.resolveOutput( getArtifactLocation("com/foo/bar/baz/baz_render_jar.jar")); - File bazRenderJarFile = ((LocalFileOutputArtifact) bazRenderJar).getFile(); + File bazRenderJarFile = ((LocalFileOutputArtifactWithoutDigest) bazRenderJar).getFile(); assertThat(Paths.get(bazRenderJarFile.getParent()).toFile().mkdirs()).isTrue(); assertThat(bazRenderJarFile.createNewFile()).isTrue(); assertThat(bazRenderJarFile.setLastModified(100000L)).isTrue(); @@ -238,7 +241,7 @@ private void setupProjectData() throws IOException { BlazeArtifact quxRenderJar = artifactLocationDecoder.resolveOutput( getArtifactLocation("com/foo/bar/qux/qux_render_jar.jar")); - File quxRenderJarFile = ((LocalFileOutputArtifact) quxRenderJar).getFile(); + File quxRenderJarFile = ((LocalFileOutputArtifactWithoutDigest) quxRenderJar).getFile(); assertThat(Paths.get(quxRenderJarFile.getParent()).toFile().mkdirs()).isTrue(); assertThat(quxRenderJarFile.createNewFile()).isTrue(); assertThat(quxRenderJarFile.setLastModified(100000L)).isTrue(); @@ -251,11 +254,12 @@ private void registerMockBlazeImportSettings() throws IOException { File projectDataDir = temporaryFolder.newFolder("project_data"); importSettingsManager.setImportSettings( new BlazeImportSettings( - /*workspaceRoot=*/ "", + /* workspaceRoot= */ "", intellijRule.getProject().getName(), projectDataDir.getAbsolutePath(), - /*projectViewFile=*/ "", - BuildSystemName.Blaze)); + /* projectViewFile= */ "", + BuildSystemName.Blaze, + ProjectType.ASPECT_SYNC)); intellijRule.registerProjectService(BlazeImportSettingsManager.class, importSettingsManager); } diff --git a/aswb/tests/unittests/com/google/idea/blaze/android/libraries/UnpackedAarsTest.java b/aswb/tests/unittests/com/google/idea/blaze/android/libraries/UnpackedAarsTest.java index 53b0e171d03..10bffb346eb 100644 --- a/aswb/tests/unittests/com/google/idea/blaze/android/libraries/UnpackedAarsTest.java +++ b/aswb/tests/unittests/com/google/idea/blaze/android/libraries/UnpackedAarsTest.java @@ -29,9 +29,7 @@ import com.google.idea.blaze.android.sync.model.BlazeAndroidImportResult; import com.google.idea.blaze.android.sync.model.BlazeAndroidSyncData; import com.google.idea.blaze.base.BlazeTestCase; -import com.google.idea.blaze.base.command.buildresult.BlazeArtifact; import com.google.idea.blaze.base.command.buildresult.RemoteOutputArtifact; -import com.google.idea.blaze.base.filecache.ArtifactState; import com.google.idea.blaze.base.filecache.FileCache; import com.google.idea.blaze.base.ideinfo.ArtifactLocation; import com.google.idea.blaze.base.ideinfo.LibraryArtifact; @@ -49,8 +47,8 @@ import com.google.idea.blaze.base.projectview.ProjectViewSet; import com.google.idea.blaze.base.scope.BlazeContext; import com.google.idea.blaze.base.scope.OutputSink; -import com.google.idea.blaze.base.scope.output.PrintOutput; import com.google.idea.blaze.base.settings.BlazeImportSettings; +import com.google.idea.blaze.base.settings.BlazeImportSettings.ProjectType; import com.google.idea.blaze.base.settings.BlazeImportSettingsManager; import com.google.idea.blaze.base.settings.BuildSystemName; import com.google.idea.blaze.base.sync.BlazeSyncPlugin; @@ -59,6 +57,9 @@ import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings; import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder; import com.google.idea.blaze.base.sync.workspace.MockArtifactLocationDecoder; +import com.google.idea.blaze.common.PrintOutput; +import com.google.idea.blaze.common.artifact.ArtifactState; +import com.google.idea.blaze.common.artifact.BlazeArtifact; import com.google.idea.common.experiments.ExperimentService; import com.google.idea.common.experiments.MockExperimentService; import com.intellij.openapi.util.io.FileUtil; @@ -140,7 +141,12 @@ public BlazeArtifact resolveOutput(ArtifactLocation artifact) { File projectDataDirectory = folder.newFolder("projectdata"); BlazeImportSettings dummyImportSettings = new BlazeImportSettings( - "", "", projectDataDirectory.getAbsolutePath(), "", BuildSystemName.Bazel); + "", + "", + projectDataDirectory.getAbsolutePath(), + "", + BuildSystemName.Bazel, + ProjectType.ASPECT_SYNC); BlazeImportSettingsManager.getInstance(project).setImportSettings(dummyImportSettings); } catch (IOException e) { throw new AssertionError("Fail to create directory for test", e); @@ -201,6 +207,13 @@ public String getHashId() { public long getSyncTimeMillis() { return 0; } + + @Override + public String getDigest() { + // The digest algorithm depends on the build system and thus in-memory hash code is suitable + // in tests. + return String.valueOf(FileUtil.fileHashCode(file)); + } } private ArtifactLocation generateArtifactLocation(String relativePath) { @@ -211,6 +224,89 @@ private ArtifactLocation generateArtifactLocation(String relativePath) { .build(); } + @Test + public void refresh_localArtifact_srcJarIsCopied() throws IOException { + testRefreshSrcJarIsCopied(localArtifactLocationDecoder); + } + + @Test + public void refresh_remoteArtifact_srcJarIsCopied() throws IOException { + testRefreshSrcJarIsCopied(remoteArtifactLocationDecoder); + } + + private void testRefreshSrcJarIsCopied(ArtifactLocationDecoder decoder) throws IOException { + UnpackedAars unpackedAars = UnpackedAars.getInstance(project); + File aarCacheDir = unpackedAars.getCacheDir(); + + // new aar with jar files + String importedAar = "import.aar"; + String importedAarJar = "importAar.jar"; + String importedAarSrcJar = "importAar-src.jar"; + String colorsXmlRelativePath = "res/values/colors.xml"; + LibraryFileBuilder.aar(workspaceRoot, importedAar) + .addContent(colorsXmlRelativePath, ImmutableList.of(COLORS_XML_CONTENT)) + .build(); + File jar = workspaceRoot.fileForPath(new WorkspacePath(importedAarJar)); + try (ZipOutputStream zo = new ZipOutputStream(new FileOutputStream(jar))) { + zo.putNextEntry(new ZipEntry("com/google/foo/gen/Gen.java")); + zo.write("package gen; class Gen {}".getBytes(UTF_8)); + zo.closeEntry(); + } + + File srcJar = workspaceRoot.fileForPath(new WorkspacePath(importedAarSrcJar)); + try (ZipOutputStream zo = new ZipOutputStream(new FileOutputStream(srcJar))) { + zo.putNextEntry(new ZipEntry("com/google/foo/gen/Gen.class")); + zo.write("package gen; class Gen {}".getBytes(UTF_8)); + zo.closeEntry(); + } + ArtifactLocation importedAarArtifactLocation = generateArtifactLocation(importedAar); + ArtifactLocation jarArtifactLocation = generateArtifactLocation(importedAarJar); + ArtifactLocation srcJarArtifactLocation = generateArtifactLocation(importedAarSrcJar); + LibraryArtifact libraryArtifact = + LibraryArtifact.builder() + .setInterfaceJar(jarArtifactLocation) + .addSourceJar(srcJarArtifactLocation) + .build(); + AarLibrary importedAarLibrary = + new AarLibrary(libraryArtifact, importedAarArtifactLocation, null); + + BlazeAndroidImportResult importResult = + new BlazeAndroidImportResult( + ImmutableList.of(), + ImmutableMap.of( + LibraryKey.libraryNameFromArtifactLocation(importedAarArtifactLocation), + importedAarLibrary), + ImmutableList.of(), + ImmutableList.of()); + BlazeAndroidSyncData syncData = + new BlazeAndroidSyncData(importResult, new AndroidSdkPlatform("stable", 15)); + BlazeProjectData blazeProjectData = + MockBlazeProjectDataBuilder.builder(workspaceRoot) + .setWorkspaceLanguageSettings( + new WorkspaceLanguageSettings(WorkspaceType.ANDROID, ImmutableSet.of())) + .setSyncState(new SyncState.Builder().put(syncData).build()) + .setArtifactLocationDecoder(decoder) + .build(); + FileCache.EP_NAME + .extensions() + .forEach( + ep -> + ep.onSync( + getProject(), + context, + ProjectViewSet.builder().add(ProjectView.builder().build()).build(), + blazeProjectData, + null, + SyncMode.INCREMENTAL)); + + assertThat(aarCacheDir.list()).hasLength(1); + + ImmutableList cachedSrcJars = unpackedAars.getCachedSrcJars(decoder, importedAarLibrary); + assertThat(cachedSrcJars).hasSize(1); + assertThat(Files.readAllBytes(cachedSrcJars.get(0).toPath())) + .isEqualTo(Files.readAllBytes(srcJar.toPath())); + } + @Test public void refresh_localArtifact_fileNotExist_success() { testRefreshFileNotExist(localArtifactLocationDecoder); @@ -291,7 +387,7 @@ private void testRefreshIncludeLintJar(ArtifactLocationDecoder decoder) throws I zo.closeEntry(); } byte[] expectedJarContent = Files.readAllBytes(lintJar.toPath()); - AarLibraryFileBuilder.aar(workspaceRoot, aar).setLintJar(expectedJarContent).build(); + LibraryFileBuilder.aar(workspaceRoot, aar).addContent(FN_LINT_JAR, expectedJarContent).build(); ArtifactLocation aarArtifactLocation = generateArtifactLocation(aar); AarLibrary aarLibrary = new AarLibrary(aarArtifactLocation, null); @@ -352,8 +448,8 @@ private void testRefresh(ArtifactLocationDecoder decoder) throws IOException { // new aar without jar files String resourceAar = "resource.aar"; - AarLibraryFileBuilder.aar(workspaceRoot, resourceAar) - .src(stringsXmlRelativePath, ImmutableList.of(STRINGS_XML_CONTENT)) + LibraryFileBuilder.aar(workspaceRoot, resourceAar) + .addContent(stringsXmlRelativePath, ImmutableList.of(STRINGS_XML_CONTENT)) .build(); ArtifactLocation resourceAarArtifactLocation = generateArtifactLocation(resourceAar); AarLibrary resourceAarLibrary = new AarLibrary(resourceAarArtifactLocation, null); @@ -362,8 +458,8 @@ private void testRefresh(ArtifactLocationDecoder decoder) throws IOException { String importedAar = "import.aar"; String importedAarJar = "importAar.jar"; String colorsXmlRelativePath = "res/values/colors.xml"; - AarLibraryFileBuilder.aar(workspaceRoot, importedAar) - .src(colorsXmlRelativePath, ImmutableList.of(COLORS_XML_CONTENT)) + LibraryFileBuilder.aar(workspaceRoot, importedAar) + .addContent(colorsXmlRelativePath, ImmutableList.of(COLORS_XML_CONTENT)) .build(); File jar = workspaceRoot.fileForPath(new WorkspacePath(importedAarJar)); try (ZipOutputStream zo = new ZipOutputStream(new FileOutputStream(jar))) { @@ -461,7 +557,7 @@ private void testGetLintRuleJarLintFileIsReturn(ArtifactLocationDecoder decoder) zo.closeEntry(); } byte[] expectedJarContent = Files.readAllBytes(lintJar.toPath()); - AarLibraryFileBuilder.aar(workspaceRoot, aar).setLintJar(expectedJarContent).build(); + LibraryFileBuilder.aar(workspaceRoot, aar).addContent(FN_LINT_JAR, expectedJarContent).build(); ArtifactLocation aarArtifactLocation = generateArtifactLocation(aar); AarLibrary aarLibrary = new AarLibrary(aarArtifactLocation, null); @@ -520,7 +616,7 @@ private void testGetLintRuleJarNoLintFileIsReturnButNotExist(ArtifactLocationDec UnpackedAars unpackedAars = UnpackedAars.getInstance(project); String aar = "noLint.aar"; - AarLibraryFileBuilder.aar(workspaceRoot, aar).build(); + LibraryFileBuilder.aar(workspaceRoot, aar).build(); ArtifactLocation aarArtifactLocation = generateArtifactLocation(aar); AarLibrary aarLibrary = new AarLibrary(aarArtifactLocation, null); @@ -574,7 +670,7 @@ private void testGetLintRuleJarNotExistAarNullIsReturn(ArtifactLocationDecoder d UnpackedAars unpackedAars = UnpackedAars.getInstance(project); String notImportedAar = "notImported.aar"; - AarLibraryFileBuilder.aar(workspaceRoot, notImportedAar).build(); + LibraryFileBuilder.aar(workspaceRoot, notImportedAar).build(); ArtifactLocation notImportedAarArtifactLocation = generateArtifactLocation(notImportedAar); AarLibrary notImportedAarLibrary = new AarLibrary(notImportedAarArtifactLocation, null); diff --git a/aswb/tests/unittests/com/google/idea/blaze/android/projectsystem/BlazeProjectSystemBuildManagerTest.java b/aswb/tests/unittests/com/google/idea/blaze/android/projectsystem/BlazeProjectSystemBuildManagerTest.java new file mode 100644 index 00000000000..03cc3729659 --- /dev/null +++ b/aswb/tests/unittests/com/google/idea/blaze/android/projectsystem/BlazeProjectSystemBuildManagerTest.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.projectsystem; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.idea.blaze.base.BlazeTestCase; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test cases for {@link BlazeProjectSystemBuildManager}. */ +@RunWith(JUnit4.class) +public class BlazeProjectSystemBuildManagerTest extends BlazeTestCase { + @Test + public void testBlazeBuildCounter() { + BlazeProjectSystemBuildManager.BlazeBuildCounter buildCounter = + new BlazeProjectSystemBuildManager.BlazeBuildCounter(project); + assertThat(buildCounter.isBuilding()).isFalse(); + buildCounter.onBuildStarted(); + assertThat(buildCounter.isBuilding()).isTrue(); + buildCounter.onBuildCompleted(); + assertThat(buildCounter.isBuilding()).isFalse(); + } +} diff --git a/aswb/tests/unittests/com/google/idea/blaze/android/resources/actions/BlazeCreateResourceUtilsTest.java b/aswb/tests/unittests/com/google/idea/blaze/android/resources/actions/BlazeCreateResourceUtilsTest.java index d43a418ba9b..06ea866f0dc 100644 --- a/aswb/tests/unittests/com/google/idea/blaze/android/resources/actions/BlazeCreateResourceUtilsTest.java +++ b/aswb/tests/unittests/com/google/idea/blaze/android/resources/actions/BlazeCreateResourceUtilsTest.java @@ -21,6 +21,7 @@ import com.intellij.mock.MockVirtualFile; import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NotNull; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -37,14 +38,26 @@ public class BlazeCreateResourceUtilsTest extends BlazeTestCase { // foo.xml // layout/ // A.java - private MockVirtualFile xmlFile = file("foo.xml"); - private MockVirtualFile outOfPlaceSubdir = dir("misc"); - private MockVirtualFile valuesDir = dir("values-fr-rCA", xmlFile, outOfPlaceSubdir); - private MockVirtualFile layoutDir = dir("layout"); - private MockVirtualFile resDir = dir("res", valuesDir, layoutDir); - private MockVirtualFile javaFile = file("A.java"); - private MockVirtualFile srcDir = dir("src", resDir, javaFile); - private MockVirtualFile baseDir = dir("foo", srcDir); + private MockVirtualFile xmlFile; + private MockVirtualFile outOfPlaceSubdir; + private MockVirtualFile valuesDir; + private MockVirtualFile layoutDir; + private MockVirtualFile resDir; + private MockVirtualFile javaFile; + private MockVirtualFile srcDir; + private MockVirtualFile baseDir; + + @Before + public void setUp() throws Exception { + xmlFile = file("foo.xml"); + outOfPlaceSubdir = dir("misc"); + valuesDir = dir("values-fr-rCA", xmlFile, outOfPlaceSubdir); + layoutDir = dir("layout"); + resDir = dir("res", valuesDir, layoutDir); + javaFile = file("A.java"); + srcDir = dir("src", resDir, javaFile); + baseDir = dir("foo", srcDir); + } @Test public void getDirectoryFromContextResDirectory() { diff --git a/aswb/tests/unittests/com/google/idea/blaze/android/sync/BlazeAndroidSyncPluginTest.java b/aswb/tests/unittests/com/google/idea/blaze/android/sync/BlazeAndroidSyncPluginTest.java index ddd0491dd8d..94c3d9b5b36 100644 --- a/aswb/tests/unittests/com/google/idea/blaze/android/sync/BlazeAndroidSyncPluginTest.java +++ b/aswb/tests/unittests/com/google/idea/blaze/android/sync/BlazeAndroidSyncPluginTest.java @@ -46,6 +46,7 @@ import com.google.idea.blaze.java.sync.model.BlazeJavaSyncData; import com.google.idea.common.experiments.ExperimentService; import com.google.idea.common.experiments.MockExperimentService; +import com.google.idea.sdkcompat.roots.ex.ProjectRootManagerExWrapper; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; @@ -230,7 +231,7 @@ public void setLanguageLevel(@NotNull LanguageLevel languageLevel) { } /** Stores a project sdk so that it can be obtained later for verification. */ - private static class MockProjectRootManagerEx extends ProjectRootManagerEx { + private static class MockProjectRootManagerEx extends ProjectRootManagerExWrapper { Sdk projectSdk; @Nullable @@ -254,9 +255,6 @@ public void removeProjectJdkListener(@NotNull ProjectJdkListener projectJdkListe @Override public void makeRootsChange(@NotNull Runnable runnable, boolean b, boolean b1) {} - @Override - public void markRootsForRefresh() {} - @Override public void mergeRootsChangesDuring(@NotNull Runnable runnable) {} @@ -317,7 +315,7 @@ public String getProjectSdkName() { } @Nullable - // @Override #api193 + @Override public String getProjectSdkTypeName() { return null; } @@ -325,7 +323,7 @@ public String getProjectSdkTypeName() { // @Override #api211 public void setProjectSdkName(String s) {} - // @Override #api193 + @Override public void setProjectSdkName(String name, String sdkTypeName) {} } } diff --git a/aswb/tests/unittests/com/google/idea/blaze/android/sync/model/idea/BlazeClassJarProviderTest.java b/aswb/tests/unittests/com/google/idea/blaze/android/sync/model/idea/BlazeClassJarProviderTest.java index 279a24b1c33..24730150377 100644 --- a/aswb/tests/unittests/com/google/idea/blaze/android/sync/model/idea/BlazeClassJarProviderTest.java +++ b/aswb/tests/unittests/com/google/idea/blaze/android/sync/model/idea/BlazeClassJarProviderTest.java @@ -181,11 +181,10 @@ public ProjectViewSet reloadProjectView(BlazeContext context) { return null; } - @Nullable @Override public ProjectViewSet reloadProjectView( BlazeContext context, WorkspacePathResolver workspacePathResolver) { - return null; + return ProjectViewSet.EMPTY; } } diff --git a/aswb/tests/unittests/com/google/idea/blaze/android/projectsystem/BlazeModuleSystemTest.java b/aswb/tests/unittests/sdkcompat/as223/com/google/idea/blaze/android/projectsystem/BlazeModuleSystemTest.java similarity index 93% rename from aswb/tests/unittests/com/google/idea/blaze/android/projectsystem/BlazeModuleSystemTest.java rename to aswb/tests/unittests/sdkcompat/as223/com/google/idea/blaze/android/projectsystem/BlazeModuleSystemTest.java index b514e834aba..1b7194a7e31 100644 --- a/aswb/tests/unittests/com/google/idea/blaze/android/projectsystem/BlazeModuleSystemTest.java +++ b/aswb/tests/unittests/sdkcompat/as223/com/google/idea/blaze/android/projectsystem/BlazeModuleSystemTest.java @@ -15,6 +15,7 @@ */ package com.google.idea.blaze.android.projectsystem; +import static com.android.ide.common.repository.GoogleMavenArtifactIdCompat.APP_COMPAT_V7; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -22,7 +23,6 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; -import com.android.tools.idea.projectsystem.GoogleMavenArtifactId; import com.google.common.collect.Maps; import com.google.idea.blaze.android.resources.BlazeLightResourceClassService; import com.google.idea.blaze.android.sync.model.AndroidResourceModule; @@ -39,10 +39,11 @@ 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.Kind.Provider; import com.google.idea.blaze.base.model.primitives.Label; import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; +import com.google.idea.blaze.base.qsync.settings.QuerySyncSettings; import com.google.idea.blaze.base.settings.BlazeImportSettings; +import com.google.idea.blaze.base.settings.BlazeImportSettings.ProjectType; import com.google.idea.blaze.base.settings.BlazeImportSettingsManager; import com.google.idea.blaze.base.settings.BuildSystemName; import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; @@ -80,10 +81,11 @@ public class BlazeModuleSystemTest extends BlazeTestCase { @Override protected void initTest(Container applicationServices, Container projectServices) { - ExtensionPointImpl kindProvider = + ExtensionPointImpl kindProvider = registerExtensionPoint(Kind.Provider.EP_NAME, Kind.Provider.class); kindProvider.registerExtension(new AndroidBlazeRules()); applicationServices.register(Kind.ApplicationState.class, new Kind.ApplicationState()); + applicationServices.register(QuerySyncSettings.class, new QuerySyncSettings()); module = new MockModule(project, () -> {}); @@ -93,6 +95,7 @@ protected void initTest(Container applicationServices, Container projectServices mockBlazeImportSettings(projectServices); // For Blaze.isBlazeProject. createMocksForAddDependency(applicationServices, projectServices); + project.setBaseDir(new MockVirtualFile("/")); service = new BlazeProjectSystem(project); } @@ -111,8 +114,7 @@ public void testAddDependencyWithBuildTargetPsi() throws Exception { assertThat(buildFile).isNotNull(); when(psiFile.getVirtualFile()).thenReturn(buildFile); - BlazeModuleSystem.create(module) - .registerDependency(GoogleMavenArtifactId.APP_COMPAT_V7.getCoordinate("+")); + BlazeModuleSystem.create(module).registerDependency(APP_COMPAT_V7); ArgumentCaptor descriptorCaptor = ArgumentCaptor.forClass(OpenFileDescriptor.class); @@ -135,8 +137,7 @@ public void testAddDependencyWithoutBuildTargetPsi() throws Exception { VirtualFileSystemProvider.getInstance().getSystem().findFileByPath("/foo/BUILD"); assertThat(buildFile).isNotNull(); - BlazeModuleSystem.create(module) - .registerDependency(GoogleMavenArtifactId.APP_COMPAT_V7.getCoordinate("+")); + BlazeModuleSystem.create(module).registerDependency(APP_COMPAT_V7); verify(FileEditorManager.getInstance(project)).openFile(buildFile, true); verifyNoMoreInteractions(FileEditorManager.getInstance(project)); @@ -145,16 +146,13 @@ public void testAddDependencyWithoutBuildTargetPsi() throws Exception { @Test public void testGetResolvedDependencyWithoutLocators() throws Exception { registerExtensionPoint(MavenArtifactLocator.EP_NAME, MavenArtifactLocator.class); - assertThat( - BlazeModuleSystem.create(module) - .getResolvedDependency(GoogleMavenArtifactId.APP_COMPAT_V7.getCoordinate("+"))) - .isNull(); + assertThat(BlazeModuleSystem.create(module).getResolvedDependency(APP_COMPAT_V7)).isNull(); } private void mockBlazeImportSettings(Container projectServices) { BlazeImportSettingsManager importSettingsManager = new BlazeImportSettingsManager(project); importSettingsManager.setImportSettings( - new BlazeImportSettings("", "", "", "", BuildSystemName.Blaze)); + new BlazeImportSettings("", "", "", "", BuildSystemName.Blaze, ProjectType.ASPECT_SYNC)); projectServices.register(BlazeImportSettingsManager.class, importSettingsManager); } diff --git a/aswb/tests/unittests/com/google/idea/blaze/android/rendering/BlazeRenderErrorContributorTest.java b/aswb/tests/unittests/sdkcompat/as223/com/google/idea/blaze/android/rendering/BlazeRenderErrorContributorTest.java similarity index 94% rename from aswb/tests/unittests/com/google/idea/blaze/android/rendering/BlazeRenderErrorContributorTest.java rename to aswb/tests/unittests/sdkcompat/as223/com/google/idea/blaze/android/rendering/BlazeRenderErrorContributorTest.java index a33b10743f4..21774fd45cf 100644 --- a/aswb/tests/unittests/com/google/idea/blaze/android/rendering/BlazeRenderErrorContributorTest.java +++ b/aswb/tests/unittests/sdkcompat/as223/com/google/idea/blaze/android/rendering/BlazeRenderErrorContributorTest.java @@ -15,18 +15,17 @@ */ package com.google.idea.blaze.android.rendering; +import static com.google.common.collect.MoreCollectors.onlyElement; 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; -import com.android.tools.idea.rendering.RenderResult; import com.android.tools.idea.rendering.errors.ui.RenderErrorModel; +import com.android.tools.rendering.RenderResultCompat; 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.android.sync.model.AndroidResourceModule; import com.google.idea.blaze.android.sync.model.AndroidResourceModuleRegistry; import com.google.idea.blaze.base.BlazeTestCase; @@ -41,10 +40,11 @@ import com.google.idea.blaze.base.model.BlazeProjectData; import com.google.idea.blaze.base.model.MockBlazeProjectDataBuilder; import com.google.idea.blaze.base.model.primitives.Kind; -import com.google.idea.blaze.base.model.primitives.Kind.Provider; import com.google.idea.blaze.base.model.primitives.Label; import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; +import com.google.idea.blaze.base.qsync.settings.QuerySyncSettings; import com.google.idea.blaze.base.settings.BlazeImportSettings; +import com.google.idea.blaze.base.settings.BlazeImportSettings.ProjectType; import com.google.idea.blaze.base.settings.BlazeImportSettingsManager; import com.google.idea.blaze.base.settings.BuildSystemName; import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; @@ -75,7 +75,6 @@ import com.intellij.psi.search.ProjectScopeBuilder; import com.intellij.psi.search.ProjectScopeBuilderImpl; import java.io.File; -import java.util.stream.Collectors; import javax.annotation.Nullable; import org.junit.Ignore; import org.junit.Test; @@ -94,12 +93,13 @@ public class BlazeRenderErrorContributorTest extends BlazeTestCase { private ProjectFileIndex projectFileIndex; private Module module; private MockBlazeProjectDataManager projectDataManager; - private BlazeRenderErrorContributor.BlazeProvider provider; + private RenderResultCompat.BlazeProvider provider; @Override protected void initTest(Container applicationServices, Container projectServices) { super.initTest(applicationServices, projectServices); applicationServices.register(FileTypeManager.class, new MockFileTypeManager()); + applicationServices.register(QuerySyncSettings.class, new QuerySyncSettings()); projectFileIndex = mock(ProjectFileIndex.class); projectServices.register(ProjectFileIndex.class, projectFileIndex); @@ -109,13 +109,14 @@ protected void initTest(Container applicationServices, Container projectServices projectServices.register( AndroidResourceModuleRegistry.class, new AndroidResourceModuleRegistry()); - ExtensionPointImpl kindProvider = + ExtensionPointImpl kindProvider = registerExtensionPoint(Kind.Provider.EP_NAME, Kind.Provider.class); kindProvider.registerExtension(new AndroidBlazeRules()); applicationServices.register(Kind.ApplicationState.class, new Kind.ApplicationState()); BlazeImportSettingsManager importSettingsManager = new BlazeImportSettingsManager(project); - BlazeImportSettings settings = new BlazeImportSettings("", "", "", "", BuildSystemName.Blaze); + BlazeImportSettings settings = + new BlazeImportSettings("", "", "", "", BuildSystemName.Blaze, ProjectType.ASPECT_SYNC); importSettingsManager.setImportSettings(settings); projectServices.register(BlazeImportSettingsManager.class, importSettingsManager); @@ -130,12 +131,12 @@ protected void initTest(Container applicationServices, Container projectServices ExtensionPointName.create("com.android.rendering.renderErrorContributor"), RenderErrorContributor.Provider.class); extensionPoint.registerExtension(new RenderErrorContributor.Provider()); - extensionPoint.registerExtension(new BlazeRenderErrorContributor.BlazeProvider()); + extensionPoint.registerExtension(new RenderResultCompat.BlazeProvider()); module = new MockModule(project, () -> {}); // For the isApplicable tests. - provider = new BlazeRenderErrorContributor.BlazeProvider(); + provider = new RenderResultCompat.BlazeProvider(); } @Test @@ -155,8 +156,7 @@ public void testNoIssuesIfNoErrors() { when(projectFileIndex.getModuleForFile(virtualFile)).thenReturn(module); PsiFile file = new MockPsiFile(virtualFile, new MockPsiManager(project)); file.putUserData(ModuleUtilCore.KEY_MODULE, module); - RenderResult result = RenderResult.createBlank(file); - RenderErrorModel errorModel = RenderErrorModelFactory.createErrorModel(null, result, null); + RenderErrorModel errorModel = RenderResultCompat.createBlank(file).createErrorModel(); assertThat(errorModel.getIssues()).isEmpty(); } @@ -180,10 +180,9 @@ public void testReportGeneratedResources() { RenderErrorModel errorModel = createRenderErrorModelWithBrokenClasses(); RenderErrorModel.Issue generatedResourcesIssue = - Iterables.getOnlyElement( - errorModel.getIssues().stream() - .filter(issue -> issue.getSummary().equals(GENERATED_RESOURCES_ERROR)) - .collect(Collectors.toList())); + errorModel.getIssues().stream() + .filter(issue -> issue.getSummary().equals(GENERATED_RESOURCES_ERROR)) + .collect(onlyElement()); assertThat(generatedResourcesIssue.getHtmlContent()) .isEqualTo( @@ -217,10 +216,9 @@ public void testReportNonStandardAndroidManifestName() { RenderErrorModel errorModel = createRenderErrorModelWithBrokenClasses(); RenderErrorModel.Issue nonStandardManifestNameIssue = - Iterables.getOnlyElement( - errorModel.getIssues().stream() - .filter(issue -> issue.getSummary().equals(NON_STANDARD_MANIFEST_NAME_ERROR)) - .collect(Collectors.toList())); + errorModel.getIssues().stream() + .filter(issue -> issue.getSummary().equals(NON_STANDARD_MANIFEST_NAME_ERROR)) + .collect(onlyElement()); assertThat(nonStandardManifestNameIssue.getHtmlContent()) .isEqualTo( @@ -259,10 +257,9 @@ public void testReportMissingClassDependencies() { "com.google.example.ResourceView"); RenderErrorModel.Issue missingClassDependenciesIssue = - Iterables.getOnlyElement( - errorModel.getIssues().stream() - .filter(issue -> issue.getSummary().equals(MISSING_CLASS_DEPENDENCIES_ERROR)) - .collect(Collectors.toList())); + errorModel.getIssues().stream() + .filter(issue -> issue.getSummary().equals(MISSING_CLASS_DEPENDENCIES_ERROR)) + .collect(onlyElement()); assertThat(missingClassDependenciesIssue.getHtmlContent()) .isEqualTo( @@ -330,11 +327,11 @@ private RenderErrorModel createRenderErrorModelWithBrokenClasses() { when(projectFileIndex.getModuleForFile(virtualFile)).thenReturn(module); PsiFile file = new MockPsiFile(virtualFile, new MockPsiManager(project)); file.putUserData(ModuleUtilCore.KEY_MODULE, module); - RenderResult result = RenderResult.createBlank(file); + RenderResultCompat result = RenderResultCompat.createBlank(file); result .getLogger() .addBrokenClass("com.google.example.CustomView", new Exception("resource not found")); - return RenderErrorModelFactory.createErrorModel(null, result, null); + return result.createErrorModel(); } private RenderErrorModel createRenderErrorModelWithMissingClasses(String... classNames) { @@ -342,11 +339,11 @@ private RenderErrorModel createRenderErrorModelWithMissingClasses(String... clas when(projectFileIndex.getModuleForFile(virtualFile)).thenReturn(module); PsiFile file = new MockPsiFile(virtualFile, new MockPsiManager(project)); file.putUserData(ModuleUtilCore.KEY_MODULE, module); - RenderResult result = RenderResult.createBlank(file); + RenderResultCompat result = RenderResultCompat.createBlank(file); for (String className : classNames) { result.getLogger().addMissingClass(className); } - return RenderErrorModelFactory.createErrorModel(null, result, null); + return result.createErrorModel(); } private static ArtifactLocation artifact(String relativePath, boolean isSource) { @@ -662,7 +659,6 @@ private void createPsiClassesAndSourceToTargetMap(Container projectServices) { new MockVirtualFile("src/com/google/example/dependent/LibraryView.java"); VirtualFile resourceView = new MockVirtualFile("src/com/google/example/ResourceView.java"); - ImmutableMap sourceToTarget = ImmutableMap.of( VfsUtilCore.virtualToIoFile(independentLibraryView), @@ -729,6 +725,18 @@ public File decode(ArtifactLocation location) { public BlazeProjectData getBlazeProjectData() { return blazeProjectData; } + + @Nullable + @Override + public BlazeProjectData loadProject(BlazeImportSettings importSettings) { + throw new UnsupportedOperationException(); + } + + @Override + public void saveProject( + final BlazeImportSettings importSettings, final BlazeProjectData projectData) { + throw new UnsupportedOperationException(); + } } private static class MockBuildReferenceManager extends BuildReferenceManager { diff --git a/aswb/tests/unittests/sdkcompat/as231/com/google/idea/blaze/android/projectsystem/BlazeModuleSystemTest.java b/aswb/tests/unittests/sdkcompat/as231/com/google/idea/blaze/android/projectsystem/BlazeModuleSystemTest.java new file mode 100644 index 00000000000..1b7194a7e31 --- /dev/null +++ b/aswb/tests/unittests/sdkcompat/as231/com/google/idea/blaze/android/projectsystem/BlazeModuleSystemTest.java @@ -0,0 +1,242 @@ +/* + * 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.projectsystem; + +import static com.android.ide.common.repository.GoogleMavenArtifactIdCompat.APP_COMPAT_V7; +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import com.google.common.collect.Maps; +import com.google.idea.blaze.android.resources.BlazeLightResourceClassService; +import com.google.idea.blaze.android.sync.model.AndroidResourceModule; +import com.google.idea.blaze.android.sync.model.AndroidResourceModuleRegistry; +import com.google.idea.blaze.base.BlazeTestCase; +import com.google.idea.blaze.base.ideinfo.ArtifactLocation; +import com.google.idea.blaze.base.ideinfo.TargetIdeInfo; +import com.google.idea.blaze.base.ideinfo.TargetKey; +import com.google.idea.blaze.base.ideinfo.TargetMap; +import com.google.idea.blaze.base.ideinfo.TargetMapBuilder; +import com.google.idea.blaze.base.io.VirtualFileSystemProvider; +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.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.Label; +import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; +import com.google.idea.blaze.base.qsync.settings.QuerySyncSettings; +import com.google.idea.blaze.base.settings.BlazeImportSettings; +import com.google.idea.blaze.base.settings.BlazeImportSettings.ProjectType; +import com.google.idea.blaze.base.settings.BlazeImportSettingsManager; +import com.google.idea.blaze.base.settings.BuildSystemName; +import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; +import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder; +import com.google.idea.blaze.base.sync.workspace.MockArtifactLocationDecoder; +import com.google.idea.blaze.java.AndroidBlazeRules; +import com.google.idea.common.experiments.ExperimentService; +import com.google.idea.common.experiments.MockExperimentService; +import com.intellij.mock.MockModule; +import com.intellij.mock.MockVirtualFile; +import com.intellij.openapi.editor.LazyRangeMarkerFactory; +import com.intellij.openapi.editor.impl.LazyRangeMarkerFactoryImpl; +import com.intellij.openapi.extensions.impl.ExtensionPointImpl; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.fileEditor.OpenFileDescriptor; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.ex.temp.TempFileSystem; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import java.io.File; +import java.util.Map; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.ArgumentCaptor; + +/** Test cases for {@link BlazeModuleSystem}. */ +@RunWith(JUnit4.class) +public class BlazeModuleSystemTest extends BlazeTestCase { + WorkspaceRoot workspaceRoot = new WorkspaceRoot(new File("/")); + Module module; + BlazeProjectSystem service; + + @Override + protected void initTest(Container applicationServices, Container projectServices) { + ExtensionPointImpl kindProvider = + registerExtensionPoint(Kind.Provider.EP_NAME, Kind.Provider.class); + kindProvider.registerExtension(new AndroidBlazeRules()); + applicationServices.register(Kind.ApplicationState.class, new Kind.ApplicationState()); + applicationServices.register(QuerySyncSettings.class, new QuerySyncSettings()); + + module = new MockModule(project, () -> {}); + + // For the 'blaze.class.file.finder.name' experiment. + applicationServices.register(ExperimentService.class, new MockExperimentService()); + + mockBlazeImportSettings(projectServices); // For Blaze.isBlazeProject. + createMocksForAddDependency(applicationServices, projectServices); + + project.setBaseDir(new MockVirtualFile("/")); + service = new BlazeProjectSystem(project); + } + + @Test + public void testAddDependencyWithBuildTargetPsi() throws Exception { + PsiElement buildTargetPsi = mock(PsiElement.class); + PsiFile psiFile = mock(PsiFile.class); + + BuildReferenceManager buildReferenceManager = BuildReferenceManager.getInstance(project); + when(buildReferenceManager.resolveLabel(Label.create("//foo:bar"))).thenReturn(buildTargetPsi); + when(buildTargetPsi.getContainingFile()).thenReturn(psiFile); + when(buildTargetPsi.getTextOffset()).thenReturn(1337); + + VirtualFile buildFile = + VirtualFileSystemProvider.getInstance().getSystem().findFileByPath("/foo/BUILD"); + assertThat(buildFile).isNotNull(); + when(psiFile.getVirtualFile()).thenReturn(buildFile); + + BlazeModuleSystem.create(module).registerDependency(APP_COMPAT_V7); + + ArgumentCaptor descriptorCaptor = + ArgumentCaptor.forClass(OpenFileDescriptor.class); + verify(FileEditorManager.getInstance(project)) + .openTextEditor(descriptorCaptor.capture(), eq(true)); + OpenFileDescriptor descriptor = descriptorCaptor.getValue(); + assertThat(descriptor.getProject()).isEqualTo(project); + assertThat(descriptor.getFile()).isEqualTo(buildFile); + assertThat(descriptor.getOffset()).isEqualTo(1337); + verifyNoMoreInteractions(FileEditorManager.getInstance(project)); + } + + @Test + public void testAddDependencyWithoutBuildTargetPsi() throws Exception { + // Can't find PSI for the target. + when(BuildReferenceManager.getInstance(project).resolveLabel(Label.create("//foo:bar"))) + .thenReturn(null); + + VirtualFile buildFile = + VirtualFileSystemProvider.getInstance().getSystem().findFileByPath("/foo/BUILD"); + assertThat(buildFile).isNotNull(); + + BlazeModuleSystem.create(module).registerDependency(APP_COMPAT_V7); + + verify(FileEditorManager.getInstance(project)).openFile(buildFile, true); + verifyNoMoreInteractions(FileEditorManager.getInstance(project)); + } + + @Test + public void testGetResolvedDependencyWithoutLocators() throws Exception { + registerExtensionPoint(MavenArtifactLocator.EP_NAME, MavenArtifactLocator.class); + assertThat(BlazeModuleSystem.create(module).getResolvedDependency(APP_COMPAT_V7)).isNull(); + } + + private void mockBlazeImportSettings(Container projectServices) { + BlazeImportSettingsManager importSettingsManager = new BlazeImportSettingsManager(project); + importSettingsManager.setImportSettings( + new BlazeImportSettings("", "", "", "", BuildSystemName.Blaze, ProjectType.ASPECT_SYNC)); + projectServices.register(BlazeImportSettingsManager.class, importSettingsManager); + } + + private void createMocksForAddDependency( + Container applicationServices, Container projectServices) { + projectServices.register( + BlazeProjectDataManager.class, + new MockBlazeProjectDataManager(createMockBlazeProjectData())); + projectServices.register(FileEditorManager.class, mock(FileEditorManager.class)); + projectServices.register(BuildReferenceManager.class, mock(BuildReferenceManager.class)); + projectServices.register(LazyRangeMarkerFactory.class, mock(LazyRangeMarkerFactoryImpl.class)); + projectServices.register( + BlazeLightResourceClassService.class, mock(BlazeLightResourceClassService.class)); + + applicationServices.register( + VirtualFileSystemProvider.class, new MockVirtualFileSystemProvider("/foo/BUILD")); + + AndroidResourceModuleRegistry moduleRegistry = new AndroidResourceModuleRegistry(); + moduleRegistry.put( + module, + AndroidResourceModule.builder(TargetKey.forPlainTarget(Label.create("//foo:bar"))).build()); + projectServices.register(AndroidResourceModuleRegistry.class, moduleRegistry); + } + + private BlazeProjectData createMockBlazeProjectData() { + TargetMap targetMap = + TargetMapBuilder.builder() + .addTarget( + TargetIdeInfo.builder() + .setLabel(Label.create("//foo:bar")) + .setKind(AndroidBlazeRules.RuleTypes.ANDROID_LIBRARY.getKind()) + .setBuildFile(ArtifactLocation.builder().setRelativePath("foo/BUILD").build()) + .build()) + .build(); + ArtifactLocationDecoder decoder = + new MockArtifactLocationDecoder() { + @Override + public File decode(ArtifactLocation artifactLocation) { + return new File("/", artifactLocation.getRelativePath()); + } + }; + return MockBlazeProjectDataBuilder.builder(workspaceRoot) + .setTargetMap(targetMap) + .setArtifactLocationDecoder(decoder) + .build(); + } + + private static class MockFileSystem extends TempFileSystem { + private Map files; + + public MockFileSystem(String... paths) { + files = Maps.newHashMap(); + for (String path : paths) { + files.put(path, new MockVirtualFile(path)); + } + } + + @Override + public VirtualFile findFileByPath(String path) { + return files.get(path); + } + + @Override + public VirtualFile findFileByPathIfCached(String path) { + return findFileByPath(path); + } + + @Override + public VirtualFile findFileByIoFile(File file) { + return findFileByPath(file.getPath()); + } + } + + private static class MockVirtualFileSystemProvider implements VirtualFileSystemProvider { + + private final LocalFileSystem fileSystem; + + MockVirtualFileSystemProvider(String... paths) { + fileSystem = new MockFileSystem(paths); + } + + @Override + public LocalFileSystem getSystem() { + return fileSystem; + } + } +} diff --git a/aswb/tests/unittests/sdkcompat/as231/com/google/idea/blaze/android/rendering/BlazeRenderErrorContributorTest.java b/aswb/tests/unittests/sdkcompat/as231/com/google/idea/blaze/android/rendering/BlazeRenderErrorContributorTest.java new file mode 100644 index 00000000000..21774fd45cf --- /dev/null +++ b/aswb/tests/unittests/sdkcompat/as231/com/google/idea/blaze/android/rendering/BlazeRenderErrorContributorTest.java @@ -0,0 +1,771 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.rendering; + +import static com.google.common.collect.MoreCollectors.onlyElement; +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.errors.ui.RenderErrorModel; +import com.android.tools.rendering.RenderResultCompat; +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.idea.blaze.android.sync.model.AndroidResourceModule; +import com.google.idea.blaze.android.sync.model.AndroidResourceModuleRegistry; +import com.google.idea.blaze.base.BlazeTestCase; +import com.google.idea.blaze.base.ideinfo.AndroidIdeInfo; +import com.google.idea.blaze.base.ideinfo.ArtifactLocation; +import com.google.idea.blaze.base.ideinfo.JavaIdeInfo; +import com.google.idea.blaze.base.ideinfo.TargetIdeInfo; +import com.google.idea.blaze.base.ideinfo.TargetKey; +import com.google.idea.blaze.base.ideinfo.TargetMap; +import com.google.idea.blaze.base.ideinfo.TargetMapBuilder; +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.MockBlazeProjectDataBuilder; +import com.google.idea.blaze.base.model.primitives.Kind; +import com.google.idea.blaze.base.model.primitives.Label; +import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; +import com.google.idea.blaze.base.qsync.settings.QuerySyncSettings; +import com.google.idea.blaze.base.settings.BlazeImportSettings; +import com.google.idea.blaze.base.settings.BlazeImportSettings.ProjectType; +import com.google.idea.blaze.base.settings.BlazeImportSettingsManager; +import com.google.idea.blaze.base.settings.BuildSystemName; +import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; +import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder; +import com.google.idea.blaze.base.sync.workspace.MockArtifactLocationDecoder; +import com.google.idea.blaze.base.targetmaps.SourceToTargetMap; +import com.google.idea.blaze.base.targetmaps.TransitiveDependencyMap; +import com.google.idea.blaze.java.AndroidBlazeRules; +import com.intellij.mock.MockModule; +import com.intellij.mock.MockPsiFile; +import com.intellij.mock.MockPsiManager; +import com.intellij.mock.MockVirtualFile; +import com.intellij.openapi.extensions.ExtensionPoint; +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.openapi.extensions.impl.ExtensionPointImpl; +import com.intellij.openapi.fileTypes.FileTypeManager; +import com.intellij.openapi.fileTypes.MockFileTypeManager; +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.JvmPsiConversionHelper; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.impl.JvmPsiConversionHelperImpl; +import com.intellij.psi.search.ProjectScopeBuilder; +import com.intellij.psi.search.ProjectScopeBuilderImpl; +import java.io.File; +import javax.annotation.Nullable; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link BlazeRenderErrorContributor}. */ +@RunWith(JUnit4.class) +public class BlazeRenderErrorContributorTest extends BlazeTestCase { + private static final String BLAZE_BIN = "blaze-out/crosstool/bin"; + private static final String GENERATED_RESOURCES_ERROR = "Generated resources"; + private static final String NON_STANDARD_MANIFEST_NAME_ERROR = "Non-standard manifest name"; + private static final String MISSING_CLASS_DEPENDENCIES_ERROR = "Missing class dependencies"; + + private static final WorkspaceRoot workspaceRoot = new WorkspaceRoot(new File("/")); + private ProjectFileIndex projectFileIndex; + private Module module; + private MockBlazeProjectDataManager projectDataManager; + private RenderResultCompat.BlazeProvider provider; + + @Override + protected void initTest(Container applicationServices, Container projectServices) { + super.initTest(applicationServices, projectServices); + applicationServices.register(FileTypeManager.class, new MockFileTypeManager()); + applicationServices.register(QuerySyncSettings.class, new QuerySyncSettings()); + + projectFileIndex = mock(ProjectFileIndex.class); + projectServices.register(ProjectFileIndex.class, projectFileIndex); + projectServices.register(BuildReferenceManager.class, new MockBuildReferenceManager(project)); + projectServices.register(TransitiveDependencyMap.class, new TransitiveDependencyMap(project)); + projectServices.register(ProjectScopeBuilder.class, new ProjectScopeBuilderImpl(project)); + projectServices.register( + AndroidResourceModuleRegistry.class, new AndroidResourceModuleRegistry()); + + ExtensionPointImpl kindProvider = + registerExtensionPoint(Kind.Provider.EP_NAME, Kind.Provider.class); + kindProvider.registerExtension(new AndroidBlazeRules()); + applicationServices.register(Kind.ApplicationState.class, new Kind.ApplicationState()); + + BlazeImportSettingsManager importSettingsManager = new BlazeImportSettingsManager(project); + BlazeImportSettings settings = + new BlazeImportSettings("", "", "", "", BuildSystemName.Blaze, ProjectType.ASPECT_SYNC); + importSettingsManager.setImportSettings(settings); + projectServices.register(BlazeImportSettingsManager.class, importSettingsManager); + + projectServices.register(JvmPsiConversionHelper.class, new JvmPsiConversionHelperImpl()); + createPsiClassesAndSourceToTargetMap(projectServices); + + projectDataManager = new MockBlazeProjectDataManager(); + projectServices.register(BlazeProjectDataManager.class, projectDataManager); + + ExtensionPoint extensionPoint = + registerExtensionPoint( + ExtensionPointName.create("com.android.rendering.renderErrorContributor"), + RenderErrorContributor.Provider.class); + extensionPoint.registerExtension(new RenderErrorContributor.Provider()); + extensionPoint.registerExtension(new RenderResultCompat.BlazeProvider()); + + module = new MockModule(project, () -> {}); + + // For the isApplicable tests. + provider = new RenderResultCompat.BlazeProvider(); + } + + @Test + public void testProviderIsApplicable() { + assertThat(provider.isApplicable(project)).isTrue(); + } + + @Test + public void testProviderNotApplicableIfNotBlaze() { + BlazeImportSettingsManager.getInstance(project).loadState(null); + assertThat(provider.isApplicable(project)).isFalse(); + } + + @Test + public void testNoIssuesIfNoErrors() { + VirtualFile virtualFile = new MockVirtualFile("layout.xml"); + when(projectFileIndex.getModuleForFile(virtualFile)).thenReturn(module); + PsiFile file = new MockPsiFile(virtualFile, new MockPsiManager(project)); + file.putUserData(ModuleUtilCore.KEY_MODULE, module); + RenderErrorModel errorModel = RenderResultCompat.createBlank(file).createErrorModel(); + assertThat(errorModel.getIssues()).isEmpty(); + } + + @Test + public void testNoBlazeIssuesIfNoRelatedErrors() { + RenderErrorModel errorModel = createRenderErrorModelWithBrokenClasses(); + errorModel + .getIssues() + .forEach( + issue -> + assertThat(issue.getSummary()) + .isNoneOf( + GENERATED_RESOURCES_ERROR, + NON_STANDARD_MANIFEST_NAME_ERROR, + MISSING_CLASS_DEPENDENCIES_ERROR)); + } + + @Test + public void testReportGeneratedResources() { + createTargetMapWithGeneratedResources(); + RenderErrorModel errorModel = createRenderErrorModelWithBrokenClasses(); + + RenderErrorModel.Issue generatedResourcesIssue = + errorModel.getIssues().stream() + .filter(issue -> issue.getSummary().equals(GENERATED_RESOURCES_ERROR)) + .collect(onlyElement()); + + assertThat(generatedResourcesIssue.getHtmlContent()) + .isEqualTo( + "Generated resources will not be discovered by the IDE:" + + "
" + + "
-&NBSP;" + + "com/google/example/dependency/generated/res " + + "from " + + "//com/google/example:generated" + + "
-&NBSP;" + + "com/google/example/main/generated/res " + + "from " + + "//com/google/example:main" + + "
-&NBSP;" + + "com/google/example/transitive/generated/one/res " + + "from " + + "//com/google/example/transitive:generated" + + "
-&NBSP;" + + "com/google/example/transitive/generated/two/res " + + "from " + + "//com/google/example/transitive:generated" + + "
" + + "Please avoid using generated resources, then " + + "sync the project and " + + "refresh the layout."); + } + + @Test + public void testReportNonStandardAndroidManifestName() { + createTargetMapWithNonStandardAndroidManifestName(); + RenderErrorModel errorModel = createRenderErrorModelWithBrokenClasses(); + + RenderErrorModel.Issue nonStandardManifestNameIssue = + errorModel.getIssues().stream() + .filter(issue -> issue.getSummary().equals(NON_STANDARD_MANIFEST_NAME_ERROR)) + .collect(onlyElement()); + + assertThat(nonStandardManifestNameIssue.getHtmlContent()) + .isEqualTo( + "" + + "//com/google/example:main " + + "uses a non-standard name for the Android manifest: " + + "" + + "WeirdManifest.xml" + + "
" + + "Please rename it to AndroidManifest.xml, then " + + "sync the project and " + + "refresh the layout."); + } + + @Test + public void testNoReportNonStandardAndroidManifestNameInDependency() { + createTargetMapWithNonStandardAndroidManifestNameInDependency(); + RenderErrorModel errorModel = createRenderErrorModelWithBrokenClasses(); + + errorModel + .getIssues() + .forEach( + issue -> assertThat(issue.getSummary()).isNotEqualTo(NON_STANDARD_MANIFEST_NAME_ERROR)); + } + + @Ignore("b/145809318") + @Test + public void testReportMissingClassDependencies() { + createTargetMapWithMissingClassDependency(); + RenderErrorModel errorModel = + createRenderErrorModelWithMissingClasses( + "com.google.example.independent.LibraryView", + "com.google.example.independent.LibraryView2", + "com.google.example.independent.Library2View", + "com.google.example.dependent.LibraryView", + "com.google.example.ResourceView"); + + RenderErrorModel.Issue missingClassDependenciesIssue = + errorModel.getIssues().stream() + .filter(issue -> issue.getSummary().equals(MISSING_CLASS_DEPENDENCIES_ERROR)) + .collect(onlyElement()); + + assertThat(missingClassDependenciesIssue.getHtmlContent()) + .isEqualTo( + "" + + "//com/google/example:resources " + + "contains resource files that reference these classes:" + + "
" + + "
-&NBSP;" + + "" + + "com.google.example.independent.Library2View " + + "from " + + "//com/google/example/independent:library2 " + + "
-&NBSP;" + + "" + + "com.google.example.independent.LibraryView " + + "from " + + "//com/google/example/independent:library " + + "
-&NBSP;" + + "" + + "com.google.example.independent.LibraryView2 " + + "from " + + "//com/google/example/independent:library " + + "
" + + "Please fix your dependencies so that " + + "" + + "//com/google/example:resources " + + "correctly depends on these classes, then " + + "sync the project and " + + "refresh the layout." + + "
" + + "
" + + "NOTE: blaze can still build with the incorrect dependencies " + + "due to the way it handles resources, " + + "but the layout editor needs them to be correct."); + } + + @Ignore("b/145809318") + @Test + public void testNoReportMissingClassDependenciesIfClassInSameTarget() { + createTargetMapWithMissingClassDependency(); + RenderErrorModel errorModel = + createRenderErrorModelWithMissingClasses("com.google.example.ResourceView"); + + errorModel + .getIssues() + .forEach( + issue -> assertThat(issue.getSummary()).isNotEqualTo(MISSING_CLASS_DEPENDENCIES_ERROR)); + } + + @Ignore("b/145809318") + @Test + public void testNoReportMissingClassDependenciesIfClassInDependency() { + createTargetMapWithMissingClassDependency(); + RenderErrorModel errorModel = + createRenderErrorModelWithMissingClasses("com.google.example.dependent.LibraryView"); + + errorModel + .getIssues() + .forEach( + issue -> assertThat(issue.getSummary()).isNotEqualTo(MISSING_CLASS_DEPENDENCIES_ERROR)); + } + + private RenderErrorModel createRenderErrorModelWithBrokenClasses() { + VirtualFile virtualFile = new MockVirtualFile("layout.xml"); + when(projectFileIndex.getModuleForFile(virtualFile)).thenReturn(module); + PsiFile file = new MockPsiFile(virtualFile, new MockPsiManager(project)); + file.putUserData(ModuleUtilCore.KEY_MODULE, module); + RenderResultCompat result = RenderResultCompat.createBlank(file); + result + .getLogger() + .addBrokenClass("com.google.example.CustomView", new Exception("resource not found")); + return result.createErrorModel(); + } + + private RenderErrorModel createRenderErrorModelWithMissingClasses(String... classNames) { + VirtualFile virtualFile = new MockVirtualFile("layout.xml"); + when(projectFileIndex.getModuleForFile(virtualFile)).thenReturn(module); + PsiFile file = new MockPsiFile(virtualFile, new MockPsiManager(project)); + file.putUserData(ModuleUtilCore.KEY_MODULE, module); + RenderResultCompat result = RenderResultCompat.createBlank(file); + for (String className : classNames) { + result.getLogger().addMissingClass(className); + } + return result.createErrorModel(); + } + + private static ArtifactLocation artifact(String relativePath, boolean isSource) { + return ArtifactLocation.builder() + .setIsSource(isSource) + .setRootExecutionPathFragment(isSource ? "" : BLAZE_BIN) + .setRelativePath(relativePath) + .build(); + } + + private void createTargetMapWithGeneratedResources() { + Label mainResourcesTarget = Label.create("//com/google/example:main"); + Label dependencyGeneratedResourceTarget = Label.create("//com/google/example:generated"); + Label dependencySourceResourceTarget = Label.create("//com/google/example:source"); + Label transitiveGeneratedResourcesTarget = + Label.create("//com/google/example/transitive:generated"); + Label transitiveSourceResourceTarget = Label.create("//com/google/example/transitive:source"); + Label unrelatedGeneratedResourceTarget = Label.create("//com/google/unrelated:generated"); + Label unrelatedSourceResourceTarget = Label.create("//com/google/unrelated:source"); + + ArtifactLocation mainGeneratedResource = + artifact("com/google/example/main/generated/res", false); + ArtifactLocation mainSourceResource = artifact("com/google/example/main/source/res", true); + ArtifactLocation dependencyGeneratedResource = + artifact("com/google/example/dependency/generated/res", false); + ArtifactLocation dependencySourceResource = + artifact("com/google/example/dependency/source/res", true); + ArtifactLocation transitiveGeneratedResourceOne = + artifact("com/google/example/transitive/generated/one/res", false); + ArtifactLocation transitiveGeneratedResourceTwo = + artifact("com/google/example/transitive/generated/two/res", false); + ArtifactLocation transitiveSourceResource = + artifact("com/google/example/transitive/source/res", true); + ArtifactLocation unrelatedGeneratedResource = + artifact("com/google/unrelated/generated/res", false); + ArtifactLocation unrelatedSourceResource = artifact("com/google/unrelated/source/res", true); + + ArtifactLocation mainBuildFile = artifact("com/google/example/main/BUILD", true); + ArtifactLocation dependencyBuildFile = artifact("com/google/example/dependency/BUILD", true); + ArtifactLocation transitiveBuildFile = artifact("com/google/example/transitive/BUILD", true); + ArtifactLocation unrelatedBuildFile = artifact("com/google/unrelated/BUILD", true); + + AndroidResourceModuleRegistry registry = AndroidResourceModuleRegistry.getInstance(project); + registry.put( + module, + AndroidResourceModule.builder(TargetKey.forPlainTarget(mainResourcesTarget)) + // .addResource(mainGeneratedResource) // Dropped. + .addResource(mainSourceResource) + .addTransitiveResourceDependency(dependencyGeneratedResourceTarget) + .addTransitiveResource(dependencyGeneratedResource) + .addTransitiveResourceDependency(dependencySourceResourceTarget) + .addTransitiveResource(dependencySourceResource) + .addTransitiveResourceDependency(transitiveGeneratedResourcesTarget) + .addTransitiveResource(transitiveGeneratedResourceOne) + .addTransitiveResource(transitiveGeneratedResourceTwo) + .addTransitiveResourceDependency(transitiveSourceResourceTarget) + .addTransitiveResource(transitiveSourceResource) + .build()); + // Not using these, but they should be in the registry. + registry.put( + mock(Module.class), + AndroidResourceModule.builder(TargetKey.forPlainTarget(dependencyGeneratedResourceTarget)) + // .addResource(dependencyGeneratedResource) // Dropped. + .addTransitiveResourceDependency(transitiveSourceResourceTarget) + .addTransitiveResource(transitiveSourceResource) + .build()); + registry.put( + mock(Module.class), + AndroidResourceModule.builder(TargetKey.forPlainTarget(dependencySourceResourceTarget)) + .addResource(dependencySourceResource) + .addTransitiveResourceDependency(transitiveGeneratedResourcesTarget) + .addTransitiveResource(transitiveGeneratedResourceOne) + .addTransitiveResource(transitiveGeneratedResourceTwo) + .build()); + registry.put( + mock(Module.class), + AndroidResourceModule.builder(TargetKey.forPlainTarget(transitiveGeneratedResourcesTarget)) + // .addResource(transitiveGeneratedResourceOne) // Dropped. + // .addResource(transitiveGeneratedResourceTwo) // Dropped. + .build()); + registry.put( + mock(Module.class), + AndroidResourceModule.builder(TargetKey.forPlainTarget(transitiveSourceResourceTarget)) + .addResource(transitiveSourceResource) + .build()); + registry.put( + mock(Module.class), + AndroidResourceModule.builder(TargetKey.forPlainTarget(unrelatedGeneratedResourceTarget)) + // .addResource(unrelatedGeneratedResource) // Dropped. + .build()); + registry.put( + mock(Module.class), + AndroidResourceModule.builder(TargetKey.forPlainTarget(unrelatedSourceResourceTarget)) + .addResource(unrelatedSourceResource) + .build()); + + TargetMap targetMap = + TargetMapBuilder.builder() + .addTarget( + mockTargetIdeInfoBuilder() + .setLabel(mainResourcesTarget) + .setBuildFile(mainBuildFile) + .setAndroidInfo( + AndroidIdeInfo.builder() + .setGenerateResourceClass(true) + .addResource(mainGeneratedResource) + .addResource(mainSourceResource)) + .addDependency(dependencyGeneratedResourceTarget) + .addDependency(dependencySourceResourceTarget)) + .addTarget( + mockTargetIdeInfoBuilder() + .setLabel(dependencyGeneratedResourceTarget) + .setBuildFile(dependencyBuildFile) + .setAndroidInfo( + AndroidIdeInfo.builder() + .setGenerateResourceClass(true) + .addResource(dependencyGeneratedResource)) + .addDependency(transitiveSourceResourceTarget)) + .addTarget( + mockTargetIdeInfoBuilder() + .setLabel(dependencySourceResourceTarget) + .setBuildFile(dependencyBuildFile) + .setAndroidInfo( + AndroidIdeInfo.builder() + .setGenerateResourceClass(true) + .addResource(dependencySourceResource)) + .addDependency(transitiveGeneratedResourcesTarget)) + .addTarget( + mockTargetIdeInfoBuilder() + .setLabel(transitiveGeneratedResourcesTarget) + .setBuildFile(transitiveBuildFile) + .setAndroidInfo( + AndroidIdeInfo.builder() + .setGenerateResourceClass(true) + .addResource(transitiveGeneratedResourceOne) + .addResource(transitiveGeneratedResourceTwo))) + .addTarget( + mockTargetIdeInfoBuilder() + .setLabel(transitiveSourceResourceTarget) + .setBuildFile(transitiveBuildFile) + .setAndroidInfo( + AndroidIdeInfo.builder() + .setGenerateResourceClass(true) + .addResource(transitiveSourceResource))) + .addTarget( + mockTargetIdeInfoBuilder() + .setLabel(unrelatedGeneratedResourceTarget) + .setBuildFile(unrelatedBuildFile) + .setAndroidInfo( + AndroidIdeInfo.builder() + .setGenerateResourceClass(true) + .addResource(unrelatedGeneratedResource))) + .addTarget( + mockTargetIdeInfoBuilder() + .setLabel(unrelatedSourceResourceTarget) + .setBuildFile(unrelatedBuildFile) + .setAndroidInfo( + AndroidIdeInfo.builder() + .setGenerateResourceClass(true) + .addResource(unrelatedSourceResource))) + .build(); + + projectDataManager.setTargetMap(targetMap); + } + + private void createTargetMapWithNonStandardAndroidManifestName() { + Label mainResourceTarget = Label.create("//com/google/example:main"); + + ArtifactLocation mainManifest = artifact("com/google/example/main/WeirdManifest.xml", true); + ArtifactLocation mainResource = artifact("com/google/example/main/res", true); + ArtifactLocation mainBuildFile = artifact("com/google/example/main/BUILD", true); + + AndroidResourceModuleRegistry registry = AndroidResourceModuleRegistry.getInstance(project); + registry.put( + module, + AndroidResourceModule.builder(TargetKey.forPlainTarget(mainResourceTarget)) + .addResource(mainResource) + .build()); + + TargetMap targetMap = + TargetMapBuilder.builder() + .addTarget( + mockTargetIdeInfoBuilder() + .setLabel(mainResourceTarget) + .setBuildFile(mainBuildFile) + .setAndroidInfo( + AndroidIdeInfo.builder() + .setGenerateResourceClass(true) + .setManifestFile(mainManifest) + .addResource(mainResource))) + .build(); + + projectDataManager.setTargetMap(targetMap); + } + + private void createTargetMapWithNonStandardAndroidManifestNameInDependency() { + Label mainResourceTarget = Label.create("//com/google/example:main"); + Label dependencyResourceTarget = Label.create("//com/google/example:dependency"); + + ArtifactLocation mainManifest = artifact("com/google/example/main/AndroidManifest.xml", true); + ArtifactLocation mainResource = artifact("com/google/example/main/res", true); + ArtifactLocation mainBuildFile = artifact("com/google/example/main/BUILD", true); + + ArtifactLocation dependencyManifest = + artifact("com/google/example/dependency/MyManifest.xml", true); + ArtifactLocation dependencyResource = artifact("com/google/example/dependency/res", true); + ArtifactLocation dependencyBuildFile = artifact("com/google/example/dependency/BUILD", true); + + AndroidResourceModuleRegistry registry = AndroidResourceModuleRegistry.getInstance(project); + registry.put( + module, + AndroidResourceModule.builder(TargetKey.forPlainTarget(mainResourceTarget)) + .addResource(mainResource) + .addTransitiveResourceDependency(dependencyResourceTarget) + .addTransitiveResource(dependencyResource) + .build()); + registry.put( + mock(Module.class), + AndroidResourceModule.builder(TargetKey.forPlainTarget(dependencyResourceTarget)) + .addResource(dependencyResource) + .build()); + + TargetMap targetMap = + TargetMapBuilder.builder() + .addTarget( + mockTargetIdeInfoBuilder() + .setLabel(mainResourceTarget) + .setBuildFile(mainBuildFile) + .setAndroidInfo( + AndroidIdeInfo.builder() + .setGenerateResourceClass(true) + .setManifestFile(mainManifest) + .addResource(mainResource))) + .addTarget( + mockTargetIdeInfoBuilder() + .setLabel(dependencyResourceTarget) + .setBuildFile(dependencyBuildFile) + .setAndroidInfo( + AndroidIdeInfo.builder() + .setGenerateResourceClass(true) + .setManifestFile(dependencyManifest) + .addResource(dependencyResource))) + .build(); + + projectDataManager.setTargetMap(targetMap); + } + + private void createTargetMapWithMissingClassDependency() { + Label parentTarget = Label.create("//com/google/example:app"); + Label independentLibraryTarget = Label.create("//com/google/example/independent:library"); + Label independentLibrary2Target = Label.create("//com/google/example/independent:library2"); + Label dependentLibraryTarget = Label.create("//com/google/example/dependent:library"); + Label resourcesTarget = Label.create("//com/google/example:resources"); + + ArtifactLocation manifest = artifact("com/google/example/AndroidManifest.xml", true); + ArtifactLocation resources = artifact("com/google/example/res", true); + ArtifactLocation buildFile = artifact("com/google/example/BUILD", true); + + AndroidResourceModuleRegistry registry = AndroidResourceModuleRegistry.getInstance(project); + registry.put( + module, + AndroidResourceModule.builder(TargetKey.forPlainTarget(resourcesTarget)) + .addResource(resources) + .build()); + + TargetMap targetMap = + TargetMapBuilder.builder() + .addTarget( + mockTargetIdeInfoBuilder() + .setLabel(parentTarget) + .setBuildFile(buildFile) + .addDependency(independentLibraryTarget) + .addDependency(resourcesTarget)) + .addTarget( + mockTargetIdeInfoBuilder() + .setLabel(independentLibraryTarget) + .setBuildFile(buildFile) + .setJavaInfo(JavaIdeInfo.builder()) + .addDependency(independentLibrary2Target)) + .addTarget( + mockTargetIdeInfoBuilder() + .setLabel(independentLibrary2Target) + .setBuildFile(buildFile) + .setJavaInfo(JavaIdeInfo.builder())) + .addTarget( + mockTargetIdeInfoBuilder() + .setLabel(resourcesTarget) + .setBuildFile(buildFile) + .setAndroidInfo( + AndroidIdeInfo.builder() + .setGenerateResourceClass(true) + .setManifestFile(manifest) + .addResource(resources)) + .addDependency(dependentLibraryTarget)) + .addTarget( + mockTargetIdeInfoBuilder() + .setLabel(dependentLibraryTarget) + .setBuildFile(buildFile) + .setJavaInfo(JavaIdeInfo.builder())) + .build(); + + projectDataManager.setTargetMap(targetMap); + } + + private void createPsiClassesAndSourceToTargetMap(Container projectServices) { + VirtualFile independentLibraryView = + new MockVirtualFile("src/com/google/example/independent/LibraryView.java"); + VirtualFile independentLibraryView2 = + new MockVirtualFile("src/com/google/example/independent/LibraryView2.java"); + VirtualFile independentLibrary2View = + new MockVirtualFile("src/com/google/example/independent/Library2View.java"); + VirtualFile dependentLibraryView = + new MockVirtualFile("src/com/google/example/dependent/LibraryView.java"); + VirtualFile resourceView = new MockVirtualFile("src/com/google/example/ResourceView.java"); + + ImmutableMap sourceToTarget = + ImmutableMap.of( + VfsUtilCore.virtualToIoFile(independentLibraryView), + TargetKey.forPlainTarget(Label.create("//com/google/example/independent:library")), + VfsUtilCore.virtualToIoFile(independentLibraryView2), + TargetKey.forPlainTarget(Label.create("//com/google/example/independent:library")), + VfsUtilCore.virtualToIoFile(independentLibrary2View), + TargetKey.forPlainTarget(Label.create("//com/google/example/independent:library2")), + VfsUtilCore.virtualToIoFile(dependentLibraryView), + TargetKey.forPlainTarget(Label.create("//com/google/example/dependent:library")), + VfsUtilCore.virtualToIoFile(resourceView), + TargetKey.forPlainTarget(Label.create("//com/google/example:resources"))); + + /* b/145809318 + ImmutableMap classes = + ImmutableMap.of( + "com.google.example.independent.LibraryView", + mockPsiClass(independentLibraryView), + "com.google.example.independent.LibraryView2", + mockPsiClass(independentLibraryView2), + "com.google.example.independent.Library2View", + mockPsiClass(independentLibrary2View), + "com.google.example.dependent.LibraryView", + mockPsiClass(dependentLibraryView), + "com.google.example.ResourceView", + mockPsiClass(resourceView)); + + projectServices.register( + JavaPsiFacade.class, new MockJavaPsiFacade(project, classes)); + b/145809318 */ + projectServices.register(SourceToTargetMap.class, new MockSourceToTargetMap(sourceToTarget)); + } + + private static TargetIdeInfo.Builder mockTargetIdeInfoBuilder() { + return TargetIdeInfo.builder().setKind(AndroidBlazeRules.RuleTypes.ANDROID_LIBRARY.getKind()); + } + + /** + * b/145809318 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; } b/145809318 + */ + private static class MockBlazeProjectDataManager implements BlazeProjectDataManager { + private BlazeProjectData blazeProjectData; + + public void setTargetMap(TargetMap targetMap) { + ArtifactLocationDecoder decoder = + new MockArtifactLocationDecoder() { + @Override + public File decode(ArtifactLocation location) { + return new File("/src", location.getExecutionRootRelativePath()); + } + }; + this.blazeProjectData = + MockBlazeProjectDataBuilder.builder(workspaceRoot) + .setTargetMap(targetMap) + .setArtifactLocationDecoder(decoder) + .build(); + } + + @Nullable + @Override + public BlazeProjectData getBlazeProjectData() { + return blazeProjectData; + } + + @Nullable + @Override + public BlazeProjectData loadProject(BlazeImportSettings importSettings) { + throw new UnsupportedOperationException(); + } + + @Override + public void saveProject( + final BlazeImportSettings importSettings, final BlazeProjectData projectData) { + throw new UnsupportedOperationException(); + } + } + + private static class MockBuildReferenceManager extends BuildReferenceManager { + public MockBuildReferenceManager(Project project) { + super(project); + } + + @Nullable + @Override + public PsiElement resolveLabel(Label label) { + return null; + } + } + + private static class MockSourceToTargetMap implements SourceToTargetMap { + private ImmutableMap sourceToTarget; + + public MockSourceToTargetMap(ImmutableMap sourceToTarget) { + this.sourceToTarget = sourceToTarget; + } + + @Override + public ImmutableList