diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt index c81260852b4..280be77d483 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt @@ -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) { 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) { +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 { + return this.getProperty(name)?.split(' ') ?: emptyList() +} + +private fun processLib(ktGenRoot: String, + nativeLibsDir: String, + llvmInstallPath: String, + substitutions: Map, + additionalArgs: List) { + + 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() diff --git a/InteropExample/build.gradle b/InteropExample/build.gradle index c6e3ce27cc8..74a6f498b63 100644 --- a/InteropExample/build.gradle +++ b/InteropExample/build.gradle @@ -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' diff --git a/InteropExample/src/main/kotlin/llvm/LLVM.def b/InteropExample/src/main/kotlin/llvm/LLVM.def deleted file mode 100644 index 504a298cddc..00000000000 --- a/InteropExample/src/main/kotlin/llvm/LLVM.def +++ /dev/null @@ -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 diff --git a/backend.native/build.gradle b/backend.native/build.gradle index 67462f210fc..211e0fba13b 100644 --- a/backend.native/build.gradle +++ b/backend.native/build.gradle @@ -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" -} diff --git a/backend.native/compiler/ir/backend.native/src/llvm/LLVM.def b/backend.native/llvm.def similarity index 98% rename from backend.native/compiler/ir/backend.native/src/llvm/LLVM.def rename to backend.native/llvm.def index b028ab00de8..bcf757b61c5 100644 --- a/backend.native/compiler/ir/backend.native/src/llvm/LLVM.def +++ b/backend.native/llvm.def @@ -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 \ diff --git a/buildSrc/src/main/groovy/org/jetbrains/kotlin/NativeInteropPlugin.groovy b/buildSrc/src/main/groovy/org/jetbrains/kotlin/NativeInteropPlugin.groovy index 88321f4c474..1465a7964e7 100644 --- a/buildSrc/src/main/groovy/org/jetbrains/kotlin/NativeInteropPlugin.groovy +++ b/buildSrc/src/main/groovy/org/jetbrains/kotlin/NativeInteropPlugin.groovy @@ -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 compilerOpts = [] + private FileCollection headers; + private List linkerOpts = [] + private FileCollection linkFiles; + private List 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 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 { + + 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 { @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 { 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")) {