Implement Info.plist generation for produced frameworks

This commit is contained in:
Svyatoslav Scherbina
2017-11-29 13:57:53 +03:00
committed by SvyatoslavScherbina
parent 954caf836a
commit 5ade7505b3
5 changed files with 132 additions and 22 deletions
@@ -107,7 +107,7 @@ internal open class MacOSBasedPlatform(distribution: Distribution)
open val osVersionMin by lazy {
listOf(
propertyTargetString("osVersionMinFlagLd"),
propertyTargetString("osVersionMin") + ".0")
properties.osVersionMin!! + ".0")
}
override fun filterStaticLibraries(binaries: List<String>)
@@ -35,10 +35,7 @@ import org.jetbrains.kotlin.ir.descriptors.IrBuiltinOperatorDescriptorBase
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.OverridingUtil
import org.jetbrains.kotlin.resolve.descriptorUtil.classId
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperInterfaces
import org.jetbrains.kotlin.resolve.descriptorUtil.*
import org.jetbrains.kotlin.resolve.scopes.MemberScope
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.isUnit
@@ -322,3 +319,20 @@ fun AnnotationDescriptor.getStringValueOrNull(name: String): String? {
}
fun AnnotationDescriptor.getStringValue(name: String): String = this.getStringValueOrNull(name)!!
private fun getPackagesFqNames(module: ModuleDescriptor): Set<FqName> {
val result = mutableSetOf<FqName>()
fun getSubPackages(fqName: FqName) {
result.add(fqName)
module.getSubPackagesOf(fqName) { true }.forEach { getSubPackages(it) }
}
getSubPackages(FqName.ROOT)
return result
}
fun ModuleDescriptor.getPackageFragments(): List<PackageFragmentDescriptor> =
getPackagesFqNames(this).flatMap {
getPackage(it).fragments.filter { it.module == this }
}
@@ -17,12 +17,21 @@
package org.jetbrains.kotlin.backend.konan.objcexport
import org.jetbrains.kotlin.backend.konan.Context
import org.jetbrains.kotlin.backend.konan.descriptors.getPackageFragments
import org.jetbrains.kotlin.backend.konan.llvm.CodeGenerator
import org.jetbrains.kotlin.backend.konan.llvm.objcexport.ObjCExportCodeGenerator
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.konan.file.File
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.name.isSubpackageOf
internal class ObjCExport(val context: Context) {
private val target get() = context.config.targetManager.target
internal fun produceObjCFramework() {
if (context.config.produce != CompilerOutputKind.FRAMEWORK) return
@@ -55,7 +64,108 @@ internal class ObjCExport(val context: Context) {
modules.child("module.modulemap").writeBytes(moduleMap.toByteArray())
emitInfoPlist(framework, frameworkName)
val objCCodeGenerator = ObjCExportCodeGenerator(CodeGenerator(context), namer, mapper)
objCCodeGenerator.emitRtti(headerGenerator.generatedClasses, headerGenerator.topLevel)
}
private fun emitInfoPlist(framework: File, name: String) {
val directory = when (target) {
KonanTarget.IPHONE,
KonanTarget.IPHONE_SIM -> framework
KonanTarget.MACBOOK -> framework.child("Resources").also { it.mkdirs() }
else -> error(target)
}
val file = directory.child("Info.plist")
val bundleExecutable = name
val pkg = context.moduleDescriptor.guessMainPackage() // TODO: consider showing warning if it is root.
val bundleId = pkg.child(Name.identifier(name)).asString()
val platform = when (target) {
KonanTarget.IPHONE -> "iPhoneOS"
KonanTarget.IPHONE_SIM -> "iPhoneSimulator"
KonanTarget.MACBOOK -> "MacOSX"
else -> error(target)
}
val minimumOsVersion = context.config.distribution.targetProperties.osVersionMin!!
val contents = StringBuilder()
contents.append("""
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key>
<string>$bundleExecutable</string>
<key>CFBundleIdentifier</key>
<string>$bundleId</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$name</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>$platform</string>
</array>
<key>CFBundleVersion</key>
<string>1</string>
""".trimIndent())
contents.append(when (target) {
KonanTarget.IPHONE,
KonanTarget.IPHONE_SIM -> """
| <key>MinimumOSVersion</key>
| <string>$minimumOsVersion</string>
| <key>UIDeviceFamily</key>
| <array>
| <integer>1</integer>
| <integer>2</integer>
| </array>
""".trimMargin()
KonanTarget.MACBOOK -> ""
else -> error(target)
})
if (target == KonanTarget.IPHONE) {
contents.append("""
| <key>UIRequiredDeviceCapabilities</key>
| <array>
| <string>arm64</string>
| </array>
""".trimMargin()
)
}
contents.append("""
</dict>
</plist>
""".trimIndent())
// TODO: Xcode also add some number of DT* keys.
file.writeBytes(contents.toString().toByteArray())
}
}
private fun ModuleDescriptor.guessMainPackage(): FqName {
val allPackages = this.getPackageFragments() // Includes also all parent packages, e.g. the root one.
val nonEmptyPackages = allPackages
.filter { it.getMemberScope().getContributedDescriptors().isNotEmpty() }
.map { it.fqName }.distinct()
return allPackages.map { it.fqName }.distinct()
.filter { candidate -> nonEmptyPackages.all { it.isSubpackageOf(candidate) } }
// Now there are all common ancestors of non-empty packages. Longest of them is the least common accessor:
.maxBy { it.asString().length }!!
}
@@ -17,6 +17,7 @@
package org.jetbrains.kotlin.backend.konan.objcexport
import org.jetbrains.kotlin.backend.konan.Context
import org.jetbrains.kotlin.backend.konan.descriptors.getPackageFragments
import org.jetbrains.kotlin.backend.konan.descriptors.isInterface
import org.jetbrains.kotlin.builtins.getReceiverTypeFromFunctionType
import org.jetbrains.kotlin.builtins.getReturnTypeFromFunctionType
@@ -568,20 +569,3 @@ private inline fun buildStub(block: StubBuilder.() -> Unit) = StubBuilder().let
private inline fun MutableCollection<Stub>.addBuiltBy(block: StubBuilder.() -> Unit) {
this.add(buildStub(block))
}
private fun getPackagesFqNames(module: ModuleDescriptor): Set<FqName> {
val result = mutableSetOf<FqName>()
fun getSubPackages(fqName: FqName) {
result.add(fqName)
module.getSubPackagesOf(fqName) { true }.forEach { getSubPackages(it) }
}
getSubPackages(FqName.ROOT)
return result
}
private fun ModuleDescriptor.getPackageFragments(): List<PackageFragmentDescriptor> =
getPackagesFqNames(this).flatMap {
getPackage(it).fragments.filter { it.module == this }
}
@@ -73,4 +73,6 @@ class KonanProperties(val target: KonanTarget, val properties: Properties, val b
// Use (equal) llvmHome fow now.
return targetString("llvmHome")
}
val osVersionMin: String? get() = targetString("osVersionMin")
}