tools: Add dependecy download helpers

This patch adds 2 helpers to download dependencies for compiler and
stub generator during their execution.

They take list of external dependencies as a parameter and directory
where they must be located. Helpers check if these dependencies exist
in the given directory and download not existing ones.
This commit is contained in:
Ilya Matveev
2017-03-21 15:43:13 +07:00
committed by ilmat192
parent 4a716fbf30
commit fbc14becfc
20 changed files with 342 additions and 21 deletions
+6
View File
@@ -43,3 +43,9 @@ proto/compiler/google/src/google/protobuf/compiler/kotlin/protoc
# translator auto generated artifacts
kotstd/ll
# directory for manual debug runs
run-debug/**
!run-debug/interop
!run-debug/konanc
@@ -7,6 +7,7 @@ import org.jetbrains.kotlin.native.interop.indexer.buildNativeIndex
import java.io.File
import java.lang.IllegalArgumentException
import java.util.*
import kotlin.reflect.KFunction
fun main(args: Array<String>) {
val konanHome = System.getProperty("konan.home")!!
@@ -116,7 +117,20 @@ private fun runCmd(command: Array<String>, workDir: File, verbose: Boolean = fal
.runExpectingSuccess()
}
private fun Properties.defaultCompilerOpts(target: String, dependencies: String): List<String> {
private fun maybeExecuteHelper(dependenciesRoot: String, propertiesFile: String, dependencies: List<String>) {
try {
val kClass = Class.forName("org.jetbrains.kotlin.konan.InteropHelper0").kotlin
val ctor = kClass.constructors.single() as KFunction<Runnable>
val result = ctor.call(dependenciesRoot, propertiesFile, dependencies)
result.run()
} catch (notFound: ClassNotFoundException) {
// Just ignore, no helper.
} catch (e: Throwable) {
throw IllegalStateException("Cannot download dependencies.", e)
}
}
private fun Properties.defaultCompilerOpts(target: String, dependencies: String, konanFileName: String): List<String> {
val hostSysRootDir = this.getOsSpecific("sysRoot", target)!!
val hostSysRoot = "$dependencies/$hostSysRootDir"
@@ -127,6 +141,12 @@ private fun Properties.defaultCompilerOpts(target: String, dependencies: String)
val llvmHome = "$dependencies/$llvmHomeDir"
val llvmVersion = this.getProperty("llvmVersion")!!
val dependencyList = mutableListOf<String>(sysRoot, targetSysRoot, llvmHome)
if (target == "linux") {
dependencyList.add("$dependencies/${getOsSpecific("gccToolChain", target)!!}")
}
maybeExecuteHelper(dependencies, konanFileName, dependencyList)
// StubGenerator passes the arguments to libclang which
// works not exactly the same way as the clang binary and
// (in particular) uses different default header search path.
@@ -242,7 +262,7 @@ private fun processLib(konanHome: String,
val generateShims = args["-shims"].isTrue()
val verbose = args["-verbose"].isTrue()
val defaultOpts = konanProperties.defaultCompilerOpts(target, dependencies)
val defaultOpts = konanProperties.defaultCompilerOpts(target, dependencies, konanFileName)
val headerFiles = config.getSpaceSeparated("headers") + additionalHeaders
val compilerOpts =
config.getSpaceSeparated("compilerOpts") +
+2
View File
@@ -48,6 +48,8 @@ between threads are allowed.
./dist/bin/kotlinc <some_file>.kt <dir_with_kt_files> -o <executable>.kexe
During the first run it will download all external dependencies such as LLVM.
One may use `'-h'` flag to `kotlinc` to see available flags.
For documentation on C interoperability stubs see INTEROP.md.
@@ -16,17 +16,15 @@ import kotlin.reflect.KFunction
private fun maybeExecuteHelper(configuration: CompilerConfiguration) {
try {
val kClass = Class.forName("org.jetbrains.kotlin.konan.Helper0").kotlin
val kClass = Class.forName("org.jetbrains.kotlin.konan.CompilerHelper0").kotlin
val ctor = kClass.constructors.single() as KFunction<Runnable>
val distribution = Distribution(configuration)
val target = TargetManager(configuration)
val result = ctor.call(
distribution.konanHome, TargetManager.host, target.current)
val result = ctor.call(distribution)
result.run()
} catch (notFound: ClassNotFoundException) {
// Just ignore, no helper.
} catch (e: Throwable) {
println(e.toString())
throw IllegalStateException("Cannot download dependencies.", e)
}
}
@@ -35,7 +35,13 @@ class Distribution(val config: CompilerConfiguration) {
val llvmHome = "$dependencies/${properties.propertyString("llvmHome.$suffix")}"
val sysRoot = "$dependencies/${properties.propertyString("sysRoot.$suffix")}"
val libGcc = "$dependencies/${properties.propertyString("libGcc.$suffix")}"
val libGcc = "$dependencies/${properties.propertyString("libGcc.$suffix")}"
val targetSysRoot = if (properties.hasProperty("targetSysRoot.$suffix")) {
"$dependencies/${properties.propertyString("targetSysRoot.$suffix")}"
} else {
sysRoot
}
val llvmBin = "$llvmHome/bin"
val llvmLib = "$llvmHome/lib"
@@ -4,12 +4,24 @@
//
package org.jetbrains.kotlin.konan
import org.jetbrains.kotlin.backend.konan.KonanTarget
import org.jetbrains.kotlin.backend.konan.Distribution
import org.jetbrains.kotlin.backend.konan.TargetManager
// Name it Helper0 so that it get loaded.
class Helper0Disabled(
val konanHome: String, val host: KonanTarget, val target: KonanTarget) : Runnable {
// Name it CompilerHelper0 so that it get loaded.
class CompilerHelper0Disabled(val distribution: Distribution) : Runnable {
override fun run() {
println("Running helper with $konanHome $host $target")
println("Running helper with ${distribution.konanHome} ${distribution.target} ${TargetManager.host}")
}
}
//
// Example startup helper for Kotlin N stub generator. Compile to JAR and add it to
// Kotlin N classpath - it will get loaded and executed automatically.
//
// Name it InteropHelper0 so that it get loaded.
class InteropHelper0Disabled(val dependenciesRoot: String, val dependencies: List<String>) : Runnable {
override fun run() {
println("Running helper with $dependenciesRoot $dependencies")
}
}
@@ -15,10 +15,13 @@ public class KonanProperties(val propertyFile: String) {
}
fun propertyString(key: String): String? = properties.getProperty(key)
fun propertyString(key: String, default: String) : String = properties.getProperty(key, default)
fun propertyList(key: String): List<String> {
val value = properties.getProperty(key)
return value?.split(' ') ?: listOf<String>()
}
fun hasProperty(key: String): Boolean = properties.getProperty(key) != null
}
+1
View File
@@ -1,6 +1,7 @@
// TODO: Do we need a $variable substitution mechanism here?
llvmVersion = 3.9.0
dependenciesUrl = https://jetbrains.bintray.com/kotlin-native-dependencies
// macbook
arch.osx = x86_64
+2 -2
View File
@@ -25,8 +25,8 @@ dependencies {
}
}
def testOutputRoot = rootProject.file("test.output").absolutePath
def externalTestsDir = project.file("external")
ext.testOutputRoot = rootProject.file("test.output").absolutePath
ext.externalTestsDir = project.file("external")
externalTestsDir.mkdirs()
allprojects {
+6
View File
@@ -125,6 +125,7 @@ class PlatformInfo {
task dist_compiler(type: Copy) {
dependsOn ':backend.native:jars'
dependsOn ':tools:helpers:jar'
destinationDir file('dist')
@@ -174,6 +175,11 @@ task dist_compiler(type: Copy) {
includeEmptyDirs = false
}
// TODO: check if we use native libraries copied to dist (see above) or not.
from(project(':tools:helpers').file('build/libs')) {
into('konan/lib')
}
doLast {
// MacOS System Integrity Protection strips DYLD_LIBRARY_PATH from
// environment of all the binaries residing in /bin /usr/bin etc.
@@ -12,7 +12,7 @@ abstract class KonanTest extends JavaExec {
def backendNative = project.project(":backend.native")
def runtimeProject = project.project(":runtime")
def dist = project.rootProject.file("dist")
def dependencies = project.findProject(":dependencies").file("all")
def dependenciesDir = project.findProject(":dependencies").file("all")
def runtimeBc = new File("${dist.canonicalPath}/lib/runtime.bc").absolutePath
def launcherBc = new File("${dist.canonicalPath}/lib/launcher.bc").absolutePath
def startKtBc = new File("${dist.canonicalPath}/lib/start.kt.bc").absolutePath
@@ -59,7 +59,7 @@ abstract class KonanTest extends JavaExec {
void setJvmArgs(Iterable<?> arguments) {
super.setJvmArgs(arguments +
"-Dkonan.home=${dist.canonicalPath}" +
"-Dkonan.dependencies=${dependencies.canonicalPat}",
"-Dkonan.dependencies=${dependenciesDir.canonicalPath}" +
"-Djava.library.path=${dist.canonicalPath}/konan/nativelib")
}
+2 -2
View File
@@ -40,7 +40,6 @@ findHome() {
KONAN_HOME="$(findHome)"
NATIVE_LIB="${KONAN_HOME}/konan/nativelib"
DEPENDENCIES="${KONAN_HOME}/../dependencies/all"
JAVA_OPTS="-ea \
-Djava.library.path=${NATIVE_LIB} \
-Dkonan.home=${KONAN_HOME}"
@@ -49,7 +48,8 @@ STUB_GENERATOR_JAR="${KONAN_HOME}/konan/lib/StubGenerator.jar"
KOTLIN_JAR="${KONAN_HOME}/konan/lib/kotlin-compiler.jar"
INTEROP_INDEXER_JAR="${KONAN_HOME}/konan/lib/Indexer.jar"
INTEROP_RUNTIME_JAR="${KONAN_HOME}/konan/lib/Runtime.jar"
INTEROP_CLASSPATH="$STUB_GENERATOR_JAR:$KOTLIN_JAR:$INTEROP_INDEXER_JAR:$INTEROP_RUNTIME_JAR"
HELPERS_JAR="${KONAN_HOME}/konan/lib/helpers.jar"
INTEROP_CLASSPATH="$STUB_GENERATOR_JAR:$KOTLIN_JAR:$INTEROP_INDEXER_JAR:$INTEROP_RUNTIME_JAR:$HELPERS_JAR"
INTEROP_TOOL=org.jetbrains.kotlin.native.interop.gen.jvm.MainKt
FLAVOR_ARG=-flavor:native
+3 -2
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/usr/bin/env bash
if [ -z "$JAVACMD" -a -n "$JAVA_HOME" -a -x "$JAVA_HOME/bin/java" ]; then
JAVACMD="$JAVA_HOME/bin/java"
@@ -52,8 +52,9 @@ KONAN_HOME="$(findHome)"
KONAN_JAR="${KONAN_HOME}/konan/lib/backend.native.jar"
KOTLIN_JAR="${KONAN_HOME}/konan/lib/kotlin-compiler.jar"
INTEROP_JAR="${KONAN_HOME}/konan/lib/Runtime.jar"
HELPERS_JAR="${KONAN_HOME}/konan/lib/helpers.jar"
NATIVE_LIB="${KONAN_HOME}/konan/nativelib"
KONAN_CLASSPATH="$KOTLIN_JAR:$INTEROP_JAR:$KONAN_JAR"
KONAN_CLASSPATH="$KOTLIN_JAR:$INTEROP_JAR:$KONAN_JAR:$HELPERS_JAR"
KONAN_COMPILER=org.jetbrains.kotlin.cli.bc.K2NativeKt
JAVA_OPTS=-ea
+1
View File
@@ -7,3 +7,4 @@ include ':backend.native'
include ':runtime'
include ':common'
include ':backend.native:tests'
include ':tools:helpers'
+128
View File
@@ -0,0 +1,128 @@
import org.jetbrains.kotlin.NativeInteropPlugin
import org.jetbrains.kotlin.RunInteropKonanTest
import org.jetbrains.kotlin.RunKonanTest
import org.jetbrains.kotlin.TestFailedException
import java.util.concurrent.atomic.AtomicInteger
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
repositories {
maven {
url 'http://oss.sonatype.org/content/repositories/snapshots'
}
}
apply plugin: 'kotlin'
apply plugin: NativeInteropPlugin
configurations {
cli_bc.extendsFrom compile
}
dependencies {
compile project(path: ':backend.native', configuration: 'cli_bc')
cli_bc jar.outputs.files
}
sourceSets {
testOutputLocal {
output.dir(rootProject.file("${findProject(":backend.native:tests").testOutputRoot}/tools/helpers"))
}
}
def testDependencies = project.file("${sourceSets.testOutputLocal.output.dirs.singleFile.absolutePath}/dependencies")
void prepareDependenciesDir(File testDependencies) {
project.delete testDependencies
testDependencies.mkdirs()
// We don't download big clang dependency in this test - only sysroots.
project.copy {
from project.findProject(":dependencies").file("all")
into testDependencies
include "clang*.tar.gz/**"
}
}
kotlinNativeInterop {
sysstat {
pkg 'sysstat'
headers 'sys/stat.h'
flavor 'native'
}
}
task testInteropHelper(type: RunInteropKonanTest) {
dependsOn jar
dependsOn ':dist'
dependenciesDir = testDependencies
goldValue = "0\n0\n"
source = "testData/interop.kt"
interop = 'sysstat'
doFirst {
prepareDependenciesDir(testDependencies)
}
}
task testCompilerHelper(type: RunKonanTest) {
dependenciesDir = testDependencies
source = "testData/main.kt"
doFirst {
prepareDependenciesDir(testDependencies)
}
}
// Simple test for parallel compiler execution
task testParallel(type: DefaultTask) {
for (int i=0; i<4; i++) {
task("runParallel$i", type: RunKonanTest) {
dependenciesDir = testDependencies
source = "testData/main.kt"
}
}
doFirst {
prepareDependenciesDir(testDependencies)
}
doLast {
List<Thread> threads = new ArrayList<>()
def downloadCnt = new AtomicInteger(0)
tasks.withType(RunKonanTest).matching { it.name.startsWith("runParallel") }.each { task ->
threads.add(Thread.start {
task.executeTest()
// The helper prints messages when it is downloading.
// We check that there is only one downloading using compilation logs.
if (file("${task.buildExePath()}.compilation.log").text.contains("Download dependency")) {
downloadCnt.incrementAndGet()
}
})
}
threads.each { it.join() }
if (downloadCnt.intValue() != 1) {
throw new TestFailedException("Actual downloads: ${downloadCnt.intValue()}, expected: 1")
}
}
}
task testHelpers {
dependsOn testInteropHelper
dependsOn testCompilerHelper
dependsOn testParallel
doLast {
delete testDependencies
}
}
@@ -0,0 +1,22 @@
package org.jetbrains.kotlin.konan
import org.jetbrains.kotlin.backend.konan.Distribution
import org.jetbrains.kotlin.backend.konan.KonanTarget
import org.jetbrains.kotlin.backend.konan.TargetManager
import java.io.File
class CompilerHelper0(val dist: Distribution): Runnable {
override fun run() {
val dependencies = mutableListOf<String>()
dependencies.add(dist.sysRoot)
dependencies.add(dist.llvmHome)
if (dist.sysRoot != dist.targetSysRoot) {
dependencies.add(dist.targetSysRoot)
}
if (TargetManager.host == KonanTarget.LINUX) {
dependencies.add(dist.libGcc)
}
DependencyDownloader(File(dist.dependencies), dist.properties.properties, dependencies).run()
}
}
@@ -0,0 +1,85 @@
package org.jetbrains.kotlin.konan
import java.io.File
import java.io.RandomAccessFile
import java.net.URL
import java.nio.file.*
import java.util.*
// TODO: Try to use some dependency management system (Ivy?)
// TODO: Show % and size during downloading
class DependencyDownloader(dependenciesRoot: File, val properties: Properties, val dependencies: List<String>) {
val dependenciesRoot = dependenciesRoot.apply { mkdirs() }
val lockFile = File(dependenciesRoot, ".lock").apply { createNewFile() }
val listFile = File(dependenciesRoot, ".downloaded")
val dependenciesUrl: String =
properties.getProperty("dependenciesUrl", "https://jetbrains.bintray.com/kotlin-native-dependencies")
var isInfoShown = false
private fun File.containsLine(line: String): Boolean {
if (!exists()) {
return false
}
var result = false
listFile.forEachLine {
if (!result && it == line) {
result = true
}
}
return result
}
private fun processDependency(path: String) {
val depDir = File(path)
val depName = depDir.name
val inListFile = listFile.containsLine(depName)
if (inListFile && depDir.exists()) {
return
}
if (!isInfoShown) {
println("Downloading native dependencies (LLVM, sysroot etc). This is one-time action performing only for the first run of the compiler.")
isInfoShown = true
}
val downloaded = download(depName)
println("Extract dependency: $downloaded -> $depDir")
extract(downloaded)
if (!inListFile) {
listFile.appendText("$depName\n")
}
}
private fun extract(tarGz: File) {
val tarProcess = ProcessBuilder().apply {
command("tar", "-x", "-f", "${tarGz.canonicalPath}")
directory(tarGz.parentFile)
}.start()
tarProcess.waitFor()
if (tarProcess.exitValue() != 0) {
throw RuntimeException("Cannot extract archive with dependency: ${tarGz.canonicalPath}")
}
}
private fun download(dependencyName: String): File {
val to = File(dependenciesRoot.canonicalPath, "$dependencyName.tar.gz")
if (!to.exists()) {
val from = URL("$dependenciesUrl/$dependencyName.tar.gz")
println("Download dependency: $dependencyName from $from")
from.openStream().use {
Files.copy(it, Paths.get(to.toURI()), StandardCopyOption.REPLACE_EXISTING)
}
}
return to
}
fun run() {
val systemLock = RandomAccessFile(lockFile, "rw").channel.lock()
dependencies.forEach {
processDependency(it)
}
systemLock.release()
}
}
@@ -0,0 +1,18 @@
package org.jetbrains.kotlin.konan
import org.jetbrains.kotlin.backend.konan.KonanProperties
import java.io.File
import java.util.*
class InteropHelper0(
val dependenciesRoot: String,
val propertiesFile: String,
val dependencies: List<String>): Runnable {
override fun run() =
DependencyDownloader(
File(dependenciesRoot),
Properties().apply { load(File(propertiesFile).inputStream()) },
dependencies
).run()
}
+9
View File
@@ -0,0 +1,9 @@
import sysstat.*
import kotlinx.cinterop.*
fun main(args: Array<String>) {
val statBuf = nativeHeap.alloc<statStruct>()
val res = stat("/", statBuf.ptr)
println(res)
println(statBuf.st_uid.value)
}
+3
View File
@@ -0,0 +1,3 @@
fun main(args: Array<String>) {
println(42)
}