[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.DYNAMIC_CACHE, CompilerOutputKind.STATIC_CACHE,
|
||||||
CompilerOutputKind.LIBRARY, CompilerOutputKind.BITCODE -> false
|
CompilerOutputKind.LIBRARY, CompilerOutputKind.BITCODE -> false
|
||||||
CompilerOutputKind.FRAMEWORK -> !omitFrameworkBinary
|
CompilerOutputKind.FRAMEWORK -> !omitFrameworkBinary
|
||||||
|
CompilerOutputKind.TEST_BUNDLE -> true
|
||||||
else -> error("not supported: ${this.produce}")
|
else -> error("not supported: ${this.produce}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+36
-22
@@ -18,6 +18,7 @@ internal fun determineLinkerOutput(context: PhaseContext): LinkerOutputKind =
|
|||||||
val staticFramework = context.config.produceStaticFramework
|
val staticFramework = context.config.produceStaticFramework
|
||||||
if (staticFramework) LinkerOutputKind.STATIC_LIBRARY else LinkerOutputKind.DYNAMIC_LIBRARY
|
if (staticFramework) LinkerOutputKind.STATIC_LIBRARY else LinkerOutputKind.DYNAMIC_LIBRARY
|
||||||
}
|
}
|
||||||
|
CompilerOutputKind.TEST_BUNDLE,
|
||||||
CompilerOutputKind.DYNAMIC_CACHE,
|
CompilerOutputKind.DYNAMIC_CACHE,
|
||||||
CompilerOutputKind.DYNAMIC -> LinkerOutputKind.DYNAMIC_LIBRARY
|
CompilerOutputKind.DYNAMIC -> LinkerOutputKind.DYNAMIC_LIBRARY
|
||||||
CompilerOutputKind.STATIC_CACHE,
|
CompilerOutputKind.STATIC_CACHE,
|
||||||
@@ -91,31 +92,44 @@ internal class Linker(
|
|||||||
val additionalLinkerArgs: List<String>
|
val additionalLinkerArgs: List<String>
|
||||||
val executable: String
|
val executable: String
|
||||||
|
|
||||||
if (config.produce != CompilerOutputKind.FRAMEWORK) {
|
when (config.produce) {
|
||||||
additionalLinkerArgs = if (target.family.isAppleFamily) {
|
CompilerOutputKind.TEST_BUNDLE -> {
|
||||||
when (config.produce) {
|
val bundleDir = File(outputFile)
|
||||||
CompilerOutputKind.DYNAMIC_CACHE ->
|
val name = bundleDir.name.removeSuffix(config.produce.suffix())
|
||||||
listOf("-install_name", outputFiles.dynamicCacheInstallName)
|
require(target.family.isAppleFamily)
|
||||||
else -> listOf("-dead_strip")
|
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 {
|
additionalLinkerArgs = listOf("-dead_strip", "-install_name", "@rpath/${framework.name}/$dylibRelativePath")
|
||||||
emptyList()
|
val dylibPath = framework.child(dylibRelativePath)
|
||||||
|
dylibPath.parentFile.mkdirs()
|
||||||
|
executable = dylibPath.absolutePath
|
||||||
}
|
}
|
||||||
executable = outputFiles.nativeBinaryFile
|
else -> {
|
||||||
} else {
|
additionalLinkerArgs = if (target.family.isAppleFamily) {
|
||||||
val framework = File(outputFile)
|
when (config.produce) {
|
||||||
val dylibName = framework.name.removeSuffix(".framework")
|
CompilerOutputKind.DYNAMIC_CACHE ->
|
||||||
val dylibRelativePath = when (target.family) {
|
listOf("-install_name", outputFiles.dynamicCacheInstallName)
|
||||||
Family.IOS,
|
else -> listOf("-dead_strip")
|
||||||
Family.TVOS,
|
}
|
||||||
Family.WATCHOS -> dylibName
|
} else {
|
||||||
Family.OSX -> "Versions/A/$dylibName"
|
emptyList()
|
||||||
else -> error(target)
|
}
|
||||||
|
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()
|
File(executable).delete()
|
||||||
|
|
||||||
|
|||||||
+21
@@ -45,6 +45,7 @@ internal class DynamicCompilerDriver : CompilerDriver() {
|
|||||||
CompilerOutputKind.DYNAMIC_CACHE -> produceBinary(engine, config, environment)
|
CompilerOutputKind.DYNAMIC_CACHE -> produceBinary(engine, config, environment)
|
||||||
CompilerOutputKind.STATIC_CACHE -> produceBinary(engine, config, environment)
|
CompilerOutputKind.STATIC_CACHE -> produceBinary(engine, config, environment)
|
||||||
CompilerOutputKind.PRELIMINARY_CACHE -> TODO()
|
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(
|
private fun createBackendContext(
|
||||||
config: KonanConfig,
|
config: KonanConfig,
|
||||||
frontendOutput: FrontendPhaseOutput.Full,
|
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.common.lower
|
||||||
import org.jetbrains.kotlin.backend.konan.NativeGenerationState
|
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.PhaseContext
|
||||||
import org.jetbrains.kotlin.backend.konan.driver.PhaseEngine
|
import org.jetbrains.kotlin.backend.konan.driver.PhaseEngine
|
||||||
import org.jetbrains.kotlin.backend.konan.driver.utilities.KotlinBackendIrHolder
|
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.backend.konan.makeEntryPoint
|
||||||
import org.jetbrains.kotlin.ir.IrElement
|
import org.jetbrains.kotlin.ir.IrElement
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
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.declarations.IrModuleFragment
|
||||||
import org.jetbrains.kotlin.ir.util.NaiveSourceBasedFileEntryImpl
|
import org.jetbrains.kotlin.ir.util.NaiveSourceBasedFileEntryImpl
|
||||||
import org.jetbrains.kotlin.ir.util.addChild
|
import org.jetbrains.kotlin.ir.util.addChild
|
||||||
@@ -88,3 +90,12 @@ internal val EntryPointPhase = createSimpleNamedCompilerPhase<NativeGenerationSt
|
|||||||
|
|
||||||
file.addChild(makeEntryPoint(context))
|
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,
|
exportedInterface: ObjCExportedInterface,
|
||||||
frameworkDirectory: File
|
frameworkDirectory: File
|
||||||
) {
|
) {
|
||||||
val frameworkName = frameworkDirectory.name.removeSuffix(".framework")
|
val frameworkName = frameworkDirectory.name.removeSuffix(CompilerOutputKind.FRAMEWORK.suffix())
|
||||||
val frameworkBuilder = FrameworkBuilder(
|
val frameworkBuilder = FrameworkBuilder(
|
||||||
config,
|
config,
|
||||||
infoPListBuilder = InfoPListBuilder(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.
|
// TODO: No need for such class in dynamic driver.
|
||||||
internal class ObjCExport(
|
internal class ObjCExport(
|
||||||
private val generationState: NativeGenerationState,
|
private val generationState: NativeGenerationState,
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ enum class CompilerOutputKind {
|
|||||||
BITCODE {
|
BITCODE {
|
||||||
override fun suffix(target: KonanTarget?) = ".bc"
|
override fun suffix(target: KonanTarget?) = ".bc"
|
||||||
},
|
},
|
||||||
|
TEST_BUNDLE {
|
||||||
|
override fun suffix(target: KonanTarget?): String = ".xctest"
|
||||||
|
},
|
||||||
|
|
||||||
DYNAMIC_CACHE {
|
DYNAMIC_CACHE {
|
||||||
override fun suffix(target: KonanTarget?) = ".${target!!.family.dynamicSuffix}"
|
override fun suffix(target: KonanTarget?) = ".${target!!.family.dynamicSuffix}"
|
||||||
|
|||||||
Reference in New Issue
Block a user