use std::collections::btree_map::BTreeMap;
use std::env;
use std::fs;
use std::fs::File;
use std::io::Read;
use std::iter::FromIterator;
use std::path::Path;
use std::path::PathBuf;
use cargo::core::Workspace;
use cargo::ops;
use cargo::util::errors::CargoError;
use toml;
use toml::Parser as TomlParser;
pub struct AndroidConfig {
pub sdk_path: PathBuf,
pub ndk_path: PathBuf,
pub gradle_command: String,
pub package_name: String,
pub project_name: String,
pub package_label: String,
pub package_icon: Option<String>,
pub build_targets: Vec<String>,
pub android_version: u32,
pub build_tools_version: String,
pub assets_path: Option<PathBuf>,
pub res_path: Option<PathBuf>,
pub release: bool,
pub fullscreen: bool,
pub application_attributes: Option<String>,
pub activity_attributes: Option<String>,
pub opengles_version_major: u8,
pub opengles_version_minor: u8,
}
pub fn load(workspace: &Workspace, flag_package: &Option<String>) -> Result<AndroidConfig, CargoError> {
let package = {
let packages = Vec::from_iter(flag_package.iter().cloned());
let spec = ops::Packages::Packages(&packages);
match spec {
ops::Packages::All => unreachable!("cargo apk supports single package only"),
ops::Packages::OptOut(_) => unreachable!("cargo apk supports single package only"),
ops::Packages::Packages(xs) => match xs.len() {
0 => workspace.current()?,
1 => workspace.members()
.find(|pkg| pkg.name() == xs[0])
.ok_or_else(||
CargoError::from(
format!("package `{}` is not a member of the workspace", xs[0]))
)?,
_ => unreachable!("cargo apk supports single package only"),
}
}
};
let (package_name, manifest_content) = {
let content = {
let mut file = File::open(package.manifest_path()).unwrap();
let mut content = String::new();
file.read_to_string(&mut content).unwrap();
content
};
let toml = TomlParser::new(&content).parse().unwrap();
let decoded: TomlPackage = toml::decode(toml["package"].clone()).unwrap();
let package_name = decoded.name.clone();
(package_name, decoded.metadata.and_then(|m| m.android))
};
let ndk_path = env::var("NDK_HOME").expect("Please set the path to the Android NDK with the \
$NDK_HOME environment variable.");
let sdk_path = {
let mut try = env::var("ANDROID_SDK_HOME").ok();
if try.is_none() {
try = env::var("ANDROID_HOME").ok();
}
try.expect("Please set the path to the Android SDK with either the $ANDROID_SDK_HOME or \
the $ANDROID_HOME environment variable.")
};
let build_tools_version = {
let mut dir = fs::read_dir(Path::new(&sdk_path).join("build-tools"))
.expect("Android SDK has no build-tools directory");
let mut versions = Vec::new();
while let Some(next) = dir.next() {
let next = next.unwrap();
let meta = next.metadata().unwrap();
if !meta.is_dir() {
continue;
}
let file_name = next.file_name().into_string().unwrap();
if !file_name.chars().next().unwrap().is_digit(10) {
continue;
}
versions.push(file_name);
}
versions.sort_by(|a, b| b.cmp(&a));
versions.into_iter().next().unwrap_or("26.0.0".to_owned())
};
Ok(AndroidConfig {
sdk_path: Path::new(&sdk_path).to_owned(),
ndk_path: Path::new(&ndk_path).to_owned(),
gradle_command: "gradle".to_owned(),
package_name: manifest_content.as_ref().and_then(|a| a.package_name.clone())
.unwrap_or_else(|| format!("rust.{}", package_name)),
project_name: package_name.clone(),
package_label: manifest_content.as_ref().and_then(|a| a.label.clone())
.unwrap_or_else(|| package_name.clone()),
package_icon: manifest_content.as_ref().and_then(|a| a.icon.clone()),
build_targets: manifest_content.as_ref().and_then(|a| a.build_targets.clone())
.unwrap_or(vec!["arm-linux-androideabi".to_owned()]),
android_version: manifest_content.as_ref().and_then(|a| a.android_version).unwrap_or(18),
build_tools_version: build_tools_version,
assets_path: manifest_content.as_ref().and_then(|a| a.assets.as_ref())
.map(|p| package.manifest_path().parent().unwrap().join(p)),
res_path: manifest_content.as_ref().and_then(|a| a.res.as_ref())
.map(|p| package.manifest_path().parent().unwrap().join(p)),
release: false,
fullscreen: manifest_content.as_ref().and_then(|a| a.fullscreen.clone()).unwrap_or(false),
application_attributes: manifest_content.as_ref().and_then(|a| map_to_string(a.application_attributes.clone())),
activity_attributes: manifest_content.as_ref().and_then(|a| map_to_string(a.activity_attributes.clone())),
opengles_version_major: manifest_content.as_ref().and_then(|a| a.opengles_version_major).unwrap_or(2),
opengles_version_minor: manifest_content.as_ref().and_then(|a| a.opengles_version_minor).unwrap_or(0),
})
}
fn map_to_string(input_map: Option<BTreeMap<String, String>>) -> Option<String> {
if let Some(map) = input_map {
let mut result = String::new();
for (key, val) in map {
result.push_str(&format!("\n{}=\"{}\"", key, val))
}
Some(result)
} else {
None
}
}
#[derive(Debug, Clone, RustcDecodable)]
struct TomlPackage {
name: String,
metadata: Option<TomlMetadata>,
}
#[derive(Debug, Clone, RustcDecodable)]
struct TomlMetadata {
android: Option<TomlAndroid>,
}
#[derive(Debug, Clone, RustcDecodable)]
struct TomlAndroid {
package_name: Option<String>,
label: Option<String>,
icon: Option<String>,
assets: Option<String>,
res: Option<String>,
android_version: Option<u32>,
fullscreen: Option<bool>,
application_attributes: Option<BTreeMap<String, String>>,
activity_attributes: Option<BTreeMap<String, String>>,
build_targets: Option<Vec<String>>,
opengles_version_major: Option<u8>,
opengles_version_minor: Option<u8>,
}