Implement '-Xexport-library=<path>' compiler argument (#2262)

Produced framework includes the entire API of exported libraries
This commit is contained in:
SvyatoslavScherbina
2018-10-30 11:09:56 +03:00
committed by GitHub
parent 4f0633412d
commit d02a51ba98
9 changed files with 172 additions and 20 deletions
@@ -188,6 +188,8 @@ class K2Native : CLICompiler<K2NativeCompilerArguments>() {
if (arguments.friendModules != null)
put(FRIEND_MODULES, arguments.friendModules!!.split(File.pathSeparator).filterNot(String::isEmpty))
put(EXPORTED_LIBRARIES, selectExportedLibraries(configuration, arguments, outputKind))
put(BITCODE_EMBEDDING_MODE, selectBitcodeEmbeddingMode(this, arguments, outputKind))
put(DEBUG_INFO_VERSION, arguments.debugInfoFormatVersion.toInt())
}
@@ -254,5 +256,22 @@ private fun selectBitcodeEmbeddingMode(
}
}
private fun selectExportedLibraries(
configuration: CompilerConfiguration,
arguments: K2NativeCompilerArguments,
outputKind: CompilerOutputKind
): List<String> {
val exportedLibraries = arguments.exportedLibraries?.toList().orEmpty()
return if (exportedLibraries.isNotEmpty() && outputKind != CompilerOutputKind.FRAMEWORK) {
configuration.report(STRONG_WARNING, "-Xexport-library is only supported when producing frameworks, " +
"but the compiler is producing ${outputKind.name.toLowerCase()}")
emptyList()
} else {
exportedLibraries
}
}
fun main(args: Array<String>) = K2Native.main(args)
@@ -103,6 +103,14 @@ class K2NativeCompilerArguments : CommonCompilerArguments() {
@Argument(value = "-Xenable", deprecatedName = "--enable", valueDescription = "<Phase>", description = "Enable backend phase")
var enablePhases: Array<String>? = null
@Argument(
value = "-Xexport-library",
valueDescription = "<path>",
description = "Path to the library to be included into produced framework API\n" +
"Must be the path of a library passed with '-library'"
)
var exportedLibraries: Array<String>? = null
@Argument(value= "-Xlist-phases", deprecatedName = "--list_phases", description = "List all backend phases")
var listPhases: Boolean = false
@@ -0,0 +1,84 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package org.jetbrains.kotlin.backend.konan
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.konan.CurrentKonanModuleOrigin
import org.jetbrains.kotlin.descriptors.konan.DeserializedKonanModuleOrigin
import org.jetbrains.kotlin.descriptors.konan.SyntheticModulesOrigin
import org.jetbrains.kotlin.descriptors.konan.konanModuleOrigin
import org.jetbrains.kotlin.konan.file.File
import org.jetbrains.kotlin.konan.library.KonanLibrary
import org.jetbrains.kotlin.konan.library.isInterop
import org.jetbrains.kotlin.konan.library.resolver.KonanLibraryResolveResult
internal fun validateExportedLibraries(configuration: CompilerConfiguration, resolvedLibraries: KonanLibraryResolveResult) {
getExportedLibraries(configuration, resolvedLibraries, report = true)
}
internal fun Context.getExportedDependencies(): List<ModuleDescriptor> {
val exportedLibraries = getExportedLibraries(this.config.configuration, this.config.resolvedLibraries, report = false)
.toSet()
val result = this.moduleDescriptor.allDependencyModules.filter {
val origin = it.konanModuleOrigin
when (origin) {
CurrentKonanModuleOrigin, SyntheticModulesOrigin -> false
is DeserializedKonanModuleOrigin -> {
origin.library in exportedLibraries
}
}
}
return result
}
private fun getExportedLibraries(
configuration: CompilerConfiguration,
resolvedLibraries: KonanLibraryResolveResult,
report: Boolean
): List<KonanLibrary> {
val exportedLibraries = configuration.getList(KonanConfigKeys.EXPORTED_LIBRARIES).map { File(it) }.toSet()
val remainingExportedLibraries = exportedLibraries.toMutableSet()
val result = mutableListOf<KonanLibrary>()
val libraries = resolvedLibraries.getFullList(null)
for (library in libraries) {
val libraryFile = library.libraryFile
if (libraryFile in exportedLibraries) {
remainingExportedLibraries -= libraryFile
if (library.isInterop || library.isDefault) {
if (report) {
val kind = if (library.isInterop) "Interop" else "Default"
configuration.report(
CompilerMessageSeverity.STRONG_WARNING,
"$kind library ${library.libraryName} can't be exported with -Xexport-library"
)
}
} else {
result += library
}
}
}
if (report && remainingExportedLibraries.isNotEmpty()) {
val message = buildString {
appendln("Following libraries are specified to be exported with -Xexport-library, but not included to the build:")
remainingExportedLibraries.forEach { appendln(it) }
appendln()
appendln("Included libraries:")
libraries.forEach { appendln(it.libraryFile) }
}
configuration.report(CompilerMessageSeverity.STRONG_WARNING, message)
}
return result
}
@@ -94,7 +94,10 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration
resolver.resolveWithDependencies(
unresolvedLibraries,
noStdLib = configuration.getBoolean(KonanConfigKeys.NOSTDLIB),
noDefaultLibs = configuration.getBoolean(KonanConfigKeys.NODEFAULTLIBS) )
noDefaultLibs = configuration.getBoolean(KonanConfigKeys.NODEFAULTLIBS)).also {
validateExportedLibraries(configuration, it)
}
}
fun librariesWithDependencies(moduleDescriptor: ModuleDescriptor?): List<KonanLibrary> {
@@ -28,6 +28,8 @@ class KonanConfigKeys {
= CompilerConfigurationKey.create("enable backend phases")
val ENTRY: CompilerConfigurationKey<String?>
= CompilerConfigurationKey.create("fully qualified main() name")
val EXPORTED_LIBRARIES: CompilerConfigurationKey<List<String>>
= CompilerConfigurationKey.create<List<String>>("libraries included into produced framework API")
val FRIEND_MODULES: CompilerConfigurationKey<List<String>>
= CompilerConfigurationKey.create<List<String>>("friend module paths")
val GENERATE_TEST_RUNNER: CompilerConfigurationKey<Boolean>
@@ -54,7 +54,7 @@ internal class ObjCExport(val codegen: CodeGenerator) {
}
val namer = ObjCExportNamerImpl(context.moduleDescriptor, context.builtIns, mapper)
val namer = ObjCExportNamerImpl(emptySet(), context.builtIns, mapper, context.moduleDescriptor.namePrefix)
objCCodeGenerator = ObjCExportCodeGenerator(codegen, namer, mapper)
generatedClasses = emptySet()
@@ -10,6 +10,7 @@ import org.jetbrains.kotlin.backend.konan.descriptors.*
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.builtins.UnsignedType
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.konan.isKonanStdlib
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.Name
@@ -24,10 +25,24 @@ import org.jetbrains.kotlin.types.typeUtil.supertypes
import org.jetbrains.kotlin.utils.addIfNotNull
abstract class ObjCExportHeaderGenerator(
val moduleDescriptor: ModuleDescriptor,
val moduleDescriptors: List<ModuleDescriptor>,
val builtIns: KotlinBuiltIns,
topLevelNamePrefix: String = moduleDescriptor.namePrefix
topLevelNamePrefix: String
) {
constructor(
moduleDescriptor: ModuleDescriptor,
builtIns: KotlinBuiltIns,
topLevelNamePrefix: String = moduleDescriptor.namePrefix
) : this(moduleDescriptor, emptyList(), builtIns, topLevelNamePrefix)
constructor(
moduleDescriptor: ModuleDescriptor,
exportedDependencies: List<ModuleDescriptor>,
builtIns: KotlinBuiltIns,
topLevelNamePrefix: String = moduleDescriptor.namePrefix
) : this(listOf(moduleDescriptor) + exportedDependencies, builtIns, topLevelNamePrefix)
internal val mapper: ObjCExportMapper = object : ObjCExportMapper() {
override fun getCategoryMembersFor(descriptor: ClassDescriptor) =
extensions[descriptor].orEmpty()
@@ -39,16 +54,18 @@ abstract class ObjCExportHeaderGenerator(
}
}
internal val namer = ObjCExportNamerImpl(moduleDescriptor, builtIns, mapper, topLevelNamePrefix)
internal val namer = ObjCExportNamerImpl(moduleDescriptors.toSet(), builtIns, mapper, topLevelNamePrefix)
internal val generatedClasses = mutableSetOf<ClassDescriptor>()
internal val topLevel = mutableMapOf<SourceFile, MutableList<CallableMemberDescriptor>>()
private val stdlibModule = moduleDescriptors.first().allDependencyModules.single { it.isKonanStdlib() }
private val mappedToNSNumber: List<ClassDescriptor> = with(builtIns) {
val result = mutableListOf(boolean, byte, short, int, long, float, double)
UnsignedType.values().mapTo(result) { unsignedType ->
moduleDescriptor.findClassAcrossModuleDependencies(unsignedType.classId)!!
stdlibModule.findClassAcrossModuleDependencies(unsignedType.classId)!!
}
result
@@ -69,7 +86,7 @@ abstract class ObjCExportHeaderGenerator(
NSNumberKind.values().forEach {
// TODO: NSNumber seem to have different equality semantics.
if (it.mappedKotlinClassId != null) {
val descriptor = moduleDescriptor.findClassAcrossModuleDependencies(it.mappedKotlinClassId)!!
val descriptor = stdlibModule.findClassAcrossModuleDependencies(it.mappedKotlinClassId)!!
result += CustomTypeMapper.Simple(descriptor, namer.numberBoxName(descriptor).objCName)
}
@@ -181,7 +198,7 @@ abstract class ObjCExportHeaderGenerator(
genKotlinNumbers()
val packageFragments = moduleDescriptor.getPackageFragments()
val packageFragments = moduleDescriptors.flatMap { it.getPackageFragments() }
packageFragments.forEach { packageFragment ->
packageFragment.getMemberScope().getContributedDescriptors()
@@ -263,7 +280,7 @@ abstract class ObjCExportHeaderGenerator(
}
private fun genKotlinNumber(kotlinClassId: ClassId, kind: NSNumberKind): ObjCInterface {
val descriptor = moduleDescriptor.findClassAcrossModuleDependencies(kotlinClassId)!!
val descriptor = stdlibModule.findClassAcrossModuleDependencies(kotlinClassId)!!
val name = namer.numberBoxName(descriptor)
val members = buildMembers {
@@ -6,12 +6,16 @@
package org.jetbrains.kotlin.backend.konan.objcexport
import org.jetbrains.kotlin.backend.konan.Context
import org.jetbrains.kotlin.backend.konan.getExportedDependencies
import org.jetbrains.kotlin.backend.konan.reportCompilationWarning
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.ir.util.report
internal class ObjCExportHeaderGeneratorImpl(val context: Context)
: ObjCExportHeaderGenerator(context.moduleDescriptor, context.builtIns) {
internal class ObjCExportHeaderGeneratorImpl(val context: Context) : ObjCExportHeaderGenerator(
context.moduleDescriptor,
context.getExportedDependencies(),
context.builtIns
) {
override fun reportWarning(text: String) {
context.reportCompilationWarning(text)
@@ -24,4 +28,4 @@ internal class ObjCExportHeaderGeneratorImpl(val context: Context)
isError = false
)
}
}
}
@@ -31,8 +31,18 @@ interface ObjCExportNamer {
}
fun createNamer(moduleDescriptor: ModuleDescriptor,
topLevelNamePrefix: String = moduleDescriptor.namePrefix): ObjCExportNamer =
createNamer(moduleDescriptor, emptyList(), topLevelNamePrefix)
fun createNamer(moduleDescriptor: ModuleDescriptor,
exportedDependencies: List<ModuleDescriptor>,
topLevelNamePrefix: String = moduleDescriptor.namePrefix): ObjCExportNamer {
val generator = object : ObjCExportHeaderGenerator(moduleDescriptor, moduleDescriptor.builtIns, topLevelNamePrefix) {
val generator = object : ObjCExportHeaderGenerator(
moduleDescriptor,
exportedDependencies,
moduleDescriptor.builtIns,
topLevelNamePrefix
) {
override fun reportWarning(text: String) {}
override fun reportWarning(method: FunctionDescriptor, text: String) {}
}
@@ -40,10 +50,10 @@ fun createNamer(moduleDescriptor: ModuleDescriptor,
}
internal class ObjCExportNamerImpl(
val moduleDescriptor: ModuleDescriptor,
val moduleDescriptors: Set<ModuleDescriptor>,
builtIns: KotlinBuiltIns,
val mapper: ObjCExportMapper,
private val topLevelNamePrefix: String = moduleDescriptor.namePrefix
private val topLevelNamePrefix: String
) : ObjCExportNamer {
private fun String.toUnmangledClassOrProtocolName(): ObjCExportNamer.ClassOrProtocolName =
@@ -128,10 +138,15 @@ internal class ObjCExportNamerImpl(
override fun getFileClassName(file: SourceFile): ObjCExportNamer.ClassOrProtocolName {
val baseName by lazy {
val psiSourceFile = file as? PsiSourceFile ?: error("SourceFile '$file' is not PsiSourceFile")
val psiFile = psiSourceFile.psiFile
val ktFile = psiFile as? KtFile ?: error("PsiFile '$psiFile' is not KtFile")
PackagePartClassUtils.getFilePartShortName(ktFile.name)
val fileName = when (file) {
is PsiSourceFile -> {
val psiFile = file.psiFile
val ktFile = psiFile as? KtFile ?: error("PsiFile '$psiFile' is not KtFile")
ktFile.name
}
else -> file.name ?: error("$file has no name")
}
PackagePartClassUtils.getFilePartShortName(fileName)
}
val objCName = objCClassNames.getOrPut(file) {
@@ -196,7 +211,7 @@ internal class ObjCExportNamerImpl(
}
private fun StringBuilder.appendTopLevelClassBaseName(descriptor: ClassDescriptor) = apply {
if (descriptor.module != moduleDescriptor) {
if (descriptor.module !in moduleDescriptors) {
append(descriptor.module.namePrefix)
}
append(descriptor.name.asString())