diff --git a/buildSrc/src/main/kotlin/HmppConfigure.kt b/buildSrc/src/main/kotlin/HmppConfigure.kt index 87e64ecc8..5d98d0565 100644 --- a/buildSrc/src/main/kotlin/HmppConfigure.kt +++ b/buildSrc/src/main/kotlin/HmppConfigure.kt @@ -16,6 +16,9 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.MAIN_COMPI import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.TEST_COMPILATION_NAME import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeCompilation +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget +import java.io.File /* * Copyright 2019-2022 Mamoe Technologies and contributors. @@ -60,16 +63,18 @@ fun Project.configureHMPPJvm() { val nativeMainSets = mutableListOf<KotlinSourceSet>() val nativeTestSets = mutableListOf<KotlinSourceSet>() + val nativeTargets = mutableListOf<KotlinNativeTarget>() if (ideaActive) { - when { + val target = when { Os.isFamily(Os.FAMILY_MAC) -> if (Os.isArch("aarch64")) macosArm64("native") else macosX64("native") Os.isFamily(Os.FAMILY_WINDOWS) -> mingwX64("native") else -> linuxX64("native") } + nativeTargets.add(target) } else { // 1.6.0 - val nativeTargets: List<String> = arrayOf( + val nativeTargetNames: List<String> = arrayOf( // serialization doesn't support those commented targets // "androidNativeArm32, androidNativeArm64, androidNativeX86, androidNativeX64", "iosArm32, iosArm64, iosX64, iosSimulatorArm64", @@ -80,11 +85,12 @@ fun Project.configureHMPPJvm() { "mingwX64", // "wasm32" // linuxArm32Hfp, mingwX86 ).flatMap { it.split(",") }.map { it.trim() } - presets.filter { it.name in nativeTargets } + presets.filter { it.name in nativeTargetNames } .forEach { preset -> - val target = targetFromPreset(preset, preset.name) + val target = targetFromPreset(preset, preset.name) as KotlinNativeTarget nativeMainSets.add(target.compilations[MAIN_COMPILATION_NAME].kotlinSourceSets.first()) nativeTestSets.add(target.compilations[TEST_COMPILATION_NAME].kotlinSourceSets.first()) + nativeTargets.add(target) } if (!ideaActive) { @@ -98,31 +104,8 @@ fun Project.configureHMPPJvm() { } } -// nativeTarget.apply { -// val myrust by compilations.getByName("main").cinterops.creating { -// headers(project.projectDir.resolve("untitled/myrust.h")) -// } -// -// binaries { -// sharedLib { -// linkerOpts("-v") -// linkerOpts("-L${project.projectDir.resolve("untitled/target/debug/").absolutePath}") -//// linkerOpts("-lmyrust") -// linkerOpts("-Wl,-undefined,dynamic_lookup") // resolve symbols in runtime -// baseName = "mykotlin" -// } -// -// executable { -// -// linkerOpts("-v") -// linkerOpts("-L${project.projectDir.resolve("untitled/target/debug/").absolutePath}") -//// linkerOpts("-lmyrust") -// linkerOpts("-Wl,-undefined,dynamic_lookup") // resolve symbols in runtime -// baseName = "KotlinExecutable" -// entryPoint = "main.main" -// } -// } -// } + configureNativeInterop("main", projectDir.resolve("src/nativeMainInterop"), nativeTargets) + configureNativeInterop("test", projectDir.resolve("src/nativeTestInterop"), nativeTargets) val sourceSets = kotlinSourceSets.orEmpty() @@ -151,4 +134,107 @@ fun Project.configureHMPPJvm() { nativeMain.dependsOn(commonMain) nativeTest.dependsOn(commonTest) } +} + +private fun Project.configureNativeInterop( + compilationName: String, + nativeInteropDir: File, + nativeTargets: MutableList<KotlinNativeTarget> +) { + if (nativeInteropDir.exists() && nativeInteropDir.isDirectory && nativeInteropDir.resolve("build.rs").exists()) { + val crateName = project.name.replace("-", "_") + + val headerName = "$crateName.h" + val rustLibDir = nativeInteropDir.resolve("target/debug/") + + configure(nativeTargets) { + compilations.getByName(compilationName).cinterops.create(compilationName) { + val headerFile = nativeInteropDir.resolve(headerName) + if (headerFile.exists()) headers(headerFile) + defFile(nativeInteropDir.resolve("interop.def")) + } + + binaries { + sharedLib { + linkerOpts("-v") + linkerOpts("-L${rustLibDir.absolutePath.replace("\\", "/")}") +// linkerOpts("-lmyrust") + linkerOpts("-Wl,-undefined,dynamic_lookup") // resolve symbols in runtime + baseName = project.name + } + } + } + + val cbindgen = tasks.register("cbindgen${compilationName.titlecase()}") { + group = "mirai" + inputs.files(nativeInteropDir.resolve("src")) + outputs.file(nativeInteropDir.resolve(headerName)) + doLast { + exec { + workingDir(nativeInteropDir) + commandLine( + "cbindgen", + "--config", "cbindgen.toml", + "--crate", crateName, + "--output", headerName + ) + } + } + } + + val bindgen = tasks.register("bindgen${compilationName.titlecase()}") { + group = "mirai" + val bindingsPath = nativeInteropDir.resolve("src/bindings.rs") + inputs.files(nativeInteropDir.resolve("src")) + outputs.file(bindingsPath) + doLast { + exec { + workingDir(nativeInteropDir) + // bindgen input.h -o bindings.rs + commandLine( + "bindgen", + buildDir.resolve("bin/native/debugShared/lib${crateName}_api.h"), + "-o", bindingsPath, + ) + } + } + } + + var targetCompilation: KotlinNativeCompilation? = null + configure(nativeTargets) { + val compilations = compilations.filter { nativeInteropDir.name.contains(it.name, ignoreCase = true) } + check(compilations.isNotEmpty()) { "Should be at lease one corresponding native compilation, but found 0" } + targetCompilation = compilations.single() + targetCompilation!!.compileKotlinTask.dependsOn(cbindgen) +// tasks.getByName("cinteropNative$name").dependsOn(cbindgen) + } + targetCompilation!! + + val compileRust = tasks.register("compileRust${compilationName.titlecase()}") { + group = "mirai" + inputs.files(nativeInteropDir.resolve("src")) + outputs.file(rustLibDir.resolve("lib$crateName.dynlib")) + dependsOn(targetCompilation!!.compileKotlinTask) + dependsOn(bindgen) + dependsOn(tasks.findByName("linkDebugSharedNative")) + doLast { + exec { + workingDir(nativeInteropDir) + commandLine( + "cargo", + "build", + "--all" + ) + } + } + } + + tasks.getByName("assemble").dependsOn(compileRust) + } +} + +fun String.titlecase(): String { + if (this.isEmpty()) return this + val c = get(0) + return replaceFirst(c, Character.toTitleCase(c)) } \ No newline at end of file diff --git a/install.sh b/install.sh new file mode 100644 index 000000000..17edd0362 --- /dev/null +++ b/install.sh @@ -0,0 +1,11 @@ +# +# Copyright 2019-2022 Mamoe Technologies and contributors. +# +# 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. +# Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. +# +# https://github.com/mamoe/mirai/blob/dev/LICENSE +# + +cargo install --force cbindgen +cargo install bindgen \ No newline at end of file diff --git a/mirai-core/src/nativeMainInterop/.gitignore b/mirai-core/src/nativeMainInterop/.gitignore new file mode 100644 index 000000000..683119ebf --- /dev/null +++ b/mirai-core/src/nativeMainInterop/.gitignore @@ -0,0 +1,8 @@ +/target +Cargo.lock +myrust.h + +*.iml + +src/bindings.rs +/*.h \ No newline at end of file diff --git a/mirai-core/src/nativeMainInterop/Cargo.toml b/mirai-core/src/nativeMainInterop/Cargo.toml new file mode 100644 index 000000000..6864969ca --- /dev/null +++ b/mirai-core/src/nativeMainInterop/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "mirai_core" +version = "0.1.0" + +[dependencies] + +[lib] +name = "mirai_core" +crate-type = ["cdylib"] # Creates dynamic lib +# crate-type = ["staticlib"] # Creates static lib + +[build-dependencies] +bindgen = "0.53.1" +cbindgen = "0.20.0" \ No newline at end of file diff --git a/mirai-core/src/nativeMainInterop/build.rs b/mirai-core/src/nativeMainInterop/build.rs new file mode 100644 index 000000000..af3ef6335 --- /dev/null +++ b/mirai-core/src/nativeMainInterop/build.rs @@ -0,0 +1,32 @@ +/* + * Copyright 2019-2022 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ + +extern crate bindgen; +extern crate cbindgen; + +use std::env; +use std::path::PathBuf; + +use cbindgen::Config; +use cbindgen::Language::C; + +fn main() { + // let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + + // cbindgen::Builder::new() + // .with_crate(crate_dir) + // .with_language(C) + // .generate() + // .expect("Unable to generate bindings") + // .write_to_file("nativeInterop.h"); + + + println!("cargo:rustc-link-search=../../build/bin/native/debugShared"); + println!("cargo:rustc-link-lib=mirai_core"); +} \ No newline at end of file diff --git a/mirai-core/src/nativeMainInterop/cbindgen.toml b/mirai-core/src/nativeMainInterop/cbindgen.toml new file mode 100644 index 000000000..4aa4b4903 --- /dev/null +++ b/mirai-core/src/nativeMainInterop/cbindgen.toml @@ -0,0 +1,10 @@ +# This is a template cbindgen.toml file with all of the default values. +# Some values are commented out because their absence is the real default. +# +# See https://github.com/eqrion/cbindgen/blob/master/docs.md#cbindgentoml +# for detailed documentation of every option here. + + + +language = "C" + diff --git a/mirai-core/src/nativeMainInterop/interop.def b/mirai-core/src/nativeMainInterop/interop.def new file mode 100644 index 000000000..e69de29bb diff --git a/mirai-core/src/nativeMainInterop/src/lib.rs b/mirai-core/src/nativeMainInterop/src/lib.rs new file mode 100644 index 000000000..4572df655 --- /dev/null +++ b/mirai-core/src/nativeMainInterop/src/lib.rs @@ -0,0 +1,11 @@ +/* + * Copyright 2019-2022 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ + +/// cbindgen:ignore +mod bindings;