[ObjCExport] Fe10: Implement test with exported and non exported klib dependencies

KT-65670
This commit is contained in:
Sebastian Sellmair
2024-02-23 11:33:03 +01:00
committed by Space Team
parent b26f9cb274
commit e377a98815
18 changed files with 361 additions and 13 deletions
@@ -11,6 +11,8 @@ import org.jetbrains.kotlin.backend.konan.UnitSuspendFunctionObjCExport
import org.jetbrains.kotlin.backend.konan.objcexport.*
import org.jetbrains.kotlin.builtins.DefaultBuiltIns
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.tooling.core.closure
import org.junit.jupiter.api.extension.AfterEachCallback
import org.junit.jupiter.api.extension.ExtensionContext
import org.junit.jupiter.api.extension.ParameterContext
@@ -59,14 +61,18 @@ private class Fe10HeaderGeneratorImpl(private val disposable: Disposable) : Head
val environment: KotlinCoreEnvironment = createKotlinCoreEnvironment(disposable)
val kotlinFiles = root.walkTopDown().filter { it.isFile }.filter { it.extension == "kt" }.toList()
val moduleDescriptors = setOf(createModuleDescriptor(environment, kotlinFiles))
val moduleDescriptors = setOf(createModuleDescriptor(environment, kotlinFiles, configuration.dependencies))
val mapper = ObjCExportMapper(
unitSuspendFunctionExport = UnitSuspendFunctionObjCExport.DEFAULT
)
val exportedModuleDescriptors = moduleDescriptors + moduleDescriptors
.closure<ModuleDescriptor> { it.allDependencyModules }
.filter { it.name.asStringStripSpecialMarkers() in configuration.exportedDependencyModuleNames }
val namer = ObjCExportNamerImpl(
moduleDescriptors = moduleDescriptors,
moduleDescriptors = exportedModuleDescriptors,
builtIns = DefaultBuiltIns.Instance,
mapper = mapper,
problemCollector = ObjCExportProblemCollector.SILENT,
@@ -76,7 +82,7 @@ private class Fe10HeaderGeneratorImpl(private val disposable: Disposable) : Head
)
return ObjCExportHeaderGeneratorImpl(
moduleDescriptors = moduleDescriptors.toList(),
moduleDescriptors = exportedModuleDescriptors.toList(),
mapper = mapper,
namer = namer,
problemCollector = ObjCExportProblemCollector.SILENT,
@@ -32,6 +32,7 @@ import org.jetbrains.kotlin.storage.LockBasedStorageManager
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.jetbrains.kotlin.test.util.KtTestUtil
import java.io.File
import java.nio.file.Path
fun createModuleDescriptor(
environment: KotlinCoreEnvironment,
@@ -51,9 +52,8 @@ fun createModuleDescriptor(
fun createModuleDescriptor(
environment: KotlinCoreEnvironment,
kotlinFiles: List<File>,
dependencyKlibs: List<Path> = emptyList(),
): ModuleDescriptor {
val psiFactory = KtPsiFactory(environment.project)
val kotlinPsiFiles = kotlinFiles.map { file -> psiFactory.createFile(file.name, KtTestUtil.doLoadFile(file)) }
val klibFactory = KlibMetadataFactories(::KonanBuiltIns, DynamicTypeDeserializer)
@@ -64,6 +64,15 @@ fun createModuleDescriptor(
packageAccessHandler = null
).also { it.setDependencies(it) }
val dependencyKlibDescriptors = dependencyKlibs.map { dependencyKlib ->
klibFactory.DefaultDeserializedDescriptorFactory.createDescriptorAndNewBuiltIns(
library = resolveSingleFileKlib(org.jetbrains.kotlin.konan.file.File(dependencyKlib)),
languageVersionSettings = createLanguageVersionSettings(),
storageManager = LockBasedStorageManager.NO_LOCKS,
packageAccessHandler = null,
).also { it.setDependencies(it, stdlibModuleDescriptor) }
}
val moduleDescriptor = ModuleDescriptorImpl(
moduleName = Name.special("<test_module>"),
storageManager = LockBasedStorageManager.NO_LOCKS,
@@ -78,7 +87,7 @@ fun createModuleDescriptor(
moduleDescriptor.setDependencies(
ModuleDependenciesImpl(
allDependencies = listOf(moduleDescriptor, stdlibModuleDescriptor),
allDependencies = listOf(moduleDescriptor, stdlibModuleDescriptor) + dependencyKlibDescriptors,
modulesWhoseInternalsAreVisible = emptySet(),
directExpectedByDependencies = emptyList(),
allExpectedByDependencies = emptySet()
@@ -87,6 +96,9 @@ fun createModuleDescriptor(
val projectContext = ProjectContext(environment.project, "test project context")
val psiFactory = KtPsiFactory(environment.project)
val kotlinPsiFiles = kotlinFiles.map { file -> psiFactory.createFile(file.name, KtTestUtil.doLoadFile(file)) }
return FakeTopDownAnalyzerFacadeForNative.analyzeFilesWithGivenTrace(
files = kotlinPsiFiles,
trace = NoScopeRecordCliBindingTrace(),
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.backend.konan.testUtils
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCHeader
import java.io.File
import java.nio.file.Path
interface HeaderGenerator {
@@ -18,7 +19,22 @@ interface HeaderGenerator {
* We do not generate them by default to keep test data easier to read.
*/
val generateBaseDeclarationStubs: Boolean = false,
/**
* List of paths pointing to .klib files that can be used as dependency for the compiler when generating
* the header for the given source files.
*
* Some of those dependencies can also be exported, see [exportedDependencyModuleNames]
*/
val dependencies: List<Path> = listOf(),
/**
* Any dependency listed in [dependencies] which module name is present in this set is considered 'exported' and
* will result in the entire public API surface of the said library to be translated in the header
*/
val exportedDependencyModuleNames: Set<String> = emptySet(),
)
fun generateHeaders(root: File, configuration: Configuration = Configuration()): ObjCHeader
}
@@ -0,0 +1,21 @@
/*
* 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.backend.konan.testUtils
import java.io.File
import kotlin.io.path.Path
val testDependencyKlibs = System.getProperty("testDependencyKlibs").orEmpty()
.split(File.pathSeparator)
.map(::Path)
val testLibraryAKlibFile
get() = testDependencyKlibs.firstOrNull { it.contains(Path("testLibraryA")) }
?: error("Missing 'testLibraryA' in 'testDependencyKlibs' System Property")
val testLibraryBKlibFile
get() = testDependencyKlibs.firstOrNull { it.contains(Path("testLibraryB")) }
?: error("Missing 'testLibraryB' in 'testDependencyKlibs' System Property")
@@ -5,9 +5,7 @@
package org.jetbrains.kotlin.backend.konan.tests
import org.jetbrains.kotlin.backend.konan.testUtils.HeaderGenerator
import org.jetbrains.kotlin.backend.konan.testUtils.TodoAnalysisApi
import org.jetbrains.kotlin.backend.konan.testUtils.dependenciesDir
import org.jetbrains.kotlin.backend.konan.testUtils.*
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.junit.jupiter.api.Test
import java.io.File
@@ -21,7 +19,7 @@ import kotlin.test.fail
* - stubs orders
* - depth of traversing (some types must be skipped
*/
class ObjCDependenciesTypesTest(
class ObjCExportDependenciesHeaderGeneratorTest(
private val generator: HeaderGenerator,
) {
@@ -54,9 +52,21 @@ class ObjCDependenciesTypesTest(
doTest(dependenciesDir.resolve("implementIterator"))
}
private fun doTest(root: File) {
@Test
fun `test - exportedAndNotExportedDependency`() {
doTest(
dependenciesDir.resolve("exportedAndNotExportedDependency"), configuration = HeaderGenerator.Configuration(
frameworkName = "MyApp",
generateBaseDeclarationStubs = true,
dependencies = listOf(testLibraryAKlibFile, testLibraryBKlibFile),
exportedDependencyModuleNames = setOf("org.jetbrains.kotlin:testLibraryA")
)
)
}
private fun doTest(root: File, configuration: HeaderGenerator.Configuration = HeaderGenerator.Configuration()) {
if (!root.isDirectory) fail("Expected ${root.absolutePath} to be directory")
val generatedHeaders = generator.generateHeaders(root, HeaderGenerator.Configuration()).toString()
val generatedHeaders = generator.generateHeaders(root, configuration).toString()
KotlinTestUtils.assertEqualsToFile(root.resolve("!${root.nameWithoutExtension}.h"), generatedHeaders)
}
}
@@ -0,0 +1,171 @@
#import <Foundation/NSArray.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSError.h>
#import <Foundation/NSObject.h>
#import <Foundation/NSSet.h>
#import <Foundation/NSString.h>
#import <Foundation/NSValue.h>
@class MyAppMyLibraryA, MyAppTLBMyLibraryB;
NS_ASSUME_NONNULL_BEGIN
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunknown-warning-option"
#pragma clang diagnostic ignored "-Wincompatible-property-type"
#pragma clang diagnostic ignored "-Wnullability"
#pragma push_macro("_Nullable_result")
#if !__has_feature(nullability_nullable_result)
#undef _Nullable_result
#define _Nullable_result _Nullable
#endif
__attribute__((swift_name("KotlinBase")))
@interface MyAppBase : NSObject
- (instancetype)init __attribute__((unavailable));
+ (instancetype)new __attribute__((unavailable));
+ (void)initialize __attribute__((objc_requires_super));
@end
@interface MyAppBase (MyAppBaseCopying) <NSCopying>
@end
__attribute__((swift_name("KotlinMutableSet")))
@interface MyAppMutableSet<ObjectType> : NSMutableSet<ObjectType>
@end
__attribute__((swift_name("KotlinMutableDictionary")))
@interface MyAppMutableDictionary<KeyType, ObjectType> : NSMutableDictionary<KeyType, ObjectType>
@end
@interface NSError (NSErrorMyAppKotlinException)
@property (readonly) id _Nullable kotlinException;
@end
__attribute__((swift_name("KotlinNumber")))
@interface MyAppNumber : NSNumber
- (instancetype)initWithChar:(char)value __attribute__((unavailable));
- (instancetype)initWithUnsignedChar:(unsigned char)value __attribute__((unavailable));
- (instancetype)initWithShort:(short)value __attribute__((unavailable));
- (instancetype)initWithUnsignedShort:(unsigned short)value __attribute__((unavailable));
- (instancetype)initWithInt:(int)value __attribute__((unavailable));
- (instancetype)initWithUnsignedInt:(unsigned int)value __attribute__((unavailable));
- (instancetype)initWithLong:(long)value __attribute__((unavailable));
- (instancetype)initWithUnsignedLong:(unsigned long)value __attribute__((unavailable));
- (instancetype)initWithLongLong:(long long)value __attribute__((unavailable));
- (instancetype)initWithUnsignedLongLong:(unsigned long long)value __attribute__((unavailable));
- (instancetype)initWithFloat:(float)value __attribute__((unavailable));
- (instancetype)initWithDouble:(double)value __attribute__((unavailable));
- (instancetype)initWithBool:(BOOL)value __attribute__((unavailable));
- (instancetype)initWithInteger:(NSInteger)value __attribute__((unavailable));
- (instancetype)initWithUnsignedInteger:(NSUInteger)value __attribute__((unavailable));
+ (instancetype)numberWithChar:(char)value __attribute__((unavailable));
+ (instancetype)numberWithUnsignedChar:(unsigned char)value __attribute__((unavailable));
+ (instancetype)numberWithShort:(short)value __attribute__((unavailable));
+ (instancetype)numberWithUnsignedShort:(unsigned short)value __attribute__((unavailable));
+ (instancetype)numberWithInt:(int)value __attribute__((unavailable));
+ (instancetype)numberWithUnsignedInt:(unsigned int)value __attribute__((unavailable));
+ (instancetype)numberWithLong:(long)value __attribute__((unavailable));
+ (instancetype)numberWithUnsignedLong:(unsigned long)value __attribute__((unavailable));
+ (instancetype)numberWithLongLong:(long long)value __attribute__((unavailable));
+ (instancetype)numberWithUnsignedLongLong:(unsigned long long)value __attribute__((unavailable));
+ (instancetype)numberWithFloat:(float)value __attribute__((unavailable));
+ (instancetype)numberWithDouble:(double)value __attribute__((unavailable));
+ (instancetype)numberWithBool:(BOOL)value __attribute__((unavailable));
+ (instancetype)numberWithInteger:(NSInteger)value __attribute__((unavailable));
+ (instancetype)numberWithUnsignedInteger:(NSUInteger)value __attribute__((unavailable));
@end
__attribute__((swift_name("KotlinByte")))
@interface MyAppByte : MyAppNumber
- (instancetype)initWithChar:(char)value;
+ (instancetype)numberWithChar:(char)value;
@end
__attribute__((swift_name("KotlinUByte")))
@interface MyAppUByte : MyAppNumber
- (instancetype)initWithUnsignedChar:(unsigned char)value;
+ (instancetype)numberWithUnsignedChar:(unsigned char)value;
@end
__attribute__((swift_name("KotlinShort")))
@interface MyAppShort : MyAppNumber
- (instancetype)initWithShort:(short)value;
+ (instancetype)numberWithShort:(short)value;
@end
__attribute__((swift_name("KotlinUShort")))
@interface MyAppUShort : MyAppNumber
- (instancetype)initWithUnsignedShort:(unsigned short)value;
+ (instancetype)numberWithUnsignedShort:(unsigned short)value;
@end
__attribute__((swift_name("KotlinInt")))
@interface MyAppInt : MyAppNumber
- (instancetype)initWithInt:(int)value;
+ (instancetype)numberWithInt:(int)value;
@end
__attribute__((swift_name("KotlinUInt")))
@interface MyAppUInt : MyAppNumber
- (instancetype)initWithUnsignedInt:(unsigned int)value;
+ (instancetype)numberWithUnsignedInt:(unsigned int)value;
@end
__attribute__((swift_name("KotlinLong")))
@interface MyAppLong : MyAppNumber
- (instancetype)initWithLongLong:(long long)value;
+ (instancetype)numberWithLongLong:(long long)value;
@end
__attribute__((swift_name("KotlinULong")))
@interface MyAppULong : MyAppNumber
- (instancetype)initWithUnsignedLongLong:(unsigned long long)value;
+ (instancetype)numberWithUnsignedLongLong:(unsigned long long)value;
@end
__attribute__((swift_name("KotlinFloat")))
@interface MyAppFloat : MyAppNumber
- (instancetype)initWithFloat:(float)value;
+ (instancetype)numberWithFloat:(float)value;
@end
__attribute__((swift_name("KotlinDouble")))
@interface MyAppDouble : MyAppNumber
- (instancetype)initWithDouble:(double)value;
+ (instancetype)numberWithDouble:(double)value;
@end
__attribute__((swift_name("KotlinBoolean")))
@interface MyAppBoolean : MyAppNumber
- (instancetype)initWithBool:(BOOL)value;
+ (instancetype)numberWithBool:(BOOL)value;
@end
__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("MyLibraryA")))
@interface MyAppMyLibraryA : MyAppBase
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
- (int32_t)returnInt __attribute__((swift_name("returnInt()")));
- (MyAppMyLibraryA *)returnMe __attribute__((swift_name("returnMe()")));
@end
__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("FooKt")))
@interface MyAppFooKt : MyAppBase
+ (MyAppTLBMyLibraryB *)foo __attribute__((swift_name("foo()")));
@end
__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("TLBMyLibraryB")))
@interface MyAppTLBMyLibraryB : MyAppBase
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
- (int32_t)returnInt __attribute__((swift_name("returnInt()")));
- (MyAppTLBMyLibraryB *)returnMe __attribute__((swift_name("returnMe()")));
@end
#pragma pop_macro("_Nullable_result")
#pragma clang diagnostic pop
NS_ASSUME_NONNULL_END
@@ -0,0 +1,2 @@
// LibraryA is exported, LibraryB will just be referenced in this return type */
fun foo(): MyLibraryB = error("stub")
@@ -0,0 +1,3 @@
# Test Dependencies for ObjC Export tests:
Those dependencies will be built and the klibs can be used for the header generator to run tests against.
This should emulate the situation of having 'real life' dependencies in your project.
@@ -0,0 +1,3 @@
plugins {
id("objc-export-header-generator-test-library")
}
@@ -0,0 +1,3 @@
# https://youtrack.jetbrains.com/issue/KT-65985
kotlin.native.toolchain.enabled=false
kotlin.native.distribution.downloadFromMaven=false
@@ -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 MyLibraryA {
fun returnInt(): Int = 42
fun returnMe(): MyLibraryA = this
}
@@ -0,0 +1,3 @@
plugins {
id("objc-export-header-generator-test-library")
}
@@ -0,0 +1,3 @@
# https://youtrack.jetbrains.com/issue/KT-65985
kotlin.native.toolchain.enabled=false
kotlin.native.distribution.downloadFromMaven=false
@@ -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 MyLibraryB {
fun returnInt(): Int = 42
fun returnMe(): MyLibraryB = this
}
@@ -167,7 +167,7 @@ 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") {
if (project.path != ":native:kotlin-test-native-xctest" && !project.path.startsWith(":native:objcexport-header-generator")) {
doFirst {
if (!useAbsolutePathsInKlib) {
@Suppress("DEPRECATION")
@@ -0,0 +1,22 @@
/**
* Used in for modules in 'native/objcexport-heade-generator/testDependencies.
* Such libraries can build klibs that can then later be used for running objc export tests against
*/
plugins {
kotlin("multiplatform")
}
/*
Depends on https://youtrack.jetbrains.com/issue/KT-65985
*/
providers.systemProperty("kotlin.internal.native.test.nativeHome").orNull?.let { nativeHome ->
extensions.extraProperties.set("kotlin.native.home", nativeHome)
}
kotlin {
macosArm64()
macosX64()
linuxX64()
linuxArm64()
mingwX64()
}
@@ -1,5 +1,16 @@
import org.gradle.api.Project
import org.gradle.api.attributes.Category
import org.gradle.api.attributes.Usage
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.testing.Test
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.named
import org.gradle.kotlin.dsl.project
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
import java.io.File
/*
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
@@ -19,11 +30,49 @@ fun Project.objCExportHeaderGeneratorTest(
tag = null,
requirePlatformLibs = false,
) {
/**
* Setup klib dependencies that can be used in tests:
* The resolved klibs will be available as classpath under the `testDependencyKlibs` System property.
*/
run {
/* Configuration to resolve klibs for the current host */
val testDependencyKlibs = configurations.maybeCreate("testDependencyKlibs").also { configuration ->
configuration.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)
}
dependencies {
configuration(project(":native:objcexport-header-generator:testLibraryA"))
configuration(project(":native:objcexport-header-generator:testLibraryB"))
}
}
/* Create a classpath (list of file paths) that will be exposed as System property */
val testDependencyKlibsClasspath = testDependencyKlibs.incoming.files.elements.map { elements ->
elements.joinToString(File.pathSeparator) { location -> location.asFile.absolutePath }
}
doFirst {
systemProperty("testDependencyKlibs", testDependencyKlibsClasspath.get())
}
/* Add dependency files as inputs to this test task */
inputs.files(testDependencyKlibs).withPathSensitivity(PathSensitivity.RELATIVE)
}
useJUnitPlatform()
enableJunit5ExtensionsAutodetection()
/* Special 'Kotlin in Fleet' flag that can switch test mode to 'local development' */
systemProperty("kif.local", project.providers.gradleProperty("kif.local").isPresent)
/* Tests will show this displayName as an additional tag (e.g., to differentiate between K1 and AA tests) */
if (testDisplayNameTag != null) {
systemProperty("testDisplayName.tag", testDisplayNameTag)
}
configure()
}
+4
View File
@@ -122,6 +122,8 @@ include ":benchmarks",
":native:objcexport-header-generator",
":native:objcexport-header-generator-k1",
":native:objcexport-header-generator-analysis-api",
":native:objcexport-header-generator:testLibraryA",
":native:objcexport-header-generator:testLibraryB",
":core:compiler.common",
":core:compiler.common.jvm",
":core:compiler.common.js",
@@ -724,6 +726,8 @@ project(":native:kotlin-klib-commonizer-api").projectDir = "$rootDir/native/comm
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(':native:objcexport-header-generator:testLibraryA').projectDir = "$rootDir/native/objcexport-header-generator/testDependencies/testLibraryA" as File
project(':native:objcexport-header-generator:testLibraryB').projectDir = "$rootDir/native/objcexport-header-generator/testDependencies/testLibraryB" 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