From 7f339415e4416fb1e4f5e95ca847428ded20ff15 Mon Sep 17 00:00:00 2001 From: Ben Lee Date: Tue, 9 Jun 2026 14:46:24 -0700 Subject: [PATCH 1/2] Add more CLI tests + classpath isolation --- src/cli/AndroidLintAction.kt | 6 +- src/cli/AndroidLintCliInvoker.kt | 65 ++++--- src/cli/AndroidLintCliInvokerCache.kt | 82 +++++++++ src/cli/AndroidLintRunner.kt | 32 ++-- tests/src/cli/AndroidLintActionTest.kt | 82 ++++++++- .../src/cli/AndroidLintCliInvokerCacheTest.kt | 158 ++++++++++++++++++ tests/src/cli/BUILD | 26 ++- tests/src/cli/stubs/BUILD | 8 + tests/src/cli/stubs/LintCliFlags.kt | 6 + tests/src/cli/stubs/Main.kt | 44 +++++ tests/src/cli/stubs/UastEnvironment.kt | 15 ++ 11 files changed, 482 insertions(+), 42 deletions(-) create mode 100644 src/cli/AndroidLintCliInvokerCache.kt create mode 100644 tests/src/cli/AndroidLintCliInvokerCacheTest.kt create mode 100644 tests/src/cli/stubs/BUILD create mode 100644 tests/src/cli/stubs/LintCliFlags.kt create mode 100644 tests/src/cli/stubs/Main.kt create mode 100644 tests/src/cli/stubs/UastEnvironment.kt diff --git a/src/cli/AndroidLintAction.kt b/src/cli/AndroidLintAction.kt index 47addfe..323705e 100644 --- a/src/cli/AndroidLintAction.kt +++ b/src/cli/AndroidLintAction.kt @@ -13,7 +13,9 @@ object AndroidLintAction { exitProcess(exitCode) } - private class AndroidLintExecutor : Worker.WorkRequestCallback { + internal class AndroidLintExecutor( + private val invokerCache: AndroidLintCliInvokerCache = AndroidLintCliInvokerCache(), + ) : Worker.WorkRequestCallback { override fun processWorkRequest( args: List, printStream: PrintStream, @@ -21,7 +23,7 @@ object AndroidLintAction { val workingDirectory = Files.createTempDirectory("rules") try { - val runner = AndroidLintRunner() + val runner = AndroidLintRunner(invokerCache) val parsedArgs = AndroidLintActionArgs.parseArgs(args) runner.runAndroidLint(parsedArgs, workingDirectory) return 0 diff --git a/src/cli/AndroidLintCliInvoker.kt b/src/cli/AndroidLintCliInvoker.kt index 0c87926..2d2ceaf 100644 --- a/src/cli/AndroidLintCliInvoker.kt +++ b/src/cli/AndroidLintCliInvoker.kt @@ -1,5 +1,6 @@ package com.rules.android.lint.cli +import java.lang.reflect.InvocationTargetException import java.net.URL import java.net.URLClassLoader import java.nio.file.Path @@ -8,20 +9,17 @@ import kotlin.io.path.isRegularFile import kotlin.io.path.pathString class AndroidLintCliInvoker( - classLoader: ClassLoader, + internal val classLoader: ClassLoader, ) { private val mainClass = Class.forName("com.android.tools.lint.Main", true, classLoader) - private val cliFlags = classLoader.loadClass("com.android.tools.lint.LintCliFlags") - private val mainInstance = - mainClass - .getDeclaredConstructor() - .newInstance() - private val flagsInstance = + private val cliFlagsClass = classLoader.loadClass("com.android.tools.lint.LintCliFlags") + private val runMethod = mainClass.getDeclaredMethod("run", Array::class.java) + private val flagsField = mainClass .getDeclaredField("flags") - .apply { - isAccessible = true - }.get(mainInstance) + .apply { isAccessible = true } + private val setCheckDependenciesMethod = + cliFlagsClass.getDeclaredMethod("setCheckDependencies", Boolean::class.java) /* * Exit Status: @@ -34,18 +32,45 @@ class AndroidLintCliInvoker( * 6 -> Created baseline file. * 100 -> Internal continue. */ - fun invoke(args: Array): Int { - val runMethod = mainClass.getDeclaredMethod("run", Array::class.java) - return runMethod.invoke(mainInstance, args) as Int + fun invoke( + args: Array, + enableCheckDependencies: Boolean = false, + ): Int { + // A fresh Main per invocation: Main carries per-run flag state, and the cached invoker may + // serve concurrent work requests in a multiplex worker. + val mainInstance = mainClass.getDeclaredConstructor().newInstance() + setCheckDependenciesMethod.invoke(flagsField.get(mainInstance), enableCheckDependencies) + + // Null out the context classloader while lint runs so service loading inside lint cannot + // resolve against the worker's own classpath. + val previousContextClassLoader = Thread.currentThread().contextClassLoader + return try { + Thread.currentThread().contextClassLoader = null + runMethod.invoke(mainInstance, args) as Int + } catch (exception: InvocationTargetException) { + throw exception.targetException + } finally { + Thread.currentThread().contextClassLoader = previousContextClassLoader + } } - fun setCheckDependencies(enableCheckDependencies: Boolean) { - val setCheckDependenciesMethod = - cliFlags.getDeclaredMethod( - "setCheckDependencies", - Boolean::class.java, - ) - setCheckDependenciesMethod.invoke(flagsInstance, enableCheckDependencies) + /** + * Releases the resources held by this invoker's classloader. + * + * Lint keeps a JVM-wide IntelliJ application environment alive per classloader. Without + * disposing it, every evicted classloader leaks metaspace for the lifetime of the worker. + */ + fun dispose() { + try { + classLoader + .loadClass("com.android.tools.lint.UastEnvironment") + .getDeclaredMethod("disposeApplicationEnvironment") + .invoke(null) + } catch (exception: ReflectiveOperationException) { + // Best-effort cleanup; never fail eviction because the jar lacks UastEnvironment. + } finally { + (classLoader as? URLClassLoader)?.close() + } } companion object { diff --git a/src/cli/AndroidLintCliInvokerCache.kt b/src/cli/AndroidLintCliInvokerCache.kt new file mode 100644 index 0000000..e5cda61 --- /dev/null +++ b/src/cli/AndroidLintCliInvokerCache.kt @@ -0,0 +1,82 @@ +package com.rules.android.lint.cli + +import java.nio.file.Files +import java.nio.file.Path + +/** + * Caches the [AndroidLintCliInvoker] across work requests so a persistent worker pays lint's + * classloading and UAST application-environment warm-up cost once, not once per request. + * + * The cache key is derived from each jar's real path, size, and mtime rather than its content + * digest: hashing the multi-hundred-megabyte lint deploy jar on every request would claw back + * much of the win, and a lint version change always changes the jar size in practice. + * + * Eviction is lease-aware: a multiplex worker can receive a request built against a new lint jar + * while a request against the old jar is still running, so the old classloader is only disposed + * once its last lease is released. + */ +internal class AndroidLintCliInvokerCache { + private class Entry( + val key: String, + val invoker: AndroidLintCliInvoker, + var activeLeases: Int, + ) + + private var current: Entry? = null + private val evictedWithLeases = mutableListOf() + + /** Number of invokers (classloaders) created over the lifetime of this cache. */ + internal var createdCount: Int = 0 + private set + + @Synchronized + internal fun acquire(jars: List): AndroidLintCliInvoker { + val key = cacheKey(jars) + current?.let { entry -> + if (entry.key == key) { + entry.activeLeases += 1 + return entry.invoker + } + if (entry.activeLeases > 0) { + evictedWithLeases.add(entry) + } else { + entry.invoker.dispose() + } + current = null + } + val invoker = AndroidLintCliInvoker.createUsingJars(jars = jars.toTypedArray()) + createdCount += 1 + current = Entry(key = key, invoker = invoker, activeLeases = 1) + return invoker + } + + @Synchronized + internal fun release(invoker: AndroidLintCliInvoker) { + current?.let { entry -> + if (entry.invoker === invoker) { + entry.activeLeases -= 1 + return + } + } + val iterator = evictedWithLeases.iterator() + while (iterator.hasNext()) { + val entry = iterator.next() + if (entry.invoker === invoker) { + entry.activeLeases -= 1 + if (entry.activeLeases <= 0) { + iterator.remove() + entry.invoker.dispose() + } + return + } + } + } + + private fun cacheKey(jars: List): String = + jars.joinToString(separator = "|") { jar -> + val realPath = jar.toRealPath() + val size = Files.size(realPath) + val lastModified = Files.getLastModifiedTime(realPath).toMillis() + "$realPath:$size:$lastModified" + } +} diff --git a/src/cli/AndroidLintRunner.kt b/src/cli/AndroidLintRunner.kt index eff01ea..9a7b4e3 100644 --- a/src/cli/AndroidLintRunner.kt +++ b/src/cli/AndroidLintRunner.kt @@ -9,7 +9,9 @@ import kotlin.io.path.isRegularFile import kotlin.io.path.pathString import kotlin.io.path.writeText -internal class AndroidLintRunner { +internal class AndroidLintRunner( + private val invokerCache: AndroidLintCliInvokerCache, +) { internal fun runAndroidLint( args: AndroidLintActionArgs, workingDirectory: Path, @@ -50,16 +52,20 @@ internal class AndroidLintRunner { // Run Android Lint val androidCacheFolder = workingDirectory.resolve("android-cache") Files.createDirectory(androidCacheFolder) - val invoker = AndroidLintCliInvoker.createUsingJars(jars = arrayOf(args.androidLintCliTool)) + val invoker = invokerCache.acquire(listOf(args.androidLintCliTool)) val exitCode = - invokeAndroidLintCLI( - invoker = invoker, - actionArgs = args, - rootDirPath = rootDir, - projectFilePath = projectFile, - baselineFilePath = baselineFile, - cacheDirectoryPath = androidCacheFolder, - ) + try { + invokeAndroidLintCLI( + invoker = invoker, + actionArgs = args, + rootDirPath = rootDir, + projectFilePath = projectFile, + baselineFilePath = baselineFile, + cacheDirectoryPath = androidCacheFolder, + ) + } finally { + invokerCache.release(invoker) + } return when (exitCode) { AndroidLintCliInvoker.ERRNO_SUCCESS, @@ -135,7 +141,9 @@ internal class AndroidLintRunner { args.add(jdkHome.absolutePathString()) } - invoker.setCheckDependencies(actionArgs.enableCheckDependencies) - return invoker.invoke(args.toTypedArray()) + return invoker.invoke( + args = args.toTypedArray(), + enableCheckDependencies = actionArgs.enableCheckDependencies, + ) } } diff --git a/tests/src/cli/AndroidLintActionTest.kt b/tests/src/cli/AndroidLintActionTest.kt index e891def..f10e2fd 100644 --- a/tests/src/cli/AndroidLintActionTest.kt +++ b/tests/src/cli/AndroidLintActionTest.kt @@ -1,13 +1,91 @@ package com.rules.android.lint.cli +import com.android.tools.lint.Main +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.Rule import org.junit.Test +import org.junit.rules.TemporaryFolder import org.junit.runner.RunWith import org.junit.runners.JUnit4 +import java.nio.file.Files +import java.nio.file.Path +import java.util.jar.JarEntry +import java.util.jar.JarOutputStream @RunWith(JUnit4::class) class AndroidLintActionTest { + @get:Rule + val temporaryFolder = TemporaryFolder() + + @Before + fun setUp() { + Main.reset() + } + + @Test + fun `sequential work requests reuse a single cached lint classloader`() { + val cache = AndroidLintCliInvokerCache() + val executor = AndroidLintAction.AndroidLintExecutor(cache) + val jar = writeStubLintJar() + + val firstExitCode = executor.processWorkRequest(workRequestArgs(jar, "first"), System.err) + val secondExitCode = executor.processWorkRequest(workRequestArgs(jar, "second"), System.err) + + assertThat(firstExitCode).isEqualTo(0) + assertThat(secondExitCode).isEqualTo(0) + assertThat(Main.recordedRuns).hasSize(2) + assertThat(cache.createdCount).isEqualTo(1) + } + @Test - fun `empty test case`() { - // TODO(bencodes) Add some tests for AndroidLintAction + fun `lint issue exit codes do not fail the work request or evict the cache`() { + // By design the action always succeeds and produces a report; enforcement happens in the + // android_lint_test validator. A lint exit code must not kill or degrade the worker. + val cache = AndroidLintCliInvokerCache() + val executor = AndroidLintAction.AndroidLintExecutor(cache) + val jar = writeStubLintJar() + + Main.exitCode = AndroidLintCliInvoker.ERRNO_ERRORS + val errorsExitCode = executor.processWorkRequest(workRequestArgs(jar, "failing"), System.err) + + Main.exitCode = AndroidLintCliInvoker.ERRNO_SUCCESS + val successExitCode = executor.processWorkRequest(workRequestArgs(jar, "passing"), System.err) + + assertThat(errorsExitCode).isEqualTo(0) + assertThat(successExitCode).isEqualTo(0) + assertThat(Main.recordedRuns).hasSize(2) + assertThat(cache.createdCount).isEqualTo(1) + } + + private fun workRequestArgs( + lintJar: Path, + label: String, + ): List { + val output = temporaryFolder.root.toPath().resolve("$label-output.xml") + return listOf( + "--android-lint-cli-tool", + lintJar.toString(), + "--label", + label, + "--output", + output.toString(), + "--compile-sdk-version", + "34", + "--java-language-level", + "17", + "--kotlin-language-level", + "1.9", + ) + } + + private fun writeStubLintJar(): Path { + val jar = temporaryFolder.newFile("lint.jar").toPath() + JarOutputStream(Files.newOutputStream(jar)).use { stream -> + stream.putNextEntry(JarEntry("stub.txt")) + stream.write("stub".toByteArray()) + stream.closeEntry() + } + return jar } } diff --git a/tests/src/cli/AndroidLintCliInvokerCacheTest.kt b/tests/src/cli/AndroidLintCliInvokerCacheTest.kt new file mode 100644 index 0000000..b97c319 --- /dev/null +++ b/tests/src/cli/AndroidLintCliInvokerCacheTest.kt @@ -0,0 +1,158 @@ +package com.rules.android.lint.cli + +import com.android.tools.lint.Main +import com.android.tools.lint.UastEnvironment +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.attribute.FileTime +import java.util.concurrent.CyclicBarrier +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit +import java.util.jar.JarEntry +import java.util.jar.JarOutputStream + +@RunWith(JUnit4::class) +class AndroidLintCliInvokerCacheTest { + @get:Rule + val temporaryFolder = TemporaryFolder() + + @Before + fun setUp() { + Main.reset() + } + + @Test + fun `acquiring twice with an unchanged jar reuses the invoker`() { + val cache = AndroidLintCliInvokerCache() + val jar = writeJar("lint.jar", entryCount = 1) + + val first = cache.acquire(listOf(jar)) + cache.release(first) + val second = cache.acquire(listOf(jar)) + cache.release(second) + + assertThat(second).isSameAs(first) + assertThat(cache.createdCount).isEqualTo(1) + } + + @Test + fun `a changed jar evicts the cached invoker and disposes it`() { + val cache = AndroidLintCliInvokerCache() + val jar = writeJar("lint.jar", entryCount = 1) + + val first = cache.acquire(listOf(jar)) + cache.release(first) + + val disposesBefore = UastEnvironment.disposeCount.get() + rewriteJar(jar, entryCount = 2) + val second = cache.acquire(listOf(jar)) + cache.release(second) + + assertThat(second).isNotSameAs(first) + assertThat(cache.createdCount).isEqualTo(2) + assertThat(UastEnvironment.disposeCount.get()).isEqualTo(disposesBefore + 1) + } + + @Test + fun `eviction defers disposal until the last lease is released`() { + val cache = AndroidLintCliInvokerCache() + val jar = writeJar("lint.jar", entryCount = 1) + + val first = cache.acquire(listOf(jar)) + + val disposesBefore = UastEnvironment.disposeCount.get() + rewriteJar(jar, entryCount = 2) + val second = cache.acquire(listOf(jar)) + + // First invoker is evicted but still leased, so it must not be disposed yet. + assertThat(UastEnvironment.disposeCount.get()).isEqualTo(disposesBefore) + + cache.release(first) + assertThat(UastEnvironment.disposeCount.get()).isEqualTo(disposesBefore + 1) + + cache.release(second) + assertThat(UastEnvironment.disposeCount.get()).isEqualTo(disposesBefore + 1) + } + + @Test + fun `invoke creates a fresh Main per call and applies checkDependencies per call`() { + val cache = AndroidLintCliInvokerCache() + val jar = writeJar("lint.jar", entryCount = 1) + val invoker = cache.acquire(listOf(jar)) + + val exitCode = invoker.invoke(args = arrayOf("--first"), enableCheckDependencies = true) + invoker.invoke(args = arrayOf("--second"), enableCheckDependencies = false) + cache.release(invoker) + + assertThat(exitCode).isEqualTo(AndroidLintCliInvoker.ERRNO_SUCCESS) + assertThat(Main.recordedRuns).hasSize(2) + val (firstRun, secondRun) = Main.recordedRuns + assertThat(firstRun.args).containsExactly("--first") + assertThat(firstRun.checkDependencies).isTrue() + assertThat(secondRun.args).containsExactly("--second") + assertThat(secondRun.checkDependencies).isFalse() + assertThat(firstRun.instance).isNotSameAs(secondRun.instance) + } + + @Test + fun `concurrent invocations share one classloader with independent Main instances`() { + val cache = AndroidLintCliInvokerCache() + val jar = writeJar("lint.jar", entryCount = 1) + // Both runs must be in flight at the same time before either completes. + Main.runBarrier = CyclicBarrier(2) + + val executor = Executors.newFixedThreadPool(2) + try { + val futures = + (1..2).map { index -> + executor.submit { + val invoker = cache.acquire(listOf(jar)) + try { + invoker.invoke(args = arrayOf("--request-$index"), enableCheckDependencies = false) + } finally { + cache.release(invoker) + } + } + } + futures.forEach { assertThat(it.get(10, TimeUnit.SECONDS)).isEqualTo(0) } + } finally { + executor.shutdownNow() + } + + assertThat(cache.createdCount).isEqualTo(1) + assertThat(Main.recordedRuns).hasSize(2) + assertThat(Main.recordedRuns[0].instance).isNotSameAs(Main.recordedRuns[1].instance) + } + + private fun writeJar( + name: String, + entryCount: Int, + ): Path { + val jar = temporaryFolder.newFile(name).toPath() + rewriteJar(jar, entryCount) + return jar + } + + private fun rewriteJar( + jar: Path, + entryCount: Int, + ) { + JarOutputStream(Files.newOutputStream(jar)).use { stream -> + repeat(entryCount) { index -> + stream.putNextEntry(JarEntry("entry-$index.txt")) + stream.write("content-$index".toByteArray()) + stream.closeEntry() + } + } + // Force a key change even on filesystems with coarse mtime resolution. + val currentTime = Files.getLastModifiedTime(jar).toMillis() + Files.setLastModifiedTime(jar, FileTime.fromMillis(currentTime + 2_000)) + } +} diff --git a/tests/src/cli/BUILD b/tests/src/cli/BUILD index 48ebffc..168711f 100644 --- a/tests/src/cli/BUILD +++ b/tests/src/cli/BUILD @@ -8,8 +8,8 @@ kt_jvm_test( associates = ["//src/cli:lint"], test_class = "com.rules.android.lint.cli.AndroidLintActionArgsTest", deps = [ - "@rules_android_lint_deps//:junit_junit", - "@rules_android_lint_deps//:org_assertj_assertj_core", + "@rules_android_lint_test_deps//:junit_junit", + "@rules_android_lint_test_deps//:org_assertj_assertj_core", ], ) @@ -26,8 +26,21 @@ kt_jvm_test( associates = ["//src/cli:lint"], test_class = "com.rules.android.lint.cli.AndroidLintActionTest", deps = [ - "@rules_android_lint_deps//:junit_junit", - "@rules_android_lint_deps//:org_assertj_assertj_core", + "//tests/src/cli/stubs", + "@rules_android_lint_test_deps//:junit_junit", + "@rules_android_lint_test_deps//:org_assertj_assertj_core", + ], +) + +kt_jvm_test( + name = "AndroidLintCliInvokerCacheTest", + srcs = ["AndroidLintCliInvokerCacheTest.kt"], + associates = ["//src/cli:lint"], + test_class = "com.rules.android.lint.cli.AndroidLintCliInvokerCacheTest", + deps = [ + "//tests/src/cli/stubs", + "@rules_android_lint_test_deps//:junit_junit", + "@rules_android_lint_test_deps//:org_assertj_assertj_core", ], ) @@ -44,8 +57,8 @@ kt_jvm_test( associates = ["//src/cli:lint"], test_class = "com.rules.android.lint.cli.AndroidLintProjectTest", deps = [ - "@rules_android_lint_deps//:junit_junit", - "@rules_android_lint_deps//:org_assertj_assertj_core", + "@rules_android_lint_test_deps//:junit_junit", + "@rules_android_lint_test_deps//:org_assertj_assertj_core", ], ) @@ -75,6 +88,7 @@ test_suite( tests = [ ":AndroidLintActionArgsTest", ":AndroidLintActionTest", + ":AndroidLintCliInvokerCacheTest", ":AndroidLintProjectTest", ], ) diff --git a/tests/src/cli/stubs/BUILD b/tests/src/cli/stubs/BUILD new file mode 100644 index 0000000..69caf4f --- /dev/null +++ b/tests/src/cli/stubs/BUILD @@ -0,0 +1,8 @@ +load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") + +kt_jvm_library( + name = "stubs", + testonly = True, + srcs = glob(["*.kt"]), + visibility = ["//tests/src/cli:__pkg__"], +) diff --git a/tests/src/cli/stubs/LintCliFlags.kt b/tests/src/cli/stubs/LintCliFlags.kt new file mode 100644 index 0000000..cc90aea --- /dev/null +++ b/tests/src/cli/stubs/LintCliFlags.kt @@ -0,0 +1,6 @@ +package com.android.tools.lint + +/** Test stub; the generated setter satisfies the invoker's setCheckDependencies lookup. */ +class LintCliFlags { + var checkDependencies: Boolean = false +} diff --git a/tests/src/cli/stubs/Main.kt b/tests/src/cli/stubs/Main.kt new file mode 100644 index 0000000..0a07b13 --- /dev/null +++ b/tests/src/cli/stubs/Main.kt @@ -0,0 +1,44 @@ +package com.android.tools.lint + +import java.util.concurrent.CopyOnWriteArrayList +import java.util.concurrent.CyclicBarrier +import java.util.concurrent.TimeUnit + +/** + * Test stub standing in for lint's real entrypoint. [AndroidLintCliInvoker] resolves this class + * reflectively through the jar classloader, which delegates to the test classpath, so tests can + * observe invocations through the companion recorders. + */ +class Main { + @JvmField + val flags: LintCliFlags = LintCliFlags() + + fun run(args: Array): Int { + runBarrier?.await(5, TimeUnit.SECONDS) + recordedRuns.add(RunRecord(this, args.toList(), flags.checkDependencies)) + return exitCode + } + + data class RunRecord( + val instance: Main, + val args: List, + val checkDependencies: Boolean, + ) + + companion object { + val recordedRuns = CopyOnWriteArrayList() + + @Volatile + var exitCode: Int = 0 + + /** When set, run() blocks until [CyclicBarrier.parties] runs are in flight concurrently. */ + @Volatile + var runBarrier: CyclicBarrier? = null + + fun reset() { + recordedRuns.clear() + exitCode = 0 + runBarrier = null + } + } +} diff --git a/tests/src/cli/stubs/UastEnvironment.kt b/tests/src/cli/stubs/UastEnvironment.kt new file mode 100644 index 0000000..8e3f7b3 --- /dev/null +++ b/tests/src/cli/stubs/UastEnvironment.kt @@ -0,0 +1,15 @@ +package com.android.tools.lint + +import java.util.concurrent.atomic.AtomicInteger + +/** Test stub recording disposal calls made by [AndroidLintCliInvoker.dispose]. */ +class UastEnvironment { + companion object { + val disposeCount = AtomicInteger(0) + + @JvmStatic + fun disposeApplicationEnvironment() { + disposeCount.incrementAndGet() + } + } +} From e126a7a4efef2967b09b37018e349d10652ffcf2 Mon Sep 17 00:00:00 2001 From: Ben Lee Date: Tue, 9 Jun 2026 14:48:53 -0700 Subject: [PATCH 2/2] More --- BUILD.bazel | 1 + MODULE.bazel | 23 +++++- maven_install.json | 150 +----------------------------------- maven_install_test.json | 165 ++++++++++++++++++++++++++++++++++++++++ tests/src/worker/BUILD | 4 +- 5 files changed, 190 insertions(+), 153 deletions(-) create mode 100755 maven_install_test.json diff --git a/BUILD.bazel b/BUILD.bazel index cd63d94..b6b3db0 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -3,6 +3,7 @@ load("@rules_kotlin//kotlin:lint.bzl", "ktlint_config") exports_files([ ".bazelversion", "maven_install.json", + "maven_install_test.json", ]) ktlint_config( diff --git a/MODULE.bazel b/MODULE.bazel index 4d0d1a0..7f5f80f 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -34,9 +34,6 @@ maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven") maven.install( name = "rules_android_lint_deps", artifacts = [ - # Testing - "org.assertj:assertj-core:3.24.2", - "junit:junit:4.13.2", # Worker Dependencies "com.google.protobuf:protobuf-java:4.29.0", "com.google.protobuf:protobuf-java-util:4.29.0", @@ -55,3 +52,23 @@ maven.install( ], ) use_repo(maven, "rules_android_lint_deps", "unpinned_rules_android_lint_deps") + +# Test-only maven dependencies, kept out of the release path. +maven_test = use_extension( + "@rules_jvm_external//:extensions.bzl", + "maven", + dev_dependency = True, +) +maven_test.install( + name = "rules_android_lint_test_deps", + artifacts = [ + "org.assertj:assertj-core:3.24.2", + "junit:junit:4.13.2", + ], + lock_file = "//:maven_install_test.json", + repositories = [ + "https://maven.google.com", + "https://repo1.maven.org/maven2", + ], +) +use_repo(maven_test, "rules_android_lint_test_deps", "unpinned_rules_android_lint_test_deps") diff --git a/maven_install.json b/maven_install.json index 4d41973..24116be 100755 --- a/maven_install.json +++ b/maven_install.json @@ -1,7 +1,7 @@ { "__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": "THERE_IS_NO_DATA_ONLY_ZUUL", - "__INPUT_ARTIFACTS_HASH": 1111136302, - "__RESOLVED_ARTIFACTS_HASH": 374536965, + "__INPUT_ARTIFACTS_HASH": 337570525, + "__RESOLVED_ARTIFACTS_HASH": 252373774, "artifacts": { "com.android.tools.analytics-library:protos": { "shasums": { @@ -255,18 +255,6 @@ }, "version": "1" }, - "junit:junit": { - "shasums": { - "jar": "8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3" - }, - "version": "4.13.2" - }, - "net.bytebuddy:byte-buddy": { - "shasums": { - "jar": "f6f45c2237a7f132c16745ad2a52c4cdde58028b11ee80b09f0d422f4930d685" - }, - "version": "1.12.21" - }, "net.java.dev.jna:jna": { "shasums": { "jar": "5557e235a8aa2f9766d5dc609d67948f2a8832c2d796cea9ef1d6cbe0b3b7eaf" @@ -315,12 +303,6 @@ }, "version": "4.5.6" }, - "org.assertj:assertj-core": { - "shasums": { - "jar": "df3d0b348f1fe806bdddcb10fa4ae63c6679e9888d4bc7055f09848517976aa3" - }, - "version": "3.24.2" - }, "org.bouncycastle:bcpkix-jdk18on": { "shasums": { "jar": "3639a24ddf9ba4b7eba0659b44770e91eba816421888e571f285aadefe532cd6" @@ -357,12 +339,6 @@ }, "version": "2.3.2" }, - "org.hamcrest:hamcrest-core": { - "shasums": { - "jar": "66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9" - }, - "version": "1.3" - }, "org.jetbrains.kotlin:kotlin-reflect": { "shasums": { "jar": "c48db4f8cd1bfd67f72d59255065fb41bfda43ca2942dac4d9deb897aa126ba4" @@ -634,9 +610,6 @@ "jakarta.xml.bind:jakarta.xml.bind-api": [ "jakarta.activation:jakarta.activation-api" ], - "junit:junit": [ - "org.hamcrest:hamcrest-core" - ], "net.java.dev.jna:jna-platform": [ "net.java.dev.jna:jna" ], @@ -653,9 +626,6 @@ "org.apache.httpcomponents:httpmime": [ "org.apache.httpcomponents:httpclient" ], - "org.assertj:assertj-core": [ - "net.bytebuddy:byte-buddy" - ], "org.bouncycastle:bcpkix-jdk18on": [ "org.bouncycastle:bcprov-jdk18on", "org.bouncycastle:bcutil-jdk18on" @@ -2564,80 +2534,6 @@ "javax.inject:javax.inject": [ "javax.inject" ], - "junit:junit": [ - "junit.extensions", - "junit.framework", - "junit.runner", - "junit.textui", - "org.junit", - "org.junit.experimental", - "org.junit.experimental.categories", - "org.junit.experimental.max", - "org.junit.experimental.results", - "org.junit.experimental.runners", - "org.junit.experimental.theories", - "org.junit.experimental.theories.internal", - "org.junit.experimental.theories.suppliers", - "org.junit.function", - "org.junit.internal", - "org.junit.internal.builders", - "org.junit.internal.management", - "org.junit.internal.matchers", - "org.junit.internal.requests", - "org.junit.internal.runners", - "org.junit.internal.runners.model", - "org.junit.internal.runners.rules", - "org.junit.internal.runners.statements", - "org.junit.matchers", - "org.junit.rules", - "org.junit.runner", - "org.junit.runner.manipulation", - "org.junit.runner.notification", - "org.junit.runners", - "org.junit.runners.model", - "org.junit.runners.parameterized", - "org.junit.validator" - ], - "net.bytebuddy:byte-buddy": [ - "net.bytebuddy", - "net.bytebuddy.agent.builder", - "net.bytebuddy.asm", - "net.bytebuddy.build", - "net.bytebuddy.description", - "net.bytebuddy.description.annotation", - "net.bytebuddy.description.enumeration", - "net.bytebuddy.description.field", - "net.bytebuddy.description.method", - "net.bytebuddy.description.modifier", - "net.bytebuddy.description.type", - "net.bytebuddy.dynamic", - "net.bytebuddy.dynamic.loading", - "net.bytebuddy.dynamic.scaffold", - "net.bytebuddy.dynamic.scaffold.inline", - "net.bytebuddy.dynamic.scaffold.subclass", - "net.bytebuddy.implementation", - "net.bytebuddy.implementation.attribute", - "net.bytebuddy.implementation.auxiliary", - "net.bytebuddy.implementation.bind", - "net.bytebuddy.implementation.bind.annotation", - "net.bytebuddy.implementation.bytecode", - "net.bytebuddy.implementation.bytecode.assign", - "net.bytebuddy.implementation.bytecode.assign.primitive", - "net.bytebuddy.implementation.bytecode.assign.reference", - "net.bytebuddy.implementation.bytecode.collection", - "net.bytebuddy.implementation.bytecode.constant", - "net.bytebuddy.implementation.bytecode.member", - "net.bytebuddy.jar.asm", - "net.bytebuddy.jar.asm.commons", - "net.bytebuddy.jar.asm.signature", - "net.bytebuddy.matcher", - "net.bytebuddy.pool", - "net.bytebuddy.utility", - "net.bytebuddy.utility.dispatcher", - "net.bytebuddy.utility.nullability", - "net.bytebuddy.utility.privilege", - "net.bytebuddy.utility.visitor" - ], "net.java.dev.jna:jna": [ "com.sun.jna", "com.sun.jna.internal", @@ -2775,35 +2671,6 @@ "org.apache.http.entity.mime", "org.apache.http.entity.mime.content" ], - "org.assertj:assertj-core": [ - "org.assertj.core.annotations", - "org.assertj.core.api", - "org.assertj.core.api.exception", - "org.assertj.core.api.filter", - "org.assertj.core.api.iterable", - "org.assertj.core.api.junit.jupiter", - "org.assertj.core.api.recursive", - "org.assertj.core.api.recursive.assertion", - "org.assertj.core.api.recursive.comparison", - "org.assertj.core.condition", - "org.assertj.core.configuration", - "org.assertj.core.data", - "org.assertj.core.description", - "org.assertj.core.error", - "org.assertj.core.error.array2d", - "org.assertj.core.error.future", - "org.assertj.core.error.uri", - "org.assertj.core.extractor", - "org.assertj.core.groups", - "org.assertj.core.internal", - "org.assertj.core.matcher", - "org.assertj.core.presentation", - "org.assertj.core.util", - "org.assertj.core.util.diff", - "org.assertj.core.util.diff.myers", - "org.assertj.core.util.introspection", - "org.assertj.core.util.xml" - ], "org.bouncycastle:bcpkix-jdk18on": [ "org.bouncycastle.cert", "org.bouncycastle.cert.bc", @@ -3160,11 +3027,6 @@ "com.sun.xml.txw2.annotation", "com.sun.xml.txw2.output" ], - "org.hamcrest:hamcrest-core": [ - "org.hamcrest", - "org.hamcrest.core", - "org.hamcrest.internal" - ], "org.jetbrains.kotlin:kotlin-reflect": [ "kotlin.reflect.full", "kotlin.reflect.jvm", @@ -3368,8 +3230,6 @@ "jakarta.activation:jakarta.activation-api", "jakarta.xml.bind:jakarta.xml.bind-api", "javax.inject:javax.inject", - "junit:junit", - "net.bytebuddy:byte-buddy", "net.java.dev.jna:jna", "net.java.dev.jna:jna-platform", "net.sf.kxml:kxml2", @@ -3378,14 +3238,12 @@ "org.apache.httpcomponents:httpclient", "org.apache.httpcomponents:httpcore", "org.apache.httpcomponents:httpmime", - "org.assertj:assertj-core", "org.bouncycastle:bcpkix-jdk18on", "org.bouncycastle:bcprov-jdk18on", "org.bouncycastle:bcutil-jdk18on", "org.checkerframework:checker-qual", "org.glassfish.jaxb:jaxb-runtime", "org.glassfish.jaxb:txw2", - "org.hamcrest:hamcrest-core", "org.jetbrains.kotlin:kotlin-reflect", "org.jetbrains.kotlin:kotlin-stdlib", "org.jetbrains.kotlin:kotlin-stdlib-jdk7", @@ -3442,8 +3300,6 @@ "jakarta.activation:jakarta.activation-api", "jakarta.xml.bind:jakarta.xml.bind-api", "javax.inject:javax.inject", - "junit:junit", - "net.bytebuddy:byte-buddy", "net.java.dev.jna:jna", "net.java.dev.jna:jna-platform", "net.sf.kxml:kxml2", @@ -3452,14 +3308,12 @@ "org.apache.httpcomponents:httpclient", "org.apache.httpcomponents:httpcore", "org.apache.httpcomponents:httpmime", - "org.assertj:assertj-core", "org.bouncycastle:bcpkix-jdk18on", "org.bouncycastle:bcprov-jdk18on", "org.bouncycastle:bcutil-jdk18on", "org.checkerframework:checker-qual", "org.glassfish.jaxb:jaxb-runtime", "org.glassfish.jaxb:txw2", - "org.hamcrest:hamcrest-core", "org.jetbrains.kotlin:kotlin-reflect", "org.jetbrains.kotlin:kotlin-stdlib", "org.jetbrains.kotlin:kotlin-stdlib-jdk7", diff --git a/maven_install_test.json b/maven_install_test.json new file mode 100755 index 0000000..4f1f2bb --- /dev/null +++ b/maven_install_test.json @@ -0,0 +1,165 @@ +{ + "__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": "THERE_IS_NO_DATA_ONLY_ZUUL", + "__INPUT_ARTIFACTS_HASH": 278678914, + "__RESOLVED_ARTIFACTS_HASH": 428393146, + "artifacts": { + "junit:junit": { + "shasums": { + "jar": "8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3" + }, + "version": "4.13.2" + }, + "net.bytebuddy:byte-buddy": { + "shasums": { + "jar": "f6f45c2237a7f132c16745ad2a52c4cdde58028b11ee80b09f0d422f4930d685" + }, + "version": "1.12.21" + }, + "org.assertj:assertj-core": { + "shasums": { + "jar": "df3d0b348f1fe806bdddcb10fa4ae63c6679e9888d4bc7055f09848517976aa3" + }, + "version": "3.24.2" + }, + "org.hamcrest:hamcrest-core": { + "shasums": { + "jar": "66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9" + }, + "version": "1.3" + } + }, + "dependencies": { + "junit:junit": [ + "org.hamcrest:hamcrest-core" + ], + "org.assertj:assertj-core": [ + "net.bytebuddy:byte-buddy" + ] + }, + "packages": { + "junit:junit": [ + "junit.extensions", + "junit.framework", + "junit.runner", + "junit.textui", + "org.junit", + "org.junit.experimental", + "org.junit.experimental.categories", + "org.junit.experimental.max", + "org.junit.experimental.results", + "org.junit.experimental.runners", + "org.junit.experimental.theories", + "org.junit.experimental.theories.internal", + "org.junit.experimental.theories.suppliers", + "org.junit.function", + "org.junit.internal", + "org.junit.internal.builders", + "org.junit.internal.management", + "org.junit.internal.matchers", + "org.junit.internal.requests", + "org.junit.internal.runners", + "org.junit.internal.runners.model", + "org.junit.internal.runners.rules", + "org.junit.internal.runners.statements", + "org.junit.matchers", + "org.junit.rules", + "org.junit.runner", + "org.junit.runner.manipulation", + "org.junit.runner.notification", + "org.junit.runners", + "org.junit.runners.model", + "org.junit.runners.parameterized", + "org.junit.validator" + ], + "net.bytebuddy:byte-buddy": [ + "net.bytebuddy", + "net.bytebuddy.agent.builder", + "net.bytebuddy.asm", + "net.bytebuddy.build", + "net.bytebuddy.description", + "net.bytebuddy.description.annotation", + "net.bytebuddy.description.enumeration", + "net.bytebuddy.description.field", + "net.bytebuddy.description.method", + "net.bytebuddy.description.modifier", + "net.bytebuddy.description.type", + "net.bytebuddy.dynamic", + "net.bytebuddy.dynamic.loading", + "net.bytebuddy.dynamic.scaffold", + "net.bytebuddy.dynamic.scaffold.inline", + "net.bytebuddy.dynamic.scaffold.subclass", + "net.bytebuddy.implementation", + "net.bytebuddy.implementation.attribute", + "net.bytebuddy.implementation.auxiliary", + "net.bytebuddy.implementation.bind", + "net.bytebuddy.implementation.bind.annotation", + "net.bytebuddy.implementation.bytecode", + "net.bytebuddy.implementation.bytecode.assign", + "net.bytebuddy.implementation.bytecode.assign.primitive", + "net.bytebuddy.implementation.bytecode.assign.reference", + "net.bytebuddy.implementation.bytecode.collection", + "net.bytebuddy.implementation.bytecode.constant", + "net.bytebuddy.implementation.bytecode.member", + "net.bytebuddy.jar.asm", + "net.bytebuddy.jar.asm.commons", + "net.bytebuddy.jar.asm.signature", + "net.bytebuddy.matcher", + "net.bytebuddy.pool", + "net.bytebuddy.utility", + "net.bytebuddy.utility.dispatcher", + "net.bytebuddy.utility.nullability", + "net.bytebuddy.utility.privilege", + "net.bytebuddy.utility.visitor" + ], + "org.assertj:assertj-core": [ + "org.assertj.core.annotations", + "org.assertj.core.api", + "org.assertj.core.api.exception", + "org.assertj.core.api.filter", + "org.assertj.core.api.iterable", + "org.assertj.core.api.junit.jupiter", + "org.assertj.core.api.recursive", + "org.assertj.core.api.recursive.assertion", + "org.assertj.core.api.recursive.comparison", + "org.assertj.core.condition", + "org.assertj.core.configuration", + "org.assertj.core.data", + "org.assertj.core.description", + "org.assertj.core.error", + "org.assertj.core.error.array2d", + "org.assertj.core.error.future", + "org.assertj.core.error.uri", + "org.assertj.core.extractor", + "org.assertj.core.groups", + "org.assertj.core.internal", + "org.assertj.core.matcher", + "org.assertj.core.presentation", + "org.assertj.core.util", + "org.assertj.core.util.diff", + "org.assertj.core.util.diff.myers", + "org.assertj.core.util.introspection", + "org.assertj.core.util.xml" + ], + "org.hamcrest:hamcrest-core": [ + "org.hamcrest", + "org.hamcrest.core", + "org.hamcrest.internal" + ] + }, + "repositories": { + "https://maven.google.com/": [ + "junit:junit", + "net.bytebuddy:byte-buddy", + "org.assertj:assertj-core", + "org.hamcrest:hamcrest-core" + ], + "https://repo1.maven.org/maven2/": [ + "junit:junit", + "net.bytebuddy:byte-buddy", + "org.assertj:assertj-core", + "org.hamcrest:hamcrest-core" + ] + }, + "services": {}, + "version": "2" +} diff --git a/tests/src/worker/BUILD b/tests/src/worker/BUILD index d1dc8db..653f459 100644 --- a/tests/src/worker/BUILD +++ b/tests/src/worker/BUILD @@ -8,8 +8,8 @@ kt_jvm_test( associates = ["//src/worker"], test_class = "com.rules.android.lint.worker.WorkerTest", deps = [ - "@rules_android_lint_deps//:junit_junit", - "@rules_android_lint_deps//:org_assertj_assertj_core", + "@rules_android_lint_test_deps//:junit_junit", + "@rules_android_lint_test_deps//:org_assertj_assertj_core", ], )