GN: Side-load dex files as well as native code in incremental installs

This also re-organizes a bit by moving all incremental install related
files into //build/android/incremental_install.

BUG=520082

Review URL: https://codereview.chromium.org/1338813003

Cr-Original-Commit-Position: refs/heads/master@{#349200}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: 933f2593199c4f95678ddda884aa476ff1fe0c23
This commit is contained in:
agrieve 2015-09-16 12:56:47 -07:00 коммит произвёл Commit bot
Родитель be7248bb97
Коммит 6457f677cf
16 изменённых файлов: 1097 добавлений и 120 удалений

Просмотреть файл

@ -16,16 +16,22 @@ def CommonChecks(input_api, output_api):
"""Returns a path relative to presubmit directory."""
return input_api.os_path.join(input_api.PresubmitLocalPath(), *dirs)
build_pys = [
r'gyp/.*\.py$',
r'gn/.*\.py',
r'incremental_install/.*\.py',
]
output.extend(input_api.canned_checks.RunPylint(
input_api,
output_api,
pylintrc='pylintrc',
black_list=[r'pylib/symbols/.*\.py$', r'gyp/.*\.py$', r'gn/.*\.py'],
# symbols has its own PRESUBMIT.py
black_list=build_pys + [r'pylib/symbols/.*\.py$'],
extra_paths_list=[J(), J('buildbot')]))
output.extend(input_api.canned_checks.RunPylint(
input_api,
output_api,
white_list=[r'gyp/.*\.py$', r'gn/.*\.py'],
white_list=build_pys,
extra_paths_list=[J('gyp'), J('gn')]))
# Disabled due to http://crbug.com/410936

Просмотреть файл

@ -13,7 +13,7 @@ from pylib import constants
_AAPT_PATH = os.path.join(constants.ANDROID_SDK_TOOLS, 'aapt')
_MANIFEST_ATTRIBUTE_RE = re.compile(
r'\s*A: ([^\(\)= ]*)\([^\(\)= ]*\)="(.*)" \(Raw: .*\)$')
r'\s*A: ([^\(\)= ]*)\([^\(\)= ]*\)=(?:"(.*)" \(Raw: .*\)|\(type.*?\)(.*))$')
_MANIFEST_ELEMENT_RE = re.compile(r'\s*(?:E|N): (\S*) .*$')
_PACKAGE_NAME_RE = re.compile(r'package: .*name=\'(\S*)\'')
_SPLIT_NAME_RE = re.compile(r'package: .*split=\'(\S*)\'')
@ -60,7 +60,7 @@ def _ParseManifestFromApk(apk_path):
if m:
if not m.group(1) in node:
node[m.group(1)] = []
node[m.group(1)].append(m.group(2))
node[m.group(1)].append(m.group(2) or m.group(3))
continue
return parsed_manifest
@ -72,6 +72,7 @@ class ApkHelper(object):
self._manifest = None
self._package_name = None
self._split_name = None
self._has_isolated_processes = None
def GetActivityName(self):
"""Returns the name of the Activity in the apk."""
@ -130,6 +131,18 @@ class ApkHelper(object):
return self._split_name
return None
def HasIsolatedProcesses(self):
"""Returns whether any services exist that use isolatedProcess=true."""
if self._has_isolated_processes is None:
manifest_info = self._GetManifest()
try:
services = manifest_info['manifest']['application']['service']
self._has_isolated_processes = (
any(int(v, 0) for v in services['android:isolatedProcess']))
except KeyError:
self._has_isolated_processes = False
return self._has_isolated_processes
def _GetManifest(self):
if not self._manifest:
self._manifest = _ParseManifestFromApk(self._apk_path)

Просмотреть файл

@ -627,6 +627,8 @@ class DeviceUtils(object):
retries=None):
"""Remove the app |package_name| from the device.
This is a no-op if the app is not already installed.
Args:
package_name: The package to uninstall.
keep_data: (optional) Whether to keep the data and cache directories.
@ -638,6 +640,9 @@ class DeviceUtils(object):
CommandTimeoutError if the uninstallation times out.
DeviceUnreachableError on missing device.
"""
installed = self._GetApplicationPathsInternal(package_name)
if not installed:
return
try:
self.adb.Uninstall(package_name, keep_data)
self._cache['package_apk_paths'][package_name] = []

Просмотреть файл

@ -605,7 +605,7 @@ class DeviceUtilsInstallTest(DeviceUtilsTest):
'test.package'),
(self.call.device._GetApplicationPathsInternal('test.package'),
['/fake/data/app/test.package.apk']),
self.call.adb.Uninstall('test.package', False),
self.call.device.Uninstall('test.package'),
self.call.adb.Install('/fake/test/app.apk', reinstall=False)):
self.device.Install('/fake/test/app.apk', retries=0, permissions=[])
@ -690,12 +690,18 @@ class DeviceUtilsInstallSplitApkTest(DeviceUtilsTest):
class DeviceUtilsUninstallTest(DeviceUtilsTest):
def testUninstall_callsThrough(self):
with self.assertCalls(
(self.call.device._GetApplicationPathsInternal('test.package'),
['/path.apk']),
self.call.adb.Uninstall('test.package', True)):
self.device.Uninstall('test.package', True)
def testUninstall_noop(self):
with self.assertCalls(
(self.call.device._GetApplicationPathsInternal('test.package'), [])):
self.device.Uninstall('test.package', True)
class DeviceUtilsSuTest(DeviceUtilsTest):
def testSu_preM(self):

Просмотреть файл

@ -0,0 +1,16 @@
# Copyright 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import("//build/config/android/rules.gni")
android_library("bootstrap_java") {
# Use .dex rather than .dex.jar to be usable by package_apk().
dex_path = "$target_gen_dir/bootstrap.dex"
java_files = [
"java/org/chromium/incrementalinstall/BootstrapApplication.java",
"java/org/chromium/incrementalinstall/ClassLoaderPatcher.java",
"java/org/chromium/incrementalinstall/LockFile.java",
"java/org/chromium/incrementalinstall/Reflect.java",
]
}

Просмотреть файл

