Skip to content

Commit 9ef93a1

Browse files
micapolos-googleSpace Cloud
authored andcommitted
[K/N] Add support for custom entry points filter to ObjCExport
A new `-Xbinary=objcExportEntryPointsPath` flag is added, which allows providing a file with custom ObjC entry points. Each entry point matches the following pattern: `<kind> <pattern>`, where: * `<kind>` is one of: `function`, `property` or `callable`, * `<pattern>` is a fully qualified name with potential wildcard. Examples: ``` // Explicit pattern matching a given function (with all overloads). function kotlin.collections.Collection.contains // Explicit pattern matching a given property. function kotlin.collections.Collection.size // Wildcard pattern matching any function or property in a given class. callable kotlin.Throwable.* ``` At this moment, it's only possible to provide an explicit list of functions / properties. The mechanism could be extended to provide explicit classes, by adding a new `class` kind. This feature is experimental. ^KT-69789
1 parent ac5795a commit 9ef93a1

File tree

14 files changed

+385
-21
lines changed

14 files changed

+385
-21
lines changed

kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/BinaryOptions.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ object BinaryOptions : BinaryOptionRegistry() {
3939

4040
val objcExportErrorOnNameCollisions by booleanOption()
4141

42+
val objcExportEntryPointsPath by stringOption()
43+
4244
val gc by option<GC>(shortcut = { it.shortcut })
4345

4446
val gcSchedulerType by option<GCSchedulerType>(hideValue = { it.deprecatedWithReplacement != null })

kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@ import com.google.common.base.StandardSystemProperty
99
import com.intellij.openapi.project.Project
1010
import org.jetbrains.kotlin.backend.common.linkage.issues.UserVisibleIrModulesSupport
1111
import org.jetbrains.kotlin.backend.konan.ir.BridgesPolicy
12+
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCEntryPoints
13+
import org.jetbrains.kotlin.backend.konan.objcexport.readObjCEntryPoints
1214
import org.jetbrains.kotlin.backend.konan.serialization.KonanUserVisibleIrModulesSupport
1315
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
1416
import org.jetbrains.kotlin.cli.common.config.kotlinSourceRoots
1517
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
1618
import org.jetbrains.kotlin.config.CommonConfigurationKeys
1719
import org.jetbrains.kotlin.config.CompilerConfiguration
18-
import org.jetbrains.kotlin.config.IrVerificationMode
1920
import org.jetbrains.kotlin.config.KotlinCompilerVersion
2021
import org.jetbrains.kotlin.ir.linkage.partial.partialLinkageConfig
2122
import org.jetbrains.kotlin.konan.file.File
@@ -230,6 +231,13 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration
230231
configuration.get(BinaryOptions.objcDisposeWithRunLoop) ?: true
231232
}
232233

