Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mach bootstrap-android and test-android-startup commands #21094

Merged
merged 30 commits into from Jul 2, 2018
Merged
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
b9d5f11
Add `./mach bootstrap-android`
SimonSapin Jun 21, 2018
510cf1a
boostrap: set executable bits when extracting zip files
SimonSapin Jun 21, 2018
56df7f4
Install complete Android SDK (as much as on Buildbot CI)
SimonSapin Jun 21, 2018
244a332
Use more recent Android tools
SimonSapin Jun 25, 2018
1ee54ab
Create and Android virtual device
SimonSapin Jun 25, 2018
8889742
mach bootstrap-android: configure and show how to start an emulator
SimonSapin Jun 25, 2018
fe24816
mach run --android: show PID
SimonSapin Jun 25, 2018
4cbf3de
mach {package,install} --android: add --emulator and --usb
SimonSapin Jun 25, 2018
eab971c
Android cross-compiled command line hello world
SimonSapin Jun 25, 2018
90ba22b
egl-configs: link to EGL and generate bindings
SimonSapin Jun 25, 2018
89f6c6d
egl-configs: get the number of configs
SimonSapin Jun 25, 2018
7e7316e
egl-configs: print all config attributes
SimonSapin Jun 25, 2018
1f8d04b
egl-configs: print hex too
SimonSapin Jun 25, 2018
6e68705
cargo run into Android/adb: configurable target device
SimonSapin Jun 25, 2018
484eee8
Tidy
SimonSapin Jun 25, 2018
3c992af
Remove debugging println from a year ago
SimonSapin Jun 26, 2018
7d7f202
Fix copy/paste mistake
SimonSapin Jun 26, 2018
b7a8b81
egl-configs: add i686 support
SimonSapin Jun 26, 2018
f4d740f
Typo fixes
SimonSapin Jun 27, 2018
e54ad77
Do not prompt for Android emulator hardware profile
SimonSapin Jun 28, 2018
0e2e9cb
Create emulator images for both ARM and x86
SimonSapin Jun 28, 2018
aa1c3ce
bootstrap-android: use predictable paths for SDK and NDK
SimonSapin Jun 28, 2018
65122b1
bootstrap-android: always run sdkmanager
SimonSapin Jun 28, 2018
fc77db4
Use the bootstraped Android toolchains by default
SimonSapin Jun 28, 2018
b6b9fe0
Add "./mach android-emulator"
SimonSapin Jun 28, 2018
9e544c2
Remove the egl-configs diagnostic program
SimonSapin Jun 29, 2018
8293b29
bootstrap-android: check SHA1 hashes of downloaded archives
SimonSapin Jun 29, 2018
bee3fd0
mach android-emulator: avoid mach error messages for Python exceptions
SimonSapin Jun 29, 2018
eecbe83
Add ./mach test-android-startup
SimonSapin Jun 29, 2018
c0d1b8e
Android: increase emulator disk size, for debug builds
SimonSapin Jul 2, 2018
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