@ -4,23 +4,25 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Creates a script to run a "_incremental" .apk."""
"""Creates a script to run an "_incremental" .apk."""
import argparse
import os
import pprint
import sys
sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir))
sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, 'gyp'))
from util import build_utils
from pylib import constants
from util import build_utils
SCRIPT_TEMPLATE = """\
#!/usr/bin/env python
#
# This file was generated by:
# //build/android/gyp/create_incremental_install_script.py
# //build/android/incremental_install/create_install_script.py
import os
import subprocess
@ -47,7 +49,7 @@ if __name__ == '__main__':
"""
def main(args):
def _ParseArgs(args):
args = build_utils.ExpandFileArgs(args)
parser = argparse.ArgumentParser()
build_utils.AddDepfileOption(parser)
@ -68,31 +70,49 @@ def main(args):
'Can be specified multiple times.')
parser.add_argument('--lib-dir',
help='Path to native libraries directory.')
parser.add_argument('--dex-file',
action='append',
default=[],
dest='dex_files',
help='List of dex files to include.')
parser.add_argument('--dex-file-list',
help='GYP-list of dex files.')
options = parser.parse_args(args)
options.dex_files += build_utils.ParseGypList(options.dex_file_list)
return options
def main(args):
options = _ParseArgs(args)
def relativize(path):
return os.path.relpath(path, os.path.dirname(options.script_output_path))
incremental_install_path = os.path.join(constants.DIR_SOURCE_ROOT, 'build',
'android', 'incremental_install.py')
incremental_install_path = relativize(incremental_install_path)
installer_path = os.path.join(constants.DIR_SOURCE_ROOT, 'build', 'android',
'incremental_install', 'installer.py')
installer_path = relativize(installer_path)
incremental_install_path_args = [
path_args = [
('--output-directory', relativize(options.output_directory)),
(None, relativize(options.apk_path)),
]
if options.lib_dir:
incremental_install_path_args.append(
('--lib-dir', relativize(options.lib_dir)))
path_args.append(('--lib-dir', relativize(options.lib_dir)))
if options.dex_files:
for dex_file in options.dex_files:
path_args.append(('--dex-file', relativize(dex_file)))
for split_arg in options.splits:
incremental_install_path_args.append(('--split', relativize(split_arg)))
path_args.append(('--split', relativize(split_arg)))
with open(options.script_output_path, 'w') as script:
script.write(SCRIPT_TEMPLATE.format(
cmd_path=repr(incremental_install_path),
cmd_path=pprint.pformat(installer_path),
cmd_args='[]',
cmd_path_args=repr(incremental_install_path_args)))
cmd_path_args=pprint.pformat(path_args)))
os.chmod(options.script_output_path, 0750)

Просмотреть файл

@ -0,0 +1,96 @@
#!/usr/bin/env python
#
# Copyright 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Creates an AndroidManifest.xml for an incremental APK.
Given the manifest file for the real APK, generates an AndroidManifest.xml with
the application class changed to IncrementalApplication.
"""
import argparse
import os
import sys
from xml.etree import ElementTree
sys.path.append(os.path.join(os.path.dirname(__file__), os.path.pardir, 'gyp'))
from util import build_utils
_ANDROID_NAMESPACE = 'http://schemas.android.com/apk/res/android'
ElementTree.register_namespace('android', _ANDROID_NAMESPACE)
_INCREMENTAL_APP_NAME = 'org.chromium.incrementalinstall.BootstrapApplication'
_META_DATA_NAME = 'incremental-install-real-app'
def _AddNamespace(name):
"""Adds the android namespace prefix to the given identifier."""
return '{%s}%s' % (_ANDROID_NAMESPACE, name)
def _ParseArgs():
parser = argparse.ArgumentParser()
build_utils.AddDepfileOption(parser)
parser.add_argument('--src-manifest',
help='The main manifest of the app',
required=True)
parser.add_argument('--out-manifest',
help='The output manifest',
required=True)
parser.add_argument('--disable-isolated-processes',
help='Changes all android:isolatedProcess to false. '
'This is required on Android M+',
action='store_true')
return parser.parse_args()
def _ProcessManifest(main_manifest, main_manifest_path,
disable_isolated_processes):
"""Returns a transformed AndroidManifest.xml for use with _incremental apks.
Args:
main_manifest: Manifest contents to transform.
main_manifest_path: Path to main_manifest (used for error messages).
disable_isolated_processes: Whether to set all isolatedProcess attributes to
false
Returns:
The transformed AndroidManifest.xml.
"""
if disable_isolated_processes:
main_manifest = main_manifest.replace('isolatedProcess="true"',
'isolatedProcess="false"')
doc = ElementTree.fromstring(main_manifest)
app_node = doc.find('application')
if app_node is None:
raise Exception('Could not find <application> in %s' % main_manifest_path)
real_app_class = app_node.get(_AddNamespace('name'))
if real_app_class is None:
raise Exception('Could not find android:name in <application> in %s' %
main_manifest_path)
app_node.set(_AddNamespace('name'), _INCREMENTAL_APP_NAME)
meta_data_node = ElementTree.SubElement(app_node, 'meta-data')
meta_data_node.set(_AddNamespace('name'), _META_DATA_NAME)
meta_data_node.set(_AddNamespace('value'), real_app_class)
return ElementTree.tostring(doc, encoding='UTF-8')
def main():
options = _ParseArgs()
with open(options.src_manifest) as f:
main_manifest_data = f.read()
new_manifest_data = _ProcessManifest(main_manifest_data, options.src_manifest,
options.disable_isolated_processes)
with open(options.out_manifest, 'w') as f:
f.write(new_manifest_data)
if options.depfile:
build_utils.WriteDepfile(
options.depfile,
[options.src_manifest] + build_utils.GetPythonDependencies())
if __name__ == '__main__':
main()

Просмотреть файл

