use std::{env, fs, path, process};
use std::io::Write;
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
use cargo;
use errors::*;
use cargo::util::important_paths::find_root_manifest_for_wd;
pub fn setup_linker(device_target: &str) -> Result<()> {
let cfg = cargo::util::config::Config::default()?;
if let Some(linker) = cfg.get_string(&*format!("target.{}.linker", device_target))? {
debug!("Config specifies linker {:?} in {}",
linker.val,
linker.definition);
return Ok(());
}
let wd_path = find_root_manifest_for_wd(None, &env::current_dir()?)?;
let root = wd_path.parent().ok_or("building at / ?")?;
if let Some(linker) = guess_linker(device_target)? {
let shim = create_shim(&root, device_target, &*linker)?;
let var_name = format!("CARGO_TARGET_{}_LINKER",
device_target.replace("-", "_").to_uppercase());
env::set_var(var_name, shim);
return Ok(());
}
warn!("No linker set or guessed for target {}. See http://doc.crates.io/config.html .",
device_target);
Ok(())
}
#[cfg(not(target_os="windows"))]
fn create_shim<P: AsRef<path::Path>>(root: P,
device_target: &str,
shell: &str)
-> Result<path::PathBuf> {
let target_path = root.as_ref().join("target").join(device_target);
fs::create_dir_all(&target_path)?;
let shim = target_path.join("linker");
let mut linker_shim = fs::File::create(&shim)?;
writeln!(linker_shim, "#!/bin/sh")?;
linker_shim.write_all(shell.as_bytes())?;
writeln!(linker_shim, "\n")?;
fs::set_permissions(&shim, PermissionsExt::from_mode(0o777))?;
Ok(shim)
}
#[cfg(target_os="windows")]
fn create_shim<P: AsRef<path::Path>>(root: P,
device_target: &str,
shell: &str)
-> Result<path::PathBuf> {
let target_path = root.as_ref().join("target").join(device_target);
fs::create_dir_all(&target_path)?;
let shim = target_path.join("linker.bat");
let mut linker_shim = fs::File::create(&shim)?;
linker_shim.write_all(shell.as_bytes())?;
writeln!(linker_shim, "\n")?;
Ok(shim)
}
#[cfg(not(target_os="windows"))]
fn guess_linker(device_target: &str) -> Result<Option<String>> {
if device_target.ends_with("-apple-ios") {
let xcrun = if device_target.starts_with("x86") {
process::Command::new("xcrun").args(&["--sdk", "iphonesimulator", "--show-sdk-path"])
.output()?
} else {
process::Command::new("xcrun").args(&["--sdk", "iphoneos", "--show-sdk-path"]).output()?
};
let sdk_path = String::from_utf8(xcrun.stdout)?;
Ok(Some(format!(r#"cc -isysroot {} "$@""#, &*sdk_path.trim_right())))
} else if device_target.contains("-linux-android") {
if let Err(_) = env::var("ANDROID_NDK_HOME") {
if let Ok(home) = env::var("HOME") {
let mac_place = format!("{}/Library/Android/sdk/ndk-bundle", home);
if fs::metadata(&mac_place)?.is_dir() {
env::set_var("ANDROID_NDK_HOME", &mac_place)
}
} else {
warn!("Android target detected, but could not find (or guess) ANDROID_NDK_HOME. \
You may need to set it up.");
return Ok(None);
}
}
let (toolchain, gcc, arch) = ndk_details(device_target)?;
let home = env::var("ANDROID_NDK_HOME")
.map_err(|_| "environment variable ANDROID_NDK_HOME is required")?;
let api = env::var("ANDROID_API")
.unwrap_or(default_api_for_arch(arch)?.into());
let prebuilt_dir = format!(r"{home}/toolchains/{toolchain}-4.9/prebuilt",
home = home, toolchain = toolchain);
let prebuilt = fs::read_dir(&prebuilt_dir)
.map_err(|e| format!("Error finding prebuilt {}: {}", prebuilt_dir, e))?
.next()
.ok_or("No prebuilt toolchain in your android setup")??;
Ok(Some(format!(r#"{prebuilt_dir}/{prebuilt:?}/bin/{gcc}-gcc \
--sysroot {home}/platforms/{api}/{arch} \
"$@" "#,
prebuilt_dir = prebuilt_dir,
prebuilt = prebuilt.file_name(),
gcc = gcc,
home = home,
api = api,
arch = arch)))
} else {
Ok(None)
}
}
#[cfg(target_os="windows")]
fn guess_linker(device_target: &str) -> Result<Option<String>> {
if device_target.contains("-linux-android") {
let (toolchain, gcc, arch) = ndk_details(device_target)?;
let home = env::var("ANDROID_NDK_HOME")
.map_err(|_| "environment variable ANDROID_NDK_HOME is required")?;
let api = env::var("ANDROID_API")
.unwrap_or(default_api_for_arch(arch)?.into());
let prebuilt_dir = format!(r"{home}\toolchains\{toolchain}-4.9\prebuilt",
home = home, toolchain = toolchain);
if !::std::path::Path::new(&prebuilt_dir).exists() {
return Err(Error::from(format!("Could not find prebuilt android toolchain at:\n{:?}", prebuilt_dir)));
}
let mut toolchain_bin = prebuilt_dir.clone() + r"\windows-x86_64\bin";
if !::std::path::Path::new(&toolchain_bin).exists() {
toolchain_bin = prebuilt_dir + r"\windows\bin";
}
Ok(Some(format!(r"{toolchain_bin}\{gcc}-gcc --sysroot {home}\platforms\{api}\{arch} %* ",
toolchain_bin = toolchain_bin,
gcc = gcc,
home = home,
api = api,
arch = arch)))
} else {
Ok(None)
}
}
fn ndk_details(rust_target: &str) -> Result<(&str, &str, &str)>{
Ok(
match rust_target {
"armv7-linux-androideabi" => ("arm-linux-androideabi", "arm-linux-androideabi", "arch-arm"),
"aarch64-linux-android" => (rust_target, rust_target, "arch-arm64"),
"i686-linux-android" => ("x86", rust_target, "arch-x86"),
_ => (rust_target, rust_target, "arch-arm"),
})
}
fn default_api_for_arch(android_arch: &str) -> Result<&'static str> {
Ok(
match android_arch {
"arch-arm" => "android-18",
"arch-arm64" => "android-21",
"arch-mips" => "android-18",
"arch-mips64" => "android-21",
"arch-x86" => "android-18",
"arch-x86_64" => "android-21",
_ => return Err(Error::from(format!("Unknown android arch {}", android_arch)))
})
}