From ed97009987af289d1999924af9aea0c0e0bf1dc9 Mon Sep 17 00:00:00 2001 From: Jakob Villumsen Date: Fri, 4 Sep 2026 08:43:34 +0000 Subject: [PATCH 1/2] Add unit test for calling Resolver.getDeclarationsFromPacakge across rounds This test ensures the getDeclarationsFromPackage function returns the same result across rounds. --- .../MultiRoundPackageDeclarationProcessor.kt | 70 +++++++++++++++++++ .../devtools/ksp/test/KSPUnitTestSuite.kt | 7 ++ .../native/packageDeclarationsMultiRound.kt | 47 +++++++++++++ 3 files changed, 124 insertions(+) create mode 100644 kotlin-analysis-api/src/test/kotlin/com/google/devtools/ksp/processor/MultiRoundPackageDeclarationProcessor.kt create mode 100644 kotlin-analysis-api/testData/native/packageDeclarationsMultiRound.kt diff --git a/kotlin-analysis-api/src/test/kotlin/com/google/devtools/ksp/processor/MultiRoundPackageDeclarationProcessor.kt b/kotlin-analysis-api/src/test/kotlin/com/google/devtools/ksp/processor/MultiRoundPackageDeclarationProcessor.kt new file mode 100644 index 0000000000..92d459db2f --- /dev/null +++ b/kotlin-analysis-api/src/test/kotlin/com/google/devtools/ksp/processor/MultiRoundPackageDeclarationProcessor.kt @@ -0,0 +1,70 @@ +/* + * Copyright 2026 Google LLC + * Copyright 2010-2026 JetBrains s.r.o. and Kotlin Programming Language contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.devtools.ksp.processor + +import com.google.devtools.ksp.KspExperimental +import com.google.devtools.ksp.processing.Dependencies +import com.google.devtools.ksp.processing.Resolver +import com.google.devtools.ksp.processing.SymbolProcessor +import com.google.devtools.ksp.processing.SymbolProcessorEnvironment +import com.google.devtools.ksp.symbol.KSAnnotated + +class MultiRoundPackageDeclarationProcessor( + val packageNames: List, + override val enableNewFeatures: Boolean +) : AbstractTestProcessor() { + val results = mutableListOf() + private lateinit var env: SymbolProcessorEnvironment + private var round = 0 + + override fun toResult(): List { + return results + } + + override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor { + super.create(environment) + env = environment + return this + } + + @OptIn(KspExperimental::class) + override fun process(resolver: Resolver): List { + results.add("Round $round:") + packageNames.forEach { pkgName -> + val decls = resolver.getDeclarationsFromPackage(pkgName) + .map { it.qualifiedName?.asString() ?: it.simpleName.asString() } + .sorted() + .toList() + results.add("$pkgName: $decls") + } + + if (round == 0) { + generateDummyFileToAdvanceToNextRound() + } + + round++ + return emptyList() + } + + private fun generateDummyFileToAdvanceToNextRound() { + val dependencies = Dependencies(aggregating = true, sources = arrayOf()) + env.codeGenerator.createNewFile(dependencies, "com.example.generated", "GeneratedClass", "kt").use { + it.write("package com.example.generated\n\nclass GeneratedClass\n".toByteArray()) + } + } +} diff --git a/kotlin-analysis-api/src/test/kotlin/com/google/devtools/ksp/test/KSPUnitTestSuite.kt b/kotlin-analysis-api/src/test/kotlin/com/google/devtools/ksp/test/KSPUnitTestSuite.kt index 32454998e9..d25e2da0eb 100644 --- a/kotlin-analysis-api/src/test/kotlin/com/google/devtools/ksp/test/KSPUnitTestSuite.kt +++ b/kotlin-analysis-api/src/test/kotlin/com/google/devtools/ksp/test/KSPUnitTestSuite.kt @@ -1012,6 +1012,13 @@ abstract class KSPUnitTestSuite( runTest("$AA_PATH/native/packageDeclarations.kt") } + @Bug("https://github.com/google/ksp/issues/2396", BugState.FIXED) + @TestMetadata("native/packageDeclarationsMultiRound.kt") + @Test + fun testNativePackageDeclarationsMultiRound() { + runTest("$AA_PATH/native/packageDeclarationsMultiRound.kt") + } + @Bug( "https://github.com/google/ksp/issues/2396", BugState.FIXED, diff --git a/kotlin-analysis-api/testData/native/packageDeclarationsMultiRound.kt b/kotlin-analysis-api/testData/native/packageDeclarationsMultiRound.kt new file mode 100644 index 0000000000..a565c1e9da --- /dev/null +++ b/kotlin-analysis-api/testData/native/packageDeclarationsMultiRound.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2026 Google LLC + * Copyright 2010-2026 JetBrains s.r.o. and Kotlin Programming Language contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// TARGET_BACKEND: NATIVE +// WITH_FIXED_TARGET: linux_x64 +// TEST PROCESSOR: MultiRoundPackageDeclarationProcessor +// PROCESSOR INPUT: com.example.test, com.example.app, com.example.generated +// EXPECTED: +// Round 0: +// com.example.test: [com.example.test.LibDeclaration] +// com.example.app: [com.example.app.Foo] +// com.example.generated: [] +// Round 1: +// com.example.test: [com.example.test.LibDeclaration] +// com.example.app: [com.example.app.Foo] +// com.example.generated: [com.example.generated.GeneratedClass] +// END + +// MODULE: lib +// FILE: LibDeclaration.kt + +package com.example.test + +class LibDeclaration + +// MODULE: main(lib) +// FILE: Foo.kt + +package com.example.app + +import com.example.test.LibDeclaration + +class Foo(val decl: LibDeclaration) From 70de607f4283c6eb1831c0dc418f54274733455c Mon Sep 17 00:00:00 2001 From: Jakob Villumsen Date: Fri, 4 Sep 2026 08:44:44 +0000 Subject: [PATCH 2/2] Update KMPImplementedIT to call getDeclarationsFromPackage across rounds This commit updates the inegration test to call getDeclarationsFromPackage across rounds to ensure that the klibs are still accessible after dumping caches between rounds. --- .../ksp/test/primary/KMPImplementedIT.kt | 19 ++++++++++-- .../src/main/kotlin/TestProcessor.kt | 30 +++++++++++-------- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/primary/KMPImplementedIT.kt b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/primary/KMPImplementedIT.kt index 04d239407d..79b4c9a8bf 100644 --- a/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/primary/KMPImplementedIT.kt +++ b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/primary/KMPImplementedIT.kt @@ -444,12 +444,27 @@ class AnnoOnProperty { Assert.assertEquals(TaskOutcome.SUCCESS, it.task(":workload-klib:kspKotlinLinuxX64")?.outcome) Assert.assertTrue( it.output.contains( - "w: [ksp] com.somedependency package declarations: [com.somedependency.AnotherDepClass, com.somedependency.SomeDepClass]" + "w: [ksp] round 0 com.somedependency package declarations: [com.somedependency.AnotherDepClass, com.somedependency.SomeDepClass]" ) ) Assert.assertTrue( it.output.contains( - "w: [ksp] com.myapp package declarations: [com.myapp.AnotherAppClass, com.myapp.MyAppClass, com.myapp.anotherAppFunction, com.myapp.myAppFunction]" + "w: [ksp] round 1 com.somedependency package declarations: [com.somedependency.AnotherDepClass, com.somedependency.SomeDepClass]" + ) + ) + Assert.assertTrue( + it.output.contains( + "w: [ksp] round 0 com.myapp package declarations: [com.myapp.AnotherAppClass, com.myapp.MyAppClass, com.myapp.anotherAppFunction, com.myapp.myAppFunction]" + ) + ) + Assert.assertTrue( + it.output.contains( + "w: [ksp] round 1 com.myapp package declarations: [com.myapp.AnotherAppClass, com.myapp.MyAppClass, com.myapp.anotherAppFunction, com.myapp.myAppFunction]" + ) + ) + Assert.assertTrue( + it.output.contains( + "w: [ksp] round 1 com.example package declarations: [com.example.Foo]" ) ) } diff --git a/integration-tests/src/test/resources/kmp/test-processor/src/main/kotlin/TestProcessor.kt b/integration-tests/src/test/resources/kmp/test-processor/src/main/kotlin/TestProcessor.kt index 249bbc62e5..62f2a971e1 100644 --- a/integration-tests/src/test/resources/kmp/test-processor/src/main/kotlin/TestProcessor.kt +++ b/integration-tests/src/test/resources/kmp/test-processor/src/main/kotlin/TestProcessor.kt @@ -12,29 +12,18 @@ class TestProcessor( val logger: KSPLogger, val env: SymbolProcessorEnvironment ) : SymbolProcessor { - var invoked = false + var round = 0 override fun process(resolver: Resolver): List { val allFiles = resolver.getAllFiles().map { it.fileName } logger.warn(allFiles.toList().toString()) - if (invoked) { - return emptyList() - } - invoked = true - - logger.warn("language version: ${env.kotlinVersion}") - logger.warn("api version: ${env.apiVersion}") - logger.warn("compiler version: ${env.compilerVersion}") - val platforms = env.platforms.map { it.toString() } - logger.warn("platforms: $platforms") - val list = resolver.getClassDeclarationByName("kotlin.collections.List") - logger.warn("List has superTypes: ${list!!.superTypes.count() > 0}") val someDepDecls = resolver.getDeclarationsFromPackage("com.somedependency") .mapNotNull { it.qualifiedName?.asString() } .sorted() .toList() if (someDepDecls.isNotEmpty()) { + logger.warn("round $round com.somedependency package declarations: $someDepDecls") logger.warn("com.somedependency package declarations: $someDepDecls") } @@ -43,6 +32,7 @@ class TestProcessor( .sorted() .toList() if (myAppDecls.isNotEmpty()) { + logger.warn("round $round com.myapp package declarations: $myAppDecls") logger.warn("com.myapp package declarations: $myAppDecls") } @@ -51,9 +41,23 @@ class TestProcessor( .sorted() .toList() if (exampleDecls.isNotEmpty()) { + logger.warn("round $round com.example package declarations: $exampleDecls") logger.warn("com.example package declarations: $exampleDecls") } + if (round > 0) { + return emptyList() + } + round++ + + logger.warn("language version: ${env.kotlinVersion}") + logger.warn("api version: ${env.apiVersion}") + logger.warn("compiler version: ${env.compilerVersion}") + val platforms = env.platforms.map { it.toString() } + logger.warn("platforms: $platforms") + val list = resolver.getClassDeclarationByName("kotlin.collections.List") + logger.warn("List has superTypes: ${list!!.superTypes.count() > 0}") + codeGenerator.createNewFile( Dependencies(true, *resolver.getAllFiles().toList().toTypedArray()), "",