From e2383df06ba5dabf2d54739833e0a2aa92500418 Mon Sep 17 00:00:00 2001 From: kr328 Date: Sat, 22 May 2021 01:22:09 +0800 Subject: [PATCH] Improve: improve clash core build --- buildSrc/build.gradle.kts | 4 +- buildSrc/src/main/java/GolangBuildTask.kt | 167 ------------------ buildSrc/src/main/java/LibraryGolangPlugin.kt | 56 ------ .../github/kr328/clash/tools/BuildConfig.kt | 25 +++ .../kr328/clash/tools/ClashBuildPlugin.kt | 61 +++++++ .../kr328/clash/tools/ClashBuildTask.kt | 63 +++++++ .../com/github/kr328/clash/tools/Command.kt | 84 +++++++++ .../github/kr328/clash/tools/Environment.kt | 48 +++++ .../com/github/kr328/clash/tools/NativeAbi.kt | 25 +++ core/build.gradle.kts | 2 +- core/src/main/cpp/CMakeLists.txt | 1 + core/src/main/golang/go.mod | 2 +- 12 files changed, 311 insertions(+), 227 deletions(-) delete mode 100644 buildSrc/src/main/java/GolangBuildTask.kt delete mode 100644 buildSrc/src/main/java/LibraryGolangPlugin.kt create mode 100644 buildSrc/src/main/java/com/github/kr328/clash/tools/BuildConfig.kt create mode 100644 buildSrc/src/main/java/com/github/kr328/clash/tools/ClashBuildPlugin.kt create mode 100644 buildSrc/src/main/java/com/github/kr328/clash/tools/ClashBuildTask.kt create mode 100644 buildSrc/src/main/java/com/github/kr328/clash/tools/Command.kt create mode 100644 buildSrc/src/main/java/com/github/kr328/clash/tools/Environment.kt create mode 100644 buildSrc/src/main/java/com/github/kr328/clash/tools/NativeAbi.kt diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 573e7e24..ad650c1c 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -28,8 +28,8 @@ dependencies { gradlePlugin { plugins { create("golang") { - id = "library-golang" - implementationClass = "LibraryGolangPlugin" + id = "clash-build" + implementationClass = "com.github.kr328.clash.tools.ClashBuildPlugin" } } } diff --git a/buildSrc/src/main/java/GolangBuildTask.kt b/buildSrc/src/main/java/GolangBuildTask.kt deleted file mode 100644 index 734e16dd..00000000 --- a/buildSrc/src/main/java/GolangBuildTask.kt +++ /dev/null @@ -1,167 +0,0 @@ -import org.apache.tools.ant.taskdefs.condition.Os -import org.gradle.api.DefaultTask -import org.gradle.api.GradleScriptException -import org.gradle.api.file.DirectoryProperty -import org.gradle.api.provider.Property -import org.gradle.api.provider.SetProperty -import org.gradle.api.tasks.* -import java.io.ByteArrayOutputStream -import java.io.File -import java.io.FileNotFoundException -import java.io.IOException -import java.util.* - -abstract class GolangBuildTask : DefaultTask() { - abstract val debug: Property - @Input get - - abstract val premium: Property - @Input get - - abstract val nativeAbis: SetProperty - @Input get - - abstract val minSdkVersion: Property - @Input get - - abstract val ndkDirectory: DirectoryProperty - @InputDirectory get - - abstract val cmakeDirectory: DirectoryProperty - @InputDirectory get - - abstract val inputDirectory: DirectoryProperty - @InputDirectory get - - abstract val outputDirectory: DirectoryProperty - @OutputDirectory get - - @TaskAction - fun build() { - val src = inputDirectory.get().asFile - - val generateCmd = """go run make/make.go bridge native build android %s""" - - val buildCmd = if (debug.get()) { - """go build --buildmode=c-shared -trimpath -o "%s" -tags "without_gvisor,without_system,debug${if (premium.get()) ",premium" else ""}""" - } else { - """go build --buildmode=c-shared -trimpath -o "%s" -tags "without_gvisor,without_system${if (premium.get()) ",premium" else ""}" -ldflags "-s -w"""" - } - - "go mod tidy".exec(pwd = src) - - nativeAbis.get().parallelStream().forEach { - val out = outputDirectory.get().file("$it/libclash.so") - - generateCmd.format(it.toGoArch()).exec(pwd = src.resolve("tun2socket/bridge"), env = generateGolangGenerateEnvironment(it)) - buildCmd.format(out).exec(pwd = src, env = generateGolangBuildEnvironment(it)) - } - } - - private fun generateGolangGenerateEnvironment(abi: String): Map { - val path = cmakeDirectory.get().asFile.absolutePath + File.pathSeparator + System.getenv("PATH") - - return mapOf( - "PATH" to path, - "CMAKE_SYSTEM_NAME" to "Android", - "CMAKE_ANDROID_NDK" to ndkDirectory.get().asFile.absolutePath, - "CMAKE_ANDROID_ARCH_ABI" to abi, - "CMAKE_SYSTEM_VERSION" to minSdkVersion.get().toString() - ) - } - - private fun generateGolangBuildEnvironment(abi: String): Map { - val (goArch, goArm) = when (abi) { - "arm64-v8a" -> "arm64" to "" - "armeabi-v7a" -> "arm" to "7" - "x86" -> "386" to "" - "x86_64" -> "amd64" to "" - else -> throw UnsupportedOperationException("unsupported abi: $abi") - } - - val compiler = when (abi) { - "armeabi-v7a" -> - "armv7a-linux-androideabi${minSdkVersion.get()}-clang" - "arm64-v8a" -> - "aarch64-linux-android${minSdkVersion.get()}-clang" - "x86" -> - "i686-linux-android${minSdkVersion.get()}-clang" - "x86_64" -> - "x86_64-linux-android${minSdkVersion.get()}-clang" - else -> - throw GradleScriptException( - "Unsupported abi $abi", - FileNotFoundException("Unsupported abi $abi") - ) - } - - return mapOf( - "CC" to compilerBasePath.resolve(compiler).absolutePath, - "GOOS" to "android", - "GOARCH" to goArch, - "GOARM" to goArm, - "CGO_ENABLED" to "1", - "CFLAGS" to "-O3 -Werror", - "CMAKE_ARGS" to "-DCMAKE_TOOLCHAIN_FILE=${ndkDirectory.get().asFile.absolutePath}/build/cmake/android.toolchain.cmake -DANDROID_ABI=$abi -DANDROID_PLATFORM=android-${minSdkVersion.get()} -DCMAKE_BUILD_TYPE=Release", - "PATH" to cmakeDirectory.get().asFile.absolutePath + File.pathSeparator + System.getenv("PATH") - ) - } - - private fun String.toGoArch(): String { - return when (this) { - "arm64-v8a" -> "arm64" - "armeabi-v7a" -> "arm" - "x86" -> "386" - "x86_64" -> "amd64" - else -> throw UnsupportedOperationException("unsupported abi: $this") - } - } - - private fun String.exec( - pwd: File, - env: Map = System.getenv() - ): String { - val process = ProcessBuilder().run { - if (Os.isFamily(Os.FAMILY_WINDOWS)) - command("cmd.exe", "/c", this@exec) - else - command("bash", "-c", this@exec) - - environment().putAll(env) - directory(pwd) - - redirectErrorStream(true) - - start() - } - - val outputStream = ByteArrayOutputStream() - process.inputStream.copyTo(outputStream) - - if (process.waitFor() != 0) { - println(outputStream.toString("utf-8")) - throw GradleScriptException("Exec $this failure", IOException()) - } - - return outputStream.toString("utf-8") - } - - private val compilerBasePath: File - get() { - val host = when { - Os.isFamily(Os.FAMILY_WINDOWS) -> - "windows" - Os.isFamily(Os.FAMILY_MAC) -> - "darwin" - Os.isFamily(Os.FAMILY_UNIX) -> - "linux" - else -> - throw GradleScriptException( - "Unsupported host", - FileNotFoundException("Unsupported host") - ) - } - - return ndkDirectory.get().asFile.resolve("toolchains/llvm/prebuilt/$host-x86_64/bin") - } -} \ No newline at end of file diff --git a/buildSrc/src/main/java/LibraryGolangPlugin.kt b/buildSrc/src/main/java/LibraryGolangPlugin.kt deleted file mode 100644 index fb22c8c9..00000000 --- a/buildSrc/src/main/java/LibraryGolangPlugin.kt +++ /dev/null @@ -1,56 +0,0 @@ -import com.android.build.gradle.LibraryExtension -import org.apache.tools.ant.taskdefs.condition.Os -import org.gradle.api.GradleScriptException -import org.gradle.api.Plugin -import org.gradle.api.Project -import java.io.File -import java.io.FileNotFoundException -import java.util.* - -class LibraryGolangPlugin : Plugin { - override fun apply(target: Project) { - target.extensions.getByType(LibraryExtension::class.java).apply { - target.afterEvaluate { - val properties = Properties().apply { - target.rootProject.file("local.properties").inputStream().use(this::load) - } - val cmakeDirectory = target.rootProject.file(properties.getProperty("cmake.dir")!!) - - libraryVariants.forEach { variant -> - val abis = defaultConfig.externalNativeBuild.cmake.abiFilters + - defaultConfig.externalNativeBuild.ndkBuild.abiFilters - - val nameCapitalize = variant.name.capitalize(Locale.getDefault()) - val golangBuildDir = target.golangBuild.resolve(variant.name) - - val task = target.tasks.register( - "externalGolangBuild$nameCapitalize", - GolangBuildTask::class.java - ) { - it.premium.set(variant.flavorName == "premium") - it.debug.set(variant.name == "debug") - it.nativeAbis.set(abis) - it.minSdkVersion.set(defaultConfig.minSdk!!) - it.ndkDirectory.set(ndkDirectory) - it.cmakeDirectory.set(cmakeDirectory) - it.inputDirectory.set(target.golangSource) - it.outputDirectory.set(golangBuildDir) - } - - sourceSets.named(variant.name) { - it.jniLibs { - srcDir(golangBuildDir) - } - } - - variant.externalNativeBuildProviders.forEach { - it.get().dependsOn(task) - } - target.tasks.filter { it.name.startsWith("buildCMake") }.forEach { - it.mustRunAfter(task) - } - } - } - } - } -} \ No newline at end of file diff --git a/buildSrc/src/main/java/com/github/kr328/clash/tools/BuildConfig.kt b/buildSrc/src/main/java/com/github/kr328/clash/tools/BuildConfig.kt new file mode 100644 index 00000000..24acc5de --- /dev/null +++ b/buildSrc/src/main/java/com/github/kr328/clash/tools/BuildConfig.kt @@ -0,0 +1,25 @@ +package com.github.kr328.clash.tools + +import com.android.build.gradle.BaseExtension +import com.android.build.gradle.api.BaseVariant +import java.io.Serializable + +data class BuildConfig( + val debug: Boolean, + val premium: Boolean, + val abis: List, + val minSdkVersion: Int, +) : Serializable { + companion object { + fun of(extension: BaseExtension, variant: BaseVariant): BuildConfig { + return BuildConfig( + debug = variant.buildType.isDebuggable, + premium = variant.flavorName == "premium", + abis = extension.defaultConfig.externalNativeBuild.cmake.abiFilters + .map { NativeAbi.parse(it) } + .distinct(), + minSdkVersion = extension.defaultConfig.minSdkVersion!!.apiLevel + ) + } + } +} diff --git a/buildSrc/src/main/java/com/github/kr328/clash/tools/ClashBuildPlugin.kt b/buildSrc/src/main/java/com/github/kr328/clash/tools/ClashBuildPlugin.kt new file mode 100644 index 00000000..b6c8c5db --- /dev/null +++ b/buildSrc/src/main/java/com/github/kr328/clash/tools/ClashBuildPlugin.kt @@ -0,0 +1,61 @@ +package com.github.kr328.clash.tools + +import com.android.build.gradle.LibraryExtension +import golangBuild +import golangSource +import org.gradle.api.GradleException +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.tasks.Delete +import java.io.File +import java.util.* + +class ClashBuildPlugin : Plugin { + override fun apply(target: Project) { + target.afterEvaluate { + val cmakeDirectory = resolveCmakeDir(target) + + target.extensions.getByType(LibraryExtension::class.java).apply { + libraryVariants.forEach { variant -> + val config = BuildConfig.of(this, variant) + val buildDir = target.golangBuild.resolve(variant.name) + val capitalize = variant.name.capitalize(Locale.getDefault()) + + val task = target.tasks.register( + "externalGolangBuild$capitalize", + ClashBuildTask::class.java + ) { + it.config.set(config) + it.ndkDirectory.set(ndkDirectory) + it.cmakeDirectory.set(cmakeDirectory) + it.inputDirectory.set(target.golangSource) + it.outputDirectory.set(buildDir) + } + + sourceSets.named(variant.name) { + it.jniLibs { + srcDir(buildDir) + } + } + + variant.externalNativeBuildProviders.forEach { + it.get().dependsOn(task) + } + target.tasks.filter { it.name.startsWith("buildCMake") }.forEach { + it.mustRunAfter(task) + } + } + } + } + } + + private fun resolveCmakeDir(project: Project): File { + val properties = Properties().apply { + project.rootProject.file("local.properties").inputStream().use(this::load) + } + + return project.rootProject.file( + properties.getProperty("cmake.dir") ?: throw GradleException("cmake.dir not found") + ) + } +} \ No newline at end of file diff --git a/buildSrc/src/main/java/com/github/kr328/clash/tools/ClashBuildTask.kt b/buildSrc/src/main/java/com/github/kr328/clash/tools/ClashBuildTask.kt new file mode 100644 index 00000000..d11a5ea1 --- /dev/null +++ b/buildSrc/src/main/java/com/github/kr328/clash/tools/ClashBuildTask.kt @@ -0,0 +1,63 @@ +package com.github.kr328.clash.tools + +import org.gradle.api.DefaultTask +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputDirectory +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.TaskAction +import java.io.File + +abstract class ClashBuildTask : DefaultTask() { + abstract val config: Property + @Input get + + abstract val ndkDirectory: DirectoryProperty + @InputDirectory get + + abstract val cmakeDirectory: DirectoryProperty + @InputDirectory get + + abstract val inputDirectory: DirectoryProperty + @InputDirectory get + + abstract val outputDirectory: DirectoryProperty + @OutputDirectory get + + @TaskAction + fun build() { + val input = inputDirectory.file + val output = outputDirectory.file + + val config = config.get() + val environment = Environment(ndkDirectory.file, cmakeDirectory.file, config.minSdkVersion) + + val tags = listOf("without_gvisor", "without_system") + + (if (config.debug) listOf("debug") else emptyList()) + + (if (config.premium) listOf("premium") else emptyList()) + + Command.ofGoModuleTidy(input).exec() + + config.abis.forEach { + Command.ofGoRun( + "make/make.go", + listOf("bridge", "native", "build", "android", it.goArch), + input.resolve("tun2socket/bridge"), + environment.ofLwipBuild(it) + ).exec() + + Command.ofGoBuild( + "c-shared", + output.resolve("${it.value}/libclash.so"), + tags, + !config.debug, + input, + environment.ofCoreBuild(it) + ).exec() + } + } + + private val DirectoryProperty.file: File + get() = get().asFile +} \ No newline at end of file diff --git a/buildSrc/src/main/java/com/github/kr328/clash/tools/Command.kt b/buildSrc/src/main/java/com/github/kr328/clash/tools/Command.kt new file mode 100644 index 00000000..625efb04 --- /dev/null +++ b/buildSrc/src/main/java/com/github/kr328/clash/tools/Command.kt @@ -0,0 +1,84 @@ +package com.github.kr328.clash.tools + +import org.gradle.api.GradleException +import java.io.File +import kotlin.concurrent.thread + +class Command( + private val command: Array, + workingDir: File, + environments: Map +) { + private val processBuilder: ProcessBuilder = ProcessBuilder(*command) + .redirectErrorStream(true) + .directory(workingDir) + .apply { environment().putAll(environments) } + + fun exec() { + val process = processBuilder.start() + + thread { + process.inputStream.copyTo(System.out) + } + + val result = process.waitFor() + + if (result != 0) { + throw GradleException("exec ${command.joinToString(" ")}: exit with $result") + } + } + + companion object { + fun ofGoModuleTidy(workingDir: File): Command { + return Command(arrayOf("go", "mod", "tidy"), workingDir, System.getenv()) + } + + fun ofGoBuild( + mode: String, + output: File, + tags: List, + strip: Boolean, + workingDir: File, + environments: Map + ): Command { + val command = mutableListOf("go", "build") + + // go build mode + command += "-buildmode" + command += mode + + // output file + command += "-o" + command += output.absolutePath + + // trim path prefix + command += "-trimpath" + + if (tags.isNotEmpty()) { + command += "-tags" + command += tags.joinToString(",") + } + + if (strip) { + command += "-ldflags" + command += "-s -w" + } + + return Command(command.toTypedArray(), workingDir, environments) + } + + fun ofGoRun( + file: String, + args: List, + workingDir: File, + environments: Map + ): Command { + val command = mutableListOf("go", "run") + + command += file + command += args + + return Command(command.toTypedArray(), workingDir, environments) + } + } +} \ No newline at end of file diff --git a/buildSrc/src/main/java/com/github/kr328/clash/tools/Environment.kt b/buildSrc/src/main/java/com/github/kr328/clash/tools/Environment.kt new file mode 100644 index 00000000..0403bf36 --- /dev/null +++ b/buildSrc/src/main/java/com/github/kr328/clash/tools/Environment.kt @@ -0,0 +1,48 @@ +package com.github.kr328.clash.tools + +import org.apache.tools.ant.taskdefs.condition.Os +import org.gradle.api.GradleException +import java.io.File + +class Environment( + private val ndkDirectory: File, + private val cmakeDirectory: File, + private val minSdkVersion: Int, +) { + fun ofCoreBuild(abi: NativeAbi): Map { + val host = when { + Os.isFamily(Os.FAMILY_WINDOWS) -> + "windows" + Os.isFamily(Os.FAMILY_MAC) -> + "darwin" + Os.isFamily(Os.FAMILY_UNIX) -> + "linux" + else -> + throw GradleException("Unsupported host: ${System.getProperty("os.name")}") + } + + val compiler = ndkDirectory.resolve("toolchains/llvm/prebuilt/$host-x86_64/bin") + .resolve("${abi.compiler}${minSdkVersion}-clang") + + return mapOf( + "CC" to compiler.absolutePath, + "GOOS" to "android", + "GOARCH" to abi.goArch, + "GOARM" to abi.goArm, + "CGO_ENABLED" to "1", + "CFLAGS" to "-O3 -Werror", + ) + } + + fun ofLwipBuild(abi: NativeAbi): Map { + val path = "${cmakeDirectory.absolutePath}${File.pathSeparator}${System.getenv("PATH")}" + + return mapOf( + "PATH" to path, + "CMAKE_SYSTEM_NAME" to "Android", + "CMAKE_ANDROID_NDK" to ndkDirectory.absolutePath, + "CMAKE_ANDROID_ARCH_ABI" to abi.value, + "CMAKE_SYSTEM_VERSION" to minSdkVersion.toString() + ) + } +} \ No newline at end of file diff --git a/buildSrc/src/main/java/com/github/kr328/clash/tools/NativeAbi.kt b/buildSrc/src/main/java/com/github/kr328/clash/tools/NativeAbi.kt new file mode 100644 index 00000000..b115833b --- /dev/null +++ b/buildSrc/src/main/java/com/github/kr328/clash/tools/NativeAbi.kt @@ -0,0 +1,25 @@ +package com.github.kr328.clash.tools + +enum class NativeAbi( + val value: String, + val compiler: String, + val goArch: String, + val goArm: String +) { + ArmeabiV7a("armeabi-v7a", "armv7a-linux-androideabi", "arm", "7"), + Arm64V8a("arm64-v8a", "aarch64-linux-android", "arm64", ""), + X86("x86", "i686-linux-android", "386", ""), + X64("x86_64", "x86_64-linux-android", "amd64", ""); + + companion object { + fun parse(value: String): NativeAbi { + return when (value) { + ArmeabiV7a.value -> ArmeabiV7a + Arm64V8a.value -> Arm64V8a + X86.value -> X86 + X64.value -> X64 + else -> throw IllegalArgumentException("unsupported abi $value") + } + } + } +} diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 604333af..5cef3ed0 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -6,7 +6,7 @@ plugins { id("com.android.library") kotlin("android") id("kotlinx-serialization") - id("library-golang") + id("clash-build") } val geoipDatabaseUrl = diff --git a/core/src/main/cpp/CMakeLists.txt b/core/src/main/cpp/CMakeLists.txt index 792e21ab..2260c1a8 100644 --- a/core/src/main/cpp/CMakeLists.txt +++ b/core/src/main/cpp/CMakeLists.txt @@ -16,6 +16,7 @@ endif () include_directories("${GO_OUTPUT_BASE}/${CMAKE_ANDROID_ARCH_ABI}") include_directories("${GO_SOURCE}") +include_directories("${GO_SOURCE}/tun2socket/bridge/native") link_directories("${GO_OUTPUT_BASE}/${CMAKE_ANDROID_ARCH_ABI}") diff --git a/core/src/main/golang/go.mod b/core/src/main/golang/go.mod index fa47698f..6fd8ca33 100644 --- a/core/src/main/golang/go.mod +++ b/core/src/main/golang/go.mod @@ -5,8 +5,8 @@ go 1.16 require ( cfa/blob v0.0.0 // local generated github.com/Dreamacro/clash v0.0.0 // local - github.com/kr328/tun2socket v0.0.0 // local github.com/dlclark/regexp2 v1.4.0 + github.com/kr328/tun2socket v0.0.0 // local github.com/miekg/dns v1.1.42 github.com/oschwald/geoip2-golang v1.5.0 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c