refactor build clash scripts

This commit is contained in:
Kr328 committed 2020-06-10 21:24:37 +08:00
1 parent fd6ecda520
commit cecb1522fe
6 files changed
+156 -160

No files matched your search

+1 -1
View File
@@ -30,7 +30,7 @@ buildscript {
jcenter()
}
dependencies {
classpath("com.android.tools.build:gradle:4.0.0-rc01")
classpath("com.android.tools.build:gradle:4.0.0")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
classpath("org.jetbrains.kotlin:kotlin-serialization:$kotlinVersion")
}
+28 -7
View File
@@ -1,4 +1,4 @@
apply(from = "clash.gradle.kts")
import android.databinding.tool.ext.toCamelCase
plugins {
id("com.android.library")
@@ -7,6 +7,8 @@ plugins {
id("kotlinx-serialization")
}
apply(from = "clash.gradle.kts")
val rootExtra = rootProject.extra
val gCompileSdkVersion: Int by rootExtra
@@ -23,7 +25,10 @@ val gKotlinCoroutineVersion: String by rootExtra
val gKotlinSerializationVersion: String by rootExtra
val gAndroidKtxVersion: String by rootExtra
val clashCoreOutput = buildDir.resolve("extraSources")
val geoipOutput = buildDir.resolve("outputs/geoip")
val golangSource = file("src/main/golang")
val golangOutput = buildDir.resolve("outputs/golang")
val nativeAbis = listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
android {
compileSdkVersion(gCompileSdkVersion)
@@ -37,6 +42,13 @@ android {
versionName = gVersionName
consumerProguardFiles("consumer-rules.pro")
externalNativeBuild {
cmake {
abiFilters(*nativeAbis.toTypedArray())
arguments("-DCLASH_OUTPUT=$golangOutput", "-DCLASH_SOURCE=$golangSource")
}
}
}
buildTypes {
@@ -48,9 +60,8 @@ android {
sourceSets {
maybeCreate("main").apply {
assets.srcDir(clashCoreOutput.resolve("assets"))
jniLibs.srcDir(clashCoreOutput.resolve("jniLibs"))
java.srcDir(clashCoreOutput.resolve("classes"))
assets.srcDir(geoipOutput)
jniLibs.srcDir(golangOutput)
}
}
@@ -62,6 +73,12 @@ android {
kotlinOptions {
jvmTarget = "1.8"
}
externalNativeBuild {
cmake {
path = file("src/main/cpp/CMakeLists.txt")
}
}
}
dependencies {
@@ -77,6 +94,10 @@ repositories {
}
afterEvaluate {
tasks["clean"].dependsOn(tasks["resetGolangPathMode"])
tasks["preBuild"].dependsOn(tasks["extractSources"], tasks["downloadGeoipDatabase"])
android.buildTypes.forEach {
val cName = it.name.toCamelCase()
tasks["externalNativeBuild${cName}"].dependsOn(tasks["compileClashCore"])
tasks["package${cName}Assets"].dependsOn(tasks["downloadGeoipDatabase"])
}
}
+102 -152
View File
@@ -2,85 +2,99 @@ import org.apache.tools.ant.taskdefs.condition.Os
import java.io.*
import java.util.*
import java.net.*
import java.time.*
object Constants {
const val GEOIP_DATABASE_URL = "https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb"
const val GEOIP_INVALID_INTERVAL = 1000L * 60 * 60 * 24 * 7
val gMinSdkVersion: Int by rootProject.extra
const val SOURCE_PATH = "src/main/golang"
const val OUTPUT_PATH = "extraSources"
val geoipDatabaseUrl = "https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb"
val geoipInvalidate = Duration.ofDays(7)
val geoipOutput = buildDir.resolve("outputs/geoip")
val golangSource = file("src/main/golang")
val golangOutput = buildDir.resolve("outputs/golang")
val nativeAbis = listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
const val GOLANG_BASE = "intermediates/golang"
const val GOLANG_PATH = "$GOLANG_BASE/path"
const val GOLANG_BIND = "$GOLANG_BASE/bind"
const val GOLANG_BINARY = "$GOLANG_PATH/bin"
const val GOLANG_OUTPUT = "$GOLANG_BASE/bridge.aar"
const val GOLANG_OUTPUT_SOURCES = "$GOLANG_BASE/bridge-sources.jar"
fun generateGolangBuildEnvironment(abi: String): Map<String, String> {
val properties = Properties().apply {
load(FileInputStream(rootProject.file("local.properties")))
}
val STUB_GO_FILE_CONTENT = """
package main
val ndk = properties.getProperty("ndk.dir")
?: throw GradleScriptException("ndk.dir not found in local.properties",
FileNotFoundException("ndk.dir not found in local.properties"))
import "github.com/kr328/cfa/bridge"
func main() {}
""".trimIndent()
val STUB_GO_MOD_CONTENT = """
module github.com/kr328/cfa-bind
require github.com/kr328/cfa v0.0.0 // redirect
replace github.com/kr328/cfa => {SOURCE_PATH}
""".trimIndent()
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"))
}
val REGEX_REPLACE = Regex("replace\\s+(\\S+)\\s+(\\S*)\\s*=>\\s*(\\S+)\\s*(\\S*)\\s*")
val REGEX_JNI = Regex("^jni/")
}
val compilerBase = rootProject.file(ndk).resolve("toolchains/llvm/prebuilt/$host-x86_64/bin")
fun generateGolangBuildEnvironment(vararg pathAppend: String): Map<String, String> {
val environment = TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER).apply { putAll(System.getenv()) }
val properties = Properties().apply { load(rootProject.file("local.properties").inputStream()) }
val cCompiler = when(abi) {
"armeabi-v7a" ->
"armv7a-$host-androideabi$gMinSdkVersion-clang"
"arm64-v8a" ->
"aarch64-$host-android$gMinSdkVersion-clang"
"x86" ->
"i686-$host-android$gMinSdkVersion-clang"
"x86_64" ->
"x86_64-$host-android$gMinSdkVersion-clang"
else ->
throw GradleScriptException("Unsupported abi $abi", FileNotFoundException("Unsupported abi $abi"))
}
val sdkPath = properties.getProperty("sdk.dir")
?: throw GradleScriptException("sdk.dir not found", FileNotFoundException())
val ndkPath = properties.getProperty("ndk.dir")
?: throw GradleScriptException("ndk.dir not found", FileNotFoundException())
val cppCompiler = when(abi) {
"armeabi-v7a" ->
"armv7a-$host-androideabi$gMinSdkVersion-clang++"
"arm64-v8a" ->
"aarch64-$host-android$gMinSdkVersion-clang++"
"x86" ->
"i686-$host-android$gMinSdkVersion-clang++"
"x86_64" ->
"x86_64-$host-android$gMinSdkVersion-clang++"
else ->
throw GradleScriptException("Unsupported abi $abi", FileNotFoundException("Unsupported abi $abi"))
}
val pathSeparator = if ( Os.isFamily(Os.FAMILY_WINDOWS) ) ";" else ":"
val linker = when(abi) {
"armeabi-v7a" ->
"arm-$host-androideabi-ld"
"arm64-v8a" ->
"aarch64-$host-android-ld"
"x86" ->
"i686-$host-android-ld"
"x86_64" ->
"x86_64-$host-android-ld"
else ->
throw GradleScriptException("Unsupported abi $abi", FileNotFoundException("Unsupported abi $abi"))
}
environment["GOPATH"] = listOf(buildDir.resolve(Constants.GOLANG_PATH).absolutePath, *pathAppend)
.joinToString(separator = pathSeparator)
environment["ANDROID_HOME"] = sdkPath
environment["ANDROID_NDK_HOME"] = ndkPath
environment["PATH"] += "$pathSeparator${buildDir.resolve(Constants.GOLANG_BINARY)}"
val golangArch = when(abi) {
"armeabi-v7a" ->
"arm"
"arm64-v8a" ->
"arm64"
"x86" ->
"386"
"x86_64" ->
"amd64"
else ->
throw GradleScriptException("Unsupported abi $abi", FileNotFoundException("Unsupported abi $abi"))
}
return environment
}
fun generateGolangModule(): String {
val moduleFile = file(Constants.SOURCE_PATH).resolve("go.mod")
val replaces = moduleFile
.readLines()
.asSequence()
.map { line -> Constants.REGEX_REPLACE.matchEntire(line) }
.filterNotNull()
.map { match ->
val source = match.groupValues[1].trim()
val sVersion = match.groupValues[2].trim()
val target = match.groupValues[3].trim()
val tVersion = match.groupValues[4].trim()
val resolvedTarget = if ( target.startsWith("./") )
moduleFile.parentFile!!.resolve(target).canonicalPath
else
target
"replace $source $sVersion => $resolvedTarget $tVersion"
}.joinToString(separator = "\n")
return Constants.STUB_GO_MOD_CONTENT
.replace("{SOURCE_PATH}", file(Constants.SOURCE_PATH).absolutePath) + replaces
return mapOf(
"CC" to compilerBase.resolve(cCompiler).absolutePath,
"CXX" to compilerBase.resolve(cppCompiler).absolutePath,
"LD" to compilerBase.resolve(linker).absolutePath,
"GOOS" to "android",
"GOARCH" to golangArch,
"CGO_ENABLED" to "1"
)
}
fun String.exec(pwd: File = buildDir, env: Map<String, String> = System.getenv()): String {
@@ -109,110 +123,46 @@ fun String.exec(pwd: File = buildDir, env: Map<String, String> = System.getenv()
return outputStream.toString("utf-8")
}
task("generateClashBindSources") {
task("compileClashCore") {
onlyIf {
val lastModified = file(Constants.SOURCE_PATH).walk()
.filter { it.extension == "go" || it.extension == "mod" }
val sourceModified = golangSource.walk()
.filter { it.extension == "go" || it.name == "go.mod" }
.map { it.lastModified() }
.max() ?: 0L
.max() ?: Long.MAX_VALUE
val targetModified = golangOutput.walk()
.filter { it.extension == "so" }
.map { it.lastModified() }
.min() ?: Long.MIN_VALUE
return@onlyIf lastModified > buildDir.resolve(Constants.GOLANG_OUTPUT).lastModified()
}
doFirst {
buildDir.resolve(Constants.GOLANG_BIND).apply {
deleteRecursively()
mkdirs()
}
sourceModified > targetModified
}
doLast {
val environment = generateGolangBuildEnvironment()
nativeAbis.parallelStream().forEach {
val env = generateGolangBuildEnvironment(it)
val out = golangOutput.resolve(it).apply {
mkdirs()
}.resolve("libclash.so")
val bind = buildDir.resolve(Constants.GOLANG_BIND).apply {
resolve("main.go").writeText(Constants.STUB_GO_FILE_CONTENT)
resolve("go.mod").writeText(generateGolangModule())
}
"go mod vendor".exec(pwd = bind, env = environment)
buildDir.resolve(Constants.GOLANG_BIND).apply {
resolve("vendor").renameTo(resolve("src"))
resolve("go.mod").delete()
resolve("main.go").delete()
resolve("go.sum").delete()
"go build --buildmode=c-shared -o \"$out\"".exec(pwd = golangSource, env = env)
}
}
}
task("assembleClashCore") {
dependsOn(tasks["generateClashBindSources"])
onlyIf {
!tasks["generateClashBindSources"].state.skipped
}
doFirst {
val environment = generateGolangBuildEnvironment()
"go get golang.org/x/mobile/cmd/gomobile".exec(env = environment)
}
doLast {
val bind = buildDir.resolve(Constants.GOLANG_BIND)
val environment = generateGolangBuildEnvironment(bind.absolutePath)
"gomobile init".exec(pwd = bind, env = environment)
"gomobile bind -target=android -trimpath github.com/kr328/cfa/bridge"
.exec(pwd = buildDir.resolve(Constants.GOLANG_BASE), env = environment)
}
}
task("extractSources", type = Copy::class) {
dependsOn(tasks["assembleClashCore"])
from(zipTree(buildDir.resolve(Constants.GOLANG_OUTPUT))) {
include("**/*.so")
eachFile {
path = path.replace(Constants.REGEX_JNI, "jniLibs/")
}
}
from(zipTree(buildDir.resolve(Constants.GOLANG_OUTPUT_SOURCES))) {
include("**/*.java")
into("classes")
}
destinationDir = buildDir.resolve(Constants.OUTPUT_PATH)
}
task("downloadGeoipDatabase") {
dependsOn(tasks["extractSources"])
val geoipFile = geoipOutput.resolve("Country.mmdb")
onlyIf {
val file = buildDir.resolve(Constants.OUTPUT_PATH).resolve("assets/Country.mmdb")
System.currentTimeMillis() - file.lastModified() > Constants.GEOIP_INVALID_INTERVAL
System.currentTimeMillis() - geoipFile.lastModified() > geoipInvalidate.toMillis()
}
doLast {
val assets = buildDir.resolve(Constants.OUTPUT_PATH).resolve("assets")
geoipOutput.mkdirs()
assets.mkdirs()
URL(Constants.GEOIP_DATABASE_URL).openConnection().getInputStream().use { input ->
FileOutputStream(assets.resolve("Country.mmdb")).use { output ->
URL(geoipDatabaseUrl).openConnection().getInputStream().use { input ->
FileOutputStream(geoipFile).use { output ->
input.copyTo(output)
}
}
}
}
task("resetGolangPathMode", type = Exec::class) {
onlyIf {
!Os.isFamily(Os.FAMILY_WINDOWS)
}
commandLine("chmod", "-R", "777", buildDir.resolve(Constants.GOLANG_PATH))
isIgnoreExitValue = true
}
+10
View File
@@ -0,0 +1,10 @@
cmake_minimum_required(VERSION 3.0)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
include_directories(${CLASH_OUTPUT}/${ANDROID_ABI} ${CLASH_SOURCE})
link_directories(${CLASH_OUTPUT}/${CMAKE_ANDROID_ARCH_ABI})
link_libraries(log clash)
add_library(bridge SHARED bridge.c)
+8
View File
@@ -0,0 +1,8 @@
#include <jni.h>
#include "libclash.h"
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *unused) {
initialize();
return JNI_VERSION_1_6;
}
+7
View File
@@ -1,3 +1,10 @@
package main
import "C"
func main() {}
//export initialize
func initialize() {
}