From 6da89c8c89568aaa95e1ca2e705561e640383460 Mon Sep 17 00:00:00 2001 From: Ben Lee Date: Tue, 23 Jun 2026 16:08:28 -0700 Subject: [PATCH 1/5] Migrate to hermetic Android toolchains --- .bazelrc | 3 ++ MODULE.bazel | 20 ++++++- README.md | 7 +++ examples/simple-android/.bazelrc | 3 ++ examples/simple-android/BUILD.bazel | 1 + examples/simple-android/MODULE.bazel | 18 ++++--- src/cli/AndroidLintRunner.kt | 4 +- tests/scripts/BUILD | 1 - .../android_lint_aar_scenarios_test.sh | 2 +- tests/scripts/lint_helper.sh | 43 +++++++++++---- tests/src/cli/AndroidLintActionArgsTest.kt | 3 ++ tests/src/cli/AndroidLintActionTest.kt | 52 ++++++++++++++++++- toolchains/BUILD.bazel | 1 + toolchains/toolchain.bzl | 2 +- 14 files changed, 135 insertions(+), 25 deletions(-) diff --git a/.bazelrc b/.bazelrc index 03e8eb5..7391598 100644 --- a/.bazelrc +++ b/.bazelrc @@ -7,4 +7,7 @@ common --java_runtime_version=remotejdk_17 common --tool_java_language_version=17 common --tool_java_runtime_version=remotejdk_17 +# Required by hermetic_android_toolchains for the pinned SDK version. +common --repo_env=ACCEPTED_ANDROID_SDK_LICENSE_VERSION=34 + try-import %workspace%/.bazelrc.user diff --git a/MODULE.bazel b/MODULE.bazel index 11bbb25..b4d307b 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -4,7 +4,8 @@ module( compatibility_level = 1, ) -bazel_dep(name = "rules_android", version = "0.7.1") +bazel_dep(name = "hermetic_android_toolchains", version = "0.1.1") +bazel_dep(name = "rules_android", version = "0.7.3") bazel_dep(name = "rules_java", version = "9.3.0") bazel_dep(name = "rules_jvm_external", version = "6.9") bazel_dep(name = "rules_kotlin", version = "2.2.2") @@ -31,6 +32,23 @@ bazel_binaries = use_extension( bazel_binaries.download(version_file = "//:.bazelversion") use_repo(bazel_binaries, "bazel_binaries", "bazel_binaries_bazelisk", "build_bazel_bazel_.bazelversion") +android = use_extension("@hermetic_android_toolchains//:extensions.bzl", "android") +android.sdk( + build_tools_version = "35.0.0", + version = "34", +) +android.ndk(version = "r25c") +use_repo(android, "androidsdk") + +rules_android_sdk = use_extension( + "@rules_android//rules/android_sdk_repository:rule.bzl", + "android_sdk_repository_extension", +) + +override_repo(rules_android_sdk, "androidsdk") + +register_toolchains("@androidsdk//:all") + register_toolchains("//toolchains:android_lint_default_toolchain") # TODO(bencodes) Lint needs to be downloaded dynamically diff --git a/README.md b/README.md index bc2f1d7..430b6e8 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,13 @@ common --java_runtime_version=remotejdk_17 common --tool_java_runtime_version=remotejdk_17 ``` +The default lint toolchain uses `hermetic_android_toolchains` to provide the Android SDK +through Bazel. Accept the pinned SDK license in your `.bazelrc`: + +``` +common --repo_env=ACCEPTED_ANDROID_SDK_LICENSE_VERSION=34 +``` + ### Using a different Android Lint version The lint binary is supplied by the toolchain. To pin your own version, build a deploy jar diff --git a/examples/simple-android/.bazelrc b/examples/simple-android/.bazelrc index 1bcfb7a..83cfa05 100644 --- a/examples/simple-android/.bazelrc +++ b/examples/simple-android/.bazelrc @@ -8,3 +8,6 @@ common --java_language_version=17 common --java_runtime_version=remotejdk_17 common --tool_java_language_version=17 common --tool_java_runtime_version=remotejdk_17 + +# Required by hermetic_android_toolchains for the pinned SDK version. +common --repo_env=ACCEPTED_ANDROID_SDK_LICENSE_VERSION=34 diff --git a/examples/simple-android/BUILD.bazel b/examples/simple-android/BUILD.bazel index b7794f7..71b82d8 100644 --- a/examples/simple-android/BUILD.bazel +++ b/examples/simple-android/BUILD.bazel @@ -30,6 +30,7 @@ android_lint_test( android_lint_toolchain( name = "android_lint_default", + android_home = "@androidsdk//:files", android_lint_config = "lint_global.xml", visibility = ["//visibility:public"], ) diff --git a/examples/simple-android/MODULE.bazel b/examples/simple-android/MODULE.bazel index 9e868bc..71aa3ce 100644 --- a/examples/simple-android/MODULE.bazel +++ b/examples/simple-android/MODULE.bazel @@ -11,20 +11,24 @@ local_path_override( path = "../../", ) +bazel_dep(name = "hermetic_android_toolchains", version = "0.1.1") bazel_dep(name = "platforms", version = "1.1.0") bazel_dep(name = "rules_android", version = "0.7.3") bazel_dep(name = "rules_jvm_external", version = "7.0") -remote_android_extensions = use_extension( - "@rules_android//bzlmod_extensions:android_extensions.bzl", - "remote_android_tools_extensions", +android = use_extension("@hermetic_android_toolchains//:extensions.bzl", "android") +android.sdk( + build_tools_version = "35.0.0", + version = "34", ) -use_repo(remote_android_extensions, "android_tools") +android.ndk(version = "r25c") +use_repo(android, "androidsdk") -android_sdk_repository_extension = use_extension("@rules_android//rules/android_sdk_repository:rule.bzl", "android_sdk_repository_extension") -use_repo(android_sdk_repository_extension, "androidsdk") +rules_android_sdk = use_extension("@rules_android//rules/android_sdk_repository:rule.bzl", "android_sdk_repository_extension") -register_toolchains("@androidsdk//:sdk-toolchain", "@androidsdk//:all") +override_repo(rules_android_sdk, "androidsdk") + +register_toolchains("@androidsdk//:all") maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven") maven.install( diff --git a/src/cli/AndroidLintRunner.kt b/src/cli/AndroidLintRunner.kt index 31d7b7f..4a7ad42 100644 --- a/src/cli/AndroidLintRunner.kt +++ b/src/cli/AndroidLintRunner.kt @@ -128,8 +128,8 @@ internal class AndroidLintRunner( args.add(actionArgs.disableChecks.joinToString(",")) } - if (actionArgs.androidHome?.isNotEmpty() != null) { - var androidHomePath = + if (actionArgs.androidHome?.isNotEmpty() == true) { + val androidHomePath = Paths.get(System.getenv("PWD"), actionArgs.androidHome).absolutePathString() args.add("--sdk-home") args.add(androidHomePath) diff --git a/tests/scripts/BUILD b/tests/scripts/BUILD index 831aa57..c338119 100644 --- a/tests/scripts/BUILD +++ b/tests/scripts/BUILD @@ -43,7 +43,6 @@ script_test( name = "android_lint_aar_scenarios_test", size = "enormous", srcs = ["android_lint_aar_scenarios_test.sh"], - additional_env_inherit = ["ANDROID_HOME"], bazel_binaries = bazel_binaries, bazel_version = bazel_binaries.versions.current, tags = ["exclusive"], diff --git a/tests/scripts/android_lint_aar_scenarios_test.sh b/tests/scripts/android_lint_aar_scenarios_test.sh index 03b3000..02b1fe8 100755 --- a/tests/scripts/android_lint_aar_scenarios_test.sh +++ b/tests/scripts/android_lint_aar_scenarios_test.sh @@ -1,7 +1,7 @@ #!/bin/bash # # Scenarios for custom lint checks embedded in AAR dependencies (lint.jar), exercising the -# collect_aar_outputs_aspect extraction and auto-discovery path. Requires ANDROID_HOME. +# collect_aar_outputs_aspect extraction and auto-discovery path. # --- begin runfiles.bash initialization v2 --- # Copy-pasted from the Bazel Bash runfiles library v2. diff --git a/tests/scripts/lint_helper.sh b/tests/scripts/lint_helper.sh index e991ec7..e42f29b 100755 --- a/tests/scripts/lint_helper.sh +++ b/tests/scripts/lint_helper.sh @@ -29,12 +29,28 @@ function set_up_lint_workspace() { cat > "${dest}/MODULE.bazel" <> MODULE.bazel <> .bazelrc <>>>>>> 46680f4 (Migrate to hermetic Android toolchains) private fun workRequestArgs( lintJar: Path, label: String, + extraArgs: List = emptyList(), ): List { val output = temporaryFolder.root.toPath().resolve("$label-output.xml") return listOf( @@ -76,7 +126,7 @@ class AndroidLintActionTest { "17", "--kotlin-language-level", "1.9", - ) + ) + extraArgs } private fun writeStubLintJar(): Path { diff --git a/toolchains/BUILD.bazel b/toolchains/BUILD.bazel index aa76baa..9a2fe2c 100644 --- a/toolchains/BUILD.bazel +++ b/toolchains/BUILD.bazel @@ -8,6 +8,7 @@ toolchain_type( android_lint_toolchain( name = "android_lint_default", + android_home = "@androidsdk//:files", visibility = ["//visibility:public"], ) diff --git a/toolchains/toolchain.bzl b/toolchains/toolchain.bzl index 8a87987..0c81e4e 100644 --- a/toolchains/toolchain.bzl +++ b/toolchains/toolchain.bzl @@ -42,7 +42,7 @@ _ATTRS = dict( ), android_home = attr.label( mandatory = False, - doc = "The target that represents where the ANDROID_HOME is located.", + doc = "A target in the Android SDK repository. Its repository root is passed to lint as the SDK home.", ), ) From bb32298a2e2ca31bd9fbef450ced423b9ef13aeb Mon Sep 17 00:00:00 2001 From: Ben Lee Date: Fri, 26 Jun 2026 13:15:26 -0700 Subject: [PATCH 2/5] More --- MODULE.bazel | 12 ++++++++---- tests/src/cli/AndroidLintActionTest.kt | 11 +++++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/MODULE.bazel b/MODULE.bazel index b4d307b..1c0b413 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -4,7 +4,7 @@ module( compatibility_level = 1, ) -bazel_dep(name = "hermetic_android_toolchains", version = "0.1.1") +bazel_dep(name = "hermetic_android_toolchains", version = "0.1.1", dev_dependency = True) bazel_dep(name = "rules_android", version = "0.7.3") bazel_dep(name = "rules_java", version = "9.3.0") bazel_dep(name = "rules_jvm_external", version = "6.9") @@ -32,7 +32,11 @@ bazel_binaries = use_extension( bazel_binaries.download(version_file = "//:.bazelversion") use_repo(bazel_binaries, "bazel_binaries", "bazel_binaries_bazelisk", "build_bazel_bazel_.bazelversion") -android = use_extension("@hermetic_android_toolchains//:extensions.bzl", "android") +android = use_extension( + "@hermetic_android_toolchains//:extensions.bzl", + "android", + dev_dependency = True, +) android.sdk( build_tools_version = "35.0.0", version = "34", @@ -47,9 +51,9 @@ rules_android_sdk = use_extension( override_repo(rules_android_sdk, "androidsdk") -register_toolchains("@androidsdk//:all") +register_toolchains("@androidsdk//:all", dev_dependency = True) -register_toolchains("//toolchains:android_lint_default_toolchain") +register_toolchains("//toolchains:android_lint_default_toolchain", dev_dependency = True) # TODO(bencodes) Lint needs to be downloaded dynamically maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven") diff --git a/tests/src/cli/AndroidLintActionTest.kt b/tests/src/cli/AndroidLintActionTest.kt index e149243..25dc6db 100644 --- a/tests/src/cli/AndroidLintActionTest.kt +++ b/tests/src/cli/AndroidLintActionTest.kt @@ -139,3 +139,14 @@ class AndroidLintActionTest { return jar } } + +private fun List.argumentAfter(argument: String): String { + val argumentIndex = indexOf(argument) + assertThat(argumentIndex) + .describedAs("argument index for %s in %s", argument, this) + .isGreaterThanOrEqualTo(0) + assertThat(argumentIndex + 1) + .describedAs("value index for %s in %s", argument, this) + .isLessThan(size) + return this[argumentIndex + 1] +} From 03939a40942160ebd8887266940ff726bcb6f980 Mon Sep 17 00:00:00 2001 From: Ben Lee Date: Sat, 13 Jun 2026 14:20:53 -0700 Subject: [PATCH 3/5] Sandbox Android Home --- src/cli/AndroidLintRunner.kt | 4 ++++ tests/src/cli/AndroidLintActionTest.kt | 3 --- tests/src/cli/stubs/Main.kt | 10 +++++++++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/cli/AndroidLintRunner.kt b/src/cli/AndroidLintRunner.kt index 4a7ad42..4b5658f 100644 --- a/src/cli/AndroidLintRunner.kt +++ b/src/cli/AndroidLintRunner.kt @@ -52,9 +52,13 @@ internal class AndroidLintRunner( // Run Android Lint val androidCacheFolder = workingDirectory.resolve("android-cache") Files.createDirectory(androidCacheFolder) + val lintUserHomeFolder = workingDirectory.resolve("lint-user-home") + Files.createDirectory(lintUserHomeFolder) + val invoker = invokerCache.acquire(listOf(args.androidLintCliTool)) val exitCode = try { + System.setProperty("user.home", lintUserHomeFolder.toString()) invokeAndroidLintCLI( invoker = invoker, actionArgs = args, diff --git a/tests/src/cli/AndroidLintActionTest.kt b/tests/src/cli/AndroidLintActionTest.kt index 25dc6db..b75181c 100644 --- a/tests/src/cli/AndroidLintActionTest.kt +++ b/tests/src/cli/AndroidLintActionTest.kt @@ -59,8 +59,6 @@ class AndroidLintActionTest { assertThat(cache.createdCount).isEqualTo(1) } -<<<<<<< HEAD -======= @Test fun `lint runs with an invocation-scoped user home`() { val cache = AndroidLintCliInvokerCache() @@ -106,7 +104,6 @@ class AndroidLintActionTest { assertThat(sdkHome).isEqualTo(rootDir.resolve("external/androidsdk").toString()) } ->>>>>>> 46680f4 (Migrate to hermetic Android toolchains) private fun workRequestArgs( lintJar: Path, label: String, diff --git a/tests/src/cli/stubs/Main.kt b/tests/src/cli/stubs/Main.kt index 0a07b13..04d6bd7 100644 --- a/tests/src/cli/stubs/Main.kt +++ b/tests/src/cli/stubs/Main.kt @@ -15,7 +15,14 @@ class Main { fun run(args: Array): Int { runBarrier?.await(5, TimeUnit.SECONDS) - recordedRuns.add(RunRecord(this, args.toList(), flags.checkDependencies)) + recordedRuns.add( + RunRecord( + instance = this, + args = args.toList(), + checkDependencies = flags.checkDependencies, + userHome = System.getProperty("user.home"), + ), + ) return exitCode } @@ -23,6 +30,7 @@ class Main { val instance: Main, val args: List, val checkDependencies: Boolean, + val userHome: String?, ) companion object { From 40fd42010d856ff9afc4a9a97012b0a067133bbd Mon Sep 17 00:00:00 2001 From: Ben Lee Date: Fri, 26 Jun 2026 14:18:34 -0700 Subject: [PATCH 4/5] More --- tests/scripts/lint_helper.sh | 72 ++++++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 27 deletions(-) diff --git a/tests/scripts/lint_helper.sh b/tests/scripts/lint_helper.sh index e42f29b..8dda4af 100755 --- a/tests/scripts/lint_helper.sh +++ b/tests/scripts/lint_helper.sh @@ -29,14 +29,18 @@ function set_up_lint_workspace() { cat > "${dest}/MODULE.bazel" < "${dest}/src/BUILD.bazel" < MODULE.bazel < toolchains/BUILD.bazel < .bazelrc <> MODULE.bazel <> .bazelrc < Date: Fri, 26 Jun 2026 14:29:24 -0700 Subject: [PATCH 5/5] More --- MODULE.bazel | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/MODULE.bazel b/MODULE.bazel index 1c0b413..8c17981 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -5,6 +5,7 @@ module( ) bazel_dep(name = "hermetic_android_toolchains", version = "0.1.1", dev_dependency = True) + bazel_dep(name = "rules_android", version = "0.7.3") bazel_dep(name = "rules_java", version = "9.3.0") bazel_dep(name = "rules_jvm_external", version = "6.9") @@ -51,9 +52,15 @@ rules_android_sdk = use_extension( override_repo(rules_android_sdk, "androidsdk") -register_toolchains("@androidsdk//:all", dev_dependency = True) +register_toolchains( + "@androidsdk//:all", + dev_dependency = True, +) -register_toolchains("//toolchains:android_lint_default_toolchain", dev_dependency = True) +register_toolchains( + "//toolchains:android_lint_default_toolchain", + dev_dependency = True, +) # TODO(bencodes) Lint needs to be downloaded dynamically maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")