ndk_build/
cargo.rs

1use crate::error::NdkError;
2use crate::ndk::Ndk;
3use crate::target::Target;
4use std::path::Path;
5use std::process::Command;
6
7pub fn cargo_ndk(
8    ndk: &Ndk,
9    target: Target,
10    sdk_version: u32,
11    target_dir: impl AsRef<Path>,
12) -> Result<Command, NdkError> {
13    let triple = target.rust_triple();
14    let clang_target = format!("--target={}{}", target.ndk_llvm_triple(), sdk_version);
15    let mut cargo = Command::new("cargo");
16
17    const SEP: &str = "\x1f";
18
19    // Read initial CARGO_ENCODED_/RUSTFLAGS
20    let mut rustflags = match std::env::var("CARGO_ENCODED_RUSTFLAGS") {
21        Ok(val) => {
22            if std::env::var_os("RUSTFLAGS").is_some() {
23                panic!(
24                    "Both `CARGO_ENCODED_RUSTFLAGS` and `RUSTFLAGS` were found in the environment, please clear one or the other before invoking this script"
25                );
26            }
27
28            val
29        }
30        Err(std::env::VarError::NotPresent) => {
31            match std::env::var("RUSTFLAGS") {
32                Ok(val) => {
33                    cargo.env_remove("RUSTFLAGS");
34
35                    // Same as cargo
36                    // https://github.com/rust-lang/cargo/blob/f6de921a5d807746e972d9d10a4d8e1ca21e1b1f/src/cargo/core/compiler/build_context/target_info.rs#L682-L690
37                    val.split(' ')
38                        .map(str::trim)
39                        .filter(|s| !s.is_empty())
40                        .collect::<Vec<_>>()
41                        .join(SEP)
42                }
43                Err(std::env::VarError::NotPresent) => String::new(),
44                Err(std::env::VarError::NotUnicode(_)) => {
45                    panic!("RUSTFLAGS environment variable contains non-unicode characters")
46                }
47            }
48        }
49        Err(std::env::VarError::NotUnicode(_)) => {
50            panic!("CARGO_ENCODED_RUSTFLAGS environment variable contains non-unicode characters")
51        }
52    };
53
54    let (clang, clang_pp) = ndk.clang()?;
55
56    // Configure cross-compiler for `cc` crate
57    // https://github.com/rust-lang/cc-rs#external-configuration-via-environment-variables
58    cargo.env(format!("CC_{}", triple), &clang);
59    cargo.env(format!("CFLAGS_{}", triple), &clang_target);
60    cargo.env(format!("CXX_{}", triple), &clang_pp);
61    cargo.env(format!("CXXFLAGS_{}", triple), &clang_target);
62
63    // Configure LINKER for `rustc`
64    // https://doc.rust-lang.org/beta/cargo/reference/environment-variables.html#configuration-environment-variables
65    cargo.env(cargo_env_target_cfg("LINKER", triple), &clang);
66    if !rustflags.is_empty() {
67        rustflags.push_str(SEP);
68    }
69    rustflags.push_str("-Clink-arg=");
70    rustflags.push_str(&clang_target);
71
72    let ar = ndk.toolchain_bin("ar", target)?;
73    cargo.env(format!("AR_{}", triple), &ar);
74    cargo.env(cargo_env_target_cfg("AR", triple), &ar);
75
76    // Workaround for https://github.com/rust-windowing/android-ndk-rs/issues/149:
77    // Rust (1.56 as of writing) still requires libgcc during linking, but this does
78    // not ship with the NDK anymore since NDK r23 beta 3.
79    // See https://github.com/rust-lang/rust/pull/85806 for a discussion on why libgcc
80    // is still required even after replacing it with libunwind in the source.
81    // XXX: Add an upper-bound on the Rust version whenever this is not necessary anymore.
82    if ndk.build_tag() > 7272597 {
83        let cargo_apk_link_dir = target_dir
84            .as_ref()
85            .join("cargo-apk-temp-extra-link-libraries");
86        std::fs::create_dir_all(&cargo_apk_link_dir)
87            .map_err(|e| NdkError::IoPathError(cargo_apk_link_dir.clone(), e))?;
88        let libgcc = cargo_apk_link_dir.join("libgcc.a");
89        std::fs::write(&libgcc, "INPUT(-lunwind)").map_err(|e| NdkError::IoPathError(libgcc, e))?;
90
91        // cdylibs in transitive dependencies still get built and also need this
92        // workaround linker flag, yet arguments passed to `cargo rustc` are only
93        // forwarded to the final compiler invocation rendering our workaround ineffective.
94        // The cargo page documenting this discrepancy (https://doc.rust-lang.org/cargo/commands/cargo-rustc.html)
95        // suggests to resort to RUSTFLAGS.
96        // Note that `rustflags` will never be empty because of an unconditional `.push_str` above,
97        // so we can safely start with appending \x1f here.
98        rustflags.push_str(SEP);
99        rustflags.push_str("-L");
100        rustflags.push_str(SEP);
101        rustflags.push_str(
102            cargo_apk_link_dir
103                .to_str()
104                .expect("Target dir must be valid UTF-8"),
105        );
106    }
107
108    cargo.env("CARGO_ENCODED_RUSTFLAGS", rustflags);
109
110    Ok(cargo)
111}
112
113fn cargo_env_target_cfg(tool: &str, target: &str) -> String {
114    let utarget = target.replace('-', "_");
115    let env = format!("CARGO_TARGET_{}_{}", &utarget, tool);
116    env.to_uppercase()
117}
118
119#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
120pub struct VersionCode {
121    major: u8,
122    minor: u8,
123    patch: u8,
124}
125
126impl VersionCode {
127    pub fn new(major: u8, minor: u8, patch: u8) -> Self {
128        Self {
129            major,
130            minor,
131            patch,
132        }
133    }
134
135    pub fn from_semver(version: &str) -> Result<Self, NdkError> {
136        let mut iter = version.split(|c1| ['.', '-', '+'].iter().any(|c2| c1 == *c2));
137        let mut p = || {
138            iter.next()
139                .ok_or(NdkError::InvalidSemver)?
140                .parse()
141                .map_err(|_| NdkError::InvalidSemver)
142        };
143        Ok(Self::new(p()?, p()?, p()?))
144    }
145
146    pub fn to_code(&self, apk_id: u8) -> u32 {
147        (apk_id as u32) << 24
148            | (self.major as u32) << 16
149            | (self.minor as u32) << 8
150            | self.patch as u32
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157
158    #[test]
159    fn from_semver() {
160        let v = VersionCode::from_semver("0.0.0").unwrap();
161        assert_eq!(v, VersionCode::new(0, 0, 0));
162        let v = VersionCode::from_semver("254.254.254-alpha.fix+2").unwrap();
163        assert_eq!(v, VersionCode::new(254, 254, 254));
164    }
165}