Merge pull request #5 from JetBrains/interop-as-dependency

Represent Interop as dependency in Gradle plugin
This commit is contained in:
SvyatoslavScherbina
2016-10-13 16:41:22 +03:00
committed by GitHub
6 changed files with 295 additions and 143 deletions
@@ -1,33 +1,25 @@
package org.jetbrains.kotlin.native.interop.gen.jvm
import kotlin_native.interop.Ref.to
import org.jetbrains.kotlin.native.interop.indexer.NativeIndex
import org.jetbrains.kotlin.native.interop.indexer.buildNativeIndex
import java.io.File
import java.lang.IllegalArgumentException
import java.util.*
import kotlin.system.exitProcess
fun main(args: Array<String>) {
val llvmInstallPath = System.getProperty("llvmInstallPath")!!
val ktGenRoot = args[0]
val nativeLibsDir = args[1]
val ktSrcRoots = args.drop(2)
val otherArgs = args.drop(2)
// TODO: remove OSX defaults.
val substitutions = mapOf(
"arch" to (System.getenv("TARGET_ARCH") ?: "x86-64"),
"os" to (System.getenv("TARGET_OS") ?: detectHost())
)
ktSrcRoots.forEach { ktSrcRoot ->
val defFiles = File(ktSrcRoot).walk().filter { it.name.endsWith(".def") }
defFiles.forEach { defFile ->
processDefFile(ktSrcRoot, defFile, ktGenRoot, nativeLibsDir, llvmInstallPath, substitutions)
}
}
processLib(ktGenRoot, nativeLibsDir, llvmInstallPath, substitutions, otherArgs)
}
private fun detectHost():String {
@@ -63,26 +55,75 @@ private fun substitute(properties: Properties, substitutions: Map<String, String
}
}
private fun processDefFile(ktSrcRoot: String, defFile: File, ktGenRoot: String, nativeLibsDir: String, llvmInstallPath: String, substitutions: Map<String, String>) {
private fun getArgPrefix(arg: String): String? {
val index = arg.indexOf(':')
if (index == -1) {
return null
} else {
return arg.substring(0, index)
}
}
private fun dropPrefix(arg: String): String? {
val index = arg.indexOf(':')
if (index == -1) {
return null
} else {
return arg.substring(index + 1)
}
}
private fun ProcessBuilder.runExpectingSuccess() {
println(this.command().joinToString(" "))
val res = this.start().waitFor()
if (res != 0) {
throw Error("Process finished with non-zero exit code: $res")
}
}
private fun Properties.getSpaceSeparated(name: String): List<String> {
return this.getProperty(name)?.split(' ') ?: emptyList()
}
private fun processLib(ktGenRoot: String,
nativeLibsDir: String,
llvmInstallPath: String,
substitutions: Map<String, String>,
additionalArgs: List<String>) {
val args = additionalArgs.groupBy ({ getArgPrefix(it)!! }, { dropPrefix(it)!! }) // TODO
val defFile = args["-def"]?.single()?.let { File(it) }
val config = Properties()
defFile.bufferedReader().use { reader ->
defFile?.bufferedReader()?.use { reader ->
config.load(reader)
}
substitute(config, substitutions)
val headerFiles = config.getProperty("headers").split(' ')
val compilerOpts = config.getProperty("compilerOpts").split(' ')
val compiler = config.getProperty("compiler")
val libName = config.getProperty("libName")
val linkerOpts = config.getProperty("linkerOpts").split(' ').toTypedArray()
val linker = config.getProperty("linker")
val excludedFunctions = config.getProperty("excludedFunctions")?.split(' ')?.toSet() ?: emptySet()
val additionalHeaders = args["-h"].orEmpty()
val additionalCompilerOpts = args["-copt"].orEmpty()
val additionalLinkerOpts = args["-lopt"].orEmpty()
val headerFiles = config.getSpaceSeparated("headers") + additionalHeaders
val compilerOpts = config.getSpaceSeparated("compilerOpts") + additionalCompilerOpts
val compiler = config.getProperty("compiler") ?: "clang"
val linkerOpts = config.getSpaceSeparated("linkerOpts").toTypedArray() + additionalLinkerOpts
val linker = config.getProperty("linker") ?: "clang"
val excludedFunctions = config.getSpaceSeparated("excludedFunctions").toSet()
val defFileRelative = defFile.relativeTo(File(ktSrcRoot))
val outKtFile = File(ktGenRoot, defFileRelative.toString().substringBeforeLast(".def") + ".kt")
val outKtPkg = defFileRelative.parentFile?.path?.replace(File.separatorChar, '.') ?: ""
val fqParts = args["-pkg"]?.singleOrNull()?.let {
it.split('.')
} ?: defFile!!.name.split('.').reversed().drop(1)
val outKtFileName = fqParts.last() + ".kt"
val outKtPkg = fqParts.joinToString(".")
val outKtFileRelative = (fqParts + outKtFileName).joinToString("/")
val outKtFile = File(ktGenRoot, outKtFileRelative)
val libName = fqParts.joinToString("") + "stubs"
val nativeIndex = buildNativeIndex(headerFiles, compilerOpts)
@@ -113,16 +154,12 @@ private fun processDefFile(ktSrcRoot: String, defFile: File, ktGenRoot: String,
*compilerArgsForJniIncludes,
"-c", outCFile.path, "-o", outOFile.path)
println(compilerCmd.joinToString(" "))
val workDir = defFile?.parentFile ?: File(System.getProperty("java.io.tmpdir"))
val compilerRes = ProcessBuilder(*compilerCmd)
ProcessBuilder(*compilerCmd)
.directory(workDir)
.inheritIO()
.start()
.waitFor()
if (compilerRes != 0) {
exitProcess(compilerRes)
}
.runExpectingSuccess()
File(nativeLibsDir).mkdirs()
@@ -131,16 +168,10 @@ private fun processDefFile(ktSrcRoot: String, defFile: File, ktGenRoot: String,
val linkerCmd = arrayOf("$llvmInstallPath/bin/$linker", *linkerOpts, outOFile.path, "-shared", "-o", outLib,
"-Wl,-flat_namespace,-undefined,dynamic_lookup")
println(linkerCmd.joinToString(" "))
val linkerRes = ProcessBuilder(*linkerCmd)
ProcessBuilder(*linkerCmd)
.directory(workDir)
.inheritIO()
.start()
.waitFor()
if (linkerRes != 0) {
exitProcess(linkerRes)
}
.runExpectingSuccess()
outCFile.delete()
outOFile.delete()
+10 -8
View File
@@ -16,16 +16,18 @@ repositories {
mavenCentral()
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
kotlinNativeInterop {
llvm {
defFile '../backend.native/llvm.def'
compilerOpts "-I$llvmInstallPath/include"
linkerOpts "-L$llvmInstallPath/lib"
}
}
genInteropStubs {
// these variables will be passed to native toolchain used by stub generator:
environment 'CPATH' : "$llvmInstallPath/include"
environment 'LIBRARY_PATH' : "$llvmInstallPath/lib"
//environment 'LD_DEBUG' : "all"
environment 'LD_LIBRARY_PATH' : "$llvmInstallPath/lib"
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compile kotlinNativeInterop['llvm']
}
mainClassName = 'MainKt'
@@ -1,41 +0,0 @@
libName = llvmbridge
headers = llvm-c/Core.h llvm-c/ExecutionEngine.h llvm-c/Target.h llvm-c/Analysis.h llvm-c/BitWriter.h
compiler = clang
compilerOpts = -std=c99 -fPIC \
-Wall -W -Wno-unused-parameter -Wwrite-strings -Wmissing-field-initializers \
-pedantic -Wno-long-long -Wcovered-switch-default -Wdelete-non-virtual-dtor \
-DNDEBUG -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS
linker = clang++
linkerOpts = -Wall
linkerOpts.osx = -stdlib=libc++ -fPIC -Wl,-search_paths_first \
-Wl,-headerpad_max_install_names -fvisibility-inlines-hidden \
-Wall -W -Wno-unused-parameter -Wwrite-strings -Wcast-qual -Wmissing-field-initializers \
-pedantic -Wno-long-long -Wcovered-switch-default -Wnon-virtual-dtor -Wdelete-non-virtual-dtor \
-std=c++11 \
-DNDEBUG -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS \
-lLLVMX86Disassembler -lLLVMX86AsmParser -lLLVMX86CodeGen -lLLVMSelectionDAG -lLLVMAsmPrinter \
-lLLVMX86Desc -lLLVMMCDisassembler -lLLVMX86Info -lLLVMX86AsmPrinter -lLLVMX86Utils -lLLVMInterpreter \
-lLLVMCodeGen -lLLVMScalarOpts -lLLVMInstCombine -lLLVMInstrumentation -lLLVMProfileData -lLLVMTransformUtils \
-lLLVMBitWriter -lLLVMExecutionEngine -lLLVMTarget -lLLVMAnalysis -lLLVMRuntimeDyld -lLLVMObject -lLLVMMCParser \
-lLLVMBitReader -lLLVMMC -lLLVMCore -lLLVMSupport
linkerOpts.linux = -Wl,-z,noexecstack -fvisibility=default \
-Wl,--whole-archive \
-lLLVMX86Disassembler -lLLVMX86AsmParser -lLLVMX86CodeGen \
-lLLVMSelectionDAG -lLLVMAsmPrinter -lLLVMX86Desc -lLLVMMCDisassembler \
-lLLVMX86Info -lLLVMX86AsmPrinter -lLLVMX86Utils -lLLVMInterpreter \
-lLLVMCodeGen -lLLVMScalarOpts -lLLVMInstCombine -lLLVMInstrumentation \
-lLLVMProfileData -lLLVMTransformUtils -lLLVMBitWriter \
-lLLVMExecutionEngine -lLLVMTarget -lLLVMAnalysis -lLLVMRuntimeDyld \
-lLLVMObject -lLLVMMCParser -lLLVMBitReader -lLLVMMC -lLLVMCore \
-lLLVMSupport \
-Wl,--no-whole-archive \
-lrt -ldl -ltinfo -lpthread -lz -lm -lffi
+13 -6
View File
@@ -33,8 +33,17 @@ sourceSets {
}
}
kotlinNativeInterop {
llvm {
defFile 'llvm.def'
compilerOpts "-I$llvmInstallPath/include"
linkerOpts "-L$llvmInstallPath/lib"
}
}
dependencies {
compilerCompile deps
compilerCompile kotlinNativeInterop['llvm']
cli_bcCompile deps
cli_bcCompile sourceSets.compiler.output
@@ -42,6 +51,10 @@ dependencies {
bc_frontendCompile deps
}
configurations {
cli_bcRuntime.extendsFrom compilerRuntime
}
build.dependsOn 'compilerClasses','cli_bcClasses','bc_frontendClasses'
@@ -73,9 +86,3 @@ task jars {
repositories {
mavenCentral()
}
genCompilerInteropStubs {
environment 'CPATH' : "$llvmInstallPath/include"
environment 'LIBRARY_PATH' : "$llvmInstallPath/lib"
environment 'LD_LIBRARY_PATH' : "$llvmInstallPath/lib"
}
@@ -1,10 +1,5 @@
libName = llvmbridge
headers = llvm-c/Core.h llvm-c/ExecutionEngine.h llvm-c/Target.h llvm-c/Analysis.h llvm-c/BitWriter.h llvm-c/BitReader.h
compiler = clang
compilerOpts = -std=c99 -fPIC \
-Wall -W -Wno-unused-parameter -Wwrite-strings -Wmissing-field-initializers \
-pedantic -Wno-long-long -Wcovered-switch-default -Wdelete-non-virtual-dtor \
@@ -1,13 +1,214 @@
package org.jetbrains.kotlin
import org.gradle.api.Named
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.file.FileCollection
import org.gradle.api.internal.AbstractNamedDomainObjectContainer
import org.gradle.api.internal.file.AbstractFileCollection
import org.gradle.api.tasks.JavaExec
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.TaskDependency
import org.gradle.internal.reflect.Instantiator
class NamedNativeInteropConfig extends AbstractFileCollection implements Named {
private final Project project
final String name
private final SourceSet interopStubs
private final JavaExec genTask
private String defFile
private String pkg;
private List<String> compilerOpts = []
private FileCollection headers;
private List<String> linkerOpts = []
private FileCollection linkFiles;
private List<String> linkTasks = []
void defFile(String value) {
defFile = value
genTask.inputs.file(project.file(defFile))
}
void pkg(String value) {
pkg = value
}
void compilerOpts(String... values) {
compilerOpts.addAll(values)
}
void headers(FileCollection files) {
dependsOnFiles(files)
headers = headers + files
}
void linkerOpts(String... values) {
linkerOpts.addAll(values)
}
void dependsOn(Object... deps) {
// TODO: add all files to inputs
genTask.dependsOn(deps)
}
private void dependsOnFiles(FileCollection files) {
dependsOn(files)
genTask.inputs.files(files)
}
void link(FileCollection files) {
linkFiles = linkFiles + files
dependsOnFiles(files)
}
void linkOutputs(Task task) {
linkOutputs(task.name)
}
void linkOutputs(String task) {
linkTasks += task
dependsOn(task)
final Project prj;
final String taskName;
int index = task.lastIndexOf(':')
if (index != -1) {
prj = project.project(task.substring(0, index))
taskName = task.substring(index + 1)
} else {
prj = project
taskName = task
}
prj.tasks.matching { it.name == taskName }.all { // TODO: it is a hack
this.dependsOnFiles(it.outputs.files)
}
}
void includeDirs(String... values) {
compilerOpts.addAll(values.collect {"-I$it"})
}
private File getNativeLibsDir() {
return new File(project.buildDir, "nativelibs")
}
private File getGeneratedSrcDir() {
return new File(project.buildDir, "nativeInteropStubs/$name/kotlin")
}
NamedNativeInteropConfig(Project project, String name) {
this.name = name
this.project = project
this.headers = project.files()
this.linkFiles = project.files()
interopStubs = project.sourceSets.create(name + "InteropStubs")
genTask = project.task(interopStubs.getTaskName("gen", ""), type: JavaExec)
this.configure()
}
private void configure() {
project.tasks.getByName(interopStubs.getTaskName("compile", "Kotlin")) {
dependsOn genTask
}
interopStubs.kotlin.srcDirs generatedSrcDir
project.dependencies {
add interopStubs.getCompileConfigurationName(), project(path: ':Interop:Runtime')
}
genTask.configure {
classpath = project.configurations.interopStubGenerator
main = "org.jetbrains.kotlin.native.interop.gen.jvm.MainKt"
jvmArgs '-ea'
systemProperties "java.library.path" : project.files(
new File(project.findProject(":Interop:Indexer").buildDir, "nativelibs"),
new File(project.findProject(":Interop:Runtime").buildDir, "nativelibs")
).asPath
systemProperties "llvmInstallPath" : project.llvmInstallPath
environment "LIBCLANG_DISABLE_CRASH_RECOVERY": "1"
environment "DYLD_LIBRARY_PATH": "${project.llvmInstallPath}/lib"
environment "LD_LIBRARY_PATH": "${project.llvmInstallPath}/lib"
outputs.dir generatedSrcDir
outputs.dir nativeLibsDir
// defer as much as possible
doFirst {
linkTasks.each {
linkerOpts += project.tasks.getByPath(it).outputs.files.files
}
linkerOpts += linkFiles.files
args = [generatedSrcDir, nativeLibsDir]
if (defFile != null) {
args "-def:" + project.file(defFile)
}
if (pkg != null) {
args "-pkg:" + pkg
}
args compilerOpts.collect { "-copt:$it" }
args linkerOpts.collect { "-lopt:$it" }
headers.files.each {
args "-h:$it"
}
}
}
}
@Override
String getDisplayName() {
return "Native interop config $name"
}
@Override
Set<File> getFiles() {
return interopStubs.output.getFiles() +
interopStubs.compileClasspath.files // TODO: workaround to add Interop:Runtime
}
@Override
TaskDependency getBuildDependencies() {
return interopStubs.output.getBuildDependencies()
}
}
class NativeInteropExtension extends AbstractNamedDomainObjectContainer<NamedNativeInteropConfig> {
private final Project project
protected NativeInteropExtension(Project project) {
super(NamedNativeInteropConfig, project.gradle.services.get(Instantiator))
this.project = project
}
@Override
protected NamedNativeInteropConfig doCreate(String name) {
return new NamedNativeInteropConfig(project, name)
}
}
class NativeInteropPlugin implements Plugin<Project> {
@Override
void apply(Project prj) {
prj.extensions.add("kotlinNativeInterop", new NativeInteropExtension(prj))
def runtimeNativeLibsDir = new File(prj.findProject(':Interop:Runtime').buildDir, 'nativelibs')
def nativeLibsDir = new File(prj.buildDir, "nativelibs")
@@ -20,49 +221,6 @@ class NativeInteropPlugin implements Plugin<Project> {
interopStubGenerator project(path: ":Interop:StubGenerator")
}
prj.sourceSets.all { sourceSet ->
def generatedSrcDir = new File(prj.buildDir, "nativeInteropStubs/${sourceSet.name}/kotlin")
prj.dependencies {
add sourceSet.getCompileConfigurationName(), project(path: ':Interop:Runtime')
}
def genStubsTask = prj.task(sourceSet.getTaskName("gen", "interopStubs"), type: JavaExec) {
classpath = prj.configurations.interopStubGenerator
main = "org.jetbrains.kotlin.native.interop.gen.jvm.MainKt"
jvmArgs '-ea'
systemProperties "java.library.path" : prj.files(
new File(prj.findProject(":Interop:Indexer").buildDir, "nativelibs"),
runtimeNativeLibsDir
).asPath
systemProperties "llvmInstallPath" : prj.llvmInstallPath
environment "LIBCLANG_DISABLE_CRASH_RECOVERY": "1"
environment "DYLD_LIBRARY_PATH": "${prj.llvmInstallPath}/lib"
outputs.dir generatedSrcDir
outputs.dir nativeLibsDir
args = [generatedSrcDir, nativeLibsDir]
prj.afterEvaluate { // FIXME: it is a hack
sourceSet.kotlin.srcDirs.each { srcDir ->
if (srcDir != generatedSrcDir) {
args srcDir
inputs.files prj.fileTree(srcDir.path).include('**/*.def')
}
}
}
}
sourceSet.kotlin.srcDirs generatedSrcDir
prj.tasks.getByName(sourceSet.getTaskName("compile", "Kotlin")) {
dependsOn genStubsTask
}
}
// FIXME: choose tasks more wisely
prj.tasks.withType(JavaExec) {
if (!name.endsWith("InteropStubs")) {