ndk_build2/
cargo.rs

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