diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CAdapterCompile.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CAdapterCompile.kt index c7ddf4bce20..bcc0059243d 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CAdapterCompile.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CAdapterCompile.kt @@ -22,5 +22,5 @@ import org.jetbrains.kotlin.konan.file.* fun produceCAdapterBitcode(clang: TargetClang, cppFileName: String, bitcodeFileName: String) { val clangCommand = clang.clangCXX("-std=c++11", cppFileName, "-emit-llvm", "-c", "-o", bitcodeFileName) - runTool(clangCommand) + Command(clangCommand).execute() } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Exceptions.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Exceptions.kt index 8886ece78e5..ac36289ca40 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Exceptions.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Exceptions.kt @@ -16,15 +16,7 @@ package org.jetbrains.kotlin.backend.konan -/** - * This is a common ancestor of all Kotlin/Native exceptions. - */ -open class KonanException(message: String = "", cause: Throwable? = null) : Exception(message, cause) - -/** - * An error occured during external tool invocation. Such as non-zero exit code. - */ -class KonanExternalToolFailure(message: String = "", cause: Throwable? = null) : KonanException(message, cause) +import org.jetbrains.kotlin.konan.KonanException /** * Represents a compilation error caused by mistakes in an input file, e.g. undefined reference. 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 5fffccbf981..e919ffac806 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 @@ -16,9 +16,10 @@ package org.jetbrains.kotlin.backend.konan -import org.jetbrains.kotlin.konan.exec.runTool import java.lang.ProcessBuilder import java.lang.ProcessBuilder.Redirect +import org.jetbrains.kotlin.konan.KonanExternalToolFailure +import org.jetbrains.kotlin.konan.exec.Command import org.jetbrains.kotlin.konan.file.* import org.jetbrains.kotlin.konan.properties.* import org.jetbrains.kotlin.konan.target.* @@ -68,26 +69,6 @@ internal abstract class PlatformFlags(val properties: KonanProperties) { } -internal open class Command(tool:String) { - private val opts = mutableListOf(tool) - val libs = mutableListOf() - operator fun String.unaryPlus():Command { - opts += this - return this@Command - } - - operator fun List.unaryPlus():Command { - opts.addAll(this) - return this@Command - } - - open fun execute() = runTool(*opts.toTypedArray()) - - fun externalLibraries(deps: List) { - libs.addAll(deps) - } -} - internal open class AndroidPlatform(distribution: Distribution) : PlatformFlags(distribution.targetProperties) { @@ -133,13 +114,7 @@ internal open class MacOSBasedPlatform(distribution: Distribution) = binaries.filter { it.isUnixStaticLib } override fun linkCommand(objectFiles: List, executable: ExecutableFile, optimize: Boolean, debug: Boolean, dynamic: Boolean): Command { - return object : Command(linker){ - override fun execute() { - super.execute() - if (debug) - runTool(*dsymutilCommand(executable).toTypedArray()) - } - }.apply { + return object : Command(linker) {} .apply { + "-demangle" + listOf("-object_path_lto", "temporary.o", "-lto_library", libLTO) + listOf("-dynamic", "-arch", propertyTargetString("arch")) @@ -154,6 +129,42 @@ internal open class MacOSBasedPlatform(distribution: Distribution) } } + fun dsymUtilCommand(executable: ExecutableFile) = object : Command(dsymutilCommand(executable)) { + override fun runProcess(): Int = + executeCommandWithFilter(command) + } + + // TODO: consider introducing a better filtering directly in Command. + private fun executeCommandWithFilter(command: List): Int { + val builder = ProcessBuilder(command) + + // Inherit main process output streams. + val isDsymUtil = (command[0] == dsymutil) + + builder.redirectOutput(Redirect.INHERIT) + builder.redirectInput(Redirect.INHERIT) + if (!isDsymUtil) + builder.redirectError(Redirect.INHERIT) + + val process = builder.start() + if (isDsymUtil) { + /** + * llvm-lto has option -alias that lets tool to know which symbol we use instead of _main, + * llvm-dsym doesn't have such a option, so we ignore annoying warning manually. + */ + val errorStream = process.errorStream + val outputStream = bufferedReader(errorStream) + while (true) { + val line = outputStream.readLine() ?: break + if (!line.contains("warning: could not find object file symbol for symbol _main")) + System.err.println(line) + } + outputStream.close() + } + val exitCode = process.waitFor() + return exitCode + } + open fun dsymutilCommand(executable: ExecutableFile): List = listOf(dsymutil, executable) open fun dsymutilDryRunVerboseCommand(executable: ExecutableFile): List = @@ -253,7 +264,7 @@ internal open class WasmPlatform(distribution: Distribution) override val useCompilerDriverAsLinker: Boolean get() = false override fun filterStaticLibraries(binaries: List) - = emptyList() + = binaries.filter{it.isJavaScript} override fun linkCommand(objectFiles: List, executable: ExecutableFile, optimize: Boolean, debug: Boolean, dynamic: Boolean): Command { return object: Command("") { @@ -261,7 +272,7 @@ internal open class WasmPlatform(distribution: Distribution) val src = File(objectFiles.single()) val dst = File(executable) src.recursiveCopyTo(dst) - javaScriptLink(libs.filter{it.isJavaScript}, executable) + javaScriptLink(args, executable) } private fun javaScriptLink(jsFiles: List, executable: String): String { @@ -312,6 +323,12 @@ internal class LinkStage(val context: Context) { addAll(elements.filter { !it.isEmpty() }) } + private fun runTool(command: List) = runTool(*command.toTypedArray()) + private fun runTool(vararg command: String) = + Command(*command) + .logWith(context::log) + .execute() + private fun llvmLto(files: List): ObjectFile { val combined = temporary("combined", ".o") @@ -325,7 +342,7 @@ internal class LinkStage(val context: Context) { } command.addNonEmpty(platform.llvmLtoDynamicFlags) command.addNonEmpty(files) - runTool(*command.toTypedArray()) + runTool(command) return combined } @@ -344,7 +361,7 @@ internal class LinkStage(val context: Context) { private fun hostLlvmTool(tool: String, args: List) { val absoluteToolName = "${distribution.llvmBin}/$tool" val command = listOf(absoluteToolName) + args - runTool(*command.toTypedArray()) + runTool(command) } private fun bitcodeToWasm(bitcodeFiles: List): String { @@ -422,58 +439,22 @@ internal class LinkStage(val context: Context) { + platform.linkCommandSuffix() + platform.linkStaticLibraries(includedBinaries) + libraryProvidedLinkerFlags - externalLibraries(includedBinaries) + logger = context::log }.execute() + + if (debug && platform is MacOSBasedPlatform) { + platform.dsymUtilCommand(executable) + .logWith(context::log) + .execute() + } } catch (e: KonanExternalToolFailure) { - context.reportCompilationError("linker invocation reported errors") + context.reportCompilationError("${e.toolName} invocation reported errors") return null } return executable } - private fun executeCommand(vararg command: String): Int { - - context.log{""} - context.log{command.asList().joinToString(" ")} - - val builder = ProcessBuilder(command.asList()) - - // Inherit main process output streams. - val isDsymUtil = platform is MacOSBasedPlatform && command[0] == platform.dsymutil - - builder.redirectOutput(Redirect.INHERIT) - builder.redirectInput(Redirect.INHERIT) - if (!isDsymUtil) - builder.redirectError(Redirect.INHERIT) - - - val process = builder.start() - if (isDsymUtil) { - /** - * llvm-lto has option -alias that lets tool to know which symbol we use instead of _main, - * llvm-dsym doesn't have such a option, so we ignore annoying warning manually. - */ - val errorStream = process.errorStream - val outputStream = bufferedReader(errorStream) - while (true) { - val line = outputStream.readLine() ?: break - if (!line.contains("warning: could not find object file symbol for symbol _main")) - System.err.println(line) - } - outputStream.close() - } - val exitCode = process.waitFor() - return exitCode - } - - private fun runTool(vararg command: String) { - val code = executeCommand(*command) - if (code != 0) throw KonanExternalToolFailure("The ${command[0]} command returned non-zero exit code: $code.") - } - fun linkStage() { - context.log{"# Compiler root: ${distribution.konanHome}"} - val bitcodeFiles = listOf(emitted) + libraries.map{it -> it.bitcodePaths}.flatten() diff --git a/shared/src/main/kotlin/org/jetbrains/kotlin/konan/Exceptions.kt b/shared/src/main/kotlin/org/jetbrains/kotlin/konan/Exceptions.kt new file mode 100644 index 00000000000..3b779d3a918 --- /dev/null +++ b/shared/src/main/kotlin/org/jetbrains/kotlin/konan/Exceptions.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.konan + +/** + * This is a common ancestor of all Kotlin/Native exceptions. + */ +open class KonanException(message: String = "", cause: Throwable? = null) : Exception(message, cause) + +/** + * An error occured during external tool invocation. Such as non-zero exit code. + */ +class KonanExternalToolFailure(message: String, val toolName: String, cause: Throwable? = null) : KonanException(message, cause) + diff --git a/shared/src/main/kotlin/org/jetbrains/kotlin/konan/exec/ExecuteCommand.kt b/shared/src/main/kotlin/org/jetbrains/kotlin/konan/exec/ExecuteCommand.kt index b04e2a36d16..9f5c8465a9d 100644 --- a/shared/src/main/kotlin/org/jetbrains/kotlin/konan/exec/ExecuteCommand.kt +++ b/shared/src/main/kotlin/org/jetbrains/kotlin/konan/exec/ExecuteCommand.kt @@ -18,29 +18,51 @@ package org.jetbrains.kotlin.konan.exec import java.lang.ProcessBuilder import java.lang.ProcessBuilder.Redirect +import org.jetbrains.kotlin.konan.KonanExternalToolFailure -fun executeCommand(command: List) = - executeCommand(*command.toTypedArray()) -fun executeCommand(vararg command: String): Int { - // TODO: need a verbose logger here. +open class Command(initialCommand: List) { - val builder = ProcessBuilder(command.asList()) + constructor(tool: String) : this(listOf(tool)) + constructor(vararg command: String) : this(command.toList()) + protected val command = initialCommand.toMutableList() - builder.redirectOutput(Redirect.INHERIT) - builder.redirectInput(Redirect.INHERIT) - builder.redirectError(Redirect.INHERIT) + val args: List + get() = command.drop(1) - val process = builder.start() - val exitCode = process.waitFor() - return exitCode + operator fun String.unaryPlus(): Command { + command += this + return this@Command + } + + operator fun List.unaryPlus(): Command { + command.addAll(this) + return this@Command + } + + var logger: ((() -> String)->Unit)? = null + + fun logWith(newLogger: ((() -> String)->Unit)): Command { + logger = newLogger + return this + } + + open fun runProcess(): Int { + val builder = ProcessBuilder(command) + + builder.redirectOutput(Redirect.INHERIT) + builder.redirectInput(Redirect.INHERIT) + builder.redirectError(Redirect.INHERIT) + + val process = builder.start() + val exitCode = process.waitFor() + return exitCode + } + + open fun execute() { + if (logger != null) logger!! { command.toList().joinToString(" ") } + + val code = runProcess() + if (code != 0) throw KonanExternalToolFailure("The ${command[0]} command returned non-zero exit code: $code.", command[0]) + } } - -fun runTool(command: List) = - runTool(*command.toTypedArray()) - -fun runTool(vararg command: String) { - val code = executeCommand(*command) - if (code != 0) error("The ${command[0]} command returned non-zero exit code: $code.") -} -