dinghy-lib 0.4.11

Cross-compilation made easier - see main crate cargo-dinghy
Documentation
use config::PlatformConfiguration;
use platform::regular_platform::RegularPlatform;
use std::{env, fs, path, process, sync};
use toolchain::ToolchainConfig;
use {Compiler, Device, Platform, PlatformManager, Result};

pub use self::device::AndroidDevice;

mod device;

pub struct AndroidManager {
    compiler: sync::Arc<Compiler>,
    adb: path::PathBuf,
}

impl PlatformManager for AndroidManager {
    fn devices(&self) -> Result<Vec<Box<Device>>> {
        let result = process::Command::new(&self.adb).arg("devices").output()?;
        let mut devices = vec![];
        let device_regex = ::regex::Regex::new(r#"^(\S+)\tdevice\r?$"#)?;
        for line in String::from_utf8(result.stdout)?.split("\n").skip(1) {
            if let Some(caps) = device_regex.captures(line) {
                let d = AndroidDevice::from_id(self.adb.clone(), &caps[1])?;
                debug!("Discovered Android device {}", d);
                devices.push(Box::new(d) as Box<Device>);
            }
        }
        Ok(devices)
    }
    fn platforms(&self) -> Result<Vec<Box<Platform>>> {
        if let Some(ndk) = ndk()? {
            let default_api_level = "21";
            debug!("Android NDK: {:?}", ndk);
            let version = ndk_version(&ndk)?;
            let major = version
                .split(".")
                .next()
                .ok_or_else(|| format!("Invalid version found for ndk {:?}", &ndk))?;
            let major: usize = major
                .parse()
                .map_err(|_| format!("Invalid version found for ndk {:?}", &ndk))?;
            debug!(
                "Android ndk: {:?}, ndk version: {}, major: {}",
                ndk, version, major
            );
            if major >= 19 {
                let mut platforms = vec![];
                let prebuilt = ndk.join("toolchains/llvm/prebuilt");
                let tools = prebuilt
                    .read_dir()?
                    .next()
                    .ok_or("No tools in toolchain")??;
                let bin = tools.path().join("bin");
                debug!("Android tools bin: {:?}", bin);
                for (rustc_cpu, cc_cpu, binutils_cpu, abi_kind) in &[
                    ("aarch64", "aarch64", "aarch64", "android"),
                    ("armv7", "armv7a", "arm", "androideabi"),
                    ("i686", "i686", "i686", "android"),
                    ("x86_64", "x86_64", "x86_64", "android"),
                ] {
                    let mut api_levels : Vec<String> = Vec::new();
                    for entry in tools.path()
                        .join(format!("sysroot/usr/lib/{}-linux-{}",binutils_cpu,abi_kind)).read_dir()? {
                            let entry = entry?;
                            if entry.file_type()?.is_dir() {
                                let folder_name = entry.file_name().into_string().unwrap();
                                match folder_name.parse::<u32>() {
                                    Ok(_) => api_levels.push(folder_name),
                                    Err(_) => {}
                                }
                            }
                    }
                    api_levels.sort();
                    let create_platform = |api: &str, suffix: &str| {
                        let id = format!("auto-android-{}{}", rustc_cpu, suffix);
                        let tc = ToolchainConfig {
                            bin_dir: bin.clone(),
                            rustc_triple: format!("{}-linux-{}", rustc_cpu, abi_kind),
                            root: prebuilt.clone(),
                            sysroot: tools.path().join("sysroot"),
                            cc: "clang".to_string(),
                            binutils_prefix: format!("{}-linux-{}", binutils_cpu, abi_kind),
                            cc_prefix: format!("{}-linux-{}{}", cc_cpu, abi_kind, api),
                        };
                        RegularPlatform::new_with_tc(
                            self.compiler.clone(),
                            PlatformConfiguration::default(),
                            id,
                            tc,
                        )

                    };
                    for api in api_levels.iter() {
                        platforms.push(create_platform(&api,&format!("-api{}",api))?);
                    }
                    if !api_levels.is_empty() {
                        platforms.push(create_platform(api_levels.first().expect("The api level vector shouldn't be empty"),"-min")?);
                        platforms.push(create_platform(api_levels.last().expect("The api level vector shouldn't be empty"),"-latest")?);
                    }
                    platforms.push(create_platform(default_api_level,"")?);
                }
                return Ok(platforms);
            }
        }
        return Ok(vec![]);
    }
}


impl AndroidManager {
    pub fn probe(compiler: sync::Arc<Compiler>) -> Option<AndroidManager> {
        match adb() {
            Ok(adb) => {
                debug!("ADB found: {:?}", adb);
                Some(AndroidManager { adb, compiler })
            }
            Err(_) => {
                debug!("adb not found in path, android disabled");
                None
            }
        }
    }
}

fn probable_sdk_locs() -> Result<Vec<path::PathBuf>> {
    let mut v = vec![];
    for var in &[
        "ANDROID_HOME",
        "ANDROID_SDK",
        "ANDROID_SDK_ROOT",
        "ANDROID_SDK_HOME",
    ] {
        if let Ok(path) = env::var(var) {
            let path = path::Path::new(&path);
            if path.is_dir() {
                v.push(path.to_path_buf())
            }
        }
    }
    if let Ok(home) = env::var("HOME") {
        let mac = path::Path::new(&home).join("/Library/Android/sdk");
        if mac.is_dir() {
            v.push(mac);
        }
    }
    let casks = path::PathBuf::from("/usr/local/Caskroom/android-sdk");
    if casks.is_dir() {
        for kid in casks.read_dir()? {
            let kid = kid?;
            if kid.file_name() != ".metadata" {
                v.push(kid.path());
            }
        }
    }
    debug!("Candidates SDK: {:?}", v);
    Ok(v)
}

fn sdk() -> Result<Option<path::PathBuf>> {
    Ok(probable_sdk_locs()?.into_iter().next())
}

fn ndk() -> Result<Option<path::PathBuf>> {
    if let Ok(path) = env::var("ANDROID_NDK_HOME") {
        return Ok(Some(path.into()));
    }
    for sdk in probable_sdk_locs()? {
        if sdk.join("ndk-bundle/source.properties").is_file() {
            return Ok(Some(sdk.join("ndk-bundle")));
        }
    }
    Ok(None)
}

fn ndk_version(ndk: &path::Path) -> Result<String> {
    let sources_prop_file = ndk.join("source.properties");
    let props = fs::read_to_string(sources_prop_file)?;
    let revision_line = props
        .split("\n")
        .find(|l| l.starts_with("Pkg.Revision"))
        .ok_or(format!(
            "Android NDK at {:?} does not contains a valid ndk-bundle: no source.properties",
            ndk
        ))?;
    Ok(revision_line.split(" ").last().unwrap().to_string())
}

fn adb() -> Result<path::PathBuf> {
    fn try_out(command: &path::Path) -> bool {
        match process::Command::new(command)
            .arg("--version")
            .stdout(process::Stdio::null())
            .stderr(process::Stdio::null())
            .status()
        {
            Ok(_) => true,
            Err(_) => false,
        }
    }
    if let Ok(adb) = env::var("DINGHY_ANDROID_ADB") {
        return Ok(adb.into());
    }
    if let Ok(adb) = ::which::which("adb") {
        return Ok(adb);
    }
    for loc in probable_sdk_locs()? {
        let adb = loc.join("platform-tools/adb");
        if try_out(&adb) {
            return Ok(adb.into());
        }
    }
    Err("Adb could be found")?
}