diff --git a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt index 1115ea4d208..6700c7b19e9 100644 --- a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt +++ b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt @@ -21,7 +21,7 @@ import clang.CXIdxEntityKind.* import clang.CXTypeKind.* import kotlinx.cinterop.* -private class StructDeclImpl(spelling: String) : StructDecl(spelling) { +private class StructDeclImpl(spelling: String, override val location: Location) : StructDecl(spelling) { override var def: StructDefImpl? = null } @@ -37,29 +37,43 @@ private class StructDefImpl( override val bitFields = mutableListOf() } -private class EnumDefImpl(spelling: String, type: Type) : EnumDef(spelling, type) { +private class EnumDefImpl(spelling: String, type: Type, override val location: Location) : EnumDef(spelling, type) { override val constants = mutableListOf() } -private interface ObjCClassOrProtocolImpl { +private interface ObjCContainerImpl { val protocols: MutableList val methods: MutableList val properties: MutableList } -private class ObjCProtocolImpl(name: String) : ObjCProtocol(name), ObjCClassOrProtocolImpl { +private class ObjCProtocolImpl( + name: String, + override val location: Location +) : ObjCProtocol(name), ObjCContainerImpl { override val protocols = mutableListOf() override val methods = mutableListOf() override val properties = mutableListOf() } -private class ObjCClassImpl(name: String) : ObjCClass(name), ObjCClassOrProtocolImpl { +private class ObjCClassImpl( + name: String, + override val location: Location +) : ObjCClass(name), ObjCContainerImpl { override val protocols = mutableListOf() override val methods = mutableListOf() override val properties = mutableListOf() override var baseClass: ObjCClass? = null } +private class ObjCCategoryImpl( + name: String, clazz: ObjCClass +) : ObjCCategory(name, clazz), ObjCContainerImpl { + override val protocols = mutableListOf() + override val methods = mutableListOf() + override val properties = mutableListOf() +} + internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() { private sealed class DeclarationID { @@ -67,38 +81,86 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() { object VaList : DeclarationID() object VaListTag : DeclarationID() object BuiltinVaList : DeclarationID() + object Protocol : DeclarationID() } - private val structById = mutableMapOf() + private inner class TypeDeclarationRegistry { + private val all = mutableMapOf() - override val structs: List - get() = structById.values.toList() + val included = mutableListOf() - private val enumById = mutableMapOf() + inline fun getOrPut(cursor: CValue, create: () -> D) = getOrPut(cursor, create, configure = {}) - override val enums: List - get() = enumById.values.toList() + inline fun getOrPut(cursor: CValue, create: () -> D, configure: (D) -> Unit): D { + val key = getDeclarationId(cursor) + return all.getOrElse(key) { - private val objCClassesByName = mutableMapOf() + val value = create() + all[key] = value - override val objCClasses: List get() = objCClassesByName.values.toList() + val headerId = getHeaderId(getContainingFile(cursor)) + if (!library.headerInclusionPolicy.excludeAll(headerId)) { + // This declaration is used, and thus should be included: + included.add(value) + } - private val objCProtocolsByName = mutableMapOf() + configure(value) + value + } + } - override val objCProtocols: List get() = objCProtocolsByName.values.toList() + } - private val typedefById = mutableMapOf() + private val headerPathToId = mutableMapOf() - override val typedefs: List - get() = typedefById.values.toList() + internal fun getHeaderId(file: CXFile?): HeaderId { + if (file == null) { + return HeaderId("builtins") + } + + val filePath = clang_getFileName(file).convertAndDispose() + return headerPathToId.getOrPut(filePath) { + val headerIdValue = headerContentsHash(filePath) + HeaderId(headerIdValue) + } + } + + private fun getContainingFile(cursor: CValue): CXFile? { + return clang_getCursorLocation(cursor).getContainingFile() + } + + private fun getLocation(cursor: CValue): Location { + val headerId = getHeaderId(getContainingFile(cursor)) + return Location(headerId) + } + + override val structs: List get() = structRegistry.included + private val structRegistry = TypeDeclarationRegistry() + + override val enums: List get() = enumRegistry.included + private val enumRegistry = TypeDeclarationRegistry() + + override val objCClasses: List get() = objCClassRegistry.included + private val objCClassRegistry = TypeDeclarationRegistry() + + override val objCProtocols: List get() = objCProtocolRegistry.included + private val objCProtocolRegistry = TypeDeclarationRegistry() + + override val objCCategories: Collection get() = objCCategoryById.values + private val objCCategoryById = mutableMapOf() + + override val typedefs get() = typedefRegistry.included + private val typedefRegistry = TypeDeclarationRegistry() private val functionById = mutableMapOf() - override val functions: List - get() = functionById.values.toList() + override val functions: Collection + get() = functionById.values override val macroConstants = mutableListOf() + override lateinit var includedHeaders: List + private fun getDeclarationId(cursor: CValue): DeclarationID { val usr = clang_getCursorUSR(cursor).convertAndDispose() if (usr == "") { @@ -108,32 +170,29 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() { CXCursorKind.CXCursor_StructDecl to "__va_list_tag" -> DeclarationID.VaListTag CXCursorKind.CXCursor_StructDecl to "__va_list" -> DeclarationID.VaList CXCursorKind.CXCursor_TypedefDecl to "__builtin_va_list" -> DeclarationID.BuiltinVaList - else -> error(spelling) + CXCursorKind.CXCursor_ObjCInterfaceDecl to "Protocol" -> DeclarationID.Protocol + else -> error(kind to spelling) } } return DeclarationID.USR(usr) } - private fun getStructDeclAt(cursor: CValue): StructDeclImpl { - val declId = getDeclarationId(cursor) - - val structDecl = structById.getOrPut(declId) { - val cursorType = clang_getCursorType(cursor) - val typeSpelling = clang_getTypeSpelling(cursorType).convertAndDispose() - - StructDeclImpl(typeSpelling) + private fun getStructDeclAt( + cursor: CValue + ): StructDeclImpl = structRegistry.getOrPut(cursor, { createStructDecl(cursor) }) { decl -> + val definitionCursor = clang_getCursorDefinition(cursor) + if (clang_Cursor_isNull(definitionCursor) == 0) { + assert(clang_isCursorDefinition(definitionCursor) != 0) + createStructDef(decl, cursor) } + } - if (structDecl.def == null) { - val definitionCursor = clang_getCursorDefinition(cursor) - if (clang_Cursor_isNull(definitionCursor) == 0) { - assert(clang_isCursorDefinition(definitionCursor) != 0) - createStructDef(structDecl, cursor) - } - } + private fun createStructDecl(cursor: CValue): StructDeclImpl { + val cursorType = clang_getCursorType(cursor) + val typeSpelling = clang_getTypeSpelling(cursorType).convertAndDispose() - return structDecl + return StructDeclImpl(typeSpelling, getLocation(cursor)) } private fun createStructDef(structDecl: StructDeclImpl, cursor: CValue) { @@ -185,15 +244,13 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() { TODO("support enum forward declarations") } - val declId = getDeclarationId(cursor) - - return enumById.getOrPut(declId) { + return enumRegistry.getOrPut(cursor) { val cursorType = clang_getCursorType(cursor) val typeSpelling = clang_getTypeSpelling(cursorType).convertAndDispose() val baseType = convertType(clang_getEnumDeclIntegerType(cursor)) - val enumDef = EnumDefImpl(typeSpelling, baseType) + val enumDef = EnumDefImpl(typeSpelling, baseType, getLocation(cursor)) visitChildren(cursor) { childCursor, _ -> if (clang_getCursorKind(childCursor) == CXCursorKind.CXCursor_EnumConstantDecl) { @@ -233,13 +290,9 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() { val name = clang_getCursorDisplayName(cursor).convertAndDispose() - objCClassesByName[name]?.let { return it } - - val result = ObjCClassImpl(name) - objCClassesByName[name] = result - - addChildrenToClassOrProtocol(cursor, result) - return result + return objCClassRegistry.getOrPut(cursor, { ObjCClassImpl(name, getLocation(cursor)) }) { + addChildrenToObjCContainer(cursor, it) + } } @@ -253,16 +306,30 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() { val name = clang_getCursorDisplayName(cursor).convertAndDispose() - objCProtocolsByName[name]?.let { return it } - - val result = ObjCProtocolImpl(name) - objCProtocolsByName[name] = result - - addChildrenToClassOrProtocol(cursor, result) - return result + return objCProtocolRegistry.getOrPut(cursor, { ObjCProtocolImpl(name, getLocation(cursor)) }) { + addChildrenToObjCContainer(cursor, it) + } } - private fun addChildrenToClassOrProtocol(cursor: CValue, result: ObjCClassOrProtocolImpl) { + private fun getObjCCategoryAt(cursor: CValue): ObjCCategoryImpl? { + assert(cursor.kind == CXCursorKind.CXCursor_ObjCCategoryDecl) { cursor.kind } + + val classCursor = getObjCCategoryClassCursor(cursor) + if (!isAvailable(classCursor)) return null + + val name = clang_getCursorDisplayName(cursor).convertAndDispose() + val declarationId = getDeclarationId(cursor) + + return objCCategoryById.getOrPut(declarationId) { + val clazz = getObjCClassAt(classCursor) + val category = ObjCCategoryImpl(name, clazz) + addChildrenToObjCContainer(cursor, category) + category + } + + } + + private fun addChildrenToObjCContainer(cursor: CValue, result: ObjCContainerImpl) { visitChildren(cursor) { child, _ -> when (child.kind) { CXCursorKind.CXCursor_ObjCSuperClassRef -> { @@ -332,10 +399,9 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() { return underlying } - val declId = getDeclarationId(declCursor) - val typedefDef = typedefById.getOrPut(declId) { + val typedefDef = typedefRegistry.getOrPut(declCursor) { - TypedefDef(underlying, name) + TypedefDef(underlying, name, getLocation(declCursor)) } return Typedef(typedefDef) @@ -570,7 +636,9 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() { CXIdxEntity_Function -> { if (isSuitableFunction(cursor)) { - functionById[getDeclarationId(cursor)] = getFunction(cursor) + functionById.getOrPut(getDeclarationId(cursor)) { + getFunction(cursor) + } } } @@ -579,21 +647,23 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() { } CXIdxEntity_ObjCClass -> { - if (isAvailable(cursor)) { - getObjCClassAt(clang_getCursorReferenced(cursor)) + if (isAvailable(cursor) && + cursor.kind != CXCursorKind.CXCursor_ObjCClassRef /* not a forward declaration */) { + + getObjCClassAt(cursor) } } CXIdxEntity_ObjCCategory -> { - val classCursor = getObjCCategoryClassCursor(cursor) - if (isAvailable(classCursor)) { - val objCClass = getObjCClassAt(classCursor) - addChildrenToClassOrProtocol(cursor, objCClass) + if (isAvailable(cursor)) { + getObjCCategoryAt(cursor) } } CXIdxEntity_ObjCProtocol -> { - if (isAvailable(cursor)) { + if (isAvailable(cursor) && + cursor.kind != CXCursorKind.CXCursor_ObjCProtocolRef /* not a forward declaration */) { + getObjCProtocolAt(cursor) } } @@ -609,23 +679,16 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() { if (getter != null) { val property = ObjCProperty(entityName!!, getter, setter) - val classOrProtocol: ObjCClassOrProtocolImpl? = when (container.kind) { - CXCursorKind.CXCursor_ObjCCategoryDecl -> { - val classCursor = getObjCCategoryClassCursor(container) - if (isAvailable(classCursor)) { - getObjCClassAt(classCursor) - } else { - null - } - } + val objCContainer: ObjCContainerImpl? = when (container.kind) { + CXCursorKind.CXCursor_ObjCCategoryDecl -> getObjCCategoryAt(container) CXCursorKind.CXCursor_ObjCInterfaceDecl -> getObjCClassAt(container) CXCursorKind.CXCursor_ObjCProtocolDecl -> getObjCProtocolAt(container)!! else -> error(container.kind) } - if (classOrProtocol != null) { - classOrProtocol.properties.removeAll { property.replaces(it) } - classOrProtocol.properties.add(property) + if (objCContainer != null) { + objCContainer.properties.removeAll { property.replaces(it) } + objCContainer.properties.add(property) } } } @@ -727,19 +790,23 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() { fun buildNativeIndexImpl(library: NativeLibrary): NativeIndex { val result = NativeIndexImpl(library) - indexDeclarations(library, result) - findMacroConstants(library, result) + indexDeclarations(result) + findMacroConstants(result) return result } -private fun indexDeclarations(library: NativeLibrary, nativeIndex: NativeIndexImpl) { +private fun indexDeclarations(nativeIndex: NativeIndexImpl) { val index = clang_createIndex(0, 0)!! try { - val translationUnit = library.parse(index, options = CXTranslationUnit_DetailedPreprocessingRecord) + val translationUnit = nativeIndex.library.parse(index, options = CXTranslationUnit_DetailedPreprocessingRecord) try { translationUnit.ensureNoCompileErrors() - val headers = getFilteredHeaders(library, index, translationUnit) + val headers = getFilteredHeaders(nativeIndex, index, translationUnit) + + nativeIndex.includedHeaders = headers.map { + nativeIndex.getHeaderId(it) + } indexTranslationUnit(index, translationUnit, 0, object : Indexer { override fun indexDeclaration(info: CXIdxDeclInfo) { diff --git a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/MacroConstants.kt b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/MacroConstants.kt index ab2f35c81c7..9279f04561e 100644 --- a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/MacroConstants.kt +++ b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/MacroConstants.kt @@ -23,11 +23,11 @@ import java.io.File /** * Finds all "macro constants" and registers them as [NativeIndex.constants] in given index. */ -internal fun findMacroConstants(library: NativeLibrary, nativeIndex: NativeIndexImpl) { - val names = collectMacroConstantsNames(library) +internal fun findMacroConstants(nativeIndex: NativeIndexImpl) { + val names = collectMacroConstantsNames(nativeIndex) // TODO: apply user-defined filters. - val constants = expandMacroConstants(library, names, typeConverter = { nativeIndex.convertType(it) }) + val constants = expandMacroConstants(nativeIndex.library, names, typeConverter = { nativeIndex.convertType(it) }) nativeIndex.macroConstants.addAll(constants) } @@ -209,17 +209,17 @@ enum class VisitorState { EXPECT_END, INVALID } -private fun collectMacroConstantsNames(library: NativeLibrary): List { +private fun collectMacroConstantsNames(nativeIndex: NativeIndexImpl): List { val result = mutableSetOf() val index = clang_createIndex(excludeDeclarationsFromPCH = 0, displayDiagnostics = 0)!! try { // Include macros into AST: val options = CXTranslationUnit_DetailedPreprocessingRecord - val translationUnit = library.parse(index, options) + val translationUnit = nativeIndex.library.parse(index, options) try { translationUnit.ensureNoCompileErrors() - val headers = getFilteredHeaders(library, index, translationUnit) + val headers = getFilteredHeaders(nativeIndex, index, translationUnit) visitChildren(translationUnit) { cursor, _ -> val file = memScoped { @@ -229,7 +229,8 @@ private fun collectMacroConstantsNames(library: NativeLibrary): List { } if (cursor.kind == CXCursorKind.CXCursor_MacroDefinition && - library.includesDeclaration(cursor) && + nativeIndex.library.includesDeclaration(cursor) && + file != null && // Builtin macros mostly seem to be useless. file in headers && canMacroBeConstant(cursor)) { diff --git a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt index 50960d32443..0aed7b0c44c 100644 --- a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt +++ b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt @@ -21,13 +21,33 @@ enum class Language(val sourceFileExtension: String) { OBJECTIVE_C("m") } +interface HeaderInclusionPolicy { + /** + * Whether unused declarations from given header should be excluded. + * + * @param headerName header path relative to the appropriate include path element (e.g. `time.h` or `curl/curl.h`), + * or `null` for builtin declarations. + */ + fun excludeUnused(headerName: String?): Boolean + + /** + * Whether all declarations from this header should be excluded. + * + * Note: the declarations from such headers can be actually present in the internal representation, + * but not included into the root collections. + */ + fun excludeAll(headerId: HeaderId): Boolean + + // TODO: these methods should probably be combined into the only one, but it would require some refactoring. +} + data class NativeLibrary(val includes: List, val additionalPreambleLines: List, val compilerArgs: List, val language: Language, val excludeSystemLibs: Boolean, // TODO: drop? val excludeDepdendentModules: Boolean, - val headerFilter: (String) -> Boolean) + val headerInclusionPolicy: HeaderInclusionPolicy) /** * Retrieves the definitions from given C header file using given compiler arguments (e.g. defines). @@ -38,13 +58,28 @@ fun buildNativeIndex(library: NativeLibrary): NativeIndex = buildNativeIndexImpl * This class describes the IR of definitions from C header file(s). */ abstract class NativeIndex { - abstract val structs: List - abstract val enums: List - abstract val objCClasses: List - abstract val objCProtocols: List - abstract val typedefs: List - abstract val functions: List - abstract val macroConstants: List + abstract val structs: Collection + abstract val enums: Collection + abstract val objCClasses: Collection + abstract val objCProtocols: Collection + abstract val objCCategories: Collection + abstract val typedefs: Collection + abstract val functions: Collection + abstract val macroConstants: Collection + abstract val includedHeaders: Collection +} + +/** + * The (contents-based) header id. + * Its [value] remains valid across different runs of the indexer and the process, + * and thus can be used to 'serialize' the id. + */ +data class HeaderId(val value: String) + +data class Location(val headerId: HeaderId) + +interface TypeDeclaration { + val location: Location } /** @@ -60,7 +95,7 @@ class BitField(val name: String, val type: Type, val offset: Long, val size: Int /** * C struct declaration. */ -abstract class StructDecl(val spelling: String) { +abstract class StructDecl(val spelling: String) : TypeDeclaration { abstract val def: StructDef? } @@ -88,17 +123,19 @@ class EnumConstant(val name: String, val value: Long, val isExplicitlyDefined: B /** * C enum definition. */ -abstract class EnumDef(val spelling: String, val baseType: Type) { +abstract class EnumDef(val spelling: String, val baseType: Type) : TypeDeclaration { abstract val constants: List } -sealed class ObjCClassOrProtocol(val name: String) { +sealed class ObjCContainer { abstract val protocols: List abstract val methods: List abstract val properties: List } +sealed class ObjCClassOrProtocol(val name: String) : ObjCContainer(), TypeDeclaration + data class ObjCMethod( val selector: String, val encoding: String, val parameters: List, private val returnType: Type, val isClass: Boolean, val nsConsumesSelf: Boolean, val nsReturnsRetained: Boolean, @@ -126,10 +163,12 @@ abstract class ObjCClass(name: String) : ObjCClassOrProtocol(name) { } abstract class ObjCProtocol(name: String) : ObjCClassOrProtocol(name) +abstract class ObjCCategory(val name: String, val clazz: ObjCClass) : ObjCContainer() + /** * C function parameter. */ -class Parameter(val name: String?, val type: Type, val nsConsumed: Boolean) +data class Parameter(val name: String?, val type: Type, val nsConsumed: Boolean) /** * C function declaration. @@ -144,7 +183,7 @@ class FunctionDecl(val name: String, val parameters: List, val return * typedef $aliased $name; * ``` */ -class TypedefDef(val aliased: Type, val name: String) +class TypedefDef(val aliased: Type, val name: String, override val location: Location) : TypeDeclaration abstract class ConstantDef(val name: String, val type: Type) class IntegerConstantDef(name: String, type: Type, val value: Long) : ConstantDef(name, type) diff --git a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Utils.kt b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Utils.kt index 8e208c110f3..ea8f8f93aba 100644 --- a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Utils.kt +++ b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Utils.kt @@ -21,6 +21,8 @@ import kotlinx.cinterop.* import java.io.Closeable import java.io.File import java.nio.file.Paths +import java.security.DigestInputStream +import java.security.MessageDigest internal val CValue.kind: CXTypeKind get() = this.useContents { kind } @@ -437,8 +439,16 @@ internal class ModulesMap( } } -internal fun getFilteredHeaders(library: NativeLibrary, index: CXIndex, translationUnit: CXTranslationUnit): Set { - val result = mutableSetOf() +fun HeaderInclusionPolicy.includeAll(headerName: String?, headerId: HeaderId): Boolean = + !this.excludeUnused(headerName) && !this.excludeAll(headerId) + +internal fun getFilteredHeaders( + nativeIndex: NativeIndexImpl, + index: CXIndex, + translationUnit: CXTranslationUnit +): Set { + val library = nativeIndex.library + val result = mutableSetOf() val topLevelFiles = mutableListOf() var mainFile: CXFile? = null @@ -465,11 +475,7 @@ internal fun getFilteredHeaders(library: NativeLibrary, index: CXIndex, translat name } else { // If it is included with `#include "$name"`, then `name` can also be the path relative to the includer. - val includerFile = memScoped { - val fileVar = alloc() - clang_getFileLocation(includeLocation, fileVar.ptr, null, null, null) - fileVar.value!! - } + val includerFile = includeLocation.getContainingFile()!! val includerName = headerToName[includerFile] ?: "" val includerPath = clang_getFileName(includerFile).convertAndDispose() @@ -483,7 +489,7 @@ internal fun getFilteredHeaders(library: NativeLibrary, index: CXIndex, translat } headerToName[file] = headerName - if (library.headerFilter(headerName)) { + if (library.headerInclusionPolicy.includeAll(headerName, nativeIndex.getHeaderId(file))) { result.add(file) } } @@ -493,15 +499,19 @@ internal fun getFilteredHeaders(library: NativeLibrary, index: CXIndex, translat ModulesMap(library, translationUnit).use { modulesMap -> val topLevelModules = topLevelFiles.map { modulesMap.getModule(it) }.toSet() result.removeAll { - val module = modulesMap.getModule(it) + val module = modulesMap.getModule(it!!) module !in topLevelModules } // Note: if some of the top-level headers don't belong to modules, // then all non-modular headers are included. } + } else { + if (library.headerInclusionPolicy.includeAll(headerName = null, headerId = nativeIndex.getHeaderId(null))) { + // Builtins. + result.add(null) + } } - result.add(mainFile!!) return result @@ -512,3 +522,24 @@ fun ObjCMethod.replaces(other: ObjCMethod): Boolean = fun ObjCProperty.replaces(other: ObjCProperty): Boolean = this.getter.replaces(other.getter) + +fun File.sha256(): String { + val digest = MessageDigest.getInstance("SHA-256") + DigestInputStream(this.inputStream(), digest).use { dis -> + val buffer = ByteArray(8192) + // Read all bytes: + while (dis.read(buffer, 0, buffer.size) != -1) {} + } + // Convert to hex: + return digest.digest().joinToString("") { + Integer.toHexString((it.toInt() and 0xff) + 0x100).substring(1) + } +} + +fun headerContentsHash(filePath: String) = File(filePath).sha256() + +internal fun CValue.getContainingFile(): CXFile? = memScoped { + val fileVar = alloc() + clang_getFileLocation(this@getContainingFile, fileVar.ptr, null, null, null) + fileVar.value +} diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/Imports.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/Imports.kt new file mode 100644 index 00000000000..4a8f5c15c3a --- /dev/null +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/Imports.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.native.interop.gen + +import org.jetbrains.kotlin.native.interop.indexer.HeaderId +import org.jetbrains.kotlin.native.interop.indexer.HeaderInclusionPolicy +import org.jetbrains.kotlin.native.interop.indexer.Location + +interface Imports { + fun getPackage(location: Location): String? +} + +class ImportsImpl(internal val headerIdToPackage: Map) : Imports { + + override fun getPackage(location: Location): String? = + headerIdToPackage[location.headerId] + +} + +class HeadersInclusionPolicyImpl( + private val nameGlobs: List, + private val importsImpl: ImportsImpl +) : HeaderInclusionPolicy { + + override fun excludeUnused(headerName: String?): Boolean { + if (nameGlobs.isEmpty()) { + return false + } + + if (headerName == null) { + // Builtins; included only if no globs are specified: + return true + } + + return nameGlobs.all { !headerName.matchesToGlob(it) } + } + + override fun excludeAll(headerId: HeaderId): Boolean { + return headerId in importsImpl.headerIdToPackage + } + +} + +private fun String.matchesToGlob(glob: String): Boolean = + java.nio.file.FileSystems.getDefault() + .getPathMatcher("glob:$glob").matches(java.nio.file.Paths.get(this)) diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/KotlinCodeModel.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/KotlinCodeModel.kt new file mode 100644 index 00000000000..622fbd6d77c --- /dev/null +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/KotlinCodeModel.kt @@ -0,0 +1,260 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.native.interop.gen + +import kotlin.reflect.KProperty + +interface KotlinScope { + /** + * @return the string to be used to reference the classifier in current scope. + */ + fun reference(classifier: Classifier): String + + /** + * @return the string to be used as a name in the declaration of the classifier in current scope. + */ + fun declare(classifier: Classifier): String +} + +data class Classifier( + val pkg: String, + val topLevelName: String, + private val nestedNames: List = emptyList() +) { + + companion object { + fun topLevel(pkg: String, name: String): Classifier { + assert(!name.contains('.')) + assert(!name.contains('`')) + return Classifier(pkg, name) + } + } + + val isTopLevel: Boolean get() = this.nestedNames.isEmpty() + + fun nested(name: String): Classifier { + assert(!name.contains('.')) + assert(!name.contains('`')) + return this.copy(nestedNames = nestedNames + name) + } + + val relativeFqName: String get() = buildString { + append(topLevelName.asSimpleName()) + nestedNames.forEach { + append('.') + append(it.asSimpleName()) + } + } + + val fqName: String get() = buildString { + if (pkg.isNotEmpty()) { + append(pkg) + append('.') + } + append(relativeFqName) + } +} + +val Classifier.type + get() = KotlinClassifierType(this, arguments = emptyList(), nullable = false) + +fun Classifier.typeWith(vararg arguments: KotlinType) = + KotlinClassifierType(this, arguments.toList(), nullable = false) + +interface KotlinType { + /** + * @return the string to be used in the given scope to denote this type. + */ + fun render(scope: KotlinScope): String +} + +data class KotlinClassifierType( + val classifier: Classifier, + val arguments: List, + val nullable: Boolean +) : KotlinType { + + override fun render(scope: KotlinScope): String = buildString { + append(scope.reference(classifier)) + if (arguments.isNotEmpty()) { + append('<') + arguments.joinTo(this) { it.render(scope) } + append('>') + } + if (nullable) { + append('?') + } + } +} + +fun KotlinClassifierType.makeNullableAsSpecified(nullable: Boolean) = if (this.nullable == nullable) { + this +} else { + this.copy(nullable = nullable) +} + +fun KotlinClassifierType.makeNullable() = this.makeNullableAsSpecified(true) + +data class KotlinFunctionType( + val parameterTypes: List, + val returnType: KotlinType +) : KotlinType { + + override fun render(scope: KotlinScope) = buildString { + append('(') + parameterTypes.joinTo(this) { it.render(scope) } + append(") -> ") + append(returnType.render(scope)) + } +} + +internal val cnamesStructsPackageName = "cnames.structs" + +object KotlinTypes { + val boolean by BuiltInType + val byte by BuiltInType + val short by BuiltInType + val int by BuiltInType + val long by BuiltInType + val float by BuiltInType + val double by BuiltInType + val unit by BuiltInType + val string by BuiltInType + val any by BuiltInType + + val nativePtr by InteropType + + val cOpaque by InteropType + val cOpaquePointer by InteropType + val cOpaquePointerVar by InteropType + + val booleanVarOf by InteropClassifier + + val objCObject by InteropClassifier + val objCObjectMeta by InteropClassifier + val objCClass by InteropClassifier + + val cValuesRef by InteropClassifier + + val cPointer by InteropClassifier + val cPointerVar by InteropClassifier + val cArrayPointer by InteropClassifier + val cArrayPointerVar by InteropClassifier + val cPointerVarOf by InteropClassifier + + val cFunction by InteropClassifier + + val objCObjectVar by InteropClassifier + val objCStringVarOf by InteropClassifier + + val objCObjectBase by InteropClassifier + val objCObjectBaseMeta by InteropClassifier + + val cValue by InteropClassifier + + private object BuiltInType { + operator fun getValue(thisRef: KotlinTypes, property: KProperty<*>): KotlinClassifierType = + Classifier.topLevel("kotlin", property.name.capitalize()).type + } + + private object InteropClassifier { + operator fun getValue(thisRef: KotlinTypes, property: KProperty<*>): Classifier = + Classifier.topLevel("kotlinx.cinterop", property.name.capitalize()) + } + + private object InteropType { + operator fun getValue(thisRef: KotlinTypes, property: KProperty<*>): KotlinClassifierType = + InteropClassifier.getValue(thisRef, property).type + } +} + +class KotlinFile( + val pkg: String, + namesToBeDeclared: List +) : KotlinScope { + + // Note: all names are related to classifiers currently. + + private val namesToBeDeclared: Set + + init { + this.namesToBeDeclared = mutableSetOf() + + namesToBeDeclared.forEach { + if (it in this.namesToBeDeclared) { + throw IllegalArgumentException("'$it' is going to be declared twice") + } else { + this.namesToBeDeclared.add(it) + } + } + } + + private val importedNameToPkg = mutableMapOf() + + override fun reference(classifier: Classifier): String = if (classifier.topLevelName in namesToBeDeclared) { + if (classifier.pkg == this.pkg) { + classifier.relativeFqName + } else { + // Don't import if would clash with own declaration: + classifier.fqName + } + } else if (classifier.pkg == this.pkg) { + throw IllegalArgumentException( + "'${classifier.topLevelName}' from the file package was not reserved for declaration" + ) + } else { + val pkg = importedNameToPkg.getOrPut(classifier.topLevelName) { classifier.pkg } + if (pkg == classifier.pkg) { + // Is successfully imported: + classifier.relativeFqName + } else { + classifier.fqName + } + } + + private val alreadyDeclared = mutableSetOf() + + override fun declare(classifier: Classifier): String { + if (classifier.pkg != this.pkg) { + throw IllegalArgumentException("wrong package; expected '$pkg', got '${classifier.pkg}'") + } + + if (!classifier.isTopLevel) { + throw IllegalArgumentException( + "'${classifier.relativeFqName}' is not top-level thus can't be declared at file scope" + ) + } + + val topLevelName = classifier.topLevelName + if (topLevelName in alreadyDeclared) { + throw IllegalStateException("'$topLevelName' is already declared") + } + alreadyDeclared.add(topLevelName) + + return topLevelName.asSimpleName() + } + + fun buildImports(): List = importedNameToPkg.mapNotNull { (name, pkg) -> + if (pkg == "kotlin" || pkg == "kotlinx.cinterop") { + // Is already imported either by default or with '*': + null + } else { + "import $pkg.${name.asSimpleName()}" + } + }.sorted() + +} diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/MappingBridgeGeneratorImpl.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/MappingBridgeGeneratorImpl.kt index d5dc7b1f401..4ffbd288c88 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/MappingBridgeGeneratorImpl.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/MappingBridgeGeneratorImpl.kt @@ -26,7 +26,8 @@ import org.jetbrains.kotlin.native.interop.indexer.VoidType */ class MappingBridgeGeneratorImpl( val declarationMapper: DeclarationMapper, - val simpleBridgeGenerator: SimpleBridgeGenerator + val simpleBridgeGenerator: SimpleBridgeGenerator, + val kotlinScope: KotlinScope ) : MappingBridgeGenerator { override fun kotlinToNative( @@ -56,7 +57,7 @@ class MappingBridgeGeneratorImpl( is RecordType -> { val mirror = mirror(declarationMapper, returnType) val tmpVarName = kniRetVal - builder.out("val $tmpVarName = nativeHeap.alloc<${mirror.pointedTypeName}>()") + builder.out("val $tmpVarName = nativeHeap.alloc<${mirror.pointedType.render(kotlinScope)}>()") builder.pushBlock("try {", free = "finally { nativeHeap.free($tmpVarName) }") bridgeArguments.add(BridgeTypedKotlinValue(BridgedType.NATIVE_PTR, "$tmpVarName.rawPtr")) BridgedType.VOID @@ -106,7 +107,7 @@ class MappingBridgeGeneratorImpl( } else -> { val mirror = mirror(declarationMapper, returnType) - mirror.info.argFromBridged(callExpr) + mirror.info.argFromBridged(callExpr, kotlinScope) } } @@ -158,11 +159,12 @@ class MappingBridgeGeneratorImpl( nativeValues.forEachIndexed { index, (type, _) -> val mirror = mirror(declarationMapper, type) if (type.unwrapTypedefs() is RecordType) { + val pointedTypeName = mirror.pointedType.render(kotlinScope) kotlinValues.add( - "interpretPointed<${mirror.pointedTypeName}>(${bridgeKotlinValues[index]}).readValue()" + "interpretPointed<$pointedTypeName>(${bridgeKotlinValues[index]}).readValue()" ) } else { - kotlinValues.add(mirror.info.argFromBridged(bridgeKotlinValues[index])) + kotlinValues.add(mirror.info.argFromBridged(bridgeKotlinValues[index], kotlinScope)) } } diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/Mappings.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/Mappings.kt index 7a7db810154..78527bff80d 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/Mappings.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/Mappings.kt @@ -19,29 +19,46 @@ package org.jetbrains.kotlin.native.interop.gen import org.jetbrains.kotlin.native.interop.indexer.* interface DeclarationMapper { - fun getKotlinNameForPointed(structDecl: StructDecl): String + fun getKotlinClassForPointed(structDecl: StructDecl): Classifier fun isMappedToStrict(enumDef: EnumDef): Boolean fun getKotlinNameForValue(enumDef: EnumDef): String + fun getPackageFor(declaration: TypeDeclaration): String } -val PrimitiveType.kotlinType: String - get() = when (this) { - is CharType -> "kotlin.Byte" +fun DeclarationMapper.getKotlinClassFor( + objCClassOrProtocol: ObjCClassOrProtocol, + isMeta: Boolean = false +): Classifier { + val pkg = if (objCClassOrProtocol.shouldBeImportedAsForwardDeclaration()) { + when (objCClassOrProtocol) { + is ObjCClass -> "objcnames.classes" + is ObjCProtocol -> "objcnames.protocols" + } + } else { + this.getPackageFor(objCClassOrProtocol) + } + val className = objCClassOrProtocol.kotlinClassName(isMeta) + return Classifier.topLevel(pkg, className) +} - is BoolType -> "kotlin.Boolean" +val PrimitiveType.kotlinType: KotlinClassifierType + get() = when (this) { + is CharType -> KotlinTypes.byte + + is BoolType -> KotlinTypes.boolean // TODO: C primitive types should probably be generated as type aliases for Kotlin types. is IntegerType -> when (this.size) { - 1 -> "kotlin.Byte" - 2 -> "kotlin.Short" - 4 -> "kotlin.Int" - 8 -> "kotlin.Long" + 1 -> KotlinTypes.byte + 2 -> KotlinTypes.short + 4 -> KotlinTypes.int + 8 -> KotlinTypes.long else -> TODO(this.toString()) } is FloatingType -> when (this.size) { - 4 -> "kotlin.Float" - 8 -> "kotlin.Double" + 4 -> KotlinTypes.float + 8 -> KotlinTypes.double else -> TODO(this.toString()) } @@ -62,30 +79,31 @@ private val ObjCPointer.isNullable: Boolean /** * Describes the Kotlin types used to represent some C type. */ -sealed class TypeMirror(val pointedTypeName: String, val info: TypeInfo) { +sealed class TypeMirror(val pointedType: KotlinClassifierType, val info: TypeInfo) { /** * Type to be used in bindings for argument or return value. */ - abstract val argType: String + abstract val argType: KotlinType /** * Mirror for C type to be represented in Kotlin as by-value type. */ - class ByValue(pointedTypeName: String, info: TypeInfo, val valueTypeName: String) : - TypeMirror(pointedTypeName, info) { + class ByValue(pointedType: KotlinClassifierType, info: TypeInfo, val valueType: KotlinClassifierType) : + TypeMirror(pointedType, info) { - override val argType: String - get() = valueTypeName + - if (info is TypeInfo.Pointer || - (info is TypeInfo.ObjCPointerInfo && info.type.isNullable)) "?" else "" + override val argType: KotlinType + get() = if ((info is TypeInfo.Pointer || (info is TypeInfo.ObjCPointerInfo && info.type.isNullable))) { + valueType.makeNullable() + } else { + valueType + } } /** * Mirror for C type to be represented in Kotlin as by-ref type. */ - class ByRef(pointedTypeName: String, info: TypeInfo) : TypeMirror(pointedTypeName, info) { - override val argType: String - get() = "CValue<$pointedTypeName>" + class ByRef(pointedType: KotlinClassifierType, info: TypeInfo) : TypeMirror(pointedType, info) { + override val argType: KotlinType get() = KotlinTypes.cValue.typeWith(pointedType) } } @@ -96,109 +114,113 @@ sealed class TypeInfo { /** * The conversion from [TypeMirror.argType] to [bridgedType]. */ - abstract fun argToBridged(name: String): String + abstract fun argToBridged(expr: KotlinExpression): KotlinExpression /** * The conversion from [bridgedType] to [TypeMirror.argType]. */ - abstract fun argFromBridged(name: String): String + abstract fun argFromBridged(expr: KotlinExpression, scope: KotlinScope): KotlinExpression abstract val bridgedType: BridgedType - open fun cFromBridged(name: String): String = name + open fun cFromBridged(expr: NativeExpression): NativeExpression = expr - open fun cToBridged(name: String): String = name + open fun cToBridged(expr: NativeExpression): NativeExpression = expr /** * If this info is for [TypeMirror.ByValue], then this method describes how to * construct pointed-type from value type. */ - abstract fun constructPointedType(valueType: String): String + abstract fun constructPointedType(valueType: KotlinType): KotlinClassifierType - class Primitive(override val bridgedType: BridgedType, val varTypeName: String) : TypeInfo() { + class Primitive(override val bridgedType: BridgedType, val varClass: Classifier) : TypeInfo() { - override fun argToBridged(name: String) = name - override fun argFromBridged(name: String) = name + override fun argToBridged(expr: KotlinExpression) = expr + override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope) = expr - override fun constructPointedType(valueType: String) = "${varTypeName}Of<$valueType>" + override fun constructPointedType(valueType: KotlinType) = varClass.typeWith(valueType) } class Boolean : TypeInfo() { - override fun argToBridged(name: String) = "$name.toByte()" + override fun argToBridged(expr: KotlinExpression) = "$expr.toByte()" - override fun argFromBridged(name: String) = "$name.toBoolean()" + override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope) = "$expr.toBoolean()" override val bridgedType: BridgedType get() = BridgedType.BYTE - override fun cFromBridged(name: String) = "($name) ? 1 : 0" + override fun cFromBridged(expr: NativeExpression) = "($expr) ? 1 : 0" - override fun cToBridged(name: String) = "($name) ? 1 : 0" + override fun cToBridged(expr: NativeExpression) = "($expr) ? 1 : 0" - override fun constructPointedType(valueType: String) = "BooleanVarOf<$valueType>" + override fun constructPointedType(valueType: KotlinType) = KotlinTypes.booleanVarOf.typeWith(valueType) } - class Enum(val className: String, override val bridgedType: BridgedType) : TypeInfo() { - override fun argToBridged(name: String) = "$name.value" + class Enum(val clazz: Classifier, override val bridgedType: BridgedType) : TypeInfo() { + override fun argToBridged(expr: KotlinExpression) = "$expr.value" - override fun argFromBridged(name: String) = "$className.byValue($name)" + override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope) = + scope.reference(clazz) + ".byValue($expr)" - override fun constructPointedType(valueType: String) = "$className.Var" // TODO: improve + override fun constructPointedType(valueType: KotlinType) = + clazz.nested("Var").type // TODO: improve } - class Pointer(val pointee: String) : TypeInfo() { - override fun argToBridged(name: String) = "$name.rawValue" + class Pointer(val pointee: KotlinType) : TypeInfo() { + override fun argToBridged(expr: String) = "$expr.rawValue" - override fun argFromBridged(name: String) = "interpretCPointer<$pointee>($name)" + override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope) = + "interpretCPointer<${pointee.render(scope)}>($expr)" override val bridgedType: BridgedType get() = BridgedType.NATIVE_PTR - override fun cFromBridged(name: String) = "(void*)$name" // Note: required for JVM + override fun cFromBridged(expr: String) = "(void*)$expr" // Note: required for JVM - override fun constructPointedType(valueType: String) = "CPointerVarOf<$valueType>" + override fun constructPointedType(valueType: KotlinType) = KotlinTypes.cPointerVarOf.typeWith(valueType) } - class ObjCPointerInfo(val typeName: String, val type: ObjCPointer) : TypeInfo() { - override fun argToBridged(name: String) = "$name.rawPtr" + class ObjCPointerInfo(val kotlinType: KotlinType, val type: ObjCPointer) : TypeInfo() { + override fun argToBridged(expr: String) = "$expr.rawPtr" - override fun argFromBridged(name: String) = "interpretObjCPointerOrNull<$typeName>($name)" + - if (type.isNullable) "" else "!!" + override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope) = + "interpretObjCPointerOrNull<${kotlinType.render(scope)}>($expr)" + + if (type.isNullable) "" else "!!" override val bridgedType: BridgedType get() = BridgedType.OBJC_POINTER - override fun constructPointedType(valueType: String) = "ObjCObjectVar<$valueType>" + override fun constructPointedType(valueType: KotlinType) = KotlinTypes.objCObjectVar.typeWith(valueType) } class NSString(val type: ObjCPointer) : TypeInfo() { - override fun argToBridged(name: String) = "CreateNSStringFromKString($name)" + override fun argToBridged(expr: String) = "CreateNSStringFromKString($expr)" - override fun argFromBridged(name: String) = "CreateKStringFromNSString($name)" + + override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope) = "CreateKStringFromNSString($expr)" + if (type.isNullable) "" else "!!" override val bridgedType: BridgedType get() = BridgedType.OBJC_POINTER - override fun constructPointedType(valueType: String): String { - return "ObjCStringVarOf<$valueType>" + override fun constructPointedType(valueType: KotlinType): KotlinClassifierType { + return KotlinTypes.objCStringVarOf.typeWith(valueType) } } - class ByRef(val pointed: String) : TypeInfo() { - override fun argToBridged(name: String) = error(pointed) - override fun argFromBridged(name: String) = error(pointed) + class ByRef(val pointed: KotlinType) : TypeInfo() { + override fun argToBridged(expr: String) = error(pointed) + override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope) = error(pointed) override val bridgedType: BridgedType get() = error(pointed) - override fun cFromBridged(name: String) = error(pointed) - override fun cToBridged(name: String) = error(pointed) + override fun cFromBridged(expr: String) = error(pointed) + override fun cToBridged(expr: String) = error(pointed) // TODO: this method must not exist - override fun constructPointedType(valueType: String): String = error(pointed) + override fun constructPointedType(valueType: KotlinType): KotlinClassifierType = error(pointed) } } fun mirrorPrimitiveType(type: PrimitiveType): TypeMirror.ByValue { - val varTypeName = when (type) { + val varClassName = when (type) { is CharType -> "ByteVar" is BoolType -> "BooleanVar" is IntegerType -> when (type.size) { @@ -216,37 +238,45 @@ fun mirrorPrimitiveType(type: PrimitiveType): TypeMirror.ByValue { else -> TODO(type.toString()) } + val varClass = Classifier.topLevel("kotlinx.cinterop", varClassName) + val varClassOf = Classifier.topLevel("kotlinx.cinterop", "${varClassName}Of") + val info = if (type == BoolType) { TypeInfo.Boolean() } else { - TypeInfo.Primitive(type.bridgedType, varTypeName) + TypeInfo.Primitive(type.bridgedType, varClassOf) } - return TypeMirror.ByValue(varTypeName, info, type.kotlinType) + return TypeMirror.ByValue(varClass.type, info, type.kotlinType) } -private fun byRefTypeMirror(pointedTypeName: String) : TypeMirror.ByRef { - val info = TypeInfo.ByRef(pointedTypeName) - return TypeMirror.ByRef(pointedTypeName, info) +private fun byRefTypeMirror(pointedType: KotlinClassifierType) : TypeMirror.ByRef { + val info = TypeInfo.ByRef(pointedType) + return TypeMirror.ByRef(pointedType, info) } fun mirror(declarationMapper: DeclarationMapper, type: Type): TypeMirror = when (type) { is PrimitiveType -> mirrorPrimitiveType(type) - is RecordType -> byRefTypeMirror(declarationMapper.getKotlinNameForPointed(type.decl).asSimpleName()) + is RecordType -> byRefTypeMirror(declarationMapper.getKotlinClassForPointed(type.decl).type) is EnumType -> { + val pkg = declarationMapper.getPackageFor(type.def) val kotlinName = declarationMapper.getKotlinNameForValue(type.def) when { declarationMapper.isMappedToStrict(type.def) -> { - val classSimpleName = kotlinName.asSimpleName() val bridgedType = (type.def.baseType.unwrapTypedefs() as PrimitiveType).bridgedType - val info = TypeInfo.Enum(classSimpleName, bridgedType) - TypeMirror.ByValue("$classSimpleName.Var", info, classSimpleName) + val clazz = Classifier.topLevel(pkg, kotlinName) + val info = TypeInfo.Enum(clazz, bridgedType) + TypeMirror.ByValue(clazz.nested("Var").type, info, clazz.type) } !type.def.isAnonymous -> { val baseTypeMirror = mirror(declarationMapper, type.def.baseType) - TypeMirror.ByValue("${kotlinName}Var", baseTypeMirror.info, kotlinName.asSimpleName()) + TypeMirror.ByValue( + Classifier.topLevel(pkg, kotlinName + "Var").type, + baseTypeMirror.info, + Classifier.topLevel(pkg, kotlinName).type + ) } else -> mirror(declarationMapper, type.def.baseType) } @@ -256,15 +286,18 @@ fun mirror(declarationMapper: DeclarationMapper, type: Type): TypeMirror = when val pointeeType = type.pointeeType val unwrappedPointeeType = pointeeType.unwrapTypedefs() if (unwrappedPointeeType is VoidType) { - val info = TypeInfo.Pointer("COpaque") - TypeMirror.ByValue("COpaquePointerVar", info, "COpaquePointer") + val info = TypeInfo.Pointer(KotlinTypes.cOpaque) + TypeMirror.ByValue(KotlinTypes.cOpaquePointerVar, info, KotlinTypes.cOpaquePointer) } else if (unwrappedPointeeType is ArrayType) { mirror(declarationMapper, pointeeType) } else { val pointeeMirror = mirror(declarationMapper, pointeeType) - val info = TypeInfo.Pointer(pointeeMirror.pointedTypeName) - TypeMirror.ByValue("CPointerVar<${pointeeMirror.pointedTypeName}>", info, - "CPointer<${pointeeMirror.pointedTypeName}>") + val info = TypeInfo.Pointer(pointeeMirror.pointedType) + TypeMirror.ByValue( + KotlinTypes.cPointerVar.typeWith(pointeeMirror.pointedType), + info, + KotlinTypes.cPointer.typeWith(pointeeMirror.pointedType) + ) } } @@ -274,61 +307,76 @@ fun mirror(declarationMapper: DeclarationMapper, type: Type): TypeMirror = when if (type.elemType.unwrapTypedefs() is ArrayType) { elemTypeMirror } else { - val info = TypeInfo.Pointer(elemTypeMirror.pointedTypeName) - TypeMirror.ByValue("CArrayPointerVar<${elemTypeMirror.pointedTypeName}>", info, - "CArrayPointer<${elemTypeMirror.pointedTypeName}>") + val info = TypeInfo.Pointer(elemTypeMirror.pointedType) + TypeMirror.ByValue( + KotlinTypes.cArrayPointerVar.typeWith(elemTypeMirror.pointedType), + info, + KotlinTypes.cArrayPointer.typeWith(elemTypeMirror.pointedType) + ) } } - is FunctionType -> byRefTypeMirror("CFunction<${getKotlinFunctionType(declarationMapper, type)}>") + is FunctionType -> byRefTypeMirror(KotlinTypes.cFunction.typeWith(getKotlinFunctionType(declarationMapper, type))) is Typedef -> { val baseType = mirror(declarationMapper, type.def.aliased) + val pkg = declarationMapper.getPackageFor(type.def) + val name = type.def.name when (baseType) { - is TypeMirror.ByValue -> TypeMirror.ByValue("${name}Var", baseType.info, name.asSimpleName()) - is TypeMirror.ByRef -> TypeMirror.ByRef(name.asSimpleName(), baseType.info) + is TypeMirror.ByValue -> TypeMirror.ByValue( + Classifier.topLevel(pkg, "${name}Var").type, + baseType.info, + Classifier.topLevel(pkg, name).type + ) + + is TypeMirror.ByRef -> TypeMirror.ByRef(Classifier.topLevel(pkg, name).type, baseType.info) } } - is ObjCPointer -> objCPointerMirror(type) + is ObjCPointer -> objCPointerMirror(declarationMapper, type) else -> TODO(type.toString()) } -private fun objCPointerMirror(type: ObjCPointer): TypeMirror.ByValue { +private fun objCPointerMirror(declarationMapper: DeclarationMapper, type: ObjCPointer): TypeMirror.ByValue { if (type is ObjCObjectPointer && type.def.name == "NSString") { val info = TypeInfo.NSString(type) - val valueType = if (type.isNullable) "String?" else "String" + val valueType = KotlinTypes.string.makeNullableAsSpecified(type.isNullable) return TypeMirror.ByValue(info.constructPointedType(valueType), info, valueType) } - val typeName = when (type) { - is ObjCIdType -> type.protocols.firstOrNull()?.kotlinName ?: "ObjCObject" - is ObjCClassPointer -> "ObjCClass" - is ObjCObjectPointer -> type.def.name + val clazz = when (type) { + is ObjCIdType -> type.protocols.firstOrNull()?.let { declarationMapper.getKotlinClassFor(it) } + ?: KotlinTypes.objCObject + is ObjCClassPointer -> KotlinTypes.objCClass + is ObjCObjectPointer -> declarationMapper.getKotlinClassFor(type.def) is ObjCInstanceType -> TODO(type.toString()) // Must have already been handled. } - return objCPointerMirror(typeName.asSimpleName(), type) + return objCPointerMirror(clazz, type) } -private fun objCPointerMirror(typeName: String, type: ObjCPointer): TypeMirror.ByValue { - val valueType = if (type.isNullable) "$typeName?" else typeName - return TypeMirror.ByValue("ObjCObjectVar<$valueType>", - TypeInfo.ObjCPointerInfo(typeName, type), typeName) +private fun objCPointerMirror(clazz: Classifier, type: ObjCPointer): TypeMirror.ByValue { + val kotlinType = clazz.type + val pointedType = KotlinTypes.objCObjectVar.typeWith(kotlinType.makeNullableAsSpecified(type.isNullable)) + return TypeMirror.ByValue( + pointedType, + TypeInfo.ObjCPointerInfo(kotlinType, type), + kotlinType + ) } -fun getKotlinFunctionType(declarationMapper: DeclarationMapper, type: FunctionType): String { +fun getKotlinFunctionType(declarationMapper: DeclarationMapper, type: FunctionType): KotlinFunctionType { val returnType = if (type.returnType.unwrapTypedefs() is VoidType) { - "Unit" + KotlinTypes.unit } else { mirror(declarationMapper, type.returnType).argType } - return "(" + - type.parameterTypes.map { mirror(declarationMapper, it).argType }.joinToString(", ") + - ") -> " + + return KotlinFunctionType( + type.parameterTypes.map { mirror(declarationMapper, it).argType }, returnType + ) } diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/ObjCStubs.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/ObjCStubs.kt index 2213d2bdc52..e70498327e7 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/ObjCStubs.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/ObjCStubs.kt @@ -56,7 +56,7 @@ private fun ObjCMethod.getKotlinParameterNames(): List { class ObjCMethodStub(stubGenerator: StubGenerator, val method: ObjCMethod, - private val container: ObjCClassOrProtocol) : KotlinStub, NativeBacked { + private val container: ObjCContainer) : KotlinStub, NativeBacked { override fun generate(context: StubGenerationContext): Sequence = if (context.nativeBridges.isSupported(this)) { @@ -88,16 +88,14 @@ class ObjCMethodStub(stubGenerator: StubGenerator, private val joinedKotlinParameters: String private val header: String private val implementationTemplate: String - private val bridgeName: String + internal val bridgeName: String private val bridgeHeader: String - private val isOverride = method.isOverride(container) - init { val bodyGenerator = KotlinCodeBuilder() - val kotlinParameters = mutableListOf>() - val kotlinObjCBridgeParameters = mutableListOf>() + val kotlinParameters = mutableListOf>() + val kotlinObjCBridgeParameters = mutableListOf>() val nativeBridgeArguments = mutableListOf() val kniReceiverParameter = "kniR" @@ -105,11 +103,11 @@ class ObjCMethodStub(stubGenerator: StubGenerator, val voidPtr = PointerType(VoidType) - val returnType = method.getReturnType(container) + val returnType = method.getReturnType(container.classOrProtocol) val messengerGetter = if (returnType.isLargeOrUnaligned()) "getMessengerLU" else "getMessenger" - kotlinObjCBridgeParameters.add(kniSuperClassParameter to "NativePtr") + kotlinObjCBridgeParameters.add(kniSuperClassParameter to KotlinTypes.nativePtr) nativeBridgeArguments.add(TypedKotlinValue(voidPtr, "$messengerGetter($kniSuperClassParameter)")) if (method.nsConsumesSelf) { @@ -117,7 +115,7 @@ class ObjCMethodStub(stubGenerator: StubGenerator, bodyGenerator.out("objc_retain($kniReceiverParameter.rawPtr)") } - kotlinObjCBridgeParameters.add(kniReceiverParameter to "ObjCObject") + kotlinObjCBridgeParameters.add(kniReceiverParameter to KotlinTypes.objCObject.type) nativeBridgeArguments.add( TypedKotlinValue(voidPtr, "getReceiverOrSuper($kniReceiverParameter.rawPtr, $kniSuperClassParameter)")) @@ -135,10 +133,10 @@ class ObjCMethodStub(stubGenerator: StubGenerator, } val kotlinReturnType = if (returnType.unwrapTypedefs() is VoidType) { - "Unit" + KotlinTypes.unit } else { stubGenerator.mirror(returnType).argType - } + }.render(stubGenerator.kotlinFile) val result = stubGenerator.mappingBridgeGenerator.kotlinToNative( bodyGenerator, @@ -172,36 +170,67 @@ class ObjCMethodStub(stubGenerator: StubGenerator, bridgeName = "objcKniBridge${stubGenerator.nextUniqueId()}" - this.bridgeHeader = "internal fun $bridgeName(" + - "${kotlinObjCBridgeParameters.joinToString { "${it.first.asSimpleName()}: ${it.second}" }})" + - ": $kotlinReturnType" + this.bridgeHeader = buildString { + append("internal fun ") + append(bridgeName) + append('(') + kotlinObjCBridgeParameters.joinTo(this) { + "${it.first.asSimpleName()}: ${it.second.render(stubGenerator.kotlinFile)}" + } + append("): ") + append(kotlinReturnType) + } - this.joinedKotlinParameters = kotlinParameters.joinToString { "${it.first.asSimpleName()}: ${it.second}" } + this.joinedKotlinParameters = kotlinParameters.joinToString { + "${it.first.asSimpleName()}: ${it.second.render(stubGenerator.kotlinFile)}" + } this.header = buildString { - if (container is ObjCClass) append("external ") - if (isOverride) { - append("override ") - } else if (container is ObjCClass) { - append("open ") + if (container !is ObjCProtocol) append("external ") + val modality = when (container) { + is ObjCClassOrProtocol -> if (method.isOverride(container)) { + "override " + } else when (container) { + is ObjCClass -> "open " + is ObjCProtocol -> "" + } + is ObjCCategory -> "" } + append(modality) - append("fun ${method.kotlinName.asSimpleName()}($joinedKotlinParameters): $kotlinReturnType") + append("fun ") + if (container is ObjCCategory) { + val receiverType = stubGenerator.declarationMapper + .getKotlinClassFor(container.clazz, isMeta = method.isClass).type + .render(stubGenerator.kotlinFile) + + append(receiverType) + append('.') + } + append("${method.kotlinName.asSimpleName()}($joinedKotlinParameters): $kotlinReturnType") if (container is ObjCProtocol && method.isOptional) append(" = optional()") } } - private fun genImplementationTemplate(stubGenerator: StubGenerator): String { - val codeBuilder = NativeCodeBuilder() + private fun genImplementationTemplate(stubGenerator: StubGenerator): String = when (container) { + is ObjCClassOrProtocol -> { + val codeBuilder = NativeCodeBuilder() - val result = codeBuilder.genMethodImp(stubGenerator, this, method, container) - stubGenerator.simpleBridgeGenerator.insertNativeBridge(this, emptyList(), codeBuilder.lines) - - return result + val result = codeBuilder.genMethodImp(stubGenerator, this, method, container) + stubGenerator.simpleBridgeGenerator.insertNativeBridge(this, emptyList(), codeBuilder.lines) + result + } + is ObjCCategory -> "" } } +private val ObjCContainer.classOrProtocol: ObjCClassOrProtocol + get() = when (this) { + is ObjCClassOrProtocol -> this + is ObjCCategory -> this.clazz + } + private fun Type.isLargeOrUnaligned(): Boolean { val unwrappedType = this.unwrapTypedefs() return when (unwrappedType) { @@ -263,6 +292,7 @@ abstract class ObjCContainerStub(stubGenerator: StubGenerator, private val isMeta: Boolean) : KotlinStub { private val methods: List + private val properties: List init { val superMethods = container.inheritedMethods(isMeta) @@ -295,45 +325,47 @@ abstract class ObjCContainerStub(stubGenerator: StubGenerator, .mapNotNull { (_, inheritedMethods) -> if (inheritedMethods.size > 1) inheritedMethods.first() else null } this.methods = methods.distinctBy { it.selector }.toList() - } - private val methodStubs = methods.map { - ObjCMethodStub(stubGenerator, it, container) - } - - private val properties: List - - init { - val superProperties = container.superTypes.flatMap { it.properties.asSequence() } - - this.properties = container.properties.filter { - it.getter.isClass == isMeta && + this.properties = container.properties.filter { property -> + property.getter.isClass == isMeta && // Select only properties that don't override anything: - superProperties.none(it::replaces) + superMethods.none { property.getter.replaces(it) || property.setter?.replaces(it) ?: false } } } + private val methodToStub = methods.map { + it to ObjCMethodStub(stubGenerator, it, container) + }.toMap() + + private val methodStubs get() = methodToStub.values + val propertyStubs = properties.map { - ObjCPropertyStub(stubGenerator, it, container) + createObjCPropertyStub(stubGenerator, it, container, this.methodToStub) } private val classHeader: String init { - val nameSuffix = if (isMeta) "Meta" else "" - - val supers = mutableListOf() + val supers = mutableListOf() if (container is ObjCClass) { - supers.add(container.baseClassName) + val baseClass = container.baseClass + val baseClassifier = if (baseClass != null) { + stubGenerator.declarationMapper.getKotlinClassFor(baseClass, isMeta) + } else { + if (isMeta) KotlinTypes.objCObjectBaseMeta else KotlinTypes.objCObjectBase + } + + supers.add(baseClassifier.type) } container.protocols.forEach { - supers.add(it.kotlinName) + supers.add(stubGenerator.declarationMapper.getKotlinClassFor(it, isMeta).type) } if (supers.isEmpty()) { assert(container is ObjCProtocol) - supers.add("ObjCObject") + val classifier = if (isMeta) KotlinTypes.objCObjectMeta else KotlinTypes.objCObject + supers.add(classifier.type) } val keywords = when (container) { @@ -341,8 +373,9 @@ abstract class ObjCContainerStub(stubGenerator: StubGenerator, is ObjCProtocol -> "interface" } - val supersString = supers.joinToString { "$it$nameSuffix".asSimpleName() } - val name = "${container.kotlinName}$nameSuffix".asSimpleName() + val supersString = supers.joinToString { it.render(stubGenerator.kotlinFile) } + val classifier = stubGenerator.declarationMapper.getKotlinClassFor(container, isMeta) + val name = stubGenerator.kotlinFile.declare(classifier) this.classHeader = "@ExternalObjCClass $keywords $name : $supersString" } @@ -382,32 +415,81 @@ open class ObjCClassOrProtocolStub( class ObjCProtocolStub(stubGenerator: StubGenerator, protocol: ObjCProtocol) : ObjCClassOrProtocolStub(stubGenerator, protocol) -class ObjCClassStub(stubGenerator: StubGenerator, private val clazz: ObjCClass) : +class ObjCClassStub(private val stubGenerator: StubGenerator, private val clazz: ObjCClass) : ObjCClassOrProtocolStub(stubGenerator, clazz) { - override fun generateBody(context: StubGenerationContext) = - sequenceOf( "companion object : ${clazz.kotlinName}Meta() {}") + - super.generateBody(context) + override fun generateBody(context: StubGenerationContext): Sequence { + val companionSuper = stubGenerator.declarationMapper + .getKotlinClassFor(clazz, isMeta = true).type + .render(stubGenerator.kotlinFile) + return sequenceOf( "companion object : $companionSuper() {}") + + super.generateBody(context) + } +} + +class ObjCCategoryStub( + private val stubGenerator: StubGenerator, private val category: ObjCCategory +) : KotlinStub { + + // TODO: consider removing members that are also present in the class or its supertypes. + + private val methodToStub = category.methods.map { + it to ObjCMethodStub(stubGenerator, it, category) + }.toMap() + + private val methodStubs get() = methodToStub.values + + private val propertyStubs = category.properties.map { + createObjCPropertyStub(stubGenerator, it, category, methodToStub) + } + + override fun generate(context: StubGenerationContext): Sequence { + val description = "${category.clazz.name} (${category.name})" + return sequenceOf("// @interface $description") + + propertyStubs.asSequence().flatMap { sequenceOf("") + it.generate(context) } + + methodStubs.asSequence().flatMap { sequenceOf("") + it.generate(context) } + + sequenceOf("// @end; // $description") + } +} + +private fun createObjCPropertyStub( + stubGenerator: StubGenerator, + property: ObjCProperty, + container: ObjCContainer, + methodToStub: Map +): ObjCPropertyStub { + // Note: the code below assumes that if the property is generated, + // then its accessors are also generated as explicit methods. + val getterStub = methodToStub[property.getter]!! + val setterStub = property.setter?.let { methodToStub[it]!! } + return ObjCPropertyStub(stubGenerator, property, container, getterStub, setterStub) } class ObjCPropertyStub( - val stubGenerator: StubGenerator, val property: ObjCProperty, val clazz: ObjCClassOrProtocol + val stubGenerator: StubGenerator, val property: ObjCProperty, val container: ObjCContainer, + val getterStub: ObjCMethodStub, val setterStub: ObjCMethodStub? ) : KotlinStub { override fun generate(context: StubGenerationContext): Sequence { - val type = property.getType(clazz) + val type = property.getType(container.classOrProtocol) - val kotlinType = stubGenerator.mirror(type).argType + val kotlinType = stubGenerator.mirror(type).argType.render(stubGenerator.kotlinFile) val kind = if (property.setter == null) "val" else "var" - val modifiers = if (clazz is ObjCClass) "" else "final " + val modifiers = if (container is ObjCProtocol) "final " else "" + val receiver = when (container) { + is ObjCClassOrProtocol -> "" + is ObjCCategory -> stubGenerator.declarationMapper + .getKotlinClassFor(container.clazz, isMeta = property.getter.isClass).type + .render(stubGenerator.kotlinFile) + "." + } val result = mutableListOf( - "$modifiers$kind ${property.name.asSimpleName()}: $kotlinType", - " get() = ${property.getter.kotlinName.asSimpleName()}()" + "$modifiers$kind $receiver${property.name.asSimpleName()}: $kotlinType", + " get() = ${getterStub.bridgeName}(nativeNullPtr, this)" ) property.setter?.let { - result.add(" set(value) = ${it.kotlinName.asSimpleName()}(value)") + result.add(" set(value) = ${setterStub!!.bridgeName}(nativeNullPtr, this, value)") } return result.asSequence() @@ -415,13 +497,25 @@ class ObjCPropertyStub( } -val ObjCClassOrProtocol.kotlinName: String get() = when (this) { - is ObjCClass -> this.name - is ObjCProtocol -> "${this.name}Protocol" +fun ObjCClassOrProtocol.kotlinClassName(isMeta: Boolean): String { + val baseClassName = when (this) { + is ObjCClass -> this.name + is ObjCProtocol -> "${this.name}Protocol" + } + + return if (isMeta) "${baseClassName}Meta" else baseClassName } -private val ObjCClass.baseClassName: String - get() = baseClass?.name ?: "ObjCObjectBase" +fun ObjCClassOrProtocol.shouldBeImportedAsForwardDeclaration(): Boolean { + // Note: currently it is not very easy to distinct class or protocol declarations from forward references, + // so the code below treats the empty declarations as forward ones; + // it is correct because empty and forward declarations feel exactly the same. + + return this.protocols.isEmpty() && + this.methods.isEmpty() && + this.properties.isEmpty() && + (this as? ObjCClass)?.baseClass == null +} private fun Parameter.getTypeStringRepresentation() = (if (this.nsConsumed) "__attribute__((ns_consumed)) " else "") + type.getStringRepresentation() @@ -477,9 +571,12 @@ private fun NativeCodeBuilder.genMethodImp( bridgeArguments ) { kotlinValues -> - val kotlinReceiverType = if (method.isClass) "${container.kotlinName}Meta" else container.kotlinName + val kotlinReceiverType = stubGenerator.declarationMapper + .getKotlinClassFor(container, isMeta = method.isClass) + .type.render(stubGenerator.kotlinFile) + val kotlinRawReceiver = kotlinValues.first() - val kotlinReceiver = "$kotlinRawReceiver.uncheckedCast<${kotlinReceiverType.asSimpleName()}>()" + val kotlinReceiver = "$kotlinRawReceiver.uncheckedCast<$kotlinReceiverType>()" val namedArguments = kotlinValues.drop(1).zip(method.getKotlinParameterNames()) { value, name -> "${name.asSimpleName()} = $value" diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/SimpleBridgeGenerator.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/SimpleBridgeGenerator.kt index 1dd89b16463..e8fc8ded270 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/SimpleBridgeGenerator.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/SimpleBridgeGenerator.kt @@ -19,16 +19,16 @@ package org.jetbrains.kotlin.native.interop.gen /** * The type which has exact counterparts on both Kotlin and native side and can be directly passed through bridges. */ -enum class BridgedType(val kotlinType: String, val convertor: String? = null) { - BYTE("kotlin.Byte", "toByte"), - SHORT("kotlin.Short", "toShort"), - INT("kotlin.Int", "toInt"), - LONG("kotlin.Long", "toLong"), - FLOAT("kotlin.Float", "toFloat"), - DOUBLE("kotlin.Double", "toDouble"), - NATIVE_PTR("NativePtr"), - OBJC_POINTER("NativePtr"), - VOID("kotlin.Unit") +enum class BridgedType(val kotlinType: KotlinClassifierType, val convertor: String? = null) { + BYTE(KotlinTypes.byte, "toByte"), + SHORT(KotlinTypes.short, "toShort"), + INT(KotlinTypes.int, "toInt"), + LONG(KotlinTypes.long, "toLong"), + FLOAT(KotlinTypes.float, "toFloat"), + DOUBLE(KotlinTypes.double, "toDouble"), + NATIVE_PTR(KotlinTypes.nativePtr), + OBJC_POINTER(KotlinTypes.nativePtr), + VOID(KotlinTypes.unit) } data class BridgeTypedKotlinValue(val type: BridgedType, val value: KotlinExpression) diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/SimpleBridgeGeneratorImpl.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/SimpleBridgeGeneratorImpl.kt index 41c4cf0da0a..5a06d0eba6a 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/SimpleBridgeGeneratorImpl.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/SimpleBridgeGeneratorImpl.kt @@ -25,7 +25,8 @@ class SimpleBridgeGeneratorImpl( private val platform: KotlinPlatform, private val pkgName: String, private val jvmFileClassName: String, - private val libraryForCStubs: NativeLibrary + private val libraryForCStubs: NativeLibrary, + private val kotlinScope: KotlinScope ) : SimpleBridgeGenerator { private var nextUniqueId = 0 @@ -69,7 +70,7 @@ class SimpleBridgeGeneratorImpl( val kotlinFunctionName = "kniBridge${nextUniqueId++}" val kotlinParameters = kotlinValues.withIndex().joinToString { - "p${it.index}: ${it.value.type.kotlinType}" + "p${it.index}: ${it.value.type.kotlinType.render(kotlinScope)}" } val callExpr = "$kotlinFunctionName(${kotlinValues.joinToString { it.value }})" @@ -130,7 +131,8 @@ class SimpleBridgeGeneratorImpl( } nativeLines.add("}") - kotlinLines.add("private external fun $kotlinFunctionName($kotlinParameters): ${returnType.kotlinType}") + val kotlinReturnType = returnType.kotlinType.render(kotlinScope) + kotlinLines.add("private external fun $kotlinFunctionName($kotlinParameters): $kotlinReturnType") val nativeBridge = NativeBridge(kotlinLines, nativeLines) nativeBridges.add(nativeBacked to nativeBridge) @@ -154,7 +156,7 @@ class SimpleBridgeGeneratorImpl( val kotlinParameters = nativeValues.withIndex().map { "p${it.index}" to it.value.type.kotlinType } - val joinedKotlinParameters = kotlinParameters.joinToString { "${it.first}: ${it.second}" } + val joinedKotlinParameters = kotlinParameters.joinToString { "${it.first}: ${it.second.render(kotlinScope)}" } val cFunctionParameters = nativeValues.withIndex().map { "p${it.index}" to it.value.type.nativeType @@ -167,7 +169,8 @@ class SimpleBridgeGeneratorImpl( val cFunctionHeader = "$cReturnType $symbolName($joinedCParameters)" nativeLines.add("$cFunctionHeader;") - kotlinLines.add("private fun $kotlinFunctionName($joinedKotlinParameters): ${returnType.kotlinType} {") + val kotlinReturnType = returnType.kotlinType.render(kotlinScope) + kotlinLines.add("private fun $kotlinFunctionName($joinedKotlinParameters): $kotlinReturnType {") buildKotlinCodeLines { var kotlinExpr = block(kotlinParameters.map { (name, _) -> name }) diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt index 9a2011d39fc..07a96273fb0 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.native.interop.gen.jvm import org.jetbrains.kotlin.native.interop.gen.* import org.jetbrains.kotlin.native.interop.indexer.* import java.lang.IllegalStateException +import java.util.* enum class KotlinPlatform { JVM, @@ -31,7 +32,9 @@ class StubGenerator( val libName: String, val dumpShims: Boolean, val verbose: Boolean = false, - val platform: KotlinPlatform = KotlinPlatform.JVM) { + val platform: KotlinPlatform = KotlinPlatform.JVM, + val imports: Imports +) { private var theCounter = 0 fun nextUniqueId() = theCounter++ @@ -60,10 +63,8 @@ class StubGenerator( * The names that should not be used for struct classes to prevent name clashes */ val forbiddenStructNames = run { - val functionNames = nativeIndex.functions.map { it.name } - val fieldNames = nativeIndex.structs.mapNotNull { it.def }.flatMap { it.fields }.map { it.name } val typedefNames = nativeIndex.typedefs.map { it.name } - (functionNames + fieldNames + typedefNames).toSet() + typedefNames.toSet() } val StructDecl.isAnonymous: Boolean @@ -89,6 +90,8 @@ class StubGenerator( spelling } + // TODO: don't mangle struct names because it wouldn't work if the struct + // is imported into another interop library. return if (strippedCName !in forbiddenStructNames) strippedCName else (strippedCName + "Struct") } @@ -129,11 +132,26 @@ class StubGenerator( } val declarationMapper = object : DeclarationMapper { - override fun getKotlinNameForPointed(structDecl: StructDecl): String = structDecl.kotlinName + override fun getKotlinClassForPointed(structDecl: StructDecl): Classifier { + val baseName = structDecl.kotlinName + val pkg = when (platform) { + KotlinPlatform.JVM -> pkgName + KotlinPlatform.NATIVE -> if (structDecl.def == null) { + cnamesStructsPackageName // to be imported as forward declaration. + } else { + getPackageFor(structDecl) + } + } + return Classifier.topLevel(pkg, baseName) + } override fun isMappedToStrict(enumDef: EnumDef): Boolean = enumDef.isStrictEnum override fun getKotlinNameForValue(enumDef: EnumDef): String = enumDef.kotlinName + + override fun getPackageFor(declaration: TypeDeclaration): String { + return imports.getPackage(declaration.location) ?: pkgName + } } fun mirror(type: Type): TypeMirror = mirror(declarationMapper, type) @@ -142,6 +160,63 @@ class StubGenerator( private val macroConstantsByName = nativeIndex.macroConstants.associateBy { it.name } + val kotlinFile = KotlinFile(pkgName, namesToBeDeclared = computeNamesToBeDeclared()) + + private fun computeNamesToBeDeclared(): MutableList { + return mutableListOf().apply { + nativeIndex.typedefs.forEach { + getTypeDeclaringNames(Typedef(it), this) + } + + nativeIndex.objCProtocols.forEach { + add(it.kotlinClassName(isMeta = false)) + add(it.kotlinClassName(isMeta = true)) + } + + nativeIndex.objCClasses.forEach { + add(it.kotlinClassName(isMeta = false)) + add(it.kotlinClassName(isMeta = true)) + } + + nativeIndex.structs.forEach { + getTypeDeclaringNames(RecordType(it), this) + } + + nativeIndex.enums.forEach { + if (!it.isAnonymous) { + getTypeDeclaringNames(EnumType(it), this) + } + } + } + } + + /** + * Finds all names to be declared for the given type declaration, + * and adds them to [result]. + * + * TODO: refactor to compute these names directly from declarations. + */ + private fun getTypeDeclaringNames(type: Type, result: MutableList) { + if (type.unwrapTypedefs() == VoidType) { + return + } + + val mirror = mirror(type) + val varClassifier = mirror.pointedType.classifier + if (varClassifier.pkg == pkgName) { + result.add(varClassifier.topLevelName) + } + when (mirror) { + is TypeMirror.ByValue -> { + val valueClassifier = mirror.valueType.classifier + if (valueClassifier.pkg == pkgName && valueClassifier.topLevelName != varClassifier.topLevelName) { + result.add(valueClassifier.topLevelName) + } + } + is TypeMirror.ByRef -> {} + } + } + /** * The output currently used by the generator. * Should append line separator after any usage. @@ -262,7 +337,9 @@ class StubGenerator( } } - block("class ${decl.kotlinName.asSimpleName()}(rawPtr: NativePtr) : CStructVar(rawPtr)") { + val kotlinName = kotlinFile.declare(declarationMapper.getKotlinClassForPointed(decl)) + + block("class $kotlinName(rawPtr: NativePtr) : CStructVar(rawPtr)") { out("") out("companion object : Type(${def.size}, ${def.align})") // FIXME: align out("") @@ -276,7 +353,7 @@ class StubGenerator( val fieldRefType = mirror(field.type) val unwrappedFieldType = field.type.unwrapTypedefs() if (unwrappedFieldType is ArrayType) { - val type = (fieldRefType as TypeMirror.ByValue).valueTypeName + val type = (fieldRefType as TypeMirror.ByValue).valueType.render(kotlinFile) if (platform == KotlinPlatform.JVM) { val length = getArrayLength(unwrappedFieldType) @@ -288,13 +365,13 @@ class StubGenerator( out("val ${field.name.asSimpleName()}: $type") out(" get() = arrayMemberAt($offset)") } else { + val pointedTypeName = fieldRefType.pointedType.render(kotlinFile) if (fieldRefType is TypeMirror.ByValue) { - val pointedTypeName = fieldRefType.pointedTypeName - out("var ${field.name.asSimpleName()}: ${fieldRefType.argType}") + out("var ${field.name.asSimpleName()}: ${fieldRefType.argType.render(kotlinFile)}") out(" get() = memberAt<$pointedTypeName>($offset).value") out(" set(value) { memberAt<$pointedTypeName>($offset).value = value }") } else { - out("val ${field.name.asSimpleName()}: ${fieldRefType.pointedTypeName}") + out("val ${field.name.asSimpleName()}: $pointedTypeName") out(" get() = memberAt($offset)") } } @@ -308,7 +385,7 @@ class StubGenerator( for (field in def.bitFields) { val typeMirror = mirror(field.type) val typeInfo = typeMirror.info - val kotlinType = typeMirror.argType + val kotlinType = typeMirror.argType.render(kotlinFile) val rawType = typeInfo.bridgedType out("var ${field.name.asSimpleName()}: $kotlinType") @@ -318,7 +395,7 @@ class StubGenerator( val readBitsExpr = "readBits(this.rawPtr, ${field.offset}, ${field.size}, $signed).${rawType.convertor!!}()" - out(" get() = ${typeInfo.argFromBridged(readBitsExpr)}") + out(" get() = ${typeInfo.argFromBridged(readBitsExpr, kotlinFile)}") val rawValue = typeInfo.argToBridged("value") val setExpr = "writeBits(this.rawPtr, ${field.offset}, ${field.size}, $rawValue.toLong())" @@ -339,8 +416,9 @@ class StubGenerator( /** * Produces to [out] the definition of Kotlin class representing the reference to given forward (incomplete) struct. */ - private fun generateForwardStruct(s: StructDecl) { - out("class ${s.kotlinName.asSimpleName()}(rawPtr: NativePtr) : COpaque(rawPtr)") + private fun generateForwardStruct(s: StructDecl) = when (platform) { + KotlinPlatform.JVM -> out("class ${s.kotlinName.asSimpleName()}(rawPtr: NativePtr) : COpaque(rawPtr)") + KotlinPlatform.NATIVE -> {} } private fun EnumConstant.isMoreCanonicalThan(other: EnumConstant): Boolean = with(other.name.toLowerCase()) { @@ -359,7 +437,7 @@ class StubGenerator( } val baseTypeMirror = mirror(e.baseType) - val baseKotlinType = baseTypeMirror.argType + val baseKotlinType = baseTypeMirror.argType.render(kotlinFile) val canonicalsByValue = e.constants .groupingBy { it.value } @@ -373,7 +451,9 @@ class StubGenerator( val (canonicalConstants, aliasConstants) = e.constants.partition { canonicalsByValue[it.value] == it } - block("enum class ${e.kotlinName.asSimpleName()}(override val value: $baseKotlinType) : CEnum") { + val clazz = (mirror(EnumType(e)) as TypeMirror.ByValue).valueType.classifier + + block("enum class ${kotlinFile.declare(clazz)}(override val value: $baseKotlinType) : CEnum") { canonicalConstants.forEach { out("${it.name.asSimpleName()}(${it.value}),") } @@ -391,10 +471,11 @@ class StubGenerator( } out("") block("class Var(rawPtr: NativePtr) : CEnumVar(rawPtr)") { - out("companion object : Type(${baseTypeMirror.pointedTypeName}.size.toInt())") + val basePointedTypeName = baseTypeMirror.pointedType.render(kotlinFile) + out("companion object : Type($basePointedTypeName.size.toInt())") out("var value: ${e.kotlinName.asSimpleName()}") - out(" get() = byValue(this.reinterpret<${baseTypeMirror.pointedTypeName}>().value)") - out(" set(value) { this.reinterpret<${baseTypeMirror.pointedTypeName}>().value = value.value }") + out(" get() = byValue(this.reinterpret<$basePointedTypeName>().value)") + out(" set(value) { this.reinterpret<$basePointedTypeName>().value = value.value }") } } } @@ -415,7 +496,7 @@ class StubGenerator( val typeName: String - val baseKotlinType = mirror(e.baseType).argType + val baseKotlinType = mirror(e.baseType).argType.render(kotlinFile) if (e.isAnonymous) { if (constants.isNotEmpty()) { out("// ${e.spelling}:") @@ -429,15 +510,17 @@ class StubGenerator( } // Generate as typedef: - val varTypeName = typeMirror.info.constructPointedType(typeMirror.valueTypeName) - out("typealias ${typeMirror.pointedTypeName} = $varTypeName") - out("typealias ${typeMirror.valueTypeName} = $baseKotlinType") + val varTypeName = typeMirror.info.constructPointedType(typeMirror.valueType).render(kotlinFile) + val varTypeClassifier = typeMirror.pointedType.classifier + val valueTypeClassifier = typeMirror.valueType.classifier + out("typealias ${kotlinFile.declare(varTypeClassifier)} = $varTypeName") + out("typealias ${kotlinFile.declare(valueTypeClassifier)} = $baseKotlinType") if (constants.isNotEmpty()) { out("") } - typeName = typeMirror.valueTypeName + typeName = typeMirror.valueType.render(kotlinFile) } for (constant in constants) { @@ -450,15 +533,19 @@ class StubGenerator( val mirror = mirror(Typedef(def)) val baseMirror = mirror(def.aliased) + val varType = mirror.pointedType when (baseMirror) { is TypeMirror.ByValue -> { - val valueTypeName = (mirror as TypeMirror.ByValue).valueTypeName - val varTypeAliasee = mirror.info.constructPointedType(valueTypeName) - out("typealias ${mirror.pointedTypeName} = $varTypeAliasee") - out("typealias $valueTypeName = ${baseMirror.valueTypeName}") + val valueType = (mirror as TypeMirror.ByValue).valueType + val varTypeAliasee = mirror.info.constructPointedType(valueType).render(kotlinFile) + val valueTypeAliasee = baseMirror.valueType.render(kotlinFile) + + out("typealias ${kotlinFile.declare(varType.classifier)} = $varTypeAliasee") + out("typealias ${kotlinFile.declare(valueType.classifier)} = $valueTypeAliasee") } is TypeMirror.ByRef -> { - out("typealias ${mirror.pointedTypeName} = ${baseMirror.pointedTypeName}") + val varTypeAliasee = baseMirror.pointedType.render(kotlinFile) + out("typealias ${kotlinFile.declare(varType.classifier)} = $varTypeAliasee") } } } @@ -499,7 +586,7 @@ class StubGenerator( init { // TODO: support dumpShims - val kotlinParameters = mutableListOf>() + val kotlinParameters = mutableListOf>() val bodyGenerator = KotlinCodeBuilder() val bridgeArguments = mutableListOf() @@ -517,15 +604,17 @@ class StubGenerator( val representAsValuesRef = representCFunctionParameterAsValuesRef(parameter.type) val bridgeArgument = if (representCFunctionParameterAsString(parameter.type)) { - kotlinParameters.add(parameterName to "String?") + kotlinParameters.add(parameterName to KotlinTypes.string.makeNullable()) bodyGenerator.pushBlock("$parameterName?.cstr.usePointer { $tmpVarName ->") tmpVarName } else if (representCFunctionParameterAsWString(parameter.type)) { - kotlinParameters.add(parameterName to "String?") + kotlinParameters.add(parameterName to KotlinTypes.string.makeNullable()) bodyGenerator.pushBlock("$parameterName?.wcstr.usePointer { $tmpVarName ->") tmpVarName } else if (representAsValuesRef != null) { - kotlinParameters.add(parameterName to "CValuesRef<${mirror(representAsValuesRef).pointedTypeName}>?") + val pointedType = mirror(representAsValuesRef).pointedType + val parameterType = KotlinTypes.cValuesRef.typeWith(pointedType).makeNullable() + kotlinParameters.add(parameterName to parameterType) bodyGenerator.pushBlock("$parameterName.usePointer { $tmpVarName ->") tmpVarName } else { @@ -550,13 +639,13 @@ class StubGenerator( } else { val returnTypeKind = getFfiTypeKind(func.returnType) - kotlinParameters.add("vararg variadicArguments" to "Any?") + kotlinParameters.add("vararg variadicArguments" to KotlinTypes.any.makeNullable()) bodyGenerator.pushBlock("memScoped {") val resultVar = "kniResult" val resultPtr = if (!func.returnsVoid()) { - val returnType = mirror(func.returnType).pointedTypeName + val returnType = mirror(func.returnType).pointedType.render(kotlinFile) bodyGenerator.out("val $resultVar = allocFfiReturnValueBuffer<$returnType>(typeOf<$returnType>())") "$resultVar.rawPtr" } else { @@ -581,12 +670,14 @@ class StubGenerator( } val returnType = if (func.returnsVoid()) { - "Unit" + KotlinTypes.unit } else { mirror(func.returnType).argType - } + }.render(kotlinFile) - val joinedKotlinParameters = kotlinParameters.joinToString { (name, type) -> "$name: $type" } + val joinedKotlinParameters = kotlinParameters.joinToString { (name, type) -> + "$name: ${type.render(kotlinFile)}" + } this.header = "fun ${func.name}($joinedKotlinParameters): $returnType" this.bodyLines = bodyGenerator.build() @@ -626,10 +717,10 @@ class StubGenerator( } val narrowedValue: Number = when (unwrappedType.kotlinType) { - "kotlin.Byte" -> value.toByte() - "kotlin.Short" -> value.toShort() - "kotlin.Int" -> value.toInt() - "kotlin.Long" -> value + KotlinTypes.byte -> value.toByte() + KotlinTypes.short -> value.toShort() + KotlinTypes.int -> value.toInt() + KotlinTypes.long -> value else -> return null } @@ -663,7 +754,7 @@ class StubGenerator( } } - val kotlinType = mirror(constant.type).argType + val kotlinType = mirror(constant.type).argType.render(kotlinFile) // TODO: improve value rendering. @@ -680,12 +771,20 @@ class StubGenerator( stubs.addAll(generateStubsForFunctions(functionsToBind)) - nativeIndex.objCProtocols.mapTo(stubs) { - ObjCProtocolStub(this, it) + nativeIndex.objCProtocols.forEach { + if (!it.shouldBeImportedAsForwardDeclaration()) { + stubs.add(ObjCProtocolStub(this, it)) + } } - nativeIndex.objCClasses.mapTo(stubs) { - ObjCClassStub(this, it) + nativeIndex.objCClasses.forEach { + if (!it.shouldBeImportedAsForwardDeclaration()) { + stubs.add(ObjCClassStub(this, it)) + } + } + + nativeIndex.objCCategories.mapTo(stubs) { + ObjCCategoryStub(this, it) } nativeIndex.macroConstants.forEach { @@ -751,6 +850,7 @@ class StubGenerator( add("PARAMETER_NAME_CHANGED_ON_OVERRIDE") add("UNUSED_PARAMETER") // For constructors. add("MANY_INTERFACES_MEMBER_NOT_IMPLEMENTED") // Workaround for multiple-inherited properties. + add("EXTENSION_SHADOWED_BY_MEMBER") // For Objective-C categories represented as extensions. } } @@ -763,6 +863,11 @@ class StubGenerator( out("import konan.SymbolName") } out("import kotlinx.cinterop.*") + + kotlinFile.buildImports().forEach { + out(it) + } + out("") val context = object : StubGenerationContext { @@ -847,10 +952,20 @@ class StubGenerator( } } + fun addManifestProperties(properties: Properties) { + properties["exportForwardDeclarations"] = nativeIndex.structs + .filter { it.def == null } + .joinToString(" ") { + "$cnamesStructsPackageName.${it.kotlinName}" + } + + // TODO: consider exporting Objective-C class and protocol forward refs. + } + val simpleBridgeGenerator: SimpleBridgeGenerator = - SimpleBridgeGeneratorImpl(platform, pkgName, jvmFileClassName, libraryForCStubs) + SimpleBridgeGeneratorImpl(platform, pkgName, jvmFileClassName, libraryForCStubs, kotlinFile) val mappingBridgeGenerator: MappingBridgeGenerator = - MappingBridgeGeneratorImpl(declarationMapper, simpleBridgeGenerator) + MappingBridgeGeneratorImpl(declarationMapper, simpleBridgeGenerator, kotlinFile) } diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt index c2d046d687e..e82c6e7092e 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt @@ -16,6 +16,8 @@ package org.jetbrains.kotlin.native.interop.gen.jvm +import org.jetbrains.kotlin.native.interop.gen.HeadersInclusionPolicyImpl +import org.jetbrains.kotlin.native.interop.gen.ImportsImpl import org.jetbrains.kotlin.native.interop.indexer.* import java.io.File import java.io.StringReader @@ -134,10 +136,6 @@ private fun Collection.atMostOne(): T? { } } -private fun String.matchesToGlob(glob: String): Boolean = - java.nio.file.FileSystems.getDefault() - .getPathMatcher("glob:$glob").matches(java.nio.file.Paths.get(this)) - private fun Properties.getHostSpecific( name: String) = getProperty("$name.$host") @@ -360,6 +358,15 @@ private fun argsToCompiler(staticLibraries: List, libraryPaths: List listOf("-includeBinary", it) } .flatten() } +private fun parseImports(args: Map>): ImportsImpl { + val headerIdToPackage = (args["-import"] ?: emptyList()).map { arg -> + val (pkg, joinedIds) = arg.split(':') + val ids = joinedIds.split(',') + ids.map { HeaderId(it) to pkg } + }.reversed().flatten().toMap() + + return ImportsImpl(headerIdToPackage) +} private fun processLib(konanHome: String, substitutions: Map, @@ -444,14 +451,8 @@ private fun processLib(konanHome: String, val libName = args["-cstubsname"]?.atMostOne() ?: fqParts.joinToString("") + "stubs" val headerFilterGlobs = config.getSpaceSeparated("headerFilter") - - val headerFilter = { name: String -> - if (headerFilterGlobs.isEmpty()) { - true - } else { - headerFilterGlobs.any { name.matchesToGlob(it) } - } - } + val imports = parseImports(args) + val headerInclusionPolicy = HeadersInclusionPolicyImpl(headerFilterGlobs, imports) val library = NativeLibrary( includes = headerFiles, @@ -460,7 +461,7 @@ private fun processLib(konanHome: String, language = language, excludeSystemLibs = excludeSystemLibs, excludeDepdendentModules = excludeDependentModules, - headerFilter = headerFilter + headerInclusionPolicy = headerInclusionPolicy ) val configuration = InteropConfiguration( @@ -473,7 +474,7 @@ private fun processLib(konanHome: String, val nativeIndex = buildNativeIndex(library) - val gen = StubGenerator(nativeIndex, configuration, libName, generateShims, verbose, flavor) + val gen = StubGenerator(nativeIndex, configuration, libName, generateShims, verbose, flavor, imports) outKtFile.parentFile.mkdirs() @@ -486,6 +487,12 @@ private fun processLib(konanHome: String, } } + // TODO: if a library has partially included headers, then it shouldn't be used as a dependency. + manifestAddendProperties["includedHeaders"] = nativeIndex.includedHeaders.joinToString(" ") { it.value } + manifestAddendProperties["pkg"] = outKtPkg + + gen.addManifestProperties(manifestAddendProperties) + manifestAddend?.parentFile?.mkdirs() manifestAddend?.let { manifestAddendProperties.storeProperties(it) } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt index eeb76d87b83..0359d70a222 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt @@ -16,7 +16,12 @@ package org.jetbrains.kotlin.backend.konan +import org.jetbrains.kotlin.backend.konan.descriptors.ClassifierAliasingPackageFragmentDescriptor +import org.jetbrains.kotlin.backend.konan.descriptors.ExportedForwardDeclarationsPackageFragmentDescriptor +import org.jetbrains.kotlin.backend.konan.library.KonanLibraryReader import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.name.FqName @@ -25,6 +30,23 @@ import org.jetbrains.kotlin.resolve.scopes.MemberScope import org.jetbrains.kotlin.types.TypeUtils import org.jetbrains.kotlin.util.OperatorNameConventions +interface InteropLibrary { + fun createSyntheticPackages( + module: ModuleDescriptor, + kotlinPackageFragments: List + ): List +} + +fun createInteropLibrary(reader: KonanLibraryReader): InteropLibrary? { + val pkg = reader.manifestProperties.getProperty("pkg") ?: return null + val exportForwardDeclarations = reader.manifestProperties + .getProperty("exportForwardDeclarations").split(' ') + .map { it.trim() }.filter { it.isNotEmpty() } + .map { FqName(it) } + + return InteropLibraryImpl(FqName(pkg), exportForwardDeclarations) +} + private val cPointerName = "CPointer" private val nativePointedName = "NativePointed" @@ -35,6 +57,13 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns) { val cPointer = packageName.child(Name.identifier(cPointerName)).toUnsafe() val nativePointed = packageName.child(Name.identifier(nativePointedName)).toUnsafe() + + val cNames = FqName("cnames") + val cNamesStructs = cNames.child(Name.identifier("structs")) + + val objCNames = FqName("objcnames") + val objCNamesClasses = objCNames.child(Name.identifier("classes")) + val objCNamesProtocols = objCNames.child(Name.identifier("protocols")) } private val packageScope = builtIns.builtInsModule.getPackage(FqNames.packageName).memberScope @@ -174,4 +203,32 @@ private fun MemberScope.getContributedClass(name: String): ClassDescriptor = this.getContributedClassifier(Name.identifier(name), NoLookupLocation.FROM_BUILTINS) as ClassDescriptor private fun MemberScope.getContributedFunctions(name: String) = - this.getContributedFunctions(Name.identifier(name), NoLookupLocation.FROM_BUILTINS) \ No newline at end of file + this.getContributedFunctions(Name.identifier(name), NoLookupLocation.FROM_BUILTINS) + +private class InteropLibraryImpl( + private val packageFqName: FqName, + private val exportForwardDeclarations: List +) : InteropLibrary { + override fun createSyntheticPackages( + module: ModuleDescriptor, + kotlinPackageFragments: List + ): List { + val interopPackageFragments = kotlinPackageFragments.filter { it.fqName == packageFqName } + + val fqNames = InteropBuiltIns.FqNames + + val result = mutableListOf() + + // Allow references to forwarding declarations to be resolved into classifiers declared in this library: + listOf(fqNames.cNamesStructs, fqNames.objCNamesClasses, fqNames.objCNamesProtocols).mapTo(result) { fqName -> + ClassifierAliasingPackageFragmentDescriptor(interopPackageFragments, module, fqName) + } + // TODO: use separate namespaces for structs, enums, Objective-C protocols etc. + + result.add(ExportedForwardDeclarationsPackageFragmentDescriptor( + module, packageFqName, exportForwardDeclarations + )) + + return result + } +} diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt index eb34fc317d0..944e6975683 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt @@ -17,12 +17,14 @@ package org.jetbrains.kotlin.backend.konan import com.intellij.openapi.project.Project +import org.jetbrains.kotlin.backend.konan.descriptors.createForwardDeclarationsModule import org.jetbrains.kotlin.backend.konan.library.KonanLibraryReader import org.jetbrains.kotlin.backend.konan.library.KonanLibrarySearchPathResolver import org.jetbrains.kotlin.backend.konan.library.impl.LibraryReaderImpl import org.jetbrains.kotlin.backend.konan.util.profile import org.jetbrains.kotlin.backend.konan.util.removeSuffixIfPresent import org.jetbrains.kotlin.backend.konan.util.suffixIfNot +import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.* @@ -34,6 +36,8 @@ import org.jetbrains.kotlin.konan.target.* import org.jetbrains.kotlin.konan.target.TargetManager.* import org.jetbrains.kotlin.konan.target.CompilerOutputKind.* import org.jetbrains.kotlin.konan.util.DependencyProcessor +import org.jetbrains.kotlin.storage.LockBasedStorageManager +import org.jetbrains.kotlin.storage.StorageManager class KonanConfig(val project: Project, val configuration: CompilerConfiguration) { @@ -116,10 +120,25 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration return allMetadata } + private var forwardDeclarationsModule: ModuleDescriptorImpl? = null + + internal fun getOrCreateForwardDeclarationsModule( + builtIns: KotlinBuiltIns, storageManager: StorageManager? = null + ): ModuleDescriptorImpl { + forwardDeclarationsModule?.let { return it } + val result = createForwardDeclarationsModule( + builtIns, + storageManager ?: LockBasedStorageManager() + ) + + forwardDeclarationsModule = result + return result + } + internal val moduleDescriptors: List by lazy { for (module in loadedDescriptors) { // Yes, just to all of them. - module.setDependencies(loadedDescriptors) + module.setDependencies(loadedDescriptors + getOrCreateForwardDeclarationsModule(module.builtIns)) } loadedDescriptors diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/TopDownAnalyzerFacadeForKonan.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/TopDownAnalyzerFacadeForKonan.kt index e6df592926e..662f6634452 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/TopDownAnalyzerFacadeForKonan.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/TopDownAnalyzerFacadeForKonan.kt @@ -39,7 +39,8 @@ object TopDownAnalyzerFacadeForKonan { builtIns.builtInsModule = module if (!module.isStdlib()) { - context.setDependencies(listOf(module) + config.moduleDescriptors) + context.setDependencies(listOf(module) + config.moduleDescriptors + + config.getOrCreateForwardDeclarationsModule(builtIns, projectContext.storageManager)) } else { assert (config.moduleDescriptors.isEmpty()) context.setDependencies(module) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/InteropDescriptors.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/InteropDescriptors.kt new file mode 100644 index 00000000000..62c6bda344d --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/InteropDescriptors.kt @@ -0,0 +1,173 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.backend.konan.descriptors + +import org.jetbrains.kotlin.backend.konan.InteropBuiltIns +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.descriptors.impl.ClassDescriptorImpl +import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl +import org.jetbrains.kotlin.descriptors.impl.PackageFragmentDescriptorImpl +import org.jetbrains.kotlin.incremental.components.LookupLocation +import org.jetbrains.kotlin.incremental.components.NoLookupLocation +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns +import org.jetbrains.kotlin.resolve.scopes.MemberScope +import org.jetbrains.kotlin.resolve.scopes.MemberScopeImpl +import org.jetbrains.kotlin.storage.StorageManager +import org.jetbrains.kotlin.storage.getValue +import org.jetbrains.kotlin.utils.Printer +import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult + +/** + * The package fragment to export forward declarations from interop package namespace, i.e. + * redirect "$pkg.$name" to e.g. "cnames.structs.$name". + */ +class ExportedForwardDeclarationsPackageFragmentDescriptor( + module: ModuleDescriptor, fqName: FqName, declarations: List +) : PackageFragmentDescriptorImpl(module, fqName) { + + private val memberScope = object : MemberScopeImpl() { + + private val nameToFqName = declarations.map { it.shortName() to it }.toMap() + + override fun getContributedClassifier(name: Name, location: LookupLocation): ClassifierDescriptor? { + val declFqName = nameToFqName[name] ?: return null + + val packageView = module.getPackage(declFqName.parent()) + return packageView.memberScope.getContributedClassifier(name, location) + } + + override fun printScopeStructure(p: Printer) { + p.println(this::class.java.simpleName, " {") + p.pushIndent() + + // TODO + + p.popIndent() + p.println("}") + } + + } + + override fun getMemberScope() = memberScope +} + +/** + * The package fragment that redirects all requests for classifier lookup to its targets. + */ +class ClassifierAliasingPackageFragmentDescriptor( + targets: List, module: ModuleDescriptor, fqName: FqName +) : PackageFragmentDescriptorImpl(module, fqName) { + + private val memberScope = object : MemberScopeImpl() { + + override fun getContributedClassifier(name: Name, location: LookupLocation) = + targets.firstNotNullResult { + it.getMemberScope().getContributedClassifier(name, location) + } + + override fun printScopeStructure(p: Printer) { + p.println(this::class.java.simpleName, " {") + p.pushIndent() + + p.println("targets = " + targets) + + p.popIndent() + p.println("}") + } + } + + override fun getMemberScope(): MemberScope = memberScope +} + +/** + * Package fragment which creates descriptors for forward declarations on demand. + */ +private class ForwardDeclarationsPackageFragmentDescriptor( + storageManager: StorageManager, + module: ModuleDescriptor, fqName: FqName, supertypeName: Name, classKind: ClassKind +) : PackageFragmentDescriptorImpl(module, fqName) { + + private val memberScope = object : MemberScopeImpl() { + + private val declarations = storageManager.createMemoizedFunction(this::createDeclaration) + + private val supertype by storageManager.createLazyValue { + val descriptor = builtIns.builtInsModule.getPackage(InteropBuiltIns.FqNames.packageName) + .memberScope + .getContributedClassifier(supertypeName, NoLookupLocation.FROM_BACKEND) as ClassDescriptor + + descriptor.defaultType + } + + private fun createDeclaration(name: Name): ClassDescriptor { + return ClassDescriptorImpl( + this@ForwardDeclarationsPackageFragmentDescriptor, + name, Modality.FINAL, classKind, + listOf(supertype), SourceElement.NO_SOURCE, false + ).apply { + this.initialize(MemberScope.Empty, emptySet(), null) + } + } + + override fun getContributedClassifier(name: Name, location: LookupLocation) = declarations(name) + + override fun printScopeStructure(p: Printer) { + p.println(this::class.java.simpleName, "{}") + } + } + + override fun getMemberScope(): MemberScope = memberScope +} + +/** + * Creates module which "contains" forward declarations. + * Note: this module should be unique per compilation and should always be the last dependency of any module. + */ +fun createForwardDeclarationsModule(builtIns: KotlinBuiltIns, storageManager: StorageManager): ModuleDescriptorImpl { + val module = ModuleDescriptorImpl( + Name.special(""), + storageManager, + builtIns + ) + + fun createPackage(fqName: FqName, supertypeName: String, classKind: ClassKind = ClassKind.CLASS) = + ForwardDeclarationsPackageFragmentDescriptor( + storageManager, + module, + fqName, + Name.identifier(supertypeName), + classKind + ) + + val fqNames = InteropBuiltIns.FqNames + + val packageFragmentProvider = PackageFragmentProviderImpl( + listOf( + createPackage(fqNames.cNamesStructs, "COpaque"), + createPackage(fqNames.objCNamesClasses, "ObjCObjectBase"), + createPackage(fqNames.objCNamesProtocols, "ObjCObject", ClassKind.INTERFACE) + ) + ) + + module.initialize(packageFragmentProvider) + module.setDependencies(module) + + return module +} \ No newline at end of file diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/library/KonanLibraryReader.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/library/KonanLibraryReader.kt index 5a7ccd6cca4..097c868563b 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/library/KonanLibraryReader.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/library/KonanLibraryReader.kt @@ -18,6 +18,7 @@ package org.jetbrains.kotlin.backend.konan.library import org.jetbrains.kotlin.config.LanguageVersionSettings import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.konan.properties.Properties interface KonanLibraryReader { val libraryName: String @@ -25,6 +26,7 @@ interface KonanLibraryReader { val includedPaths: List val linkerOpts: List val escapeAnalysis: ByteArray? + val manifestProperties: Properties fun moduleDescriptor(specifics: LanguageVersionSettings): ModuleDescriptor } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/library/impl/KonanLibraryReaderImpl.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/library/impl/KonanLibraryReaderImpl.kt index 8775765c6f9..7885660d8c9 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/library/impl/KonanLibraryReaderImpl.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/library/impl/KonanLibraryReaderImpl.kt @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.backend.konan.library.impl import org.jetbrains.kotlin.backend.konan.library.KonanLibraryReader +import org.jetbrains.kotlin.backend.konan.createInteropLibrary import org.jetbrains.kotlin.backend.konan.serialization.deserializeModule import org.jetbrains.kotlin.konan.file.File import org.jetbrains.kotlin.konan.properties.* @@ -34,7 +35,7 @@ class LibraryReaderImpl(var libraryFile: File, val currentAbiVersion: Int, val t private val reader = MetadataReaderImpl(inPlace) - val manifestProperties: Properties by lazy { + override val manifestProperties: Properties by lazy { inPlace.manifestFile.loadProperties() } @@ -69,7 +70,7 @@ class LibraryReaderImpl(var libraryFile: File, val currentAbiVersion: Int, val t reader.loadSerializedPackageFragment(fqName) override fun moduleDescriptor(specifics: LanguageVersionSettings) - = deserializeModule(specifics, {packageMetadata(it)}, moduleHeaderData) + = deserializeModule(specifics, {packageMetadata(it)}, moduleHeaderData, createInteropLibrary(this)) } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt index 29d38dbd1da..af38c6070de 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt @@ -473,7 +473,7 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme if (!useKotlinDispatch) { val arguments = descriptor.valueParameters.map { expression.getValueArgument(it)!! } - assert(expression.extensionReceiver == null) + assert(expression.dispatchReceiver == null || expression.extensionReceiver == null) if (expression.superQualifier?.isObjCMetaClass() == true) { context.reportCompilationError( @@ -493,7 +493,7 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme return builder.genLoweredObjCMethodCall( methodInfo, superQualifier = expression.superQualifierSymbol, - receiver = expression.dispatchReceiver!!, + receiver = expression.dispatchReceiver ?: expression.extensionReceiver!!, arguments = arguments ) } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/serialization/KonanSerializationUtil.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/serialization/KonanSerializationUtil.kt index 676daecdb36..85965cf37f0 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/serialization/KonanSerializationUtil.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/serialization/KonanSerializationUtil.kt @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.backend.konan.serialization import org.jetbrains.kotlin.backend.konan.Context +import org.jetbrains.kotlin.backend.konan.InteropLibrary import org.jetbrains.kotlin.backend.konan.KonanBuiltIns import org.jetbrains.kotlin.backend.konan.KonanConfigKeys import org.jetbrains.kotlin.backend.konan.library.LinkData @@ -75,15 +76,19 @@ object NullFlexibleTypeDeserializer : FlexibleTypeDeserializer { } fun createKonanPackageFragmentProvider( - fragmentNames: List, + fragmentNames: List, packageLoader: (String)->KonanLinkData.PackageFragment, - storageManager: StorageManager, module: ModuleDescriptor, - configuration: DeserializationConfiguration): PackageFragmentProvider { + storageManager: StorageManager, module: ModuleDescriptor, + configuration: DeserializationConfiguration, + interopLibrary: InteropLibrary?): PackageFragmentProvider { val packageFragments = fragmentNames.map{ KonanPackageFragment(it, packageLoader, storageManager, module) } - val provider = PackageFragmentProviderImpl(packageFragments) + val syntheticInteropPackageFragments = + interopLibrary?.createSyntheticPackages(module, packageFragments) ?: emptyList() + + val provider = PackageFragmentProviderImpl(packageFragments + syntheticInteropPackageFragments) val notFoundClasses = NotFoundClasses(storageManager, module) @@ -115,7 +120,8 @@ public fun parseModuleHeader(libraryData: ByteArray): Library = KonanSerializerProtocol.extensionRegistry) internal fun deserializeModule(languageVersionSettings: LanguageVersionSettings, - packageLoader:(String)->ByteArray, library: ByteArray): ModuleDescriptorImpl { + packageLoader:(String)->ByteArray, library: ByteArray, + interopLibrary: InteropLibrary?): ModuleDescriptorImpl { val libraryProto = parseModuleHeader(library) val moduleName = libraryProto.moduleName @@ -131,7 +137,7 @@ internal fun deserializeModule(languageVersionSettings: LanguageVersionSettings, libraryProto.packageFragmentNameList, {it -> parsePackageFragment(packageLoader(it))}, storageManager, - moduleDescriptor, deserializationConfiguration) + moduleDescriptor, deserializationConfiguration, interopLibrary) moduleDescriptor.initialize(provider) diff --git a/backend.native/tests/interop/basics/0.kt b/backend.native/tests/interop/basics/0.kt index 2654255db69..36444d3aacb 100644 --- a/backend.native/tests/interop/basics/0.kt +++ b/backend.native/tests/interop/basics/0.kt @@ -2,7 +2,7 @@ import sysstat.* import kotlinx.cinterop.* fun main(args: Array) { - val statBuf = nativeHeap.alloc() + val statBuf = nativeHeap.alloc() val res = stat("/", statBuf.ptr) println(res) println(statBuf.st_uid) diff --git a/backend.native/tests/interop/objc/smoke.h b/backend.native/tests/interop/objc/smoke.h index c764f5f399d..a0598a6cc38 100644 --- a/backend.native/tests/interop/objc/smoke.h +++ b/backend.native/tests/interop/objc/smoke.h @@ -7,10 +7,13 @@ @interface Foo : NSObject @property NSString* name; --(void)hello; -(void)helloWithPrinter:(id )printer; @end; +@interface Foo (FooExtensions) +-(void)hello; +@end; + @protocol MutablePair @required @property (readonly) int first; diff --git a/backend.native/tests/interop/objc/smoke.m b/backend.native/tests/interop/objc/smoke.m index 07f2b97de42..b603b4b4e9b 100644 --- a/backend.native/tests/interop/objc/smoke.m +++ b/backend.native/tests/interop/objc/smoke.m @@ -24,11 +24,6 @@ return self; } --(void)hello { - CPrinter* printer = [[CPrinter alloc] init]; - [self helloWithPrinter:printer]; -} - -(void)helloWithPrinter:(id )printer { NSString* message = [NSString stringWithFormat:@"Hello, %@!", self.name]; [printer print:message.UTF8String]; @@ -40,6 +35,14 @@ @end; +@implementation Foo (FooExtensions) + +-(void)hello { + CPrinter* printer = [[CPrinter alloc] init]; + [self helloWithPrinter:printer]; +} +@end; + void replacePairElements(id pair, int first, int second) { [pair update:0 add:(first - pair.first)]; [pair update:1 sub:(pair.second - second)]; diff --git a/klib/src/platform/osx/objc.def b/klib/src/platform/osx/objc.def index e0423839c17..9631fe3be31 100644 --- a/klib/src/platform/osx/objc.def +++ b/klib/src/platform/osx/objc.def @@ -1,3 +1,4 @@ +language = Objective-C package = platform.objc headers = objc/List.h objc/NSObjCRuntime.h objc/NSObject.h objc/Object.h objc/Protocol.h \ objc/hashtable.h objc/hashtable2.h objc/message.h objc/objc-api.h \ diff --git a/klib/src/platform/osx/osx.def b/klib/src/platform/osx/osx.def index f912b345dc4..8dfd8116e40 100644 --- a/klib/src/platform/osx/osx.def +++ b/klib/src/platform/osx/osx.def @@ -1,3 +1,4 @@ +language = Objective-C package = platform.osx headers = AppleTextureEncoder.h AssertMacros.h Availability.h AvailabilityInternal.h \ AvailabilityMacros.h Block.h ConditionalMacros.h MacTypes.h NSSystemDirectories.h \ diff --git a/shared/src/main/kotlin/org/jetbrains/kotlin/konan/file/NoJavaUtil.kt b/shared/src/main/kotlin/org/jetbrains/kotlin/konan/file/NoJavaUtil.kt index 84d04ef3944..fac80851489 100644 --- a/shared/src/main/kotlin/org/jetbrains/kotlin/konan/file/NoJavaUtil.kt +++ b/shared/src/main/kotlin/org/jetbrains/kotlin/konan/file/NoJavaUtil.kt @@ -156,7 +156,11 @@ private val File.zipUri: URI fun File.zipFileSystem(mutable: Boolean = false): FileSystem { val zipUri = this.zipUri val attributes = hashMapOf("create" to mutable.toString()) - return FileSystems.newFileSystem(zipUri, attributes, null) + return try { + FileSystems.newFileSystem(zipUri, attributes, null) + } catch (e: FileSystemAlreadyExistsException) { + FileSystems.getFileSystem(zipUri) + } } fun File.mutableZipFileSystem() = this.zipFileSystem(mutable = true) diff --git a/utilities/src/main/kotlin/org/jetbrains/kotlin/cli/utilities/main.kt b/utilities/src/main/kotlin/org/jetbrains/kotlin/cli/utilities/main.kt index fd32ca41d8a..f11f4f35511 100644 --- a/utilities/src/main/kotlin/org/jetbrains/kotlin/cli/utilities/main.kt +++ b/utilities/src/main/kotlin/org/jetbrains/kotlin/cli/utilities/main.kt @@ -1,6 +1,9 @@ package org.jetbrains.kotlin.cli.utilities +import org.jetbrains.kotlin.backend.konan.library.impl.KonanLibrary import org.jetbrains.kotlin.konan.file.File +import org.jetbrains.kotlin.konan.properties.loadProperties +import org.jetbrains.kotlin.utils.addIfNotNull import org.jetbrains.kotlin.cli.bc.main as konancMain import org.jetbrains.kotlin.native.interop.gen.jvm.interop import org.jetbrains.kotlin.cli.klib.main as klibMain @@ -8,11 +11,16 @@ import org.jetbrains.kotlin.cli.klib.main as klibMain fun invokeCinterop(args: Array) { var outputFileName = "nativelib" var target = "host" + val libraries = mutableListOf() for (i in args.indices) { - if (args[i].startsWith("-o")) - outputFileName = args.getOrElse(i+1){outputFileName} - if (args[i] == "-target") - target = args.getOrElse(i+1){target} + val arg = args[i] + val nextArg = args.getOrNull(i + 1) + if (arg.startsWith("-o")) + outputFileName = nextArg ?: outputFileName + if (arg == "-target") + target = nextArg ?: target + if (arg == "-library") + libraries.addIfNotNull(nextArg) } val buildDir = File("$outputFileName-build") @@ -21,12 +29,23 @@ fun invokeCinterop(args: Array) { val cstubsName ="cstubs" val manifest = File(buildDir, "manifest.properties") + val importArgs = libraries.flatMap { + val library = KonanLibrary(File(it)) + val manifestProperties = library.manifestFile.loadProperties() + // TODO: handle missing properties? + val pkg = manifestProperties["pkg"] as String + val headerIds = (manifestProperties["includedHeaders"] as String).split(' ') + val arg = "$pkg:${headerIds.joinToString(",")}" + listOf("-import", arg) + } + val additionalArgs = listOf( "-generated", generatedDir.path, "-natives", nativesDir.path, "-cstubsname", cstubsName, "-manifest", manifest.path, - "-flavor", "native") + "-flavor", "native" + ) + importArgs val cinteropArgs = (additionalArgs + args.toList()).toTypedArray() val cinteropArgsToCompiler = mutableListOf() @@ -39,7 +58,7 @@ fun invokeCinterop(args: Array) { "-o", outputFileName, "-target", target, "-manifest", manifest.path - ) + cinteropArgsToCompiler + ) + cinteropArgsToCompiler + libraries.flatMap { listOf("-library", it) } konancMain(konancArgs) }