crossbundle_tools/commands/android/common/rust_compile/
rust_compiler.rs

1use super::*;
2use crate::{error::*, types::*};
3
4pub fn rust_compile(
5    ndk: &AndroidNdk,
6    build_target: AndroidTarget,
7    project_path: &std::path::Path,
8    profile: Profile,
9    features: Vec<String>,
10    all_features: bool,
11    no_default_features: bool,
12    target_sdk_version: u32,
13    lib_name: &str,
14    app_wrapper: AppWrapper,
15) -> Result<()> {
16    // Specify path to workspace
17    let rust_triple = build_target.rust_triple();
18
19    // Set environment variables needed for use with the cc crate
20    let (clang, clang_pp) = ndk.clang(build_target, target_sdk_version)?;
21    std::env::set_var(format!("CC_{}", rust_triple), &clang);
22    std::env::set_var(format!("CXX_{}", rust_triple), &clang_pp);
23    std::env::set_var(cargo_env_target_cfg("LINKER", rust_triple), &clang);
24    let ar = ndk.toolchain_bin("ar", build_target)?;
25    std::env::set_var(format!("AR_{}", rust_triple), &ar);
26
27    let cargo_config = cargo::util::Config::default()?;
28    let workspace = cargo::core::Workspace::new(&project_path.join("Cargo.toml"), &cargo_config)?;
29
30    // Define directory to build project
31    let build_target_dir = workspace
32        .root()
33        .join("target")
34        .join(rust_triple)
35        .join(profile);
36    std::fs::create_dir_all(&build_target_dir).unwrap();
37
38    set_cmake_vars(build_target, ndk, target_sdk_version, &build_target_dir)?;
39
40    // Use libc++. It is current default C++ runtime
41    std::env::set_var("CXXSTDLIB", "c++");
42
43    // Configure compilation options so that we will build the desired build_target
44    let opts = compile_options::compile_options(
45        &workspace,
46        build_target,
47        &features,
48        all_features,
49        no_default_features,
50        &build_target_dir,
51        lib_name,
52        profile,
53    )?;
54
55    // Create the executor
56    let executor: std::sync::Arc<dyn cargo::core::compiler::Executor> =
57        std::sync::Arc::new(SharedLibraryExecutor {
58            target_sdk_version,
59            build_target_dir,
60            build_target,
61            ndk: ndk.clone(),
62            profile,
63            nostrip: false,
64            app_wrapper,
65        });
66
67    // Compile all targets for the requested build target
68    cargo::ops::compile_with_exec(&workspace, &opts, &executor)?;
69    Ok(())
70}
71
72/// Executor which builds binary and example targets as static libraries
73struct SharedLibraryExecutor {
74    target_sdk_version: u32,
75    build_target_dir: std::path::PathBuf,
76    build_target: AndroidTarget,
77    ndk: AndroidNdk,
78    profile: Profile,
79    nostrip: bool,
80    app_wrapper: AppWrapper,
81}
82
83impl cargo::core::compiler::Executor for SharedLibraryExecutor {
84    fn exec(
85        &self,
86        cmd: &cargo_util::ProcessBuilder,
87        _id: cargo::core::PackageId,
88        target: &cargo::core::Target,
89        mode: cargo::core::compiler::CompileMode,
90        on_stdout_line: &mut dyn FnMut(&str) -> cargo::util::errors::CargoResult<()>,
91        on_stderr_line: &mut dyn FnMut(&str) -> cargo::util::errors::CargoResult<()>,
92    ) -> cargo::util::errors::CargoResult<()> {
93        if mode == cargo::core::compiler::CompileMode::Build
94            && (target.kind() == &cargo::core::manifest::TargetKind::Bin
95                || target.kind() == &cargo::core::manifest::TargetKind::ExampleBin)
96        {
97            let mut new_args = cmd.get_args().cloned().collect::<Vec<_>>();
98
99            let extra_code = match self.app_wrapper {
100                AppWrapper::Quad => consts::QUAD_EXTRA_CODE,
101                AppWrapper::NdkGlue => consts::NDK_GLUE_EXTRA_CODE,
102            };
103
104            let path =
105                if let cargo::core::manifest::TargetSourcePath::Path(path) = target.src_path() {
106                    path.to_owned()
107                } else {
108                    // Ignore other values
109                    return Ok(());
110                };
111
112            // Generate tmp_file with bevy or quad extra code depending on either quad or ndk glue
113            // dependency
114            let tmp_file = match self.app_wrapper {
115                AppWrapper::Quad => gen_tmp_lib_file::generate_lib_file(&path, extra_code)?,
116                AppWrapper::NdkGlue => gen_tmp_lib_file::generate_lib_file(&path, extra_code)?,
117            };
118
119            // Replace source argument
120            let filename = path.file_name().unwrap().to_owned();
121            let source_arg = new_args.iter_mut().find_map(|arg| {
122                let tmp = std::path::Path::new(&arg).file_name().unwrap();
123                if filename == tmp {
124                    Some(arg)
125                } else {
126                    None
127                }
128            });
129
130            if let Some(source_arg) = source_arg {
131                // Build a new relative path to the temporary source file and use it as the source
132                // argument Using an absolute path causes compatibility issues in
133                // some cases under windows If a UNC path is used then relative
134                // paths used in "include* macros" may not work if the relative path
135                // includes "/" instead of "\"
136                let mut path_arg = std::path::PathBuf::from(&source_arg);
137                path_arg.set_file_name(tmp_file.path().file_name().unwrap());
138                *source_arg = path_arg.into_os_string();
139            } else {
140                return Err(anyhow::Error::msg(format!(
141                    "Unable to replace source argument when building target: {}",
142                    target.name()
143                )));
144            }
145
146            // Create output directory inside the build target directory
147            std::fs::create_dir_all(&self.build_target_dir)
148                .map_err(|_| anyhow::Error::msg("Failed to create build target directory"))?;
149
150            // Change crate-type from bin to cdylib
151            let mut iter = new_args.iter_mut().rev().peekable();
152            while let Some(arg) = iter.next() {
153                if let Some(prev_arg) = iter.peek() {
154                    if *prev_arg == "--crate-type" && arg == "bin" {
155                        *arg = "cdylib".into();
156                    } else if *prev_arg == "--out-dir" {
157                        *arg = self.build_target_dir.clone().into();
158                    }
159                }
160            }
161
162            // Workaround from https://github.com/rust-windowing/android-ndk-rs/issues/149:
163            // Rust (1.56 as of writing) still requires libgcc during linking, but this does
164            // not ship with the NDK anymore since NDK r23 beta 3.
165            // See https://github.com/rust-lang/rust/pull/85806 for a discussion on why libgcc
166            // is still required even after replacing it with libunwind in the source.
167            // XXX: Add an upper-bound on the Rust version whenever this is not necessary anymore.
168            let mut cmd = cmd.clone();
169            let build_tag = self.ndk.build_tag();
170            let tool_root = self.ndk.toolchain_dir().map_err(|_| {
171                anyhow::Error::msg("Failed to get access to the toolchain directory")
172            })?;
173            if build_tag > 7272597 {
174                let error_msg = anyhow::Error::msg("Failed to write content into libgcc.a file");
175                let mut args = match self.app_wrapper {
176                    AppWrapper::Quad => {
177                        new_ndk_quad_args(tool_root, &self.build_target, self.target_sdk_version)
178                            .map_err(|_| error_msg)?
179                    }
180                    AppWrapper::NdkGlue => linker_args(&tool_root).map_err(|_| error_msg)?,
181                };
182                new_args.append(&mut args);
183                cmd.args_replace(&new_args);
184                cmd.exec_with_streaming(on_stdout_line, on_stderr_line, false)
185                    .map(drop)?;
186            } else if self.app_wrapper == AppWrapper::Quad {
187                // Set linker arguments using in ndk =< 22
188                let mut linker_args =
189                    add_clinker_args(&self.ndk, &self.build_target, self.target_sdk_version)?;
190                new_args.append(&mut linker_args);
191
192                // Strip symbols for release builds
193                if !self.nostrip && self.profile == Profile::Release {
194                    new_args.push("-Clink-arg=-strip-all".into());
195                }
196
197                // Create new command
198                let mut cmd = cmd.clone();
199                cmd.args_replace(&new_args);
200
201                // Execute the command
202                cmd.exec_with_streaming(on_stdout_line, on_stderr_line, false)
203                    .map(drop)?;
204            } else if self.app_wrapper == AppWrapper::NdkGlue {
205                cmd.args_replace(&new_args);
206
207                cmd.exec_with_streaming(on_stdout_line, on_stderr_line, false)
208                    .map(drop)?;
209            }
210        } else if mode == cargo::core::compiler::CompileMode::Test {
211            // This occurs when --all-targets is specified
212            return Err(anyhow::Error::msg(format!(
213                "Ignoring CompileMode::Test for target: {}",
214                target.name()
215            )));
216        } else if mode == cargo::core::compiler::CompileMode::Build {
217            let mut new_args = cmd.get_args().cloned().collect::<Vec<_>>();
218
219            // Change crate-type from cdylib to rlib
220            let mut iter = new_args.iter_mut().rev().peekable();
221            while let Some(arg) = iter.next() {
222                if let Some(prev_arg) = iter.peek() {
223                    if *prev_arg == "--crate-type" && arg == "cdylib" {
224                        *arg = "rlib".into();
225                    }
226                }
227            }
228            let mut cmd = cmd.clone();
229            cmd.args_replace(&new_args);
230            cmd.exec_with_streaming(on_stdout_line, on_stderr_line, false)
231                .map(drop)?
232        } else {
233            cmd.exec_with_streaming(on_stdout_line, on_stderr_line, false)
234                .map(drop)?
235        }
236        Ok(())
237    }
238}
239
240/// Helper function that allows to return environment argument with specified tool
241pub fn cargo_env_target_cfg(tool: &str, target: &str) -> String {
242    let utarget = target.replace('-', "_");
243    let env = format!("CARGO_TARGET_{}_{}", &utarget, tool);
244    env.to_uppercase()
245}