From 569bb71bca9b1928e4cd236b47003c1a37ba3770 Mon Sep 17 00:00:00 2001 From: Nikolay Igotti Date: Fri, 20 Apr 2018 12:25:59 +0100 Subject: [PATCH] Support static library production. (#1515) --- .../kotlin/backend/konan/CAdapterGenerator.kt | 2 +- .../kotlin/backend/konan/CompilerOutput.kt | 6 +- .../jetbrains/kotlin/backend/konan/Context.kt | 5 +- .../kotlin/backend/konan/LinkStage.kt | 97 +++-- .../kotlin/backend/konan/llvm/IrToBitcode.kt | 2 +- .../kotlin/konan/target/KonanTarget.kt | 21 +- .../jetbrains/kotlin/konan/target/Linker.kt | 355 +++++++++++------- .../test/EnvVariableSpecification.groovy | 5 + .../gradle/plugin/test/KonanProject.groovy | 1 + .../test/kotlin/PropertiesAsEnvVariables.kt | 4 + 10 files changed, 296 insertions(+), 202 deletions(-) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CAdapterGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CAdapterGenerator.kt index 265dd30d3e9..e58515da6d4 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CAdapterGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CAdapterGenerator.kt @@ -796,7 +796,7 @@ internal class CAdapterGenerator( output("#endif /* KONAN_${prefix.toUpperCase()}_H */") outputStreamWriter.close() - println("Produced dynamic library API in ${prefix}_api.h") + println("Produced library API in ${prefix}_api.h") outputStreamWriter = context.config.tempFiles .cAdapterCpp diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CompilerOutput.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CompilerOutput.kt index bb72b8bba90..7aedc140528 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CompilerOutput.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CompilerOutput.kt @@ -22,7 +22,8 @@ import org.jetbrains.kotlin.backend.konan.llvm.parseBitcodeFile import org.jetbrains.kotlin.konan.target.CompilerOutputKind val CompilerOutputKind.isNativeBinary: Boolean get() = when (this) { - CompilerOutputKind.PROGRAM, CompilerOutputKind.DYNAMIC, CompilerOutputKind.FRAMEWORK -> true + CompilerOutputKind.PROGRAM, CompilerOutputKind.DYNAMIC, + CompilerOutputKind.STATIC, CompilerOutputKind.FRAMEWORK -> true CompilerOutputKind.LIBRARY, CompilerOutputKind.BITCODE -> false } @@ -34,6 +35,7 @@ internal fun produceOutput(context: Context) { val produce = config.get(KonanConfigKeys.PRODUCE) when (produce) { + CompilerOutputKind.STATIC, CompilerOutputKind.DYNAMIC, CompilerOutputKind.FRAMEWORK, CompilerOutputKind.PROGRAM -> { @@ -41,7 +43,7 @@ internal fun produceOutput(context: Context) { context.bitcodeFileName = output val generatedBitcodeFiles = - if (produce == CompilerOutputKind.DYNAMIC) { + if (produce == CompilerOutputKind.DYNAMIC || produce == CompilerOutputKind.STATIC) { produceCAdapterBitcode( context.config.clang, tempFiles.cAdapterCppName, diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt index 9e05157a518..f6fe7979465 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt @@ -394,8 +394,9 @@ internal class Context(config: KonanConfig) : KonanBackendContext(config) { lateinit var debugInfo: DebugInfo - val isDynamicLibrary: Boolean by lazy { - config.configuration.get(KonanConfigKeys.PRODUCE) == CompilerOutputKind.DYNAMIC + val isNativeLibrary: Boolean by lazy { + val kind = config.configuration.get(KonanConfigKeys.PRODUCE) + kind == CompilerOutputKind.DYNAMIC || kind == CompilerOutputKind.STATIC } } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/LinkStage.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/LinkStage.kt index b0b0ed37d40..f3872263a5a 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/LinkStage.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/LinkStage.kt @@ -35,8 +35,12 @@ internal class LinkStage(val context: Context) { private val optimize = config.get(KonanConfigKeys.OPTIMIZATION) ?: false private val debug = config.get(KonanConfigKeys.DEBUG) ?: false - private val dynamic = context.config.produce == CompilerOutputKind.DYNAMIC || - context.config.produce == CompilerOutputKind.FRAMEWORK + private val linkerOutput = when (context.config.produce) { + CompilerOutputKind.DYNAMIC, CompilerOutputKind.FRAMEWORK -> LinkerOutputKind.DYNAMIC_LIBRARY + CompilerOutputKind.STATIC -> LinkerOutputKind.STATIC_LIBRARY + CompilerOutputKind.PROGRAM -> LinkerOutputKind.EXECUTABLE + else -> TODO("${context.config.produce} should not reach native linker stage") + } private val nomain = config.get(KonanConfigKeys.NOMAIN) ?: false private val emitted = context.bitcodeFileName private val libraries = context.llvm.librariesToLink @@ -45,10 +49,10 @@ internal class LinkStage(val context: Context) { } private fun runTool(command: List) = runTool(*command.toTypedArray()) - private fun runTool(vararg command: String) = - Command(*command) - .logWith(context::log) - .execute() + private fun runTool(vararg command: String) = + Command(*command) + .logWith(context::log) + .execute() private fun llvmLto(files: List): ObjectFile { val combined = temporary("combined", ".o") @@ -58,8 +62,8 @@ internal class LinkStage(val context: Context) { command.addNonEmpty(platform.llvmLtoFlags) when { optimize -> command.addNonEmpty(platform.llvmLtoOptFlags) - debug -> command.addNonEmpty(platform.llvmDebugOptFlags) - else -> command.addNonEmpty(platform.llvmLtoNooptFlags) + debug -> command.addNonEmpty(platform.llvmDebugOptFlags) + else -> command.addNonEmpty(platform.llvmLtoNooptFlags) } command.addNonEmpty(platform.llvmLtoDynamicFlags) command.addNonEmpty(files) @@ -89,27 +93,27 @@ internal class LinkStage(val context: Context) { hostLlvmTool("llvm-link", *bitcodeFiles.toTypedArray(), "-o", combinedBc) val optFlags = (configurables.optFlags + when { - optimize -> configurables.optOptFlags - debug -> configurables.optDebugFlags - else -> configurables.optNooptFlags + optimize -> configurables.optOptFlags + debug -> configurables.optDebugFlags + else -> configurables.optNooptFlags }).toTypedArray() val optimizedBc = temporary("optimized", ".bc") hostLlvmTool("opt", combinedBc, "-o", optimizedBc, *optFlags) val llcFlags = (configurables.llcFlags + when { - optimize -> configurables.llcOptFlags - debug -> configurables.llcDebugFlags - else -> configurables.llcNooptFlags + optimize -> configurables.llcOptFlags + debug -> configurables.llcDebugFlags + else -> configurables.llcNooptFlags }).toTypedArray() val combinedS = temporary("combined", ".s") targetTool("llc", optimizedBc, "-o", combinedS, *llcFlags) val s2wasmFlags = configurables.s2wasmFlags.toTypedArray() - val combinedWast = temporary( "combined", ".wast") + val combinedWast = temporary("combined", ".wast") targetTool("s2wasm", combinedS, "-o", combinedWast, *s2wasmFlags) - val combinedWasm = temporary( "combined", ".wasm") - val combinedSmap = temporary( "combined", ".smap") + val combinedWasm = temporary("combined", ".wasm") + val combinedSmap = temporary("combined", ".smap") targetTool("wasm-as", combinedWast, "-o", combinedWasm, "-g", "-s", combinedSmap) return combinedWasm @@ -153,9 +157,11 @@ internal class LinkStage(val context: Context) { // So we stick to "-alias _main _konan_main" on Mac. // And just do the same on Linux. private val entryPointSelector: List - get() = if (nomain || dynamic) emptyList() else platform.entrySelector + get() = if (nomain || linkerOutput != LinkerOutputKind.EXECUTABLE) emptyList() else platform.entrySelector - private fun link(objectFiles: List, includedBinaries: List, libraryProvidedLinkerFlags: List): ExecutableFile? { + private fun link(objectFiles: List, + includedBinaries: List, + libraryProvidedLinkerFlags: List): ExecutableFile? { val frameworkLinkerArgs: List val executable: String @@ -177,23 +183,16 @@ internal class LinkStage(val context: Context) { } try { - linker.linkCommand(objectFiles, executable, optimize, debug, dynamic).apply { - + linker.targetLibffi - + asLinkerArgs(config.getNotNull(KonanConfigKeys.LINKER_ARGS)) - + entryPointSelector - + frameworkLinkerArgs - + linker.linkCommandSuffix() - + linker.linkStaticLibraries(includedBinaries) - + libraryProvidedLinkerFlags - logger = context::log - }.execute() - - if (debug && linker is MacOSBasedLinker) { - val outputDsymBundle = context.config.outputFile + ".dSYM" // `outputFile` is either binary or bundle. - - linker.dsymUtilCommand(executable, outputDsymBundle) - .logWith(context::log) - .execute() + File(executable).delete() + linker.linkCommands(objectFiles = objectFiles, executable = executable, + libraries = linker.targetLibffi + linker.linkStaticLibraries(includedBinaries), + linkerArgs = entryPointSelector + + asLinkerArgs(config.getNotNull(KonanConfigKeys.LINKER_ARGS)) + + libraryProvidedLinkerFlags + frameworkLinkerArgs, + optimize = optimize, debug = debug, kind = linkerOutput, + outputDsymBundle = context.config.outputFile + ".dSYM").forEach { + it.logWith(context::log) + it.execute() } } catch (e: KonanExternalToolFailure) { context.reportCompilationError("${e.toolName} invocation reported errors") @@ -204,27 +203,27 @@ internal class LinkStage(val context: Context) { fun linkStage() { val bitcodeFiles = listOf(emitted) + - libraries.map{it -> it.bitcodePaths}.flatten() + libraries.map { it -> it.bitcodePaths }.flatten() - val includedBinaries = - libraries.map{it -> it.includedPaths}.flatten() + val includedBinaries = + libraries.map { it -> it.includedPaths }.flatten() - val libraryProvidedLinkerFlags = - libraries.map{it -> it.linkerOpts}.flatten() + val libraryProvidedLinkerFlags = + libraries.map { it -> it.linkerOpts }.flatten() val objectFiles: MutableList = mutableListOf() val phaser = PhaseManager(context) phaser.phase(KonanPhase.OBJECT_FILES) { - objectFiles.add( - when (platform.configurables) { - is WasmConfigurables - -> bitcodeToWasm(bitcodeFiles) - is ZephyrConfigurables - -> llvmLinkAndLlc(bitcodeFiles) - else + objectFiles.add( + when (platform.configurables) { + is WasmConfigurables + -> bitcodeToWasm(bitcodeFiles) + is ZephyrConfigurables + -> llvmLinkAndLlc(bitcodeFiles) + else -> llvmLto(bitcodeFiles) - } + } ) } phaser.phase(KonanPhase.LINKER) { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt index 22979155e62..91fc2920d8c 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt @@ -348,7 +348,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map, - executable: ExecutableFile, optimize: Boolean, debug: Boolean, dynamic: Boolean): Command + abstract fun linkCommands(objectFiles: List, executable: ExecutableFile, + libraries: List, linkerArgs: List, + optimize: Boolean, debug: Boolean, + kind: LinkerOutputKind, outputDsymBundle: String): List - open fun linkCommandSuffix(): List = emptyList() - - abstract fun filterStaticLibraries(binaries: List): List + abstract fun filterStaticLibraries(binaries: List): List open fun linkStaticLibraries(binaries: List): List { val libraries = filterStaticLibraries(binaries) - // Let's just pass them as absolute paths + // Let's just pass them as absolute paths. return libraries } + + protected fun postLinkGnuArCommand(ar: String, executable: ExecutableFile) = + Command("/bin/sh", "-c").apply { + +"/bin/echo -e 'create $executable\\naddlib $executable\\nsave\\nend' | $ar -M" + } + } open class AndroidLinker(targetProperties: AndroidConfigurables) - : LinkerFlags(targetProperties), AndroidConfigurables by targetProperties { + : LinkerFlags(targetProperties), AndroidConfigurables by targetProperties { private val prefix = "$absoluteTargetToolchain/bin/" private val clang = "$prefix/clang" + private val ar = "$prefix/ar" override val useCompilerDriverAsLinker: Boolean get() = true - override fun filterStaticLibraries(binaries: List) - = binaries.filter { it.isUnixStaticLib } + override fun filterStaticLibraries(binaries: List) = binaries.filter { it.isUnixStaticLib } - override fun linkCommand(objectFiles: List, executable: ExecutableFile, optimize: Boolean, debug: Boolean, dynamic: Boolean): Command { - // liblog.so must be linked in, as we use its functionality in runtime. - return Command(clang).apply { - + "-o" - + executable - + "-fPIC" - + "-shared" - + "-llog" - + objectFiles - if (optimize) + linkerOptimizationFlags - if (!debug) + linkerNoDebugFlags - if (dynamic) + linkerDynamicFlags - + linkerKonanFlags + override fun linkCommands(objectFiles: List, executable: ExecutableFile, + libraries: List, linkerArgs: List, + optimize: Boolean, debug: Boolean, + kind: LinkerOutputKind, outputDsymBundle: String): List { + if (kind == LinkerOutputKind.STATIC_LIBRARY) { + // Here we take somewhat unexpected approach - we create the thin + // library, and then repack it during post-link phase. + // This way we ensure .a inputs are properly processed. + return listOf( + Command(ar, "cqT", executable).apply { + +objectFiles + +libraries + }, + postLinkGnuArCommand(ar, executable)) } + val dynamic = kind == LinkerOutputKind.DYNAMIC_LIBRARY + // liblog.so must be linked in, as we use its functionality in runtime. + return listOf(Command(clang).apply { + +"-o" + +executable + +"-fPIC" + +"-shared" + +"-llog" + +objectFiles + if (optimize) +linkerOptimizationFlags + if (!debug) +linkerNoDebugFlags + if (dynamic) +linkerDynamicFlags + +linkerKonanFlags + +libraries + +linkerArgs + }) } } open class MacOSBasedLinker(targetProperties: AppleConfigurables) : LinkerFlags(targetProperties), AppleConfigurables by targetProperties { + private val libtool = "$absoluteTargetToolchain/usr/bin/libtool" private val linker = "$absoluteTargetToolchain/usr/bin/ld" internal val dsymutil = "$absoluteLlvmHome/bin/llvm-dsymutil" @@ -99,30 +129,42 @@ open class MacOSBasedLinker(targetProperties: AppleConfigurables) osVersionMin + ".0") } - override fun filterStaticLibraries(binaries: List) - = binaries.filter { it.isUnixStaticLib } + override fun filterStaticLibraries(binaries: List) = binaries.filter { it.isUnixStaticLib } - override fun linkCommand(objectFiles: List, executable: ExecutableFile, optimize: Boolean, debug: Boolean, dynamic: Boolean): Command { - return object : Command(linker) {} .apply { - + "-demangle" - + listOf("-object_path_lto", "temporary.o", "-lto_library", libLTO) - + listOf("-dynamic", "-arch", arch) - + osVersionMinFlags - + listOf("-syslibroot", absoluteTargetSysRoot, "-o", executable) - + objectFiles - if (optimize) + linkerOptimizationFlags - if (!debug) + linkerNoDebugFlags - if (dynamic) + linkerDynamicFlags - + linkerKonanFlags - + "-lSystem" - } + override fun linkCommands(objectFiles: List, executable: ExecutableFile, + libraries: List, linkerArgs: List, + optimize: Boolean, debug: Boolean, kind: LinkerOutputKind, + outputDsymBundle: String): List { + if (kind == LinkerOutputKind.STATIC_LIBRARY) + return listOf(Command(libtool).apply { + +"-static" + +listOf("-o", executable) + +objectFiles + +libraries + }) + val dynamic = kind == LinkerOutputKind.DYNAMIC_LIBRARY + return listOf(Command(linker).apply { + +"-demangle" + +listOf("-object_path_lto", "temporary.o", "-lto_library", libLTO) + +listOf("-dynamic", "-arch", arch) + +osVersionMinFlags + +listOf("-syslibroot", absoluteTargetSysRoot, "-o", executable) + +objectFiles + if (optimize) +linkerOptimizationFlags + if (!debug) +linkerNoDebugFlags + if (dynamic) +linkerDynamicFlags + +linkerKonanFlags + +"-lSystem" + +libraries + +linkerArgs + }) + if (debug) listOf(dsymUtilCommand(executable, outputDsymBundle)) else emptyList() } - fun dsymUtilCommand(executable: ExecutableFile, outputDsymBundle: String) = - object : Command(dsymutilCommand(executable, outputDsymBundle)) { - override fun runProcess(): Int = - executeCommandWithFilter(command) - } + fun dsymUtilCommand(executable: ExecutableFile, outputDsymBundle: String) = + object : Command(dsymutilCommand(executable, outputDsymBundle)) { + override fun runProcess(): Int = + executeCommandWithFilter(command) + } // TODO: consider introducing a better filtering directly in Command. private fun executeCommandWithFilter(command: List): Int { @@ -155,106 +197,136 @@ open class MacOSBasedLinker(targetProperties: AppleConfigurables) return exitCode } - open fun dsymutilCommand(executable: ExecutableFile, outputDsymBundle: String): List = - listOf(dsymutil, executable, "-o", outputDsymBundle) + open fun dsymutilCommand(executable: ExecutableFile, outputDsymBundle: String): List = + listOf(dsymutil, executable, "-o", outputDsymBundle) open fun dsymutilDryRunVerboseCommand(executable: ExecutableFile): List = - listOf(dsymutil, "-dump-debug-map" ,executable) + listOf(dsymutil, "-dump-debug-map", executable) } open class LinuxBasedLinker(targetProperties: LinuxBasedConfigurables) : LinkerFlags(targetProperties), LinuxBasedConfigurables by targetProperties { + private val ar = "$absoluteTargetToolchain/bin/ar" override val libGcc: String = "$absoluteTargetSysRoot/${super.libGcc}" private val linker = "$absoluteTargetToolchain/bin/ld.gold" - private val specificLibs - = abiSpecificLibraries.map { "-L${absoluteTargetSysRoot}/$it" } + private val specificLibs = abiSpecificLibraries.map { "-L${absoluteTargetSysRoot}/$it" } - override fun filterStaticLibraries(binaries: List) - = binaries.filter { it.isUnixStaticLib } + override fun filterStaticLibraries(binaries: List) = binaries.filter { it.isUnixStaticLib } - override fun linkCommand(objectFiles: List, executable: ExecutableFile, optimize: Boolean, debug: Boolean, dynamic: Boolean): Command { - val isMips = (configurables is LinuxMIPSConfigurables) - - // TODO: Can we extract more to the konan.configurables? - return Command(linker).apply { - + "--sysroot=${absoluteTargetSysRoot}" - + "-export-dynamic" - + "-z" - + "relro" - + "--build-id" - + "--eh-frame-hdr" - + "-dynamic-linker" - + dynamicLinker - + "-o" - + executable - if (!dynamic) + "$absoluteTargetSysRoot/usr/lib64/crt1.o" - + "$absoluteTargetSysRoot/usr/lib64/crti.o" - if (dynamic) - + "$libGcc/crtbeginS.o" - else - + "$libGcc/crtbegin.o" - + "-L$llvmLib" - + "-L$libGcc" - if (!isMips) + "--hash-style=gnu" // MIPS doesn't support hash-style=gnu - + specificLibs - + listOf("-L$absoluteTargetSysRoot/../lib", "-L$absoluteTargetSysRoot/lib", "-L$absoluteTargetSysRoot/usr/lib") - if (optimize) { - + "-plugin" - +"$llvmLib/LLVMgold.so" - + pluginOptimizationFlags - } - if (optimize) + linkerOptimizationFlags - if (!debug) + linkerNoDebugFlags - if (dynamic) + linkerDynamicFlags - + objectFiles - + linkerKonanFlags - + listOf("-lgcc", "--as-needed", "-lgcc_s", "--no-as-needed", - "-lc", "-lgcc", "--as-needed", "-lgcc_s", "--no-as-needed") - if (dynamic) - + "$libGcc/crtendS.o" - else - + "$libGcc/crtend.o" - + "$absoluteTargetSysRoot/usr/lib64/crtn.o" + override fun linkCommands(objectFiles: List, executable: ExecutableFile, + libraries: List, linkerArgs: List, + optimize: Boolean, debug: Boolean, + kind: LinkerOutputKind, outputDsymBundle: String): List { + if (kind == LinkerOutputKind.STATIC_LIBRARY) { + // Here we take somewhat unexpected approach - we create the thin + // library, and then repack it during post-link phase. + // This way we ensure .a inputs are properly processed. + return listOf(Command(ar, "cqT", executable).apply { + +objectFiles + +libraries + }, + postLinkGnuArCommand(ar, executable)) } + val isMips = (configurables is LinuxMIPSConfigurables) + val dynamic = kind == LinkerOutputKind.DYNAMIC_LIBRARY + // TODO: Can we extract more to the konan.configurables? + return listOf(Command(linker).apply { + +"--sysroot=${absoluteTargetSysRoot}" + +"-export-dynamic" + +"-z" + +"relro" + +"--build-id" + +"--eh-frame-hdr" + +"-dynamic-linker" + +dynamicLinker + +"-o" + +executable + if (!dynamic) +"$absoluteTargetSysRoot/usr/lib64/crt1.o" + +"$absoluteTargetSysRoot/usr/lib64/crti.o" + +if (dynamic) "$libGcc/crtbeginS.o" else "$libGcc/crtbegin.o" + +"-L$llvmLib" + +"-L$libGcc" + if (!isMips) +"--hash-style=gnu" // MIPS doesn't support hash-style=gnu + +specificLibs + +listOf("-L$absoluteTargetSysRoot/../lib", "-L$absoluteTargetSysRoot/lib", "-L$absoluteTargetSysRoot/usr/lib") + if (optimize) { + +"-plugin" + +"$llvmLib/LLVMgold.so" + +pluginOptimizationFlags + } + if (optimize) +linkerOptimizationFlags + if (!debug) +linkerNoDebugFlags + if (dynamic) +linkerDynamicFlags + +objectFiles + +linkerKonanFlags + +listOf("-lgcc", "--as-needed", "-lgcc_s", "--no-as-needed", + "-lc", "-lgcc", "--as-needed", "-lgcc_s", "--no-as-needed") + +if (dynamic) "$libGcc/crtendS.o" else "$libGcc/crtend.o" + +"$absoluteTargetSysRoot/usr/lib64/crtn.o" + +libraries + +linkerArgs + }) } } open class MingwLinker(targetProperties: MingwConfigurables) : LinkerFlags(targetProperties), MingwConfigurables by targetProperties { + private val ar = "$absoluteTargetToolchain\\bin\\ar" private val linker = "$absoluteTargetToolchain/bin/clang++" override val useCompilerDriverAsLinker: Boolean get() = true - override fun filterStaticLibraries(binaries: List) - = binaries.filter { it.isWindowsStaticLib || it.isUnixStaticLib } + override fun filterStaticLibraries(binaries: List) = binaries.filter { it.isWindowsStaticLib || it.isUnixStaticLib } - override fun linkCommand(objectFiles: List, executable: ExecutableFile, optimize: Boolean, debug: Boolean, dynamic: Boolean): Command { - return Command(linker).apply { - + listOf("-o", executable) - + objectFiles - if (optimize) + linkerOptimizationFlags - if (!debug) + linkerNoDebugFlags - if (dynamic) + linkerDynamicFlags + override fun linkCommands(objectFiles: List, executable: ExecutableFile, + libraries: List, linkerArgs: List, + optimize: Boolean, debug: Boolean, + kind: LinkerOutputKind, outputDsymBundle: String): List { + if (kind == LinkerOutputKind.STATIC_LIBRARY) { + // Here we take somewhat unexpected approach - we create the thin + // library, and then repack it during post-link phase. + // This way we ensure .a inputs are properly processed. + val temp = executable.replace('/', '\\') + "__" + return listOf( + Command(ar, "-rucT", temp).apply { + +objectFiles + +libraries + }, + Command("cmd", "/c").apply { + +"(echo create $executable & echo addlib ${temp} & echo save & echo end) | $ar -M" + }, + Command("cmd", "/c", "del", "/q", temp)) } + val dynamic = kind == LinkerOutputKind.DYNAMIC_LIBRARY + return listOf(Command(linker).apply { + +listOf("-o", executable) + +objectFiles + if (optimize) +linkerOptimizationFlags + if (!debug) +linkerNoDebugFlags + if (dynamic) +linkerDynamicFlags + +libraries + +linkerArgs + +linkerKonanFlags + }) } - - override fun linkCommandSuffix() = linkerKonanFlags } open class WasmLinker(targetProperties: WasmConfigurables) : LinkerFlags(targetProperties), WasmConfigurables by targetProperties { - private val clang = "clang" - override val useCompilerDriverAsLinker: Boolean get() = false - override fun filterStaticLibraries(binaries: List) - = binaries.filter{it.isJavaScript} + override fun filterStaticLibraries(binaries: List) = binaries.filter { it.isJavaScript } - override fun linkCommand(objectFiles: List, executable: ExecutableFile, optimize: Boolean, debug: Boolean, dynamic: Boolean): Command { - return object: Command("") { + override fun linkCommands(objectFiles: List, executable: ExecutableFile, + libraries: List, linkerArgs: List, + optimize: Boolean, debug: Boolean, + kind: LinkerOutputKind, outputDsymBundle: String): List { + if (kind != LinkerOutputKind.EXECUTABLE) throw Error("Unsupported linker output kind") + // TODO(horsh): make it proper list. + return listOf(object : Command() { override fun execute() { val src = File(objectFiles.single()) val dst = File(executable) @@ -281,7 +353,7 @@ open class WasmLinker(targetProperties: WasmConfigurables) linkedJavaScript.appendBytes(linkerFooter.toByteArray()); return linkedJavaScript.name } - } + }) } } @@ -292,33 +364,38 @@ open class ZephyrLinker(targetProperties: ZephyrConfigurables) override val useCompilerDriverAsLinker: Boolean get() = false - override fun filterStaticLibraries(binaries: List) - = emptyList() + override fun filterStaticLibraries(binaries: List) = emptyList() - override fun linkCommand(objectFiles: List, executable: ExecutableFile, optimize: Boolean, debug: Boolean, dynamic: Boolean): Command { - return Command(linker).apply { - + listOf("-r", "--gc-sections", "--entry", "main") - + listOf("-o", executable) - + objectFiles - } + override fun linkCommands(objectFiles: List, executable: ExecutableFile, + libraries: List, linkerArgs: List, + optimize: Boolean, debug: Boolean, + kind: LinkerOutputKind, outputDsymBundle: String): List { + if (kind != LinkerOutputKind.EXECUTABLE) throw Error("Unsupported linker output kind: $kind") + return listOf(Command(linker).apply { + +listOf("-r", "--gc-sections", "--entry", "main") + +listOf("-o", executable) + +objectFiles + +libraries + +linkerArgs + }) } } -fun linker(configurables: Configurables): LinkerFlags = - when (configurables.target) { - KonanTarget.LINUX_X64, KonanTarget.LINUX_ARM32_HFP -> - LinuxBasedLinker(configurables as LinuxConfigurables) - KonanTarget.LINUX_MIPS32, KonanTarget.LINUX_MIPSEL32 -> - LinuxBasedLinker(configurables as LinuxMIPSConfigurables) - KonanTarget.MACOS_X64, KonanTarget.IOS_ARM64, KonanTarget.IOS_X64 -> - MacOSBasedLinker(configurables as AppleConfigurables) - KonanTarget.ANDROID_ARM32, KonanTarget.ANDROID_ARM64 -> - AndroidLinker(configurables as AndroidConfigurables) - KonanTarget.MINGW_X64 -> - MingwLinker(configurables as MingwConfigurables) - KonanTarget.WASM32 -> - WasmLinker(configurables as WasmConfigurables) - is KonanTarget.ZEPHYR -> - ZephyrLinker(configurables as ZephyrConfigurables) - } +fun linker(configurables: Configurables): LinkerFlags = + when (configurables.target) { + KonanTarget.LINUX_X64, KonanTarget.LINUX_ARM32_HFP -> + LinuxBasedLinker(configurables as LinuxConfigurables) + KonanTarget.LINUX_MIPS32, KonanTarget.LINUX_MIPSEL32 -> + LinuxBasedLinker(configurables as LinuxMIPSConfigurables) + KonanTarget.MACOS_X64, KonanTarget.IOS_ARM64, KonanTarget.IOS_X64 -> + MacOSBasedLinker(configurables as AppleConfigurables) + KonanTarget.ANDROID_ARM32, KonanTarget.ANDROID_ARM64 -> + AndroidLinker(configurables as AndroidConfigurables) + KonanTarget.MINGW_X64 -> + MingwLinker(configurables as MingwConfigurables) + KonanTarget.WASM32 -> + WasmLinker(configurables as WasmConfigurables) + is KonanTarget.ZEPHYR -> + ZephyrLinker(configurables as ZephyrConfigurables) + } diff --git a/tools/kotlin-native-gradle-plugin/src/test/groovy/org/jetbrains/kotlin/gradle/plugin/test/EnvVariableSpecification.groovy b/tools/kotlin-native-gradle-plugin/src/test/groovy/org/jetbrains/kotlin/gradle/plugin/test/EnvVariableSpecification.groovy index 2ea5cd99a5d..717f12b244b 100644 --- a/tools/kotlin-native-gradle-plugin/src/test/groovy/org/jetbrains/kotlin/gradle/plugin/test/EnvVariableSpecification.groovy +++ b/tools/kotlin-native-gradle-plugin/src/test/groovy/org/jetbrains/kotlin/gradle/plugin/test/EnvVariableSpecification.groovy @@ -110,6 +110,10 @@ class EnvVariableSpecification extends BaseKonanSpecification { prefix = target.family.dynamicPrefix suffix = target.family.dynamicSuffix break + case ArtifactType.STATIC: + prefix = target.family.staticPrefix + suffix = target.family.staticSuffix + break case ArtifactType.FRAMEWORK: suffix = "framework" } @@ -194,6 +198,7 @@ class EnvVariableSpecification extends BaseKonanSpecification { files.contains(artifactFileName("program", ArtifactType.PROGRAM)) files.contains(artifactFileName("library", ArtifactType.LIBRARY)) files.contains(artifactFileName("dynamic", ArtifactType.DYNAMIC)) + files.contains(artifactFileName("static", ArtifactType.STATIC)) if (HostManager.hostIsMac) { files.contains(artifactFileName("framework", ArtifactType.FRAMEWORK)) } diff --git a/tools/kotlin-native-gradle-plugin/src/test/groovy/org/jetbrains/kotlin/gradle/plugin/test/KonanProject.groovy b/tools/kotlin-native-gradle-plugin/src/test/groovy/org/jetbrains/kotlin/gradle/plugin/test/KonanProject.groovy index 4423e5ab59c..9e86bb81e8c 100644 --- a/tools/kotlin-native-gradle-plugin/src/test/groovy/org/jetbrains/kotlin/gradle/plugin/test/KonanProject.groovy +++ b/tools/kotlin-native-gradle-plugin/src/test/groovy/org/jetbrains/kotlin/gradle/plugin/test/KonanProject.groovy @@ -29,6 +29,7 @@ enum ArtifactType { BITCODE("bitcode"), INTEROP("interop"), DYNAMIC("dynamic"), + STATIC("static"), FRAMEWORK("framework") String type diff --git a/tools/kotlin-native-gradle-plugin/src/test/kotlin/PropertiesAsEnvVariables.kt b/tools/kotlin-native-gradle-plugin/src/test/kotlin/PropertiesAsEnvVariables.kt index 83d5dd19fb5..5cef642eb70 100644 --- a/tools/kotlin-native-gradle-plugin/src/test/kotlin/PropertiesAsEnvVariables.kt +++ b/tools/kotlin-native-gradle-plugin/src/test/kotlin/PropertiesAsEnvVariables.kt @@ -32,6 +32,10 @@ open class PropertiesAsEnvVariables { prefix = target.family.dynamicPrefix suffix = target.family.dynamicSuffix } + ArtifactType.STATIC -> { + prefix = target.family.staticPrefix + suffix = target.family.staticSuffix + } } return "$prefix${baseName}.$suffix"