cargo-ndk handles the environment a Rust build needs for Android
Compile Rust projects with the Android NDK without hassle
At a glance
- What is it?
- cargo-ndk is a cargo extension that takes the pain out of cross compiling Rust against the Android NDK. It sets the toolchain and linker environment, lays the resulting shared libraries out in the jniLibs structure an Android project expects, and adds two subcommands for the parts of the workflow that are otherwise awkward, running on a device through adb and exporting the environment to an editor.
- Who is it for?
- cargo-ndk suits a Rust developer whose library has to ship inside an Android application, since it removes the environment variables, the sysroot paths and the jniLibs layout from the build script, and it suits anyone who wants their editor to understand the same configuration the build uses, thanks to the environment export.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 6 days ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Three subcommands instead of one long environment script
The tool is a cargo plugin, installed with `cargo install cargo-ndk` or with `cargo binstall cargo-ndk` if you would rather not compile it in your CI pipeline, and it provides three subcommands. The first, `cargo ndk`, is a passthrough for cargo that applies the relevant environment variables so the build succeeds against the NDK, and everything after the flags goes straight to cargo, which means the usual cargo flags still work. The second, `cargo ndk-test`, runs your tests on a connected Android device through adb. The third, `cargo ndk-env`, generates shell exports, PowerShell environment variables or JSON, and the documentation is blunt about why that exists, to make rust-analyzer happy, or other unspeakable crimes. The value of the split is that each of the three awkward parts of an NDK build is a separate tool rather than a block of shell you have to keep in your own scripts.
Building two ARM architectures into jniLibs
The example in the documentation is the case most people hit, a library that has to exist for both 32 bit and 64 bit ARM:
cargo ndk -t armeabi-v7a -t arm64-v8a -o ./jniLibs build --releaseThe `-t` flags name the Android ABIs to build, and the ordinary Rust target triples work as well, so `aarch64-linux-android` is an accepted alternative to `arm64-v8a`. The `-o` flag sets where the output goes, and this is not merely a convenience, because the directory name and the layout inside it have to match what the Android build system expects, which is what the documentation means by generating the correct jniLibs directory structure. Everything after the flags is passed on to cargo, so `--release` behaves as it would anywhere else. The toolchains have to be installed before any of this works, and the documented way to do that is rustup:
rustup target add \
aarch64-linux-android \
armv7-linux-androideabi \
x86_64-linux-android \
i686-linux-androidThe four targets are the two ARM ABIs and the two x86 ones, and the instructions say to modify the list as your use case requires.
The NDK is discovered, not managed
If you installed the NDK with Android Studio to its default location, the tool detects the most recent NDK version and uses it, with no configuration at all. When that guess is wrong, the path to the NDK root directory goes into the `ANDROID_NDK_HOME` environment variable and that path is used instead. The division of labour is worth being clear about, because cargo-ndk does not install, update or validate the NDK, it only finds one and derives the paths inside it. The same applies to the toolchains, which rustup manages and cargo-ndk assumes are present. That is a deliberate division, and it is also the source of most confusion for a newcomer, because a missing target and a missing NDK produce different errors and the tool only helps with one of them. The environment it exports makes the paths visible, which is what the next section is about.
cargo-ndk-env is what makes the editor work
Every build script, every C dependency and every editor needs to agree on the same sysroot, and the tool exports the whole set. The variables include `CARGO_NDK_ANDROID_PLATFORM` for the platform API number, `CARGO_NDK_NDK_VERSION` for the detected revision, `CARGO_NDK_OUTPUT_PATH`, `CARGO_NDK_SYSROOT_PATH`, `CARGO_NDK_SYSROOT_TARGET`, which is the target name inside the sysroot and which differs from the LLVM triples, `CARGO_NDK_SYSROOT_LIBS_PATH`, `LIBCLANG_PATH` when the shared library is found, and the standard `ANDROID_PLATFORM` and `ANDROID_ABI` names. Bindgen's environment is configured and exported as well, and the variables for C dependencies are derived the same way the `cc` crate derives them, so a `cc` based build script picks them up without changes. You can print the whole set for a shell:
source <(cargo ndk-env)In PowerShell it is `cargo ndk-env --powershell | Invoke-Expression`, and for rust-analyzer and anything else that reads JSON there is a `--json` flag. There is a subtlety the documentation takes care to spell out, because it breaks in a confusing way. By default the internal linker wrapper variables are left out, and if you want to source the environment and then build with plain cargo using the exported linker configuration you need `--include-internal`, and you should run it in a subshell so the `_CARGO_NDK_*` variables do not leak into the next `cargo ndk` call.
Running and testing on a device through adb
Two pieces of the workflow need a device, and both are handled. The first is `cargo run` for an Android build, which normally fails because the binary is an Android binary and not a native one. The fix is a runner in the cargo configuration, added per target:
[target.aarch64-linux-android]
runner = "cargo ndk-runner"The second is testing, and `cargo ndk-test` does the same thing more directly, for example `cargo ndk-test -t armeabi-v7a`. Under the hood it uses the same runner, pushing the built test binaries to the connected device and running them in the Android shell, and reporting the results back. So the same `cargo test` semantics apply, just against a different machine, which is the part that matters if your test suite touches anything the host would provide differently. Verbosity works as it does elsewhere, with `-v` and `-vv` passed after the cargo command.
Two flags solve most broken Android builds
The troubleshooting section is short, which is a sign that the common failures are few. The first is a build complaining that some compiler builtins are missing, and the fix is to add `--link-builtins` to the cargo ndk build command. The second is a need to link `libc++_shared.so`, and the fix is `--link-libcxx-shared`. Both are about the C++ side of the NDK rather than about Rust, which is expected, since the Rust standard library is not the thing being linked but the platform libraries underneath it. There is a third, quieter mechanism in the same area, the environment variables for C dependencies, which the tool derives the way the `cc` crate does, so a build script that shells out to a C compiler picks up the right target, sysroot and flags without the project having to set them by hand.
The package, the hosts and the release gap
The crate is `cargo-ndk` at version 4.1.2, authored by Brendan Molloy, licensed under Apache-2.0 OR MIT, on edition 2024 with a minimum supported Rust version of 1.86, and it ships three binaries at `src/bin/cargo-ndk.rs`, `cargo-ndk-env.rs` and `cargo-ndk-test.rs`, with the main one as the default run. The dependencies are the usual plumbing for this kind of tool, cargo metadata to read the workspace, clap for the command line, serde and toml for the configuration, libc, filetime, dunce and pathos for filesystem handling, and a windows-sys dependency with the console, job objects and file system features under a Windows target condition, alongside a colour handling crate for Windows terminals. The supported hosts section lists Linux and macOS on both x86_64 and arm64, and the examples directory shows what people build with it, with basic, bindgen, ndk-sys and openssl cases. The release history is the one soft spot, with v4.1.2, v4.1.1 and v4.1.0 all published in the first week of August 2025 and the last push to the repository on 2026-10-17, so there is work on the branch that has not been packaged into a release yet.
Editorial conclusion
cargo-ndk suits a Rust developer whose library has to ship inside an Android application, since it removes the environment variables, the sysroot paths and the jniLibs layout from the build script, and it suits anyone who wants their editor to understand the same configuration the build uses, thanks to the environment export. It is a poor fit if you only build for a single architecture on a workstation, since that is plain cargo, and it is a poor fit if you want the newest NDK behaviour, because the tool detects an installed NDK rather than managing one. Before you adopt it, check three things: which targets you need installed through rustup, since the toolchain has to be there before cargo-ndk can use it, whether the environment export works in the shell you actually use, and the version you pin, with v4.1.2 published on 2025-08-09 while the last push to the repository was on 2026-10-17.
Frequently asked questions
How do I install cargo-ndk?
With `cargo install cargo-ndk`, or with `cargo binstall cargo-ndk` to skip compiling it in your CI pipeline. You also need the Rust targets you intend to build, installed with rustup, for example aarch64-linux-android, armv7-linux-androideabi, x86_64-linux-android and i686-linux-android.
How do I build a Rust library for Android with cargo-ndk?
With a command such as `cargo ndk -t armeabi-v7a -t arm64-v8a -o ./jniLibs build --release`. The target flags take the Android ABIs, ordinary Rust triples work too, the output flag sets where the shared libraries go in the layout Android expects, and the rest is passed to cargo.
How do I run my Rust tests on an Android device?
With `cargo ndk-test -t armeabi-v7a`, which uses the cargo ndk-runner under the hood to push the test binaries to the connected device and run them in the Android shell. You can also set a runner in your cargo configuration so that a plain `cargo run` works for Android targets.
What does cargo ndk-env do?
It prints the environment cargo-ndk would use, in shell exports, PowerShell environment variables or JSON, so that rust-analyzer, an editor or a build script can use the same sysroot and linker settings. Use `source <(cargo ndk-env)`, `cargo ndk-env --powershell | Invoke-Expression`, or the --json flag, and add --include-internal when you want the internal linker wrapper variables as well.
My Android build complains about missing compiler builtins or libc++_shared.so. What do I do?
Add --link-builtins to the cargo ndk build command for the missing compiler builtins, and --link-libcxx-shared when you need to link libc++_shared.so. Both are listed in the troubleshooting section of the documentation.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/bbqsrc-cargo-ndk)Was this useful?