creator_tools/tools/
android_ndk.rs

1use crate::error::*;
2use crate::types::AndroidTarget;
3use std::path::{Path, PathBuf};
4use std::process::Command;
5
6#[derive(Debug)]
7pub struct AndroidNdk {
8    ndk_path: PathBuf,
9}
10
11impl AndroidNdk {
12    pub fn from_env(sdk_path: Option<&Path>) -> Result<Self> {
13        let ndk_path = {
14            let ndk_path = std::env::var("ANDROID_NDK_ROOT")
15                .ok()
16                .or_else(|| std::env::var("ANDROID_NDK_PATH").ok())
17                .or_else(|| std::env::var("ANDROID_NDK_HOME").ok())
18                .or_else(|| std::env::var("NDK_HOME").ok());
19            // Default ndk installation path
20            if ndk_path.is_none()
21                && sdk_path.is_some()
22                && sdk_path.as_ref().unwrap().join("ndk-bundle").exists()
23            {
24                sdk_path.unwrap().join("ndk-bundle")
25            } else {
26                PathBuf::from(ndk_path.ok_or(AndroidError::AndroidNdkNotFound)?)
27            }
28        };
29        Ok(Self { ndk_path })
30    }
31
32    pub fn ndk_path(&self) -> &Path {
33        &self.ndk_path
34    }
35
36    pub fn toolchain_dir(&self) -> Result<PathBuf> {
37        let host_os = std::env::var("HOST").ok();
38        let host_contains = |s| host_os.as_ref().map(|h| h.contains(s)).unwrap_or(false);
39        let arch = if host_contains("linux") {
40            "linux"
41        } else if host_contains("macos") {
42            "darwin"
43        } else if host_contains("windows") {
44            "windows"
45        } else if cfg!(target_os = "linux") {
46            "linux"
47        } else if cfg!(target_os = "macos") {
48            "darwin"
49        } else if cfg!(target_os = "windows") {
50            "windows"
51        } else {
52            return match host_os {
53                Some(host_os) => Err(AndroidError::UnsupportedHost(host_os)),
54                _ => Err(AndroidError::UnsupportedTarget),
55            }?;
56        };
57        let mut toolchain_dir = self
58            .ndk_path
59            .join("toolchains")
60            .join("llvm")
61            .join("prebuilt")
62            .join(format!("{}-x86_64", arch));
63        if !toolchain_dir.exists() {
64            toolchain_dir.set_file_name(arch);
65        }
66        if !toolchain_dir.exists() {
67            return Err(Error::PathNotFound(toolchain_dir));
68        }
69        Ok(toolchain_dir)
70    }
71
72    pub fn clang(&self, target: AndroidTarget, platform: u32) -> Result<(PathBuf, PathBuf)> {
73        #[cfg(target_os = "windows")]
74        let ext = ".cmd";
75        #[cfg(not(target_os = "windows"))]
76        let ext = "";
77        let bin_name = format!("{}{}-clang", target.ndk_llvm_triple(), platform);
78        let bin_path = self.toolchain_dir()?.join("bin");
79        let clang = bin_path.join(format!("{}{}", &bin_name, ext));
80        if !clang.exists() {
81            return Err(Error::PathNotFound(clang));
82        }
83        let clang_pp = bin_path.join(format!("{}++{}", &bin_name, ext));
84        if !clang_pp.exists() {
85            return Err(Error::PathNotFound(clang_pp));
86        }
87        Ok((clang, clang_pp))
88    }
89
90    pub fn toolchain_bin(&self, name: &str, build_target: AndroidTarget) -> Result<PathBuf> {
91        #[cfg(target_os = "windows")]
92        let ext = ".exe";
93        #[cfg(not(target_os = "windows"))]
94        let ext = "";
95        let toolchain_path = self.toolchain_dir()?.join("bin");
96        // Since r21 (https://github.com/android/ndk/wiki/Changelog-r21) LLVM binutils are included _for testing_;
97        // Since r22 (https://github.com/android/ndk/wiki/Changelog-r22) GNU binutils are deprecated in favour of LLVM's;
98        // Since r23 (https://github.com/android/ndk/wiki/Changelog-r23) GNU binutils have been removed.
99        // To maintain stability with the current ndk-build crate release, prefer GNU binutils for
100        // as long as it is provided by the NDK instead of trying to use llvm-* from r21 onwards.
101        let gnu_bin = format!("{}-{}{}", build_target.ndk_triple(), name, ext);
102        let gnu_path = toolchain_path.join(&gnu_bin);
103        if gnu_path.exists() {
104            Ok(gnu_path)
105        } else {
106            let llvm_bin = format!("llvm-{}{}", name, ext);
107            let llvm_path = toolchain_path.join(&llvm_bin);
108            llvm_path
109                .exists()
110                .then(|| llvm_path)
111                .ok_or(Error::ToolchainBinaryNotFound {
112                    toolchain_path,
113                    gnu_bin,
114                    llvm_bin,
115                })
116        }
117    }
118
119    pub fn readelf(&self, build_target: AndroidTarget) -> Result<Command> {
120        let readelf_path = self.toolchain_bin("readelf", build_target)?;
121        Ok(Command::new(readelf_path))
122    }
123
124    pub fn sysroot_lib_dir(&self, build_target: AndroidTarget) -> Result<PathBuf> {
125        let sysroot_lib_dir = self
126            .toolchain_dir()?
127            .join("sysroot")
128            .join("usr")
129            .join("lib")
130            .join(build_target.ndk_triple());
131        if !sysroot_lib_dir.exists() {
132            return Err(Error::PathNotFound(sysroot_lib_dir));
133        }
134        Ok(sysroot_lib_dir)
135    }
136
137    pub fn sysroot_platform_lib_dir(
138        &self,
139        build_target: AndroidTarget,
140        min_sdk_version: u32,
141    ) -> Result<PathBuf> {
142        let sysroot_lib_dir = self.sysroot_lib_dir(build_target)?;
143        // Look for a platform <= min_sdk_version
144        let mut tmp_platform = min_sdk_version;
145        while tmp_platform > 1 {
146            let path = sysroot_lib_dir.join(tmp_platform.to_string());
147            if path.exists() {
148                return Ok(path);
149            }
150            tmp_platform += 1;
151        }
152        // Look for the minimum API level supported by the NDK
153        let mut tmp_platform = min_sdk_version;
154        while tmp_platform < 100 {
155            let path = sysroot_lib_dir.join(tmp_platform.to_string());
156            if path.exists() {
157                return Ok(path);
158            }
159            tmp_platform += 1;
160        }
161        Err(AndroidError::PlatformNotFound(min_sdk_version).into())
162    }
163}