diff options
author | Lars Bergstrom <lars@lars.com> | 2015-09-24 13:33:55 -0500 |
---|---|---|
committer | Lars Bergstrom <lars@lars.com> | 2015-11-04 16:29:39 -0600 |
commit | 17a6cb587399b8650d331a85456336ccbc31e9fe (patch) | |
tree | 342217ccbcf8f8ad7c0708431229002defa9daac | |
parent | 53d8f04ac4894480aa8bc7a6a79fb5e02ae050d1 (diff) | |
download | servo-17a6cb587399b8650d331a85456336ccbc31e9fe.tar.gz servo-17a6cb587399b8650d331a85456336ccbc31e9fe.zip |
New Android suppport
25 files changed, 657 insertions, 28 deletions
diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 7ef62fa04d9..00000000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "support/android-rs-glue"] - path = support/android-rs-glue - url = https://github.com/tomaka/android-rs-glue diff --git a/components/profile/lib.rs b/components/profile/lib.rs index 087bf48e6e0..93888cc4768 100644 --- a/components/profile/lib.rs +++ b/components/profile/lib.rs @@ -2,6 +2,7 @@ * 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/. */ +#![feature(alloc_jemalloc)] #![feature(box_syntax)] #![feature(iter_arith)] #![feature(slice_splits)] @@ -12,6 +13,8 @@ extern crate log; #[macro_use] extern crate profile_traits; + +extern crate alloc_jemalloc; extern crate hbs_pow; extern crate ipc_channel; extern crate libc; diff --git a/components/servo/.cargo/config b/components/servo/.cargo/config index 66543bb1098..7e35e641e35 100644 --- a/components/servo/.cargo/config +++ b/components/servo/.cargo/config @@ -1,7 +1,5 @@ -paths = ["../../support/android-rs-glue"] - [target.arm-linux-androideabi] -linker = "../../target/debug/apk-builder" +linker = "./fake-ld.sh" ar = "arm-linux-androideabi-ar" [target.arm-unknown-linux-gnueabihf] diff --git a/components/servo/Cargo.lock b/components/servo/Cargo.lock index 5c4478bfb36..ba4009b7180 100644 --- a/components/servo/Cargo.lock +++ b/components/servo/Cargo.lock @@ -2,7 +2,7 @@ name = "servo" version = "0.0.1" dependencies = [ - "android_glue 0.1.1", + "android_glue 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "canvas 0.0.1", "canvas_traits 0.0.1", @@ -58,10 +58,6 @@ dependencies = [ [[package]] name = "android_glue" version = "0.1.1" - -[[package]] -name = "android_glue" -version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -677,7 +673,7 @@ dependencies = [ [[package]] name = "gif" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "color_quant 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -929,7 +925,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byteorder 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)", "enum_primitive 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gif 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gif 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "glob 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", "num 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "png 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/components/servo/Cargo.toml b/components/servo/Cargo.toml index db611072946..e102dec1ca6 100644 --- a/components/servo/Cargo.toml +++ b/components/servo/Cargo.toml @@ -3,6 +3,7 @@ name = "servo" version = "0.0.1" authors = ["The Servo Project Developers"] +build = "build.rs" [lib] name = "servo" @@ -111,7 +112,6 @@ path = "../../ports/glutin" optional = true [dependencies.android_glue] -path = "../../support/android-rs-glue/glue" optional = true [dependencies.log] diff --git a/components/servo/build.rs b/components/servo/build.rs new file mode 100644 index 00000000000..701b1eacc0b --- /dev/null +++ b/components/servo/build.rs @@ -0,0 +1,61 @@ +/* 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/. */ + +use std::env; +use std::path::Path; +use std::process; +use std::process::{Command, Stdio}; + +fn main() { + // build.rs is not platform-specific, so we have to check the target here. + let target = env::var("TARGET").unwrap(); + if target.contains("android") { + android_main() + } +} + +fn android_main() { + // 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().expect("Please set the NDK_STANDALONE environment variable"); + let standalone_path = Path::new(&standalone_path); + + // Get the standalone NDK path from NDK_STANDALONE env. + let out_dir = env::var("OUT_DIR").ok().expect("Cargo should have set the OUT_DIR environment variable"); + let directory = Path::new(&out_dir); + + // compiling android_native_app_glue.c + if Command::new(standalone_path.join("bin").join("arm-linux-androideabi-gcc")) + .arg(ndk_path.join("sources").join("android").join("native_app_glue").join("android_native_app_glue.c")) + .arg("-c") + .arg("-o").arg(directory.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) + } + + // compiling libandroid_native_app_glue.a + if Command::new(standalone_path.join("bin").join("arm-linux-androideabi-ar")) + .arg("rcs") + .arg(directory.join("libandroid_native_app_glue.a")) + .arg(directory.join("android_native_app_glue.o")) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .status().unwrap().code().unwrap() != 0 + { + println!("Error while executing ar"); + process::exit(1) + } + + println!("cargo:rustc-link-lib=static=android_native_app_glue"); + println!("cargo:rustc-link-search=native={}", out_dir); + println!("cargo:rustc-link-lib=log"); + println!("cargo:rustc-link-lib=android"); +} diff --git a/components/servo/fake-ld.sh b/components/servo/fake-ld.sh new file mode 100755 index 00000000000..2babdbd80c1 --- /dev/null +++ b/components/servo/fake-ld.sh @@ -0,0 +1,3 @@ +#!/bin/bash +TARGET_DIR=$OUT_DIR/../../.. +arm-linux-androideabi-gcc $@ $LDFLAGS -lc -o $TARGET_DIR/libservo.so -shared && touch $TARGET_DIR/servo diff --git a/components/servo/main.rs b/components/servo/main.rs index b339babb972..090e567da38 100644 --- a/components/servo/main.rs +++ b/components/servo/main.rs @@ -23,6 +23,8 @@ extern crate android_glue; // The window backed by glutin extern crate glutin_app as app; extern crate env_logger; +#[cfg(target_os = "android")] +extern crate libc; #[macro_use] extern crate log; // The Servo engine @@ -171,6 +173,16 @@ fn args() -> Vec<String> { env::args().collect() } + +// This extern definition ensures that the linker will not discard +// the static native lib bits, which are brought in from the NDK libraries +// we link in from build.rs. +#[cfg(target_os = "android")] +extern { + fn app_dummy() -> libc::c_void; +} + + // This macro must be used at toplevel because it defines a nested // module, but macros can only accept identifiers - not paths - // preventing the expansion of this macro within the android module @@ -193,6 +205,8 @@ mod android { //env::set_var("RUST_LOG", "servo,gfx,msg,util,layers,js,std,rt,extra"); redirect_output(STDERR_FILENO); redirect_output(STDOUT_FILENO); + + unsafe { super::app_dummy(); } } struct FilePtr(*mut self::libc::types::common::c95::FILE); diff --git a/ports/cef/Cargo.lock b/ports/cef/Cargo.lock index 051695a4b02..9c07c82c359 100644 --- a/ports/cef/Cargo.lock +++ b/ports/cef/Cargo.lock @@ -627,7 +627,7 @@ dependencies = [ [[package]] name = "gif" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "color_quant 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -879,7 +879,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byteorder 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)", "enum_primitive 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gif 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gif 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "glob 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", "num 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "png 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/ports/gonk/Cargo.lock b/ports/gonk/Cargo.lock index 07d85f98b20..72453845843 100644 --- a/ports/gonk/Cargo.lock +++ b/ports/gonk/Cargo.lock @@ -629,7 +629,7 @@ dependencies = [ [[package]] name = "gif" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "color_quant 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -828,7 +828,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byteorder 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)", "enum_primitive 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gif 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gif 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "glob 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", "num 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "png 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/python/servo/build_commands.py b/python/servo/build_commands.py index 9b5b3e4e11a..ad901ba0fd4 100644 --- a/python/servo/build_commands.py +++ b/python/servo/build_commands.py @@ -198,13 +198,6 @@ class MachCommands(CommandBase): if verbose: opts += ["-v"] if android: - # Ensure the APK builder submodule has been built first - apk_builder_dir = "support/android-rs-glue" - with cd(path.join(apk_builder_dir, "apk-builder")): - status = call(["cargo", "build"], env=self.build_env(), verbose=verbose) - if status: - return status - opts += ["--target", "arm-linux-androideabi"] if debug_mozjs or self.config["build"]["debug-mozjs"]: diff --git a/python/servo/command_base.py b/python/servo/command_base.py index 4de1caf5f71..14f8dff0c6f 100644 --- a/python/servo/command_base.py +++ b/python/servo/command_base.py @@ -135,6 +135,9 @@ class CommandBase(object): self._cargo_build_id = f.read().strip() return self._cargo_build_id + def get_top_dir(self): + return self.context.topdir + def get_target_dir(self): if "CARGO_TARGET_DIR" in os.environ: return os.environ["CARGO_TARGET_DIR"] diff --git a/python/servo/post_build_commands.py b/python/servo/post_build_commands.py index efa315158f8..6e59895f956 100644 --- a/python/servo/post_build_commands.py +++ b/python/servo/post_build_commands.py @@ -22,7 +22,7 @@ from mach.decorators import ( Command, ) -from servo.command_base import CommandBase +from servo.command_base import CommandBase, cd def read_file(filename, if_exists=False): @@ -33,8 +33,7 @@ def read_file(filename, if_exists=False): @CommandProvider -class MachCommands(CommandBase): - +class PostBuildCommands(CommandBase): @Command('run', description='Run Servo', category='post-build') @@ -167,3 +166,36 @@ class MachCommands(CommandBase): import webbrowser webbrowser.open("file://" + path.abspath(path.join( self.get_target_dir(), "doc", "servo", "index.html"))) + + @Command('package', + description='Package Servo (currently, Android APK only)', + category='post-build') + @CommandArgument('--release', '-r', action='store_true', + help='Package the release build') + @CommandArgument('--dev', '-d', action='store_true', + help='Package the dev build') + @CommandArgument( + 'params', nargs='...', + help="Command-line arguments to be passed through to Servo") + def package(self, params, release=False, dev=False, debug=False, debugger=None): + env = self.build_env() + target_dir = path.join(self.get_target_dir(), "arm-linux-androideabi") + dev_flag = "" + + if dev: + env["NDK_DEBUG"] = "1" + env["ANT_FLAVOR"] = "debug" + dev_flag = "-d" + target_dir = path.join(target_dir, "debug") + else: + env["ANT_FLAVOR"] = "release" + target_dir = path.join(target_dir, "release") + + output_apk = path.join(target_dir, "servo.apk") + try: + with cd(path.join("support", "android", "build-apk")): + subprocess.check_call(["cargo", "run", "--", dev_flag, "-o", output_apk, "-t", target_dir, + "-r", self.get_top_dir()], env=env) + except subprocess.CalledProcessError as e: + print("Packaging Android exited with return value %d" % e.returncode) + return e.returncode diff --git a/support/android-rs-glue b/support/android-rs-glue deleted file mode 160000 -Subproject a5b6234ef140b0b7e67142ef7c5c41dd526b8d6 diff --git a/support/android/apk/AndroidManifest.xml b/support/android/apk/AndroidManifest.xml new file mode 100644 index 00000000000..5f3a9e1ea5e --- /dev/null +++ b/support/android/apk/AndroidManifest.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- BEGIN_INCLUDE(manifest) --> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.mozilla.servo" + android:versionCode="1" + android:versionName="1.0"> + + <uses-sdk android:minSdkVersion="18" /> + + <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="Servo" android:icon="@mipmap/servo"> + <activity android:name="com.mozilla.servo.MainActivity" + android:label="Servo" + 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) --> diff --git a/support/android/apk/build.xml b/support/android/apk/build.xml new file mode 100644 index 00000000000..3d1611517a8 --- /dev/null +++ b/support/android/apk/build.xml @@ -0,0 +1,92 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project name="Servo" default="help"> + + <!-- The local.properties file is created and updated by the 'android' tool. + It contains the path to the SDK. It should *NOT* be checked into + Version Control Systems. --> + <property file="local.properties" /> + + <!-- The ant.properties file can be created by you. It is only edited by the + 'android' tool to add properties to it. + This is the place to change some Ant specific build properties. + Here are some properties you may want to change/update: + + source.dir + The name of the source directory. Default is 'src'. + out.dir + The name of the output directory. Default is 'bin'. + + For other overridable properties, look at the beginning of the rules + files in the SDK, at tools/ant/build.xml + + Properties related to the SDK location or the project target should + be updated using the 'android' tool with the 'update' action. + + This file is an integral part of the build system for your + application and should be checked into Version Control Systems. + + --> + <property file="ant.properties" /> + + <!-- if sdk.dir was not set from one of the property file, then + get it from the ANDROID_HOME env var. + This must be done before we load project.properties since + the proguard config can use sdk.dir --> + <property environment="env" /> + <condition property="sdk.dir" value="${env.ANDROID_HOME}"> + <isset property="env.ANDROID_HOME" /> + </condition> + + <!-- The project.properties file is created and updated by the 'android' + tool, as well as ADT. + + This contains project specific properties such as project target, and library + dependencies. Lower level build properties are stored in ant.properties + (or in .classpath for Eclipse projects). + + This file is an integral part of the build system for your + application and should be checked into Version Control Systems. --> + <loadproperties srcFile="project.properties" /> + + <!-- quick check on sdk.dir --> + <fail + message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable." + unless="sdk.dir" + /> + + <!-- + Import per project custom build rules if present at the root of the project. + This is the place to put custom intermediary targets such as: + -pre-build + -pre-compile + -post-compile (This is typically used for code obfuscation. + Compiled code location: ${out.classes.absolute.dir} + If this is not done in place, override ${out.dex.input.absolute.dir}) + -post-package + -post-build + -pre-clean + --> + <import file="custom_rules.xml" optional="true" /> + + <!-- Import the actual build file. + + To customize existing targets, there are two options: + - Customize only one target: + - copy/paste the target into this file, *before* the + <import> task. + - customize it to your needs. + - Customize the whole content of build.xml + - copy/paste the content of the rules files (minus the top node) + into this file, replacing the <import> task. + - customize to your needs. + + *********************** + ****** IMPORTANT ****** + *********************** + In all cases you must update the value of version-tag below to read 'custom' instead of an integer, + in order to avoid having your file be overridden by tools such as "android update project" + --> + <!-- version-tag: 1 --> + <import file="${sdk.dir}/tools/ant/build.xml" /> + +</project> diff --git a/support/android/apk/jni/Android.mk b/support/android/apk/jni/Android.mk new file mode 100644 index 00000000000..d922b052b4e --- /dev/null +++ b/support/android/apk/jni/Android.mk @@ -0,0 +1,22 @@ +# Copyright (C) 2010 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := servo +LOCAL_SRC_FILES := $(TARGET_ARCH_ABI)/libmain.so +include $(PREBUILT_SHARED_LIBRARY) + +# $(call import-module) diff --git a/support/android/apk/jni/Application.mk b/support/android/apk/jni/Application.mk new file mode 100644 index 00000000000..3784f3fc6c5 --- /dev/null +++ b/support/android/apk/jni/Application.mk @@ -0,0 +1,2 @@ +APP_ABI := armeabi +APP_PLATFORM := android-18 diff --git a/support/android/apk/jni/main.c b/support/android/apk/jni/main.c new file mode 100644 index 00000000000..beddf906059 --- /dev/null +++ b/support/android/apk/jni/main.c @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +//BEGIN_INCLUDE(all) +#include <jni.h> +#include <errno.h> +#include <dlfcn.h> +#include <stdlib.h> + +#include <android/log.h> +#include <android_native_app_glue.h> + +#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "servo-wrapper", __VA_ARGS__)) +#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "servo-wrapper", __VA_ARGS__)) + +/** + * This is the main entry point of a native application that is using + * android_native_app_glue. It runs in its own thread, with its own + * event loop for receiving input events and doing other things. + */ +void android_main(struct android_app* state) { + LOGI("in android_main"); + void* libservo = dlopen("libservo.so", RTLD_NOW); + if (libservo == NULL) { + LOGI("failed to load servo lib: %s", dlerror()); + return; + } + + LOGI("loaded libservo.so"); + void (*android_main)(struct android_app*); + *(void**)(&android_main) = dlsym(libservo, "android_main"); + if (android_main) { + LOGI("go into android_main()"); + (*android_main)(state); + return; + } +} +//END_INCLUDE(all) + +int main(int argc, char* argv[]) +{ + LOGI("WAT"); + return 0; +} diff --git a/support/android/apk/project.properties b/support/android/apk/project.properties new file mode 100644 index 00000000000..c0442c37105 --- /dev/null +++ b/support/android/apk/project.properties @@ -0,0 +1 @@ +target=android-18 diff --git a/support/android/apk/res/mipmap/servo.png b/support/android/apk/res/mipmap/servo.png Binary files differnew file mode 100644 index 00000000000..5d5ef43bbc2 --- /dev/null +++ b/support/android/apk/res/mipmap/servo.png diff --git a/support/android/apk/src/com/mozilla/servo/MainActivity.java b/support/android/apk/src/com/mozilla/servo/MainActivity.java new file mode 100644 index 00000000000..d99478ee3da --- /dev/null +++ b/support/android/apk/src/com/mozilla/servo/MainActivity.java @@ -0,0 +1,8 @@ +package com.mozilla.servo; +import android.util.Log; + +public class MainActivity extends android.app.NativeActivity { + static { + Log.i("servo_wrapper", "Loading the NativeActivity"); + } +} diff --git a/support/android/build-apk/Cargo.lock b/support/android/build-apk/Cargo.lock new file mode 100644 index 00000000000..aa6f83fabec --- /dev/null +++ b/support/android/build-apk/Cargo.lock @@ -0,0 +1,4 @@ +[root] +name = "build-apk" +version = "0.0.1" + diff --git a/support/android/build-apk/Cargo.toml b/support/android/build-apk/Cargo.toml new file mode 100644 index 00000000000..23257facd3b --- /dev/null +++ b/support/android/build-apk/Cargo.toml @@ -0,0 +1,13 @@ +[package] + +name = "build-apk" +version = "0.0.1" +authors = ["Pierre Krieger <pierre.krieger1708@gmail.com>", "The Servo Project Developers"] + +[[bin]] +name = "build-apk" +path = "src/main.rs" +test = false +doc = false +bench = false + diff --git a/support/android/build-apk/src/main.rs b/support/android/build-apk/src/main.rs new file mode 100644 index 00000000000..460dd620da1 --- /dev/null +++ b/support/android/build-apk/src/main.rs @@ -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 +} |