use super::tempfile::TempFile;
use super::util::{self, BuildTarget};
use crate::config::AndroidBuildTarget;
use crate::config::AndroidConfig;
use cargo::core::compiler::Executor;
use cargo::core::compiler::{CompileKind, CompileMode, CompileTarget};
use cargo::core::manifest::TargetSourcePath;
use cargo::core::{PackageId, Target, TargetKind, Workspace};
use cargo::ops::CompileOptions;
use cargo::util::{process, CargoResult, ProcessBuilder};
use failure::format_err;
use multimap::MultiMap;
use std::ffi::{OsStr, OsString};
use std::fs;
use std::fs::File;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
pub struct SharedLibrary {
pub abi: AndroidBuildTarget,
pub path: PathBuf,
pub filename: String,
}
pub struct SharedLibraries {
pub shared_libraries: MultiMap<BuildTarget, SharedLibrary>,
}
pub fn build_shared_libraries(
workspace: &Workspace,
config: &AndroidConfig,
mut options: CompileOptions,
root_build_dir: &PathBuf,
) -> CargoResult<SharedLibraries> {
let android_native_glue_src_path = write_native_app_glue_src(&root_build_dir)?;
let shared_libraries: Arc<Mutex<MultiMap<BuildTarget, SharedLibrary>>> =
Arc::new(Mutex::new(MultiMap::new()));
for &build_target in config.build_targets.iter() {
let build_target_dir = root_build_dir.join(build_target.android_abi());
fs::create_dir_all(&build_target_dir).unwrap();
std::env::set_var("CC", util::find_clang(config, build_target)?);
std::env::set_var("CXX", util::find_clang_cpp(config, build_target)?);
std::env::set_var("AR", util::find_ar(config, build_target)?);
std::env::set_var("CXXSTDLIB", "c++");
let cmake_toolchain_path = write_cmake_toolchain(config, &build_target_dir, build_target)?;
std::env::set_var("CMAKE_TOOLCHAIN_FILE", cmake_toolchain_path);
std::env::set_var("CMAKE_GENERATOR", r#"Unix Makefiles"#);
std::env::set_var("CMAKE_MAKE_PROGRAM", util::make_path(config));
let android_native_glue_object = build_android_native_glue(
config,
&android_native_glue_src_path,
&build_target_dir,
build_target,
)?;
options.build_config.requested_kind =
CompileKind::Target(CompileTarget::new(build_target.rust_triple())?);
let config = Arc::new(config.clone());
let executor: Arc<dyn Executor> = Arc::new(SharedLibraryExecutor {
config: Arc::clone(&config),
build_target_dir: build_target_dir.clone(),
android_native_glue_object,
build_target,
shared_libraries: shared_libraries.clone(),
});
cargo::ops::compile_with_exec(workspace, &options, &executor)?;
}
let mut shared_libraries = shared_libraries.lock().unwrap();
let shared_libraries = std::mem::replace(&mut *shared_libraries, MultiMap::new());
Ok(SharedLibraries { shared_libraries })
}
struct SharedLibraryExecutor {
config: Arc<AndroidConfig>,
build_target_dir: PathBuf,
android_native_glue_object: PathBuf,
build_target: AndroidBuildTarget,
shared_libraries: Arc<Mutex<MultiMap<BuildTarget, SharedLibrary>>>,
}
impl<'a> Executor for SharedLibraryExecutor {
fn exec(
&self,
cmd: ProcessBuilder,
_id: PackageId,
target: &Target,
mode: CompileMode,
on_stdout_line: &mut dyn FnMut(&str) -> CargoResult<()>,
on_stderr_line: &mut dyn FnMut(&str) -> CargoResult<()>,
) -> CargoResult<()> {
if mode == CompileMode::Build
&& (target.kind() == &TargetKind::Bin || target.kind() == &TargetKind::ExampleBin)
{
let mut new_args = cmd.get_args().to_owned();
let path = if let TargetSourcePath::Path(path) = target.src_path() {
path.to_owned()
} else {
return Ok(());
};
let original_src_filepath = path.canonicalize()?;
let tmp_lib_filepath = original_src_filepath.parent().unwrap().join(format!(
"__cargo_apk_{}.tmp",
original_src_filepath
.file_stem()
.map(|s| s.to_string_lossy().into_owned())
.unwrap_or_else(String::new)
));
let original_contents = fs::read_to_string(original_src_filepath).unwrap();
let tmp_file = TempFile::new(tmp_lib_filepath.clone(), |lib_src_file| {
let extra_code = r##"
mod cargo_apk_glue_code {
use std::os::raw::c_void;
// Exported function which is called be Android's NativeActivity
#[no_mangle]
pub unsafe extern "C" fn ANativeActivity_onCreate(
activity: *mut c_void,
saved_state: *mut c_void,
saved_state_size: usize,
) {
native_app_glue_onCreate(activity, saved_state, saved_state_size);
}
extern "C" {
#[allow(non_snake_case)]
fn native_app_glue_onCreate(
activity: *mut c_void,
saved_state: *mut c_void,
saved_state_size: usize,
);
}
#[no_mangle]
extern "C" fn android_main(_app: *mut c_void) {
let _ = super::main();
}
#[link(name = "android")]
#[link(name = "log")]
extern "C" {}
}"##;
writeln!( lib_src_file, "{}\n{}", original_contents, extra_code)?;
Ok(())
}).map_err(|e| format_err!(
"Unable to create temporary source file `{}`. Source directory must be writable. Cargo-apk creates temporary source files as part of the build process. {}.", tmp_lib_filepath.to_string_lossy(), e)
)?;
let filename = path.file_name().unwrap().to_owned();
let source_arg = new_args.iter_mut().find_map(|arg| {
let path_arg = Path::new(&arg);
let tmp = path_arg.file_name().unwrap();
if filename == tmp {
Some(arg)
} else {
None
}
});
if let Some(source_arg) = source_arg {
let path_arg = Path::new(&source_arg);
let mut path_arg = path_arg.to_path_buf();
path_arg.set_file_name(tmp_file.path.file_name().unwrap());
*source_arg = path_arg.into_os_string();
} else {
return Err(format_err!(
"Unable to replace source argument when building target '{}'",
target.name()
));
}
let build_path = self.build_target_dir.join("build");
fs::create_dir_all(&build_path).unwrap();
let mut iter = new_args.iter_mut().rev().peekable();
while let Some(arg) = iter.next() {
if let Some(prev_arg) = iter.peek() {
if *prev_arg == "--crate-type" && arg == "bin" {
*arg = "cdylib".into();
} else if *prev_arg == "--out-dir" {
*arg = build_path.clone().into();
}
}
}
fn build_arg(start: &str, end: impl AsRef<OsStr>) -> OsString {
let mut new_arg = OsString::new();
new_arg.push(start);
new_arg.push(end.as_ref());
new_arg
}
let tool_root = util::llvm_toolchain_root(&self.config);
let linker_path = tool_root
.join("bin")
.join(format!("{}-ld", &self.build_target.ndk_triple()));
let sysroot = tool_root.join("sysroot");
let version_independent_libraries_path = sysroot
.join("usr")
.join("lib")
.join(&self.build_target.ndk_triple());
let version_specific_libraries_path =
util::find_ndk_path(self.config.min_sdk_version, |platform| {
version_independent_libraries_path.join(platform.to_string())
})?;
let gcc_lib_path = tool_root
.join("lib/gcc")
.join(&self.build_target.ndk_triple())
.join("4.9.x");
new_args.push(build_arg("-Clinker=", linker_path));
new_args.push("-Clinker-flavor=ld".into());
new_args.push(build_arg("-Clink-arg=--sysroot=", sysroot));
new_args.push(build_arg("-Clink-arg=-L", version_specific_libraries_path));
new_args.push(build_arg(
"-Clink-arg=-L",
&version_independent_libraries_path,
));
new_args.push(build_arg("-Clink-arg=-L", gcc_lib_path));
new_args.push(build_arg("-Clink-arg=", &self.android_native_glue_object));
if self.config.release {
new_args.push("-Clink-arg=-strip-all".into());
}
new_args.push("-Crelocation-model=pic".into());
let mut cmd = cmd.clone();
cmd.args_replace(&new_args);
cmd.exec_with_streaming(on_stdout_line, on_stderr_line, false)
.map(drop)?;
let stdout = cmd.arg("--print").arg("file-names").exec_with_output()?;
let stdout = String::from_utf8(stdout.stdout).unwrap();
let library_path = build_path.join(stdout.lines().next().unwrap());
let mut shared_libraries = self.shared_libraries.lock().unwrap();
shared_libraries.insert(
target.into(),
SharedLibrary {
abi: self.build_target,
path: library_path.clone(),
filename: format!("lib{}.so", target.name()),
},
);
let readelf_path = util::find_readelf(&self.config, self.build_target)?;
let readelf_output = process(readelf_path)
.arg("-d")
.arg(&library_path)
.exec_with_output()?;
use std::io::BufRead;
let dynamically_links_to_cpp_standard_lib = readelf_output.stdout.lines().any(|l| {
let l = l.as_ref().unwrap();
l.contains("(NEEDED)") && l.contains("[libc++_shared.so]")
});
if dynamically_links_to_cpp_standard_lib {
let cpp_library_path = version_independent_libraries_path.join("libc++_shared.so");
shared_libraries.insert(
target.into(),
SharedLibrary {
abi: self.build_target,
path: cpp_library_path,
filename: "libc++_shared.so".into(),
},
);
}
} else if mode == CompileMode::Test {
eprintln!("Ignoring CompileMode::Test for target: {}", target.name());
} else if mode == CompileMode::Build {
let mut new_args = cmd.get_args().to_owned();
let mut iter = new_args.iter_mut().rev().peekable();
while let Some(arg) = iter.next() {
if let Some(prev_arg) = iter.peek() {
if *prev_arg == "--crate-type" && arg == "cdylib" {
*arg = "rlib".into();
}
}
}
let mut cmd = cmd.clone();
cmd.args_replace(&new_args);
cmd.exec_with_streaming(on_stdout_line, on_stderr_line, false)
.map(drop)?
} else {
cmd.exec_with_streaming(on_stdout_line, on_stderr_line, false)
.map(drop)?
}
Ok(())
}
}
fn write_native_app_glue_src(android_artifacts_dir: &Path) -> CargoResult<PathBuf> {
let output_dir = android_artifacts_dir.join("native_app_glue");
fs::create_dir_all(&output_dir).unwrap();
let mut h_file = File::create(output_dir.join("android_native_app_glue.h"))?;
h_file.write_all(&include_bytes!("../../../native_app_glue/android_native_app_glue.h")[..])?;
let c_path = output_dir.join("android_native_app_glue.c");
let mut c_file = File::create(&c_path)?;
c_file.write_all(&include_bytes!("../../../native_app_glue/android_native_app_glue.c")[..])?;
Ok(c_path)
}
fn build_android_native_glue(
config: &AndroidConfig,
android_native_glue_src_path: &PathBuf,
build_target_dir: &PathBuf,
build_target: AndroidBuildTarget,
) -> CargoResult<PathBuf> {
let clang = util::find_clang(config, build_target)?;
let android_native_glue_build_path = build_target_dir.join("android_native_glue");
fs::create_dir_all(&android_native_glue_build_path)?;
let android_native_glue_object_path =
android_native_glue_build_path.join("android_native_glue.o");
util::script_process(clang)
.arg(android_native_glue_src_path)
.arg("-c")
.arg("-o")
.arg(&android_native_glue_object_path)
.exec()?;
Ok(android_native_glue_object_path)
}
fn write_cmake_toolchain(
config: &AndroidConfig,
build_target_dir: &PathBuf,
build_target: AndroidBuildTarget,
) -> CargoResult<PathBuf> {
let toolchain_path = build_target_dir.join("cargo-apk.toolchain.cmake");
let mut toolchain_file = File::create(&toolchain_path).unwrap();
writeln!(
toolchain_file,
r#"set(ANDROID_PLATFORM android-{min_sdk_version})
set(ANDROID_ABI {abi})
string(REPLACE "--target={build_target}" "" CMAKE_C_FLAGS "${{CMAKE_C_FLAGS}}")
string(REPLACE "--target={build_target}" "" CMAKE_CXX_FLAGS "${{CMAKE_CXX_FLAGS}}")
unset(CMAKE_C_COMPILER CACHE)
unset(CMAKE_CXX_COMPILER CACHE)
include("{ndk_path}/build/cmake/android.toolchain.cmake")"#,
min_sdk_version = config.min_sdk_version,
ndk_path = config.ndk_path.to_string_lossy().replace("\\", "/"),
build_target = build_target.rust_triple(),
abi = build_target.android_abi(),
)?;
Ok(toolchain_path)
}