New Android suppport

This commit is contained in:
Lars Bergstrom 2015-09-24 13:33:55 -05:00
parent 53d8f04ac4
commit 17a6cb5873
25 changed files with 657 additions and 29 deletions

View file

@ -0,0 +1,302 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//extern crate num;
#![feature(dir_builder, fs_walk)]
use std::collections::{HashMap, HashSet};
use std::env;
use std::fs;
use std::fs::DirBuilder;
use std::path::{Path, PathBuf};
use std::process;
use std::process::{Command, Stdio};
fn main() {
let (args, passthrough) = parse_arguments();
// Find all the native shared libraries that exist in the target directory.
let native_shared_libs = find_native_libs(&args);
// Get the SDK path from the ANDROID_HOME env.
let sdk_path = env::var("ANDROID_HOME").ok().expect("Please set the ANDROID_HOME environment variable");
let sdk_path = Path::new(&sdk_path);
// Get the NDK path from NDK_HOME env.
let ndk_path = env::var("NDK_HOME").ok().expect("Please set the NDK_HOME environment variable");
let ndk_path = Path::new(&ndk_path);
// Get the standalone NDK path from NDK_STANDALONE env.
// let standalone_path = env::var("NDK_STANDALONE").ok().unwrap_or("/opt/ndk_standalone".to_string());
// let standalone_path = Path::new(&standalone_path);
let debug = passthrough.contains(&"-d".to_string());
// Set the build directory that will contain all the necessary files to create the apk
let directory = args.root_path.join("support").join("android").join("apk");
let resdir = args.root_path.join("resources/");
// executing ndk-build
env::set_var("V", "1");
if debug {
env::set_var("NDK_DEBUG", "1");
env::set_var("APP_OPTIM", "0");
} else{
// Overrides android:debuggable propery in the .xml file.
env::set_var("APP_OPTIM", "1");
}
// Copy libservo.so into the jni folder for inclusion in the build
// TODO: pass/detect target architecture
{
let source = &args.target_path.join("libservo.so");
let target_dir = &directory.join("jni").join("armeabi");
let _ = DirBuilder::new().recursive(true).create(target_dir);
let target = target_dir.join("libmain.so");
println!("Copying the file {:?} to {:?}", source, target);
fs::copy(source, target).unwrap();
}
let ndkcmd = Command::new(ndk_path.join("ndk-build"))
.arg("-B")
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.current_dir(directory.clone())
.status();
if ndkcmd.is_err() || ndkcmd.unwrap().code().unwrap() != 0 {
println!("Error while executing program `ndk-build`, or missing program.");
process::exit(1);
}
// Copy the additional native libs into the libs directory.
for (name, path) in native_shared_libs.iter() {
let target = &directory.join("libs").join("armeabi").join(name);
println!("Copying the file {:?} to {:?}", name, target);
fs::copy(path, target).unwrap();
}
// Copy over the resources
let cpcmd= Command::new("cp")
.arg("-R")
.arg(&resdir)
.arg(&directory.join("assets"))
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.current_dir(directory.clone())
.status();
if cpcmd.is_err() || cpcmd.unwrap().code().unwrap() != 0 {
println!("Error while copying files from the resources dir to the assets dir");
process::exit(1);
}
// Update the project
let androidcmd = Command::new(sdk_path.join("tools").join("android"))
.arg("update")
.arg("project")
.arg("--name")
.arg("Servo")
.arg("--target")
.arg("android-18")
.arg("--path")
.arg(".")
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.current_dir(directory.clone())
.status();
if androidcmd.is_err() || androidcmd.unwrap().code().unwrap() != 0 {
println!("Error while updating the project with the android command");
process::exit(1);
}
// Build the APK
let mut antcmd = Command::new("ant");
if debug {
antcmd.arg("debug");
} else {
antcmd.arg("release");
}
let antresult = antcmd
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.current_dir(directory.clone())
.status();
if antresult.is_err() || antresult.unwrap().code().unwrap() != 0 {
println!("Error while executing program `ant`, or missing program.");
process::exit(1);
}
// Copying apk file to the requested output
// Release builds also need to be signed. For now, we use a simple debug
// signing key.
if debug {
fs::copy(&directory.join("bin").join("Servo-debug.apk"),
&args.output).unwrap();
} else {
let keystore_dir = env::home_dir().expect("Please have a home directory");
let keystore_dir = Path::new(&keystore_dir).join(".keystore");
let keytoolcmd = Command::new("keytool")
.arg("-list")
.arg("-storepass")
.arg("android")
.arg("-alias")
.arg("androiddebugkey")
.arg("-keystore")
.arg(&keystore_dir)
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.current_dir(directory.clone())
.status();
if keytoolcmd.is_err() || keytoolcmd.unwrap().code().unwrap() != 0 {
let keytoolcreatecmd = Command::new("keytool")
.arg("-genkeypair")
.arg("-keystore")
.arg(&keystore_dir)
.arg("-storepass")
.arg("android")
.arg("-alias")
.arg("androiddebugkey")
.arg("-keypass")
.arg("android")
.arg("-dname")
.arg("CN=Android Debug,O=Android,C=US")
.arg("-keyalg")
.arg("RSA")
.arg("-validity")
.arg("365")
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.current_dir(directory.clone())
.status();
if keytoolcreatecmd.is_err() ||
keytoolcreatecmd.unwrap().code().unwrap() != 0 {
println!("Error while using `keytool` to create the debug keystore.");
process::exit(1);
}
}
let jarsigncmd = Command::new("jarsigner")
.arg("-digestalg")
.arg("SHA1")
.arg("-sigalg")
.arg("MD5withRSA")
.arg("-storepass")
.arg("android")
.arg("-keystore")
.arg(&keystore_dir)
.arg(&directory.join("bin").join("Servo-release-unsigned.apk"))
.arg("androiddebugkey")
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.current_dir(directory.clone())
.status();
if jarsigncmd.is_err() || jarsigncmd.unwrap().code().unwrap() != 0 {
println!("Error while using `jarsign` to sign the APK.");
process::exit(1);
}
let aligncmd = Command::new(sdk_path.join("tools").join("zipalign"))
.arg("-f")
.arg("-v")
.arg("4")
.arg(&directory.join("bin").join("Servo-release-unsigned.apk"))
.arg(&directory.join("bin").join("Servo-release.apk"))
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.current_dir(directory.clone())
.status();
if aligncmd.is_err() || aligncmd.unwrap().code().unwrap() != 0 {
println!("Error while using `zipalign` to sign the APK.");
process::exit(1);
}
fs::copy(&directory.join("bin").join("Servo-release.apk"),
&args.output).unwrap();
}
}
struct Args {
output: PathBuf,
root_path: PathBuf,
target_path: PathBuf,
shared_libraries: HashSet<String>,
}
fn parse_arguments() -> (Args, Vec<String>) {
let mut result_output = None;
let mut result_root_path = None;
let mut result_target_path = None;
let mut result_shared_libraries = HashSet::new();
let mut result_passthrough = Vec::new();
let args = env::args();
let mut args = args.skip(1);
loop {
let arg = match args.next() {
None => return (
Args {
output: result_output.expect("Could not find -o argument"),
root_path: result_root_path.expect("Could not find -r enlistment root argument"),
target_path: result_target_path.expect("Could not find -t target path argument"),
shared_libraries: result_shared_libraries,
},
result_passthrough
),
Some(arg) => arg
};
match &*arg {
"-o" => {
result_output = Some(PathBuf::from(args.next().expect("-o must be followed by the output name")));
},
"-r" => {
result_root_path = Some(PathBuf::from(args.next().expect("-r must be followed by the enlistment root directory")));
},
"-t" => {
result_target_path = Some(PathBuf::from(args.next().expect("-t must be followed by the target output directory")));
},
"-l" => {
let name = args.next().expect("-l must be followed by a library name");
result_shared_libraries.insert(vec!["lib", &name, ".so"].concat());
// Also pass these through.
result_passthrough.push(arg);
result_passthrough.push(name);
}
_ => {
if arg.starts_with("-l") {
result_shared_libraries.insert(vec!["lib", &arg[2..], ".so"].concat());
}
result_passthrough.push(arg)
}
};
}
}
fn find_native_libs(args: &Args) -> HashMap<String, PathBuf> {
let mut native_shared_libs: HashMap<String, PathBuf> = HashMap::new();
// Add requested .so files
fs::walk_dir(&args.target_path).and_then(|dir_walker| {
for path in dir_walker {
let path = path.unwrap().path();
match (path.file_name(), path.extension()) {
(Some(filename), Some(ext)) => {
let filename = filename.to_str().unwrap();
if filename.starts_with("lib")
&& ext == "so"
&& args.shared_libraries.contains(filename) {
println!("Adding the file {:?}", filename);
native_shared_libs.insert(filename.to_string(), path.clone());
break;
}
}
_ => {}
}
}
Ok(())
}).ok();
native_shared_libs
}