@ -9,20 +9,32 @@
import argparse
import glob
import logging
import os
import posixpath
import shutil
import sys
import time
sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir))
from devil.android import apk_helper
from devil.android import device_utils
from devil.android import device_errors
from devil.android.sdk import version_codes
from devil.utils import reraiser_thread
from pylib import constants
from pylib.utils import run_tests_helper
from pylib.utils import time_profile
sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, 'gyp'))
from util import build_utils
def _TransformDexPaths(paths):
"""Given paths like ["/a/b/c", "/a/c/d"], returns ["b.c", "c.d"]."""
prefix_len = len(os.path.commonprefix(paths))
return [p[prefix_len:].replace(os.sep, '.') for p in paths]
def main():
start_time = time.time()
parser = argparse.ArgumentParser()
parser.add_argument('apk_path',
help='The path to the APK to install.')
@ -33,6 +45,10 @@ def main():
'Can be specified multiple times.')
parser.add_argument('--lib-dir',
help='Path to native libraries directory.')
parser.add_argument('--dex-files',
help='List of dex files to push.',
action='append',
default=[])
parser.add_argument('-d', '--device', dest='device',
help='Target device for apk to install on.')
parser.add_argument('--uninstall',
@ -54,12 +70,16 @@ def main():
args = parser.parse_args()
logging.basicConfig(format='%(asctime)s (%(thread)d) %(message)s')
run_tests_helper.SetLogLevel(args.verbose_count)
constants.SetBuildType('Debug')
if args.output_directory:
constants.SetOutputDirectory(args.output_directory)
main_timer = time_profile.TimeProfile()
install_timer = time_profile.TimeProfile()
push_native_timer = time_profile.TimeProfile()
push_dex_timer = time_profile.TimeProfile()
if args.device:
# Retries are annoying when commands fail for legitimate reasons. Might want
# to enable them if this is ever used on bots though.
@ -80,19 +100,26 @@ def main():
msg += ' %s (%s)\n' % (d, desc)
raise Exception(msg)
apk_package = apk_helper.ApkHelper(args.apk_path).GetPackageName()
apk_help = apk_helper.ApkHelper(args.apk_path)
apk_package = apk_help.GetPackageName()
device_incremental_dir = '/data/local/tmp/incremental-app-%s' % apk_package
if args.uninstall:
logging.info('Uninstalling .apk')
device.Uninstall(apk_package)
logging.info('Removing side-loaded files')
device.RunShellCommand(['rm', '-rf', device_incremental_dir],
check_return=True)
logging.info('Uninstall took %s seconds.', main_timer.GetDelta())
return
if device.build_version_sdk >= version_codes.MARSHMALLOW:
if apk_help.HasIsolatedProcesses():
raise Exception('Cannot use perform incremental installs on Android M+ '
'without first disabling isolated processes. Use GN arg: '
'disable_incremental_isolated_processes=true to do so.')
# Install .apk(s) if any of them have changed.
def do_install():
install_timer.Start()
if args.splits:
splits = []
for split_glob in args.splits:
@ -101,25 +128,60 @@ def main():
allow_cached_props=True)
else:
device.Install(args.apk_path, reinstall=True)
logging.info('Finished installing .apk')
install_timer.Stop(log=False)
# Push .so files to the device (if they have changed).
def do_push_libs():
# Push .so and .dex files to the device (if they have changed).
def do_push_files():
if args.lib_dir:
push_native_timer.Start()
device_lib_dir = posixpath.join(device_incremental_dir, 'lib')
device.PushChangedFiles([(args.lib_dir, device_lib_dir)],
delete_device_stale=True)
logging.info('Finished pushing native libs')
push_native_timer.Stop(log=False)
if args.dex_files:
push_dex_timer.Start()
# Put all .dex files to be pushed into a temporary directory so that we
# can use delete_device_stale=True.
with build_utils.TempDir() as temp_dir:
device_dex_dir = posixpath.join(device_incremental_dir, 'dex')
# Ensure no two files have the same name.
transformed_names = _TransformDexPaths(args.dex_files)
for src_path, dest_name in zip(args.dex_files, transformed_names):
shutil.copyfile(src_path, os.path.join(temp_dir, dest_name))
device.PushChangedFiles([(temp_dir, device_dex_dir)],
delete_device_stale=True)
push_dex_timer.Stop(log=False)
# Create 2 lock files:
# * install.lock tells the app to pause on start-up (until we release it).
# * firstrun.lock is used by the app to pause all secondary processes until
# the primary process finishes loading the .dex / .so files.
def create_lock_files():
# Creates or zeros out lock files.
cmd = ('D="%s";'
'mkdir -p $D &&'
'echo -n >$D/install.lock 2>$D/firstrun.lock')
device.RunShellCommand(cmd % device_incremental_dir, check_return=True)
# The firstrun.lock is released by the app itself.
def release_installer_lock():
device.RunShellCommand('echo > %s/install.lock' % device_incremental_dir,
check_return=True)
create_lock_files()
# Concurrency here speeds things up quite a bit, but DeviceUtils hasn't
# been designed for multi-threading. Enabling only because this is a
# developer-only tool.
if args.no_threading:
do_install()
do_push_libs()
do_push_files()
else:
reraiser_thread.RunAsync((do_install, do_push_libs))
logging.info('Took %s seconds', round(time.time() - start_time, 1))
reraiser_thread.RunAsync((do_install, do_push_files))
release_installer_lock()
logging.info('Took %s seconds (install=%s, libs=%s, dex=%s)',
main_timer.GetDelta(), install_timer.GetDelta(),
push_native_timer.GetDelta(), push_dex_timer.GetDelta())
if __name__ == '__main__':

Просмотреть файл

