1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
use crate::{error::*, tools::Aapt2};
use std::path::{Path, PathBuf};
use std::process::Command as ProcessCommand;

/// Helper structure that contains information about the Android SDK path
/// and returns paths to the tools.
pub struct AndroidSdk {
    sdk_path: PathBuf,
    build_deps_path: PathBuf,
    build_deps_version: String,
    platforms_path: PathBuf,
    platforms: Vec<u32>,
}

impl AndroidSdk {
    /// Using environment variables tools
    pub fn from_env() -> Result<Self> {
        let sdk_path = {
            let sdk_path = std::env::var("ANDROID_SDK_ROOT")
                .ok()
                .or_else(|| std::env::var("ANDROID_SDK_PATH").ok())
                .or_else(|| std::env::var("ANDROID_HOME").ok());
            PathBuf::from(sdk_path.ok_or(AndroidError::AndroidSdkNotFound)?)
        };
        let build_deps_path = sdk_path.join("build-tools");
        let build_deps_version = std::fs::read_dir(&build_deps_path)
            .map_err(|_| Error::PathNotFound(build_deps_path.clone()))?
            .filter_map(|path| path.ok())
            .filter(|path| path.path().is_dir())
            .filter_map(|path| path.file_name().into_string().ok())
            .filter(|name| name.chars().next().unwrap().is_digit(10))
            .max()
            .ok_or(AndroidError::BuildToolsNotFound)?;
        let platforms_path = sdk_path.join("platforms");
        let platforms: Vec<u32> = std::fs::read_dir(&platforms_path)
            .map_err(|_| Error::PathNotFound(platforms_path.clone()))?
            .filter_map(|path| path.ok())
            .filter(|path| path.path().is_dir())
            .filter_map(|path| path.file_name().into_string().ok())
            .filter_map(|name| {
                name.strip_prefix("android-")
                    .and_then(|api| api.parse::<u32>().ok())
            })
            .collect();
        if platforms.is_empty() {
            return Err(AndroidError::NoPlatformsFound.into());
        };
        Ok(Self {
            sdk_path,
            build_deps_path,
            build_deps_version,
            platforms_path,
            platforms,
        })
    }

    /// Path to SDK
    pub fn sdk_path(&self) -> &Path {
        &self.sdk_path
    }

    /// Build path deps
    pub fn build_deps_path(&self) -> &Path {
        &self.build_deps_path
    }

    /// Build version deps
    pub fn build_deps_version(&self) -> &str {
        &self.build_deps_version
    }

    /// Platforms path
    pub fn platforms_path(&self) -> &Path {
        &self.platforms_path
    }

    /// Platforms
    pub fn platforms(&self) -> &[u32] {
        &self.platforms
    }

    pub fn build_tool(&self, tool: &str, current_dir: Option<&Path>) -> Result<ProcessCommand> {
        let path = self
            .build_deps_path
            .join(&self.build_deps_version)
            .join(tool);
        if !path.exists() {
            return Err(Error::CmdNotFound(tool.to_string()));
        }
        let mut command = ProcessCommand::new(dunce::canonicalize(path)?);
        if let Some(current_dir) = current_dir {
            command.current_dir(current_dir);
        };
        Ok(command)
    }

    /// AAPT2 tools
    pub fn aapt2(&self) -> Result<Aapt2> {
        self.build_tool(bin!("aapt2"), None)?;
        Ok(Aapt2)
    }

    /// Platforms tools
    pub fn platform_tool(&self, tool: &str) -> Result<ProcessCommand> {
        let path = self.sdk_path.join("platform-tools").join(tool);
        if !path.exists() {
            return Err(Error::CmdNotFound(tool.to_string()));
        }
        Ok(ProcessCommand::new(dunce::canonicalize(path)?))
    }

    /// Default platforms
    pub fn default_platform(&self) -> u32 {
        self.platforms().iter().max().cloned().unwrap()
    }

    /// Platforms directory path
    pub fn platform_dir(&self, platform: u32) -> Result<PathBuf> {
        let dir = self.platforms_path.join(format!("android-{}", platform));
        if !dir.exists() {
            return Err(AndroidError::PlatformNotFound(platform).into());
        }
        Ok(dir)
    }

    /// Returns android_jar path
    pub fn android_jar(&self, platform: u32) -> Result<PathBuf> {
        let android_jar = self.platform_dir(platform)?.join("android.jar");
        if !android_jar.exists() {
            return Err(Error::PathNotFound(android_jar));
        }
        Ok(android_jar)
    }
}