| name | cargo-workflows |
| description | Use when managing the Rust workspace, adding/removing crates, editing workspace dependencies, running cargo nextest/audit/deny, configuring Cargo profiles for Android cross-compilation, debugging Cargo.lock churn, migrating crate edition, or wiring Gradle to cargo via the ripdpi.android.rust-native plugin. |
Cargo Workflows -- RIPDPI
Project layout
native/rust/
Cargo.toml # Virtual workspace manifest (97 crates as of 2026-05)
Cargo.lock # Checked in -- reproducible builds
.cargo/config.toml # Per-target rustflags for Android NDK
.config/nextest.toml # nextest profiles (default + ci)
deny.toml # cargo-deny policy
crates/
ripdpi-android/ # cdylib -- JNI entry point (libripdpi.so)
ripdpi-tunnel-android/# cdylib -- JNI tunnel entry point (libripdpi-tunnel.so)
ripdpi-warp-android/ # cdylib -- JNI WARP entry point (libripdpi-warp.so)
ripdpi-relay-android/ # cdylib -- JNI relay entry point (libripdpi-relay.so)
ripdpi-cli/ # Host-only CLI binary
ripdpi-bench/ # Criterion benchmarks
... (34 more library crates)
Android NDK cross-compilation
How it works (no cargo-ndk)
This project does NOT use cargo-ndk. Instead, a custom Gradle convention plugin
(ripdpi.android.rust-native) invokes cargo build --locked directly with per-ABI
environment variables pointing to NDK clang linkers and llvm-ar.
Key file: build-logic/convention/src/main/kotlin/ripdpi.android.rust-native.gradle.kts
Target ABIs and Rust triples
| Android ABI | Rust target | Clang target prefix |
|---|
| arm64-v8a | aarch64-linux-android | aarch64-linux-android |
| armeabi-v7a | armv7-linux-androideabi | armv7a-linux-androideabi |
| x86_64 | x86_64-linux-android | x86_64-linux-android |
| x86 | i686-linux-android | i686-linux-android |
Required Rust targets
rustup target add aarch64-linux-android armv7-linux-androideabi \
x86_64-linux-android i686-linux-android
Cargo profiles for Android
[profile.android-jni]
inherits = "release"
[profile.android-jni-dev]
inherits = "dev"
The active profile is selected by Gradle property ripdpi.nativeCargoProfile.
Local dev overrides via ripdpi.localNativeCargoProfileDefault and
ripdpi.localNativeAbisDefault in gradle.properties or local.properties.
.cargo/config.toml (cross-compilation flags)
All four Android targets share the same rustflags:
-C link-arg=-Wl,-z,max-page-size=16384 (Android 15+ 16 KiB page size)
-C force-frame-pointers=yes (profiling / crash symbolication)
Linkers are NOT configured here -- the Gradle task sets CARGO_TARGET_<TRIPLE>_LINKER
environment variables pointing to NDK clang at build time.
Gradle <-> Cargo integration
Build flow
- Gradle task
buildRustNativeLibs (registered by ripdpi.android.rust-native plugin)
- Runs
cargo build --locked --target <triple> --profile <profile> -p ripdpi-android -p ripdpi-tunnel-android
- Builds all ABIs in parallel (one thread per ABI, capped at CPU count)
- Each ABI gets its own
CARGO_TARGET_DIR to avoid lock contention
- Copies
libripdpi_android.so -> libripdpi.so and libripdpi_tunnel_android.so -> libripdpi-tunnel.so
- Output lands in
build/generated/jniLibs/<abi>/ and is wired into Android jniLibs source set
- Task is wired into
merge*JniLibFolders, copy*JniLibsProjectOnly, merge*NativeLibs, and preBuild
Building locally
./gradlew :core:engine:assembleDebug
./gradlew :core:engine:buildRustNativeLibs
cd native/rust && cargo build --locked -p ripdpi-cli
cd native/rust && cargo bench --locked -p ripdpi-bench
cdylib crates and JNI considerations
Two crates produce shared libraries loaded via System.loadLibrary():
ripdpi-android -> libripdpi.so (path-optimization engine)
ripdpi-tunnel-android -> libripdpi-tunnel.so (VPN tunnel)
Key rules for cdylib JNI crates:
crate-type = ["cdylib"] in [lib] -- produces .so for Android
panic = "unwind" required (not "abort") so JNI can catch panics
- Exported
#[no_mangle] pub extern "system" fn Java_... entry points
- The
jni crate (v0.22) provides JNIEnv, JClass, JString wrappers
- Clippy allows
missing_safety_doc and not_unsafe_ptr_arg_deref workspace-wide for JNI/FFI
Feature flags
[features]
loom = ["dep:loom"]
Feature rules:
- Features are additive -- once enabled anywhere in the dep graph, they stay on
resolver = "2" prevents dev-dep features from leaking into regular deps
- Use
dep:optional_dep syntax to avoid implicit feature creation
Testing
cd native/rust && cargo nextest run --locked
cd native/rust && cargo nextest run --locked --profile ci
cd native/rust && cargo nextest run --locked -p ripdpi-packets
cd native/rust && cargo test --locked --doc
nextest config at native/rust/.config/nextest.toml:
default profile: fail-fast=true, slow-timeout=30s
ci profile: retries=2, fail-fast=false
Dependency auditing
cd native/rust
cargo audit
cargo deny --locked check
deny.toml policy (native/rust/deny.toml)
- Licenses: MIT, Apache-2.0, BSD-2/3-Clause, ISC, 0BSD, Zlib, Unicode-3.0, OpenSSL allowed
- Bans: multiple-versions=warn, wildcards=warn
- Sources: unknown registries denied, unknown git warned
- Advisories: one explicit ignore —
RUSTSEC-2024-0436 (paste proc-macro, unmaintained, no runtime risk). See rust-security skill for RUSTSEC triage SLA.
CI caching
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
workspaces: "native/rust -> target"
- uses: actions/cache@v4
with:
path: |
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
native/rust/target/
key: ${{ runner.os }}-cargo-${{ hashFiles('native/rust/Cargo.lock') }}
Workspace commands cheat sheet
cd native/rust
cargo check --locked --workspace
cargo clippy --locked --workspace -- -D warnings
cargo fmt --check
cargo build --locked -p ripdpi-cli
cargo tree --locked --duplicates
cargo tree --locked -i serde
cargo update -p tokio --precise 1.42.0
cargo deny --locked check
cargo audit
Rust edition
The workspace and rustfmt.toml already use edition 2024. New crates must inherit edition.workspace = true; do not pin an older edition in an individual crate. Treat any future edition migration as a workspace-wide contract change with cargo fix --edition, formatting, clippy, and test evidence captured in a dedicated change.
Feature unification silently enables features in no_std crates
Severity: WARNING
Cargo resolves features per-package, not per-dependency-edge. With resolver v2 (Rust 2021 edition default), dev-dependency features are isolated from normal dependencies — but normal-dependency features are still unified across the workspace. If any crate in the workspace enables feature std on a shared dep, every other workspace crate that depends on that dep gets std too, even crates that declare themselves no_std.
Concrete hazard in RIPDPI: a test binary or bench crate depending on serde with derive feature will enable derive for all serde users in the workspace. More critically, if a cdylib crate depends on a dep without std, but a dev-dep in the workspace pulls in the same dep with std, the no_std crate silently gains std — potentially including println!, Vec heap allocation, or panicking infrastructure that should be absent.
Detection:
cd native/rust
cargo tree --locked -f '{p}: {f}' -i serde | grep -v '^$'
cargo tree --locked -f '{p}: {f}' -i tokio | grep -v '^$'
cargo check --locked -p ripdpi-packets --no-default-features 2>&1 | grep 'std\|alloc'
Fix: for crates that must remain no_std, add an explicit default-features = false on every dep declaration and verify via cargo check --locked --no-default-features. If a workspace test binary needs the std feature of a dep, consider gating it behind a dev-dep rather than a normal dep.
Reference: cargo feature unification pitfall — nickb.dev, Cargo Resolver docs.
Workspace dependency inheritance breaks target-specific features
Severity: WARNING
When you define a dependency in [workspace.dependencies] and reference it as foo = { workspace = true } in a subcrate, Cargo resolver v2 sometimes fails to limit features to the current compilation target. The same dependency declared directly (not via workspace inheritance) works correctly.
This manifests as platform-specific features being enabled on all platforms, potentially breaking no_std / #![forbid(unsafe_code)] crates on some targets or pulling in unwanted platform code (Windows-only, Unix-only) on cross-compilation targets.
Affected in RIPDPI: Android NDK cross-compilation targets. If a dep's unix or linux feature is activated via workspace inheritance and the dep does not correctly gate that feature, the Android build may pull in Linux-specific code not supported by the NDK.
Detection:
cargo tree --locked --target aarch64-linux-android -f '{p}: {f}' -i <dep-name>
Workaround: for deps where target-specific features are critical, declare the dep directly in the subcrate's Cargo.toml with explicit target.'cfg(...)'.dependencies rather than relying on workspace inheritance.
Reference: Cargo issue #11779.
Related skills
rust-debugging -- GDB/LLDB, async debugging, backtraces
rust-security -- cargo-audit, cargo-deny, supply chain safety
rust-performance -- runtime profiling and build-time tuning
rust-unsafe -- unsafe code review, JNI safety patterns