[aa-klib-reader] Implement 'analysis-api-klib-reader' for swift- and objc export

This new module gives the ability to resolve symbols
from provided klib KtLibraryModules withing a Analysis Session.

^KT-65327 Fixed
This commit is contained in:
Sebastian Sellmair
2024-03-01 11:28:09 +01:00
committed by Space Team
parent 1bb6f869a2
commit b5b7e5f262
32 changed files with 1031 additions and 2 deletions
+1
View File
@@ -360,6 +360,7 @@
/license/ "Kotlin Build Infrastructure"
/native/ "Kotlin Native"
/native/analysis-api-klib-reader/ "Kotlin Native" "Kotlin in Fleet"
/native/commonizer/ "Kotlin Apple Ecosystem"
/native/commonizer-api/ "Kotlin Apple Ecosystem"
/native/commonizer-embeddable/ "Kotlin Apple Ecosystem"
+2 -1
View File
@@ -811,10 +811,11 @@ tasks {
// - different GCs
// ...
register("nativeCompilerTest") {
dependsOn(":kotlin-atomicfu-compiler-plugin:nativeTest")
dependsOn(":native:analysis-api-klib-reader:check")
dependsOn(":native:native.tests:test")
dependsOn(":native:objcexport-header-generator:check")
dependsOn(":native:swift:swift-export-standalone:test")
dependsOn(":kotlin-atomicfu-compiler-plugin:nativeTest")
}
// These are unit tests of Native compiler
+61
View File
@@ -0,0 +1,61 @@
# Native Analysis Api based Klib reader
## Tasks
**Run Tests**
```
./gradlew :native:analysis-api-klib-reader:check
```
## Usage: Reading symbols from a given klib module
A set of top level `KlibDeclarationAddress` can be read from `KtLibraryModule` by
```kotlin
fun example(module: KtLibraryModule) {
val addresses = module.readKlibDeclarationAddresses()
}
```
Such addresses can be resolved to `KtSymbol`s by
```kotlin
addresses.flatMap { address -> address.getSymbols() }
```
Note: `getSymbols` returns a sequence as multiple symbols can live under the same address.
## Usage: Getting all library modules in a Standalone Analysis API session.
Example of creating a session
```kotin
val session = buildStandaloneAnalysisAPISession {
buildKtModuleProvider {
// ...
addModule(buildKtLibraryModule {
addBinaryRoot(pathToKlib)
// ..
})
}
}
```
Example analyzing libraries within the session
```kotlin
session.getAllLibraryModules().forEach { module ->
analyze(module) {
module.readKlibDeclarationAddresses()
.flatMap { address -> address.getSymbols() }
}
}
```
## Test Strategy
### Black Box Test:
We are building a klib from the `testProject`.
This klib will be used to read the addresses. Those addresses will be rendered and compared to
[!testProject.addresses](testData%2F%21testProject.addresses)
@@ -0,0 +1,67 @@
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinUsages
import org.jetbrains.kotlin.konan.target.HostManager
plugins {
kotlin("jvm")
}
sourceSets {
"main" { projectDefault() }
"test" { projectDefault() }
}
kotlin {
compilerOptions {
explicitApi()
/* Required to use Analysis Api */
freeCompilerArgs.add("-Xcontext-receivers")
}
}
projectTest(jUnitMode = JUnitMode.JUnit5) {
workingDir = rootDir
useJUnitPlatform()
val testProjectKlib = configurations.create("testProjectKlib") {
attributes {
attribute(Usage.USAGE_ATTRIBUTE, objects.named(KotlinUsages.KOTLIN_API))
attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.LIBRARY))
attribute(KotlinPlatformType.attribute, KotlinPlatformType.native)
attribute(KotlinNativeTarget.konanTargetAttribute, HostManager.host.name)
}
}
val testProjectKlibFiles = testProjectKlib.incoming.files
dependencies {
testProjectKlib(project("testProject"))
}
inputs.files(testProjectKlibFiles)
.withPathSensitivity(PathSensitivity.RELATIVE)
doFirst {
systemProperty("testKlibs", testProjectKlibFiles.joinToString(File.pathSeparator))
}
}
dependencies {
api(project(":analysis:analysis-api"))
implementation(project(":core:compiler.common"))
implementation(project(":core:compiler.common.native"))
implementation(project(":kotlin-tooling-core"))
compileOnly(project(":analysis:analysis-api-standalone"))
compileOnly(project(":core:metadata"))
compileOnly(project(":kotlin-metadata"))
compileOnly(project(":kotlin-util-klib-metadata"))
compileOnly(protobufLite())
testImplementation(kotlinTest("junit5"))
testImplementation(project(":compiler:tests-common", "tests-jar"))
testImplementation(project(":analysis:analysis-api-standalone"))
}
@@ -0,0 +1,51 @@
/*
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.analysis.api.klib.reader
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
public sealed class KlibDeclarationAddress {
public abstract val sourceFileName: String?
public abstract val packageFqName: FqName
}
public sealed class KlibClassifierAddress : KlibDeclarationAddress() {
public abstract val classId: ClassId
}
public data class KlibClassAddress(
public override val sourceFileName: String?,
public override val packageFqName: FqName,
public override val classId: ClassId,
) : KlibClassifierAddress()
public data class KlibTypeAliasAddress(
override val packageFqName: FqName,
override val classId: ClassId,
) : KlibClassifierAddress() {
/**
* TypeAlias do not encode their source file name into klibs. This value is always null.
*/
override val sourceFileName: Nothing? = null
}
public sealed class KlibCallableAddress : KlibDeclarationAddress() {
public abstract val callableName: Name
}
public data class KlibPropertyAddress(
override val sourceFileName: String?,
override val packageFqName: FqName,
override val callableName: Name,
) : KlibCallableAddress()
public data class KlibFunctionAddress(
override val sourceFileName: String?,
override val packageFqName: FqName,
override val callableName: Name,
) : KlibCallableAddress()
@@ -0,0 +1,24 @@
/*
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.analysis.api.klib.reader
import org.jetbrains.kotlin.library.metadata.KlibMetadataProtoBuf
import org.jetbrains.kotlin.metadata.ProtoBuf
import org.jetbrains.kotlin.metadata.deserialization.NameResolverImpl
import org.jetbrains.kotlin.metadata.deserialization.getExtensionOrNull
import org.jetbrains.kotlin.name.FqName
internal fun PackageFragmentReadingContext(packageFragmentProto: ProtoBuf.PackageFragment): PackageFragmentReadingContext? {
val nameResolver = NameResolverImpl(packageFragmentProto.strings, packageFragmentProto.qualifiedNames)
val packageFqName = packageFragmentProto.`package`.getExtensionOrNull(KlibMetadataProtoBuf.packageFqName)
?.let { packageFqNameStringIndex -> nameResolver.getPackageFqName(packageFqNameStringIndex) } ?: return null
return PackageFragmentReadingContext(FqName(packageFqName), nameResolver)
}
internal class PackageFragmentReadingContext(
val packageFqName: FqName,
val nameResolver: NameResolverImpl,
)
@@ -0,0 +1,42 @@
/*
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.analysis.api.klib.reader
import org.jetbrains.kotlin.analysis.api.standalone.StandaloneAnalysisAPISession
import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.KtStaticProjectStructureProvider
import org.jetbrains.kotlin.analysis.project.structure.KtLibraryModule
import org.jetbrains.kotlin.analysis.project.structure.KtModule
import org.jetbrains.kotlin.analysis.project.structure.ProjectStructureProvider
import org.jetbrains.kotlin.analysis.project.structure.allDirectDependencies
import org.jetbrains.kotlin.tooling.core.withClosureSequence
/**
* Returns all registered [KtLibraryModule] in this [StandaloneAnalysisAPISession].
* Note: If a library module is not added as a dependency of another module, make sure to add the module directly as in:
* ```kotlin
* buildKtModuleProvider {
* addModule( // <- !! addModule !!
* buildKtLibraryModule {
* addBinaryRoot(myKlibRootPath)
* libraryName = myLibraryName
* // ...
* }
* )
* }
* ```
*/
public fun StandaloneAnalysisAPISession.getAllLibraryModules(): Sequence<KtLibraryModule> {
val projectStructureProvider = project.getService(ProjectStructureProvider::class.java)
?: error("${ProjectStructureProvider::class.java} not found")
if (projectStructureProvider !is KtStaticProjectStructureProvider) {
error("Expected implementation of ${KtStaticProjectStructureProvider::class.java} but found ${projectStructureProvider.javaClass}")
}
return projectStructureProvider.allKtModules
.withClosureSequence<KtModule> { module -> module.allDirectDependencies().asIterable() }
.filterIsInstance<KtLibraryModule>()
}
@@ -0,0 +1,78 @@
/*
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.analysis.api.klib.reader
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.symbols.*
/**
* Note: A single [KlibDeclarationAddress] can be shared by multiple symbols.
* e.g. the [KlibDeclarationAddress] for functions with overloads will be shared.
*
* ```kotlin
* fun foo() = 42
* fun foo(param: Int) = param
* ```
*
* Both foo functions will live under the same [KlibDeclarationAddress]
*
* @return all symbols under the given [KlibDeclarationAddress].
* @see [getClassOrObjectSymbol]
* @see [getFunctionSymbols]
* @see [getPropertySymbols]
*/
context(KtAnalysisSession)
public fun KlibDeclarationAddress.getSymbols(): Sequence<KtSymbol> {
return when (this) {
is KlibClassAddress -> getClassOrObjectSymbol()?.let { symbol -> sequenceOf(symbol) } ?: emptySequence()
is KlibTypeAliasAddress -> getTypeAliasSymbol()?.let { symbol -> sequenceOf(symbol) } ?: emptySequence()
is KlibFunctionAddress -> getFunctionSymbols()
is KlibPropertyAddress -> getPropertySymbols()
}
}
/**
* @see [getSymbols]
*/
context(KtAnalysisSession)
public fun KlibClassAddress.getClassOrObjectSymbol(): KtClassOrObjectSymbol? {
return getClassOrObjectSymbolByClassId(classId)
}
context(KtAnalysisSession)
public fun KlibTypeAliasAddress.getTypeAliasSymbol(): KtTypeAliasSymbol? {
return getTypeAliasByClassId(classId)
}
/**
* @see [getSymbols]
*/
context(KtAnalysisSession)
public fun KlibCallableAddress.getCallableSymbols(): Sequence<KtCallableSymbol> {
return when (this) {
is KlibFunctionAddress -> getFunctionSymbols()
is KlibPropertyAddress -> getPropertySymbols()
}
}
/**
* @see [getSymbols]
*/
context(KtAnalysisSession)
public fun KlibFunctionAddress.getFunctionSymbols(): Sequence<KtFunctionSymbol> {
return getTopLevelCallableSymbols(packageFqName, callableName)
.filterIsInstance<KtFunctionSymbol>()
}
/**
* @see [getSymbols]
*/
context(KtAnalysisSession)
public fun KlibPropertyAddress.getPropertySymbols(): Sequence<KtPropertySymbol> {
return getTopLevelCallableSymbols(packageFqName, callableName)
.filterIsInstance<KtPropertySymbol>()
}
@@ -0,0 +1,135 @@
/*
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.analysis.api.klib.reader
import org.jetbrains.kotlin.analysis.api.symbols.KtSymbol
import org.jetbrains.kotlin.analysis.project.structure.KtLibraryModule
import org.jetbrains.kotlin.konan.file.File
import org.jetbrains.kotlin.library.KotlinLibrary
import org.jetbrains.kotlin.library.ToolingSingleFileKlibResolveStrategy
import org.jetbrains.kotlin.library.metadata.KlibMetadataProtoBuf
import org.jetbrains.kotlin.library.metadata.parseModuleHeader
import org.jetbrains.kotlin.library.metadata.parsePackageFragment
import org.jetbrains.kotlin.metadata.ProtoBuf
import org.jetbrains.kotlin.metadata.deserialization.getExtensionOrNull
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.util.DummyLogger
import org.jetbrains.kotlin.util.Logger
import java.nio.file.Path
import kotlin.io.path.extension
import kotlin.io.path.isDirectory
/**
* Provides a set of [KlibDeclarationAddress] contained within the given [KtLibraryModule] (if the library is based upon a klib file).
* These addresses will contain all top level declarations such as top level classes, interfaces, objects, ... and
* top level callables such as functions and properties.
*
* Example, given the provided source file
* ```kotlin
* package com.example
* fun topLevelFunction() = 42
*
* class Foo {
* val foo() = 42
* class Bar {
* fun bar() = 42
* }
* }
* ```
*
* The read addresses will include `topLevelFunction` as well as `Foo`, as both declarations are top level.
* These addresses can then be resolved to its given [KtSymbol] by
*
* - [KlibClassAddress.getClassOrObjectSymbol]: Resolves to the class symbol or null
* - [KlibCallableAddress.getCallableSymbols]: Resolves all callable symbols under this given address
*
* @return The returned set has a stable order
*/
public fun KtLibraryModule.readKlibDeclarationAddresses(): Set<KlibDeclarationAddress>? {
val binary = getBinaryRoots().singleOrNull() ?: return null
if (!(binary.extension == "klib" || binary.isDirectory())) return null
return readKlibDeclarationAddresses(binary)
}
internal fun readKlibDeclarationAddresses(path: Path, logger: Logger = DummyLogger): Set<KlibDeclarationAddress>? {
val library = ToolingSingleFileKlibResolveStrategy.tryResolve(File(path), logger) ?: return null
return readKlibDeclarationAddresses(library)
}
internal fun readKlibDeclarationAddresses(library: KotlinLibrary): Set<KlibDeclarationAddress> {
val headerProto = parseModuleHeader(library.moduleHeaderData)
val packageMetadataSequence = headerProto.packageFragmentNameList.asSequence().flatMap { packageFragmentName ->
library.packageMetadataParts(packageFragmentName).asSequence().map { packageMetadataPart ->
library.packageMetadata(packageFragmentName, packageMetadataPart)
}
}
return packageMetadataSequence.flatMap { packageMetadata ->
val packageFragmentProto = parsePackageFragment(packageMetadata)
with(PackageFragmentReadingContext(packageFragmentProto) ?: return@flatMap emptySet()) {
packageFragmentProto.readKlibClassAddresses() +
packageFragmentProto.readKlibTypeAliasAddresses() +
packageFragmentProto.readKlibPropertyAddresses() +
packageFragmentProto.readKlibFunctionAddresses()
}
}.toSet()
}
context(PackageFragmentReadingContext)
internal fun ProtoBuf.PackageFragment.readKlibClassAddresses(): Set<KlibClassAddress> {
return class_List.mapNotNull { classProto ->
val classId = ClassId.fromString(nameResolver.getQualifiedClassName(classProto.fqName))
if (classId.isNestedClass) return@mapNotNull null
KlibClassAddress(
packageFqName = packageFqName,
sourceFileName = classProto.getExtensionOrNull(KlibMetadataProtoBuf.classFile)?.let { fileNameId ->
nameResolver.strings.getString(fileNameId)
},
classId = classId
)
}.toSet()
}
context(PackageFragmentReadingContext)
internal fun ProtoBuf.PackageFragment.readKlibTypeAliasAddresses(): Set<KlibTypeAliasAddress> {
return this.`package`.typeAliasList.map { typeAliasProto ->
val name = Name.identifier(nameResolver.getString(typeAliasProto.name))
KlibTypeAliasAddress(
packageFqName = packageFqName,
classId = ClassId(packageFqName, name)
)
}.toSet()
}
context(PackageFragmentReadingContext)
internal fun ProtoBuf.PackageFragment.readKlibPropertyAddresses(): Set<KlibPropertyAddress> {
return `package`.propertyList.map { propertyProto ->
KlibPropertyAddress(
sourceFileName = propertyProto.getExtensionOrNull(KlibMetadataProtoBuf.propertyFile)?.let { fileNameId ->
nameResolver.strings.getString(fileNameId)
},
packageFqName = packageFqName,
callableName = Name.identifier(nameResolver.getString(propertyProto.name))
)
}.toSet()
}
context(PackageFragmentReadingContext)
internal fun ProtoBuf.PackageFragment.readKlibFunctionAddresses(): Set<KlibFunctionAddress> {
return `package`.functionList.map { functionProto ->
KlibFunctionAddress(
sourceFileName = functionProto.getExtensionOrNull(KlibMetadataProtoBuf.functionFile)?.let { fileNameId ->
nameResolver.getString(fileNameId)
},
packageFqName = packageFqName,
callableName = Name.identifier(nameResolver.getString(functionProto.name))
)
}.toSet()
}
@@ -0,0 +1,19 @@
/*
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.analysis.api.klib.reader.testUtils
import java.io.File
import java.nio.file.Path
import kotlin.io.path.Path
val providedTestKlibs: Set<Path> = System.getProperty("testKlibs").let { testKlibs ->
if (testKlibs == null) error("Missing 'testKlibs' System property")
testKlibs.split(File.pathSeparator).map(::Path).toSet()
}
val providedTestProjectKlib = providedTestKlibs.find { path ->
path.contains(Path("testProject"))
} ?: error("Missing 'testProject' klib")
@@ -0,0 +1,47 @@
/*
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.analysis.api.klib.reader.testUtils
import org.jetbrains.kotlin.analysis.api.klib.reader.*
fun Iterable<KlibDeclarationAddress>.render() = joinToString(System.lineSeparator().repeat(2)) { it.render() }
fun KlibDeclarationAddress.render(): String {
return when (this) {
is KlibFunctionAddress -> render()
is KlibPropertyAddress -> render()
is KlibClassAddress -> render()
is KlibTypeAliasAddress -> render()
}
}
private fun KlibFunctionAddress.render(): String = """
Function (${packageFqName.child(callableName).asString()})
Source File Name : "$sourceFileName"
Package Name : "${packageFqName.asString()}"
Function Name : "${callableName.asString()}"
""".trimIndent()
private fun KlibPropertyAddress.render(): String = """
Property (${packageFqName.child(callableName).asString()})
Source File Name : "$sourceFileName"
Package Name : "${packageFqName.asString()}"
Property Name : "${callableName.asString()}"
""".trimIndent()
private fun KlibClassAddress.render(): String = """
Class (${classId.asFqNameString()})
Source File Name : "$sourceFileName"
Package Name : "${packageFqName.asString()}"
ClassId : "${classId.asString()}"
""".trimIndent()
private fun KlibTypeAliasAddress.render(): String = """
TypeAlias (${classId.asFqNameString()})
Package Name : "${packageFqName.asString()}"
ClassId : "${classId.asString()}"
""".trimIndent()
@@ -0,0 +1,10 @@
/*
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.analysis.api.klib.reader.testUtils
import kotlin.io.path.Path
val testDataDir = Path("native/analysis-api-klib-reader/testData")
@@ -0,0 +1,158 @@
/*
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.analysis.api.klib.reader.tests
import org.jetbrains.kotlin.analysis.api.KtAnalysisApiInternals
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.analyze
import org.jetbrains.kotlin.analysis.api.klib.reader.*
import org.jetbrains.kotlin.analysis.api.klib.reader.testUtils.providedTestProjectKlib
import org.jetbrains.kotlin.analysis.api.lifetime.KtLifetimeTokenProvider
import org.jetbrains.kotlin.analysis.api.standalone.KtAlwaysAccessibleLifetimeTokenProvider
import org.jetbrains.kotlin.analysis.api.standalone.buildStandaloneAnalysisAPISession
import org.jetbrains.kotlin.analysis.api.symbols.KtClassKind
import org.jetbrains.kotlin.analysis.api.symbols.KtPropertySymbol
import org.jetbrains.kotlin.analysis.api.symbols.nameOrAnonymous
import org.jetbrains.kotlin.analysis.project.structure.KtLibraryModule
import org.jetbrains.kotlin.analysis.project.structure.builder.buildKtLibraryModule
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.platform.konan.NativePlatforms
import kotlin.io.path.nameWithoutExtension
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.fail
class GetSymbolsTest {
@Test
fun `test - getClassOrObjectSymbol - RootPkgClass`() {
withTestProjectLibraryAnalysisSession {
val addresses = (useSiteModule as KtLibraryModule).readKlibDeclarationAddresses()
?: error("Failed reading declaration addresses")
val rootPkgClassAddress = addresses.filterIsInstance<KlibClassAddress>()
.first { it.classId == ClassId.fromString("RootPkgClass") }
val rootPkgClassSymbol = assertNotNull(rootPkgClassAddress.getClassOrObjectSymbol())
assertEquals("RootPkgClass", rootPkgClassSymbol.nameOrAnonymous.asString())
}
}
@Test
fun `test - getClassOrObjectSymbol - AObject`() {
withTestProjectLibraryAnalysisSession {
val addresses = (useSiteModule as KtLibraryModule).readKlibDeclarationAddresses()
?: error("Failed reading declaration addresses")
val aObjectAddress = addresses.filterIsInstance<KlibClassAddress>()
.first { it.classId == ClassId.fromString("org/jetbrains/sample/a/AObject") }
val aObjectSymbol = assertNotNull(aObjectAddress.getClassOrObjectSymbol())
assertEquals("AObject", aObjectSymbol.nameOrAnonymous.asString())
assertEquals(KtClassKind.OBJECT, aObjectSymbol.classKind)
}
}
@Test
fun `test - getSymbols - rootPkgProperty`() {
withTestProjectLibraryAnalysisSession {
val addresses = (useSiteModule as KtLibraryModule).readKlibDeclarationAddresses()
?: error("Failed reading declaration addresses")
val rootPkgPropertyAddress = addresses.filterIsInstance<KlibPropertyAddress>()
.first { it.callableName == Name.identifier("rootPkgProperty") }
val rootPkgPropertySymbols = rootPkgPropertyAddress.getSymbols().toList()
if (rootPkgPropertySymbols.size != 1) fail("Expected only a single 'rootPkgProperty' symbol. Found $rootPkgPropertySymbols")
/* Check getSymbols and getPropertySymbols behave the same */
assertEquals(
rootPkgPropertySymbols,
rootPkgPropertyAddress.getPropertySymbols().toList(),
"Expected 'getSymbols' and 'getPropertySymbols' to be equal"
)
val symbol = rootPkgPropertySymbols.single() as KtPropertySymbol
assertEquals("rootPkgProperty", symbol.name.asString())
}
}
@Test
fun `test - getSymbols - aFunction`() {
withTestProjectLibraryAnalysisSession {
val addresses = (useSiteModule as KtLibraryModule).readKlibDeclarationAddresses()
?: error("Failed reading declaration addresses")
val aFunctionAddress = addresses.filterIsInstance<KlibFunctionAddress>()
.first { it.callableName == Name.identifier("aFunction") }
val aFunctionSymbols = aFunctionAddress.getFunctionSymbols().toList()
if (aFunctionSymbols.size != 2) fail("Expected exactly 2 'aFunction' symbols. Found $aFunctionSymbols")
/* Check: getFunctionSymbols and getSymbols behave the same */
assertEquals(
aFunctionSymbols,
aFunctionAddress.getSymbols().toList(),
"Expected 'getSymbols' and 'getFunctionSymbols' to be equal"
)
aFunctionSymbols.forEach { symbol ->
assertEquals("aFunction", symbol.name.asString())
}
/* Check: One function does not have any value params, the other function has a single param with name 'seed' */
assertEquals(
setOf(emptyList(), listOf("seed")), aFunctionSymbols.map { it.valueParameters.map { it.name.asString() } }.toSet()
)
}
}
@Test
fun `test - getTypeAlias - TypeAliasA`() {
withTestProjectLibraryAnalysisSession {
val addresses = (useSiteModule as KtLibraryModule).readKlibDeclarationAddresses() ?: fail("Failed reading addresses")
val typeAliasAAddress = addresses.filterIsInstance<KlibTypeAliasAddress>()
.find { it.classId == ClassId.fromString("org/jetbrains/sample/TypeAliasA") }
?: fail("Could not find TypeAliasA")
val typeAliasASymbol = assertNotNull(typeAliasAAddress.getTypeAliasSymbol())
assertEquals(Name.identifier("TypeAliasA"), typeAliasASymbol.name)
assertEquals(Name.identifier("AClass"), typeAliasASymbol.expandedType.expandedClassSymbol?.name)
}
}
/**
* Runs the given [block] in an analysis session that will have the built library as [KtAnalysisSession.useSiteModule]
*/
private fun <T> withTestProjectLibraryAnalysisSession(block: context(KtAnalysisSession) () -> T): T {
val session = buildStandaloneAnalysisAPISession {
val currentArchitectureTarget = HostManager.host
val nativePlatform = NativePlatforms.nativePlatformByTargets(listOf(currentArchitectureTarget))
@OptIn(KtAnalysisApiInternals::class)
registerProjectService(KtLifetimeTokenProvider::class.java, KtAlwaysAccessibleLifetimeTokenProvider())
buildKtModuleProvider {
platform = nativePlatform
addModule(buildKtLibraryModule {
addBinaryRoot(providedTestProjectKlib)
platform = nativePlatform
libraryName = providedTestProjectKlib.nameWithoutExtension
})
}
}
return analyze(session.getAllLibraryModules().single()) {
block(this)
}
}
}
@@ -0,0 +1,23 @@
/*
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.analysis.api.klib.reader.tests
import org.jetbrains.kotlin.analysis.api.klib.reader.readKlibDeclarationAddresses
import org.jetbrains.kotlin.analysis.api.klib.reader.testUtils.providedTestProjectKlib
import org.jetbrains.kotlin.analysis.api.klib.reader.testUtils.render
import org.jetbrains.kotlin.analysis.api.klib.reader.testUtils.testDataDir
import org.jetbrains.kotlin.test.KotlinTestUtils
import kotlin.test.Test
import kotlin.test.fail
class ReadKlibDeclarationAddressesBlackBoxTest {
@Test
fun `test - testProject`() {
val addresses = readKlibDeclarationAddresses(providedTestProjectKlib) ?: fail("Failed loading klib: $providedTestProjectKlib")
KotlinTestUtils.assertEqualsToFile(testDataDir.resolve("!testProject.addresses"), addresses.render())
}
}
@@ -0,0 +1,122 @@
Property (rootPkgProperty)
Source File Name : "RootPkgCallables.kt"
Package Name : ""
Property Name : "rootPkgProperty"
Function (rootPkgFunction)
Source File Name : "RootPkgCallables.kt"
Package Name : ""
Function Name : "rootPkgFunction"
Class (RootPkgClass)
Source File Name : "RootPkgClass.kt"
Package Name : ""
ClassId : "RootPkgClass"
Property (org.jetbrains.sample.constantInt)
Source File Name : "Constants.kt"
Package Name : "org.jetbrains.sample"
Property Name : "constantInt"
Property (org.jetbrains.sample.constantString)
Source File Name : "Constants.kt"
Package Name : "org.jetbrains.sample"
Property Name : "constantString"
Property (org.jetbrains.sample.aExtensionProperty)
Source File Name : "Extensions.kt"
Package Name : "org.jetbrains.sample"
Property Name : "aExtensionProperty"
Function (org.jetbrains.sample.aExtensionFun)
Source File Name : "Extensions.kt"
Package Name : "org.jetbrains.sample"
Function Name : "aExtensionFun"
Class (org.jetbrains.sample.GenericClass)
Source File Name : "Generics.kt"
Package Name : "org.jetbrains.sample"
ClassId : "org/jetbrains/sample/GenericClass"
Property (org.jetbrains.sample.genericProperty)
Source File Name : "Generics.kt"
Package Name : "org.jetbrains.sample"
Property Name : "genericProperty"
Function (org.jetbrains.sample.genericFun)
Source File Name : "Generics.kt"
Package Name : "org.jetbrains.sample"
Function Name : "genericFun"
Class (org.jetbrains.sample.PrivateClass)
Source File Name : "SymbolsWithDifferentVisibilities.kt"
Package Name : "org.jetbrains.sample"
ClassId : "org/jetbrains/sample/PrivateClass"
Class (org.jetbrains.sample.InternalClass)
Source File Name : "SymbolsWithDifferentVisibilities.kt"
Package Name : "org.jetbrains.sample"
ClassId : "org/jetbrains/sample/InternalClass"
Class (org.jetbrains.sample.PublicClass)
Source File Name : "SymbolsWithDifferentVisibilities.kt"
Package Name : "org.jetbrains.sample"
ClassId : "org/jetbrains/sample/PublicClass"
Function (org.jetbrains.sample.privateFun)
Source File Name : "SymbolsWithDifferentVisibilities.kt"
Package Name : "org.jetbrains.sample"
Function Name : "privateFun"
Function (org.jetbrains.sample.internalFun)
Source File Name : "SymbolsWithDifferentVisibilities.kt"
Package Name : "org.jetbrains.sample"
Function Name : "internalFun"
Function (org.jetbrains.sample.publicFun)
Source File Name : "SymbolsWithDifferentVisibilities.kt"
Package Name : "org.jetbrains.sample"
Function Name : "publicFun"
TypeAlias (org.jetbrains.sample.TypeAliasA)
Package Name : "org.jetbrains.sample"
ClassId : "org/jetbrains/sample/TypeAliasA"
TypeAlias (org.jetbrains.sample.TypeAliasB)
Package Name : "org.jetbrains.sample"
ClassId : "org/jetbrains/sample/TypeAliasB"
Property (org.jetbrains.sample.a.aProperty)
Source File Name : "ACallables.kt"
Package Name : "org.jetbrains.sample.a"
Property Name : "aProperty"
Function (org.jetbrains.sample.a.aFunction)
Source File Name : "ACallables.kt"
Package Name : "org.jetbrains.sample.a"
Function Name : "aFunction"
Class (org.jetbrains.sample.a.AClass)
Source File Name : "AClass.kt"
Package Name : "org.jetbrains.sample.a"
ClassId : "org/jetbrains/sample/a/AClass"
Class (org.jetbrains.sample.a.AObject)
Source File Name : "AObject.kt"
Package Name : "org.jetbrains.sample.a"
ClassId : "org/jetbrains/sample/a/AObject"
Class (org.jetbrains.sample.b.BClass)
Source File Name : "BClass.kt"
Package Name : "org.jetbrains.sample.b"
ClassId : "org/jetbrains/sample/b/BClass"
Function (org.jetbrains.sample.filePrivateSymbolsClash.foo)
Source File Name : "A.kt"
Package Name : "org.jetbrains.sample.filePrivateSymbolsClash"
Function Name : "foo"
Function (org.jetbrains.sample.filePrivateSymbolsClash.foo)
Source File Name : "B.kt"
Package Name : "org.jetbrains.sample.filePrivateSymbolsClash"
Function Name : "foo"
@@ -0,0 +1,11 @@
plugins {
kotlin("multiplatform")
}
kotlin {
macosArm64()
macosX64()
linuxX64()
linuxArm64()
mingwX64()
}
@@ -0,0 +1,3 @@
# https://youtrack.jetbrains.com/issue/KT-65985
kotlin.native.toolchain.enabled=false
kotlin.native.distribution.downloadFromMaven=false
@@ -0,0 +1,14 @@
@file:Suppress("unused")
import kotlin.random.Random
/*
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
val rootPkgProperty get() = Random.nextInt()
fun rootPkgFunction() = Random.nextInt()
fun rootPkgFunction(seed: Int) = Random(seed).nextInt()
@@ -0,0 +1,10 @@
@file:Suppress("unused")
/*
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
class RootPkgClass {
fun foo() = Unit
class Nested
}
@@ -0,0 +1,12 @@
/*
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
@file:Suppress("unused")
package org.jetbrains.sample
const val constantInt = 42
const val constantString = "Hello There!"
@@ -0,0 +1,14 @@
/*
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
@file:Suppress("unused")
package org.jetbrains.sample
import org.jetbrains.sample.a.AClass
fun AClass.aExtensionFun() = this.toString()
val AClass.aExtensionProperty get() = this.toString()
@@ -0,0 +1,16 @@
/*
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
@file:Suppress("unused")
package org.jetbrains.sample
class GenericClass<T> {
fun foo(value: T) = value
}
fun <T> genericFun(value: T) = value.toString()
val <T> T.genericProperty get() = toString()
@@ -0,0 +1,20 @@
/*
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
@file:Suppress("unused")
package org.jetbrains.sample
private class PrivateClass
internal class InternalClass
class PublicClass
private fun privateFun() = 42
internal fun internalFun() = 42
fun publicFun() = 42
@@ -0,0 +1,15 @@
/*
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
@file:Suppress("unused")
package org.jetbrains.sample
import org.jetbrains.sample.a.AClass
import org.jetbrains.sample.b.BClass
typealias TypeAliasA = AClass
internal typealias TypeAliasB = BClass
@@ -0,0 +1,16 @@
/*
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
@file:Suppress("unused")
package org.jetbrains.sample.a
import kotlin.random.Random
val aProperty get() = Random.nextInt()
fun aFunction() = Random.nextInt()
fun aFunction(seed: Int) = Random(seed).nextInt()
@@ -0,0 +1,12 @@
/*
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
@file:Suppress("unused")
package org.jetbrains.sample.a
class AClass {
class Nested
}
@@ -0,0 +1,12 @@
/*
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
@file:Suppress("unused")
package org.jetbrains.sample.a
object AObject {
class Nested
}
@@ -0,0 +1,10 @@
/*
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
@file:Suppress("unused")
package org.jetbrains.sample.b
class BClass
@@ -0,0 +1,10 @@
/*
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
@file:Suppress("unused")
package org.jetbrains.sample.filePrivateSymbolsClash
private fun foo() = 42
@@ -0,0 +1,10 @@
/*
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
@file:Suppress("unused")
package org.jetbrains.sample.filePrivateSymbolsClash
private fun foo() = 42
@@ -167,7 +167,10 @@ fun Project.configureKotlinCompilationOptions() {
// This is a workaround for KT-50876, but with no clear explanation why doFirst is used.
// However, KGP with Native targets is used in the native-xctest project, and this code fails with
// The value for property 'freeCompilerArgs' is final and cannot be changed any further.
if (project.path != ":native:kotlin-test-native-xctest" && !project.path.startsWith(":native:objcexport-header-generator")) {
if (project.path != ":native:kotlin-test-native-xctest" &&
!project.path.startsWith(":native:objcexport-header-generator") &&
!project.path.startsWith(":native:analysis-api-klib-reader")
) {
doFirst {
if (!useAbsolutePathsInKlib) {
@Suppress("DEPRECATION")
+2
View File
@@ -118,6 +118,8 @@ include ":benchmarks",
":native:kotlin-klib-commonizer-embeddable",
":native:executors",
":native:base",
":native:analysis-api-klib-reader",
":native:analysis-api-klib-reader:testProject",
":native:objcexport-header-generator",
":native:objcexport-header-generator-k1",
":native:objcexport-header-generator-analysis-api",