@ -0,0 +1,167 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.incrementalinstall;
import android.app.Application;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.util.Log;
import java.io.File;
import java.lang.ref.WeakReference;
import java.util.List;
import java.util.Map;
/**
* An Application that replaces itself with another Application (as defined in
* the "incremental-install-real-app" meta-data tag within the
* AndroidManifest.xml). It loads the other application only after side-loading
* its .so and .dex files from /data/local/tmp.
*/
public final class BootstrapApplication extends Application {
private static final String TAG = "cr.incrementalinstall";
private static final String MANAGED_DIR_PREFIX = "/data/local/tmp/incremental-app-";
private static final String REAL_APP_META_DATA_NAME = "incremental-install-real-app";
private ClassLoaderPatcher mClassLoaderPatcher;
private Application mRealApplication;
private Object mStashedProviderList;
private Object mActivityThread;
@Override
protected void attachBaseContext(Context context) {
super.attachBaseContext(context);
File incrementalRootDir = new File(MANAGED_DIR_PREFIX + context.getPackageName());
File libDir = new File(incrementalRootDir, "lib");
File dexDir = new File(incrementalRootDir, "dex");
File installLockFile = new File(incrementalRootDir, "install.lock");
File firstRunLockFile = new File(incrementalRootDir, "firstrun.lock");
try {
mActivityThread = Reflect.invokeMethod(Class.forName("android.app.ActivityThread"),
"currentActivityThread");
mClassLoaderPatcher = new ClassLoaderPatcher(context);
boolean isFirstRun = LockFile.installerLockExists(firstRunLockFile);
if (isFirstRun) {
if (mClassLoaderPatcher.mIsPrimaryProcess) {
// Wait for incremental_install.py to finish.
LockFile.waitForInstallerLock(installLockFile, 20 * 1000);
} else {
// Wait for the browser process to create the optimized dex files
// (and for M+, copy the library files).
LockFile.waitForInstallerLock(firstRunLockFile, 30 * 1000);
}
}
mClassLoaderPatcher.importNativeLibs(libDir);
mClassLoaderPatcher.loadDexFiles(dexDir);
if (isFirstRun && mClassLoaderPatcher.mIsPrimaryProcess) {
LockFile.clearInstallerLock(firstRunLockFile);
}
// attachBaseContext() is called from ActivityThread#handleBindApplication() and
// Application#mApplication is changed right after we return. Thus, we cannot swap
// the Application instances until onCreate() is called.
String realApplicationName = getRealApplicationName();
Log.i(TAG, "Instantiating " + realApplicationName);
mRealApplication =
(Application) Reflect.newInstance(Class.forName(realApplicationName));
Reflect.invokeMethod(mRealApplication, "attachBaseContext", context);
// Between attachBaseContext() and onCreate(), ActivityThread tries to instantiate
// all ContentProviders. The ContentProviders break without the correct Application
// class being installed, so temporarily pretend there are no providers, and then
// instantiate them explicitly within onCreate().
disableContentProviders();
Log.i(TAG, "Waiting for onCreate");
} catch (Exception e) {
throw new RuntimeException("Incremental install failed.", e);
}
}
@Override
public void onCreate() {
super.onCreate();
try {
Log.i(TAG, "onCreate() called. Swapping Application references");
swapApplicationReferences();
enableContentProviders();
Log.i(TAG, "Calling onCreate");
mRealApplication.onCreate();
} catch (Exception e) {
throw new RuntimeException("Incremental install failed.", e);
}
}
/**
* Returns the class name of the real Application class (recorded in the
* AndroidManifest.xml)
*/
private String getRealApplicationName() throws NameNotFoundException {
ApplicationInfo appInfo = getPackageManager().getApplicationInfo(getPackageName(),
PackageManager.GET_META_DATA);
return appInfo.metaData.getString(REAL_APP_META_DATA_NAME);
}
/**
* Nulls out ActivityThread.mBoundApplication.providers.
*/
private void disableContentProviders() throws ReflectiveOperationException {
Object data = Reflect.getField(mActivityThread, "mBoundApplication");
mStashedProviderList = Reflect.getField(data, "providers");
Reflect.setField(data, "providers", null);
}
/**
* Restores the value of ActivityThread.mBoundApplication.providers, and invokes
* ActivityThread#installContentProviders().
*/
private void enableContentProviders() throws ReflectiveOperationException {
Object data = Reflect.getField(mActivityThread, "mBoundApplication");
Reflect.setField(data, "providers", mStashedProviderList);
if (mStashedProviderList != null && mClassLoaderPatcher.mIsPrimaryProcess) {
Log.i(TAG, "Instantiating content providers");
Reflect.invokeMethod(mActivityThread, "installContentProviders", mRealApplication,
mStashedProviderList);
}
mStashedProviderList = null;
}
/**
* Changes all fields within framework classes that have stored an reference to this
* BootstrapApplication to instead store references to mRealApplication.
* @throws NoSuchFieldException
*/
@SuppressWarnings("unchecked")
private void swapApplicationReferences() throws ReflectiveOperationException {
if (Reflect.getField(mActivityThread, "mInitialApplication") == this) {
Reflect.setField(mActivityThread, "mInitialApplication", mRealApplication);
}
List<Application> allApplications =
(List<Application>) Reflect.getField(mActivityThread, "mAllApplications");
for (int i = 0; i < allApplications.size(); i++) {
if (allApplications.get(i) == this) {
allApplications.set(i, mRealApplication);
}
}
for (String fieldName : new String[] { "mPackages", "mResourcePackages" }) {
Map<String, WeakReference<?>> packageMap =
(Map<String, WeakReference<?>>) Reflect.getField(mActivityThread, fieldName);
for (Map.Entry<String, WeakReference<?>> entry : packageMap.entrySet()) {
Object loadedApk = entry.getValue().get();
if (loadedApk != null && Reflect.getField(loadedApk, "mApplication") == this) {
Reflect.setField(loadedApk, "mApplication", mRealApplication);
Reflect.setField(mRealApplication, "mLoadedApk", loadedApk);
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,232 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.incrementalinstall;
import android.content.Context;
import android.os.Build;
import android.util.Log;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
/**
* Provides the ability to add native libraries and .dex files to an existing class loader.
* Tested with Jellybean MR2 - Marshmellow.
*/
final class ClassLoaderPatcher {
private static final String TAG = "cr.incrementalinstall";
private final File mAppFilesSubDir;
private final ClassLoader mClassLoader;
private final Object mLibcoreOs;
private final int mProcessUid;
final boolean mIsPrimaryProcess;
ClassLoaderPatcher(Context context) throws ReflectiveOperationException {
mAppFilesSubDir =
new File(context.getApplicationInfo().dataDir, "incremental-install-files");
mClassLoader = context.getClassLoader();
mLibcoreOs = Reflect.getField(Class.forName("libcore.io.Libcore"), "os");
mProcessUid = (Integer) Reflect.invokeMethod(mLibcoreOs, "getuid");
mIsPrimaryProcess = context.getApplicationInfo().uid == mProcessUid;
Log.i(TAG, "uid=" + mProcessUid + " (isPrimary=" + mIsPrimaryProcess + ")");
}
/**
* Loads all dex files within |dexDir| into the app's ClassLoader.
*/
void loadDexFiles(File dexDir) throws ReflectiveOperationException, FileNotFoundException {
Log.i(TAG, "Installing dex files from: " + dexDir);
File[] dexFilesArr = dexDir.listFiles();
if (dexFilesArr == null) {
throw new FileNotFoundException("Dex dir does not exist: " + dexDir);
}
// The optimized dex files will be owned by this process' user.
// Store them within the app's data dir rather than on /data/local/tmp
// so that they are still deleted (by the OS) when we uninstall
// (even on a non-rooted device).
File incrementalDexesDir = new File(mAppFilesSubDir, "optimized-dexes");
File isolatedDexesDir = new File(mAppFilesSubDir, "isolated-dexes");
File optimizedDir;
if (mIsPrimaryProcess) {
ensureAppFilesSubDirExists();
// Allows isolated processes to access the same files.
incrementalDexesDir.mkdir();
incrementalDexesDir.setReadable(true, false);
incrementalDexesDir.setExecutable(true, false);
// Create a directory for isolated processes to create directories in.
isolatedDexesDir.mkdir();
isolatedDexesDir.setWritable(true, false);
isolatedDexesDir.setExecutable(true, false);
optimizedDir = incrementalDexesDir;
} else {
// There is a UID check of the directory in dalvik.system.DexFile():
// https://android.googlesource.com/platform/libcore/+/45e0260/dalvik/src/main/java/dalvik/system/DexFile.java#101
// Rather than have each isolated process run DexOpt though, we use
// symlinks within the directory to point at the browser process'
// optimized dex files.
optimizedDir = new File(isolatedDexesDir, "isolated-" + mProcessUid);
optimizedDir.mkdir();
// Always wipe it out and re-create for simplicity.
Log.i(TAG, "Creating dex file symlinks for isolated process");
for (File f : optimizedDir.listFiles()) {
f.delete();
}
for (File f : incrementalDexesDir.listFiles()) {
String to = "../../" + incrementalDexesDir.getName() + "/" + f.getName();
File from = new File(optimizedDir, f.getName());
createSymlink(to, from);
}
}
Log.i(TAG, "Code cache dir: " + optimizedDir);
// TODO(agrieve): Might need to record classpath ordering if we ever have duplicate
// class names (since then order will matter here).
Log.i(TAG, "Loading " + dexFilesArr.length + " dex files");
Object dexPathList = Reflect.getField(mClassLoader, "pathList");
Object[] dexElements = (Object[]) Reflect.getField(dexPathList, "dexElements");
Object[] additionalElements = makeDexElements(dexFilesArr, optimizedDir);
Reflect.setField(
dexPathList, "dexElements", Reflect.concatArrays(dexElements, additionalElements));
}
/**
* Sets up all libraries within |libDir| to be loadable by System.loadLibrary().
*/
void importNativeLibs(File libDir) throws ReflectiveOperationException, IOException {
Log.i(TAG, "Importing native libraries from: " + libDir);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
libDir = prepareNativeLibsAndroidM(libDir);
}
addNativeLibrarySearchPath(libDir);
}
/**
* Primary process: Copies native libraries into the app's data directory
* Other processes: Waits for primary process to finish copying.
*/
private File prepareNativeLibsAndroidM(File libDir) throws IOException {
File localLibsDir = new File(mAppFilesSubDir, "lib");
File copyLibsLockFile = new File(mAppFilesSubDir, "libcopy.lock");
// Due to a new SELinux policy, all libs must be copied into the app's
// data directory first.
// https://code.google.com/p/android/issues/detail?id=79480
if (mIsPrimaryProcess) {
LockFile lockFile = LockFile.acquireRuntimeLock(copyLibsLockFile);
if (lockFile == null) {
LockFile.waitForRuntimeLock(copyLibsLockFile, 10 * 1000);
} else {
try {
ensureAppFilesSubDirExists();
localLibsDir.mkdir();
localLibsDir.setReadable(true, false);
localLibsDir.setExecutable(true, false);
copyChangedFiles(libDir, localLibsDir);
} finally {
lockFile.release();
}
}
} else {
// TODO: Work around this issue by using APK splits to install each dex / lib.
throw new RuntimeException("Incremental install does not work on Android M+ "
+ "with isolated processes. Use the gn arg:\n"
+ " disable_incremental_isolated_processes=true\n"
+ "and try again.");
}
return localLibsDir;
}
@SuppressWarnings("unchecked")
private void addNativeLibrarySearchPath(File nativeLibDir) throws ReflectiveOperationException {
Object dexPathList = Reflect.getField(mClassLoader, "pathList");
Object currentDirs = Reflect.getField(dexPathList, "nativeLibraryDirectories");
File[] newDirs = new File[] { nativeLibDir };
// Switched from an array to an ArrayList in Lollipop.
if (currentDirs instanceof List) {
List<File> dirsAsList = (List<File>) currentDirs;
dirsAsList.add(nativeLibDir);
} else {
File[] dirsAsArray = (File[]) currentDirs;
Reflect.setField(dexPathList, "nativeLibraryDirectories",
Reflect.concatArrays(dirsAsArray, newDirs));
}
Object[] nativeLibraryPathElements;
try {
nativeLibraryPathElements =
(Object[]) Reflect.getField(dexPathList, "nativeLibraryPathElements");
} catch (NoSuchFieldException e) {
// This field doesn't exist pre-M.
return;
}
Object[] additionalElements = makeNativePathElements(newDirs);
Reflect.setField(
dexPathList, "nativeLibraryPathElements",
Reflect.concatArrays(nativeLibraryPathElements, additionalElements));
}
private static void copyChangedFiles(File srcDir, File dstDir) throws IOException {
// No need to delete stale libs since libraries are loaded explicitly.
for (File f : srcDir.listFiles()) {
// Note: Tried using hardlinks, but resulted in EACCES exceptions.
File dest = new File(dstDir, f.getName());
copyIfModified(f, dest);
}
}
private static void copyIfModified(File src, File dest) throws IOException {
long lastModified = src.lastModified();
if (!dest.exists() || dest.lastModified() != lastModified) {
Log.i(TAG, "Copying " + src + " -> " + dest);
FileInputStream istream = new FileInputStream(src);
FileOutputStream ostream = new FileOutputStream(dest);
ostream.getChannel().transferFrom(istream.getChannel(), 0, istream.getChannel().size());
istream.close();
ostream.close();
dest.setReadable(true, false);
dest.setExecutable(true, false);
dest.setLastModified(lastModified);
}
}
private void ensureAppFilesSubDirExists() {
mAppFilesSubDir.mkdir();
mAppFilesSubDir.setExecutable(true, false);
}
private void createSymlink(String to, File from) throws ReflectiveOperationException {
Reflect.invokeMethod(mLibcoreOs, "symlink", to, from.getAbsolutePath());
}
private static Object[] makeNativePathElements(File[] paths)
throws ReflectiveOperationException {
Class<?> entryClazz = Class.forName("dalvik.system.DexPathList$Element");
Object[] entries = new Object[paths.length];
for (int i = 0; i < paths.length; ++i) {
entries[i] = Reflect.newInstance(entryClazz, paths[i], true, null, null);
}
return entries;
}
private static Object[] makeDexElements(File[] files, File optimizedDirectory)
throws ReflectiveOperationException {
Class<?> entryClazz = Class.forName("dalvik.system.DexPathList$Element");
Class<?> clazz = Class.forName("dalvik.system.DexPathList");
Object[] entries = new Object[files.length];
File emptyDir = new File("");
for (int i = 0; i < files.length; ++i) {
File file = files[i];
Object dexFile = Reflect.invokeMethod(clazz, "loadDexFile", file, optimizedDirectory);
entries[i] = Reflect.newInstance(entryClazz, emptyDir, false, file, dexFile);
}
return entries;
}
}

Просмотреть файл

@ -0,0 +1,128 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.incrementalinstall;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileLock;
import java.util.concurrent.Callable;
/**
* Helpers for dealing with .lock files used during install / first run.
*/
final class LockFile {
private static final String TAG = "cr.incrementalinstall";
private final File mFile;
private final FileOutputStream mOutputStream;
private final FileLock mFileLock;
private LockFile(File file, FileOutputStream outputStream, FileLock fileLock) {
mFile = file;
mOutputStream = outputStream;
mFileLock = fileLock;
}
/**
* Clears the lock file by writing to it (making it non-zero in length);
*/
static void clearInstallerLock(File lockFile) throws IOException {
Log.i(TAG, "Clearing " + lockFile);
// On Android M+, we can't delete files in /data/local/tmp, so we write to it instead.
FileOutputStream os = new FileOutputStream(lockFile);
os.write(1);
os.close();
}
/**
* Waits for the given file to be non-zero in length.
*/
static void waitForInstallerLock(final File file, long timeoutMs) {
pollingWait(new Callable<Boolean>() {
@Override public Boolean call() {
return !installerLockExists(file);
}
}, file, timeoutMs);
}
/**
* Waits for the given file to be non-zero in length.
*/
private static void pollingWait(Callable<Boolean> func, File file, long timeoutMs) {
long pollIntervalMs = 200;
for (int i = 0; i < timeoutMs / pollIntervalMs; i++) {
try {
if (func.call()) {
if (i > 0) {
Log.i(TAG, "Finished waiting on lock file: " + file);
}
return;
} else if (i == 0) {
Log.i(TAG, "Waiting on lock file: " + file);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
try {
Thread.sleep(pollIntervalMs);
} catch (InterruptedException e) {
// Should never happen.
}
}
throw new RuntimeException("Timed out waiting for lock file: " + file);
}
/**
* Returns whether the given lock file is missing or is in the locked state.
*/
static boolean installerLockExists(File file) {
return !file.exists() || file.length() == 0;
}
/**
* Attempts to acquire a lock for the given file.
* @return Returns the FileLock if it was acquired, or null otherwise.
*/
static LockFile acquireRuntimeLock(File file) {
try {
FileOutputStream outputStream = new FileOutputStream(file);
FileLock lock = outputStream.getChannel().tryLock();
if (lock != null) {
Log.i(TAG, "Created lock file: " + file);
return new LockFile(file, outputStream, lock);
}
outputStream.close();
} catch (IOException e) {
// Do nothing. We didn't get the lock.
}
return null;
}
/**
* Waits for the given file to not exist.
*/
static void waitForRuntimeLock(final File file, long timeoutMs) {
pollingWait(new Callable<Boolean>() {
@Override public Boolean call() {
return !file.exists();
}
}, file, timeoutMs);
}
/**
* Releases and deletes the lock file.
*/
void release() throws IOException {
Log.i(TAG, "Deleting lock file: " + mFile);
mFileLock.release();
mOutputStream.close();
if (!mFile.delete()) {
throw new IOException("Failed to delete lock file: " + mFile);
}
}
}

Просмотреть файл

@ -0,0 +1,142 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.incrementalinstall;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* Reflection helper methods.
*/
final class Reflect {
/**
* Sets the value of an object's field (even if it's not visible).
*
* @param instance The object containing the field to set.
* @param name The name of the field to set.
* @param value The new value for the field.
*/
static void setField(Object instance, String name, Object value)
throws ReflectiveOperationException {
Field field = findField(instance, name);
field.setAccessible(true);
field.set(instance, value);
}
/**
* Retrieves the value of an object's field (even if it's not visible).
*
* @param instance The object containing the field to set.
* @param name The name of the field to set.
* @return The field's value. Primitive values are returned as their boxed
* type.
*/
static Object getField(Object instance, String name) throws ReflectiveOperationException {
Field field = findField(instance, name);
field.setAccessible(true);
return field.get(instance);
}
/**
* Concatenates two arrays into a new array. The arrays must be of the same
* type.
*/
static Object[] concatArrays(Object[] left, Object[] right) {
Object[] result = (Object[]) Array.newInstance(
left.getClass().getComponentType(), left.length + right.length);
System.arraycopy(left, 0, result, 0, left.length);
System.arraycopy(right, 0, result, left.length, right.length);
return result;
}
/**
* Invokes a method with zero or more parameters. For static methods, use the Class as the
* instance.
*/
static Object invokeMethod(Object instance, String name, Object... params)
throws ReflectiveOperationException {
boolean isStatic = instance instanceof Class;
Class<?> clazz = isStatic ? (Class<?>) instance : instance.getClass();
Method method = findMethod(clazz, name, params);
method.setAccessible(true);
return method.invoke(instance, params);
}
/**
* Calls a constructor with zero or more parameters.
*/
static Object newInstance(Class<?> clazz, Object... params)
throws ReflectiveOperationException {
Constructor<?> constructor = findConstructor(clazz, params);
constructor.setAccessible(true);
return constructor.newInstance(params);
}
private static Field findField(Object instance, String name) throws NoSuchFieldException {
boolean isStatic = instance instanceof Class;
Class<?> clazz = isStatic ? (Class<?>) instance : instance.getClass();
for (; clazz != null; clazz = clazz.getSuperclass()) {
try {
return clazz.getDeclaredField(name);
} catch (NoSuchFieldException e) {
// Need to look in the super class.
}
}
throw new NoSuchFieldException("Field " + name + " not found in " + instance.getClass());
}
private static Method findMethod(Class<?> clazz, String name, Object... params)
throws NoSuchMethodException {
for (; clazz != null; clazz = clazz.getSuperclass()) {
for (Method method : clazz.getDeclaredMethods()) {
if (method.getName().equals(name)
&& areParametersCompatible(method.getParameterTypes(), params)) {
return method;
}
}
}
throw new NoSuchMethodException("Method " + name + " with parameters "
+ Arrays.asList(params) + " not found in " + clazz);
}
private static Constructor<?> findConstructor(Class<?> clazz, Object... params)
throws NoSuchMethodException {
for (Constructor<?> constructor : clazz.getDeclaredConstructors()) {
if (areParametersCompatible(constructor.getParameterTypes(), params)) {
return constructor;
}
}
throw new NoSuchMethodException("Constructor with parameters " + Arrays.asList(params)
+ " not found in " + clazz);
}
private static boolean areParametersCompatible(Class<?>[] paramTypes, Object... params) {
if (params.length != paramTypes.length) {
return false;
}
for (int i = 0; i < params.length; i++) {
if (!isAssignableFrom(paramTypes[i], params[i])) {
return false;
}
}
return true;
}
private static boolean isAssignableFrom(Class<?> left, Object right) {
if (right == null) {
return !left.isPrimitive();
}
Class<?> rightClazz = right.getClass();
if (left.isPrimitive()) {
// TODO(agrieve): Fill in the rest as needed.
return left == boolean.class && rightClazz == Boolean.class
|| left == int.class && rightClazz == Integer.class;
}
return left.isAssignableFrom(rightClazz);
}
}

Просмотреть файл

@ -9,18 +9,37 @@ import time
class TimeProfile(object):
"""Class for simple profiling of action, with logging of cost."""
def __init__(self, description):
def __init__(self, description='operation'):
self._starttime = None
self._endtime = None
self._description = description
self.Start()
def Start(self):
self._starttime = time.time()
self._endtime = None
def Stop(self):
"""Stop profiling and dump a log."""
if self._starttime:
stoptime = time.time()
logging.info('%fsec to perform %s',
stoptime - self._starttime, self._description)
self._starttime = None
def GetDelta(self):
"""Returns the rounded delta.
Also stops the timer if Stop() has not already been called.
"""
if self._endtime is None:
self.Stop(log=False)
delta = self._endtime - self._starttime
delta = round(delta, 2) if delta < 10 else round(delta, 1)
return delta
def LogResult(self):
"""Logs the result."""
logging.info('%s seconds to perform %s', self.GetDelta(), self._description)
def Stop(self, log=True):
"""Stop profiling.
Args:
log: Log the delta (defaults to true).
"""
self._endtime = time.time()
if log:
self.LogResult()

Просмотреть файл

@ -65,6 +65,10 @@ if (is_android) {
# Set to true to enable the Errorprone compiler
use_errorprone_java_compiler = false
# Disables process isolation when building _incremental targets.
# Required for Android M+ due to SELinux policies (stronger sandboxing).
disable_incremental_isolated_processes = false
}
# Host stuff -----------------------------------------------------------------

Просмотреть файл

@ -480,7 +480,6 @@ template("package_apk") {
forward_variables_from(invoker,
[
"deps",
"data_deps",
"public_deps",
"testonly",
])
@ -488,6 +487,7 @@ template("package_apk") {
_ant_script = "//build/android/ant/apk-package.xml"
depfile = "$target_gen_dir/$target_name.d"
data_deps = [ "//tools/android/md5sum" ] # Used when deploying APKs
inputs = [
invoker.resource_packaged_apk_path,
@ -616,15 +616,16 @@ template("create_apk") {
if (defined(invoker.resources_zip)) {
_resources_zip = invoker.resources_zip
assert(_resources_zip != "") # Mark as used.
}
if (defined(invoker.dex_path)) {
_dex_path = invoker.dex_path
}
_load_library_from_apk = invoker.load_library_from_apk
_package_deps = []
_deps = []
if (defined(invoker.deps)) {
_package_deps = invoker.deps
_deps = invoker.deps
}
_native_libs_dir = "//build/android/empty/res"
@ -634,19 +635,24 @@ template("create_apk") {
if (defined(invoker.asset_location)) {
_asset_location = invoker.asset_location
assert(_asset_location != "") # Mark as used.
}
_version_code = invoker.version_code
_version_name = invoker.version_name
assert(_version_code != -1) # Mark as used.
assert(_version_name != "") # Mark as used.
_base_apk_path = _base_path + ".apk_intermediates"
_resource_packaged_apk_path = _base_apk_path + ".ap_"
_incremental_resource_packaged_apk_path = _base_apk_path + "_incremental.ap_"
_packaged_apk_path = _base_apk_path + ".unfinished.apk"
_incremental_packaged_apk_path =
_base_apk_path + "_incremental.unfinished.apk"
_shared_resources =
defined(invoker.shared_resources) && invoker.shared_resources
assert(_shared_resources || true) # Mark as used.
_keystore_path = invoker.keystore_path
_keystore_name = invoker.keystore_name
@ -668,83 +674,129 @@ template("create_apk") {
_split_languages = invoker.language_splits
}
_package_resources_target_name = "${target_name}__package_resources"
action(_package_resources_target_name) {
deps = _package_deps
template("package_resources_helper") {
action(target_name) {
deps = invoker.deps
script = "//build/android/gyp/package_resources.py"
script = "//build/android/gyp/package_resources.py"
depfile = "${target_gen_dir}/${target_name}.d"
inputs = [
invoker.android_manifest,
]
if (defined(_resources_zip)) {
inputs += [ _resources_zip ]
}
outputs = [
depfile,
invoker.resource_packaged_apk_path,
]
args = [
"--depfile",
rebase_path(depfile, root_build_dir),
"--android-sdk",
rebased_android_sdk,
"--aapt-path",
android_aapt_path,
"--configuration-name=$android_configuration_name",
"--android-manifest",
rebase_path(invoker.android_manifest, root_build_dir),
"--version-code",
_version_code,
"--version-name",
_version_name,
"--apk-path",
rebase_path(invoker.resource_packaged_apk_path, root_build_dir),
]
if (defined(_asset_location)) {
args += [
"--asset-dir",
rebase_path(_asset_location, root_build_dir),
]
}
if (defined(_resources_zip)) {
args += [
"--resource-zips",
rebase_path(_resources_zip, root_build_dir),
]
}
if (_shared_resources) {
args += [ "--shared-resources" ]
}
if (_split_densities != []) {
args += [ "--create-density-splits" ]
foreach(_density, _split_densities) {
outputs += [ "${invoker.resource_packaged_apk_path}_${_density}" ]
}
}
if (_split_languages != []) {
args += [ "--language-splits=$_split_languages" ]
foreach(_language, _split_languages) {
outputs += [ "${invoker.resource_packaged_apk_path}_${_language}" ]
}
}
if (defined(invoker.extensions_to_not_compress)) {
args += [
"--no-compress",
invoker.extensions_to_not_compress,
]
}
}
}
_package_resources_target_name = "${target_name}__package_resources"
package_resources_helper(_package_resources_target_name) {
forward_variables_from(invoker, [ "extensions_to_not_compress" ])
deps = _deps
android_manifest = _android_manifest
resource_packaged_apk_path = _resource_packaged_apk_path
}
_generate_incremental_manifest_target_name =
"${target_name}_incremental_generate_manifest"
_incremental_android_manifest =
get_label_info(_generate_incremental_manifest_target_name,
"target_gen_dir") + "/AndroidManifest.xml"
action(_generate_incremental_manifest_target_name) {
deps = _deps
script = "//build/android/incremental_install/generate_android_manifest.py"
depfile = "${target_gen_dir}/${target_name}.d"
inputs = [
_android_manifest,
]
if (defined(_resources_zip)) {
inputs += [ _resources_zip ]
}
outputs = [
depfile,
_resource_packaged_apk_path,
_incremental_android_manifest,
]
_rebased_src_manifest = rebase_path(_android_manifest, root_build_dir)
_rebased_incremental_manifest =
rebase_path(_incremental_android_manifest, root_build_dir)
args = [
"--depfile",
rebase_path(depfile, root_build_dir),
"--android-sdk",
rebased_android_sdk,
"--aapt-path",
android_aapt_path,
"--configuration-name=$android_configuration_name",
"--android-manifest",
rebase_path(_android_manifest, root_build_dir),
"--version-code",
_version_code,
"--version-name",
_version_name,
"--apk-path",
rebase_path(_resource_packaged_apk_path, root_build_dir),
"--src-manifest=$_rebased_src_manifest",
"--out-manifest=$_rebased_incremental_manifest",
]
if (disable_incremental_isolated_processes) {
args += [ "--disable-isolated-processes" ]
}
}
if (defined(_asset_location)) {
args += [
"--asset-dir",
rebase_path(_asset_location, root_build_dir),
]
}
if (defined(_resources_zip)) {
args += [
"--resource-zips",
rebase_path(_resources_zip, root_build_dir),
]
}
if (_shared_resources) {
args += [ "--shared-resources" ]
}
if (_split_densities != []) {
args += [ "--create-density-splits" ]
foreach(_density, _split_densities) {
outputs += [ "${_resource_packaged_apk_path}_${_density}" ]
}
}
if (_split_languages != []) {
args += [ "--language-splits=$_split_languages" ]
foreach(_language, _split_languages) {
outputs += [ "${_resource_packaged_apk_path}_${_language}" ]
}
}
if (defined(invoker.extensions_to_not_compress)) {
args += [
"--no-compress",
invoker.extensions_to_not_compress,
]
}
_incremental_package_resources_target_name =
"${target_name}_incremental__package_resources"
# TODO(agrieve): See if we can speed up this step by swapping the manifest
# from the result of the main package_resources step.
package_resources_helper(_incremental_package_resources_target_name) {
forward_variables_from(invoker, [ "extensions_to_not_compress" ])
deps = _deps + [ ":$_generate_incremental_manifest_target_name" ]
android_manifest = _incremental_android_manifest
resource_packaged_apk_path = _incremental_resource_packaged_apk_path
}
package_target = "${target_name}__package"
package_apk(package_target) {
deps = []
forward_variables_from(invoker, [ "deps" ])
deps += [ ":${_package_resources_target_name}" ]
data_deps = [ "//tools/android/md5sum" ] # Used when deploying APKs
deps = _deps + [ ":${_package_resources_target_name}" ]
if (defined(_dex_path)) {
dex_path = _dex_path
@ -756,19 +808,21 @@ template("create_apk") {
_incremental_package_target = "${target_name}_incremental__package"
package_apk(_incremental_package_target) {
deps = []
forward_variables_from(invoker, [ "deps" ])
deps += [ ":${_package_resources_target_name}" ]
_dex_target = "//build/android/incremental_install:bootstrap_java__dex"
deps = _deps + [
":${_incremental_package_resources_target_name}",
_dex_target,
]
# TODO(agrieve): Multidex
if (defined(_dex_path)) {
dex_path = _dex_path
dex_path =
get_label_info(_dex_target, "target_gen_dir") + "/bootstrap.dex"
}
# TODO(agrieve): Add a placeholder .so for http://crbug.com/384638
native_libs_dir = "//build/android/empty/res"
output_apk_path = _incremental_packaged_apk_path
resource_packaged_apk_path = _resource_packaged_apk_path
resource_packaged_apk_path = _incremental_resource_packaged_apk_path
}
_finalize_apk_rule_name = "${target_name}__finalize"

Просмотреть файл

@ -1161,7 +1161,9 @@ template("android_apk") {
_build_config = "$target_gen_dir/$target_name.build_config"
resources_zip_path = "$base_path.resources.zip"
_all_resources_zip_path = "$base_path.resources.all.zip"
jar_path = "$base_path.jar"
_jar_path = "$base_path.jar"
_lib_dex_path = "$base_path.dex.jar"
_rebased_lib_dex_path = rebase_path(_lib_dex_path, root_build_dir)
_template_name = target_name
final_dex_path = "$gen_dir/classes.dex"
@ -1297,6 +1299,7 @@ template("android_apk") {
write_build_config(build_config_target) {
forward_variables_from(invoker, [ "apk_under_test" ])
type = "android_apk"
jar_path = _jar_path
dex_path = final_dex_path
resources_zip = resources_zip_path
build_config = _build_config
@ -1389,7 +1392,8 @@ template("android_apk") {
java_files = []
}
srcjar_deps = _srcjar_deps
dex_path = base_path + ".dex.jar"
jar_path = _jar_path
dex_path = _lib_dex_path
if (defined(invoker.deps)) {
deps += invoker.deps
@ -1421,8 +1425,8 @@ template("android_apk") {
rebase_path(_dist_jar_path, root_build_dir),
"--inputs=@FileArg($_rebased_build_config:dist_jar:dependency_jars)",
]
inputs += [ jar_path ]
_rebased_jar_path = rebase_path([ jar_path ], root_build_dir)
inputs += [ _jar_path ]
_rebased_jar_path = rebase_path([ _jar_path ], root_build_dir)
args += [ "--inputs=$_rebased_jar_path" ]
deps = [
":$build_config_target", # Generates the build config file.
@ -1436,15 +1440,15 @@ template("android_apk") {
":$build_config_target",
":$java_target",
]
sources = [
jar_path,
]
inputs = [
_build_config,
]
output = final_dex_path
dex_arg_key = "${_rebased_build_config}:final_dex:dependency_dex_files"
args = [ "--inputs=@FileArg($dex_arg_key)" ]
_dex_arg_key = "${_rebased_build_config}:final_dex:dependency_dex_files"
args = [
"--inputs=@FileArg($_dex_arg_key)",
_rebased_lib_dex_path,
]
}
if (_native_libs != []) {
@ -1598,7 +1602,7 @@ template("android_apk") {
_create_incremental_script_rule_name = "${_template_name}__incremental_script"
_incremental_final_deps += [ ":${_create_incremental_script_rule_name}" ]
action(_create_incremental_script_rule_name) {
script = "//build/android/gn/create_incremental_install_script.py"
script = "//build/android/incremental_install/create_install_script.py"
depfile = "$target_gen_dir/$target_name.d"
_generated_script_path =
@ -1613,9 +1617,12 @@ template("android_apk") {
_rebased_generated_script_path =
rebase_path(_generated_script_path, root_build_dir)
_rebased_depfile = rebase_path(depfile, root_build_dir)
_dex_arg_key = "${_rebased_build_config}:final_dex:dependency_dex_files"
args = [
"--apk-path=${_rebased_apk_path_no_ext}_incremental.apk",
"--script-output-path=$_rebased_generated_script_path",
"--dex-file=$_rebased_lib_dex_path",
"--dex-file-list=@FileArg($_dex_arg_key)",
"--depfile=$_rebased_depfile",
]
if (defined(_native_libs_dir)) {