@@ -2,6 +2,7 @@
/.cargo/*
!/.cargo/config.*
/.servobuild
/android-toolchains
/target
/ports/android/bin
/ports/android/libs
@@ -436,7 +436,6 @@ pub fn for_each_available_family<F>(mut callback: F) where F: FnMut(String) {
pub fn for_each_variation<F>(family_name: &str, mut callback: F)
where F: FnMut(String)
{
println!("Variatioooon {:?}", family_name);
if let Some(family) = FONT_LIST.find_family(family_name) {
for font in &family.fonts {
callback(FontList::font_absolute_path(&font.filename));
@@ -13,6 +13,7 @@
import json
import os
import os.path as path
import platform
import re
import subprocess
import sys
@@ -28,7 +29,7 @@

import servo.bootstrap as bootstrap
from servo.command_base import CommandBase, cd, check_call
from servo.util import delete, download_bytes
from servo.util import delete, download_bytes, download_file, extract, check_hash


@CommandProvider
@@ -54,6 +55,111 @@ def env(self):
def bootstrap(self, force=False):
return bootstrap.bootstrap(self.context, force=force)

@Command('bootstrap-android',
description='Install the Android SDK and NDK.',
category='bootstrap')
@CommandArgument('--update',
action='store_true',
help='Run SDK component install and emulator image creation again')
def bootstrap_android(self, update=False):

ndk = "android-ndk-r12b-{system}-{arch}"
tools = "sdk-tools-{system}-4333796"

sdk_build_tools = "25.0.2"
emulator_images = [
("servo-arm", "25", "google_apis;armeabi-v7a"),
("servo-x86", "28", "google_apis;x86"),
]

known_sha1 = {
# https://dl.google.com/android/repository/repository2-1.xml
"sdk-tools-darwin-4333796.zip": "ed85ea7b59bc3483ce0af4c198523ba044e083ad",
"sdk-tools-linux-4333796.zip": "8c7c28554a32318461802c1291d76fccfafde054",
"sdk-tools-windows-4333796.zip": "aa298b5346ee0d63940d13609fe6bec621384510",

# https://developer.android.com/ndk/downloads/older_releases
"android-ndk-r12b-windows-x86.zip": "8e6eef0091dac2f3c7a1ecbb7070d4fa22212c04",
"android-ndk-r12b-windows-x86_64.zip": "337746d8579a1c65e8a69bf9cbdc9849bcacf7f5",
"android-ndk-r12b-darwin-x86_64.zip": "e257fe12f8947be9f79c10c3fffe87fb9406118a",
"android-ndk-r12b-linux-x86_64.zip": "170a119bfa0f0ce5dc932405eaa3a7cc61b27694",
}

toolchains = path.join(self.context.topdir, "android-toolchains")
if not path.isdir(toolchains):
os.makedirs(toolchains)

def download(target_dir, name, flatten=False):
final = path.join(toolchains, target_dir)
if path.isdir(final):
return

base_url = "https://dl.google.com/android/repository/"
filename = name + ".zip"
url = base_url + filename
archive = path.join(toolchains, filename)

if not path.isfile(archive):
download_file(filename, url, archive)

This comment has been minimized.

Copy link
@paulrouget

paulrouget Jun 28, 2018

Contributor

If the download gets interrupted, don't we need a way to force the download? Also, in the initial commit you were checking the md5 of the file. Can't we keep that?

This comment has been minimized.

Copy link
@SimonSapin

SimonSapin Jun 29, 2018

Author Member

Done.

check_hash(archive, known_sha1[filename], "sha1")
print("Extracting " + filename)
remove = True # Set to False to avoid repeated downloads while debugging this script
if flatten:
extracted = final + "_"
extract(archive, extracted, remove=remove)
contents = os.listdir(extracted)
assert len(contents) == 1
os.rename(path.join(extracted, contents[0]), final)
os.rmdir(extracted)
else:
extract(archive, final, remove=remove)

system = platform.system().lower()
machine = platform.machine().lower()
arch = {"i386": "x86"}.get(machine, machine)
download("ndk", ndk.format(system=system, arch=arch), flatten=True)
download("sdk", tools.format(system=system))

subprocess.check_call([
path.join(toolchains, "sdk", "tools", "bin", "sdkmanager"),
"platform-tools",
"build-tools;" + sdk_build_tools,
"emulator",
] + [
arg
for avd_name, api_level, system_image in emulator_images
for arg in [
"platforms;android-" + api_level,
"system-images;android-%s;%s" % (api_level, system_image),
]
])
for avd_name, api_level, system_image in emulator_images:
process = subprocess.Popen(stdin=subprocess.PIPE, stdout=subprocess.PIPE, args=[
path.join(toolchains, "sdk", "tools", "bin", "avdmanager"),
"create", "avd",
"--path", path.join(toolchains, "avd", avd_name),
"--name", avd_name,
"--package", "system-images;android-%s;%s" % (api_level, system_image),
"--force",
])
output = b""
while 1:
# Read one byte at a time because in Python:
# * readline() blocks until "\n", which doesn't come before the prompt
# * read() blocks until EOF, which doesn't come before the prompt
# * read(n) keeps reading until it gets n bytes or EOF,
# but we don't know reliably how many bytes to read until the prompt
byte = process.stdout.read(1)
if len(byte) == 0:
break
output += byte
# There seems to be no way to disable this prompt:
if output.endswith(b"Do you wish to create a custom hardware profile? [no]"):
process.stdin.write("no\n")
assert process.wait() == 0
with open(path.join(toolchains, "avd", avd_name, "config.ini"), "a") as f:
f.write("disk.dataPartition.size=2G\n")

@Command('update-hsts-preload',
description='Download the HSTS preload list',
category='bootstrap')
@@ -260,10 +260,10 @@ def build(self, target=None, release=False, dev=False, jobs=None,
env['RUSTFLAGS'] = env.get('RUSTFLAGS', "") + " -C debug_assertions"

if android:
if "ANDROID_NDK" not in os.environ:
if "ANDROID_NDK" not in env:
print("Please set the ANDROID_NDK environment variable.")
sys.exit(1)
if "ANDROID_SDK" not in os.environ:
if "ANDROID_SDK" not in env:
print("Please set the ANDROID_SDK environment variable.")
sys.exit(1)

@@ -525,6 +525,16 @@ def package_dir(package):
if self.config["android"]["platform"]:
env["ANDROID_PLATFORM"] = self.config["android"]["platform"]

toolchains = path.join(self.context.topdir, "android-toolchains")
for kind in ["sdk", "ndk"]:
default = os.path.join(toolchains, kind)
if os.path.isdir(default):
env.setdefault("ANDROID_" + kind.upper(), default)

tools = os.path.join(toolchains, "sdk", "platform-tools")
if os.path.isdir(tools):
env["PATH"] = "%s%s%s" % (tools, os.pathsep, env["PATH"])

# These are set because they are the variable names that build-apk
# expects. However, other submodules have makefiles that reference
# the env var names above. Once glutin is enabled and set as the
@@ -613,6 +623,13 @@ def android_adb_path(self, env):
return sdk_adb
return "adb"

def android_emulator_path(self, env):
if "ANDROID_SDK" in env:
sdk_adb = path.join(env["ANDROID_SDK"], "emulator", "emulator")
if path.exists(sdk_adb):
return sdk_adb
return "emulator"

def handle_android_target(self, target):
if target == "arm-linux-androideabi":
self.config["android"]["platform"] = "android-18"
@@ -389,10 +389,16 @@ def package(self, release=False, dev=False, android=None, debug=False, debugger=
@CommandArgument('--android',
action='store_true',
help='Install on Android')
@CommandArgument('--emulator',
action='store_true',
help='For Android, install to the only emulated device')
@CommandArgument('--usb',
action='store_true',
help='For Android, install to the only USB device')
@CommandArgument('--target', '-t',
default=None,
help='Install the given target platform')
def install(self, release=False, dev=False, android=False, target=None):
def install(self, release=False, dev=False, android=False, emulator=False, usb=False, target=None):
env = self.build_env()
if target and android:
print("Please specify either --target or --android.")
@@ -416,7 +422,15 @@ def install(self, release=False, dev=False, android=False, target=None):

if android:
pkg_path = binary_path + ".apk"
exec_command = [self.android_adb_path(env), "install", "-r", pkg_path]
exec_command = [self.android_adb_path(env)]
if emulator and usb:
print("Cannot install to both emulator and USB at the same time.")
return 1
if emulator:
exec_command += ["-e"]

This comment has been minimized.

Copy link
@paulrouget

paulrouget Jun 28, 2018

Contributor

Do we need a helper to start the emulator? We installed it. We push and run to the emulator. Maybe we should also start it somehow? Or test if it's running, and if not, check if the toolchain is present and print the command to run it.

This comment has been minimized.

Copy link
@SimonSapin

SimonSapin Jun 28, 2018

Author Member

Added ./mach android-emulator which just forwards arguments to the emulator script. I’ll look later into having ./mach run start it automatically.

This comment has been minimized.

Copy link
@SimonSapin

SimonSapin Jun 28, 2018

Author Member

One difficulty is that after starting the emulator and after adb wait-for-device returns (which shows that adb shell is responsive), the userland might not have finished booting yet. In particular, there is an indeterminate amount of time until the Activity Manager is running and the am start command would success. This time can be especially long for emulator ARM. (Or x86 without working CPU acceleration.)

if usb:
exec_command += ["-d"]
exec_command += ["install", "-r", pkg_path]
elif is_windows():
pkg_path = path.join(path.dirname(binary_path), 'msi', 'Servo.msi')
exec_command = ["msiexec", "/i", pkg_path]
@@ -45,6 +45,12 @@ class PostBuildCommands(CommandBase):
help='Run the dev build')
@CommandArgument('--android', action='store_true', default=None,
help='Run on an Android device through `adb shell`')
@CommandArgument('--emulator',
action='store_true',
help='For Android, run in the only emulated device')
@CommandArgument('--usb',
action='store_true',
help='For Android, run in the only USB device')
@CommandArgument('--debug', action='store_true',
help='Enable the debugger. Not specifying a '
'--debugger option will result in the default '
@@ -64,7 +70,7 @@ class PostBuildCommands(CommandBase):
'params', nargs='...',
help="Command-line arguments to be passed through to Servo")
def run(self, params, release=False, dev=False, android=None, debug=False, debugger=None,
headless=False, software=False, bin=None, nightly=None):
headless=False, software=False, bin=None, emulator=False, usb=False, nightly=None):
env = self.build_env()
env["RUST_BACKTRACE"] = "1"

@@ -91,9 +97,19 @@ def run(self, params, release=False, dev=False, android=None, debug=False, debug
]
script += [
"am start com.mozilla.servo/com.mozilla.servo.MainActivity",
"sleep 0.5",
"echo Servo PID: $(pidof com.mozilla.servo)",
"exit"
]
shell = subprocess.Popen([self.android_adb_path(env), "shell"], stdin=subprocess.PIPE)
args = [self.android_adb_path(env)]
if emulator and usb:
print("Cannot run in both emulator and USB at the same time.")
return 1
if emulator:
args += ["-e"]
if usb:
args += ["-d"]
shell = subprocess.Popen(args + ["shell"], stdin=subprocess.PIPE)
shell.communicate("\n".join(script) + "\n")
return shell.wait()

@@ -153,6 +169,18 @@ def run(self, params, release=False, dev=False, android=None, debug=False, debug
else:
raise e

@Command('android-emulator',
description='Run the Android emulator',
category='post-build')
@CommandArgument(
'args', nargs='...',
help="Command-line arguments to be passed through to the emulator")
def android_emulator(self, args=None):
if not args:
print("AVDs created by `./mach bootstrap-android` are servo-arm and servo-x86.")
emulator = self.android_emulator_path(self.build_env())
return subprocess.call([emulator] + args)

@Command('rr-record',
description='Run Servo whilst recording execution with rr',
category='post-build')
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.