OLD FILE KEPT ALIVE IN ORDER TO COMPARE WITH THE OLD BEHAVIOR
extern crate tempdir;
extern crate num;
use std::collections::{HashMap, HashSet};
use std::env;
use std::fs;
use std::fs::{File};
use std::path::{Path, PathBuf};
use std::process;
use std::process::{Command, Stdio};
use tempdir::{TempDir};
fn main() {
let (args, passthrough) = parse_arguments();
let native_shared_libs = find_native_libs(&args);
let sdk_path = env::var("ANDROID_HOME").ok().expect("Please set the ANDROID_HOME environment variable");
let sdk_path = Path::new(&sdk_path);
let ndk_path = env::var("NDK_HOME").ok().expect("Please set the NDK_HOME environment variable");
let ndk_path = Path::new(&ndk_path);
let standalone_path = env::var("NDK_STANDALONE").ok().unwrap_or("/opt/ndk_standalone".to_string());
let standalone_path = Path::new(&standalone_path);
let directory = build_directory(&sdk_path, args.output.file_stem().and_then(|s| s.to_str()).unwrap(), &native_shared_libs);
for (name, path) in native_shared_libs.iter() {
fs::copy(path, &directory.path().join("libs").join("armeabi").join(name)).unwrap();
}
let toolgccpath = standalone_path.join("bin").join("arm-linux-androideabi-gcc");
let toolantpath = Path::new("ant");
if let Err(_) = File::open(toolgccpath.clone()) {
println!("Missing Tool `{}`!", toolgccpath.display());
process::exit(1);
}
if Command::new(&toolgccpath.clone())
.arg(ndk_path.join("sources").join("android").join("native_app_glue").join("android_native_app_glue.c"))
.arg("-c")
.arg("-o").arg(directory.path().join("android_native_app_glue.o"))
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status().unwrap().code().unwrap() != 0
{
println!("Error while executing gcc");
process::exit(1);
}
if Command::new(&toolgccpath.clone())
.args(&*passthrough)
.arg(directory.path().join("android_native_app_glue.o"))
.arg("-o").arg(directory.path().join("libs").join("armeabi").join("libmain.so"))
.arg("-shared")
.arg("-Wl,-E")
.stdout(Stdio::inherit())
.stderr(Stdio::inherit()) .status().unwrap().code().unwrap() != 0
{
println!("Error while executing gcc");
process::exit(1);
}
copy_assets(&directory.path());
let antcmd = Command::new(toolantpath).arg("debug")
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.current_dir(directory.path())
.status();
if antcmd.is_err() || antcmd.unwrap().code().unwrap() != 0 {
println!("Error while executing program `ant` debug, or missing program.");
process::exit(1);
}
fs::copy(&directory.path().join("bin").join("rust-android-debug.apk"),
&args.output).unwrap();
}
#[cfg(feature = "assets_hack")]
fn copy_assets(build_path: &Path) {
let cwd = env::current_dir().ok()
.expect("Can not get current working directory!");
let assets_path = cwd.join("assets");
if let Ok(_) = File::open(assets_path.clone()) {
fs::soft_link(&assets_path, &build_path.join("assets"))
.ok().expect("Can not create symlink to assets");
}
}
#[cfg(not(feature = "assets_hack"))]
fn copy_assets(_: &Path) {}
struct Args {
output: PathBuf,
library_path: Vec<PathBuf>,
shared_libraries: HashSet<String>,
}
fn parse_arguments() -> (Args, Vec<String>) {
let mut result_output = None;
let mut result_library_path = Vec::new();
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"),
library_path: result_library_path,
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")));
},
"-L" => {
let path = args.next().expect("-L must be followed by a path");
result_library_path.push(PathBuf::from(path.clone()));
result_passthrough.push(arg);
result_passthrough.push(path);
},
"-l" => {
let name = args.next().expect("-l must be followed by a library name");
result_shared_libraries.insert(vec!["lib", &name, ".so"].concat());
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();
for dir in &args.library_path {
fs::read_dir(&dir).and_then(|paths| {
for path in paths {
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) {
native_shared_libs.insert(filename.to_string(), path.clone());
}
}
_ => {}
}
}
Ok(())
}).ok();
}
native_shared_libs
}
fn build_directory(sdk_dir: &Path, crate_name: &str, libs: &HashMap<String, PathBuf>) -> TempDir {
use std::io::Write;
let build_directory = TempDir::new("android-rs-glue-rust-to-apk")
.ok().expect("Could not create temporary build directory");
let activity_name = if libs.len() > 0 {
let src_path = build_directory.path().join("src/rust/glutin");
fs::create_dir_all(&src_path).unwrap();
File::create(&src_path.join("MainActivity.java")).unwrap()
.write_all(java_src(libs).as_bytes())
.unwrap();
"rust.glutin.MainActivity"
} else {
"android.app.NativeActivity"
};
File::create(&build_directory.path().join("AndroidManifest.xml")).unwrap()
.write_all(build_manifest(crate_name, activity_name).as_bytes())
.unwrap();
File::create(&build_directory.path().join("build.xml")).unwrap()
.write_all(build_build_xml().as_bytes())
.unwrap();
File::create(&build_directory.path().join("local.properties")).unwrap()
.write_all(build_local_properties(sdk_dir).as_bytes())
.unwrap();
File::create(&build_directory.path().join("project.properties")).unwrap()
.write_all(build_project_properties().as_bytes())
.unwrap();
{
let libs_path = build_directory.path().join("libs").join("armeabi");
fs::create_dir_all(&libs_path).unwrap();
}
{
let src_path = build_directory.path().join("src");
fs::create_dir_all(&src_path).unwrap();
}
build_directory
}
fn java_src(libs: &HashMap<String, PathBuf>) -> String {
let mut libs_string = "".to_string();
for (name, _) in libs.iter() {
let line = format!(" System.loadLibrary(\"{}\");\n",
name.trim_left_matches("lib").trim_right_matches(".so"));
libs_string.push_str(&*line);
}
format!(r#"package rust.glutin;
public class MainActivity extends android.app.NativeActivity {{
static {{
{0}
}}
}}"#, libs_string)
}
fn build_manifest(crate_name: &str, activity_name: &str) -> String {
format!(r#"<?xml version="1.0" encoding="utf-8"?>
<!-- BEGIN_INCLUDE(manifest) -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.native_activity"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="9" />
<uses-feature android:glEsVersion="0x00020000" android:required="true"></uses-feature>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application android:label="{0}">
<activity android:name="{1}"
android:label="{0}"
android:configChanges="orientation|keyboardHidden">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
<!-- END_INCLUDE(manifest) -->
"#, crate_name, activity_name)
}
fn build_build_xml() -> String {
format!(r#"<?xml version="1.0" encoding="UTF-8"?>
<project name="rust-android" default="help">
<property file="local.properties" />
<loadproperties srcFile="project.properties" />
<import file="custom_rules.xml" optional="true" />
<import file="${{sdk.dir}}/tools/ant/build.xml" />
</project>
"#)
}
fn build_local_properties(sdk_dir: &Path) -> String {
let abs_dir = if sdk_dir.is_absolute() {
sdk_dir.to_path_buf()
} else {
env::current_dir().unwrap().join(sdk_dir)
};
format!(r"sdk.dir={}", abs_dir.to_str().unwrap())
}
fn build_project_properties() -> String {
format!(r"target=android-18")
}