[ObjCExport] Split ObjCExport into K1 and Analysis Api implementation

FL-23390
^KT-64168 Fixed
This commit is contained in:
Sebastian Sellmair
2023-12-06 18:14:00 +01:00
committed by Space Team
parent 3e57265fcb
commit e409c60780
95 changed files with 1042 additions and 383 deletions
+1
View File
@@ -7,6 +7,7 @@
<w>instantiator</w>
<w>interops</w>
<w>klibrary</w>
<w>namer</w>
<w>undispatched</w>
</words>
</dictionary>
+2
View File
@@ -337,6 +337,8 @@ val projectsUsedInIntelliJKotlinPlugin =
arrayOf(
":native:base",
":native:objcexport-header-generator",
":native:objcexport-header-generator-k1",
":native:objcexport-header-generator-analysis-api",
":compiler:ir.serialization.native"
)
@@ -156,6 +156,7 @@ dependencies {
compilerApi project(":kotlin-native:utilities:basic-utils")
compilerApi project(":native:objcexport-header-generator")
compilerApi project(":native:objcexport-header-generator-k1")
compilerApi project(":native:base")
compilerImplementation project(":kotlin-compiler")
@@ -13,7 +13,6 @@ import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportProblemCollector
import org.jetbrains.kotlin.backend.konan.objcexport.dumpObjCHeader
import org.jetbrains.kotlin.container.*
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.deprecation.DeprecationResolver
+1
View File
@@ -164,6 +164,7 @@ dependencies {
distPack project(':kotlin-native:Interop:Skia')
distPack project(':kotlin-native:backend.native')
distPack project(':native:objcexport-header-generator')
distPack project(':native:objcexport-header-generator-k1')
distPack project(':native:base')
distPack project(':kotlin-native:utilities:cli-runner')
distPack project(':kotlin-native:utilities:basic-utils')
@@ -0,0 +1,61 @@
# ObjC Export Header generator
This tool is used for 'translating' Kotlin code into ObjC 'stubs' and ultimately rendering ObjC header files.
## Usage
### CLI: Building .framework files
The CLI will use this module for building the corresponding .framework binaries from Kotlin. This method will operate on previously
built .klib binaries (with fully validated frontend). The 'klibs' will be deserialized and headers and bridges between
Kotlin and ObjC will be built. This mode currently operates on K1 based descriptors
### IDE: Providing Kotlin <-> ObjC/Swift cross language support
In order for Fleet to provide tooling that is capable of refactoring symbols between Kotlin and ObjC, this tool is used.
Example:
given the following Kotlin code
```kotlin
@ObjCName("FooObjC")
class Foo
```
and the following Swift usage
```swift
func bar() {
FooObjC()
}
```
refactoring inside either Kotlin or Swift will be consistent across Kotlin and Swift.
## Two Implementations (K1, Analysis Api)
There are currently two implementations for this tool
### K1
This is the K1 (descriptor based) implementation that is currently used by the CLI and K1 based IDEs.
### Analysis Api (WiP)
This implementation is currently 'work in progress' and shall replace the K1 usage in the IDE later.
This implementation _could_ theoretically also replace the K1 implementation if necessary.
## Testing
### Run all tests
```
./gradlew :native:objcexport-header-generator:check
```
The most important test is [ObjCExportHeaderGeneratorTest.kt](test%2Forg%2Fjetbrains%2Fkotlin%2Fbackend%2Fkonan%2Ftests%2FObjCExportHeaderGeneratorTest.kt)
as this test defines the contract of how a header shall be generated from a given Kotlin input. This test can run against
both implementations.
```
./gradlew :native:objcexport-header-generator:testK1
./gradlew :native:objcexport-header-generator:testAnalysisApi
```
Note: Since the Analysis Api implementation is WIP yet, this test can be used for debugging, but is not fully implemented yet.
@@ -1,23 +1,23 @@
@file:Suppress("HasPlatformType")
plugins {
kotlin("jvm")
}
sourceSets {
"main" { projectDefault() }
"test" { projectDefault() }
}
dependencies {
implementation(intellijCore())
implementation(project(":compiler:cli-base"))
implementation(project(":compiler:cli-common"))
implementation(project(":compiler:ir.objcinterop"))
implementation(project(":compiler:ir.serialization.native"))
implementation(project(":core:compiler.common.native"))
implementation(project(":core:descriptors"))
implementation(project(":native:base"))
implementation(project(":native:kotlin-native-utils"))
api(intellijCore())
api(project(":native:base"))
api(project(":core:compiler.common"))
testImplementation(libs.junit.jupiter.api)
testImplementation(libs.junit.jupiter.params)
testImplementation(project(":compiler:tests-common", "tests-jar"))
testRuntimeOnly(libs.junit.jupiter.engine)
testApi(libs.junit.jupiter.api)
testApi(libs.junit.jupiter.engine)
testApi(libs.junit.jupiter.params)
testApi(project(":compiler:tests-common", "tests-jar"))
}
kotlin {
@@ -26,8 +26,40 @@ kotlin {
}
}
nativeTest("test", tag = null) {
useJUnitPlatform()
systemProperty("projectDir", projectDir.absolutePath)
workingDir(rootProject.projectDir)
/* Configure tests */
testsJar()
val k1TestRuntimeClasspath by configurations.creating
val analysisApiRuntimeClasspath by configurations.creating
dependencies {
k1TestRuntimeClasspath(project(":native:objcexport-header-generator-k1"))
k1TestRuntimeClasspath(projectTests(":native:objcexport-header-generator-k1"))
analysisApiRuntimeClasspath(project(":native:objcexport-header-generator-analysis-api"))
analysisApiRuntimeClasspath(projectTests(":native:objcexport-header-generator-analysis-api"))
}
tasks.test.configure {
enabled = false
}
nativeTest("testK1", tag = null) {
useJUnitPlatform()
enableJunit5ExtensionsAutodetection()
classpath += k1TestRuntimeClasspath
}
nativeTest("testAnalysisApi", tag = null) {
useJUnitPlatform()
enableJunit5ExtensionsAutodetection()
testClassesDirs += files(sourceSets.test.map { it.output.classesDirs })
classpath += analysisApiRuntimeClasspath
}
tasks.check.configure {
dependsOn("testK1")
dependsOn(":native:objcexport-header-generator-k1:check")
dependsOn(":native:objcexport-header-generator-analysis-api:check")
}
@@ -0,0 +1,30 @@
plugins {
kotlin("jvm")
}
kotlin {
compilerOptions {
/* Required to use Analysis Api */
freeCompilerArgs.add("-Xcontext-receivers")
}
}
dependencies {
api(project(":native:objcexport-header-generator"))
api(project(":analysis:analysis-api"))
testImplementation(projectTests(":native:objcexport-header-generator"))
testApi(project(":analysis:analysis-api-standalone"))
}
sourceSets {
"main" { projectDefault() }
"test" { projectDefault() }
}
testsJar()
nativeTest("test", tag = null) {
useJUnitPlatform()
enableJunit5ExtensionsAutodetection()
}
@@ -0,0 +1,47 @@
/*
* Copyright 2010-2023 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.objcexport
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.symbols.KtClassLikeSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtPropertySymbol
import org.jetbrains.kotlin.analysis.api.symbols.nameOrAnonymous
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportClassOrProtocolName
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportPropertyName
interface KtObjCExportNamer {
context(KtAnalysisSession)
fun getClassOrProtocolName(symbol: KtClassLikeSymbol): ObjCExportClassOrProtocolName
context(KtAnalysisSession)
fun getPropertyName(symbol: KtPropertySymbol): ObjCExportPropertyName
}
fun ObjCExportNamer(): KtObjCExportNamer {
return ObjCExportNamerImpl()
}
private class ObjCExportNamerImpl : KtObjCExportNamer {
context(KtAnalysisSession)
override fun getClassOrProtocolName(symbol: KtClassLikeSymbol): ObjCExportClassOrProtocolName {
val resolvedObjCNameAnnotation = symbol.resolveObjCNameAnnotation()
return ObjCExportClassOrProtocolName(
objCName = resolvedObjCNameAnnotation.objCName ?: symbol.nameOrAnonymous.asString(),
swiftName = resolvedObjCNameAnnotation.swiftName ?: symbol.nameOrAnonymous.asString()
)
}
context(KtAnalysisSession)
override fun getPropertyName(symbol: KtPropertySymbol): ObjCExportPropertyName {
val resolveObjCNameAnnotation = symbol.resolveObjCNameAnnotation()
return ObjCExportPropertyName(
objCName = resolveObjCNameAnnotation.objCName ?: symbol.name.asString(),
swiftName = resolveObjCNameAnnotation.swiftName ?: symbol.name.asString()
)
}
}
@@ -0,0 +1,72 @@
/*
* Copyright 2010-2023 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.objcexport
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.annotations.KtConstantAnnotationValue
import org.jetbrains.kotlin.analysis.api.base.KtConstantValue
import org.jetbrains.kotlin.analysis.api.base.KtConstantValue.KtStringConstantValue
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtAnnotatedSymbol
import org.jetbrains.kotlin.backend.konan.KonanFqNames
/**
* Represents the values resolved from the [kotlin.native.ObjCName] annotation.
*
* ### Example
*
* **Given a class Foo**
* ```kotlin
* @ObjCName("FooObjC", "FooSwift", true)
* class Foo
* ```
*
* **Given class Foo being analyzed**
* ```kotlin
* val foo = getFooClassOrObjectSymbol()
* // ^
* // Imaginary method to get the symbol 'Foo' from above
*
* val resolvedObjCNameAnnotation = foo.resolveObjCNameAnnotation()
* // ^
* // objCName = "FooObjC"
* // swiftName = "FooSwift"
* // isExaclt = true
* ```
*/
internal class KtResolvedObjCNameAnnotation(
val objCName: String?,
val swiftName: String?,
val isExact: Boolean,
)
context(KtAnalysisSession)
internal fun KtAnnotatedSymbol.resolveObjCNameAnnotation(): KtResolvedObjCNameAnnotation {
var objCName: String? = null
var swiftName: String? = null
var isExact = false
annotationsList.annotations.find { it.classId?.asSingleFqName() == KonanFqNames.objCName }?.let { annotation ->
annotation.arguments.forEach { argument ->
when (argument.name.identifier) {
"name" -> objCName = argument.expression.let { it as? KtConstantAnnotationValue }
?.constantValue?.let { it as KtStringConstantValue }
?.value
"swiftName" -> swiftName = argument.expression.let { it as? KtConstantAnnotationValue }
?.constantValue?.let { it as KtStringConstantValue }
?.value
"exact" -> isExact = argument.expression.let { it as? KtConstantAnnotationValue }
?.constantValue?.let { it as KtConstantValue.KtBooleanConstantValue }
?.value ?: isExact
}
}
}
return KtResolvedObjCNameAnnotation(
objCName = objCName,
swiftName = swiftName,
isExact = isExact
)
}
@@ -0,0 +1,28 @@
/*
* Copyright 2010-2023 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.objcexport.testUtils
import org.jetbrains.kotlin.backend.konan.tests.ObjCExportHeaderGeneratorTest.HeaderGenerator
import org.junit.jupiter.api.extension.ExtensionContext
import org.junit.jupiter.api.extension.ParameterContext
import org.junit.jupiter.api.extension.ParameterResolver
import java.io.File
class AnalysisApiHeaderGeneratorExtension : ParameterResolver {
override fun supportsParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Boolean {
return parameterContext.parameter.type == HeaderGenerator::class.java
}
override fun resolveParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Any {
return AnalysisApiHeaderGenerator
}
}
object AnalysisApiHeaderGenerator : HeaderGenerator {
override fun generateHeaders(root: File): String {
TODO("Analysis Api based header generation in not yet implemented")
}
}
@@ -0,0 +1,75 @@
/*
* Copyright 2010-2023 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.objcexport.testUtils
import org.intellij.lang.annotations.Language
import org.jetbrains.kotlin.psi.KtFile
import org.junit.jupiter.api.extension.AfterEachCallback
import org.junit.jupiter.api.extension.ExtensionContext
import org.junit.jupiter.api.extension.ExtensionContext.Namespace
import org.junit.jupiter.api.extension.ParameterContext
import org.junit.jupiter.api.extension.ParameterResolver
import java.io.File
import java.nio.file.Files
/**
* Provides ability to quickly write tests with 'inline source code' aka passing Kotlin source code as String.
*
* This interface can be injected into any test class constructor.
*
* ### Example
* ```
* class MyTest(
* private val inlineSourceCodeAnalysis: InlineSourceCodeAnalysis
* ) {
* @Test
* fun `test - something important`() {
* val myFile = inlineSourceCodeAnalysis.createKtFile("class Foo")
* analyze(myFile) {
* // Use analysis session to write advanced tests
* }
* }
* }
* ```
*/
interface InlineSourceCodeAnalysis {
fun createKtFile(@Language("kotlin") sourceCode: String): KtFile
}
/**
* Extension used to inject an instance of [InlineSourceCodeAnalysis] into tests.
*/
class InlineSourceCodeAnalysisExtension : ParameterResolver, AfterEachCallback {
private companion object {
val namespace: Namespace = Namespace.create(Any())
val tempDirKey = Any()
}
override fun supportsParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Boolean {
return parameterContext.parameter.type == InlineSourceCodeAnalysis::class.java
}
override fun resolveParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Any {
val temporaryDirectory = Files.createTempDirectory("inlineSourceCode").toFile()
extensionContext.getStore(namespace.append(extensionContext.requiredTestClass)).put(tempDirKey, temporaryDirectory)
return InlineSourceCodeAnalysisImpl(temporaryDirectory)
}
override fun afterEach(context: ExtensionContext) {
context.getStore(namespace.append(context.requiredTestClass))?.get(tempDirKey, File::class.java)?.deleteRecursively()
}
}
/**
* Simple implementation [InlineSourceCodeAnalysis]
*/
private class InlineSourceCodeAnalysisImpl(private val tempDir: File) : InlineSourceCodeAnalysis {
override fun createKtFile(@Language("kotlin") sourceCode: String): KtFile {
return createStandaloneAnalysisApiSession(tempDir, listOf(sourceCode))
.modulesWithFiles.entries.single()
.value.single() as KtFile
}
}
@@ -0,0 +1,69 @@
/*
* Copyright 2010-2023 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.objcexport.testUtils
import org.jetbrains.kotlin.analysis.api.KtAnalysisApiInternals
import org.jetbrains.kotlin.analysis.api.lifetime.KtLifetimeTokenProvider
import org.jetbrains.kotlin.analysis.api.standalone.KtAlwaysAccessibleLifetimeTokenProvider
import org.jetbrains.kotlin.analysis.api.standalone.StandaloneAnalysisAPISession
import org.jetbrains.kotlin.analysis.api.standalone.buildStandaloneAnalysisAPISession
import org.jetbrains.kotlin.analysis.project.structure.builder.buildKtLibraryModule
import org.jetbrains.kotlin.analysis.project.structure.builder.buildKtSourceModule
import org.jetbrains.kotlin.backend.konan.testUtils.kotlinNativeStdlibPath
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.platform.konan.NativePlatforms
import java.io.File
import kotlin.io.path.Path
/**
* Creates a standalone analysis session from Kotlin source code passed as [kotlinSources]
*/
fun createStandaloneAnalysisApiSession(
tempDir: File,
kotlinSources: List<String>,
): StandaloneAnalysisAPISession {
val testModuleRoot = tempDir.resolve("testModule")
testModuleRoot.mkdirs()
kotlinSources.forEachIndexed { index, kotlinSource ->
testModuleRoot.resolve("TestSources$index.kt").apply {
writeText(kotlinSource)
}
}
return createStandaloneAnalysisApiSession(listOf(testModuleRoot))
}
/**
* Creates a standalone analysis session from [kotlinFiles] on disk.
* The Kotlin/Native stdlib will be provided as dependency
*/
fun createStandaloneAnalysisApiSession(kotlinFiles: List<File>): StandaloneAnalysisAPISession {
val currentArchitectureTarget = HostManager.host
val nativePlatform = NativePlatforms.nativePlatformByTargets(listOf(currentArchitectureTarget))
return buildStandaloneAnalysisAPISession {
@OptIn(KtAnalysisApiInternals::class)
registerProjectService(KtLifetimeTokenProvider::class.java, KtAlwaysAccessibleLifetimeTokenProvider())
buildKtModuleProvider {
platform = nativePlatform
val kLib = addModule(
buildKtLibraryModule {
addBinaryRoot(Path(kotlinNativeStdlibPath))
platform = nativePlatform
libraryName = "klib"
}
)
addModule(
buildKtSourceModule {
addSourceRoots(kotlinFiles.map { it.toPath() })
addRegularDependency(kLib)
platform = nativePlatform
moduleName = "source"
}
)
}
}
}
@@ -0,0 +1,37 @@
/*
* Copyright 2010-2023 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.objcexport.tests
import org.jetbrains.kotlin.analysis.api.analyze
import org.jetbrains.kotlin.analysis.api.symbols.KtNamedClassOrObjectSymbol
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportClassOrProtocolName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.objcexport.ObjCExportNamer
import org.jetbrains.kotlin.objcexport.testUtils.InlineSourceCodeAnalysis
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
class KtObjCExportNamerTest(
private val inlineSourceCodeAnalysis: InlineSourceCodeAnalysis,
) {
private val namer = ObjCExportNamer()
@Test
fun `test - simple class`() {
val foo = inlineSourceCodeAnalysis.createKtFile("class Foo")
analyze(foo) {
val fooSymbol = foo.getFileSymbol().getFileScope()
.getClassifierSymbols(Name.identifier("Foo"))
.single() as KtNamedClassOrObjectSymbol
assertEquals(
ObjCExportClassOrProtocolName("Foo", "Foo"),
namer.getClassOrProtocolName(fooSymbol)
)
}
}
}
@@ -0,0 +1,69 @@
/*
* Copyright 2010-2023 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.objcexport.tests
import org.jetbrains.kotlin.objcexport.resolveObjCNameAnnotation
import org.jetbrains.kotlin.analysis.api.analyze
import org.jetbrains.kotlin.analysis.api.symbols.KtClassLikeSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionSymbol
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.objcexport.testUtils.InlineSourceCodeAnalysis
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNull
import kotlin.test.assertTrue
class KtResolvedObjCNameAnnotationTest(
private val inlineSourceCodeAnalysis: InlineSourceCodeAnalysis,
) {
@Test
fun `test - class - no ObjCName annotation`() {
val ktFile = inlineSourceCodeAnalysis.createKtFile("class Foo")
analyze(ktFile) {
val fooSymbol = ktFile.getFileSymbol().getFileScope().getClassifierSymbols(Name.identifier("Foo")).single() as KtClassLikeSymbol
val resolvedObjCAnnotation = fooSymbol.resolveObjCNameAnnotation()
assertNull(resolvedObjCAnnotation.swiftName)
assertNull(resolvedObjCAnnotation.objCName)
assertFalse(resolvedObjCAnnotation.isExact)
}
}
@Test
fun `test - class - with ObjCName annotation`() {
val ktFile = inlineSourceCodeAnalysis.createKtFile(
"""
@kotlin.native.ObjCName("FooObjC", "FooSwift", true)
class Foo
""".trimIndent()
)
analyze(ktFile) {
val fooSymbol = ktFile.getFileSymbol().getFileScope().getClassifierSymbols(Name.identifier("Foo")).single() as KtClassLikeSymbol
val resolvedObjCAnnotation = fooSymbol.resolveObjCNameAnnotation()
assertEquals("FooObjC", resolvedObjCAnnotation.objCName)
assertEquals("FooSwift", resolvedObjCAnnotation.swiftName)
assertTrue(resolvedObjCAnnotation.isExact)
}
}
@Test
fun `test - function - with ObjCName annotation`() {
val ktFile = inlineSourceCodeAnalysis.createKtFile(
"""
@kotlin.native.ObjCName("fooObjC", "fooSwift", true)
fun foo() = Unit
""".trimIndent()
)
analyze(ktFile) {
val fooSymbol = ktFile.getFileSymbol().getFileScope().getCallableSymbols(Name.identifier("foo")).single() as KtFunctionSymbol
val resolvedObjCAnnotation = fooSymbol.resolveObjCNameAnnotation()
assertEquals("fooObjC", resolvedObjCAnnotation.objCName)
assertEquals("fooSwift", resolvedObjCAnnotation.swiftName)
assertTrue(resolvedObjCAnnotation.isExact)
}
}
}
@@ -0,0 +1,6 @@
#
# Copyright 2010-2023 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.
#
org.jetbrains.kotlin.objcexport.testUtils.AnalysisApiHeaderGeneratorExtension
org.jetbrains.kotlin.objcexport.testUtils.InlineSourceCodeAnalysisExtension
@@ -0,0 +1,31 @@
plugins {
kotlin("jvm")
}
sourceSets {
"main" { projectDefault() }
"test" { projectDefault() }
}
dependencies {
api(project(":native:objcexport-header-generator"))
implementation(project(":compiler:cli-base"))
implementation(project(":compiler:ir.objcinterop"))
implementation(project(":compiler:ir.serialization.native"))
implementation(project(":core:descriptors"))
testImplementation(projectTests(":native:objcexport-header-generator"))
}
kotlin {
compilerOptions {
optIn.add("org.jetbrains.kotlin.backend.konan.InternalKotlinNativeApi")
}
}
testsJar()
nativeTest("test", tag = null) {
useJUnitPlatform()
enableJunit5ExtensionsAutodetection()
}
@@ -46,8 +46,16 @@ internal interface ObjCExportNameTranslator {
}
interface ObjCExportNamer {
data class ClassOrProtocolName(val swiftName: String, val objCName: String, val binaryName: String = objCName)
data class PropertyName(val swiftName: String, val objCName: String)
data class ClassOrProtocolName(
override val swiftName: String,
override val objCName: String,
override val binaryName: String = objCName,
) : ObjCExportClassOrProtocolName
data class PropertyName(
override val swiftName: String,
override val objCName: String,
) : ObjCExportPropertyName
interface Configuration {
val topLevelNamePrefix: String
@@ -0,0 +1,111 @@
/*
* Copyright 2010-2023 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.backend.konan.objcexport
import org.jetbrains.kotlin.backend.common.serialization.extractSerializedKdocString
import org.jetbrains.kotlin.backend.common.serialization.metadata.findKDocString
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.resolve.source.PsiSourceElement
fun ObjCExportStubOrigin(descriptor: DeclarationDescriptor?): ObjCExportStubOrigin? {
if (descriptor == null) return null
if (descriptor is DeclarationDescriptorWithSource) {
return ObjCExportStubOrigin.Source(descriptor.name, descriptor.findKDocString(), (descriptor.source as? PsiSourceElement)?.psi)
}
assert(descriptor is DeserializedDescriptor) { "Expected '$descriptor' to implement ${DeserializedDescriptor::class.simpleName}" }
return ObjCExportStubOrigin.Binary(descriptor.name, descriptor.extractSerializedKdocString())
}
fun ObjCProtocolImpl(
name: String,
descriptor: ClassDescriptor,
superProtocols: List<String>,
members: List<ObjCExportStub>,
attributes: List<String> = emptyList(),
comment: ObjCComment? = null,
) = ObjCProtocolImpl(
name = name,
comment = comment,
origin = ObjCExportStubOrigin(descriptor),
attributes = attributes,
superProtocols = superProtocols,
members = members
)
fun ObjCInterfaceImpl(
name: String,
generics: List<ObjCGenericTypeDeclaration> = emptyList(),
descriptor: ClassDescriptor? = null,
superClass: String? = null,
superClassGenerics: List<ObjCNonNullReferenceType> = emptyList(),
superProtocols: List<String> = emptyList(),
categoryName: String? = null,
members: List<ObjCExportStub> = emptyList(),
attributes: List<String> = emptyList(),
comment: ObjCComment? = null,
) = ObjCInterfaceImpl(
name = name,
comment = comment,
origin = ObjCExportStubOrigin(descriptor),
attributes = attributes,
superProtocols = superProtocols,
members = members,
categoryName = categoryName,
generics = generics,
superClass = superClass,
superClassGenerics = superClassGenerics
)
fun ObjCMethod(
descriptor: DeclarationDescriptor?,
isInstanceMethod: Boolean,
returnType: ObjCType,
selectors: List<String>,
parameters: List<ObjCParameter>,
attributes: List<String>,
comment: ObjCComment? = null,
) = ObjCMethod(
comment = comment,
origin = ObjCExportStubOrigin(descriptor),
isInstanceMethod = isInstanceMethod,
returnType = returnType,
selectors = selectors,
parameters = parameters,
attributes = attributes
)
fun ObjCParameter(
name: String,
descriptor: ParameterDescriptor?,
type: ObjCType,
) = ObjCParameter(
name = name,
origin = ObjCExportStubOrigin(descriptor),
type = type,
todo = null
)
fun ObjCProperty(
name: String,
descriptor: DeclarationDescriptorWithSource?,
type: ObjCType,
propertyAttributes: List<String>,
setterName: String? = null,
getterName: String? = null,
declarationAttributes: List<String> = emptyList(),
comment: ObjCComment? = null,
) = ObjCProperty(
name = name,
comment = comment,
origin = ObjCExportStubOrigin(descriptor),
type = type,
propertyAttributes = propertyAttributes,
setterName = setterName,
getterName = getterName,
declarationAttributes = declarationAttributes
)
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2023 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.
*/
@@ -0,0 +1,23 @@
/*
* Copyright 2010-2023 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.backend.konan.objcexport
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
fun ObjCGenericTypeParameterUsage(
typeParameterDescriptor: TypeParameterDescriptor,
namer: ObjCExportNamer,
) = ObjCGenericTypeParameterUsage(
typeName = namer.getTypeParameterName(typeParameterDescriptor)
)
fun ObjCGenericTypeParameterDeclaration(
typeParameterDescriptor: TypeParameterDescriptor,
namer: ObjCExportNamer,
) = ObjCGenericTypeParameterDeclaration(
typeName = namer.getTypeParameterName(typeParameterDescriptor),
variance = ObjCVariance.fromKotlinVariance(typeParameterDescriptor.variance)
)
@@ -1,3 +1,8 @@
/*
* Copyright 2010-2023 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.backend.konan.objcexport
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
@@ -1,6 +1,6 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
* Copyright 2010-2023 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.backend.konan.objcexport
@@ -6,15 +6,46 @@
package org.jetbrains.kotlin.backend.konan.testUtils
import com.intellij.openapi.Disposable
import com.intellij.openapi.util.Disposer
import org.jetbrains.kotlin.backend.konan.UnitSuspendFunctionObjCExport
import org.jetbrains.kotlin.backend.konan.objcexport.*
import org.jetbrains.kotlin.backend.konan.tests.ObjCExportHeaderGeneratorTest
import org.jetbrains.kotlin.builtins.DefaultBuiltIns
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.junit.jupiter.api.extension.AfterEachCallback
import org.junit.jupiter.api.extension.ExtensionContext
import org.junit.jupiter.api.extension.ParameterContext
import org.junit.jupiter.api.extension.ParameterResolver
import java.io.File
object Fe10ObjCExportHeaderGenerator : AbstractObjCExportHeaderGeneratorTest.ObjCExportHeaderGenerator {
override fun generateHeaders(disposable: Disposable, root: File): String {
class Fe10HeaderGeneratorExtension : ParameterResolver, AfterEachCallback {
companion object {
val namespace = ExtensionContext.Namespace.create(Fe10HeaderGeneratorExtension::class)
val disposableKey = "disposable"
}
override fun supportsParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Boolean {
return parameterContext.parameter.type == ObjCExportHeaderGeneratorTest.HeaderGenerator::class.java
}
override fun resolveParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Any {
val disposable = Disposer.newDisposable()
extensionContext.getStore(namespace).put(disposableKey, disposable)
return Fe10HeaderGeneratorImpl(disposable)
}
override fun afterEach(context: ExtensionContext) {
val disposable = context.getStore(namespace).get(disposableKey, Disposable::class.java) ?: return
Disposer.dispose(disposable)
}
}
private class Fe10HeaderGeneratorImpl(private val disposable: Disposable) :
ObjCExportHeaderGeneratorTest.HeaderGenerator {
override fun generateHeaders(root: File): String {
val headerGenerator = createObjCExportHeaderGenerator(disposable, root)
headerGenerator.translateModuleDeclarations()
return headerGenerator.build().joinToString(System.lineSeparator())
@@ -105,7 +105,7 @@ private object DependenciesContainerImpl : CommonDependenciesContainer {
private val klibFactory = KlibMetadataFactories(::KonanBuiltIns, DynamicTypeDeserializer)
private val stdlibModuleDescriptor = klibFactory.DefaultDeserializedDescriptorFactory.createDescriptor(
library = resolveSingleFileKlib(org.jetbrains.kotlin.konan.file.File("$konanHomePath/klib/common/stdlib")),
library = resolveSingleFileKlib(org.jetbrains.kotlin.konan.file.File(kotlinNativeStdlibPath)),
languageVersionSettings = createLanguageVersionSettings(),
builtIns = DefaultBuiltIns.Instance,
storageManager = LockBasedStorageManager.NO_LOCKS,
@@ -52,7 +52,6 @@ interface InlineSourceTestEnvironment {
val testTempDir: File
}
interface InlineSourceCodeCollector {
fun source(@Language("kotlin") sourceCode: String)
}
@@ -0,0 +1,5 @@
#
# Copyright 2010-2023 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.
#
org.jetbrains.kotlin.backend.konan.testUtils.Fe10HeaderGeneratorExtension
@@ -0,0 +1,46 @@
/*
* Copyright 2010-2023 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.backend.konan.objcexport
sealed interface ObjCExportName {
val swiftName: String
val objCName: String
}
interface ObjCExportClassOrProtocolName : ObjCExportName {
val binaryName: String
}
interface ObjCExportPropertyName : ObjCExportName
fun ObjCExportClassOrProtocolName(
swiftName: String,
objCName: String,
binaryName: String = objCName,
): ObjCExportClassOrProtocolName = ObjCExportClassOrProtocolNameImpl(
swiftName = swiftName,
objCName = objCName,
binaryName = binaryName
)
private data class ObjCExportClassOrProtocolNameImpl(
override val swiftName: String,
override val objCName: String,
override val binaryName: String,
) : ObjCExportClassOrProtocolName
fun ObjCExportPropertyName(
swiftName: String,
objCName: String,
): ObjCExportPropertyName = ObjCExportPropertyNameImpl(
swiftName = swiftName,
objCName = objCName
)
private data class ObjCExportPropertyNameImpl(
override val swiftName: String,
override val objCName: String,
) : ObjCExportPropertyName
@@ -6,24 +6,8 @@
package org.jetbrains.kotlin.backend.konan.objcexport
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.backend.common.serialization.extractSerializedKdocString
import org.jetbrains.kotlin.backend.common.serialization.metadata.findKDocString
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource
import org.jetbrains.kotlin.descriptors.DeserializedDescriptor
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.source.PsiSourceElement
fun ObjCExportStubOrigin(descriptor: DeclarationDescriptor?): ObjCExportStubOrigin? {
if (descriptor == null) return null
if (descriptor is DeclarationDescriptorWithSource) {
return ObjCExportStubOrigin.Source(descriptor.name, descriptor.findKDocString(), (descriptor.source as? PsiSourceElement)?.psi)
}
assert(descriptor is DeserializedDescriptor) { "Expected '$descriptor' to implement ${DeserializedDescriptor::class.simpleName}" }
return ObjCExportStubOrigin.Binary(descriptor.name, descriptor.extractSerializedKdocString())
}
sealed class ObjCExportStubOrigin {
@@ -1,6 +1,6 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
* Copyright 2010-2023 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.backend.konan.objcexport
@@ -1,10 +1,12 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
* Copyright 2010-2023 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.backend.konan.objcexport
import org.jetbrains.kotlin.backend.konan.InternalKotlinNativeApi
object StubRenderer {
fun render(stub: ObjCExportStub): List<String> = render(stub, false)
@@ -21,7 +23,8 @@ object StubRenderer {
return kDoc.size
}
internal fun render(stub: ObjCExportStub, shouldExportKDoc: Boolean): List<String> = collect {
@InternalKotlinNativeApi
fun render(stub: ObjCExportStub, shouldExportKDoc: Boolean): List<String> = collect {
stub.run {
val (kDocEnding, commentBlockEnding) = if (comment?.contentLines == null) {
Pair("*/", null) // Close kDoc with `*/`, and print nothing after empty comment
@@ -1,12 +1,11 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
* Copyright 2010-2023 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.backend.konan.objcexport
import org.jetbrains.kotlin.backend.konan.InternalKotlinNativeApi
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import org.jetbrains.kotlin.types.Variance
sealed class ObjCType {
@@ -67,12 +66,8 @@ sealed class ObjCGenericTypeUsage : ObjCNonNullReferenceType() {
data class ObjCGenericTypeRawUsage(override val typeName: String) : ObjCGenericTypeUsage()
data class ObjCGenericTypeParameterUsage(
val typeParameterDescriptor: TypeParameterDescriptor,
val namer: ObjCExportNamer,
) : ObjCGenericTypeUsage() {
override val typeName: String
get() = namer.getTypeParameterName(typeParameterDescriptor)
}
) : ObjCGenericTypeUsage()
data class ObjCProtocolType(
val protocolName: String,
@@ -203,14 +198,9 @@ data class ObjCGenericTypeRawDeclaration(
) : ObjCGenericTypeDeclaration()
data class ObjCGenericTypeParameterDeclaration(
val typeParameterDescriptor: TypeParameterDescriptor,
val namer: ObjCExportNamer,
) : ObjCGenericTypeDeclaration() {
override val typeName: String
get() = namer.getTypeParameterName(typeParameterDescriptor)
override val typeName: String,
override val variance: ObjCVariance
get() = ObjCVariance.fromKotlinVariance(typeParameterDescriptor.variance)
}
) : ObjCGenericTypeDeclaration()
@InternalKotlinNativeApi
fun ObjCType.makeNullableIfReferenceOrPointer(): ObjCType = when (this) {
@@ -1,17 +1,12 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
* Copyright 2010-2023 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:JvmName("ObjCExportStubKt")
package org.jetbrains.kotlin.backend.konan.objcexport
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource
import org.jetbrains.kotlin.descriptors.ParameterDescriptor
@Deprecated("Use 'ObjCExportStub' instead", replaceWith = ReplaceWith("ObjCExportStub"))
@Suppress("unused")
typealias Stub<@Suppress("UNUSED_TYPEALIAS_PARAMETER") T> = ObjCExportStub
@@ -77,23 +72,7 @@ class ObjCProtocolImpl(
override val attributes: List<String>,
override val superProtocols: List<String>,
override val members: List<ObjCExportStub>,
) : ObjCProtocol() {
constructor(
name: String,
descriptor: ClassDescriptor,
superProtocols: List<String>,
members: List<ObjCExportStub>,
attributes: List<String> = emptyList(),
comment: ObjCComment? = null,
) : this(
name = name,
comment = comment,
origin = ObjCExportStubOrigin(descriptor),
attributes = attributes,
superProtocols = superProtocols,
members = members
)
}
) : ObjCProtocol()
class ObjCInterfaceImpl(
override val name: String,
@@ -106,31 +85,7 @@ class ObjCInterfaceImpl(
override val generics: List<ObjCGenericTypeDeclaration>,
override val superClass: String?,
override val superClassGenerics: List<ObjCNonNullReferenceType>,
) : ObjCInterface() {
constructor(
name: String,
generics: List<ObjCGenericTypeDeclaration> = emptyList(),
descriptor: ClassDescriptor? = null,
superClass: String? = null,
superClassGenerics: List<ObjCNonNullReferenceType> = emptyList(),
superProtocols: List<String> = emptyList(),
categoryName: String? = null,
members: List<ObjCExportStub> = emptyList(),
attributes: List<String> = emptyList(),
comment: ObjCComment? = null,
) : this(
name = name,
comment = comment,
origin = ObjCExportStubOrigin(descriptor),
attributes = attributes,
superProtocols = superProtocols,
members = members,
categoryName = categoryName,
generics = generics,
superClass = superClass,
superClassGenerics = superClassGenerics
)
}
) : ObjCInterface()
class ObjCMethod(
override val comment: ObjCComment?,
@@ -141,43 +96,15 @@ class ObjCMethod(
val parameters: List<ObjCParameter>,
val attributes: List<String>,
) : ObjCExportStub {
constructor(
descriptor: DeclarationDescriptor?,
isInstanceMethod: Boolean,
returnType: ObjCType,
selectors: List<String>,
parameters: List<ObjCParameter>,
attributes: List<String>,
comment: ObjCComment? = null,
) : this(
comment = comment,
origin = ObjCExportStubOrigin(descriptor),
isInstanceMethod = isInstanceMethod,
returnType = returnType,
selectors = selectors,
parameters = parameters,
attributes = attributes
)
override val name: String = buildMethodName(selectors, parameters)
}
class ObjCParameter private constructor(
class ObjCParameter(
override val name: String,
override val origin: ObjCExportStubOrigin?,
val type: ObjCType,
val todo: Nothing?
) : ObjCExportStub {
constructor(
name: String,
descriptor: ParameterDescriptor?,
type: ObjCType,
) : this(
name = name,
origin = ObjCExportStubOrigin(descriptor),
type = type
)
override val comment: Nothing? = null
}
@@ -190,27 +117,7 @@ class ObjCProperty(
val setterName: String? = null,
val getterName: String? = null,
val declarationAttributes: List<String> = emptyList(),
) : ObjCExportStub {
constructor(
name: String,
descriptor: DeclarationDescriptorWithSource?,
type: ObjCType,
propertyAttributes: List<String>,
setterName: String? = null,
getterName: String? = null,
declarationAttributes: List<String> = emptyList(),
comment: ObjCComment? = null,
) : this(
name = name,
comment = comment,
origin = ObjCExportStubOrigin(descriptor),
type = type,
propertyAttributes = propertyAttributes,
setterName = setterName,
getterName = getterName,
declarationAttributes = declarationAttributes
)
}
) : ObjCExportStub
private fun buildMethodName(selectors: List<String>, parameters: List<ObjCParameter>): String =
if (selectors.size == 1 && parameters.size == 0) {
@@ -1,39 +0,0 @@
/*
* Copyright 2010-2023 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.backend.konan.testUtils
import com.intellij.openapi.Disposable
import com.intellij.openapi.util.Disposer
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.junit.After
import java.io.File
import kotlin.test.fail
abstract class AbstractObjCExportHeaderGeneratorTest(
private val generator: ObjCExportHeaderGenerator,
) {
fun interface ObjCExportHeaderGenerator {
fun generateHeaders(disposable: Disposable, root: File): String
}
private val testRootDisposable = Disposer.newDisposable("${AbstractObjCExportHeaderGeneratorTest::class.simpleName}.testRootDisposable")
protected val objCExportTestDataDir = testDataDir.resolve("objcexport")
@After
fun dispose() {
Disposer.dispose(testRootDisposable)
}
protected fun doTest(root: File) {
if (!root.isDirectory) fail("Expected ${root.absolutePath} to be directory")
val generatedHeaders = generator.generateHeaders(testRootDisposable, root)
KotlinTestUtils.assertEqualsToFile(root.resolve("!${root.nameWithoutExtension}.h"), generatedHeaders)
}
}
abstract class AbstractFE10ObjCExportHeaderGeneratorTest : AbstractObjCExportHeaderGeneratorTest(Fe10ObjCExportHeaderGenerator)
@@ -1,110 +0,0 @@
/*
* Copyright 2010-2023 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.backend.konan.tests
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportHeaderGenerator
import org.jetbrains.kotlin.backend.konan.testUtils.AbstractFE10ObjCExportHeaderGeneratorTest
import org.junit.jupiter.api.Test
/**
* ## Test Scope
* This test will cover the generation of 'objc' headers.
* The corresponding class in would be [ObjCExportHeaderGenerator].
*
* The input of the test are Kotlin source files;
* The output is the generated header files;
* The output will be compared to already checked in golden files.
*
* ## How to create a new test
* Every test has a corresponding 'root' directory.
* All directories are found in `backend.native/functionalTest/testData/objcexport`.
*
* 1) Create new root directory (e.g. myTest) in testData/objcexport
* 2) Place kotlin files into the directory e.g. testData/objcexport/myTest/Foo.kt
* 3) Create a test function and call ` doTest(objCExportTestDataDir.resolve("myTest"))`
* 4) The first invocation will fail the test, but generates the header that can be checked in (if sufficient)
*/
class Fe10ObjCExportHeaderGeneratorTest : AbstractFE10ObjCExportHeaderGeneratorTest() {
@Test
fun `test - simpleClass`() {
doTest(objCExportTestDataDir.resolve("simpleClass"))
}
@Test
fun `test - simpleInterface`() {
doTest(objCExportTestDataDir.resolve("simpleInterface"))
}
@Test
fun `test - simpleEnumClass`() {
doTest(objCExportTestDataDir.resolve("simpleEnumClass"))
}
@Test
fun `test - simpleObject`() {
doTest(objCExportTestDataDir.resolve("simpleObject"))
}
@Test
fun `test - topLevelFunction`() {
doTest(objCExportTestDataDir.resolve("topLevelFunction"))
}
@Test
fun `test - topLevelProperty`() {
doTest(objCExportTestDataDir.resolve("topLevelProperty"))
}
@Test
fun `test - sameClassNameInDifferentPackage`() {
doTest(objCExportTestDataDir.resolve("sameClassNameInDifferentPackage"))
}
@Test
fun `test - samePropertyAndFunctionName`() {
doTest(objCExportTestDataDir.resolve("samePropertyAndFunctionName"))
}
@Test
fun `test - classImplementingInterface`() {
doTest(objCExportTestDataDir.resolve("classImplementingInterface"))
}
@Test
fun `test - interfaceImplementingInterface`() {
doTest(objCExportTestDataDir.resolve("interfaceImplementingInterface"))
}
@Test
fun `test - classWithObjCNameAnnotation`() {
doTest(objCExportTestDataDir.resolve("classWithObjCNameAnnotation"))
}
@Test
fun `test - functionWithObjCNameAnnotation`() {
doTest(objCExportTestDataDir.resolve("functionWithObjCNameAnnotation"))
}
@Test
fun `test - classWithKDoc`() {
doTest(objCExportTestDataDir.resolve("classWithKDoc"))
}
@Test
fun `test - classWithHidesFromObjCAnnotation`() {
doTest(objCExportTestDataDir.resolve("classWithHidesFromObjCAnnotation"))
}
@Test
fun `test - functionWithThrowsAnnotation`() {
doTest(objCExportTestDataDir.resolve("functionWithThrowsAnnotation"))
}
@Test
fun `test - functionWithErrorType`() {
doTest(objCExportTestDataDir.resolve("functionWithErrorType"))
}
}
@@ -9,3 +9,6 @@ private const val konanHomePropertyKey = "kotlin.internal.native.test.nativeHome
val konanHomePath: String
get() = System.getProperty(konanHomePropertyKey) ?: error("Missing System property: '$konanHomePropertyKey'")
val kotlinNativeStdlibPath: String
get() = "$konanHomePath/klib/common/stdlib"
@@ -7,5 +7,5 @@ package org.jetbrains.kotlin.backend.konan.testUtils
import java.io.File
val projectDir = File(System.getProperty("projectDir"))
val testDataDir = projectDir.resolve("src/test/testData")
val testDataDir = File("native/objcexport-header-generator/testData")
val headersTestDataDir = testDataDir.resolve("headers")
@@ -0,0 +1,122 @@
/*
* Copyright 2010-2023 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.backend.konan.tests
import org.jetbrains.kotlin.backend.konan.testUtils.headersTestDataDir
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.junit.jupiter.api.Test
import java.io.File
import kotlin.test.fail
/**
* ## Test Scope
* This test will cover the generation of 'ObjC' headers.
*
* The input of the test are Kotlin source files;
* The output is the generated header files;
* The output will be compared to already checked in golden files.
*
* ## How to create a new test
* Every test has a corresponding 'root' directory.
* All directories are found in `native/objcexport-header-generator/testData/headers`.
*
* 1) Create new root directory (e.g. myTest) in testData/headers
* 2) Place kotlin files into the directory e.g. testData/headers/myTest/Foo.kt
* 3) Create a test function and call ` doTest(headersTestDataDir.resolve("myTest"))`
* 4) The first invocation will fail the test, but generates the header that can be checked in (if sufficient)
*/
class ObjCExportHeaderGeneratorTest(val generator: HeaderGenerator) {
@Test
fun `test - simpleClass`() {
doTest(headersTestDataDir.resolve("simpleClass"))
}
@Test
fun `test - simpleInterface`() {
doTest(headersTestDataDir.resolve("simpleInterface"))
}
@Test
fun `test - simpleEnumClass`() {
doTest(headersTestDataDir.resolve("simpleEnumClass"))
}
@Test
fun `test - simpleObject`() {
doTest(headersTestDataDir.resolve("simpleObject"))
}
@Test
fun `test - topLevelFunction`() {
doTest(headersTestDataDir.resolve("topLevelFunction"))
}
@Test
fun `test - topLevelProperty`() {
doTest(headersTestDataDir.resolve("topLevelProperty"))
}
@Test
fun `test - sameClassNameInDifferentPackage`() {
doTest(headersTestDataDir.resolve("sameClassNameInDifferentPackage"))
}
@Test
fun `test - samePropertyAndFunctionName`() {
doTest(headersTestDataDir.resolve("samePropertyAndFunctionName"))
}
@Test
fun `test - classImplementingInterface`() {
doTest(headersTestDataDir.resolve("classImplementingInterface"))
}
@Test
fun `test - interfaceImplementingInterface`() {
doTest(headersTestDataDir.resolve("interfaceImplementingInterface"))
}
@Test
fun `test - classWithObjCNameAnnotation`() {
doTest(headersTestDataDir.resolve("classWithObjCNameAnnotation"))
}
@Test
fun `test - functionWithObjCNameAnnotation`() {
doTest(headersTestDataDir.resolve("functionWithObjCNameAnnotation"))
}
@Test
fun `test - classWithKDoc`() {
doTest(headersTestDataDir.resolve("classWithKDoc"))
}
@Test
fun `test - classWithHidesFromObjCAnnotation`() {
doTest(headersTestDataDir.resolve("classWithHidesFromObjCAnnotation"))
}
@Test
fun `test - functionWithThrowsAnnotation`() {
doTest(headersTestDataDir.resolve("functionWithThrowsAnnotation"))
}
@Test
fun `test - functionWithErrorType`() {
doTest(headersTestDataDir.resolve("functionWithErrorType"))
}
fun interface HeaderGenerator {
fun generateHeaders(root: File): String
}
private fun doTest(root: File) {
if (!root.isDirectory) fail("Expected ${root.absolutePath} to be directory")
val generatedHeaders = generator.generateHeaders(root)
KotlinTestUtils.assertEqualsToFile(root.resolve("!${root.nameWithoutExtension}.h"), generatedHeaders)
}
}
@@ -6,6 +6,8 @@ publishJarsForIde(
listOf(
":native:base",
":native:objcexport-header-generator",
":native:objcexport-header-generator-k1",
":native:objcexport-header-generator-analysis-api",
":compiler:ir.serialization.native"
)
)
@@ -265,6 +265,10 @@ fun Project.projectTest(
}.apply { configure(body) }
}
fun Test.enableJunit5ExtensionsAutodetection() {
systemProperty("junit.jupiter.extensions.autodetection.enabled", "true")
}
val defaultMaxMemoryPerTestWorkerMb = 1600
val reservedMemoryMb = 9000 // system processes, gradle daemon, kotlin daemon, etc ...
+4
View File
@@ -112,6 +112,8 @@ include ":benchmarks",
":native:executors",
":native:base",
":native:objcexport-header-generator",
":native:objcexport-header-generator-k1",
":native:objcexport-header-generator-analysis-api",
":core:compiler.common",
":core:compiler.common.jvm",
":core:compiler.common.js",
@@ -695,6 +697,8 @@ project(':native:frontend.native').projectDir = "$rootDir/native/frontend" as Fi
project(':native:kotlin-klib-commonizer').projectDir = "$rootDir/native/commonizer" as File
project(":native:kotlin-klib-commonizer-api").projectDir = "$rootDir/native/commonizer-api" as File
project(':native:kotlin-klib-commonizer-embeddable').projectDir = "$rootDir/native/commonizer-embeddable" as File
project(':native:objcexport-header-generator-k1').projectDir = "$rootDir/native/objcexport-header-generator/impl/k1" as File
project(':native:objcexport-header-generator-analysis-api').projectDir = "$rootDir/native/objcexport-header-generator/impl/analysis-api" as File
project(':plugins:android-extensions-compiler').projectDir = "$rootDir/plugins/android-extensions/android-extensions-compiler" as File
project(':kotlin-android-extensions').projectDir = "$rootDir/prepare/android-extensions-compiler-gradle" as File
project(':kotlin-parcelize-compiler').projectDir = "$rootDir/prepare/parcelize-compiler-gradle" as File