Further refactored external utility execution.

This commit is contained in:
Alexander Gorshenev
2018-01-11 18:34:09 +03:00
committed by alexander-gorshenev
parent 4daaf9c99f
commit e64c2f5990
5 changed files with 129 additions and 106 deletions
@@ -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()
}
@@ -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.
@@ -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<String>()
operator fun String.unaryPlus():Command {
opts += this
return this@Command
}
operator fun List<String>.unaryPlus():Command {
opts.addAll(this)
return this@Command
}
open fun execute() = runTool(*opts.toTypedArray())
fun externalLibraries(deps: List<String>) {
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<ObjectFile>, 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<String>): 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<String> = listOf(dsymutil, executable)
open fun dsymutilDryRunVerboseCommand(executable: ExecutableFile): List<String> =
@@ -253,7 +264,7 @@ internal open class WasmPlatform(distribution: Distribution)
override val useCompilerDriverAsLinker: Boolean get() = false
override fun filterStaticLibraries(binaries: List<String>)
= emptyList<String>()
= binaries.filter{it.isJavaScript}
override fun linkCommand(objectFiles: List<ObjectFile>, 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<String>, executable: String): String {
@@ -312,6 +323,12 @@ internal class LinkStage(val context: Context) {
addAll(elements.filter { !it.isEmpty() })
}
private fun runTool(command: List<String>) = runTool(*command.toTypedArray())
private fun runTool(vararg command: String) =
Command(*command)
.logWith(context::log)
.execute()
private fun llvmLto(files: List<BitcodeFile>): 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<String>) {
val absoluteToolName = "${distribution.llvmBin}/$tool"
val command = listOf(absoluteToolName) + args
runTool(*command.toTypedArray())
runTool(command)
}
private fun bitcodeToWasm(bitcodeFiles: List<BitcodeFile>): 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<String>().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()
@@ -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)
@@ -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<String>) =
executeCommand(*command.toTypedArray())
fun executeCommand(vararg command: String): Int {
// TODO: need a verbose logger here.
open class Command(initialCommand: List<String>) {
val builder = ProcessBuilder(command.asList())
constructor(tool: String) : this(listOf(tool))
constructor(vararg command: String) : this(command.toList<String>())
protected val command = initialCommand.toMutableList()
builder.redirectOutput(Redirect.INHERIT)
builder.redirectInput(Redirect.INHERIT)
builder.redirectError(Redirect.INHERIT)
val args: List<String>
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<String>.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<String>().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<String>) =
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.")
}