234+
val objcEntryPoints: ObjCEntryPoints by lazy {
235+
configuration
236+
.get(BinaryOptions.objcExportEntryPointsPath)
237+
?.let { File(it).readObjCEntryPoints() }
238+
?: ObjCEntryPoints.ALL
239+
}
240+
233241
val enableSafepointSignposts: Boolean = configuration.get(BinaryOptions.enableSafepointSignposts)?.also {
234242
if (it && !target.supportsSignposts) {
235243
configuration.report(CompilerMessageSeverity.STRONG_WARNING, "Signposts are not available on $target. The setting will have no effect.")

kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExport.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,11 @@ internal fun produceObjCExportInterface(
4747
// and can't do this per-module, e.g. due to global name conflict resolution.
4848

4949
val unitSuspendFunctionExport = config.unitSuspendFunctionObjCExport
50-
val mapper = ObjCExportMapper(frontendServices.deprecationResolver, unitSuspendFunctionExport = unitSuspendFunctionExport)
50+
val entryPoints = config.objcEntryPoints
51+
val mapper = ObjCExportMapper(
52+
frontendServices.deprecationResolver,
53+
unitSuspendFunctionExport = unitSuspendFunctionExport,
54+
entryPoints = entryPoints)
5155
val moduleDescriptors = listOf(moduleDescriptor) + moduleDescriptor.getExportedDependencies(config)
5256
val objcGenerics = config.configuration.getBoolean(KonanConfigKeys.OBJC_GENERICS)
5357
val disableSwiftMemberNameMangling = config.configuration.getBoolean(BinaryOptions.objcExportDisableSwiftMemberNameMangling)
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
3+
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
4+
*/
5+
6+
package org.jetbrains.kotlin.backend.konan.objcexport
7+
8+
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCEntryPoint.Kind.*
9+
import org.jetbrains.kotlin.konan.file.File
10+
11+
/**
12+
* An entry point which matches declarations of a given kind and fully-qualified name pattern.
13+
*
14+
* @property kind a kind of entry point.
15+
* @property pattern a pattern which matches fully-qualified declaration name.
16+
*/
17+
data class ObjCEntryPoint(val kind: Kind, val pattern: Pattern) {
18+
/** Entry point kind. */
19+
enum class Kind {
20+
/** A function. */
21+
FUNCTION,
22+
23+
/** A property. */
24+
PROPERTY,
25+
26+
/** A callable: function or property. */
27+
CALLABLE,
28+
}
29+
30+
/**
31+
* A pattern which matches fully-qualified name.
32+
*
33+
* @property path Fully-qualified name components preceding the final name component.
34+
* @property name The last component in a fully-qualified name, which may either be an explicit name or a wildcard.
35+
*/
36+
data class Pattern(val path: List<String>, val name: Name) {
37+
/** The last component of a pattern. */
38+
sealed class Name {
39+
/** Matches explicit name. */
40+
data class Explicit(val string: String) : Name()
41+
42+
/** Matches any name. */
43+
data object Wildcard : Name()
44+
}
45+
}
46+
}
47+
48+
/** Parent kind in a hierarchy of kinds, or null for root. */
49+
val ObjCEntryPoint.Kind.parentOrNull: ObjCEntryPoint.Kind?
50+
get() =
51+
when (this) {
52+
FUNCTION -> CALLABLE
53+
PROPERTY -> CALLABLE
54+
CALLABLE -> null
55+
}
56+
57+
/** Reads a list of entry points from this file. */
58+
fun File.readObjCEntryPointList(): List<ObjCEntryPoint> =
59+
readStrings()
60+
.asSequence()
61+
.map { it.trim() } // Strip leading / trailing whitespaces
62+
.filter { !it.startsWith("//") } // Strip comment lines
63+
.filter { it.isNotBlank() } // Remove empty lines
64+
.map { it.toObjCEntryPoint() }
65+
.toList()
66+
67+
/** Convert this string to an entry point kind. */
68+
private fun String.toObjCEntryPointKind(): ObjCEntryPoint.Kind =
69+
when (this) {
70+
"function" -> FUNCTION
71+
"property" -> PROPERTY
72+
"callable" -> CALLABLE
73+
else -> throw IllegalArgumentException("invalid kind: $this, should be one of: function, property or callable")
74+
}
75+
76+
/** Convert this string to an entry point pattern. */
77+
private fun String.toObjCEntryPointPattern(): ObjCEntryPoint.Pattern =
78+
split('.').let { components ->
79+
ObjCEntryPoint.Pattern(
80+
components.dropLast(1),
81+
components.lastOrNull()
82+
.let { it ?: throw IllegalArgumentException("invalid pattern: \"$this\", should be non-empty") }
83+
.toObjCEntryPointPatternName())
84+
}
85+
86+
/** Convert this string to an entry point pattern name. */
87+
private fun String.toObjCEntryPointPatternName(): ObjCEntryPoint.Pattern.Name =
88+
when (this) {
89+
"*" -> ObjCEntryPoint.Pattern.Name.Wildcard
90+
else -> ObjCEntryPoint.Pattern.Name.Explicit(this)
91+
}
92+
93+
/** Convert this string to an entry point. */
94+
private fun String.toObjCEntryPoint(): ObjCEntryPoint =
95+
split(' ')
96+
.also { if (it.size != 2) throw IllegalArgumentException("invalid entry point: \"$this\", should match: \"<kind> <pattern>\"") }
97+
.let { ObjCEntryPoint(it[0].toObjCEntryPointKind(), it[1].toObjCEntryPointPattern()) }
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
3+
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
4+
*/
5+
6+
package org.jetbrains.kotlin.backend.konan.objcexport
7+
8+
import org.jetbrains.kotlin.descriptors.*
9+
import org.jetbrains.kotlin.konan.file.File
10+
import org.jetbrains.kotlin.name.FqName
11+
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
12+
13+
/** A predicate which checks whether the given declaration is entry point, and should be exposed in Objective-C. */
14+
interface ObjCEntryPoints {
15+
fun shouldBeExposed(descriptor: CallableMemberDescriptor) = true
16+
17+
companion object {
18+
val ALL: ObjCEntryPoints = object : ObjCEntryPoints {}
19+
}
20+
}
21+
22+
/**
23+
* Reads entry points from this file.
24+
*/
25+
fun File.readObjCEntryPoints(): ObjCEntryPoints =
26+
readObjCEntryPointList()
27+
.toSet()
28+
.let { entryPointSet ->
29+
object : ObjCEntryPoints {
30+
override fun shouldBeExposed(descriptor: CallableMemberDescriptor): Boolean =
31+
descriptor.objCEntryPointKindOrNull
32+
?.let { objcEntryPointKind -> shouldBeExposed(objcEntryPointKind, descriptor.fqNameSafe) }
33+
?: false
34+
35+
private fun shouldBeExposed(kind: ObjCEntryPoint.Kind, fqName: FqName): Boolean =
36+
entryPointSet.contains(ObjCEntryPoint(kind, fqName.toObjCExplicitPattern())) ||
37+
entryPointSet.contains(ObjCEntryPoint(kind, fqName.toObjCWildcardPattern())) ||
38+
kind.parentOrNull?.let { shouldBeExposed(it, fqName) }.let { it ?: false }
39+
40+
/** A kind which matches this descriptor. */
41+
private val DeclarationDescriptor.objCEntryPointKindOrNull: ObjCEntryPoint.Kind?
42+
get() = when (this) {
43+
is FunctionDescriptor -> ObjCEntryPoint.Kind.FUNCTION
44+
is PropertyDescriptor -> ObjCEntryPoint.Kind.PROPERTY
45+
is CallableDescriptor -> ObjCEntryPoint.Kind.CALLABLE
46+
else -> null
47+
}
48+
49+
/** Convert this fully-qualified name to a pattern path, containing all but last components. */
50+
private fun FqName.toObjCPatternPath(): List<String> =
51+
pathSegments().map { it.asString() }
52+
53+
/** Convert this fully-qualified name to an explicit pattern. */
54+
private fun FqName.toObjCExplicitPattern(): ObjCEntryPoint.Pattern =
55+
ObjCEntryPoint.Pattern(
56+
parent().toObjCPatternPath(),
57+
ObjCEntryPoint.Pattern.Name.Explicit(shortName().asString())
58+
)
59+
60+
/** Convert this fully-qualified name to a wildcard pattern. */
61+
private fun FqName.toObjCWildcardPattern(): ObjCEntryPoint.Pattern =
62+
ObjCEntryPoint.Pattern(
63+
parent().toObjCPatternPath(),
64+
ObjCEntryPoint.Pattern.Name.Wildcard
65+
)
66+
}
67+
}

native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportLazy.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ interface ObjCExportLazy {
5353
val unitSuspendFunctionExport: UnitSuspendFunctionObjCExport
5454
val ignoreInterfaceMethodCollisions: Boolean
5555
get() = false
56+
57+
val entryPoints: ObjCEntryPoints
58+
get() = ObjCEntryPoints.ALL
5659
}
5760

5861
fun generateBase(): List<ObjCTopLevel>
@@ -97,7 +100,12 @@ class ObjCExportLazyImpl(
97100

98101
private val nameTranslator: ObjCExportNameTranslator = ObjCExportNameTranslatorImpl(namerConfiguration)
99102

100-
private val mapper = ObjCExportMapper(deprecationResolver, local = true, configuration.unitSuspendFunctionExport)
103+
private val mapper = ObjCExportMapper(
104+
deprecationResolver,
105+
local = true,
106+
configuration.unitSuspendFunctionExport,
107+
configuration.entryPoints,
108+
)
101109

102110
private val namer = ObjCExportNamerImpl(namerConfiguration, builtIns, mapper, problemCollector, local = true)
103111

native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportMapper.kt

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.backend.konan.descriptors.isArray
1212
import org.jetbrains.kotlin.backend.konan.descriptors.isInterface
1313
import org.jetbrains.kotlin.builtins.*
1414
import org.jetbrains.kotlin.descriptors.*
15+
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
1516
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
1617
import org.jetbrains.kotlin.ir.objcinterop.isObjCObjectType
1718
import org.jetbrains.kotlin.name.ClassId
@@ -30,6 +31,7 @@ class ObjCExportMapper(
3031
internal val deprecationResolver: DeprecationResolver? = null,
3132
private val local: Boolean = false,
3233
internal val unitSuspendFunctionExport: UnitSuspendFunctionObjCExport,
34+
internal val entryPoints: ObjCEntryPoints = ObjCEntryPoints.ALL,
3335
) {
3436
fun getCustomTypeMapper(descriptor: ClassDescriptor): CustomTypeMapper? = CustomTypeMappers.getMapper(descriptor)
3537

@@ -49,7 +51,7 @@ class ObjCExportMapper(
4951
internal fun isSpecialMapped(descriptor: ClassDescriptor): Boolean {
5052
// TODO: this method duplicates some of the [ObjCExportTranslatorImpl.mapReferenceType] logic.
5153
return KotlinBuiltIns.isAny(descriptor) ||
52-
descriptor.getAllSuperClassifiers().any { it is ClassDescriptor && CustomTypeMappers.hasMapper(it) }
54+
descriptor.getAllSuperClassifiers().any { it is ClassDescriptor && CustomTypeMappers.hasMapper(it) }
5355
}
5456

5557
/**
@@ -125,25 +127,26 @@ fun ObjCExportMapper.shouldBeExposed(descriptor: CallableMemberDescriptor): Bool
125127
// because they are useless in Objective-C/Swift.
126128
isComponentNMethod(descriptor) && descriptor.overriddenDescriptors.isEmpty() -> false
127129
descriptor.isHiddenFromObjC() -> false
130+
!entryPoints.shouldBeExposed(descriptor) -> false
128131
else -> true
129132
}
130133

134+
private fun AnnotationDescriptor.hidesFromObjC(): Boolean =
135+
annotationClass?.annotations?.any { it.fqName == KonanFqNames.hidesFromObjC } ?: false
136+
131137
private fun CallableMemberDescriptor.isHiddenFromObjC(): Boolean = when {
132138
// Note: the front-end checker requires all overridden descriptors to be either refined or not refined.
133139
overriddenDescriptors.isNotEmpty() -> overriddenDescriptors.first().isHiddenFromObjC()
134-
else -> annotations.any { annotation ->
135-
annotation.annotationClass?.annotations?.any { it.fqName == KonanFqNames.hidesFromObjC } == true
136-
}
140+
else -> annotations.any(AnnotationDescriptor::hidesFromObjC)
137141
}
138142

139143
/**
140144
* Check if the given class or its enclosing declaration is marked as @HiddenFromObjC.
141145
*/
142146
internal fun ClassDescriptor.isHiddenFromObjC(): Boolean = when {
143-
(this.containingDeclaration as? ClassDescriptor)?.isHiddenFromObjC() == true -> true
144-
else -> annotations.any { annotation ->
145-
annotation.annotationClass?.annotations?.any { it.fqName == KonanFqNames.hidesFromObjC } == true
146-
}
147+
containingDeclaration.let { it as? ClassDescriptor }?.isHiddenFromObjC() ?: false -> true
148+
annotations.any(AnnotationDescriptor::hidesFromObjC) -> true
149+
else -> false
147150
}
148151

149152
internal fun ObjCExportMapper.shouldBeExposed(descriptor: ClassDescriptor): Boolean =
@@ -211,10 +214,15 @@ private fun ObjCExportMapper.isHiddenByDeprecation(descriptor: ClassDescriptor):
211214

212215
// Note: the logic is partially duplicated in ObjCExportLazyImpl.translateClasses.
213216
internal fun ObjCExportMapper.shouldBeVisible(descriptor: ClassDescriptor): Boolean =
214-
descriptor.isEffectivelyPublicApi && when (descriptor.kind) {
215-
ClassKind.CLASS, ClassKind.INTERFACE, ClassKind.ENUM_CLASS, ClassKind.OBJECT -> true
216-
ClassKind.ENUM_ENTRY, ClassKind.ANNOTATION_CLASS -> false
217-
} && !descriptor.isExpect && !descriptor.isInlined() && !isHiddenByDeprecation(descriptor) && !descriptor.isHiddenFromObjC()
217+
descriptor.isEffectivelyPublicApi &&
218+
when (descriptor.kind) {
219+
ClassKind.CLASS, ClassKind.INTERFACE, ClassKind.ENUM_CLASS, ClassKind.OBJECT -> true
220+
ClassKind.ENUM_ENTRY, ClassKind.ANNOTATION_CLASS -> false
221+
} &&
222+
!descriptor.isExpect &&
223+
!descriptor.isInlined() &&
224+
!isHiddenByDeprecation(descriptor) &&
225+
!descriptor.isHiddenFromObjC()
218226

219227
private fun ObjCExportMapper.isBase(descriptor: CallableMemberDescriptor): Boolean =
220228
descriptor.overriddenDescriptors.all { !shouldBeExposed(it) }
@@ -369,7 +377,7 @@ private fun ObjCExportMapper.bridgeReturnType(
369377
}
370378

371379
descriptor.containingDeclaration.let { it is ClassDescriptor && KotlinBuiltIns.isAny(it) } &&
372-
descriptor.name.asString() == "hashCode" -> {
380+
descriptor.name.asString() == "hashCode" -> {
373381
assert(!convertExceptionsToErrors)
374382
MethodBridge.ReturnValue.HashCode
375383
}
@@ -460,4 +468,3 @@ internal fun ObjCExportMapper.bridgePropertyType(descriptor: PropertyDescriptor)
460468

461469
return bridgeType(descriptor.type)
462470
}
463-

native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslatorMobile.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@ import org.jetbrains.kotlin.descriptors.FunctionDescriptor
1111
class ObjCExportTranslatorMobile internal constructor(private val delegate: ObjCExportTranslatorImpl) : ObjCExportTranslator by delegate {
1212
companion object {
1313
fun create(namer: ObjCExportNamer, configuration: ObjCExportLazy.Configuration): ObjCExportTranslatorMobile {
14-
val mapper = ObjCExportMapper(local = true, unitSuspendFunctionExport = configuration.unitSuspendFunctionExport)
14+
val mapper = ObjCExportMapper(
15+
local = true,
16+
unitSuspendFunctionExport = configuration.unitSuspendFunctionExport,
17+
entryPoints = configuration.entryPoints,
18+
)
1519
return ObjCExportTranslatorMobile(
1620
ObjCExportTranslatorImpl(
1721
null,
@@ -29,4 +33,4 @@ class ObjCExportTranslatorMobile internal constructor(private val delegate: ObjC
2933
val scope = classDescriptor?.let { delegate.createGenericExportScope(it) } ?: ObjCRootExportScope
3034
return delegate.buildMethod(descriptor, descriptor, scope)
3135
}
32-
}
36+
}

native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjcExportHeaderGeneratorMobile.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class ObjcExportHeaderGeneratorMobile internal constructor(
3030
local: Boolean = false,
3131
restrictToLocalModules: Boolean = false,
3232
): ObjCExportHeaderGenerator {
33-
val mapper = ObjCExportMapper(deprecationResolver, local, configuration.unitSuspendFunctionExport)
33+
val mapper = ObjCExportMapper(deprecationResolver, local, configuration.unitSuspendFunctionExport, configuration.entryPoints)
3434
val namerConfiguration = createNamerConfiguration(configuration)
3535
val namer = ObjCExportNamerImpl(namerConfiguration, builtIns, mapper, problemCollector, local)
3636

native/objcexport-header-generator/impl/k1/test/org/jetbrains/kotlin/backend/konan/testUtils/Fe10ObjCExportHeaderGenerator.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,22 @@ private class Fe10HeaderGeneratorImpl(private val disposable: Disposable) : Head
6969
val kotlinFiles = root.walkTopDown().filter { it.isFile }.filter { it.extension == "kt" }.toList()
7070
val moduleDescriptors = setOf(createModuleDescriptor(environment, kotlinFiles, configuration.dependencies))
7171

72+
// Parse objc-entry-points file if present
73+
val entryPoints = File(root, "objc-entry-points")
74+
.takeIf { it.isFile }
75+
?.toPath()
76+
?.let { org.jetbrains.kotlin.konan.file.File(it) }
77+
?.readObjCEntryPoints()
78+
?: ObjCEntryPoints.ALL
79+
7280
val mapper = ObjCExportMapper(
7381
deprecationResolver = DeprecationResolver(
7482
storageManager = LockBasedStorageManager.NO_LOCKS,
7583
languageVersionSettings = createLanguageVersionSettings(),
7684
deprecationSettings = JavaDeprecationSettings
7785
),
78-
unitSuspendFunctionExport = UnitSuspendFunctionObjCExport.DEFAULT
86+
unitSuspendFunctionExport = UnitSuspendFunctionObjCExport.DEFAULT,
87+
entryPoints = entryPoints,
7988
)
8089

8190
val exportedModuleDescriptors = moduleDescriptors + moduleDescriptors

0 commit comments

Comments
 (0)