From 637ae1cf7b7721f1b09fb27b86d0cf23c4fbe129 Mon Sep 17 00:00:00 2001 From: Matthew Stanton Date: Wed, 3 Jun 2026 19:38:54 -0400 Subject: [PATCH] Add opt-in internal vars to ndk-env --- README.md | 12 +++++ src/cli/env.rs | 117 +++++++++++++++++++++++++++++++++++++++------- tests/cli_help.rs | 1 + 3 files changed, 113 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 39950a56a8..e74b13fd14 100644 --- a/README.md +++ b/README.md @@ -152,6 +152,18 @@ Rust Analyzer and anything else with JSON-based environment handling: For configuring rust-analyzer, add the `--json` flag and paste the blob into the relevant place in the config. +By default, `cargo ndk-env` omits cargo-ndk's internal linker wrapper variables from its output. If you want to source +the generated environment and then build directly with Cargo using the exported linker configuration, add +`--include-internal` so the linker wrapper receives its required metadata. Run this in a subshell, or unset the +`_CARGO_NDK_*` variables before running `cargo ndk` again in the same shell: + +``` +( + source <(cargo ndk-env --target arm64-v8a --include-internal) + cargo build --target aarch64-linux-android +) +``` + ## Troubleshooting ### The build is complaining that some compiler builtins are missing. What do I do? diff --git a/src/cli/env.rs b/src/cli/env.rs index 396ddf2b5a..91889ff722 100644 --- a/src/cli/env.rs +++ b/src/cli/env.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeMap; +use std::{collections::BTreeMap, ffi::OsString}; use clap::Parser; @@ -35,6 +35,37 @@ struct EnvArgs { /// Print output in JSON format #[arg(long)] json: bool, + + /// Include internal cargo-ndk linker wrapper variables + #[arg(long)] + include_internal: bool, +} + +fn filter_env( + env: BTreeMap, + include_internal: bool, +) -> BTreeMap { + if include_internal { + env + } else { + env.into_iter() + .filter(|(k, _)| !k.starts_with('_')) + .collect() + } +} + +fn env_value(value: &OsString) -> &str { + value + .to_str() + .expect("cargo-ndk environment values should be valid UTF-8") +} + +fn shell_quote(value: &OsString) -> String { + format!("'{}'", env_value(value).replace('\'', "'\\''")) +} + +fn powershell_quote(value: &OsString) -> String { + format!("'{}'", env_value(value).replace('\'', "''")) } pub fn run(args: Vec) -> anyhow::Result<()> { @@ -56,40 +87,40 @@ pub fn run(args: Vec) -> anyhow::Result<()> { let clang_target = clang_target(args.target.triple(), args.platform); // Try command line, then config. Config falls back to defaults in any case. - let env = build_env( - args.target.triple(), - &ndk_home, - &ndk_version, - &clang_target, - args.platform, - &args.target.to_string(), - args.link_builtins, - args.link_libcxx_shared, - ) - .into_iter() - .filter(|(k, _)| !k.starts_with('_')) - .collect::>(); + let env = filter_env( + build_env( + args.target.triple(), + &ndk_home, + &ndk_version, + &clang_target, + args.platform, + &args.target.to_string(), + args.link_builtins, + args.link_libcxx_shared, + ), + args.include_internal, + ); if args.json { println!( "{}", serde_json::to_string_pretty( &env.into_iter() - .map(|(k, v)| (k, v.to_str().unwrap().to_string())) + .map(|(k, v)| (k, env_value(&v).to_string())) .collect::>() ) .unwrap() ); } else if args.powershell { for (k, v) in env { - println!("${{env:{k}}}={v:?}"); + println!("${{env:{k}}}={}", powershell_quote(&v)); } println!(); println!("# To import with PowerShell:"); println!("# cargo ndk-env --powershell | Invoke-Expression"); } else { for (k, v) in env { - println!("export {}={:?}", k.replace('-', "_"), v); + println!("export {}={}", k.replace('-', "_"), shell_quote(&v)); } println!(); println!("# To import with bash/zsh/etc:"); @@ -98,3 +129,55 @@ pub fn run(args: Vec) -> anyhow::Result<()> { Ok(()) } + +#[cfg(test)] +mod tests { + use std::{collections::BTreeMap, ffi::OsString}; + + use super::{filter_env, powershell_quote, shell_quote}; + + fn sample_env() -> BTreeMap { + BTreeMap::from([ + ("ANDROID_PLATFORM".to_string(), OsString::from("28")), + ( + "_CARGO_NDK_LINK_TARGET".to_string(), + OsString::from("--target=aarch64-linux-android28"), + ), + ]) + } + + #[test] + fn filter_env_hides_internal_values_by_default() { + let env = filter_env(sample_env(), false); + + assert!(env.contains_key("ANDROID_PLATFORM")); + assert!(!env.contains_key("_CARGO_NDK_LINK_TARGET")); + } + + #[test] + fn filter_env_keeps_internal_values_when_requested() { + let env = filter_env(sample_env(), true); + + assert!(env.contains_key("ANDROID_PLATFORM")); + assert_eq!( + env["_CARGO_NDK_LINK_TARGET"], + OsString::from("--target=aarch64-linux-android28") + ); + } + + #[test] + fn shell_quote_preserves_internal_ldflag_separator() { + assert_eq!( + shell_quote(&OsString::from("-L/path with spaces\x1f-lbuiltins's")), + "'-L/path with spaces\x1f-lbuiltins'\\''s'" + ); + } + + #[test] + fn powershell_quote_preserves_internal_ldflag_separator() { + assert_eq!( + powershell_quote(&OsString::from("-L/path with spaces\x1f-lbuiltins's")), + "'-L/path with spaces\x1f-lbuiltins''s'" + ); + } +} diff --git a/tests/cli_help.rs b/tests/cli_help.rs index 6fa41e2bdc..20ee8e9eb7 100644 --- a/tests/cli_help.rs +++ b/tests/cli_help.rs @@ -25,6 +25,7 @@ fn cargo_ndk_env_help_shows_env_format_options() { assert!(help.contains("Usage: cargo-ndk-env")); assert!(help.contains("--powershell")); assert!(help.contains("--json")); + assert!(help.contains("--include-internal")); assert!(!help.contains("--output-dir")); } }