[K/N] XCTest support: Make compiler produce bundles
XCTest test binary is a bundle plug-in that is similar to a framework. This is a part of ^KT-58928 Merge-request: KT-MR-10662 Merged-by: Pavel Punegov <Pavel.Punegov@jetbrains.com>
This commit is contained in:
committed by
Space Team
parent
92bcf3b2d5
commit
1ad0a662fd
+1
@@ -25,6 +25,7 @@ val KonanConfig.isFinalBinary: Boolean get() = when (this.produce) {
|
||||
CompilerOutputKind.DYNAMIC_CACHE, CompilerOutputKind.STATIC_CACHE,
|
||||
CompilerOutputKind.LIBRARY, CompilerOutputKind.BITCODE -> false
|
||||
CompilerOutputKind.FRAMEWORK -> !omitFrameworkBinary
|
||||
CompilerOutputKind.TEST_BUNDLE -> true
|
||||
else -> error("not supported: ${this.produce}")
|
||||
}
|
||||
|
||||
|
||||
+36
-22
@@ -18,6 +18,7 @@ internal fun determineLinkerOutput(context: PhaseContext): LinkerOutputKind =
|
||||
val staticFramework = context.config.produceStaticFramework
|
||||
if (staticFramework) LinkerOutputKind.STATIC_LIBRARY else LinkerOutputKind.DYNAMIC_LIBRARY
|
||||
}
|
||||
CompilerOutputKind.TEST_BUNDLE,
|
||||
CompilerOutputKind.DYNAMIC_CACHE,
|
||||
CompilerOutputKind.DYNAMIC -> LinkerOutputKind.DYNAMIC_LIBRARY
|
||||
CompilerOutputKind.STATIC_CACHE,
|
||||
@@ -91,31 +92,44 @@ internal class Linker(
|
||||
val additionalLinkerArgs: List<String>
|
||||
val executable: String
|
||||
|
||||
if (config.produce != CompilerOutputKind.FRAMEWORK) {
|
||||
additionalLinkerArgs = if (target.family.isAppleFamily) {
|
||||
when (config.produce) {
|
||||
CompilerOutputKind.DYNAMIC_CACHE ->
|
||||
listOf("-install_name", outputFiles.dynamicCacheInstallName)
|
||||
else -> listOf("-dead_strip")
|
||||
when (config.produce) {
|
||||
CompilerOutputKind.TEST_BUNDLE -> {
|
||||
val bundleDir = File(outputFile)
|
||||
val name = bundleDir.name.removeSuffix(config.produce.suffix())
|
||||
require(target.family.isAppleFamily)
|
||||
val bundleRelativePath = if (target.family == Family.OSX) "Contents/MacOS/$name" else name
|
||||
additionalLinkerArgs = listOf("-bundle")
|
||||
val bundlePath = bundleDir.child(bundleRelativePath)
|
||||
bundlePath.parentFile.mkdirs()
|
||||
executable = bundlePath.absolutePath
|
||||
}
|
||||
CompilerOutputKind.FRAMEWORK -> {
|
||||
val framework = File(outputFile)
|
||||
val dylibName = framework.name.removeSuffix(".framework")
|
||||
val dylibRelativePath = when (target.family) {
|
||||
Family.IOS,
|
||||
Family.TVOS,
|
||||
Family.WATCHOS -> dylibName
|
||||
Family.OSX -> "Versions/A/$dylibName"
|
||||
else -> error(target)
|
||||
}
|
||||
} else {
|
||||
emptyList()
|
||||
additionalLinkerArgs = listOf("-dead_strip", "-install_name", "@rpath/${framework.name}/$dylibRelativePath")
|
||||
val dylibPath = framework.child(dylibRelativePath)
|
||||
dylibPath.parentFile.mkdirs()
|
||||
executable = dylibPath.absolutePath
|
||||
}
|
||||
executable = outputFiles.nativeBinaryFile
|
||||
} else {
|
||||
val framework = File(outputFile)
|
||||
val dylibName = framework.name.removeSuffix(".framework")
|
||||
val dylibRelativePath = when (target.family) {
|
||||
Family.IOS,
|
||||
Family.TVOS,
|
||||
Family.WATCHOS -> dylibName
|
||||
Family.OSX -> "Versions/A/$dylibName"
|
||||
else -> error(target)
|
||||
else -> {
|
||||
additionalLinkerArgs = if (target.family.isAppleFamily) {
|
||||
when (config.produce) {
|
||||
CompilerOutputKind.DYNAMIC_CACHE ->
|
||||
listOf("-install_name", outputFiles.dynamicCacheInstallName)
|
||||
else -> listOf("-dead_strip")
|
||||
}
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
executable = outputFiles.nativeBinaryFile
|
||||
}
|
||||
additionalLinkerArgs = listOf("-dead_strip", "-install_name", "@rpath/${framework.name}/$dylibRelativePath")
|
||||
val dylibPath = framework.child(dylibRelativePath)
|
||||
dylibPath.parentFile.mkdirs()
|
||||
executable = dylibPath.absolutePath
|
||||
}
|
||||
File(executable).delete()
|
||||
|
||||
|
||||
+21
@@ -45,6 +45,7 @@ internal class DynamicCompilerDriver : CompilerDriver() {
|
||||
CompilerOutputKind.DYNAMIC_CACHE -> produceBinary(engine, config, environment)
|
||||
CompilerOutputKind.STATIC_CACHE -> produceBinary(engine, config, environment)
|
||||
CompilerOutputKind.PRELIMINARY_CACHE -> TODO()
|
||||
CompilerOutputKind.TEST_BUNDLE -> produceBundle(engine, config, environment)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,6 +155,26 @@ internal class DynamicCompilerDriver : CompilerDriver() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce a bundle that is a directory with code and resources.
|
||||
* It consists of
|
||||
* - Info.plist
|
||||
* - Binary without an entry point.
|
||||
*
|
||||
* See https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFBundles/AboutBundles/AboutBundles.html
|
||||
*/
|
||||
private fun produceBundle(engine: PhaseEngine<PhaseContext>, config: KonanConfig, environment: KotlinCoreEnvironment) {
|
||||
require(config.target.family.isAppleFamily)
|
||||
require(config.produce == CompilerOutputKind.TEST_BUNDLE)
|
||||
|
||||
val frontendOutput = engine.runFrontend(config, environment) ?: return
|
||||
engine.runPhase(CreateTestBundlePhase, frontendOutput)
|
||||
val psiToIrOutput = engine.runPsiToIr(frontendOutput, isProducingLibrary = false)
|
||||
require(psiToIrOutput is PsiToIrOutput.ForBackend)
|
||||
val backendContext = createBackendContext(config, frontendOutput, psiToIrOutput)
|
||||
engine.runBackend(backendContext, psiToIrOutput.irModule)
|
||||
}
|
||||
|
||||
private fun createBackendContext(
|
||||
config: KonanConfig,
|
||||
frontendOutput: FrontendPhaseOutput.Full,
|
||||
|
||||
+11
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.backend.konan.driver.phases
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.lower
|
||||
import org.jetbrains.kotlin.backend.konan.NativeGenerationState
|
||||
import org.jetbrains.kotlin.backend.konan.OutputFiles
|
||||
import org.jetbrains.kotlin.backend.konan.driver.PhaseContext
|
||||
import org.jetbrains.kotlin.backend.konan.driver.PhaseEngine
|
||||
import org.jetbrains.kotlin.backend.konan.driver.utilities.KotlinBackendIrHolder
|
||||
@@ -17,6 +18,7 @@ import org.jetbrains.kotlin.backend.konan.lower.SpecialBackendChecksTraversal
|
||||
import org.jetbrains.kotlin.backend.konan.makeEntryPoint
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||
import org.jetbrains.kotlin.backend.konan.objcexport.createTestBundle
|
||||
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
|
||||
import org.jetbrains.kotlin.ir.util.NaiveSourceBasedFileEntryImpl
|
||||
import org.jetbrains.kotlin.ir.util.addChild
|
||||
@@ -87,4 +89,13 @@ internal val EntryPointPhase = createSimpleNamedCompilerPhase<NativeGenerationSt
|
||||
}
|
||||
|
||||
file.addChild(makeEntryPoint(context))
|
||||
}
|
||||
|
||||
internal val CreateTestBundlePhase = createSimpleNamedCompilerPhase<PhaseContext, FrontendPhaseOutput.Full>(
|
||||
"CreateTestBundlePhase",
|
||||
"Create XCTest bundle"
|
||||
) { context, input ->
|
||||
val config = context.config
|
||||
val output = OutputFiles(config.outputPath, config.target, config.produce).mainFile
|
||||
createTestBundle(config, input.moduleDescriptor, output)
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.backend.konan.objcexport
|
||||
|
||||
import org.jetbrains.kotlin.backend.konan.KonanConfig
|
||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||
import org.jetbrains.kotlin.konan.file.File
|
||||
import org.jetbrains.kotlin.konan.target.Family
|
||||
|
||||
/**
|
||||
* Builds Apple bundle directory.
|
||||
*/
|
||||
internal class BundleBuilder(
|
||||
private val config: KonanConfig,
|
||||
private val infoPListBuilder: InfoPListBuilder,
|
||||
private val mainPackageGuesser: MainPackageGuesser,
|
||||
) {
|
||||
fun build(
|
||||
moduleDescriptor: ModuleDescriptor,
|
||||
bundleDirectory: File,
|
||||
name: String,
|
||||
) {
|
||||
val target = config.target
|
||||
val bundleContents = when (target.family) {
|
||||
Family.IOS,
|
||||
Family.WATCHOS,
|
||||
Family.TVOS -> bundleDirectory
|
||||
Family.OSX -> bundleDirectory.child("Contents")
|
||||
else -> error(target)
|
||||
}.apply { mkdirs() }
|
||||
|
||||
bundleContents.child("Info.plist").run {
|
||||
val infoPlistContents = infoPListBuilder.build(name, mainPackageGuesser, moduleDescriptor)
|
||||
writeBytes(infoPlistContents.toByteArray())
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
-1
@@ -74,7 +74,7 @@ internal fun createObjCFramework(
|
||||
exportedInterface: ObjCExportedInterface,
|
||||
frameworkDirectory: File
|
||||
) {
|
||||
val frameworkName = frameworkDirectory.name.removeSuffix(".framework")
|
||||
val frameworkName = frameworkDirectory.name.removeSuffix(CompilerOutputKind.FRAMEWORK.suffix())
|
||||
val frameworkBuilder = FrameworkBuilder(
|
||||
config,
|
||||
infoPListBuilder = InfoPListBuilder(config),
|
||||
@@ -91,6 +91,16 @@ internal fun createObjCFramework(
|
||||
)
|
||||
}
|
||||
|
||||
internal fun createTestBundle(
|
||||
config: KonanConfig,
|
||||
moduleDescriptor: ModuleDescriptor,
|
||||
bundleDirectory: File
|
||||
) {
|
||||
val name = bundleDirectory.name.removeSuffix(CompilerOutputKind.TEST_BUNDLE.suffix())
|
||||
BundleBuilder(config, infoPListBuilder = InfoPListBuilder(config), mainPackageGuesser = MainPackageGuesser())
|
||||
.build(moduleDescriptor, bundleDirectory, name)
|
||||
}
|
||||
|
||||
// TODO: No need for such class in dynamic driver.
|
||||
internal class ObjCExport(
|
||||
private val generationState: NativeGenerationState,
|
||||
|
||||
@@ -26,6 +26,9 @@ enum class CompilerOutputKind {
|
||||
BITCODE {
|
||||
override fun suffix(target: KonanTarget?) = ".bc"
|
||||
},
|
||||
TEST_BUNDLE {
|
||||
override fun suffix(target: KonanTarget?): String = ".xctest"
|
||||
},
|
||||
|
||||
DYNAMIC_CACHE {
|
||||
override fun suffix(target: KonanTarget?) = ".${target!!.family.dynamicSuffix}"
|
||||
|
||||
Reference in New Issue
Block a user