diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4f8a8ff..a483e8c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,13 +63,13 @@ jobs: rustup target add i686-linux-android rustup target add x86_64-linux-android + - name: Setup sccache-cache + uses: mozilla-actions/sccache-action@v0.0.9 + - uses: taiki-e/install-action@v2 with: - tool: cargo-ndk + tool: cargo-ndk,just - - name: Run sccache-cache - uses: mozilla-actions/sccache-action@v0.0.9 - - name: Cache Gradle dependencies uses: actions/cache@v5 with: @@ -102,26 +102,16 @@ jobs: else echo "version=dev-$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT fi - - - name: Gen binding + + - name: Generate Rust bindings run: | - touch local.properties - ./gradlew assembleDebug -Prust-target=arm64 - ./gradlew clean - cd uniffi - cargo clean - env: - SCCACHE_GHA_ENABLED: "true" - RUSTC_WRAPPER: "sccache" - + just generate-bindings + - name: Build Debug APK if: github.event_name == 'pull_request' - env: - SCCACHE_GHA_ENABLED: "true" - RUSTC_WRAPPER: "sccache" run: | - ./gradlew assembleDebug - + ./gradlew app:assembleDebug + - name: Build Release APKs if: github.event_name != 'pull_request' env: @@ -131,11 +121,9 @@ jobs: KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} ANDROID_SPLIT_ABI_ENABLE: true ANDROID_SPLIT_ABI_UNIVERSAL_APK: true - SCCACHE_GHA_ENABLED: "true" - RUSTC_WRAPPER: "sccache" run: | rm -rf app/build/outputs/apk - ./gradlew assembleRelease + ./gradlew app:assembleRelease - name: Organize APKs if: github.event_name != 'pull_request' diff --git a/.gitignore b/.gitignore index 410836d..c81bb4a 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,4 @@ replay_pid* local.properties signing.properties uniffi/.vscode/settings.json +.claude diff --git a/README.md b/README.md index e3f5eda..4626fed 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ # clash-android +# Develop + +每次修改 Rust 部分后使用 `generate-bindings` 更新绑定。 + # Requirements Android SDK & NDK diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 61ecbfc..36ed58e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -23,6 +23,7 @@ fun Project.exec(command: String): String = fun env(key: String): String? = System.getenv(key).let { if (it.isNullOrEmpty()) null else it } android { + buildToolsVersion = rootProject.extra["buildToolsVersion"] as String val keystore = env("KEYSTORE_FILE") namespace = "rs.clash.android" diff --git a/build.gradle.kts b/build.gradle.kts index 1dcea04..5aa46ac 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,7 +4,7 @@ plugins { alias(libs.plugins.android.library) apply false alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.kotlin.compose) apply false - alias(libs.plugins.cargo.ndk) apply false + alias(libs.plugins.rust.android) apply false alias(libs.plugins.ksp) apply false alias(libs.plugins.ktlint) apply false } diff --git a/core/build.gradle.kts b/core/build.gradle.kts index ed42e20..5d5207d 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -4,26 +4,7 @@ plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.compose) - alias(libs.plugins.cargo.ndk) -} - -fun findRustlsPlatformVerifierClasses(): File { - val dependencyJson = providers.exec { - workingDir = File(project.rootDir, "uniffi") - commandLine("cargo", "metadata", "--format-version", "1") - }.standardOutput.asText - - val jsonSlurper = JsonSlurper() - val jsonData = jsonSlurper.parseText(dependencyJson.get()) as Map<*, *> - val packages = jsonData["packages"] as List<*> - val path = packages - .first { element -> - val pkg = element as Map<*, *> - pkg["name"] == "rustls-platform-verifier-android" - }.let { it as Map<*, *> }["manifest_path"] as String - - val manifestFile = File(path) - return File(manifestFile.parentFile, "classes.jar") + alias(libs.plugins.rust.android) } android { @@ -52,7 +33,7 @@ kotlin { } dependencies { - implementation(files(findRustlsPlatformVerifierClasses())) + implementation(files("../deps/rustls-platform-verifier-0.1.1.aar")) implementation(libs.androidx.core.ktx) implementation(libs.androidx.appcompat) implementation(libs.androidx.runtime) @@ -65,18 +46,13 @@ dependencies { androidTestImplementation(libs.androidx.espresso.core) } -cargoNdk { - module = "uniffi" // Directory containing Cargo.toml - librariesNames = arrayListOf("libclash_android_ffi.so") +cargo { + module = "../uniffi" // Directory containing Cargo.toml + libname = "clash_android_ffi" - extraCargoBuildArguments = arrayListOf("-p", "clash-android-ffi").apply { - // Enable jemallocator feature on Linux - if (System.getProperty("os.name").lowercase().contains("linux")) { - add("--features") - add("jemallocator") - } - } - buildType = "release" + extraCargoBuildArguments = arrayListOf("-p", "clash-android-ffi") + targets = listOf("arm64", "arm", "x86", "x86_64") + profile = "release" } android { @@ -85,32 +61,15 @@ android { sourceCompatibility = JavaVersion.VERSION_25 targetCompatibility = JavaVersion.VERSION_25 } - libraryVariants.all { - val variant = this - val variantName = variant.name.replaceFirstChar(Char::titlecase) - val bDir = layout.projectDirectory.dir("src/main/java") - val generateBindings = tasks.register("generate${variantName}UniFFIBindings", Exec::class) { - workingDir = file("../uniffi") - commandLine( - "cargo", "run", "-p", "uniffi-bindgen", "generate", - "--library", "../core/src/main/jniLibs/arm64-v8a/libclash_android_ffi.so", - "--language", "kotlin", - "--out-dir", bDir.asFile.absolutePath - ) - dependsOn("buildCargoNdk${variantName}") - } - - // Make Java compilation depend on generating UniFFI bindings - variant.javaCompileProvider.get().dependsOn(generateBindings) + libraryVariants.all { + val variantName = name.replaceFirstChar(Char::titlecase) - // Also hook into Kotlin compilation - tasks.named("compile${variantName}Kotlin").configure { - dependsOn(generateBindings) - } + // Make Java compilation depend on generating UniFFI bindings + javaCompileProvider.get().dependsOn("cargoBuild") - // And connectedDebugAndroidTest -// tasks.named("connected${variantName}AndroidTest").configure { -// dependsOn(generateBindings) -// } - } + // Also hook into Kotlin compilation + tasks.named("compile${variantName}Kotlin").configure { + dependsOn("cargoBuild") + } + } } diff --git a/deps/rustls-platform-verifier-0.1.1.aar b/deps/rustls-platform-verifier-0.1.1.aar new file mode 100644 index 0000000..8acc8b5 Binary files /dev/null and b/deps/rustls-platform-verifier-0.1.1.aar differ diff --git a/gradle/gradle-daemon-jvm.properties b/gradle/gradle-daemon-jvm.properties new file mode 100644 index 0000000..5c34300 --- /dev/null +++ b/gradle/gradle-daemon-jvm.properties @@ -0,0 +1,13 @@ +#This file is generated by updateDaemonJvm +toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/56a19bc915b9ba2eb62ba7554c61b919/redirect +toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/398ffe3949748bfb1d5636f023d228fd/redirect +toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/56a19bc915b9ba2eb62ba7554c61b919/redirect +toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/398ffe3949748bfb1d5636f023d228fd/redirect +toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/e99bae143b75f9a10ead10248f02055e/redirect +toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/04e088f8677de3b384108493cc9481d0/redirect +toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/56a19bc915b9ba2eb62ba7554c61b919/redirect +toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/398ffe3949748bfb1d5636f023d228fd/redirect +toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/e55dccbfe27cb97945148c61a39c89c5/redirect +toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/dbd05c4936d573642f94cd149e1356c8/redirect +toolchainVendor=JETBRAINS +toolchainVersion=21 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 87a569c..a5dc42a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] agp = "8.13.2" kotlin = "2.3.21" -cargo-ndk = "0.5.3" +rust-android-gradle = "0.10.1" coreKtx = "1.17.0" junit = "4.13.2" junitVersion = "1.3.0" @@ -53,7 +53,7 @@ androidx-compose-material3 = { group = "androidx.compose.material3", name = "mat android-application = { id = "com.android.application", version.ref = "agp" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } -cargo-ndk = { id = "com.gemwallet.cargo-ndk", version.ref = "cargo-ndk" } +rust-android = { id = "net.mullvad.rust-android", version.ref = "rust-android-gradle" } android-library = { id = "com.android.library", version.ref = "agp" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp-plugin" } ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" } diff --git a/justfile b/justfile index a7126bc..d9b53ed 100644 --- a/justfile +++ b/justfile @@ -1,4 +1,9 @@ -gen: - ./gradlew assembleDebug -Prust-target=arm64 +generate-bindings: + cd uniffi && cargo ndk -t arm64-v8a build -p clash-android-ffi + cd uniffi && cargo run -p uniffi-bindgen generate \ + --library target/aarch64-linux-android/debug/libclash_android_ffi.so \ + --language kotlin \ + --out-dir ../core/src/main/java + build: ./gradlew assembleDebug \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 9e03851..e0fc7ed 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -11,6 +11,9 @@ pluginManagement { gradlePluginPortal() } } +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" +} rootProject.name = "clash-android" include(":app") diff --git a/uniffi/.cargo/config.toml b/uniffi/.cargo/config.toml index ddc54ff..f1e692d 100644 --- a/uniffi/.cargo/config.toml +++ b/uniffi/.cargo/config.toml @@ -7,12 +7,16 @@ RUSTC_BOOTSTRAP = { value = "1" } [target.aarch64-linux-android] linker = "aarch64-linux-android23-clang" +rustflags = ["-C", "link-arg=--rtlib=compiler-rt"] [target.armv7-linux-androideabi] linker = "armv7a-linux-androideabi23-clang" +rustflags = ["-C", "link-arg=--rtlib=compiler-rt"] [target.i686-linux-android] linker = "i686-linux-android23-clang" +rustflags = ["-C", "link-arg=--rtlib=compiler-rt"] [target.x86_64-linux-android] linker = "x86_64-linux-android23-clang" +rustflags = ["-C", "link-arg=--rtlib=compiler-rt"] diff --git a/uniffi/clash-android-ffi/src/lib.rs b/uniffi/clash-android-ffi/src/lib.rs index 66254ed..443b04a 100644 --- a/uniffi/clash-android-ffi/src/lib.rs +++ b/uniffi/clash-android-ffi/src/lib.rs @@ -226,19 +226,40 @@ async fn run_clash( .clone() .unwrap_or_default() }; + // 需要 clash-rs 实现 dns 路由 + // let nameserver = if config.dns.nameserver.is_empty() { + // vec![ + // NameServer { + // net: DNSNetMode::DoT, + // host: Host::Domain("one.one.one.one".to_string()), + // port: 853, + // interface: None, + // proxy: None, + // }, + // NameServer { + // net: DNSNetMode::DoT, + // host: Host::Domain("dns.google".to_string()), + // port: 853, + // interface: None, + // proxy: None, + // }, + // ] + // } else { + // config.dns.nameserver.clone() + // }; let nameserver = if config.dns.nameserver.is_empty() { vec![ NameServer { net: DNSNetMode::DoT, - host: Host::Domain("one.one.one.one".to_string()), + host: Host::Domain("dns.alidns.com".to_string()), port: 853, interface: None, proxy: None, }, NameServer { net: DNSNetMode::DoT, - host: Host::Domain("dns.google".to_string()), + host: Host::Domain("dot.pub".to_string()), port: 853, interface: None, proxy: None,